Skip to content

Commit

Permalink
Install plugin by archive remote url or local path (stripe#937)
Browse files Browse the repository at this point in the history
* install plugin by archive remote url or local path

* do not duplicate unmanaged versions in config

* add more sanity check

* remove unused param
  • Loading branch information
etsai-stripe authored Aug 11, 2022
1 parent e3bd893 commit 04851e7
Show file tree
Hide file tree
Showing 3 changed files with 287 additions and 31 deletions.
62 changes: 51 additions & 11 deletions pkg/cmd/plugin/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ type InstallCmd struct {
cfg *config.Config
Cmd *cobra.Command
fs afero.Fs

archiveURL string
archivePath string
}

// NewInstallCmd creates a command for installing plugins
Expand All @@ -34,13 +37,16 @@ func NewInstallCmd(config *config.Config) *InstallCmd {

ic.Cmd = &cobra.Command{
Use: "install",
Args: validators.ExactArgs(1),
Args: validators.MaximumNArgs(1),
Short: "Install a Stripe CLI plugin",
Long: `Install a Stripe CLI plugin. To download a specific version, run stripe install [plugin_name]@[version].
By default, the most recent version will be installed.`,
RunE: ic.runInstallCmd,
}

ic.Cmd.Flags().StringVar(&ic.archiveURL, "archive-url", "", "Install a plugin by an archive URL")
ic.Cmd.Flags().StringVar(&ic.archivePath, "archive", "", "Install a plugin by an archive path")

return ic
}

Expand All @@ -58,15 +64,8 @@ func parseInstallArg(arg string) (string, string) {
return plugin, version
}

func (ic *InstallCmd) runInstallCmd(cmd *cobra.Command, args []string) error {
// Refresh the plugin before proceeding
err := plugins.RefreshPluginManifest(cmd.Context(), ic.cfg, ic.fs, stripe.DefaultAPIBaseURL)

if err != nil {
return err
}

pluginName, version := parseInstallArg(args[0])
func (ic *InstallCmd) installPluginByName(cmd *cobra.Command, arg string) error {
pluginName, version := parseInstallArg(arg)
plugin, err := plugins.LookUpPlugin(cmd.Context(), ic.cfg, ic.fs, pluginName)

if err != nil {
Expand All @@ -85,12 +84,53 @@ func (ic *InstallCmd) runInstallCmd(cmd *cobra.Command, args []string) error {

err = plugin.Install(ctx, ic.cfg, ic.fs, version, stripe.DefaultAPIBaseURL)

return err
}

func (ic *InstallCmd) installPluginByArchive(cmd *cobra.Command) error {
if ic.archiveURL == "" && ic.archivePath == "" {
return fmt.Errorf("please provide the plugin name or the archive URL/path to install")
}

if ic.archiveURL != "" {
err := plugins.FetchAndExtractRemoteArchive(cmd.Context(), ic.cfg, ic.archiveURL)
if err != nil {
return err
}
} else if ic.archivePath != "" {
err := plugins.ExtractLocalArchive(cmd.Context(), ic.cfg, ic.archivePath)
if err != nil {
return err
}
}

return nil
}

func (ic *InstallCmd) runInstallCmd(cmd *cobra.Command, args []string) error {
var err error

if len(args) == 0 {
err = ic.installPluginByArchive(cmd)
} else {
// Refresh the plugin before proceeding
err = plugins.RefreshPluginManifest(cmd.Context(), ic.cfg, ic.fs, stripe.DefaultAPIBaseURL)
if err != nil {
return err
}

err = ic.installPluginByName(cmd, args[0])
if err != nil {
return err
}
}

if err == nil {
color := ansi.Color(os.Stdout)
fmt.Println(color.Green("✔ installation complete."))
}

return err
return nil
}

func withSIGTERMCancel(ctx context.Context, onCancel func()) context.Context {
Expand Down
46 changes: 26 additions & 20 deletions pkg/plugins/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ var (

// Plugin contains the plugin properties
type Plugin struct {
Shortname string
Shortdesc string
Binary string
Shortname string `toml:"Shortname"`
Shortdesc string `toml:"Shortdesc"`
Binary string `toml:"Binary"`
Releases []Release `toml:"Release"`
MagicCookieValue string
MagicCookieValue string `toml:"MagicCookieValue"`
}

// PluginList contains a list of plugins
Expand All @@ -47,10 +47,11 @@ type PluginList struct {

// Release is the type that holds release data for a specific build of a plugin
type Release struct {
Arch string
OS string
Version string
Sum string
Arch string `toml:"Arch"`
OS string `toml:"OS"`
Version string `toml:"Version"`
Sum string `toml:"Sum"`
Unmanaged bool `toml:"Unmanaged"`
}

// getPluginInterface computes the correct metadata needed for starting the hcplugin client
Expand Down Expand Up @@ -250,6 +251,20 @@ func (p *Plugin) Uninstall(ctx context.Context, config config.IConfig, fs afero.
}

func (p *Plugin) downloadAndSavePlugin(config config.IConfig, pluginDownloadURL string, fs afero.Fs, version string) error {
body, err := FetchRemoteResource(pluginDownloadURL)
if err != nil {
return err
}

err = p.verifychecksumAndSavePlugin(body, config, fs, version)
if err != nil {
return err
}

return nil
}

func (p *Plugin) verifychecksumAndSavePlugin(pluginData []byte, config config.IConfig, fs afero.Fs, version string) error {
logger := log.WithFields(log.Fields{
"prefix": "plugins.plugin.Install",
})
Expand All @@ -260,30 +275,21 @@ func (p *Plugin) downloadAndSavePlugin(config config.IConfig, pluginDownloadURL

logger.Debugf("installing %s to %s...", p.Shortname, pluginFilePath)

body, err := FetchRemoteResource(pluginDownloadURL)

if err != nil {
return err
}

reader := bytes.NewReader(body)

err = p.verifyChecksum(reader, version)
reader := bytes.NewReader(pluginData)

err := p.verifyChecksum(reader, version)
if err != nil {
logger.Debug("could not match checksum of plugin")
return err
}

err = fs.MkdirAll(pluginDir, 0755)

if err != nil {
logger.Debugf("could not create plugin directory: %s", pluginDir)
return err
}

err = afero.WriteFile(fs, pluginFilePath, body, 0755)

err = afero.WriteFile(fs, pluginFilePath, pluginData, 0755)
if err != nil {
logger.Debug("could not save plugin to disk")
return err
Expand Down
Loading

0 comments on commit 04851e7

Please sign in to comment.