diff --git a/cmd/add.go b/cmd/add.go index fd74a218..4d0172a0 100644 --- a/cmd/add.go +++ b/cmd/add.go @@ -9,7 +9,7 @@ var addCmd = &cobra.Command{ Aliases: []string{"a"}, Short: "Add a project, or add notifications and variables to projects or environments", PersistentPreRun: func(cmd *cobra.Command, args []string) { - validateToken(lagoonCLIConfig.Current) // get a new token if the current one is invalid + validateToken(lContext.Name) // get a new token if the current one is invalid }, } @@ -18,7 +18,7 @@ var addNotificationCmd = &cobra.Command{ Aliases: []string{"n"}, Short: "Add notifications or add notifications to projects", PersistentPreRun: func(cmd *cobra.Command, args []string) { - validateToken(lagoonCLIConfig.Current) // get a new token if the current one is invalid + validateToken(lContext.Name) // get a new token if the current one is invalid }, } diff --git a/cmd/config.go b/cmd/config.go deleted file mode 100644 index dde5bfa4..00000000 --- a/cmd/config.go +++ /dev/null @@ -1,418 +0,0 @@ -package cmd - -import ( - "context" - "encoding/json" - "fmt" - "os" - "path/filepath" - "sort" - "strings" - - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "github.com/uselagoon/lagoon-cli/internal/lagoon" - "github.com/uselagoon/lagoon-cli/internal/lagoon/client" - "github.com/uselagoon/lagoon-cli/pkg/output" - "gopkg.in/yaml.v3" -) - -// LagoonConfigFlags . -type LagoonConfigFlags struct { - Lagoon string `json:"lagoon,omitempty"` - Hostname string `json:"hostname,omitempty"` - Port string `json:"port,omitempty"` - GraphQL string `json:"graphql,omitempty"` - Token string `json:"token,omitempty"` - UI string `json:"ui,omitempty"` - Kibana string `json:"kibana,omitempty"` - SSHKey string `json:"sshkey,omitempty"` - SSHPortal bool `json:"sshportal,omitempty"` -} - -func parseLagoonConfig(flags pflag.FlagSet) LagoonConfigFlags { - configMap := make(map[string]interface{}) - flags.VisitAll(func(f *pflag.Flag) { - if flags.Changed(f.Name) { - configMap[f.Name] = f.Value - } - }) - jsonStr, _ := json.Marshal(configMap) - parsedFlags := LagoonConfigFlags{} - _ = json.Unmarshal(jsonStr, &parsedFlags) - return parsedFlags -} - -var configCmd = &cobra.Command{ - Use: "config", - Aliases: []string{"c"}, - Short: "Configure Lagoon CLI", - PersistentPreRun: func(cmd *cobra.Command, args []string) { - }, -} - -var configDefaultCmd = &cobra.Command{ - Use: "default", - Aliases: []string{"d"}, - Short: "Set the default Lagoon to use", - RunE: func(cmd *cobra.Command, args []string) error { - lagoonConfig := parseLagoonConfig(*cmd.Flags()) - if lagoonConfig.Lagoon == "" { - return fmt.Errorf("not enough arguments") - } - lagoonCLIConfig.Default = strings.TrimSpace(string(lagoonConfig.Lagoon)) - contextExists := false - for l := range lagoonCLIConfig.Lagoons { - if l == lagoonCLIConfig.Current { - contextExists = true - } - } - if !contextExists { - return fmt.Errorf("chosen context '%s' doesn't exist in config file", lagoonCLIConfig.Current) - } - err := writeLagoonConfig(&lagoonCLIConfig, filepath.Join(configFilePath, configName+configExtension)) - handleError(err) - - resultData := output.Result{ - Result: "success", - ResultData: map[string]interface{}{ - "default-lagoon": lagoonConfig.Lagoon, - }, - } - r := output.RenderResult(resultData, outputOptions) - fmt.Fprintf(cmd.OutOrStdout(), "%s", r) - return nil - }, -} - -var configLagoonsCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"l"}, - Short: "View all configured Lagoon instances", - RunE: func(cmd *cobra.Command, args []string) error { - var data []output.Data - for l, lc := range lagoonCLIConfig.Lagoons { - var isDefault, isCurrent string - if l == lagoonCLIConfig.Default { - isDefault = "(default)" - } - if l == lagoonCLIConfig.Current { - isCurrent = "(current)" - } - mapData := []string{ - returnNonEmptyString(fmt.Sprintf("%s%s%s", l, isDefault, isCurrent)), - returnNonEmptyString(lc.Version), - returnNonEmptyString(lc.GraphQL), - returnNonEmptyString(lc.HostName), - returnNonEmptyString(lc.Port), - } - if fullConfigList { - mapData = append(mapData, returnNonEmptyString(lc.UI)) - mapData = append(mapData, returnNonEmptyString(lc.Kibana)) - } - mapData = append(mapData, returnNonEmptyString(lc.SSHKey)) - data = append(data, mapData) - } - sort.Slice(data, func(i, j int) bool { - return data[i][0] < data[j][0] - }) - tableHeader := []string{ - "Name", - "Version", - "GraphQL", - "SSH-Hostname", - "SSH-Port", - } - if fullConfigList { - tableHeader = append(tableHeader, "UI-URL") - tableHeader = append(tableHeader, "Kibana-URL") - } - tableHeader = append(tableHeader, "SSH-Key") - r := output.RenderOutput(output.Table{ - Header: tableHeader, - Data: data, - }, outputOptions) - fmt.Fprintf(cmd.OutOrStdout(), "%s", r) - return nil - }, -} - -var configAddCmd = &cobra.Command{ - Use: "add", - Aliases: []string{"a"}, - Short: "Add information about an additional Lagoon instance to use", - RunE: func(cmd *cobra.Command, args []string) error { - lagoonConfig := parseLagoonConfig(*cmd.Flags()) - if lagoonConfig.Lagoon == "" { - return fmt.Errorf("missing arguments: Lagoon name is not defined") - } - - if lagoonConfig.Hostname != "" && lagoonConfig.Port != "" && lagoonConfig.GraphQL != "" { - lc := lagoonCLIConfig.Lagoons[lagoonConfig.Lagoon] - lc.HostName = lagoonConfig.Hostname - lc.Port = lagoonConfig.Port - lc.GraphQL = lagoonConfig.GraphQL - if lagoonConfig.UI != "" { - lc.UI = lagoonConfig.UI - } - if lagoonConfig.Kibana != "" { - lc.Kibana = lagoonConfig.Kibana - } - if lagoonConfig.Token != "" { - lc.Token = lagoonConfig.Token - } - if lagoonConfig.SSHKey != "" { - lc.SSHKey = lagoonConfig.SSHKey - } - // check identity files flag - identityFiles, err := cmd.Flags().GetStringSlice("publickey-identityfile") - if err != nil { - return err - } - if identityFiles != nil { - lc.PublicKeyIdentities = identityFiles - } - lagoonCLIConfig.Lagoons[lagoonConfig.Lagoon] = lc - if err := writeLagoonConfig(&lagoonCLIConfig, filepath.Join(configFilePath, configName+configExtension)); err != nil { - return fmt.Errorf("couldn't write config: %v", err) - } - r := output.RenderOutput(output.Table{ - Header: []string{ - "Name", - "GraphQL", - "SSH-Hostname", - "SSH-Port", - "UI-URL", - "Kibana-URL", - "SSH-Key", - }, - Data: []output.Data{ - []string{ - lagoonConfig.Lagoon, - lagoonConfig.GraphQL, - lagoonConfig.Hostname, - lagoonConfig.Port, - lagoonConfig.UI, - lagoonConfig.Kibana, - lagoonConfig.SSHKey, - }, - }, - }, outputOptions) - fmt.Fprintf(cmd.OutOrStdout(), "%s", r) - } else { - return fmt.Errorf("must have Hostname, Port, and GraphQL endpoint") - } - return nil - }, -} - -var configDeleteCmd = &cobra.Command{ - Use: "delete", - Aliases: []string{"d"}, - Short: "Delete a Lagoon instance configuration", - Run: func(cmd *cobra.Command, args []string) { - lagoonConfig := parseLagoonConfig(*cmd.Flags()) - - if lagoonConfig.Lagoon == "" { - fmt.Println("Missing arguments: Lagoon name is not defined") - cmd.Help() - os.Exit(1) - } - if yesNo(fmt.Sprintf("You are attempting to delete config for lagoon '%s', are you sure?", lagoonConfig.Lagoon)) { - err := removeConfig(lagoonConfig.Lagoon) - if err != nil { - output.RenderError(err.Error(), outputOptions) - os.Exit(1) - } - } - }, -} - -var configFeatureSwitch = &cobra.Command{ - Use: "feature", - Aliases: []string{"f"}, - Short: "Enable or disable CLI features", - Run: func(cmd *cobra.Command, args []string) { - switch updateCheck { - case "true": - lagoonCLIConfig.UpdateCheckDisable = true - case "false": - lagoonCLIConfig.UpdateCheckDisable = false - } - switch environmentFromDirectory { - case "true": - lagoonCLIConfig.EnvironmentFromDirectory = true - case "false": - lagoonCLIConfig.EnvironmentFromDirectory = false - } - strictHostKeyChecking, err := cmd.Flags().GetString("strict-host-key-checking") - if err != nil { - output.RenderError(err.Error(), outputOptions) - os.Exit(1) - } - strictHostKeyCheckingProvided := cmd.Flags().Lookup("strict-host-key-checking").Changed - if strictHostKeyCheckingProvided { - lagoonCLIConfig.StrictHostKeyChecking = strictHostKeyChecking - } - - if err := writeLagoonConfig(&lagoonCLIConfig, filepath.Join(configFilePath, configName+configExtension)); err != nil { - output.RenderError(err.Error(), outputOptions) - os.Exit(1) - } - }, -} - -var configGetCurrent = &cobra.Command{ - Use: "current", - Aliases: []string{"cur"}, - Short: "Display the current Lagoon that commands would be executed against", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println(lagoonCLIConfig.Current) - }, -} - -var configLagoonVersionCmd = &cobra.Command{ - Use: "lagoon-version", - Aliases: []string{"l"}, - Short: "Checks the current Lagoon for its version and sets it in the config file", - PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) - }, - RunE: func(cmd *cobra.Command, args []string) error { - debug, err := cmd.Flags().GetBool("debug") - if err != nil { - return err - } - current := lagoonCLIConfig.Current - lc := client.New( - lagoonCLIConfig.Lagoons[current].GraphQL, - lagoonCLIConfig.Lagoons[current].Token, - lagoonCLIConfig.Lagoons[current].Version, - lagoonCLIVersion, - debug) - lagoonVersion, err := lagoon.GetLagoonAPIVersion(context.TODO(), lc) - if err != nil { - return err - } - lagu := lagoonCLIConfig.Lagoons[current] - lagu.Version = lagoonVersion.LagoonVersion - lagoonCLIConfig.Lagoons[current] = lagu - if err = writeLagoonConfig(&lagoonCLIConfig, filepath.Join(configFilePath, configName+configExtension)); err != nil { - return fmt.Errorf("couldn't write config: %v", err) - } - fmt.Println(lagoonVersion.LagoonVersion) - return nil - }, -} - -var updateCheck string -var environmentFromDirectory string -var strictHostKeyChecking string -var fullConfigList bool - -func init() { - configCmd.AddCommand(configAddCmd) - configCmd.AddCommand(configGetCurrent) - configCmd.AddCommand(configDefaultCmd) - configCmd.AddCommand(configDeleteCmd) - configCmd.AddCommand(configFeatureSwitch) - configCmd.AddCommand(configLagoonsCmd) - configCmd.AddCommand(configLagoonVersionCmd) - configAddCmd.Flags().StringVarP(&lagoonHostname, "hostname", "H", "", - "Lagoon SSH hostname") - configAddCmd.Flags().StringVarP(&lagoonPort, "port", "P", "", - "Lagoon SSH port") - configAddCmd.Flags().StringVarP(&lagoonGraphQL, "graphql", "g", "", - "Lagoon GraphQL endpoint") - configAddCmd.Flags().StringVarP(&lagoonToken, "token", "t", "", - "Lagoon GraphQL token") - configAddCmd.Flags().StringVarP(&lagoonUI, "ui", "u", "", - "Lagoon UI location (https://dashboard.amazeeio.cloud)") - configAddCmd.PersistentFlags().BoolVarP(&createConfig, "create-config", "", false, - "Create the config file if it is non existent (to be used with --config-file)") - configAddCmd.Flags().StringVarP(&lagoonKibana, "kibana", "k", "", - "Lagoon Kibana URL (https://logs.amazeeio.cloud)") - configAddCmd.Flags().StringVarP(&lagoonSSHKey, "ssh-key", "", "", - "SSH Key to use for this cluster for generating tokens") - configAddCmd.Flags().StringSliceP("publickey-identityfile", "", []string{}, - "Specific public key identity files to use when doing ssh-agent checks (support multiple)") - configLagoonsCmd.Flags().BoolVarP(&fullConfigList, "show-full", "", false, - "Show full config output when listing Lagoon configurations") - configFeatureSwitch.Flags().StringVarP(&updateCheck, "disable-update-check", "", "", - "Enable or disable checking of updates (true/false)") - configFeatureSwitch.Flags().StringVarP(&environmentFromDirectory, "enable-local-dir-check", "", "", - "Enable or disable checking of local directory for Lagoon project (true/false)") - configFeatureSwitch.Flags().StringVar(&strictHostKeyChecking, "strict-host-key-checking", "", - "Enable or disable StrictHostKeyChecking (yes, no, ignore)") -} - -// readLagoonConfig reads the lagoon config from specified file. -func readLagoonConfig(lc *lagoon.Config, file string) error { - data, err := os.ReadFile(file) - if err != nil { - // if there is no file found in the specified location, prompt the user to create it with the default - // configuration to point to the amazeeio lagoon instance - if yesNo(fmt.Sprintf("Config file '%s' does not exist, do you want to create it with defaults?", file)) { - l := lagoon.Context{ - GraphQL: "https://api.lagoon.amazeeio.cloud/graphql", - HostName: "ssh.lagoon.amazeeio.cloud", - Token: "", - Port: "32222", - UI: "https://dashboard.amazeeio.cloud", - Kibana: "https://logs.amazeeio.cloud/", - } - lc.Lagoons = map[string]lagoon.Context{} - lc.Lagoons["amazeeio"] = l - lc.Default = "amazeeio" - return writeLagoonConfig(lc, file) - } - return err - } - err = yaml.Unmarshal(data, &lc) - if err != nil { - return fmt.Errorf("unable to unmarshal config, yaml is likely invalid: %v", err) - } - for ln, l := range lc.Lagoons { - if l.GraphQL == "" || l.HostName == "" || l.Port == "" { - return fmt.Errorf("configured lagoon %s is missing required configuration for graphql, hostname, or port", ln) - } - } - return nil - -} - -// functions to handle read/write of configuration file - -// writeLagoonConfig writes the lagoon config to specified file. -func writeLagoonConfig(lc *lagoon.Config, file string) error { - d, err := yaml.Marshal(&lc) - if err != nil { - return fmt.Errorf("unable to marshal config into valid yaml: %v", err) - } - err = os.WriteFile(file, d, 0777) - if err != nil { - return err - } - return nil -} - -func setConfigDefaultVersion(lc *lagoon.Config, lagoon string, version string) error { - if lc.Lagoons[lagoon].Version == "" { - l := lc.Lagoons[lagoon] - l.Version = version - lc.Lagoons[lagoon] = l - if err := writeLagoonConfig(&lagoonCLIConfig, filepath.Join(configFilePath, configName+configExtension)); err != nil { - return fmt.Errorf("couldn't write config: %v", err) - } - } - return nil -} - -func removeConfig(key string) error { - delete(lagoonCLIConfig.Lagoons, key) - if err := writeLagoonConfig(&lagoonCLIConfig, filepath.Join(configFilePath, configName+configExtension)); err != nil { - output.RenderError(err.Error(), outputOptions) - os.Exit(1) - } - return nil -} diff --git a/cmd/config_test.go b/cmd/config_test.go deleted file mode 100644 index 0dbb7984..00000000 --- a/cmd/config_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package cmd - -import ( - "reflect" - "testing" - - "github.com/uselagoon/lagoon-cli/internal/lagoon" -) - -func TestConfigRead(t *testing.T) { - var testCases = map[string]struct { - input string - description string - readfailallowed bool - expect lagoon.Config - }{ - "valid-yaml": { - input: "testdata/lagoon.yml", - description: "This test checks that a valid and complete configuration is parsed", - readfailallowed: false, - expect: lagoon.Config{ - Current: "amazeeio", - Default: "amazeeio", - Lagoons: map[string]lagoon.Context{ - "amazeeio": { - GraphQL: "https://api.lagoon.amazeeio.cloud/graphql", - HostName: "ssh.lagoon.amazeeio.cloud", - Kibana: "https://logs-db-ui-lagoon-master.ch.amazee.io/", - UI: "https://ui-lagoon-master.ch.amazee.io", - Port: "32222", - }, - }, - UpdateCheckDisable: false, - EnvironmentFromDirectory: false, - }, - }, - "invalid-yaml": { - input: "testdata/lagoon.yml.invalid", - description: "This test checks to see if an invalid yaml config is not parsed", - readfailallowed: true, - expect: lagoon.Config{}, - }, - "missing-yaml": { - input: "testdata/lagoon.yml.missing", - description: "This test checks if a context is missing the required data (graphql, hostname, port)", - readfailallowed: true, - expect: lagoon.Config{ - Current: "amazeeio", - Default: "amazeeio", - Lagoons: map[string]lagoon.Context{ - "amazeeio": { - Kibana: "https://logs-db-ui-lagoon-master.ch.amazee.io/", - UI: "https://ui-lagoon-master.ch.amazee.io", - }, - }, - UpdateCheckDisable: false, - EnvironmentFromDirectory: false, - }, - }, - } - for name, tc := range testCases { - t.Run(name, func(tt *testing.T) { - lc := lagoon.Config{} - if err := readLagoonConfig(&lc, tc.input); err != nil { - if tc.readfailallowed == false { - tt.Fatal(err) - } - } - if !reflect.DeepEqual(lc, tc.expect) { - tt.Fatalf("Read config does not match expected config") - } - }) - } -} diff --git a/cmd/configuration.go b/cmd/configuration.go new file mode 100644 index 00000000..282e4fde --- /dev/null +++ b/cmd/configuration.go @@ -0,0 +1,914 @@ +package cmd + +import ( + "fmt" + "os" + "path/filepath" + "slices" + "strconv" + "strings" + + "github.com/manifoldco/promptui" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/uselagoon/lagoon-cli/internal/lagoon" + "github.com/uselagoon/lagoon-cli/pkg/output" + config "github.com/uselagoon/machinery/utils/config" + "github.com/uselagoon/machinery/utils/discovery" + "golang.org/x/oauth2" + "gopkg.in/yaml.v3" +) + +// configv2 related globals +var lConfig *config.Config +var lContext *config.Context +var lUser *config.User +var configFeaturePrefix = "cli" +var cliFeatures = []string{ + "environment-from-directory", // feature that enables the sourcing of project/environment from within a git repository + "disable-update-check", // feature that disables the automatic update check + "ssh-token", // feature that forces the cli to use ssh token base authentication instead of keycloak based + "no-strict-host-key-checking", // feature that enables or disables strict host key checking, default is enabled +} + +var configCmd = &cobra.Command{ + Use: "configuration", + Aliases: []string{"config", "conf", "c"}, + Short: "Manage or view the contexts and users for interacting with Lagoon", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + }, +} + +var configListUsersCmd = &cobra.Command{ + Use: "list-users", + Aliases: []string{"lu"}, + Short: "View all configured Lagoon context users", + RunE: func(cmd *cobra.Command, args []string) error { + data := []output.Data{} + for _, user := range lConfig.Users { + data = append(data, []string{ + returnNonEmptyString(fmt.Sprintf("%v", user.Name)), + returnNonEmptyString(fmt.Sprintf("%v", user.UserConfig.SSHKey)), + }) + } + output.RenderOutput(output.Table{ + Header: []string{ + "Name", + "SSH-Key", + }, + Data: data, + }, outputOptions) + return nil + }, +} + +var configListContextsCmd = &cobra.Command{ + Use: "list-contexts", + Aliases: []string{"lc"}, + Short: "View all configured Lagoon contexts", + RunE: func(cmd *cobra.Command, args []string) error { + data := []output.Data{} + featurePrefix := fmt.Sprintf("%s-", configFeaturePrefix) + for _, con := range lConfig.Contexts { + defa := false + if con.Name == lConfig.DefaultContext { + defa = true + } + selected := "" + if con.Name == cmdLagoon { + selected = "(selected)" + } + contextFeatures := map[string]bool{} + for f, b := range con.ContextConfig.Features { + // only add cli prefixed features in the cli + if strings.Contains(f, featurePrefix) { + // could include if it is context or global sourced here + contextFeatures[strings.TrimPrefix(f, featurePrefix)] = b + } + } + for f, b := range lConfig.Features { + // only add cli prefixed features in the cli + if strings.Contains(f, featurePrefix) { + if _, ok := con.ContextConfig.Features[f]; !ok { + // could include if it is context or global sourced here + contextFeatures[strings.TrimPrefix(f, featurePrefix)] = b + } + } + } + features := []string{} + // transform the features into a string slice for printing + for f, b := range contextFeatures { + features = append(features, fmt.Sprintf("%s=%t", f, b)) + } + data = append(data, []string{ + returnNonEmptyString(fmt.Sprintf("%v%s", con.Name, selected)), + returnNonEmptyString(fmt.Sprintf("%v", defa)), + returnNonEmptyString(fmt.Sprintf("%v", con.User)), + returnNonEmptyString(fmt.Sprintf("%v", con.ContextConfig.APIHostname)), + returnNonEmptyString(fmt.Sprintf("%v", con.ContextConfig.TokenHost)), + returnNonEmptyString(fmt.Sprintf("%v", con.ContextConfig.TokenPort)), + returnNonEmptyString(fmt.Sprintf("%v", con.ContextConfig.AuthenticationEndpoint)), + returnNonEmptyString(fmt.Sprintf("%v", strings.Join(features, ","))), + }) + } + output.RenderOutput(output.Table{ + Header: []string{ + "Name", + "Default", + "User", + "API-Hostname", + "Token-Hostname", + "Token-Port", + "Authentication-Hostname", + "Features", + }, + Data: data, + }, outputOptions) + return nil + }, +} + +var configAddUserCmd = &cobra.Command{ + Use: "add-user", + Aliases: []string{"au"}, + Short: "Add a new Lagoon context user", + RunE: func(cmd *cobra.Command, args []string) error { + uName, err := cmd.Flags().GetString("name") + if err != nil { + return err + } + uSSHKey, err := cmd.Flags().GetString("ssh-key") + if err != nil { + return err + } + // create the requested user + uConfig := config.UserConfig{ + Name: uName, + Grant: &oauth2.Token{}, + } + // visit the flags and check for any defined flags to set + cmd.Flags().Visit(func(f *pflag.Flag) { + if f.Name == "ssh-key" { + uConfig.SSHKey = uSSHKey + } + }) + err = lConfig.NewUser(uConfig) + if err != nil { + return err + } + data := []output.Data{} + for _, user := range lConfig.Users { + if user.Name == uName { + data = append(data, []string{ + returnNonEmptyString(fmt.Sprintf("%v", user.Name)), + returnNonEmptyString(fmt.Sprintf("%v", user.UserConfig.SSHKey)), + }) + } + } + output.RenderOutput(output.Table{ + Header: []string{ + "Name", + "SSH-Key", + }, + Data: data, + }, outputOptions) + // then save it + return lConfig.WriteConfig() + }, +} + +var configUpdateUserCmd = &cobra.Command{ + Use: "update-user", + Aliases: []string{"uu"}, + Short: "Update a Lagoon context user", + RunE: func(cmd *cobra.Command, args []string) error { + uName, err := cmd.Flags().GetString("name") + if err != nil { + return err + } + uSSHKey, err := cmd.Flags().GetString("ssh-key") + if err != nil { + return err + } + // get the requested user + u, err := lConfig.GetUser(uName) + if err != nil { + return err + } + // visit the flags and check for any defined flags to set + cmd.Flags().Visit(func(f *pflag.Flag) { + if f.Name == "ssh-key" { + u.UserConfig.SSHKey = uSSHKey + } + }) + err = lConfig.UpdateUser(u.UserConfig) + if err != nil { + return err + } + data := []output.Data{} + for _, user := range lConfig.Users { + if user.Name == uName { + data = append(data, []string{ + returnNonEmptyString(fmt.Sprintf("%v", user.Name)), + returnNonEmptyString(fmt.Sprintf("%v", user.UserConfig.SSHKey)), + }) + } + } + output.RenderOutput(output.Table{ + Header: []string{ + "Name", + "SSH-Key", + }, + Data: data, + }, outputOptions) + // then save it + return lConfig.WriteConfig() + }, +} + +var configAddContextCmd = &cobra.Command{ + Use: "add-context", + Aliases: []string{"ac"}, + Short: "Add a new Lagoon context", + RunE: func(cmd *cobra.Command, args []string) error { + cName, err := cmd.Flags().GetString("name") + if err != nil { + return err + } + cUser, err := cmd.Flags().GetString("user") + if err != nil { + return err + } + cAPIHost, err := cmd.Flags().GetString("api-hostname") + if err != nil { + return err + } + cTokenHost, err := cmd.Flags().GetString("token-hostname") + if err != nil { + return err + } + cTokenPort, err := cmd.Flags().GetInt("token-port") + if err != nil { + return err + } + cAuthHost, err := cmd.Flags().GetString("authentication-hostname") + if err != nil { + return err + } + cUIHost, err := cmd.Flags().GetString("ui-hostname") + if err != nil { + return err + } + cWebhookHost, err := cmd.Flags().GetString("webhook-hostname") + if err != nil { + return err + } + // create the requested context + cConfig := config.ContextConfig{ + Name: cName, + } + // visit the flags and check for any defined flags to set + cmd.Flags().Visit(func(f *pflag.Flag) { + if f.Name == "api-hostname" { + // strip /graphql from the host + cConfig.APIHostname = strings.TrimSuffix(cAPIHost, "/graphql") + } + if f.Name == "token-hostname" { + cConfig.TokenHost = cTokenHost + } + if f.Name == "token-port" { + cConfig.TokenPort = cTokenPort + } + if f.Name == "authentication-hostname" { + cConfig.AuthenticationEndpoint = cAuthHost + } + if f.Name == "ui-hostname" { + cConfig.UIHostname = cUIHost + } + if f.Name == "webhook-hostname" { + cConfig.WebhookEndpoint = cWebhookHost + } + }) + apiDiscoverEndpoint, err := discovery.Discover(cConfig.APIHostname) + if err != nil { + // throw error here, even if the endpoint is unreachable + return err + } + if apiDiscoverEndpoint != nil { + outputOptions.MessagePrefix = fmt.Sprintf("context %s", cName) + // try and extract information from the endpoint + if apiDiscoverEndpoint.AuthorizationEndpoint != "" && cConfig.AuthenticationEndpoint == "" { + cConfig.AuthenticationEndpoint = apiDiscoverEndpoint.AuthorizationEndpoint + output.RenderInfo(fmt.Sprintf("using authentication endpoint '%s' from api discovery for context\n", cConfig.AuthenticationEndpoint), outputOptions) + } + if apiDiscoverEndpoint.SSHTokenExchange.TokenHost != "" && cConfig.TokenHost == "" { + cConfig.TokenHost = apiDiscoverEndpoint.SSHTokenExchange.TokenHost + output.RenderInfo(fmt.Sprintf("using token host endpoint '%s' from api discovery for context\n", cConfig.TokenHost), outputOptions) + } + if apiDiscoverEndpoint.SSHTokenExchange.TokenPort != 0 && cConfig.TokenPort == 0 { + cConfig.TokenPort = apiDiscoverEndpoint.SSHTokenExchange.TokenPort + output.RenderInfo(fmt.Sprintf("using token host port '%d' from api discovery for context\n", cConfig.TokenPort), outputOptions) + } + if apiDiscoverEndpoint.UIHostname != "" && cConfig.UIHostname == "" { + cConfig.UIHostname = apiDiscoverEndpoint.UIHostname + output.RenderInfo(fmt.Sprintf("using ui host '%s' from api discovery for context\n", cConfig.UIHostname), outputOptions) + } + if apiDiscoverEndpoint.WebhookEndpoint != "" && cConfig.WebhookEndpoint == "" { + cConfig.WebhookEndpoint = apiDiscoverEndpoint.WebhookEndpoint + output.RenderInfo(fmt.Sprintf("using webhook endpoint '%s' from api discovery for context\n", cConfig.WebhookEndpoint), outputOptions) + } + } + err = lConfig.NewContext(cConfig, cUser) + if err != nil { + return err + } + data := []output.Data{} + for _, con := range lConfig.Contexts { + defa := false + if con.Name == lConfig.DefaultContext { + defa = true + } + if con.Name == cName { + data = append(data, []string{ + returnNonEmptyString(fmt.Sprintf("%v", con.Name)), + returnNonEmptyString(fmt.Sprintf("%v", defa)), + returnNonEmptyString(fmt.Sprintf("%v", con.User)), + returnNonEmptyString(fmt.Sprintf("%v", con.ContextConfig.APIHostname)), + returnNonEmptyString(fmt.Sprintf("%v", con.ContextConfig.TokenHost)), + returnNonEmptyString(fmt.Sprintf("%v", con.ContextConfig.TokenPort)), + returnNonEmptyString(fmt.Sprintf("%v", con.ContextConfig.AuthenticationEndpoint)), + }) + } + } + output.RenderOutput(output.Table{ + Header: []string{ + "Name", + "Default", + "User", + "API-Hostname", + "Token-Hostname", + "Token-Port", + "Authentication-Hostname", + }, + Data: data, + }, outputOptions) + // then save it + return lConfig.WriteConfig() + }, +} + +var configUpdateContextCmd = &cobra.Command{ + Use: "update-context", + Aliases: []string{"uc"}, + Short: "Update a Lagoon context", + RunE: func(cmd *cobra.Command, args []string) error { + cName, err := cmd.Flags().GetString("name") + if err != nil { + return err + } + cUser, err := cmd.Flags().GetString("user") + if err != nil { + return err + } + cAPIHost, err := cmd.Flags().GetString("api-hostname") + if err != nil { + return err + } + cTokenHost, err := cmd.Flags().GetString("token-hostname") + if err != nil { + return err + } + cTokenPort, err := cmd.Flags().GetInt("token-port") + if err != nil { + return err + } + cAuthHost, err := cmd.Flags().GetString("authentication-hostname") + if err != nil { + return err + } + cUIHost, err := cmd.Flags().GetString("ui-hostname") + if err != nil { + return err + } + cWebhookHost, err := cmd.Flags().GetString("webhook-hostname") + if err != nil { + return err + } + // get the requested context + cConfig, err := lConfig.GetContext(cName) + if err != nil { + return err + } + // visit the flags and check for any defined flags to set + cmd.Flags().Visit(func(f *pflag.Flag) { + // these will override the context ones if they are defined, otherwise the existing + // fields will remain untouched + if f.Name == "api-hostname" { + cConfig.ContextConfig.APIHostname = cAPIHost + } + if f.Name == "token-hostname" { + cConfig.ContextConfig.TokenHost = cTokenHost + } + if f.Name == "token-port" { + cConfig.ContextConfig.TokenPort = cTokenPort + } + if f.Name == "authentication-hostname" { + cConfig.ContextConfig.AuthenticationEndpoint = cAuthHost + } + if f.Name == "ui-hostname" { + cConfig.ContextConfig.UIHostname = cUIHost + } + if f.Name == "webhook-hostname" { + cConfig.ContextConfig.WebhookEndpoint = cWebhookHost + } + if f.Name == "user" { + cConfig.User = cUser + } + }) + // update the context within the configuration + err = lConfig.UpdateContext(cConfig.ContextConfig, cConfig.User) + if err != nil { + return err + } + data := []output.Data{} + for _, con := range lConfig.Contexts { + defa := false + if con.Name == lConfig.DefaultContext { + defa = true + } + if con.Name == cName { + data = append(data, []string{ + returnNonEmptyString(fmt.Sprintf("%v", con.Name)), + returnNonEmptyString(fmt.Sprintf("%v", defa)), + returnNonEmptyString(fmt.Sprintf("%v", con.User)), + returnNonEmptyString(fmt.Sprintf("%v", con.ContextConfig.APIHostname)), + returnNonEmptyString(fmt.Sprintf("%v", con.ContextConfig.TokenHost)), + returnNonEmptyString(fmt.Sprintf("%v", con.ContextConfig.TokenPort)), + returnNonEmptyString(fmt.Sprintf("%v", con.ContextConfig.AuthenticationEndpoint)), + }) + } + } + output.RenderOutput(output.Table{ + Header: []string{ + "Name", + "Default", + "User", + "API-Hostname", + "Token-Hostname", + "Token-Port", + "Authentication-Hostname", + }, + Data: data, + }, outputOptions) + // then save it + return lConfig.WriteConfig() + }, +} + +var configGetConfigPathCmd = &cobra.Command{ + Use: "config-path", + Aliases: []string{"cp"}, + Short: "Get the path of where the config file lives", + RunE: func(cmd *cobra.Command, args []string) error { + c := config.Config{} + path, err := c.GetConfigLocation() + if err != nil { + return err + } + fmt.Println(path) + return nil + }, +} + +var configSetDefaultCmd = &cobra.Command{ + Use: "default-context", + Aliases: []string{"dc"}, + Short: "Change which context is the default", + RunE: func(cmd *cobra.Command, args []string) error { + cName, err := cmd.Flags().GetString("name") + if err != nil { + return err + } + if err = lConfig.SetDefaultContext(cName); err != nil { + return err + } + data := []output.Data{} + for _, con := range lConfig.Contexts { + defa := false + if con.Name == lConfig.DefaultContext { + defa = true + } + selected := "" + if con.Name == cmdLagoon { + selected = " (selected)" + } + data = append(data, []string{ + returnNonEmptyString(fmt.Sprintf("%v%s", con.Name, selected)), + returnNonEmptyString(fmt.Sprintf("%v", defa)), + returnNonEmptyString(fmt.Sprintf("%v", con.User)), + returnNonEmptyString(fmt.Sprintf("%v", con.ContextConfig.APIHostname)), + returnNonEmptyString(fmt.Sprintf("%v", con.ContextConfig.TokenHost)), + returnNonEmptyString(fmt.Sprintf("%v", con.ContextConfig.TokenPort)), + returnNonEmptyString(fmt.Sprintf("%v", con.ContextConfig.AuthenticationEndpoint)), + }) + } + output.RenderOutput(output.Table{ + Header: []string{ + "Name", + "Default", + "User", + "API-Hostname", + "Token-Hostname", + "Token-Port", + "Authentication-Hostname", + }, + Data: data, + }, outputOptions) + return lConfig.WriteConfig() + }, +} + +var configConvertLegacyConfig = &cobra.Command{ + Use: "convert-config", + Aliases: []string{"convert"}, + Short: "Convert legacy .lagoon.yml config to the new configuration format", + Long: `Convert legacy .lagoon.yml config to the new configuration format. +This will prompt you to provide any required information if it is missing from your legacy configuration. +Running this command initially will run in dry-run mode, if you're happy with the result you can run it again +with the --write-config flag to save the new configuration.`, + RunE: func(cmd *cobra.Command, args []string) error { + writeConfig, err := cmd.Flags().GetBool("write-config") + if err != nil { + return err + } + return convertConfig(writeConfig) + }, +} + +var configSetFeatureStatus = &cobra.Command{ + Use: "feature", + Aliases: []string{"feat", "f"}, + Short: "Enable or disable a feature for all contexts or a specific context", + RunE: func(cmd *cobra.Command, args []string) error { + cContext, err := cmd.Flags().GetString("context") + if err != nil { + return err + } + cFeature, err := cmd.Flags().GetString("feature") + if err != nil { + return err + } + cFeatureState, err := cmd.Flags().GetBool("state") + if err != nil { + return err + } + contextFeature := false + cmd.Flags().Visit(func(f *pflag.Flag) { + if f.Name == "context" { + contextFeature = true + } + }) + if cFeature == "" { + return fmt.Errorf("feature name must be provided") + } + if !slices.Contains(cliFeatures, cFeature) { + return fmt.Errorf("feature %s is not one of the supported features: %s", cFeature, cliFeatures) + } + if cContext != "" && contextFeature { + lConfig.SetContextFeature(cContext, configFeaturePrefix, cFeature, cFeatureState) + return lConfig.WriteConfig() + } else if cContext == "" && contextFeature { + return fmt.Errorf("context name must be provided") + } + lConfig.SetGlobalFeature(configFeaturePrefix, cFeature, cFeatureState) + return lConfig.WriteConfig() + }, +} + +func init() { + configCmd.AddCommand(configListContextsCmd) + configCmd.AddCommand(configListUsersCmd) + configCmd.AddCommand(configGetConfigPathCmd) + + configCmd.AddCommand(configConvertLegacyConfig) + configConvertLegacyConfig.Flags().Bool("write-config", false, "Whether the config should be written to the config file or not") + + configCmd.AddCommand(configSetDefaultCmd) + configSetDefaultCmd.Flags().String("name", "", "The name of the context to be default") + + configCmd.AddCommand(configAddContextCmd) + configAddContextCmd.Flags().String("name", "", "The name to reference this context as") + configAddContextCmd.Flags().String("api-hostname", "", "Lagoon API hostname (eg: https://api.lagoon.sh)") + configAddContextCmd.Flags().String("token-hostname", "", "Lagoon Token endpoint hostname (eg: token.lagoon.sh)") + configAddContextCmd.Flags().Int("token-port", 0, "Lagoon Token endpoint port (eg: 22)") + configAddContextCmd.Flags().String("authentication-hostname", "", "Lagoon authentication hostname (eg: https://keycloak.lagoon.sh)") + configAddContextCmd.Flags().String("user", "", "The user to associate to this context") + configAddContextCmd.Flags().String("ui-hostname", "", "Lagoon UI hostname (eg: https://ui.lagoon.sh)") + configAddContextCmd.Flags().String("webhook-hostname", "", "Lagoon webhook hostname (eg: https://webhook.lagoon.sh)") + + configCmd.AddCommand(configAddUserCmd) + configAddUserCmd.Flags().String("name", "", "The name to reference this user as") + configAddUserCmd.Flags().String("ssh-key", "", "The full path to this users ssh-key") + + configCmd.AddCommand(configUpdateUserCmd) + configUpdateUserCmd.Flags().String("name", "", "The name to reference this user as") + configUpdateUserCmd.Flags().String("ssh-key", "", "The full path to this users ssh-key") + + configCmd.AddCommand(configUpdateContextCmd) + configUpdateContextCmd.Flags().String("name", "", "The name to reference this context as") + configUpdateContextCmd.Flags().String("api-hostname", "", "Lagoon API hostname (eg: https://api.lagoon.sh)") + configUpdateContextCmd.Flags().String("token-hostname", "", "Lagoon Token endpoint hostname (eg: token.lagoon.sh)") + configUpdateContextCmd.Flags().Int("token-port", 0, "Lagoon Token endpoint port (eg: 22)") + configUpdateContextCmd.Flags().String("authentication-hostname", "", "Lagoon authentication hostname (eg: https://keycloak.lagoon.sh)") + configUpdateContextCmd.Flags().String("user", "", "The user to associate to this context") + configUpdateContextCmd.Flags().String("ui-hostname", "", "Lagoon UI hostname (eg: https://ui.lagoon.sh)") + configUpdateContextCmd.Flags().String("webhook-hostname", "", "Lagoon webhook hostname (eg: https://webhook.lagoon.sh)") + + configCmd.AddCommand(configSetFeatureStatus) + configSetFeatureStatus.Flags().Bool("state", false, "The state of the feature (--state=true or --state=false)") + configSetFeatureStatus.Flags().String("context", "", "If provided the feature will be enabled for this context, otherwise globally") + configSetFeatureStatus.Flags().String("feature", "", fmt.Sprintf("The name of the feature to enable or disable [%s]", strings.Join(cliFeatures, ","))) +} + +func getLegacyConfigFile(configPath *string, configName *string, configExtension *string, cmd *cobra.Command) error { + // check if we have an envvar or flag to define our confg file + var configFilePath string + configFilePath, err := cmd.Flags().GetString("config-file") + if err != nil { + return fmt.Errorf("error reading flag `config-file`: %v", err) + } + if configFilePath == "" { + if lagoonConfigEnvar, ok := os.LookupEnv("LAGOONCONFIG"); ok { + configFilePath = lagoonConfigEnvar + } + // prefer LAGOON_CONFIG_FILE + if lagoonConfigEnvar, ok := os.LookupEnv("LAGOON_CONFIG_FILE"); ok { + configFilePath = lagoonConfigEnvar + } + } + if configFilePath != "" { + if fileExists(configFilePath) || createConfig { + *configPath = filepath.Dir(configFilePath) + *configExtension = filepath.Ext(configFilePath) + *configName = strings.TrimSuffix(filepath.Base(configFilePath), *configExtension) + return nil + } + return fmt.Errorf("%s/%s File doesn't exist", *configPath, configFilePath) + } + // no config file found + return nil +} + +func readLegacyConfig() ([]byte, error) { + // check for the legacy config file + userPath, err := os.UserHomeDir() + if err != nil { + return nil, err + } + configFilePath = userPath + err = getLegacyConfigFile(&configFilePath, &configName, &configExtension, rootCmd) + if err != nil { + return nil, err + } + data, err := os.ReadFile(filepath.Join(configFilePath, configName+configExtension)) + if err != nil { + return nil, err + } + return data, nil +} + +func convertConfig(writeConfig bool) error { + data, err := readLegacyConfig() + if err != nil { + return err + } + lc := lagoon.Config{} + err = yaml.Unmarshal(data, &lc) + if err != nil { + return fmt.Errorf("unable to unmarshal config, yaml is likely invalid: %v", err) + } + // convert the legacy config into the new user/context config file + cc := config.Config{} + for n, l := range lc.Lagoons { + apiHostname := strings.TrimSuffix(l.GraphQL, "/graphql") + // try and extract the auth endpoint from the discovery endpoint of the api + var authEndpoint string + apiDiscoverEndpoint, err := discovery.Discover(apiHostname) + if err != nil { + // if the endpoint isn't reachable during a conversion either because its old or incorrect + // ignore the error and prompt the user to enter missing values manually + if !strings.Contains(err.Error(), "no route") && !strings.Contains(err.Error(), "connection refused") { + return err + } + } + tokenHost := l.HostName + tokenPort := l.Port + uiHostname := l.UI + var webhookEndpoint string + iTokenPort, _ := strconv.Atoi(tokenPort) + if apiDiscoverEndpoint != nil { + // if the discovery endpoint returns data, check for the auth endpoint + outputOptions.MessagePrefix = fmt.Sprintf("context %s", n) + if apiDiscoverEndpoint.AuthorizationEndpoint != "" { + authEndpoint = apiDiscoverEndpoint.AuthorizationEndpoint + output.RenderInfo(fmt.Sprintf("using authentication endpoint '%s' from api discovery for context\n", authEndpoint), outputOptions) + } + if apiDiscoverEndpoint.SSHTokenExchange.TokenHost != "" { + tokenHost = apiDiscoverEndpoint.SSHTokenExchange.TokenHost + output.RenderInfo(fmt.Sprintf("using token host endpoint '%s' from api discovery for context\n", tokenHost), outputOptions) + } + if apiDiscoverEndpoint.SSHTokenExchange.TokenPort != 0 { + iTokenPort = apiDiscoverEndpoint.SSHTokenExchange.TokenPort + output.RenderInfo(fmt.Sprintf("using token host port '%d' from api discovery for context\n", iTokenPort), outputOptions) + } + if apiDiscoverEndpoint.UIHostname != "" { + uiHostname = apiDiscoverEndpoint.UIHostname + output.RenderInfo(fmt.Sprintf("using ui host '%s' from api discovery for context\n", uiHostname), outputOptions) + } + if apiDiscoverEndpoint.WebhookEndpoint != "" { + webhookEndpoint = apiDiscoverEndpoint.WebhookEndpoint + output.RenderInfo(fmt.Sprintf("using webhook endpoint '%s' from api discovery for context\n", webhookEndpoint), outputOptions) + } + } + if authEndpoint == "" { + prompt := promptui.Prompt{ + Label: fmt.Sprintf("Enter authentication endpoint for context %s / %s", n, apiHostname), + Default: "https://keycloak.example.com/auth", + AllowEdit: true, + } + authEndpoint, err = prompt.Run() + if err != nil { + return err + } + } + uConfig := config.UserConfig{ + Name: n, + Grant: &oauth2.Token{ + AccessToken: l.Token, + }, + SSHKey: l.SSHKey, + } + if l.PublicKeyIdentities != nil { + uConfig.PublicKeyIdentities = l.PublicKeyIdentities + } + cConfig := config.ContextConfig{ + Name: n, + APIHostname: apiHostname, + TokenHost: tokenHost, + TokenPort: iTokenPort, + AuthenticationEndpoint: authEndpoint, + Version: l.Version, + UIHostname: uiHostname, + WebhookEndpoint: webhookEndpoint, + } + err = cc.NewUser(uConfig) + if err != nil { + return err + } + err = cc.NewContext(cConfig, uConfig.Name) + if err != nil { + return err + } + } + // convert old global feature flags if defined to replacement feature flags + if lc.EnvironmentFromDirectory { + cc.SetGlobalFeature("cli", "environment-from-directory", true) + } + if lc.UpdateCheckDisable { + cc.SetGlobalFeature("cli", "disable-update-check", true) + } + if lc.StrictHostKeyChecking == "no" { + cc.SetGlobalFeature("cli", "no-strict-host-key-checking", true) + } + cc.SetDefaultContext(lc.Default) + if writeConfig { + if err := cc.WriteConfig(); err != nil { + return err + } + } else { + cb, _ := yaml.Marshal(cc) + fmt.Println(string(cb)) + fmt.Println("configuration file not written, to save converted config run this again with the flag --write-config") + } + return nil +} + +// helper function for creating an initial configuration with prompts if no legacy or new config is detected +func createInitialConfig() error { + lConfig, _ = config.LoadConfig(true) + uPrompt := promptui.Prompt{ + Label: "Enter a user name", + Default: "user", + AllowEdit: true, + } + userName, err := uPrompt.Run() + if err != nil { + return fmt.Errorf("unable to create configuration: %s", err.Error()) + } + lConfig.NewUser(config.UserConfig{ + Name: userName, + }) + cPrompt := promptui.Prompt{ + Label: "Enter a context name", + Default: "lagoon", + AllowEdit: true, + } + cName, err := cPrompt.Run() + if err != nil { + return fmt.Errorf("unable to create configuration: %s", err.Error()) + } + prompt := promptui.Prompt{ + Label: "Enter API hostname, omit the /graphql path", + Default: "https://api.example.com", + AllowEdit: true, + } + apiHostname, err := prompt.Run() + if err != nil { + return fmt.Errorf("unable to create configuration: %s", err.Error()) + } + var authEndpoint, tokenHost, uiHostname, webhookEndpoint string + var iTokenPort int + apiHostname = strings.TrimSuffix(apiHostname, "/graphql") // remove the suffix just incase + apiDiscoverEndpoint, err := discovery.Discover(apiHostname) + if err != nil { + // throw error here, even if the endpoint is unreachable + return err + } + if apiDiscoverEndpoint != nil { + // try and extract information from the endpoint + outputOptions.MessagePrefix = fmt.Sprintf("context %s", cName) + if apiDiscoverEndpoint.AuthorizationEndpoint != "" { + authEndpoint = apiDiscoverEndpoint.AuthorizationEndpoint + output.RenderInfo(fmt.Sprintf("using authentication endpoint '%s' from api discovery for context\n", authEndpoint), outputOptions) + } + if apiDiscoverEndpoint.SSHTokenExchange.TokenHost != "" { + tokenHost = apiDiscoverEndpoint.SSHTokenExchange.TokenHost + output.RenderInfo(fmt.Sprintf("using token host endpoint '%s' from api discovery for context\n", tokenHost), outputOptions) + } + if apiDiscoverEndpoint.SSHTokenExchange.TokenPort != 0 { + iTokenPort = apiDiscoverEndpoint.SSHTokenExchange.TokenPort + output.RenderInfo(fmt.Sprintf("using token host port '%d' from api discovery for context\n", iTokenPort), outputOptions) + } + if apiDiscoverEndpoint.UIHostname != "" { + uiHostname = apiDiscoverEndpoint.UIHostname + output.RenderInfo(fmt.Sprintf("using ui host '%s' from api discovery for context\n", uiHostname), outputOptions) + } + if apiDiscoverEndpoint.WebhookEndpoint != "" { + webhookEndpoint = apiDiscoverEndpoint.WebhookEndpoint + output.RenderInfo(fmt.Sprintf("using webhook endpoint '%s' from api discovery for context\n", webhookEndpoint), outputOptions) + } + } + // if the endpoint has no values, prompt for them + if authEndpoint == "" { + prompt2 := promptui.Prompt{ + Label: fmt.Sprintf("Enter authentication endpoint for context %s / %s", cName, apiHostname), + Default: "https://keycloak.example.com/auth", + AllowEdit: true, + } + authEndpoint, err = prompt2.Run() + if err != nil { + return err + } + } + if tokenHost == "" { + prompt3 := promptui.Prompt{ + Label: fmt.Sprintf("Enter SSH token endpoint for context %s / %s", cName, apiHostname), + Default: "token.example.com", + AllowEdit: true, + } + tokenHost, err = prompt3.Run() + if err != nil { + return fmt.Errorf("unable to create configuration: %s", err.Error()) + } + } + if iTokenPort == 0 { + prompt4 := promptui.Prompt{ + Label: fmt.Sprintf("Enter SSH token endpoint port for context %s / %s", cName, apiHostname), + Default: "22", + AllowEdit: true, + } + tokenPort, err := prompt4.Run() + if err != nil { + return fmt.Errorf("unable to create configuration: %s", err.Error()) + } + iTokenPort, err = strconv.Atoi(tokenPort) + if err != nil { + return fmt.Errorf("provided port is not a number: %s", err.Error()) + } + } + lConfig.NewContext(config.ContextConfig{ + Name: cName, + APIHostname: apiHostname, + AuthenticationEndpoint: authEndpoint, + TokenHost: tokenHost, + TokenPort: iTokenPort, + UIHostname: uiHostname, + WebhookEndpoint: webhookEndpoint, + }, userName) + lConfig.SetDefaultContext(cName) + err = lConfig.WriteConfig() + if err != nil { + return fmt.Errorf("unable to create configuration: %s", err.Error()) + } + return nil +} diff --git a/cmd/delete.go b/cmd/delete.go index 85dea02b..620544be 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -9,7 +9,7 @@ var deleteCmd = &cobra.Command{ Aliases: []string{"del"}, Short: "Delete a project, or delete notifications and variables from projects or environments", PersistentPreRun: func(cmd *cobra.Command, args []string) { - validateToken(lagoonCLIConfig.Current) // get a new token if the current one is invalid + validateToken(lContext.Name) // get a new token if the current one is invalid }, } @@ -18,7 +18,7 @@ var deleteNotificationCmd = &cobra.Command{ Aliases: []string{"n"}, Short: "Delete notifications or delete notifications from projects", PersistentPreRun: func(cmd *cobra.Command, args []string) { - validateToken(lagoonCLIConfig.Current) // get a new token if the current one is invalid + validateToken(lContext.Name) // get a new token if the current one is invalid }, } diff --git a/cmd/deploy.go b/cmd/deploy.go index 48543185..61fe2a3b 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -7,10 +7,9 @@ import ( "github.com/uselagoon/lagoon-cli/pkg/output" - lclient "github.com/uselagoon/machinery/api/lagoon/client" - "github.com/spf13/cobra" "github.com/uselagoon/machinery/api/lagoon" + lclient "github.com/uselagoon/machinery/api/lagoon/client" "github.com/uselagoon/machinery/api/schema" ) @@ -28,7 +27,7 @@ This branch may or may not already exist in lagoon, if it already exists you may use 'lagoon deploy latest' instead`, Aliases: []string{"b"}, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -61,13 +60,12 @@ use 'lagoon deploy latest' instead`, } if yesNo(fmt.Sprintf("You are attempting to deploy branch '%s' for project '%s', are you sure?", branch, cmdProjectName)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) depBranch := &schema.DeployEnvironmentBranchInput{ Branch: branch, @@ -96,7 +94,7 @@ var deployPromoteCmd = &cobra.Command{ Short: "Promote an environment", Long: "Promote one environment to another", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -129,13 +127,12 @@ var deployPromoteCmd = &cobra.Command{ } if yesNo(fmt.Sprintf("You are attempting to promote environment '%s' to '%s' for project '%s', are you sure?", sourceEnvironment, destinationEnvironment, cmdProjectName)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) result, err := lagoon.DeployPromote(context.TODO(), &schema.DeployEnvironmentPromoteInput{ SourceEnvironment: sourceEnvironment, @@ -163,7 +160,7 @@ var deployLatestCmd = &cobra.Command{ Long: `Deploy latest environment This environment should already exist in lagoon. It is analogous with the 'Deploy' button in the Lagoon UI`, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { @@ -189,13 +186,12 @@ This environment should already exist in lagoon. It is analogous with the 'Deplo } if yesNo(fmt.Sprintf("You are attempting to deploy the latest environment '%s' for project '%s', are you sure?", cmdProjectEnvironment, cmdProjectName)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) result, err := lagoon.DeployLatest(context.TODO(), &schema.DeployEnvironmentLatestInput{ Environment: schema.EnvironmentInput{ @@ -226,7 +222,7 @@ var deployPullrequestCmd = &cobra.Command{ Long: `Deploy a pullrequest This pullrequest may not already exist as an environment in lagoon.`, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -274,15 +270,13 @@ This pullrequest may not already exist as an environment in lagoon.`, return err } if yesNo(fmt.Sprintf("You are attempting to deploy pull request '%v' for project '%s', are you sure?", prNumber, cmdProjectName)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) - result, err := lagoon.DeployPullRequest(context.TODO(), &schema.DeployEnvironmentPullrequestInput{ Project: schema.ProjectInput{ Name: cmdProjectName, diff --git a/cmd/deploytarget.go b/cmd/deploytarget.go index 6fe0704f..4703f742 100644 --- a/cmd/deploytarget.go +++ b/cmd/deploytarget.go @@ -89,13 +89,13 @@ var addDeployTargetCmd = &cobra.Command{ if err != nil { return err } - current := lagoonCLIConfig.Current - lagoonToken := lagoonCLIConfig.Lagoons[current].Token + + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &lagoonToken, + lContext.ContextConfig.Version, + &utoken, debug) if yesNo(fmt.Sprintf("You are attempting to add '%s' DeployTarget, are you sure?", addDeployTarget.Name)) { @@ -208,13 +208,12 @@ var updateDeployTargetCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - lagoonToken := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &lagoonToken, + lContext.ContextConfig.Version, + &utoken, debug) updateDeployTarget := &schema.UpdateDeployTargetInput{ @@ -300,13 +299,12 @@ var deleteDeployTargetCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) deleteDeployTarget := &schema.DeleteDeployTargetInput{ @@ -333,7 +331,7 @@ var addDeployTargetToOrganizationCmd = &cobra.Command{ Aliases: []string{"org-dt"}, Short: "Add a deploy target to an Organization", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -352,13 +350,13 @@ var addDeployTargetToOrganizationCmd = &cobra.Command{ if err := requiredInputCheck("Organization name", organizationName, "Deploy Target", strconv.Itoa(int(deploytarget))); err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) organization, err := lagoon.GetOrganizationByName(context.TODO(), organizationName, lc) @@ -397,7 +395,7 @@ var removeDeployTargetFromOrganizationCmd = &cobra.Command{ Aliases: []string{"org-dt"}, Short: "Remove a deploy target from an Organization", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -417,13 +415,12 @@ var removeDeployTargetFromOrganizationCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) organization, err := lagoon.GetOrganizationByName(context.TODO(), organizationName, lc) diff --git a/cmd/deploytargetconfig.go b/cmd/deploytargetconfig.go index e5e94a2e..b6951494 100644 --- a/cmd/deploytargetconfig.go +++ b/cmd/deploytargetconfig.go @@ -20,7 +20,7 @@ var addDeployTargetConfigCmd = &cobra.Command{ Hidden: false, Short: "Add deploytarget config to a project", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -48,13 +48,12 @@ var addDeployTargetConfigCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) addDeployTargetConfig := &schema.AddDeployTargetConfigInput{ @@ -110,7 +109,7 @@ var updateDeployTargetConfigCmd = &cobra.Command{ Hidden: false, Short: "Update a deploytarget config", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -141,13 +140,12 @@ var updateDeployTargetConfigCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) updateDeployTargetConfig := &schema.UpdateDeployTargetConfigInput{ @@ -205,7 +203,7 @@ var deleteDeployTargetConfigCmd = &cobra.Command{ Hidden: false, Short: "Delete a deploytarget config", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -220,13 +218,12 @@ var deleteDeployTargetConfigCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) if yesNo(fmt.Sprintf("You are attempting to delete deploytarget configuration with id '%d' from project '%s', are you sure?", id, cmdProjectName)) { @@ -250,7 +247,7 @@ var listDeployTargetConfigsCmd = &cobra.Command{ Hidden: false, Short: "List deploytarget configs for a project", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -262,13 +259,12 @@ var listDeployTargetConfigsCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) deployTargetConfigs, err := lagoon.GetDeployTargetConfigsByProjectName(context.TODO(), cmdProjectName, lc) diff --git a/cmd/environment.go b/cmd/environment.go index 4311f1a1..cc5ee531 100644 --- a/cmd/environment.go +++ b/cmd/environment.go @@ -18,7 +18,7 @@ var deleteEnvCmd = &cobra.Command{ Aliases: []string{"e"}, Short: "Delete an environment", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -29,13 +29,12 @@ var deleteEnvCmd = &cobra.Command{ if err := requiredInputCheck("Project name", cmdProjectName, "Environment name", cmdProjectEnvironment); err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) if yesNo(fmt.Sprintf("You are attempting to delete environment '%s' from project '%s', are you sure?", cmdProjectEnvironment, cmdProjectName)) { environment, err := lagoon.DeleteEnvironment(context.TODO(), cmdProjectEnvironment, cmdProjectName, true, lc) @@ -57,7 +56,7 @@ var updateEnvironmentCmd = &cobra.Command{ Aliases: []string{"e"}, Short: "Update an environment", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -110,13 +109,12 @@ var updateEnvironmentCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) environment, err := lagoon.GetEnvironmentByNameAndProjectName(context.TODO(), cmdProjectEnvironment, cmdProjectName, lc) @@ -183,14 +181,12 @@ var listBackupsCmd = &cobra.Command{ if err := requiredInputCheck("Project name", cmdProjectName, "Environment name", cmdProjectEnvironment); err != nil { return err } - - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) backupsResult, err := lagoon.GetBackupsForEnvironmentByNameAndProjectName(context.TODO(), cmdProjectEnvironment, cmdProjectName, lc) @@ -250,14 +246,12 @@ This returns a direct URL to the backup, this is a signed download link with a l if err := requiredInputCheck("Project name", cmdProjectName, "Environment name", cmdProjectEnvironment); err != nil { return err } - - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) backupsResult, err := lagoon.GetBackupsForEnvironmentByNameAndProjectName(context.TODO(), cmdProjectEnvironment, cmdProjectName, lc) diff --git a/cmd/get.go b/cmd/get.go index 0701bb31..739c4285 100644 --- a/cmd/get.go +++ b/cmd/get.go @@ -25,7 +25,7 @@ var getCmd = &cobra.Command{ Aliases: []string{"g"}, Short: "Get info on a resource", PersistentPreRun: func(cmd *cobra.Command, args []string) { - validateToken(lagoonCLIConfig.Current) // get a new token if the current one is invalid + validateToken(lContext.Name) // get a new token if the current one is invalid }, } @@ -34,7 +34,7 @@ var getProjectCmd = &cobra.Command{ Aliases: []string{"p"}, Short: "Get details about a project", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -48,13 +48,12 @@ var getProjectCmd = &cobra.Command{ if err := requiredInputCheck("Project name", cmdProjectName); err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) project, err := lagoon.GetProjectByName(context.TODO(), cmdProjectName, lc) @@ -157,13 +156,12 @@ This returns information about a deployment, the logs of this build can also be return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) deployment, err := lagoon.GetDeploymentByName(context.TODO(), cmdProjectName, cmdProjectEnvironment, buildName, showLogs, lc) if err != nil { @@ -216,7 +214,7 @@ var getEnvironmentCmd = &cobra.Command{ Aliases: []string{"e"}, Short: "Get details about an environment", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -230,13 +228,12 @@ var getEnvironmentCmd = &cobra.Command{ if err := requiredInputCheck("Project name", cmdProjectName, "Environment name", cmdProjectEnvironment); err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) project, err := lagoon.GetProjectByName(context.TODO(), cmdProjectName, lc) @@ -307,7 +304,7 @@ var getProjectKeyCmd = &cobra.Command{ Aliases: []string{"pk"}, Short: "Get a projects public key", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -318,13 +315,12 @@ var getProjectKeyCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) projectKey, err := lagoon.GetProjectKeyByName(context.TODO(), cmdProjectName, revealValue, lc) @@ -366,11 +362,10 @@ var getToken = &cobra.Command{ Aliases: []string{"tk"}, Short: "Generates a Lagoon auth token (for use in, for example, graphQL queries)", RunE: func(cmd *cobra.Command, args []string) error { - token, err := retrieveTokenViaSsh() - if err != nil { + if err := validateTokenE(lContext.Name); err != nil { // use existing token or fetch a new one if required return err } - fmt.Println(token) + fmt.Println(lUser.UserConfig.Grant.AccessToken) return nil }, } @@ -395,13 +390,12 @@ var getOrganizationCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) organization, err := lagoon.GetOrganizationByName(context.TODO(), organizationName, lc) if err != nil { diff --git a/cmd/groups.go b/cmd/groups.go index ec779b29..0a691f14 100644 --- a/cmd/groups.go +++ b/cmd/groups.go @@ -3,11 +3,13 @@ package cmd import ( "context" "fmt" - "slices" "strings" - "github.com/uselagoon/machinery/api/lagoon" + "slices" + lclient "github.com/uselagoon/machinery/api/lagoon/client" + + "github.com/uselagoon/machinery/api/lagoon" "github.com/uselagoon/machinery/api/schema" "github.com/spf13/cobra" @@ -21,7 +23,7 @@ var addGroupCmd = &cobra.Command{ Short: "Add a group to Lagoon, or add a group to an organization", Long: "To add a group to an organization, you'll need to include the `organization` flag and provide the name of the organization. You need to be an owner of this organization to do this.\nIf you're the organization owner and want to grant yourself ownership to this group to be able to deploy projects that may be added to it, specify the `owner` flag, otherwise you will still be able to add and remove users without being an owner", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -44,13 +46,12 @@ var addGroupCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) if organizationName != "" { @@ -100,7 +101,7 @@ var addUserToGroupCmd = &cobra.Command{ Aliases: []string{"ug"}, Short: "Add a user to a group in lagoon", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -141,13 +142,12 @@ var addUserToGroupCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) userGroupRole := &schema.UserGroupRoleInput{ @@ -174,7 +174,7 @@ var addProjectToGroupCmd = &cobra.Command{ Aliases: []string{"pg"}, Short: "Add a project to a group in lagoon", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -200,13 +200,12 @@ var addProjectToGroupCmd = &cobra.Command{ }, } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) _, err = lagoon.AddProjectToGroup(context.TODO(), projectGroup, lc) @@ -228,7 +227,7 @@ var deleteUserFromGroupCmd = &cobra.Command{ Aliases: []string{"ug"}, Short: "Delete a user from a group in lagoon", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -253,13 +252,12 @@ var deleteUserFromGroupCmd = &cobra.Command{ GroupName: groupName, } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) if yesNo(fmt.Sprintf("You are attempting to delete user '%s' from group '%s', are you sure?", userEmail, groupName)) { @@ -286,7 +284,7 @@ var deleteProjectFromGroupCmd = &cobra.Command{ Aliases: []string{"pg"}, Short: "Delete a project from a group in lagoon", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -312,13 +310,12 @@ var deleteProjectFromGroupCmd = &cobra.Command{ }, } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) if yesNo(fmt.Sprintf("You are attempting to delete project '%s' from group '%s', are you sure?", projectGroup.Project.Name, projectGroup.Groups[0].Name)) { @@ -353,13 +350,12 @@ var deleteGroupCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) if yesNo(fmt.Sprintf("You are attempting to delete group '%s', are you sure?", groupName)) { diff --git a/cmd/import.go b/cmd/import.go index 3fda1329..dfeb4717 100644 --- a/cmd/import.go +++ b/cmd/import.go @@ -19,7 +19,7 @@ var importCmd = &cobra.Command{ By default this command will exit on encountering an error (such as an existing object). You can get it to continue anyway with --keep-going. To disable any prompts, use --force.`, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { importFile, err := cmd.Flags().GetString("import-file") @@ -39,21 +39,16 @@ You can get it to continue anyway with --keep-going. To disable any prompts, use return err } - current := lagoonCLIConfig.Current + current := lContext.Name if !yesNo(fmt.Sprintf( `Are you sure you want to import config from %s into "%s" lagoon?`, importFile, current)) { return nil // user cancelled } - - err = setConfigDefaultVersion(&lagoonCLIConfig, current, "1.0.0") - if err != nil { - return err - } lc := client.New( - lagoonCLIConfig.Lagoons[current].GraphQL, - lagoonCLIConfig.Lagoons[current].Token, - lagoonCLIConfig.Lagoons[current].Version, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), + lUser.UserConfig.Grant.AccessToken, + lContext.ContextConfig.Version, lagoonCLIVersion, debug) @@ -83,7 +78,7 @@ var exportCmd = &cobra.Command{ Long: `Export lagoon output to yaml You must specify to export a specific project by using the '-p ' flag`, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { project, err := cmd.Flags().GetString("project") @@ -102,21 +97,17 @@ You must specify to export a specific project by using the '-p ' f return err } - current := lagoonCLIConfig.Current + current := lContext.Name if !yesNo(fmt.Sprintf( `Are you sure you want to export lagoon config for %s on "%s" lagoon?`, project, current)) { return nil // user cancelled } - err = setConfigDefaultVersion(&lagoonCLIConfig, current, "1.0.0") - if err != nil { - return err - } lc := client.New( - lagoonCLIConfig.Lagoons[current].GraphQL, - lagoonCLIConfig.Lagoons[current].Token, - lagoonCLIConfig.Lagoons[current].Version, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), + lUser.UserConfig.Grant.AccessToken, + lContext.ContextConfig.Version, lagoonCLIVersion, debug) diff --git a/cmd/list.go b/cmd/list.go index 388108c8..2370f705 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -23,7 +23,7 @@ var listCmd = &cobra.Command{ Use: "list", Short: "List projects, environments, deployments, variables or notifications", PersistentPreRun: func(cmd *cobra.Command, args []string) { - validateToken(lagoonCLIConfig.Current) // get a new token if the current one is invalid + validateToken(lContext.Name) // get a new token if the current one is invalid }, } @@ -43,13 +43,12 @@ var listProjectsCmd = &cobra.Command{ // if err != nil { // return err // } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) projects, err := lagoon.ListAllProjects(context.TODO(), lc) @@ -136,13 +135,12 @@ var listDeployTargetsCmd = &cobra.Command{ if err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) deploytargets, err := lagoon.ListDeployTargets(context.TODO(), lc) if err != nil { @@ -212,13 +210,12 @@ var listGroupsCmd = &cobra.Command{ if err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) groups, err := lagoon.ListAllGroups(context.TODO(), lc) @@ -273,13 +270,12 @@ var listGroupProjectsCmd = &cobra.Command{ } } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) var groupProjects *[]schema.Group @@ -335,7 +331,7 @@ var listEnvironmentsCmd = &cobra.Command{ Aliases: []string{"e"}, Short: "List environments for a project (alias: e)", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -346,13 +342,12 @@ var listEnvironmentsCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) environments, err := lagoon.GetEnvironmentsByProjectName(context.TODO(), cmdProjectName, lc) if err != nil { @@ -408,13 +403,12 @@ var listVariablesCmd = &cobra.Command{ if err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) in := &schema.EnvVariableByProjectEnvironmentNameInput{ Project: cmdProjectName, @@ -485,13 +479,12 @@ var listDeploymentsCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) deployments, err := lagoon.GetDeploymentsByEnvironmentAndProjectName(context.TODO(), cmdProjectName, cmdProjectEnvironment, lc) @@ -541,13 +534,12 @@ var listTasksCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) tasks, err := lagoon.GetTasksByEnvironmentAndProjectName(context.TODO(), cmdProjectName, cmdProjectEnvironment, lc) @@ -601,13 +593,12 @@ Without a group name, this query may time out in large Lagoon instalschema.`, if err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) data := []output.Data{} if groupName != "" { @@ -669,13 +660,12 @@ This query can take a long time to run if there are a lot of users.`, if err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) allUsers, err := lagoon.AllUsers(context.TODO(), schema.AllUsersFilter{ Email: emailAddress, @@ -723,13 +713,12 @@ var listUsersGroupsCmd = &cobra.Command{ if err := requiredInputCheck("Email Address", emailAddress); err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) allUsers, err := lagoon.GetUserByEmail(context.TODO(), emailAddress, lc) if err != nil { @@ -771,13 +760,12 @@ var listInvokableTasks = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) tasks, err := lagoon.GetInvokableAdvancedTaskDefinitionsByEnvironmentAndProjectName(context.TODO(), cmdProjectName, cmdProjectEnvironment, lc) @@ -811,7 +799,7 @@ var listNotificationCmd = &cobra.Command{ Aliases: []string{"n"}, Short: "List all notifications or notifications on projects", PersistentPreRun: func(cmd *cobra.Command, args []string) { - validateToken(lagoonCLIConfig.Current) // get a new token if the current one is invalid + validateToken(lContext.Name) // get a new token if the current one is invalid }, } @@ -820,7 +808,7 @@ var listProjectGroupsCmd = &cobra.Command{ Aliases: []string{"pg"}, Short: "List groups in a project (alias: pg)", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -831,13 +819,12 @@ var listProjectGroupsCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) projectGroups, err := lagoon.GetProjectGroups(context.TODO(), cmdProjectName, lc) if err != nil { @@ -890,13 +877,12 @@ var listOrganizationProjectsCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) organization, err := lagoon.GetOrganizationByName(context.TODO(), organizationName, lc) @@ -953,13 +939,12 @@ var listOrganizationGroupsCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) organization, err := lagoon.GetOrganizationByName(context.TODO(), organizationName, lc) @@ -1020,13 +1005,12 @@ var listOrganizationDeployTargetsCmd = &cobra.Command{ return fmt.Errorf("missing arguments: Organization name or ID is not defined") } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) deployTargets, err := lagoon.ListDeployTargetsByOrganizationNameOrID(context.TODO(), nullStrCheck(organizationName), nullUintCheck(organizationID), lc) if err != nil { @@ -1078,13 +1062,12 @@ var listOrganizationUsersCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) users, err := lagoon.UsersByOrganizationName(context.TODO(), organizationName, lc) if err != nil { @@ -1131,13 +1114,12 @@ var listOrganizationAdminsCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) users, err := lagoon.ListOrganizationAdminsByName(context.TODO(), organizationName, lc) if err != nil { @@ -1186,13 +1168,12 @@ var listOrganizationsCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) organizations, err := lagoon.AllOrganizations(context.TODO(), lc) diff --git a/cmd/login.go b/cmd/login.go index 5aa16b44..c35f2d74 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -2,26 +2,40 @@ package cmd import ( "bytes" + "encoding/json" "fmt" "net" "os" - "path/filepath" - "strings" "github.com/spf13/cobra" lagoonssh "github.com/uselagoon/lagoon-cli/pkg/lagoon/ssh" + auth "github.com/uselagoon/machinery/utils/auth" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" + "golang.org/x/oauth2" terminal "golang.org/x/term" ) var loginCmd = &cobra.Command{ - Use: "login", - Short: "Log into a Lagoon instance", + Use: "login", + Short: "Login and refresh a token", + Long: `Login and refresh a token +Optionally reset the token to force a new one to be retrieved`, Aliases: []string{"l"}, - Run: func(cmd *cobra.Command, args []string) { - validateToken(lagoonCLIConfig.Current) // get a new token if the current one is invalid - fmt.Println("Token fetched and saved.") + RunE: func(cmd *cobra.Command, args []string) error { + reset, err := cmd.Flags().GetBool("reset-token") + if err != nil { + return err + } + if reset { + lUser.UserConfig.Grant = &oauth2.Token{} // reset the token + lConfig.WriteConfig() // write out the blank token + } + if err := validateTokenE(lContext.Name); err != nil { + return err + } + fmt.Printf("token for user %s and context %s fetched and saved.\n", lUser.Name, lContext.Name) + return nil }, } @@ -109,30 +123,37 @@ func publicKey(path, publicKeyOverride string, publicKeyIdentities []string, ski } func loginToken() error { - out, err := retrieveTokenViaSsh() + // check if the ssh-token only feature is enabled for this context for cli generally + sshTokenOnly, err := lConfig.GetFeature(lContext.Name, configFeaturePrefix, "ssh-token") if err != nil { return err } - - lc := lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current] - lc.Token = out - lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current] = lc - if err = writeLagoonConfig(&lagoonCLIConfig, filepath.Join(configFilePath, configName+configExtension)); err != nil { - return fmt.Errorf("couldn't write config: %v", err) - } - if err = versionCheck(lagoonCLIConfig.Current); err != nil { - return fmt.Errorf("couldn't check version: %v", err) + if lContext.ContextConfig.AuthenticationEndpoint == "" || sshTokenOnly { + // if no keycloak url is found in the config, perform a token request via ssh + // or the ssh-token override is set to enforce tokens via ssh (accounts in CI jobs) + out, err := retrieveTokenViaSsh() + if err != nil { + return err + } + lUser.UserConfig.Grant = out + } else { + // otherwise get a token via keycloak + token := &oauth2.Token{} + if lUser.UserConfig.Grant != nil { + token = lUser.UserConfig.Grant + } + _ = auth.TokenRequest(lContext.ContextConfig.AuthenticationEndpoint, "lagoon", "", token) + lUser.UserConfig.Grant = token } - - return nil + return lConfig.WriteConfig() } -func retrieveTokenViaSsh() (string, error) { +func retrieveTokenViaSsh() (*oauth2.Token, error) { skipAgent := false privateKey := fmt.Sprintf("%s/.ssh/id_rsa", userPath) // if the user has a key defined in their lagoon cli config, use it - if lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].SSHKey != "" { - privateKey = lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].SSHKey + if lUser.UserConfig.SSHKey != "" { + privateKey = lUser.UserConfig.SSHKey skipAgent = true } // otherwise check if one has been provided by the override flag @@ -141,14 +162,14 @@ func retrieveTokenViaSsh() (string, error) { skipAgent = true } ignoreHostKey, acceptNewHostKey := lagoonssh.CheckStrictHostKey(strictHostKeyCheck) - sshHost := fmt.Sprintf("%s:%s", - lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].HostName, - lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].Port) + sshHost := fmt.Sprintf("%s:%d", + lContext.ContextConfig.TokenHost, + lContext.ContextConfig.TokenPort) hkcb, hkalgo, err := lagoonssh.InteractiveKnownHosts(userPath, sshHost, ignoreHostKey, acceptNewHostKey) if err != nil { - return "", fmt.Errorf("couldn't get ~/.ssh/known_hosts: %v", err) + return nil, fmt.Errorf("couldn't get ~/.ssh/known_hosts: %v", err) } - authMethod, closeSSHAgent := publicKey(privateKey, cmdPubkeyIdentity, lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].PublicKeyIdentities, skipAgent) + authMethod, closeSSHAgent := publicKey(privateKey, cmdPubkeyIdentity, lUser.UserConfig.PublicKeyIdentities, skipAgent) config := &ssh.ClientConfig{ User: "lagoon", Auth: []ssh.AuthMethod{ @@ -166,18 +187,24 @@ func retrieveTokenViaSsh() (string, error) { conn, err := ssh.Dial("tcp", sshHost, config) if err != nil { - return "", fmt.Errorf("unable to authenticate or connect to host %s\nthere may be an issue determining which ssh-key to use, or there may be an issue establishing a connection to the host\nthe error returned was: %v", sshHost, err) + return nil, fmt.Errorf("unable to authenticate or connect to host %s\nthere may be an issue determining which ssh-key to use, or there may be an issue establishing a connection to the host\nthe error returned was: %v", sshHost, err) } defer conn.Close() session, err := conn.NewSession() if err != nil { - return "", fmt.Errorf("unable to establish ssh session, error from attempt is: %v", err) + return nil, fmt.Errorf("unable to establish ssh session, error from attempt is: %v", err) } - out, err := session.CombinedOutput("token") + out, err := session.CombinedOutput("grant") if err != nil { - return "", fmt.Errorf("unable to get token: %v", err) + return nil, fmt.Errorf("unable to get token: %v", err) } - return strings.TrimSpace(string(out)), err + token := &oauth2.Token{} + json.Unmarshal(out, token) + return token, err +} + +func init() { + loginCmd.Flags().Bool("reset-token", false, "clear the token before attempting to log in") } diff --git a/cmd/logs.go b/cmd/logs.go index e484275f..e3f2b999 100644 --- a/cmd/logs.go +++ b/cmd/logs.go @@ -58,19 +58,17 @@ func generateLogsCommand(service, container string, lines uint, } func getSSHHostPort(environmentName string, debug bool) (string, string, string, bool, error) { - current := lagoonCLIConfig.Current - // set the default ssh host and port to the core ssh endpoint - sshHost := lagoonCLIConfig.Lagoons[current].HostName - sshPort := lagoonCLIConfig.Lagoons[current].Port - token := lagoonCLIConfig.Lagoons[current].Token + sshHost := lContext.ContextConfig.TokenHost + sshPort := fmt.Sprintf("%d", lContext.ContextConfig.TokenPort) portal := false - // get SSH Portal endpoint if required + // if the config for this lagoon is set to use ssh portal support, handle that here + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) ctx, cancel := context.WithTimeout(context.Background(), connTimeout) defer cancel() @@ -103,8 +101,8 @@ func getSSHClientConfig(username, host string, ignoreHostKey, acceptNewHostKey b skipAgent := false privateKey := fmt.Sprintf("%s/.ssh/id_rsa", userPath) // check for user-defined key - if lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].SSHKey != "" { - privateKey = lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].SSHKey + if lUser.UserConfig.SSHKey != "" { + privateKey = lUser.UserConfig.SSHKey skipAgent = true } // check for specified key @@ -119,7 +117,7 @@ func getSSHClientConfig(username, host string, ignoreHostKey, acceptNewHostKey b } // configure an SSH client session - authMethod, closeSSHAgent := publicKey(privateKey, cmdPubkeyIdentity, lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].PublicKeyIdentities, skipAgent) + authMethod, closeSSHAgent := publicKey(privateKey, cmdPubkeyIdentity, lUser.UserConfig.PublicKeyIdentities, skipAgent) return &ssh.ClientConfig{ User: username, Auth: []ssh.AuthMethod{authMethod}, @@ -134,7 +132,7 @@ var logsCmd = &cobra.Command{ Short: "Display logs for a service of an environment and project", RunE: func(cmd *cobra.Command, args []string) error { // validate/refresh token - validateToken(lagoonCLIConfig.Current) + validateToken(lContext.Name) // validate and parse arguments if cmdProjectName == "" || cmdProjectEnvironment == "" { return fmt.Errorf( diff --git a/cmd/notificationsemail.go b/cmd/notificationsemail.go index e89f2d6f..40a32ee8 100644 --- a/cmd/notificationsemail.go +++ b/cmd/notificationsemail.go @@ -20,7 +20,7 @@ This command is used to set up a new email notification in Lagoon. This requires It does not configure a project to send notifications to email though, you need to use project-email for that.`, Aliases: []string{"e"}, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -43,13 +43,12 @@ It does not configure a project to send notifications to email though, you need return err } if yesNo(fmt.Sprintf("You are attempting to create an email notification '%s' with email address '%s', are you sure?", name, email)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) notification := schema.AddNotificationEmailInput{ @@ -100,7 +99,7 @@ var addProjectNotificationEmailCmd = &cobra.Command{ Long: `Add an email notification to a project This command is used to add an existing email notification in Lagoon to a project.`, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -116,13 +115,12 @@ This command is used to add an existing email notification in Lagoon to a projec return err } if yesNo(fmt.Sprintf("You are attempting to add email notification '%s' to project '%s', are you sure?", name, cmdProjectName)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) notification := &schema.AddNotificationToProjectInput{ NotificationType: schema.EmailNotification, @@ -148,7 +146,7 @@ var listProjectEmailsCmd = &cobra.Command{ Aliases: []string{"pe"}, Short: "List email details about a project (alias: pe)", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -158,14 +156,12 @@ var listProjectEmailsCmd = &cobra.Command{ if err := requiredInputCheck("Project name", cmdProjectName); err != nil { return err } - - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) result, err := lagoon.GetProjectNotificationEmail(context.TODO(), cmdProjectName, lc) @@ -204,20 +200,19 @@ var listAllEmailsCmd = &cobra.Command{ Aliases: []string{"e"}, Short: "List all email notification details (alias: e)", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") if err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) result, err := lagoon.GetAllNotificationEmail(context.TODO(), lc) if err != nil { @@ -254,7 +249,7 @@ var deleteProjectEmailNotificationCmd = &cobra.Command{ Aliases: []string{"pe"}, Short: "Delete a email notification from a project", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -269,13 +264,12 @@ var deleteProjectEmailNotificationCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) project, err := lagoon.GetProjectByName(context.TODO(), cmdProjectName, lc) @@ -310,7 +304,7 @@ var deleteEmailNotificationCmd = &cobra.Command{ Aliases: []string{"e"}, Short: "Delete an email notification from Lagoon", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -326,13 +320,12 @@ var deleteEmailNotificationCmd = &cobra.Command{ } // Todo: Verify notifcation name exists - requires #PR https://github.com/uselagoon/lagoon/pull/3740 if yesNo(fmt.Sprintf("You are attempting to delete email notification '%s', are you sure?", name)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) result, err := lagoon.DeleteNotificationEmail(context.TODO(), name, lc) if err != nil { @@ -353,7 +346,7 @@ var updateEmailNotificationCmd = &cobra.Command{ Aliases: []string{"e"}, Short: "Update an existing email notification", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -384,13 +377,12 @@ var updateEmailNotificationCmd = &cobra.Command{ } if yesNo(fmt.Sprintf("You are attempting to update email notification '%s', are you sure?", name)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) notification := &schema.UpdateNotificationEmailInput{ diff --git a/cmd/notificationsrocketchat.go b/cmd/notificationsrocketchat.go index 119264a7..55e23ab4 100644 --- a/cmd/notificationsrocketchat.go +++ b/cmd/notificationsrocketchat.go @@ -21,7 +21,7 @@ var addNotificationRocketchatCmd = &cobra.Command{ This command is used to set up a new RocketChat notification in Lagoon. This requires information to talk to RocketChat like the webhook URL and the name of the channel. It does not configure a project to send notifications to RocketChat though, you need to use project-rocketchat for that.`, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -48,13 +48,12 @@ It does not configure a project to send notifications to RocketChat though, you return err } if yesNo(fmt.Sprintf("You are attempting to create an RocketChat notification '%s' with webhook '%s' channel '%s', are you sure?", name, webhook, channel)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) notification := schema.AddNotificationRocketChatInput{ @@ -108,7 +107,7 @@ var addProjectNotificationRocketChatCmd = &cobra.Command{ Long: `Add a RocketChat notification to a project This command is used to add an existing RocketChat notification in Lagoon to a project.`, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -123,13 +122,12 @@ This command is used to add an existing RocketChat notification in Lagoon to a p return err } if yesNo(fmt.Sprintf("You are attempting to add RocketChat notification '%s' to project '%s', are you sure?", name, cmdProjectName)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) notification := &schema.AddNotificationToProjectInput{ @@ -157,7 +155,7 @@ var listProjectRocketChatsCmd = &cobra.Command{ Aliases: []string{"pr"}, Short: "List RocketChats details about a project (alias: pr)", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -167,14 +165,12 @@ var listProjectRocketChatsCmd = &cobra.Command{ if err := requiredInputCheck("Project name", cmdProjectName); err != nil { return err } - - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) result, err := lagoon.GetProjectNotificationRocketChat(context.TODO(), cmdProjectName, lc) @@ -215,20 +211,19 @@ var listAllRocketChatsCmd = &cobra.Command{ Aliases: []string{"r"}, Short: "List all RocketChats notification details (alias: r)", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") if err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) result, err := lagoon.GetAllNotificationRocketChat(context.TODO(), lc) if err != nil { @@ -267,7 +262,7 @@ var deleteProjectRocketChatNotificationCmd = &cobra.Command{ Aliases: []string{"pr"}, Short: "Delete a RocketChat notification from a project", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -282,13 +277,12 @@ var deleteProjectRocketChatNotificationCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) project, err := lagoon.GetProjectByName(context.TODO(), cmdProjectName, lc) @@ -324,7 +318,7 @@ var deleteRocketChatNotificationCmd = &cobra.Command{ Aliases: []string{"r"}, Short: "Delete a RocketChat notification from Lagoon", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -340,13 +334,12 @@ var deleteRocketChatNotificationCmd = &cobra.Command{ } // Todo: Verify notifcation name exists - requires #PR https://github.com/uselagoon/lagoon/pull/3740 if yesNo(fmt.Sprintf("You are attempting to delete RocketChat notification '%s', are you sure?", name)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) result, err := lagoon.DeleteNotificationRocketChat(context.TODO(), name, lc) if err != nil { @@ -367,7 +360,7 @@ var updateRocketChatNotificationCmd = &cobra.Command{ Aliases: []string{"r"}, Short: "Update an existing RocketChat notification", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -403,13 +396,12 @@ var updateRocketChatNotificationCmd = &cobra.Command{ } if yesNo(fmt.Sprintf("You are attempting to update RocketChat notification '%s', are you sure?", name)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) notification := &schema.UpdateNotificationRocketChatInput{ diff --git a/cmd/notificationsslack.go b/cmd/notificationsslack.go index 2ff28ac6..31530ea1 100644 --- a/cmd/notificationsslack.go +++ b/cmd/notificationsslack.go @@ -21,7 +21,7 @@ var addNotificationSlackCmd = &cobra.Command{ This command is used to set up a new Slack notification in Lagoon. This requires information to talk to Slack like the webhook URL and the name of the channel. It does not configure a project to send notifications to Slack though, you need to use project-slack for that.`, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -48,13 +48,12 @@ It does not configure a project to send notifications to Slack though, you need return err } if yesNo(fmt.Sprintf("You are attempting to create an Slack notification '%s' with webhook '%s' channel '%s', are you sure?", name, webhook, channel)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) notification := schema.AddNotificationSlackInput{ @@ -108,7 +107,7 @@ var addProjectNotificationSlackCmd = &cobra.Command{ Long: `Add a Slack notification to a project This command is used to add an existing Slack notification in Lagoon to a project.`, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -123,13 +122,12 @@ This command is used to add an existing Slack notification in Lagoon to a projec return err } if yesNo(fmt.Sprintf("You are attempting to add Slack notification '%s' to project '%s', are you sure?", name, cmdProjectName)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) notification := &schema.AddNotificationToProjectInput{ NotificationType: schema.SlackNotification, @@ -155,7 +153,7 @@ var listProjectSlacksCmd = &cobra.Command{ Aliases: []string{"ps"}, Short: "List Slacks details about a project (alias: ps)", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -165,14 +163,12 @@ var listProjectSlacksCmd = &cobra.Command{ if err := requiredInputCheck("Project name", cmdProjectName); err != nil { return err } - - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) result, err := lagoon.GetProjectNotificationSlack(context.TODO(), cmdProjectName, lc) @@ -213,20 +209,19 @@ var listAllSlacksCmd = &cobra.Command{ Aliases: []string{"s"}, Short: "List all Slacks notification details (alias: s)", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") if err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) result, err := lagoon.GetAllNotificationSlack(context.TODO(), lc) if err != nil { @@ -265,7 +260,7 @@ var deleteProjectSlackNotificationCmd = &cobra.Command{ Aliases: []string{"ps"}, Short: "Delete a Slack notification from a project", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -280,13 +275,12 @@ var deleteProjectSlackNotificationCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) project, err := lagoon.GetProjectByName(context.TODO(), cmdProjectName, lc) @@ -322,7 +316,7 @@ var deleteSlackNotificationCmd = &cobra.Command{ Aliases: []string{"s"}, Short: "Delete a Slack notification from Lagoon", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -338,13 +332,12 @@ var deleteSlackNotificationCmd = &cobra.Command{ } // Todo: Verify notifcation name exists - requires #PR https://github.com/uselagoon/lagoon/pull/3740 if yesNo(fmt.Sprintf("You are attempting to delete Slack notification '%s', are you sure?", name)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) result, err := lagoon.DeleteNotificationSlack(context.TODO(), name, lc) if err != nil { @@ -365,7 +358,7 @@ var updateSlackNotificationCmd = &cobra.Command{ Aliases: []string{"s"}, Short: "Update an existing Slack notification", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -401,15 +394,13 @@ var updateSlackNotificationCmd = &cobra.Command{ } if yesNo(fmt.Sprintf("You are attempting to update Slack notification '%s', are you sure?", name)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) - notification := &schema.UpdateNotificationSlackInput{ Name: name, Patch: patch, diff --git a/cmd/notificationsteams.go b/cmd/notificationsteams.go index 97236881..839c9c30 100644 --- a/cmd/notificationsteams.go +++ b/cmd/notificationsteams.go @@ -21,7 +21,7 @@ This command is used to set up a new Microsoft Teams notification in Lagoon. Thi It does not configure a project to send notifications to Microsoft Teams though, you need to use project-microsoftteams for that.`, Aliases: []string{"m"}, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -44,13 +44,12 @@ It does not configure a project to send notifications to Microsoft Teams though, return err } if yesNo(fmt.Sprintf("You are attempting to create a Microsoft Teams notification '%s' with webhook url '%s', are you sure?", name, webhook)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) notification := schema.AddNotificationMicrosoftTeamsInput{ @@ -101,7 +100,7 @@ var addProjectNotificationMicrosoftTeamsCmd = &cobra.Command{ Long: `Add a Microsoft Teams notification to a project This command is used to add an existing Microsoft Teams notification in Lagoon to a project.`, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -116,13 +115,12 @@ This command is used to add an existing Microsoft Teams notification in Lagoon t return err } if yesNo(fmt.Sprintf("You are attempting to add Microsoft Teams notification '%s' to project '%s', are you sure?", name, cmdProjectName)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) notification := &schema.AddNotificationToProjectInput{ NotificationType: schema.MicrosoftTeamsNotification, @@ -148,7 +146,7 @@ var listProjectMicrosoftTeamsCmd = &cobra.Command{ Aliases: []string{"pm"}, Short: "List Microsoft Teams details about a project (alias: pm)", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -158,14 +156,12 @@ var listProjectMicrosoftTeamsCmd = &cobra.Command{ if err := requiredInputCheck("Project name", cmdProjectName); err != nil { return err } - - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) result, err := lagoon.GetProjectNotificationMicrosoftTeams(context.TODO(), cmdProjectName, lc) @@ -204,20 +200,19 @@ var listAllMicrosoftTeamsCmd = &cobra.Command{ Aliases: []string{"m"}, Short: "List all Microsoft Teams notification details (alias: m)", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") if err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) result, err := lagoon.GetAllNotificationMicrosoftTeams(context.TODO(), lc) if err != nil { @@ -254,7 +249,7 @@ var deleteProjectMicrosoftTeamsNotificationCmd = &cobra.Command{ Aliases: []string{"pm"}, Short: "Delete a Microsoft Teams notification from a project", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -269,13 +264,12 @@ var deleteProjectMicrosoftTeamsNotificationCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) project, err := lagoon.GetProjectByName(context.TODO(), cmdProjectName, lc) @@ -311,7 +305,7 @@ var deleteMicrosoftTeamsNotificationCmd = &cobra.Command{ Aliases: []string{"m"}, Short: "Delete a Microsoft Teams notification from Lagoon", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -327,13 +321,12 @@ var deleteMicrosoftTeamsNotificationCmd = &cobra.Command{ } // Todo: Verify notifcation name exists - requires #PR https://github.com/uselagoon/lagoon/pull/3740 if yesNo(fmt.Sprintf("You are attempting to delete Microsoft Teams notification '%s', are you sure?", name)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) result, err := lagoon.DeleteNotificationMicrosoftTeams(context.TODO(), name, lc) if err != nil { @@ -354,7 +347,7 @@ var updateMicrosoftTeamsNotificationCmd = &cobra.Command{ Aliases: []string{"m"}, Short: "Update an existing Microsoft Teams notification", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -385,13 +378,12 @@ var updateMicrosoftTeamsNotificationCmd = &cobra.Command{ } if yesNo(fmt.Sprintf("You are attempting to update Microsoft Teams notification '%s', are you sure?", name)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) notification := &schema.UpdateNotificationMicrosoftTeamsInput{ diff --git a/cmd/notificationswebhook.go b/cmd/notificationswebhook.go index 8340fd5d..e510663e 100644 --- a/cmd/notificationswebhook.go +++ b/cmd/notificationswebhook.go @@ -21,7 +21,7 @@ This command is used to set up a new webhook notification in Lagoon. This requir It does not configure a project to send notifications to webhook though, you need to use project-webhook for that.`, Aliases: []string{"w"}, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -44,13 +44,12 @@ It does not configure a project to send notifications to webhook though, you nee return err } if yesNo(fmt.Sprintf("You are attempting to create a webhook notification '%s' with webhook url '%s', are you sure?", name, webhook)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) notification := schema.AddNotificationWebhookInput{ @@ -101,7 +100,7 @@ var addProjectNotificationWebhookCmd = &cobra.Command{ Long: `Add a webhook notification to a project This command is used to add an existing webhook notification in Lagoon to a project.`, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -116,13 +115,12 @@ This command is used to add an existing webhook notification in Lagoon to a proj return err } if yesNo(fmt.Sprintf("You are attempting to add webhook notification '%s' to project '%s', are you sure?", name, cmdProjectName)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) notification := &schema.AddNotificationToProjectInput{ NotificationType: schema.WebhookNotification, @@ -148,7 +146,7 @@ var listProjectWebhooksCmd = &cobra.Command{ Aliases: []string{"pw"}, Short: "List webhook details about a project (alias: pw)", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -158,14 +156,12 @@ var listProjectWebhooksCmd = &cobra.Command{ if err := requiredInputCheck("Project name", cmdProjectName); err != nil { return err } - - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) result, err := lagoon.GetProjectNotificationWebhook(context.TODO(), cmdProjectName, lc) @@ -204,20 +200,19 @@ var listAllWebhooksCmd = &cobra.Command{ Aliases: []string{"w"}, Short: "List all webhook notification details (alias: w)", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") if err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) result, err := lagoon.GetAllNotificationWebhook(context.TODO(), lc) if err != nil { @@ -254,7 +249,7 @@ var deleteProjectWebhookNotificationCmd = &cobra.Command{ Aliases: []string{"pw"}, Short: "Delete a webhook notification from a project", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -269,13 +264,12 @@ var deleteProjectWebhookNotificationCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) project, err := lagoon.GetProjectByName(context.TODO(), cmdProjectName, lc) @@ -311,7 +305,7 @@ var deleteWebhookNotificationCmd = &cobra.Command{ Aliases: []string{"w"}, Short: "Delete a webhook notification from Lagoon", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -327,13 +321,12 @@ var deleteWebhookNotificationCmd = &cobra.Command{ } // Todo: Verify notifcation name exists - requires #PR https://github.com/uselagoon/lagoon/pull/3740 if yesNo(fmt.Sprintf("You are attempting to delete webhook notification '%s', are you sure?", name)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) result, err := lagoon.DeleteNotificationWebhook(context.TODO(), name, lc) if err != nil { @@ -354,7 +347,7 @@ var updateWebhookNotificationCmd = &cobra.Command{ Aliases: []string{"w"}, Short: "Update an existing webhook notification", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -385,13 +378,12 @@ var updateWebhookNotificationCmd = &cobra.Command{ } if yesNo(fmt.Sprintf("You are attempting to update webhook notification '%s', are you sure?", name)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) notification := &schema.UpdateNotificationWebhookInput{ diff --git a/cmd/organization.go b/cmd/organization.go index 5ef92789..d48edd0e 100644 --- a/cmd/organization.go +++ b/cmd/organization.go @@ -16,7 +16,7 @@ var addOrganizationCmd = &cobra.Command{ Aliases: []string{"o"}, Short: "Add a new organization to Lagoon", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -59,13 +59,12 @@ var addOrganizationCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) organizationInput := schema.AddOrganizationInput{ @@ -101,7 +100,7 @@ var deleteOrganizationCmd = &cobra.Command{ Aliases: []string{"o"}, Short: "Delete an organization", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -116,13 +115,12 @@ var deleteOrganizationCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) organization, err := lagoon.GetOrganizationByName(context.TODO(), organizationName, lc) @@ -152,7 +150,7 @@ var updateOrganizationCmd = &cobra.Command{ Aliases: []string{"o"}, Short: "Update an organization", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -195,13 +193,12 @@ var updateOrganizationCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) organization, err := lagoon.GetOrganizationByName(context.TODO(), organizationName, lc) diff --git a/cmd/project.go b/cmd/project.go index 1b640282..1c488acd 100644 --- a/cmd/project.go +++ b/cmd/project.go @@ -33,13 +33,12 @@ var deleteProjectCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) if yesNo(fmt.Sprintf("You are attempting to delete project '%s', are you sure?", cmdProjectName)) { @@ -63,7 +62,7 @@ var addProjectCmd = &cobra.Command{ Short: "Add a new project to Lagoon, or add a project to an organization", Long: "To add a project to an organization, you'll need to include the `organization` flag and provide the name of the organization. You need to be an owner of this organization to do this.\nIf you're the organization owner and want to grant yourself ownership to this project to be able to deploy environments, specify the `owner` flag.", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -148,13 +147,12 @@ var addProjectCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) projectInput := schema.AddProjectInput{ @@ -332,13 +330,12 @@ var updateProjectCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) projectPatch := schema.UpdateProjectPatchInput{ @@ -437,13 +434,12 @@ var listProjectByMetadata = &cobra.Command{ if err := requiredInputCheck("Key", key); err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) projects, err := lagoon.GetProjectsByMetadata(context.TODO(), key, value, lc) if err != nil { @@ -498,13 +494,12 @@ var getProjectMetadata = &cobra.Command{ if err := requiredInputCheck("Project name", cmdProjectName); err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) project, err := lagoon.GetProjectMetadata(context.TODO(), cmdProjectName, lc) if err != nil { @@ -558,13 +553,12 @@ var updateProjectMetadata = &cobra.Command{ return err } if yesNo(fmt.Sprintf("You are attempting to update key '%s' for project '%s' metadata, are you sure?", key, cmdProjectName)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) projectResult, err := lagoon.UpdateProjectMetadataByName(context.TODO(), cmdProjectName, key, value, lc) @@ -612,13 +606,12 @@ var deleteProjectMetadataByKey = &cobra.Command{ return err } if yesNo(fmt.Sprintf("You are attempting to delete key '%s' from project '%s' metadata, are you sure?", key, cmdProjectName)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) projectResult, err := lagoon.RemoveProjectMetadataByKeyByName(context.TODO(), cmdProjectName, key, lc) @@ -652,7 +645,7 @@ var removeProjectFromOrganizationCmd = &cobra.Command{ Short: "Remove a project from an Organization", Long: "Removes a project from an Organization, but does not delete the project.\nThis is used by platform administrators to be able to reset a project.", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -668,13 +661,12 @@ var removeProjectFromOrganizationCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) // this can stay for now as `removeProjectFromOrganization` is platform only scoped diff --git a/cmd/raw.go b/cmd/raw.go index ef96ec95..885b97f8 100644 --- a/cmd/raw.go +++ b/cmd/raw.go @@ -30,13 +30,12 @@ The output of this command will be the JSON response from the API`, if err := requiredInputCheck("Raw query or mutation", raw); err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) if err != nil { return err diff --git a/cmd/retrieve.go b/cmd/retrieve.go index 89e29a48..b18a1b13 100644 --- a/cmd/retrieve.go +++ b/cmd/retrieve.go @@ -16,7 +16,7 @@ var retrieveCmd = &cobra.Command{ Aliases: []string{"re", "ret"}, Short: "Trigger a retrieval operation on backups", PersistentPreRun: func(cmd *cobra.Command, args []string) { - validateToken(lagoonCLIConfig.Current) // get a new token if the current one is invalid + validateToken(lContext.Name) // get a new token if the current one is invalid }, } @@ -29,7 +29,7 @@ var retrieveBackupCmd = &cobra.Command{ Given a backup-id, you can initiate a retrieval for it. You can check the status of the backup using the list backups or get backup command.`, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -44,13 +44,12 @@ You can check the status of the backup using the list backups or get backup comm return err } if yesNo(fmt.Sprintf("You are attempting to trigger a retrieval for backup ID '%s', are you sure?", backupID)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) result, err := lagoon.AddBackupRestore(context.TODO(), backupID, lc) if err != nil { diff --git a/cmd/root.go b/cmd/root.go index af4711b3..09ecbcab 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,23 +3,22 @@ package cmd import ( "bufio" - "context" "fmt" "net" "os" - "path/filepath" "strings" "time" + "github.com/adrg/xdg" "github.com/golang-jwt/jwt" "github.com/manifoldco/promptui" "github.com/spf13/cobra" "github.com/spf13/cobra/doc" - lagooncli "github.com/uselagoon/lagoon-cli/internal/lagoon" - "github.com/uselagoon/lagoon-cli/internal/lagoon/client" "github.com/uselagoon/lagoon-cli/pkg/app" "github.com/uselagoon/lagoon-cli/pkg/output" "github.com/uselagoon/lagoon-cli/pkg/updatecheck" + config "github.com/uselagoon/machinery/utils/config" + "golang.org/x/oauth2" ) var cmdProject app.LagoonProject @@ -43,10 +42,6 @@ var skipUpdateCheck bool var strictHostKeyCheck string -// global for the lagoon config that the cli uses -// @TODO: when lagoon-cli rewrite happens, do this a bit better -var lagoonCLIConfig lagooncli.Config - // version/build information (populated at build time by make file) var ( lagoonCLIVersion = "0.x.x" @@ -59,16 +54,25 @@ var rootCmd = &cobra.Command{ Short: "Command line integration for Lagoon", Long: `Lagoon CLI. Manage your Lagoon hosted projects.`, DisableAutoGenTag: true, - PersistentPreRun: func(cmd *cobra.Command, args []string) { - if lagoonCLIConfig.UpdateCheckDisable { - skipUpdateCheck = true + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + skipUpdateCheck, err := lConfig.GetFeature(lContext.Name, configFeaturePrefix, "disable-update-check") + if err != nil { + return err + } + shkc, err := lConfig.GetFeature(lContext.Name, configFeaturePrefix, "no-strict-host-key-checking") + if err != nil { + return err } - if lagoonCLIConfig.StrictHostKeyChecking != "" { - strictHostKeyCheck = lagoonCLIConfig.StrictHostKeyChecking + if shkc { + strictHostKeyCheck = "no" + fmt.Println("AAA", shkc, strictHostKeyCheck) } if !skipUpdateCheck { + updateFile, err := xdg.StateFile(fmt.Sprintf("%s/%s", "lagoon", "update")) + if err != nil { + return err + } // Using code from https://github.com/drud/ddev/ - updateFile := filepath.Join(userPath, ".lagoon.update") // Do periodic detection of whether an update is available for lagoon-cli users. timeToCheckForUpdates, err := updatecheck.IsUpdateNeeded(updateFile, updateInterval) if err != nil { @@ -83,38 +87,42 @@ var rootCmd = &cobra.Command{ updateNeeded, updateURL, err := updatecheck.AvailableUpdates("uselagoon", "lagoon-cli", lagoonCLIVersion) if err != nil { output.RenderInfo("Could not check for updates. This is most often caused by a networking issue.\n", outputOptions) - output.RenderError(err.Error(), outputOptions) - return } if updateNeeded { output.RenderInfo(fmt.Sprintf("A new update is available! please visit %s to download the update.\nFor upgrade help see %s\n\nIf installed using brew, upgrade using `brew upgrade lagoon`\n", updateURL, updateDocURL), outputOptions) } } } + return nil }, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { if docsFlag { err := doc.GenMarkdownTree(cmd, "docs/commands") if err != nil { - output.RenderError(err.Error(), outputOptions) - os.Exit(1) + return err } fmt.Println("Documentation updated") - return + return nil } if versionFlag { displayVersionInfo() - return + return nil } - cmd.Help() - os.Exit(1) + return fmt.Errorf("no command provided") }, } // Execute the root command. func Execute() { if err := rootCmd.Execute(); err != nil { - output.RenderError(err.Error(), outputOptions) + if strings.Contains(err.Error(), "Unauthorized: invalid token") { + output.RenderError("token is invalid, try the command again or run 'lagoon login' to fetch a new token\n", outputOptions) + // invalidate the token in local config to force a token refresh next time a command is called + lUser.UserConfig.Grant = &oauth2.Token{} // clear the token + lConfig.WriteConfig() // save the cleared token + } else { + output.RenderError(fmt.Sprintf("%s\n", err.Error()), outputOptions) + } os.Exit(1) } } @@ -123,7 +131,7 @@ func Execute() { // internet connection. It just tries a quick DNS query. // This requires that the named record be query-able. func isInternetActive() bool { - _, err := net.LookupHost("amazee.io") + _, err := net.LookupHost("lagoon.sh") return err == nil } @@ -186,11 +194,9 @@ Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} `) rootCmd.AddCommand(addCmd) - rootCmd.AddCommand(configCmd) rootCmd.AddCommand(deleteCmd) rootCmd.AddCommand(deployCmd) rootCmd.AddCommand(getCmd) - rootCmd.AddCommand(kibanaCmd) rootCmd.AddCommand(listCmd) rootCmd.AddCommand(loginCmd) rootCmd.AddCommand(runCmd) @@ -198,12 +204,12 @@ Use "{{.CommandPath}} [command] --help" for more information about a command.{{e rootCmd.AddCommand(updateCmd) rootCmd.AddCommand(retrieveCmd) rootCmd.AddCommand(versionCmd) - rootCmd.AddCommand(webCmd) rootCmd.AddCommand(importCmd) rootCmd.AddCommand(exportCmd) rootCmd.AddCommand(whoamiCmd) rootCmd.AddCommand(uploadCmd) rootCmd.AddCommand(rawCmd) + rootCmd.AddCommand(configCmd) rootCmd.AddCommand(resetPasswordCmd) rootCmd.AddCommand(logsCmd) } @@ -230,21 +236,61 @@ func initConfig() { output.RenderError(fmt.Errorf("couldn't get $HOME: %v", err).Error(), outputOptions) os.Exit(1) } - configFilePath = userPath - - // check if we are being given a path to a different config file - err = getLagoonConfigFile(&configFilePath, &configName, &configExtension, createConfig, rootCmd) + // load or prompt user to create configuration + lConfig, err = config.LoadConfig(createConfig) if err != nil { - output.RenderError(err.Error(), outputOptions) - os.Exit(1) + // if there is no new config format + if strings.Contains(err.Error(), "could not locate `config.yaml`") { + // check for a legacy config + data, err2 := readLegacyConfig() + noConfig := false + if err2 != nil { + noConfig = true + } + if len(data) > 0 { + if yn("Only a legacy configuration file was found, would you like to convert it to the new format?") { + convertConfig(true) + fmt.Println("Configuration converted") + os.Exit(0) + } else { + noConfig = true + } + } + if noConfig { + if yn("Unable to find configuration, would you like to create one?") { + err := createInitialConfig() + if err != nil { + output.RenderError(err.Error(), outputOptions) + os.Exit(1) + } + fmt.Println("Configuration created") + os.Exit(0) + } + output.RenderError("No configuration created, configuration is required to use the CLI", outputOptions) + os.Exit(1) + } + } else { + output.RenderError(fmt.Sprintf("Unable to detect or load configuration: %s", err.Error()), outputOptions) + os.Exit(1) + } } - - err = readLagoonConfig(&lagoonCLIConfig, filepath.Join(configFilePath, configName+configExtension)) - if err != nil { - output.RenderError(err.Error(), outputOptions) - os.Exit(1) + if cmdLagoon == "" { + // source the default context for the config + lContext, err = lConfig.GetDefaultContext() + if err != nil { + output.RenderError(err.Error(), outputOptions) + os.Exit(1) + } + } else { + // otherwise try find the one the user has provided + lContext, err = lConfig.GetContext(cmdLagoon) + if err != nil { + output.RenderError(err.Error(), outputOptions) + os.Exit(1) + } } - err = getLagoonContext(&lagoonCLIConfig, &cmdLagoon, rootCmd) + // get the users details for the context + lUser, err = lConfig.GetUser(lContext.User) if err != nil { output.RenderError(err.Error(), outputOptions) os.Exit(1) @@ -253,8 +299,13 @@ func initConfig() { // if the directory or repository you're in has a valid .lagoon.yml and docker-compose.yml with x-lagoon-project in it // we can use that inplaces where projects already exist so you don't have to type it out // and environments too - // this option is opt-in now, so to use it you will need to `lagoon config feature --enable-local-dir-check=true` - if lagoonCLIConfig.EnvironmentFromDirectory { + // this option is opt-in and will be deprecated in the future + envFromDir, err := lConfig.GetFeature(lContext.Name, configFeaturePrefix, "environment-from-directory") + if err != nil { + output.RenderError(err.Error(), outputOptions) + os.Exit(1) + } + if envFromDir { cmdProject, _ = app.GetLocalProject() } if cmdProject.Name != "" && cmdProjectName == "" { @@ -267,20 +318,24 @@ func initConfig() { func yesNo(message string) bool { if !forceAction { - prompt := promptui.Select{ - Label: message + "; Select[Yes/No]", - Items: []string{"No", "Yes"}, - } - _, result, err := prompt.Run() - if err != nil { - output.RenderError(err.Error(), outputOptions) - os.Exit(1) - } - return result == "Yes" + return yn(message) } return true } +func yn(message string) bool { + prompt := promptui.Select{ + Label: message + "; Select[Yes/No]", + Items: []string{"No", "Yes"}, + } + _, result, err := prompt.Run() + if err != nil { + output.RenderError(err.Error(), outputOptions) + os.Exit(1) + } + return result == "Yes" +} + // GetInput reads input from an input buffer and returns the result as a string. func GetInput() string { inputScanner.Scan() @@ -305,12 +360,7 @@ const ( ) func validateToken(lagoon string) { - var err error - if err = checkContextExists(&lagoonCLIConfig); err != nil { - fmt.Println(err) - os.Exit(1) - } - valid := VerifyTokenExpiry(&lagoonCLIConfig, lagoon) + valid := VerifyTokenExpiry(lUser.UserConfig.Grant.AccessToken, lagoon) if !valid { loginErr := loginToken() if loginErr != nil { @@ -325,10 +375,7 @@ func validateToken(lagoon string) { // error instead of exiting on error. func validateTokenE(lagoon string) error { var err error - if err = checkContextExists(&lagoonCLIConfig); err != nil { - return err - } - if VerifyTokenExpiry(&lagoonCLIConfig, lagoon) { + if VerifyTokenExpiry(lUser.UserConfig.Grant.AccessToken, lagoon) { // check the API for the version of lagoon if we haven't got one set // otherwise return nil, nothing to do return nil @@ -341,110 +388,15 @@ func validateTokenE(lagoon string) error { return nil } -// check if we have a version set in config, if not get the version. -// this checks whenever a token is refreshed -func versionCheck(lagoon string) error { - lc := client.New( - lagoonCLIConfig.Lagoons[lagoon].GraphQL, - lagoonCLIConfig.Lagoons[lagoon].Token, - lagoonCLIConfig.Lagoons[lagoon].Version, - lagoonCLIVersion, - debugEnable) - lagoonVersion, err := lagooncli.GetLagoonAPIVersion(context.TODO(), lc) - if err != nil { - return err - } - l := lagoonCLIConfig.Lagoons[lagoon] - l.Version = lagoonVersion.LagoonVersion - lagoonCLIConfig.Lagoons[lagoon] = l - if err = writeLagoonConfig(&lagoonCLIConfig, filepath.Join(configFilePath, configName+configExtension)); err != nil { - return fmt.Errorf("couldn't write config: %v", err) - } - return nil -} - -func getLagoonConfigFile(configPath *string, configName *string, configExtension *string, createConfig bool, cmd *cobra.Command) error { - // check if we have an envvar or flag to define our confg file - var configFilePath string - configFilePath, err := cmd.Flags().GetString("config-file") - if err != nil { - return fmt.Errorf("error reading flag `config-file`: %v", err) - } - if configFilePath == "" { - if lagoonConfigEnvar, ok := os.LookupEnv("LAGOONCONFIG"); ok { - configFilePath = lagoonConfigEnvar - } - // prefer LAGOON_CONFIG_FILE - if lagoonConfigEnvar, ok := os.LookupEnv("LAGOON_CONFIG_FILE"); ok { - configFilePath = lagoonConfigEnvar - } - } - if configFilePath != "" { - if fileExists(configFilePath) || createConfig { - *configPath = filepath.Dir(configFilePath) - *configExtension = filepath.Ext(configFilePath) - *configName = strings.TrimSuffix(filepath.Base(configFilePath), *configExtension) - return nil - } - return fmt.Errorf("%s/%s File doesn't exist", *configPath, configFilePath) - - } - // no config file found - return nil -} - -func getLagoonContext(lagoonCLIConfig *lagooncli.Config, lagoon *string, cmd *cobra.Command) error { - // check if we have an envvar or flag to define our lagoon context - var lagoonContext string - lagoonContext, err := cmd.Flags().GetString("lagoon") - if err != nil { - return fmt.Errorf("error reading flag `lagoon`: %v", err) - } - if lagoonContext == "" { - if lagoonContextEnvar, ok := os.LookupEnv("LAGOONCONTEXT"); ok { - lagoonContext = lagoonContextEnvar - } - // prefer LAGOON_CONTEXT - if lagoonContextEnvar, ok := os.LookupEnv("LAGOON_CONTEXT"); ok { - configFilePath = lagoonContextEnvar - } - } - if lagoonContext != "" { - *lagoon = lagoonContext - } else { - if lagoonCLIConfig.Default == "" { - *lagoon = "amazeeio" - } else { - *lagoon = lagoonCLIConfig.Default - } - } - // set the Current lagoon to the one we've determined it needs to be - lagoonCLIConfig.Current = strings.TrimSpace(*lagoon) - return nil -} - -func checkContextExists(lagoonCLIConfig *lagooncli.Config) error { - contextExists := false - for l := range lagoonCLIConfig.Lagoons { - if l == lagoonCLIConfig.Current { - contextExists = true - } - } - if !contextExists { - return fmt.Errorf("chosen context '%s' doesn't exist in config file", lagoonCLIConfig.Current) - } - return nil -} - // VerifyTokenExpiry verfies if the current token is valid or not -func VerifyTokenExpiry(lc *lagooncli.Config, lagoon string) bool { +func VerifyTokenExpiry(token, lagoon string) bool { var p jwt.Parser - token, _, err := p.ParseUnverified( - lc.Lagoons[lagoon].Token, &jwt.StandardClaims{}) + token2, _, err := p.ParseUnverified( + token, &jwt.StandardClaims{}) if err != nil { return false } - if token.Claims.Valid() != nil { + if token2.Claims.Valid() != nil { return false } return true diff --git a/cmd/run.go b/cmd/run.go index e22d4538..255d7aa6 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -9,7 +9,7 @@ var runCmd = &cobra.Command{ Aliases: []string{"r"}, Short: "Run a task against an environment", PersistentPreRun: func(cmd *cobra.Command, args []string) { - validateToken(lagoonCLIConfig.Current) // get a new token if the current one is invalid + validateToken(lContext.Name) // get a new token if the current one is invalid }, } diff --git a/cmd/shared.go b/cmd/shared.go index a40bf502..08d9723d 100644 --- a/cmd/shared.go +++ b/cmd/shared.go @@ -9,15 +9,6 @@ import ( "github.com/uselagoon/lagoon-cli/pkg/output" ) -// config vars -var lagoonHostname string -var lagoonPort string -var lagoonGraphQL string -var lagoonToken string -var lagoonUI string -var lagoonKibana string -var lagoonSSHKey string - // group vars var groupName string diff --git a/cmd/ssh.go b/cmd/ssh.go index 7c585b21..b2f72928 100644 --- a/cmd/ssh.go +++ b/cmd/ssh.go @@ -44,8 +44,8 @@ var sshEnvCmd = &cobra.Command{ privateKey := fmt.Sprintf("%s/.ssh/id_rsa", userPath) // if the user has a key defined in their lagoon cli config, use it - if lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].SSHKey != "" { - privateKey = lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].SSHKey + if lUser.UserConfig.SSHKey != "" { + privateKey = lUser.UserConfig.SSHKey skipAgent = true } // otherwise check if one has been provided by the override flag @@ -67,7 +67,7 @@ var sshEnvCmd = &cobra.Command{ return fmt.Errorf("couldn't get ~/.ssh/known_hosts: %v", err) } // start an interactive ssh session - authMethod, closeSSHAgent := publicKey(privateKey, cmdPubkeyIdentity, lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].PublicKeyIdentities, skipAgent) + authMethod, closeSSHAgent := publicKey(privateKey, cmdPubkeyIdentity, lUser.UserConfig.PublicKeyIdentities, skipAgent) config := &ssh.ClientConfig{ User: sshConfig["username"], Auth: []ssh.AuthMethod{ diff --git a/cmd/tasks.go b/cmd/tasks.go index a934ca00..0f7cb6c6 100644 --- a/cmd/tasks.go +++ b/cmd/tasks.go @@ -24,7 +24,7 @@ var getTaskByID = &cobra.Command{ Long: `Get information about a task by its ID`, Aliases: []string{"t", "tbi"}, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -43,13 +43,12 @@ var getTaskByID = &cobra.Command{ if err := requiredInputCheck("ID", strconv.Itoa(taskID)); err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) result, err := lagoon.TaskByID(context.TODO(), taskID, lc) if err != nil { @@ -93,7 +92,7 @@ You should only run this once and then check the status of the task that gets cr If the task fails or fails to update, contact your Lagoon administrator for assistance.`, Aliases: []string{"as", "standby"}, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -104,19 +103,18 @@ If the task fails or fails to update, contact your Lagoon administrator for assi return err } if yesNo(fmt.Sprintf("You are attempting to run the active/standby switch for project '%s', are you sure?", cmdProjectName)) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) result, err := lagoon.ActiveStandbySwitch(context.TODO(), cmdProjectName, lc) if err != nil { return err } - fmt.Printf("Created a new task with ID %d \nYou can use the following command to query the task status: \nlagoon -l %s get task-by-id --id %d --logs \n", result.ID, current, result.ID) + fmt.Printf("Created a new task with ID %d \nYou can use the following command to query the task status: \nlagoon -l %s get task-by-id --id %d --logs \n", result.ID, lContext.Name, result.ID) } return nil }, @@ -127,7 +125,7 @@ var runDrushArchiveDump = &cobra.Command{ Aliases: []string{"dard"}, Short: "Run a drush archive dump on an environment", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -137,13 +135,12 @@ var runDrushArchiveDump = &cobra.Command{ if err := requiredInputCheck("Project name", cmdProjectName, "Environment name", cmdProjectEnvironment); err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) if err != nil { return err @@ -192,7 +189,7 @@ var runDrushSQLDump = &cobra.Command{ Aliases: []string{"dsqld"}, Short: "Run a drush sql dump on an environment", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -202,13 +199,12 @@ var runDrushSQLDump = &cobra.Command{ if err := requiredInputCheck("Project name", cmdProjectName, "Environment name", cmdProjectEnvironment); err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) if err != nil { return err @@ -257,7 +253,7 @@ var runDrushCacheClear = &cobra.Command{ Aliases: []string{"dcc"}, Short: "Run a drush cache clear on an environment", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -267,13 +263,12 @@ var runDrushCacheClear = &cobra.Command{ if err := requiredInputCheck("Project name", cmdProjectName, "Environment name", cmdProjectEnvironment); err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) if err != nil { return err @@ -327,7 +322,7 @@ Direct: lagoon run invoke -p example -e main -N "advanced task name" `, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -342,13 +337,12 @@ Direct: return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) project, err := lagoon.GetProjectByName(context.TODO(), cmdProjectName, lc) @@ -402,7 +396,7 @@ Path: lagoon run custom -p example -e main -N "My Task" -S cli -s /path/to/my-script.sh `, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -450,13 +444,12 @@ Path: if err := requiredInputCheck("Project name", cmdProjectName, "Environment name", cmdProjectEnvironment, "Task command", taskCommand, "Task name", taskName, "Task service", taskService); err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) task := schema.Task{ @@ -490,7 +483,7 @@ var uploadFilesToTask = &cobra.Command{ Long: `Upload files to a task by its ID`, Aliases: []string{"tf"}, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -509,13 +502,12 @@ var uploadFilesToTask = &cobra.Command{ if err != nil { return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) result, err := lagoon.UploadFilesForTask(context.TODO(), taskID, files, lc) if err != nil { diff --git a/cmd/update.go b/cmd/update.go index 12106614..17d15130 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -9,7 +9,7 @@ var updateCmd = &cobra.Command{ Aliases: []string{"u"}, Short: "Update a resource", PersistentPreRun: func(cmd *cobra.Command, args []string) { - validateToken(lagoonCLIConfig.Current) // get a new token if the current one is invalid + validateToken(lContext.Name) // get a new token if the current one is invalid }, } @@ -18,7 +18,7 @@ var updateNotificationCmd = &cobra.Command{ Aliases: []string{"n"}, Short: "List all notifications or notifications on projects", PersistentPreRun: func(cmd *cobra.Command, args []string) { - validateToken(lagoonCLIConfig.Current) // get a new token if the current one is invalid + validateToken(lContext.Name) // get a new token if the current one is invalid }, } diff --git a/cmd/upload.go b/cmd/upload.go index 33049ae0..88222d1d 100644 --- a/cmd/upload.go +++ b/cmd/upload.go @@ -9,7 +9,7 @@ var uploadCmd = &cobra.Command{ Aliases: []string{"u"}, Short: "Upload files to tasks", PersistentPreRun: func(cmd *cobra.Command, args []string) { - validateToken(lagoonCLIConfig.Current) // get a new token if the current one is invalid + validateToken(lContext.Name) // get a new token if the current one is invalid }, } diff --git a/cmd/users.go b/cmd/users.go index e5e8a1d7..30371fa3 100644 --- a/cmd/users.go +++ b/cmd/users.go @@ -75,13 +75,12 @@ var addUserCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) userInput := &schema.AddUserInput{ @@ -156,13 +155,12 @@ Add key by defining key value, but not specifying a key name (will default to tr return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) userSSHKey, err := parseSSHKeyFile(pubKeyFile, sshKeyName, pubKeyValue, email) @@ -206,13 +204,12 @@ var deleteSSHKeyCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) if yesNo(fmt.Sprintf("You are attempting to delete SSH key ID:'%d', are you sure?", sshKeyID)) { @@ -250,13 +247,12 @@ var deleteUserCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) deleteUserInput := &schema.DeleteUserInput{ @@ -315,13 +311,12 @@ var updateUserCmd = &cobra.Command{ return nil } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) currentUser := &schema.UpdateUserInput{ @@ -374,13 +369,12 @@ var getUserKeysCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) userKeys, err := lagoon.GetUserSSHKeysByEmail(context.TODO(), userEmail, lc) if err != nil { @@ -432,13 +426,12 @@ var getAllUserKeysCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) groupMembers, err := lagoon.ListAllGroupMembersWithKeys(context.TODO(), groupName, lc) if err != nil { @@ -489,7 +482,7 @@ var addAdministratorToOrganizationCmd = &cobra.Command{ Short: "Add an administrator to an Organization", Long: "Add an administrator to an Organization. If the role flag is not provided users will be added as viewers", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -525,13 +518,12 @@ var addAdministratorToOrganizationCmd = &cobra.Command{ return fmt.Errorf(`role '%s' is not valid - valid roles include "viewer", "admin", or "owner"`, role) } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) organization, err := lagoon.GetOrganizationByName(context.TODO(), organizationName, lc) @@ -567,7 +559,7 @@ var removeAdministratorFromOrganizationCmd = &cobra.Command{ Aliases: []string{"organization-admin", "org-admin", "org-a"}, Short: "Remove an administrator from an Organization", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -590,13 +582,12 @@ var removeAdministratorFromOrganizationCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) organization, err := lagoon.GetOrganizationByName(context.TODO(), organizationName, lc) @@ -636,7 +627,7 @@ var resetPasswordCmd = &cobra.Command{ Aliases: []string{"reset-pass", "rp"}, Short: "Send a password reset email", PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -651,13 +642,12 @@ var resetPasswordCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) resetPasswordInput := schema.ResetUserPasswordInput{ diff --git a/cmd/variables.go b/cmd/variables.go index 4a5a2714..623dbd97 100644 --- a/cmd/variables.go +++ b/cmd/variables.go @@ -41,13 +41,12 @@ var addVariableCmd = &cobra.Command{ return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) in := &schema.EnvVariableByNameInput{ @@ -123,13 +122,12 @@ var deleteVariableCmd = &cobra.Command{ deleteMsg = fmt.Sprintf("You are attempting to delete variable '%s' from environment '%s' in project '%s', are you sure?", varName, cmdProjectEnvironment, cmdProjectName) } if yesNo(deleteMsg) { - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) in := &schema.DeleteEnvVariableByNameInput{ Project: cmdProjectName, diff --git a/cmd/web.go b/cmd/web.go deleted file mode 100644 index e052a5a0..00000000 --- a/cmd/web.go +++ /dev/null @@ -1,55 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - "strings" - - "github.com/pkg/browser" - "github.com/spf13/cobra" - "github.com/uselagoon/lagoon-cli/pkg/output" -) - -var webCmd = &cobra.Command{ - Use: "web", - Aliases: []string{"w"}, - Short: "Launch the web user interface", - Run: func(cmd *cobra.Command, args []string) { - if cmdProjectName == "" { - fmt.Println("Missing arguments: Project name is not defined") - cmd.Help() - os.Exit(1) - } - - urlBuilder := strings.Builder{} - urlBuilder.WriteString(lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].UI) - if lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].UI != "" { - urlBuilder.WriteString(fmt.Sprintf("/projects/%s", cmdProjectName)) - } else { - output.RenderError("unable to determine url for ui, is one set?", outputOptions) - os.Exit(1) - } - - url := urlBuilder.String() - fmt.Printf("Opening %s\n", url) - _ = browser.OpenURL(url) - }, -} - -var kibanaCmd = &cobra.Command{ - Use: "kibana", - Aliases: []string{"k"}, - Short: "Launch the kibana interface", - Run: func(cmd *cobra.Command, args []string) { - urlBuilder := strings.Builder{} - urlBuilder.WriteString(lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].Kibana) - if lagoonCLIConfig.Lagoons[lagoonCLIConfig.Current].Kibana == "" { - output.RenderError("unable to determine url for kibana, is one set?", outputOptions) - os.Exit(1) - } - - url := urlBuilder.String() - fmt.Printf("Opening %s\n", url) - _ = browser.OpenURL(url) - }, -} diff --git a/cmd/whoami.go b/cmd/whoami.go index ff0387b8..a9467717 100644 --- a/cmd/whoami.go +++ b/cmd/whoami.go @@ -20,7 +20,7 @@ var whoamiCmd = &cobra.Command{ Long: `Whoami will return your user information for lagoon. This is useful if you have multiple keys or accounts in multiple lagoons and need to check which you are using.`, PreRunE: func(_ *cobra.Command, _ []string) error { - return validateTokenE(lagoonCLIConfig.Current) + return validateTokenE(lContext.Name) }, RunE: func(cmd *cobra.Command, args []string) error { debug, err := cmd.Flags().GetBool("debug") @@ -32,13 +32,12 @@ This is useful if you have multiple keys or accounts in multiple lagoons and nee return err } - current := lagoonCLIConfig.Current - token := lagoonCLIConfig.Lagoons[current].Token + utoken := lUser.UserConfig.Grant.AccessToken lc := lclient.New( - lagoonCLIConfig.Lagoons[current].GraphQL, + fmt.Sprintf("%s/graphql", lContext.ContextConfig.APIHostname), lagoonCLIVersion, - lagoonCLIConfig.Lagoons[current].Version, - &token, + lContext.ContextConfig.Version, + &utoken, debug) user, err := lagoon.Me(context.TODO(), lc) diff --git a/docs/commands/lagoon.md b/docs/commands/lagoon.md index b2541563..bbc1c8da 100644 --- a/docs/commands/lagoon.md +++ b/docs/commands/lagoon.md @@ -37,15 +37,14 @@ lagoon [flags] * [lagoon add](lagoon_add.md) - Add a project, or add notifications and variables to projects or environments * [lagoon completion](lagoon_completion.md) - Generate the autocompletion script for the specified shell -* [lagoon config](lagoon_config.md) - Configure Lagoon CLI +* [lagoon configuration](lagoon_configuration.md) - Manage or view the contexts and users for interacting with Lagoon * [lagoon delete](lagoon_delete.md) - Delete a project, or delete notifications and variables from projects or environments * [lagoon deploy](lagoon_deploy.md) - Actions for deploying or promoting branches or environments in lagoon * [lagoon export](lagoon_export.md) - Export lagoon output to yaml * [lagoon get](lagoon_get.md) - Get info on a resource * [lagoon import](lagoon_import.md) - Import a config from a yaml file -* [lagoon kibana](lagoon_kibana.md) - Launch the kibana interface * [lagoon list](lagoon_list.md) - List projects, environments, deployments, variables or notifications -* [lagoon login](lagoon_login.md) - Log into a Lagoon instance +* [lagoon login](lagoon_login.md) - Login and refresh a token * [lagoon logs](lagoon_logs.md) - Display logs for a service of an environment and project * [lagoon raw](lagoon_raw.md) - Run a custom query or mutation * [lagoon reset-password](lagoon_reset-password.md) - Send a password reset email @@ -55,6 +54,5 @@ lagoon [flags] * [lagoon update](lagoon_update.md) - Update a resource * [lagoon upload](lagoon_upload.md) - Upload files to tasks * [lagoon version](lagoon_version.md) - Version information -* [lagoon web](lagoon_web.md) - Launch the web user interface * [lagoon whoami](lagoon_whoami.md) - Whoami will return your user information for lagoon diff --git a/docs/commands/lagoon_config.md b/docs/commands/lagoon_config.md deleted file mode 100644 index aca5db80..00000000 --- a/docs/commands/lagoon_config.md +++ /dev/null @@ -1,42 +0,0 @@ -## lagoon config - -Configure Lagoon CLI - -### Options - -``` - -h, --help help for config -``` - -### Options inherited from parent commands - -``` - --config-file string Path to the config file to use (must be *.yml or *.yaml) - --debug Enable debugging output (if supported) - -e, --environment string Specify an environment to use - --force Force yes on prompts (if supported) - -l, --lagoon string The Lagoon instance to interact with - --no-header No header on table (if supported) - --output-csv Output as CSV (if supported) - --output-json Output as JSON (if supported) - --pretty Make JSON pretty (if supported) - -p, --project string Specify a project to use - --skip-update-check Skip checking for updates - -i, --ssh-key string Specify path to a specific SSH key to use for lagoon authentication - --ssh-publickey string Specify path to a specific SSH public key to use for lagoon authentication using ssh-agent. - This will override any public key identities defined in configuration - --strict-host-key-checking string Similar to SSH StrictHostKeyChecking (accept-new, no, ignore) (default "accept-new") - -v, --verbose Enable verbose output to stderr (if supported) -``` - -### SEE ALSO - -* [lagoon](lagoon.md) - Command line integration for Lagoon -* [lagoon config add](lagoon_config_add.md) - Add information about an additional Lagoon instance to use -* [lagoon config current](lagoon_config_current.md) - Display the current Lagoon that commands would be executed against -* [lagoon config default](lagoon_config_default.md) - Set the default Lagoon to use -* [lagoon config delete](lagoon_config_delete.md) - Delete a Lagoon instance configuration -* [lagoon config feature](lagoon_config_feature.md) - Enable or disable CLI features -* [lagoon config lagoon-version](lagoon_config_lagoon-version.md) - Checks the current Lagoon for its version and sets it in the config file -* [lagoon config list](lagoon_config_list.md) - View all configured Lagoon instances - diff --git a/docs/commands/lagoon_config_add.md b/docs/commands/lagoon_config_add.md deleted file mode 100644 index 87a35750..00000000 --- a/docs/commands/lagoon_config_add.md +++ /dev/null @@ -1,47 +0,0 @@ -## lagoon config add - -Add information about an additional Lagoon instance to use - -``` -lagoon config add [flags] -``` - -### Options - -``` - --create-config Create the config file if it is non existent (to be used with --config-file) - -g, --graphql string Lagoon GraphQL endpoint - -h, --help help for add - -H, --hostname string Lagoon SSH hostname - -k, --kibana string Lagoon Kibana URL (https://logs.amazeeio.cloud) - -P, --port string Lagoon SSH port - --publickey-identityfile strings Specific public key identity files to use when doing ssh-agent checks (support multiple) - --ssh-key string SSH Key to use for this cluster for generating tokens - -t, --token string Lagoon GraphQL token - -u, --ui string Lagoon UI location (https://dashboard.amazeeio.cloud) -``` - -### Options inherited from parent commands - -``` - --config-file string Path to the config file to use (must be *.yml or *.yaml) - --debug Enable debugging output (if supported) - -e, --environment string Specify an environment to use - --force Force yes on prompts (if supported) - -l, --lagoon string The Lagoon instance to interact with - --no-header No header on table (if supported) - --output-csv Output as CSV (if supported) - --output-json Output as JSON (if supported) - --pretty Make JSON pretty (if supported) - -p, --project string Specify a project to use - --skip-update-check Skip checking for updates - --ssh-publickey string Specify path to a specific SSH public key to use for lagoon authentication using ssh-agent. - This will override any public key identities defined in configuration - --strict-host-key-checking string Similar to SSH StrictHostKeyChecking (accept-new, no, ignore) (default "accept-new") - -v, --verbose Enable verbose output to stderr (if supported) -``` - -### SEE ALSO - -* [lagoon config](lagoon_config.md) - Configure Lagoon CLI - diff --git a/docs/commands/lagoon_config_feature.md b/docs/commands/lagoon_config_feature.md deleted file mode 100644 index 182fb196..00000000 --- a/docs/commands/lagoon_config_feature.md +++ /dev/null @@ -1,41 +0,0 @@ -## lagoon config feature - -Enable or disable CLI features - -``` -lagoon config feature [flags] -``` - -### Options - -``` - --disable-update-check string Enable or disable checking of updates (true/false) - --enable-local-dir-check string Enable or disable checking of local directory for Lagoon project (true/false) - -h, --help help for feature - --strict-host-key-checking string Enable or disable StrictHostKeyChecking (yes, no, ignore) -``` - -### Options inherited from parent commands - -``` - --config-file string Path to the config file to use (must be *.yml or *.yaml) - --debug Enable debugging output (if supported) - -e, --environment string Specify an environment to use - --force Force yes on prompts (if supported) - -l, --lagoon string The Lagoon instance to interact with - --no-header No header on table (if supported) - --output-csv Output as CSV (if supported) - --output-json Output as JSON (if supported) - --pretty Make JSON pretty (if supported) - -p, --project string Specify a project to use - --skip-update-check Skip checking for updates - -i, --ssh-key string Specify path to a specific SSH key to use for lagoon authentication - --ssh-publickey string Specify path to a specific SSH public key to use for lagoon authentication using ssh-agent. - This will override any public key identities defined in configuration - -v, --verbose Enable verbose output to stderr (if supported) -``` - -### SEE ALSO - -* [lagoon config](lagoon_config.md) - Configure Lagoon CLI - diff --git a/docs/commands/lagoon_config_list.md b/docs/commands/lagoon_config_list.md deleted file mode 100644 index c26f2a2e..00000000 --- a/docs/commands/lagoon_config_list.md +++ /dev/null @@ -1,40 +0,0 @@ -## lagoon config list - -View all configured Lagoon instances - -``` -lagoon config list [flags] -``` - -### Options - -``` - -h, --help help for list - --show-full Show full config output when listing Lagoon configurations -``` - -### Options inherited from parent commands - -``` - --config-file string Path to the config file to use (must be *.yml or *.yaml) - --debug Enable debugging output (if supported) - -e, --environment string Specify an environment to use - --force Force yes on prompts (if supported) - -l, --lagoon string The Lagoon instance to interact with - --no-header No header on table (if supported) - --output-csv Output as CSV (if supported) - --output-json Output as JSON (if supported) - --pretty Make JSON pretty (if supported) - -p, --project string Specify a project to use - --skip-update-check Skip checking for updates - -i, --ssh-key string Specify path to a specific SSH key to use for lagoon authentication - --ssh-publickey string Specify path to a specific SSH public key to use for lagoon authentication using ssh-agent. - This will override any public key identities defined in configuration - --strict-host-key-checking string Similar to SSH StrictHostKeyChecking (accept-new, no, ignore) (default "accept-new") - -v, --verbose Enable verbose output to stderr (if supported) -``` - -### SEE ALSO - -* [lagoon config](lagoon_config.md) - Configure Lagoon CLI - diff --git a/docs/commands/lagoon_kibana.md b/docs/commands/lagoon_configuration.md similarity index 54% rename from docs/commands/lagoon_kibana.md rename to docs/commands/lagoon_configuration.md index 4d722157..4e44293c 100644 --- a/docs/commands/lagoon_kibana.md +++ b/docs/commands/lagoon_configuration.md @@ -1,15 +1,11 @@ -## lagoon kibana +## lagoon configuration -Launch the kibana interface - -``` -lagoon kibana [flags] -``` +Manage or view the contexts and users for interacting with Lagoon ### Options ``` - -h, --help help for kibana + -h, --help help for configuration ``` ### Options inherited from parent commands @@ -36,4 +32,14 @@ lagoon kibana [flags] ### SEE ALSO * [lagoon](lagoon.md) - Command line integration for Lagoon +* [lagoon configuration add-context](lagoon_configuration_add-context.md) - Add a new Lagoon context +* [lagoon configuration add-user](lagoon_configuration_add-user.md) - Add a new Lagoon context user +* [lagoon configuration config-path](lagoon_configuration_config-path.md) - Get the path of where the config file lives +* [lagoon configuration convert-config](lagoon_configuration_convert-config.md) - Convert legacy .lagoon.yml config to the new configuration format +* [lagoon configuration default-context](lagoon_configuration_default-context.md) - Change which context is the default +* [lagoon configuration feature](lagoon_configuration_feature.md) - Enable or disable a feature for all contexts or a specific context +* [lagoon configuration list-contexts](lagoon_configuration_list-contexts.md) - View all configured Lagoon contexts +* [lagoon configuration list-users](lagoon_configuration_list-users.md) - View all configured Lagoon context users +* [lagoon configuration update-context](lagoon_configuration_update-context.md) - Update a Lagoon context +* [lagoon configuration update-user](lagoon_configuration_update-user.md) - Update a Lagoon context user diff --git a/docs/commands/lagoon_configuration_add-context.md b/docs/commands/lagoon_configuration_add-context.md new file mode 100644 index 00000000..cabfddce --- /dev/null +++ b/docs/commands/lagoon_configuration_add-context.md @@ -0,0 +1,47 @@ +## lagoon configuration add-context + +Add a new Lagoon context + +``` +lagoon configuration add-context [flags] +``` + +### Options + +``` + --api-hostname string Lagoon API hostname (eg: https://api.lagoon.sh) + --authentication-hostname string Lagoon authentication hostname (eg: https://keycloak.lagoon.sh) + -h, --help help for add-context + --name string The name to reference this context as + --token-hostname string Lagoon Token endpoint hostname (eg: token.lagoon.sh) + --token-port int Lagoon Token endpoint port (eg: 22) + --ui-hostname string Lagoon UI hostname (eg: https://ui.lagoon.sh) + --user string The user to associate to this context + --webhook-hostname string Lagoon webhook hostname (eg: https://webhook.lagoon.sh) +``` + +### Options inherited from parent commands + +``` + --config-file string Path to the config file to use (must be *.yml or *.yaml) + --debug Enable debugging output (if supported) + -e, --environment string Specify an environment to use + --force Force yes on prompts (if supported) + -l, --lagoon string The Lagoon instance to interact with + --no-header No header on table (if supported) + --output-csv Output as CSV (if supported) + --output-json Output as JSON (if supported) + --pretty Make JSON pretty (if supported) + -p, --project string Specify a project to use + --skip-update-check Skip checking for updates + -i, --ssh-key string Specify path to a specific SSH key to use for lagoon authentication + --ssh-publickey string Specify path to a specific SSH public key to use for lagoon authentication using ssh-agent. + This will override any public key identities defined in configuration + --strict-host-key-checking string Similar to SSH StrictHostKeyChecking (accept-new, no, ignore) (default "accept-new") + -v, --verbose Enable verbose output to stderr (if supported) +``` + +### SEE ALSO + +* [lagoon configuration](lagoon_configuration.md) - Manage or view the contexts and users for interacting with Lagoon + diff --git a/docs/commands/lagoon_configuration_add-user.md b/docs/commands/lagoon_configuration_add-user.md new file mode 100644 index 00000000..0a91b1db --- /dev/null +++ b/docs/commands/lagoon_configuration_add-user.md @@ -0,0 +1,40 @@ +## lagoon configuration add-user + +Add a new Lagoon context user + +``` +lagoon configuration add-user [flags] +``` + +### Options + +``` + -h, --help help for add-user + --name string The name to reference this user as + --ssh-key string The full path to this users ssh-key +``` + +### Options inherited from parent commands + +``` + --config-file string Path to the config file to use (must be *.yml or *.yaml) + --debug Enable debugging output (if supported) + -e, --environment string Specify an environment to use + --force Force yes on prompts (if supported) + -l, --lagoon string The Lagoon instance to interact with + --no-header No header on table (if supported) + --output-csv Output as CSV (if supported) + --output-json Output as JSON (if supported) + --pretty Make JSON pretty (if supported) + -p, --project string Specify a project to use + --skip-update-check Skip checking for updates + --ssh-publickey string Specify path to a specific SSH public key to use for lagoon authentication using ssh-agent. + This will override any public key identities defined in configuration + --strict-host-key-checking string Similar to SSH StrictHostKeyChecking (accept-new, no, ignore) (default "accept-new") + -v, --verbose Enable verbose output to stderr (if supported) +``` + +### SEE ALSO + +* [lagoon configuration](lagoon_configuration.md) - Manage or view the contexts and users for interacting with Lagoon + diff --git a/docs/commands/lagoon_config_default.md b/docs/commands/lagoon_configuration_config-path.md similarity index 84% rename from docs/commands/lagoon_config_default.md rename to docs/commands/lagoon_configuration_config-path.md index 398ec7f6..cfa04315 100644 --- a/docs/commands/lagoon_config_default.md +++ b/docs/commands/lagoon_configuration_config-path.md @@ -1,15 +1,15 @@ -## lagoon config default +## lagoon configuration config-path -Set the default Lagoon to use +Get the path of where the config file lives ``` -lagoon config default [flags] +lagoon configuration config-path [flags] ``` ### Options ``` - -h, --help help for default + -h, --help help for config-path ``` ### Options inherited from parent commands @@ -35,5 +35,5 @@ lagoon config default [flags] ### SEE ALSO -* [lagoon config](lagoon_config.md) - Configure Lagoon CLI +* [lagoon configuration](lagoon_configuration.md) - Manage or view the contexts and users for interacting with Lagoon diff --git a/docs/commands/lagoon_configuration_convert-config.md b/docs/commands/lagoon_configuration_convert-config.md new file mode 100644 index 00000000..a9472ddc --- /dev/null +++ b/docs/commands/lagoon_configuration_convert-config.md @@ -0,0 +1,47 @@ +## lagoon configuration convert-config + +Convert legacy .lagoon.yml config to the new configuration format + +### Synopsis + +Convert legacy .lagoon.yml config to the new configuration format. +This will prompt you to provide any required information if it is missing from your legacy configuration. +Running this command initially will run in dry-run mode, if you're happy with the result you can run it again +with the --write-config flag to save the new configuration. + +``` +lagoon configuration convert-config [flags] +``` + +### Options + +``` + -h, --help help for convert-config + --write-config Whether the config should be written to the config file or not +``` + +### Options inherited from parent commands + +``` + --config-file string Path to the config file to use (must be *.yml or *.yaml) + --debug Enable debugging output (if supported) + -e, --environment string Specify an environment to use + --force Force yes on prompts (if supported) + -l, --lagoon string The Lagoon instance to interact with + --no-header No header on table (if supported) + --output-csv Output as CSV (if supported) + --output-json Output as JSON (if supported) + --pretty Make JSON pretty (if supported) + -p, --project string Specify a project to use + --skip-update-check Skip checking for updates + -i, --ssh-key string Specify path to a specific SSH key to use for lagoon authentication + --ssh-publickey string Specify path to a specific SSH public key to use for lagoon authentication using ssh-agent. + This will override any public key identities defined in configuration + --strict-host-key-checking string Similar to SSH StrictHostKeyChecking (accept-new, no, ignore) (default "accept-new") + -v, --verbose Enable verbose output to stderr (if supported) +``` + +### SEE ALSO + +* [lagoon configuration](lagoon_configuration.md) - Manage or view the contexts and users for interacting with Lagoon + diff --git a/docs/commands/lagoon_config_lagoon-version.md b/docs/commands/lagoon_configuration_default-context.md similarity index 81% rename from docs/commands/lagoon_config_lagoon-version.md rename to docs/commands/lagoon_configuration_default-context.md index 6486d67b..7cd59135 100644 --- a/docs/commands/lagoon_config_lagoon-version.md +++ b/docs/commands/lagoon_configuration_default-context.md @@ -1,15 +1,16 @@ -## lagoon config lagoon-version +## lagoon configuration default-context -Checks the current Lagoon for its version and sets it in the config file +Change which context is the default ``` -lagoon config lagoon-version [flags] +lagoon configuration default-context [flags] ``` ### Options ``` - -h, --help help for lagoon-version + -h, --help help for default-context + --name string The name of the context to be default ``` ### Options inherited from parent commands @@ -35,5 +36,5 @@ lagoon config lagoon-version [flags] ### SEE ALSO -* [lagoon config](lagoon_config.md) - Configure Lagoon CLI +* [lagoon configuration](lagoon_configuration.md) - Manage or view the contexts and users for interacting with Lagoon diff --git a/docs/commands/lagoon_configuration_feature.md b/docs/commands/lagoon_configuration_feature.md new file mode 100644 index 00000000..513c0496 --- /dev/null +++ b/docs/commands/lagoon_configuration_feature.md @@ -0,0 +1,42 @@ +## lagoon configuration feature + +Enable or disable a feature for all contexts or a specific context + +``` +lagoon configuration feature [flags] +``` + +### Options + +``` + --context string If provided the feature will be enabled for this context, otherwise globally + --feature string The name of the feature to enable or disable [environment-from-directory,disable-update-check,ssh-token,no-strict-host-key-checking] + -h, --help help for feature + --state The state of the feature (--state=true or --state=false) +``` + +### Options inherited from parent commands + +``` + --config-file string Path to the config file to use (must be *.yml or *.yaml) + --debug Enable debugging output (if supported) + -e, --environment string Specify an environment to use + --force Force yes on prompts (if supported) + -l, --lagoon string The Lagoon instance to interact with + --no-header No header on table (if supported) + --output-csv Output as CSV (if supported) + --output-json Output as JSON (if supported) + --pretty Make JSON pretty (if supported) + -p, --project string Specify a project to use + --skip-update-check Skip checking for updates + -i, --ssh-key string Specify path to a specific SSH key to use for lagoon authentication + --ssh-publickey string Specify path to a specific SSH public key to use for lagoon authentication using ssh-agent. + This will override any public key identities defined in configuration + --strict-host-key-checking string Similar to SSH StrictHostKeyChecking (accept-new, no, ignore) (default "accept-new") + -v, --verbose Enable verbose output to stderr (if supported) +``` + +### SEE ALSO + +* [lagoon configuration](lagoon_configuration.md) - Manage or view the contexts and users for interacting with Lagoon + diff --git a/docs/commands/lagoon_config_current.md b/docs/commands/lagoon_configuration_list-contexts.md similarity index 84% rename from docs/commands/lagoon_config_current.md rename to docs/commands/lagoon_configuration_list-contexts.md index 5be10b24..dd8ff704 100644 --- a/docs/commands/lagoon_config_current.md +++ b/docs/commands/lagoon_configuration_list-contexts.md @@ -1,15 +1,15 @@ -## lagoon config current +## lagoon configuration list-contexts -Display the current Lagoon that commands would be executed against +View all configured Lagoon contexts ``` -lagoon config current [flags] +lagoon configuration list-contexts [flags] ``` ### Options ``` - -h, --help help for current + -h, --help help for list-contexts ``` ### Options inherited from parent commands @@ -35,5 +35,5 @@ lagoon config current [flags] ### SEE ALSO -* [lagoon config](lagoon_config.md) - Configure Lagoon CLI +* [lagoon configuration](lagoon_configuration.md) - Manage or view the contexts and users for interacting with Lagoon diff --git a/docs/commands/lagoon_config_delete.md b/docs/commands/lagoon_configuration_list-users.md similarity index 84% rename from docs/commands/lagoon_config_delete.md rename to docs/commands/lagoon_configuration_list-users.md index 97f7c120..adb84a96 100644 --- a/docs/commands/lagoon_config_delete.md +++ b/docs/commands/lagoon_configuration_list-users.md @@ -1,15 +1,15 @@ -## lagoon config delete +## lagoon configuration list-users -Delete a Lagoon instance configuration +View all configured Lagoon context users ``` -lagoon config delete [flags] +lagoon configuration list-users [flags] ``` ### Options ``` - -h, --help help for delete + -h, --help help for list-users ``` ### Options inherited from parent commands @@ -35,5 +35,5 @@ lagoon config delete [flags] ### SEE ALSO -* [lagoon config](lagoon_config.md) - Configure Lagoon CLI +* [lagoon configuration](lagoon_configuration.md) - Manage or view the contexts and users for interacting with Lagoon diff --git a/docs/commands/lagoon_configuration_update-context.md b/docs/commands/lagoon_configuration_update-context.md new file mode 100644 index 00000000..8447ab3a --- /dev/null +++ b/docs/commands/lagoon_configuration_update-context.md @@ -0,0 +1,47 @@ +## lagoon configuration update-context + +Update a Lagoon context + +``` +lagoon configuration update-context [flags] +``` + +### Options + +``` + --api-hostname string Lagoon API hostname (eg: https://api.lagoon.sh) + --authentication-hostname string Lagoon authentication hostname (eg: https://keycloak.lagoon.sh) + -h, --help help for update-context + --name string The name to reference this context as + --token-hostname string Lagoon Token endpoint hostname (eg: token.lagoon.sh) + --token-port int Lagoon Token endpoint port (eg: 22) + --ui-hostname string Lagoon UI hostname (eg: https://ui.lagoon.sh) + --user string The user to associate to this context + --webhook-hostname string Lagoon webhook hostname (eg: https://webhook.lagoon.sh) +``` + +### Options inherited from parent commands + +``` + --config-file string Path to the config file to use (must be *.yml or *.yaml) + --debug Enable debugging output (if supported) + -e, --environment string Specify an environment to use + --force Force yes on prompts (if supported) + -l, --lagoon string The Lagoon instance to interact with + --no-header No header on table (if supported) + --output-csv Output as CSV (if supported) + --output-json Output as JSON (if supported) + --pretty Make JSON pretty (if supported) + -p, --project string Specify a project to use + --skip-update-check Skip checking for updates + -i, --ssh-key string Specify path to a specific SSH key to use for lagoon authentication + --ssh-publickey string Specify path to a specific SSH public key to use for lagoon authentication using ssh-agent. + This will override any public key identities defined in configuration + --strict-host-key-checking string Similar to SSH StrictHostKeyChecking (accept-new, no, ignore) (default "accept-new") + -v, --verbose Enable verbose output to stderr (if supported) +``` + +### SEE ALSO + +* [lagoon configuration](lagoon_configuration.md) - Manage or view the contexts and users for interacting with Lagoon + diff --git a/docs/commands/lagoon_configuration_update-user.md b/docs/commands/lagoon_configuration_update-user.md new file mode 100644 index 00000000..8c9921c0 --- /dev/null +++ b/docs/commands/lagoon_configuration_update-user.md @@ -0,0 +1,40 @@ +## lagoon configuration update-user + +Update a Lagoon context user + +``` +lagoon configuration update-user [flags] +``` + +### Options + +``` + -h, --help help for update-user + --name string The name to reference this user as + --ssh-key string The full path to this users ssh-key +``` + +### Options inherited from parent commands + +``` + --config-file string Path to the config file to use (must be *.yml or *.yaml) + --debug Enable debugging output (if supported) + -e, --environment string Specify an environment to use + --force Force yes on prompts (if supported) + -l, --lagoon string The Lagoon instance to interact with + --no-header No header on table (if supported) + --output-csv Output as CSV (if supported) + --output-json Output as JSON (if supported) + --pretty Make JSON pretty (if supported) + -p, --project string Specify a project to use + --skip-update-check Skip checking for updates + --ssh-publickey string Specify path to a specific SSH public key to use for lagoon authentication using ssh-agent. + This will override any public key identities defined in configuration + --strict-host-key-checking string Similar to SSH StrictHostKeyChecking (accept-new, no, ignore) (default "accept-new") + -v, --verbose Enable verbose output to stderr (if supported) +``` + +### SEE ALSO + +* [lagoon configuration](lagoon_configuration.md) - Manage or view the contexts and users for interacting with Lagoon + diff --git a/docs/commands/lagoon_login.md b/docs/commands/lagoon_login.md index 140d66ec..02b975cd 100644 --- a/docs/commands/lagoon_login.md +++ b/docs/commands/lagoon_login.md @@ -1,6 +1,11 @@ ## lagoon login -Log into a Lagoon instance +Login and refresh a token + +### Synopsis + +Login and refresh a token +Optionally reset the token to force a new one to be retrieved ``` lagoon login [flags] @@ -9,7 +14,8 @@ lagoon login [flags] ### Options ``` - -h, --help help for login + -h, --help help for login + --reset-token clear the token before attempting to log in ``` ### Options inherited from parent commands diff --git a/docs/commands/lagoon_web.md b/docs/commands/lagoon_web.md deleted file mode 100644 index 84f940ba..00000000 --- a/docs/commands/lagoon_web.md +++ /dev/null @@ -1,39 +0,0 @@ -## lagoon web - -Launch the web user interface - -``` -lagoon web [flags] -``` - -### Options - -``` - -h, --help help for web -``` - -### Options inherited from parent commands - -``` - --config-file string Path to the config file to use (must be *.yml or *.yaml) - --debug Enable debugging output (if supported) - -e, --environment string Specify an environment to use - --force Force yes on prompts (if supported) - -l, --lagoon string The Lagoon instance to interact with - --no-header No header on table (if supported) - --output-csv Output as CSV (if supported) - --output-json Output as JSON (if supported) - --pretty Make JSON pretty (if supported) - -p, --project string Specify a project to use - --skip-update-check Skip checking for updates - -i, --ssh-key string Specify path to a specific SSH key to use for lagoon authentication - --ssh-publickey string Specify path to a specific SSH public key to use for lagoon authentication using ssh-agent. - This will override any public key identities defined in configuration - --strict-host-key-checking string Similar to SSH StrictHostKeyChecking (accept-new, no, ignore) (default "accept-new") - -v, --verbose Enable verbose output to stderr (if supported) -``` - -### SEE ALSO - -* [lagoon](lagoon.md) - Command line integration for Lagoon - diff --git a/docs/config.md b/docs/config.md index 321b027b..4279185b 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,95 +1,71 @@ # Introduction -By default the CLI is configured to use the `amazeeio` Lagoon. But you can also define additional Lagoons if you need to. - -The `.lagoon.yml` file will be installed in your home directory by default - -## Layout of the configuration file -The configuration file is laid out like below -```yaml -current: amazeeio -default: amazeeio -lagoons: - amazeeio: - graphql: https://api.lagoon.amazeeio.cloud/graphql - hostname: ssh.lagoon.amazeeio.cloud - port: 32222 - token: ey.....xA -``` -There are a few sections to cover off - -* `current` is the current Lagoon that you will be using, if you only have the one, it will be `amazeeio` -* `default` is the default Lagoon to use, if you always use a particular Lagoon then you can set your preference as your default -* `lagoons` is where the actual connection parameters are stored for each Lagoon, they all follow the same template. - * `graphql` is the graphql endpoint - * `hostname` is the ssh hostname - * `port` is the ssh port - * `token` is the graphql token, this is automatically generate the first time you `lagoon login` and will automatically refresh if it expires via ssh. - -# Add a Lagoon -If you want to add a different Lagoon to use, then you can use the CLI command to view the flags available -```bash -lagoon config add --lagoon LagoonName -``` -## Example -```bash -lagoon config add --lagoon amazeeio \ - --graphql https://api.lagoon.amazeeio.cloud/graphql \ - --hostname ssh.lagoon.amazeeio.cloud \ - --port 32222 -``` +Lagoon CLI uses a shared configuration package. You can [read more about it here](https://github.com/uselagoon/machinery/tree/main/utils/config). -# Delete a Lagoon -If you want to remove a Lagoon, you can use -```bash -lagoon config delete --lagoon LagoonName -``` -## Example -```bash -lagoon config delete --lagoon amazeeio -``` +It uses [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) for storing the configuration in known locations. -# Change default Lagoon -If you add additional Lagoons, you can select which one is the default you want to use by running -```bash -lagoon config default --lagoon LagoonName -``` -## Example -```bash -lagoon config default --lagoon amazeeio -``` +The main configuration file will be stored in `$XDG_CONFIG_HOME` with the file location being in a `lagoon` directory, with the actual configuration file being named `config.yaml`. -# Use a different Lagoon -If you want to temporarily use a different Lagoon, when you run any commands you can specify the flag `--lagoon` or `-l` and then the name of the Lagoon -## Example -```bash -lagoon --lagoon mylagoon list projects -``` +The full path will then be `$XDG_CONFIG_HOME/lagoon/config.yml`. + +If `$XDG_CONFIG_HOME` is not set, then depending on the operating system, this location could be different. Review the specification to understand more about this. + +# Usage + +See the sub command `lagoon configuration` for information on managing users and contexts. + +# Components + +Configuration is broken down into two core components, `users` and `contexts`. You need a user, and you need a context, a user must be linked to a context. + +Information about the two components are below, but further information can be found in the package that defines configurations if you want further information. + +## Users + +Users are a way to leverage separate SSH keys if using SSH based authentication. This user does not have to match the name of an account within the Lagoon, but a friendly name for the conifugration owner. + +## Contexts + +Contexts are a way to leverage multiple Lagoons. A context needs a user linked to it, you can change the user associated to a context, and you can define multiple contexts for the same cluster with different users. -# View Lagoons -You can view all the Lagoons you have configured by running -```bash -lagoon config list +## Features + +The Lagoon CLI has some features that can be enabled and disabled. Features may be disabled by default, depending on the feature or features name. Features can be defined globally, or for a specific context if required. + +See the help output of `lagoon configuration feature` for how to change features. + +### ssh-token + +This feature when set to `true` will only use SSH to get a token, instead of using the Lagoon provided Keycloak OAuth mechanism. This is useful if you're using the CLI and are unable to use the OAuth mechanism to authenticate with the API for any reason, for example in a CI job. + +The default value of this is `false` (or unset). + +### disable-update-check + +This feature when set to `true` will inform the CLI to not perform the automatic update checks. + +The default value of this is `false` (or unset). + +### environment-from-directory + +This feature when set to `true` will enable the abilty for the CLI to read some basic information from the directory the command is executed in to try and guess which project or environment you're in. + +> Note: The recommendation is to never use this feature. We may deprecate this feature in the future. + +The default value of this is `false` (or unset). + +# Migrating from the legacy configuration + +Previous versions of Lagoon CLI used a `.lagoon.yml` in a home directory. This is no longer the case. + +The CLI offers a way to convert a legacy configuration into the new format. It will not modify the legacy configuration though, only read it. + +You can run it in dry run mode first to see what the resulting configuration will prompt you to provide input for. ``` -Output -```yaml -You have the following lagoons configured: -Name: amazeeio - - Hostname: ssh.lagoon.amazeeio.cloud - - GraphQL: https://api.lagoon.amazeeio.cloud/graphql - - Port: 32222 -Name: mylagoon - - Hostname: ssh.mylagoon.example - - GraphQL: https://api.mylagoon.example/graphql - - Port: 32000 -Name: local - - Hostname: localhost - - GraphQL: http://localhost:3000/graphql - - Port: 2020 - -Your default lagoon is: -Name: local - -Your current lagoon is: -Name: local +lagoon configuration convert-config +``` + +If you're happy with the process, and the output looks good, you can run it again with the `--write-config` flag (you will be prompted for input again). ``` +lagoon configuration convert-config --write-config +``` \ No newline at end of file diff --git a/go.mod b/go.mod index d579ad1f..c079a84b 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.21 require ( github.com/Masterminds/semver/v3 v3.2.1 + github.com/adrg/xdg v0.4.0 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/go-github v0.0.0-20180716180158-c0b63e2f9bb1 github.com/google/uuid v1.5.0 @@ -14,14 +15,14 @@ require ( github.com/logrusorgru/aurora v2.0.3+incompatible github.com/machinebox/graphql v0.2.3-0.20181106130121-3a9253180225 github.com/manifoldco/promptui v0.9.0 - github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 github.com/skeema/knownhosts v1.3.0 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.2 - github.com/uselagoon/machinery v0.0.30 + github.com/uselagoon/machinery v0.0.31-0.20241018074943-4e806ad38cc9 go.uber.org/mock v0.4.0 golang.org/x/crypto v0.26.0 + golang.org/x/oauth2 v0.17.0 golang.org/x/term v0.23.0 gopkg.in/yaml.v3 v3.0.1 sigs.k8s.io/yaml v1.4.0 @@ -31,6 +32,7 @@ require ( github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-querystring v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect @@ -38,7 +40,10 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + golang.org/x/net v0.21.0 // indirect golang.org/x/sys v0.23.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.31.0 // indirect ) //replace github.com/uselagoon/machinery => ../machinery diff --git a/go.sum b/go.sum index 8c7f558e..adc54129 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= +github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= @@ -13,6 +15,11 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +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.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v0.0.0-20180716180158-c0b63e2f9bb1 h1:tV3a8xSFYfQgA9b54eOE0A6Db//aeD+SQWawHfhaoLs= @@ -41,8 +48,6 @@ github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98= -github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= @@ -61,23 +66,42 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/uselagoon/machinery v0.0.30 h1:nmdDyeEfts+obg4x/uIzCbHcNqyOkJZL0dUkI0hli48= -github.com/uselagoon/machinery v0.0.30/go.mod h1:RsHzIMOam3hiA4CKR12yANgzdTGy6tz4D19umjMzZyw= +github.com/uselagoon/machinery v0.0.31-0.20241018074943-4e806ad38cc9 h1:3V/5NDQYLX38SK6RQO3c/AwBFwSegAqKvbSibAlNAJs= +github.com/uselagoon/machinery v0.0.31-0.20241018074943-4e806ad38cc9/go.mod h1:RsHzIMOam3hiA4CKR12yANgzdTGy6tz4D19umjMzZyw= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= +golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/lagoon/config.go b/internal/lagoon/config.go index b93206e0..74f7734b 100644 --- a/internal/lagoon/config.go +++ b/internal/lagoon/config.go @@ -1,5 +1,20 @@ package lagoon +/* + + _ _ _ + | | | | | | + __| | ___ _ __ _ __ ___ ___ __ _| |_ ___ __| | + / _` |/ _ \ '_ \| '__/ _ \/ __/ _` | __/ _ \/ _` | +| (_| | __/ |_) | | | __/ (_| (_| | || __/ (_| | + \__,_|\___| .__/|_| \___|\___\__,_|\__\___|\__,_| + | | + |_| + +this configuration has being replaced with github.com/uselagoon/machinery/utils/config + +*/ + // Config is used for the lagoon configuration. type Config struct { Current string `json:"current"` diff --git a/pkg/output/main.go b/pkg/output/main.go index 29c28e8e..0fe0caef 100644 --- a/pkg/output/main.go +++ b/pkg/output/main.go @@ -22,13 +22,14 @@ type Data []string // Options . type Options struct { - Header bool - CSV bool - JSON bool - Pretty bool - Debug bool - Error string - MultiLine bool + Header bool + CSV bool + JSON bool + Pretty bool + Debug bool + Error string + MultiLine bool + MessagePrefix string } // Result . @@ -77,7 +78,11 @@ func RenderInfo(infoMsg string, opts Options) { } RenderJSON(jsonData, opts) } else { - os.Stderr.WriteString(fmt.Sprintf("Info: %s", trimQuotes(infoMsg))) + if opts.MessagePrefix != "" { + os.Stderr.WriteString(fmt.Sprintf("%s: %s", opts.MessagePrefix, trimQuotes(infoMsg))) + } else { + os.Stderr.WriteString(fmt.Sprintf("Info: %s", trimQuotes(infoMsg))) + } } }