diff --git a/go.mod b/go.mod index 7f48e91..dd76287 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,8 @@ module github.com/skilld-labs/plasmactl-update go 1.21.6 require ( - github.com/google/renameio v1.0.1 - github.com/launchrctl/keyring v0.1.1 - github.com/launchrctl/launchr v0.6.0 + github.com/launchrctl/keyring v0.2.0 + github.com/launchrctl/launchr v0.7.0 github.com/spf13/cobra v1.8.0 ) diff --git a/go.sum b/go.sum index 712e923..91d8f52 100644 --- a/go.sum +++ b/go.sum @@ -43,8 +43,6 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/renameio v1.0.1 h1:Lh/jXZmvZxb0BBeSY5VKEfidcbcbenKjZFzM/q0fSeU= -github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -57,10 +55,10 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/launchrctl/keyring v0.1.1 h1:arwOg7ultBcE/ZEeERNFfDrL1q615xDG6v2CC2EfcKg= -github.com/launchrctl/keyring v0.1.1/go.mod h1:ECTYIH2h8CX+9pC93oVtmTMsqgeWd+MOruq+Kk7Y0io= -github.com/launchrctl/launchr v0.6.0 h1:hOyftbkd/Sv7Q+O902WXaCprNx8pMAyZ1juw8r+s94c= -github.com/launchrctl/launchr v0.6.0/go.mod h1:mz7JSPdg/PqRX3iUZdln8n0kmq/B7vhC8sPeT9z5LHs= +github.com/launchrctl/keyring v0.2.0 h1:rwIfYryajarEH1uoM2OU6OfAhjABNg09UQWH4GQ6T8s= +github.com/launchrctl/keyring v0.2.0/go.mod h1:/jdzj6fbw/idyd0AsragVSri4I0BUhT5596mU5rQ8OI= +github.com/launchrctl/launchr v0.7.0 h1:lHnPebOXJAMZK1FpRnShgw6BNwxbhF3HrUAQKpHBF5o= +github.com/launchrctl/launchr v0.7.0/go.mod h1:et+ykNbE3m7mMPydWKDV/6slFus1CD1vOaGH+j5uJ3M= github.com/moby/moby v25.0.4+incompatible h1:vea1J80wDM5x5geaZSaywFkfFxLABJIQ3mmR4ewZGbU= github.com/moby/moby v25.0.4+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= diff --git a/plugin.go b/plugin.go index be21125..07cd35a 100644 --- a/plugin.go +++ b/plugin.go @@ -3,16 +3,17 @@ package plasmactlupdate import ( "fmt" - "github.com/launchrctl/keyring" - "github.com/launchrctl/launchr" - "github.com/launchrctl/launchr/pkg/cli" - "github.com/launchrctl/launchr/pkg/log" - "github.com/spf13/cobra" "os" "os/exec" "path/filepath" "regexp" "strings" + + "github.com/launchrctl/keyring" + "github.com/launchrctl/launchr" + "github.com/launchrctl/launchr/pkg/cli" + "github.com/launchrctl/launchr/pkg/log" + "github.com/spf13/cobra" ) func init() { @@ -20,21 +21,26 @@ func init() { } // Plugin is launchr plugin providing update action. -type Plugin struct{} +type Plugin struct { + k keyring.Keyring +} // PluginInfo implements launchr.Plugin interface. func (p *Plugin) PluginInfo() launchr.PluginInfo { - return launchr.PluginInfo{} + return launchr.PluginInfo{ + Weight: 20, + } } // OnAppInit implements launchr.Plugin interface. -func (p *Plugin) OnAppInit(_ launchr.App) error { +func (p *Plugin) OnAppInit(app launchr.App) error { + app.GetService(&p.k) return nil } // CobraAddCommands implements launchr.CobraPlugin interface to provide bump functionality. func (p *Plugin) CobraAddCommands(rootCmd *cobra.Command) error { - var creds keyring.CredentialsItem + var ci keyring.CredentialsItem var updCmd = &cobra.Command{ Use: "update", @@ -42,7 +48,7 @@ func (p *Plugin) CobraAddCommands(rootCmd *cobra.Command) error { RunE: func(cmd *cobra.Command, args []string) error { // Don't show usage help on a runtime error. cmd.SilenceUsage = true - u, err := CreateUpdate(creds) + u, err := createUpdateAction(p.k, ci) if err != nil { return err } @@ -52,16 +58,16 @@ func (p *Plugin) CobraAddCommands(rootCmd *cobra.Command) error { } // Credentials flags - creds.URL = BaseUrl - updCmd.Flags().StringVarP(&creds.Username, "username", "u", "", "Username") - updCmd.Flags().StringVarP(&creds.Password, "password", "p", "", "Password") + ci.URL = baseURL + updCmd.Flags().StringVarP(&ci.Username, "username", "u", "", "Username") + updCmd.Flags().StringVarP(&ci.Password, "password", "p", "", "Password") rootCmd.AddCommand(updCmd) return nil } // runUpdate command entrypoint. -func runUpdate(u *Update) error { +func runUpdate(u *updateAction) error { // Wrapper to conclude errors. if err := runCommands(u); err != nil { u.exitWithError() @@ -72,7 +78,7 @@ func runUpdate(u *Update) error { } // runCommands run commands one by one. -func runCommands(u *Update) error { +func runCommands(u *updateAction) error { cli.Println("Starting plasmactl installation...") currOS, arch, err := u.initVars() @@ -98,7 +104,7 @@ func runCommands(u *Update) error { } // Format the URL with the determined 'os', 'arch' and 'extension' values. - u.c.URL = fmt.Sprintf(binPathMask, BaseUrl, sr, currOS, arch, u.ext) + u.c.URL = fmt.Sprintf(binPathMask, baseURL, sr, currOS, arch, u.ext) cli.Println("Downloading file: %s", u.c.URL) // Download file to the temp folder. diff --git a/update.go b/update.go index 11cb863..92f1a93 100644 --- a/update.go +++ b/update.go @@ -3,9 +3,6 @@ package plasmactlupdate import ( "errors" "fmt" - "github.com/launchrctl/keyring" - "github.com/launchrctl/launchr/pkg/cli" - "github.com/launchrctl/launchr/pkg/log" "io" "net/http" "os" @@ -13,10 +10,14 @@ import ( "path/filepath" "runtime" "strings" + + "github.com/launchrctl/keyring" + "github.com/launchrctl/launchr/pkg/cli" + "github.com/launchrctl/launchr/pkg/log" ) -// Update stored update definition. -type Update struct { +type updateAction struct { + k keyring.Keyring c keyring.CredentialsItem ext string fName string @@ -25,29 +26,30 @@ type Update struct { fDir string } -// CreateUpdate instance. -func CreateUpdate(cr keyring.CredentialsItem) (*Update, error) { - return &Update{cr, "", "", "", "", ""}, nil +// createUpdateAction instance. +func createUpdateAction(kr keyring.Keyring, cr keyring.CredentialsItem) (*updateAction, error) { + return &updateAction{k: kr, c: cr}, nil } // Errors. var ( - errUnsupportedOS = errors.New("unsupported operating system") - errUnsupportedArch = errors.New("unsupported architecture") - errInvalidCreds = errors.New("failed to validate credentials") + errUnsupportedOS = errors.New("unsupported operating system") + errUnsupportedArch = errors.New("unsupported architecture") + errInvalidCreds = errors.New("failed to validate credentials") + errMalformedKeyring = errors.New("the keyring is malformed or wrong passphrase provided") ) // Define the URL pattern for the file. const ( - BaseUrl = "https://repositories.skilld.cloud/repository/pla-plasmactl-raw" + baseURL = "https://repositories.skilld.cloud/repository/pla-plasmactl-raw" releasePath = "stable_release" binPathMask = "%s/%s/plasmactl_%s_%s%s" ) // initVars initialize plugin variables. -func (u *Update) initVars() (string, string, error) { +func (u *updateAction) initVars() (string, string, error) { // Get username and password. - if err := u.getCreds(); err != nil { + if err := u.getCredentials(); err != nil { return "", "", err } @@ -57,7 +59,7 @@ func (u *Update) initVars() (string, string, error) { return "", "", err } - // Set Update vars. + // Set updateAction vars. u.fName = fmt.Sprintf("plasmactl%s", u.ext) u.fTmpPath = filepath.Join(os.TempDir(), u.fName) @@ -67,7 +69,7 @@ func (u *Update) initVars() (string, string, error) { return "", "", err } - u.c.URL = fmt.Sprintf("%s/%s", BaseUrl, releasePath) + u.c.URL = fmt.Sprintf("%s/%s", baseURL, releasePath) log.Debug("OS = %s\n", currOS) log.Debug("Arch = %s\n", arch) @@ -77,25 +79,44 @@ func (u *Update) initVars() (string, string, error) { return currOS, arch, nil } -// getCreds stores username and password credentials. -func (u *Update) getCreds() error { - if u.c.Username != "" && u.c.Password != "" { - return nil - } +// getCredentials stores username and password credentials. +func (u *updateAction) getCredentials() error { + repoURL := fmt.Sprintf("%s/%s", baseURL, releasePath) + log.Debug("Source url of release: %s\n", repoURL) - if u.c.URL != "" { - cli.Println("Enter credentials for %s", u.c.URL) - } + // Get credentials and save in keyring. + ci, err := u.k.GetForURL(repoURL) + if err != nil { + if errors.Is(err, keyring.ErrEmptyPass) { + return err + } else if !errors.Is(err, keyring.ErrNotFound) { + log.Debug("%s", err) + return errMalformedKeyring + } - if err := keyring.RequestCredentialsFromTty(&u.c); err != nil { - return err + ci.URL = repoURL + ci.Username = u.c.Username + ci.Password = u.c.Password + if ci.Username == "" || ci.Password == "" { + cli.Println("Enter credentials for %s", ci.URL) + if err = keyring.RequestCredentialsFromTty(&ci); err != nil { + return err + } + } + + if err = u.k.AddItem(ci); err != nil { + return err + } + + err = u.k.Save() } - return nil + u.c = ci + return err } // getOS check operating system supports and set extension package file. -func (u *Update) getOS() (os string, err error) { +func (u *updateAction) getOS() (os string, err error) { os = runtime.GOOS if os == "windows" { @@ -113,14 +134,14 @@ func getArch() (arch string, err error) { if arch == "amd64" || arch == "386" || arch == "arm64" { return arch, nil - } else { - cli.Println("Unsupported architecture: %s", arch) - return "", errUnsupportedArch } + + cli.Println("Unsupported architecture: %s", arch) + return "", errUnsupportedArch } // validateCredentials make request to validate credentials and return HTTP status code. -func (u *Update) validateCredentials() error { +func (u *updateAction) validateCredentials() error { r, err := u.sendRequest() if err != nil { return err @@ -151,7 +172,7 @@ func (u *Update) validateCredentials() error { } // sendRequest send HTTP request, make authorization and return response. -func (u *Update) sendRequest() (*http.Response, error) { +func (u *updateAction) sendRequest() (*http.Response, error) { client := &http.Client{} req, err := http.NewRequest("GET", u.c.URL, nil) if err != nil { @@ -168,7 +189,7 @@ func (u *Update) sendRequest() (*http.Response, error) { } // getStableRelease send request and get stable release version. -func (u *Update) getStableRelease() (string, error) { +func (u *updateAction) getStableRelease() (string, error) { resp, err := u.sendRequest() if err != nil { @@ -189,7 +210,7 @@ func (u *Update) getStableRelease() (string, error) { } // downloadFile Download the file using with Basic Auth header. -func (u *Update) downloadFile() error { +func (u *updateAction) downloadFile() error { resp, err := u.sendRequest() if err != nil { return err @@ -221,7 +242,7 @@ func (u *Update) downloadFile() error { } // setBinPath get bin folder path. -func (u *Update) setBinPath(envPath, homeDir string) { +func (u *updateAction) setBinPath(envPath, homeDir string) { if strings.Contains(envPath, homeDir+"/.global/bin") { u.fDir = filepath.Join(homeDir, ".global/bin") } else if strings.Contains(envPath, homeDir+"/.local/bin") { @@ -234,7 +255,7 @@ func (u *Update) setBinPath(envPath, homeDir string) { } // installFile copy file to the bin folder and remove temp file. -func (u *Update) installFile(dirPath string) error { +func (u *updateAction) installFile(dirPath string) error { cli.Println("Installing %s binary under %s", u.fName, dirPath) info, err := os.Stat(u.fDir) @@ -248,6 +269,7 @@ func (u *Update) installFile(dirPath string) error { if err != nil { return err } + defer src.Close() // Set temp permissions for the folder with plasmactl. if err = u.setFolderPermissions("777", u.fDir); err != nil { @@ -255,16 +277,14 @@ func (u *Update) installFile(dirPath string) error { } fTmpName := u.fPath + ".tmp" - dst, err := os.Create(fTmpName) + dst, err := os.Create(filepath.Clean(fTmpName)) if err != nil { - src.Close() u.setFolderPermissions(pathPerm, u.fDir) return err } + defer dst.Close() _, err = io.Copy(dst, src) - src.Close() - dst.Close() if err != nil { u.setFolderPermissions(pathPerm, u.fDir) return err @@ -289,7 +309,7 @@ func (u *Update) installFile(dirPath string) error { return nil } -func (u *Update) setFolderPermissions(pathPerm, fPath string) error { +func (u *updateAction) setFolderPermissions(pathPerm, fPath string) error { cmd := exec.Command("sudo", "chmod", pathPerm, fPath) if err := cmd.Run(); err != nil { log.Debug("Error to set back %s folder permissions", fPath) @@ -299,7 +319,7 @@ func (u *Update) setFolderPermissions(pathPerm, fPath string) error { } // exitWithError exit with error and remove temp file. -func (u *Update) exitWithError() { +func (u *updateAction) exitWithError() { if _, err := os.Stat(u.fTmpPath); err == nil { if err = os.Remove(u.fTmpPath); err != nil { log.Err("Error file %s deleting: %s", u.fTmpPath, err)