diff --git a/cmd/add.go b/cmd/add.go index 1752678..27bc928 100644 --- a/cmd/add.go +++ b/cmd/add.go @@ -16,15 +16,11 @@ limitations under the License. package cmd import ( - "bufio" - "fmt" - "os" "strings" "time" "github.com/spf13/cobra" "gitlab.com/openlizz/lizz/internal/flags" - "golang.org/x/term" ) var addCmd = &cobra.Command{ @@ -91,27 +87,6 @@ func init() { rootCmd.AddCommand(addCmd) } -// readPasswordFromStdin reads a password from stdin and returns the input -// with trailing newline and/or carriage return removed. It also makes sure that terminal -// echoing is turned off if stdin is a terminal. -func readPasswordFromStdin(prompt string) (string, error) { - var out string - var err error - fmt.Fprint(os.Stdout, prompt) - stdinFD := int(os.Stdin.Fd()) - if term.IsTerminal(stdinFD) { - var inBytes []byte - inBytes, err = term.ReadPassword(int(os.Stdin.Fd())) - out = string(inBytes) - } else { - out, err = bufio.NewReader(os.Stdin).ReadString('\n') - } - if err != nil { - return "", fmt.Errorf("could not read from stdin: %w", err) - } - return strings.TrimRight(out, "\r\n"), nil -} - func mapTeamSlice(s []string, defaultPermission string) map[string]string { m := make(map[string]string, len(s)) for _, v := range s { diff --git a/cmd/add_gitlab.go b/cmd/add_gitlab.go index 71f0a6d..6222573 100644 --- a/cmd/add_gitlab.go +++ b/cmd/add_gitlab.go @@ -26,6 +26,7 @@ import ( "github.com/fluxcd/go-git-providers/gitprovider" "github.com/spf13/cobra" + "gitlab.com/openlizz/lizz/internal/gitlab" "gitlab.com/openlizz/lizz/internal/provider" "gitlab.com/openlizz/lizz/internal/repo" "gitlab.com/openlizz/lizz/internal/yaml" @@ -38,13 +39,6 @@ var addGitlabCmd = &cobra.Command{ RunE: addGitlabCmdRun, } -const ( - glDefaultPermission = "maintain" - glDefaultDomain = "gitlab.com" - glTokenEnvVar = "GITLAB_TOKEN" - gitlabProjectRegex = `\A[[:alnum:]\x{00A9}-\x{1f9ff}_][[:alnum:]\p{Pd}\x{00A9}-\x{1f9ff}_\.]*\z` -) - type addGitlabFlags struct { owner string fleet string @@ -66,7 +60,7 @@ func init() { addGitlabCmd.Flags().StringSliceVar(&addGitlabArgs.teams, "team", []string{}, "GitLab teams to be given maintainer access (also accepts comma-separated values)") addGitlabCmd.Flags().BoolVar(&addGitlabArgs.personal, "personal", false, "if true, the owner is assumed to be a GitLab user; otherwise a group") addGitlabCmd.Flags().DurationVar(&addGitlabArgs.interval, "interval", time.Minute, "sync interval") - addGitlabCmd.Flags().StringVar(&addGitlabArgs.hostname, "hostname", glDefaultDomain, "GitLab hostname") + addGitlabCmd.Flags().StringVar(&addGitlabArgs.hostname, "hostname", gitlab.DefaultDomain, "GitLab hostname") addGitlabCmd.Flags().BoolVar(&addGitlabArgs.readWriteKey, "read-write-key", false, "if true, the deploy key is configured with read/write permissions") addGitlabCmd.Flags().BoolVar(&addGitlabArgs.reconcile, "reconcile", false, "if true, the configured options are also reconciled if the repository already exists") @@ -76,16 +70,12 @@ func init() { func addGitlabCmdRun(cmd *cobra.Command, args []string) error { logger.V(0).Infof("Add new application...") - glToken := os.Getenv(glTokenEnvVar) - if glToken == "" { - var err error - glToken, err = readPasswordFromStdin("Please enter your GitLab personal access token (PAT): ") - if err != nil { - return fmt.Errorf("could not read token: %w", err) - } + glToken, err := gitlab.GetToken() + if err != nil { + return err } - if projectNameIsValid, err := regexp.MatchString(gitlabProjectRegex, addGitlabArgs.fleet); err != nil || !projectNameIsValid { + if projectNameIsValid, err := regexp.MatchString(gitlab.ProjectRegex, addGitlabArgs.fleet); err != nil || !projectNameIsValid { if err == nil { err = fmt.Errorf( "%s is an invalid project name for gitlab.\nIt can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'.", @@ -94,7 +84,7 @@ func addGitlabCmdRun(cmd *cobra.Command, args []string) error { } return err } - if projectNameIsValid, err := regexp.MatchString(gitlabProjectRegex, addGitlabArgs.destination); err != nil || !projectNameIsValid { + if projectNameIsValid, err := regexp.MatchString(gitlab.ProjectRegex, addGitlabArgs.destination); err != nil || !projectNameIsValid { if err == nil { err = fmt.Errorf( "%s is an invalid project name for gitlab.\nIt can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'.", @@ -121,7 +111,7 @@ func addGitlabCmdRun(cmd *cobra.Command, args []string) error { CaBundle: caBundle, } // Workaround for: https://github.com/fluxcd/go-git-providers/issues/55 - if hostname := providerCfg.Hostname; hostname != glDefaultDomain && + if hostname := providerCfg.Hostname; hostname != gitlab.DefaultDomain && !strings.HasPrefix(hostname, "https://") && !strings.HasPrefix(hostname, "http://") { providerCfg.Hostname = "https://" + providerCfg.Hostname @@ -159,7 +149,7 @@ func addGitlabCmdRun(cmd *cobra.Command, args []string) error { Timeout: rootArgs.timeout, Personal: addGitlabArgs.personal, Reconcile: addGitlabArgs.reconcile, - Teams: mapTeamSlice(addGitlabArgs.teams, glDefaultPermission), + Teams: mapTeamSlice(addGitlabArgs.teams, gitlab.DefaultPermission), CaBundle: caBundle, SshHostname: addArgs.sshHostname, Provider: providerClient, @@ -179,7 +169,7 @@ func addGitlabCmdRun(cmd *cobra.Command, args []string) error { Timeout: rootArgs.timeout, Personal: addGitlabArgs.personal, Reconcile: addGitlabArgs.reconcile, - Teams: mapTeamSlice(addGitlabArgs.teams, glDefaultPermission), + Teams: mapTeamSlice(addGitlabArgs.teams, gitlab.DefaultPermission), CaBundle: caBundle, SshHostname: addArgs.sshHostname, Provider: providerClient, @@ -227,7 +217,7 @@ func addGitlabCmdRun(cmd *cobra.Command, args []string) error { Timeout: rootArgs.timeout, Personal: addGitlabArgs.personal, Reconcile: addGitlabArgs.reconcile, - Teams: mapTeamSlice(addGitlabArgs.teams, glDefaultPermission), + Teams: mapTeamSlice(addGitlabArgs.teams, gitlab.DefaultPermission), SshHostname: addArgs.sshHostname, Provider: providerClient, }, status) diff --git a/cmd/env_gitlab.go b/cmd/env_gitlab.go index afd691e..4e05261 100644 --- a/cmd/env_gitlab.go +++ b/cmd/env_gitlab.go @@ -17,11 +17,11 @@ package cmd import ( "fmt" - "os" "regexp" "strings" "github.com/spf13/cobra" + "gitlab.com/openlizz/lizz/internal/gitlab" "gitlab.com/openlizz/lizz/internal/provider" "gitlab.com/openlizz/lizz/internal/repo" ) @@ -49,7 +49,7 @@ func init() { envGitlabCmd.Flags().StringVar(&envGitlabArgs.fleet, "fleet", "", "GitLab repository name where to push the application repository") envGitlabCmd.Flags().StringSliceVar(&envGitlabArgs.teams, "team", []string{}, "GitLab teams to be given maintainer access (also accepts comma-separated values)") envGitlabCmd.Flags().BoolVar(&envGitlabArgs.personal, "personal", false, "if true, the owner is assumed to be a GitLab user; otherwise a group") - envGitlabCmd.Flags().StringVar(&envGitlabArgs.hostname, "hostname", glDefaultDomain, "GitLab hostname") + envGitlabCmd.Flags().StringVar(&envGitlabArgs.hostname, "hostname", gitlab.DefaultDomain, "GitLab hostname") envGitlabCmd.Flags().BoolVar(&envGitlabArgs.reconcile, "reconcile", false, "if true, the configured options are also reconciled if the repository already exists") envCmd.AddCommand(envGitlabCmd) @@ -58,16 +58,12 @@ func init() { func envGitlabCmdRun(cmd *cobra.Command, args []string) error { logger.V(0).Infof("Add env variable to the cluster configuration...") - glToken := os.Getenv(glTokenEnvVar) - if glToken == "" { - var err error - glToken, err = readPasswordFromStdin("Please enter your GitLab personal access token (PAT): ") - if err != nil { - return fmt.Errorf("could not read token: %w", err) - } + glToken, err := gitlab.GetToken() + if err != nil { + return err } - if projectNameIsValid, err := regexp.MatchString(gitlabProjectRegex, envGitlabArgs.fleet); err != nil || !projectNameIsValid { + if projectNameIsValid, err := regexp.MatchString(gitlab.ProjectRegex, envGitlabArgs.fleet); err != nil || !projectNameIsValid { if err == nil { err = fmt.Errorf( "%s is an invalid project name for gitlab.\nIt can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'.", @@ -84,7 +80,7 @@ func envGitlabCmdRun(cmd *cobra.Command, args []string) error { Token: glToken, } // Workaround for: https://github.com/fluxcd/go-git-providers/issues/55 - if hostname := providerCfg.Hostname; hostname != glDefaultDomain && + if hostname := providerCfg.Hostname; hostname != gitlab.DefaultDomain && !strings.HasPrefix(hostname, "https://") && !strings.HasPrefix(hostname, "http://") { providerCfg.Hostname = "https://" + providerCfg.Hostname @@ -104,7 +100,7 @@ func envGitlabCmdRun(cmd *cobra.Command, args []string) error { Timeout: rootArgs.timeout, Personal: envGitlabArgs.personal, Reconcile: envGitlabArgs.reconcile, - Teams: mapTeamSlice(envGitlabArgs.teams, glDefaultPermission), + Teams: mapTeamSlice(envGitlabArgs.teams, gitlab.DefaultPermission), Provider: providerClient, }, status, diff --git a/cmd/init_gitlab.go b/cmd/init_gitlab.go index 8a2513f..9328f04 100644 --- a/cmd/init_gitlab.go +++ b/cmd/init_gitlab.go @@ -22,6 +22,7 @@ import ( "strings" "github.com/spf13/cobra" + "gitlab.com/openlizz/lizz/internal/gitlab" "gitlab.com/openlizz/lizz/internal/provider" "gitlab.com/openlizz/lizz/internal/repo" ) @@ -49,7 +50,7 @@ func init() { initGitlabCmd.Flags().StringVar(&initGitlabArgs.destination, "destination", "", "GitLab repository name where to push the application repository") initGitlabCmd.Flags().StringSliceVar(&initGitlabArgs.teams, "team", []string{}, "GitLab teams to be given maintainer access (also accepts comma-separated values)") initGitlabCmd.Flags().BoolVar(&initGitlabArgs.personal, "personal", false, "if true, the owner is assumed to be a GitLab user; otherwise a group") - initGitlabCmd.Flags().StringVar(&initGitlabArgs.hostname, "hostname", glDefaultDomain, "GitLab hostname") + initGitlabCmd.Flags().StringVar(&initGitlabArgs.hostname, "hostname", gitlab.DefaultDomain, "GitLab hostname") initGitlabCmd.Flags().BoolVar(&initGitlabArgs.reconcile, "reconcile", false, "if true, the configured options are also reconciled if the repository already exists") initCmd.AddCommand(initGitlabCmd) @@ -58,16 +59,12 @@ func init() { func initGitlabCmdRun(cmd *cobra.Command, args []string) error { logger.V(0).Infof("Initialize the cluster repository...") - glToken := os.Getenv(glTokenEnvVar) - if glToken == "" { - var err error - glToken, err = readPasswordFromStdin("Please enter your GitLab personal access token (PAT): ") - if err != nil { - return fmt.Errorf("could not read token: %w", err) - } + glToken, err := gitlab.GetToken() + if err != nil { + return err } - if projectNameIsValid, err := regexp.MatchString(gitlabProjectRegex, initGitlabArgs.destination); err != nil || !projectNameIsValid { + if projectNameIsValid, err := regexp.MatchString(gitlab.ProjectRegex, initGitlabArgs.destination); err != nil || !projectNameIsValid { if err == nil { err = fmt.Errorf( "%s is an invalid project name for gitlab.\nIt can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'.", @@ -94,7 +91,7 @@ func initGitlabCmdRun(cmd *cobra.Command, args []string) error { CaBundle: caBundle, } // Workaround for: https://github.com/fluxcd/go-git-providers/issues/55 - if hostname := providerCfg.Hostname; hostname != glDefaultDomain && + if hostname := providerCfg.Hostname; hostname != gitlab.DefaultDomain && !strings.HasPrefix(hostname, "https://") && !strings.HasPrefix(hostname, "http://") { providerCfg.Hostname = "https://" + providerCfg.Hostname @@ -135,7 +132,7 @@ func initGitlabCmdRun(cmd *cobra.Command, args []string) error { Timeout: rootArgs.timeout, Personal: initGitlabArgs.personal, Reconcile: initGitlabArgs.reconcile, - Teams: mapTeamSlice(initGitlabArgs.teams, glDefaultPermission), + Teams: mapTeamSlice(initGitlabArgs.teams, gitlab.DefaultPermission), SshHostname: addArgs.sshHostname, Provider: providerClient, }, status) diff --git a/cmd/remove_gitlab.go b/cmd/remove_gitlab.go index e10b056..9f95276 100644 --- a/cmd/remove_gitlab.go +++ b/cmd/remove_gitlab.go @@ -17,11 +17,11 @@ package cmd import ( "fmt" - "os" "regexp" "strings" "github.com/spf13/cobra" + "gitlab.com/openlizz/lizz/internal/gitlab" "gitlab.com/openlizz/lizz/internal/provider" "gitlab.com/openlizz/lizz/internal/repo" ) @@ -49,7 +49,7 @@ func init() { removeGitlabCmd.Flags().StringVar(&removeGitlabArgs.fleet, "fleet", "", "GitLab repository name where to push the application repository") removeGitlabCmd.Flags().StringSliceVar(&removeGitlabArgs.teams, "team", []string{}, "GitLab teams to be given maintainer access (also accepts comma-separated values)") removeGitlabCmd.Flags().BoolVar(&removeGitlabArgs.personal, "personal", false, "if true, the owner is assumed to be a GitLab user; otherwise a group") - removeGitlabCmd.Flags().StringVar(&removeGitlabArgs.hostname, "hostname", glDefaultDomain, "GitLab hostname") + removeGitlabCmd.Flags().StringVar(&removeGitlabArgs.hostname, "hostname", gitlab.DefaultDomain, "GitLab hostname") removeGitlabCmd.Flags().BoolVar(&removeGitlabArgs.reconcile, "reconcile", false, "if true, the configured options are also reconciled if the repository already exists") removeCmd.AddCommand(removeGitlabCmd) @@ -58,16 +58,12 @@ func init() { func removeGitlabCmdRun(cmd *cobra.Command, args []string) error { logger.V(0).Infof("Remove application...") - glToken := os.Getenv(glTokenEnvVar) - if glToken == "" { - var err error - glToken, err = readPasswordFromStdin("Please enter your GitLab personal access token (PAT): ") - if err != nil { - return fmt.Errorf("could not read token: %w", err) - } + glToken, err := gitlab.GetToken() + if err != nil { + return err } - if projectNameIsValid, err := regexp.MatchString(gitlabProjectRegex, removeGitlabArgs.fleet); err != nil || !projectNameIsValid { + if projectNameIsValid, err := regexp.MatchString(gitlab.ProjectRegex, removeGitlabArgs.fleet); err != nil || !projectNameIsValid { if err == nil { err = fmt.Errorf( "%s is an invalid project name for gitlab.\nIt can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'.", @@ -84,7 +80,7 @@ func removeGitlabCmdRun(cmd *cobra.Command, args []string) error { Token: glToken, } // Workaround for: https://github.com/fluxcd/go-git-providers/issues/55 - if hostname := providerCfg.Hostname; hostname != glDefaultDomain && + if hostname := providerCfg.Hostname; hostname != gitlab.DefaultDomain && !strings.HasPrefix(hostname, "https://") && !strings.HasPrefix(hostname, "http://") { providerCfg.Hostname = "https://" + providerCfg.Hostname @@ -104,7 +100,7 @@ func removeGitlabCmdRun(cmd *cobra.Command, args []string) error { Timeout: rootArgs.timeout, Personal: removeGitlabArgs.personal, Reconcile: removeGitlabArgs.reconcile, - Teams: mapTeamSlice(removeGitlabArgs.teams, glDefaultPermission), + Teams: mapTeamSlice(removeGitlabArgs.teams, gitlab.DefaultPermission), Provider: providerClient, }, status, diff --git a/cmd/secretmanagement_gitlab.go b/cmd/secretmanagement_gitlab.go index bbcb2d2..407a801 100644 --- a/cmd/secretmanagement_gitlab.go +++ b/cmd/secretmanagement_gitlab.go @@ -17,12 +17,12 @@ package cmd import ( "fmt" - "os" "regexp" "strings" "github.com/spf13/cobra" + "gitlab.com/openlizz/lizz/internal/gitlab" "gitlab.com/openlizz/lizz/internal/provider" "gitlab.com/openlizz/lizz/internal/repo" ) @@ -50,7 +50,7 @@ func init() { secretManagementGitlabCmd.Flags().StringVar(&secretManagementGitlabArgs.fleet, "fleet", "", "GitLab repository name where to push the application repository") secretManagementGitlabCmd.Flags().StringSliceVar(&secretManagementGitlabArgs.teams, "team", []string{}, "GitLab teams to be given maintainer access (also accepts comma-separated values)") secretManagementGitlabCmd.Flags().BoolVar(&secretManagementGitlabArgs.personal, "personal", false, "if true, the owner is assumed to be a GitLab user; otherwise a group") - secretManagementGitlabCmd.Flags().StringVar(&secretManagementGitlabArgs.hostname, "hostname", glDefaultDomain, "GitLab hostname") + secretManagementGitlabCmd.Flags().StringVar(&secretManagementGitlabArgs.hostname, "hostname", gitlab.DefaultDomain, "GitLab hostname") secretManagementGitlabCmd.Flags().BoolVar(&secretManagementGitlabArgs.reconcile, "reconcile", false, "if true, the configured options are also reconciled if the repository already exists") secretManagementCmd.AddCommand(secretManagementGitlabCmd) @@ -59,16 +59,12 @@ func init() { func secretManagementGitlabCmdRun(cmd *cobra.Command, args []string) error { logger.V(0).Infof("Configure secret management...") - glToken := os.Getenv(glTokenEnvVar) - if glToken == "" { - var err error - glToken, err = readPasswordFromStdin("Please enter your GitLab personal access token (PAT): ") - if err != nil { - return fmt.Errorf("could not read token: %w", err) - } + glToken, err := gitlab.GetToken() + if err != nil { + return err } - if projectNameIsValid, err := regexp.MatchString(gitlabProjectRegex, secretManagementGitlabArgs.fleet); err != nil || !projectNameIsValid { + if projectNameIsValid, err := regexp.MatchString(gitlab.ProjectRegex, secretManagementGitlabArgs.fleet); err != nil || !projectNameIsValid { if err == nil { err = fmt.Errorf( "%s is an invalid project name for gitlab.\nIt can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'.", @@ -85,7 +81,7 @@ func secretManagementGitlabCmdRun(cmd *cobra.Command, args []string) error { Token: glToken, } // Workaround for: https://github.com/fluxcd/go-git-providers/issues/55 - if hostname := providerCfg.Hostname; hostname != glDefaultDomain && + if hostname := providerCfg.Hostname; hostname != gitlab.DefaultDomain && !strings.HasPrefix(hostname, "https://") && !strings.HasPrefix(hostname, "http://") { providerCfg.Hostname = "https://" + providerCfg.Hostname @@ -105,7 +101,7 @@ func secretManagementGitlabCmdRun(cmd *cobra.Command, args []string) error { Timeout: rootArgs.timeout, Personal: secretManagementGitlabArgs.personal, Reconcile: secretManagementGitlabArgs.reconcile, - Teams: mapTeamSlice(secretManagementGitlabArgs.teams, glDefaultPermission), + Teams: mapTeamSlice(secretManagementGitlabArgs.teams, gitlab.DefaultPermission), Provider: providerClient, }, status, diff --git a/internal/gitlab/gitlab.go b/internal/gitlab/gitlab.go new file mode 100644 index 0000000..0e88140 --- /dev/null +++ b/internal/gitlab/gitlab.go @@ -0,0 +1,50 @@ +package gitlab + +import ( + "bufio" + "fmt" + "os" + "strings" + + "golang.org/x/term" +) + +const ( + DefaultPermission = "maintain" + DefaultDomain = "gitlab.com" + tokenEnvVar = "GITLAB_TOKEN" + ProjectRegex = `\A[[:alnum:]\x{00A9}-\x{1f9ff}_][[:alnum:]\p{Pd}\x{00A9}-\x{1f9ff}_\.]*\z` +) + +func GetToken() (string, error) { + glToken := os.Getenv(tokenEnvVar) + if glToken == "" { + var err error + glToken, err = readPasswordFromStdin("Please enter your GitLab personal access token (PAT): ") + if err != nil { + return "", fmt.Errorf("could not read token: %w", err) + } + } + return glToken, nil +} + +// readPasswordFromStdin reads a password from stdin and returns the input +// with trailing newline and/or carriage return removed. It also makes sure that terminal +// echoing is turned off if stdin is a terminal. +func readPasswordFromStdin(prompt string) (string, error) { + var out string + var err error + fmt.Fprint(os.Stdout, prompt) + stdinFD := int(os.Stdin.Fd()) + if term.IsTerminal(stdinFD) { + var inBytes []byte + inBytes, err = term.ReadPassword(int(os.Stdin.Fd())) + out = string(inBytes) + } else { + out, err = bufio.NewReader(os.Stdin).ReadString('\n') + } + if err != nil { + return "", fmt.Errorf("could not read from stdin: %w", err) + } + return strings.TrimRight(out, "\r\n"), nil +}