diff --git a/cmd/mpas/bootstrap.go b/cmd/mpas/bootstrap.go index d909444..988db46 100644 --- a/cmd/mpas/bootstrap.go +++ b/cmd/mpas/bootstrap.go @@ -42,6 +42,7 @@ func NewBootstrap(cfg *config.MpasConfig) *cobra.Command { cmd.AddCommand(NewBootstrapGithub(cfg)) cmd.AddCommand(NewBootstrapGitea(cfg)) + cmd.AddCommand(NewBootstrapGitlab(cfg)) return cmd } @@ -198,6 +199,80 @@ func NewBootstrapGitea(cfg *config.MpasConfig) *cobra.Command { return cmd } +// NewBootstrapGitlab returns a new cobra.Command for gitlab bootstrap +func NewBootstrapGitlab(cfg *config.MpasConfig) *cobra.Command { + c := &config.GitlabConfig{} + cmd := &cobra.Command{ + Use: "gitlab [flags]", + Short: "Bootstrap an mpas management repository on Gitlab", + Example: ` - Bootstrap with a private organization repository + mpas bootstrap gitlab --owner ocmOrg --repository mpas --registry ghcr.io/open-component-model/mpas-bootstrap-component --path clusters/my-cluster --hostname gitlab.example.com + + - Bootstrap with a private user repository + mpas bootstrap gitlab --owner myUser --repository mpas --registry ghcr.io/open-component-model/mpas-bootstrap-component --personal --path clusters/my-cluster --hostname gitlab.example.com + + - Bootstrap with a public user repository + mpas bootstrap gitlab --owner myUser --repository mpas --registry ghcr.io/open-component-model/mpas-bootstrap-component --personal --private=false --path clusters/my-cluster --hostname gitlab.example.com + + - Bootstrap with a public organization repository + mpas bootstrap gitlab --owner ocmOrg --repository mpas --registry ghcr.io/open-component-model/mpas-bootstrap-component --private=false --path clusters/my-cluster --hostname gitlab.example.com +`, + RunE: func(cmd *cobra.Command, args []string) (err error) { + b := bootstrap.GitlabCmd{ + Owner: c.Owner, + Personal: c.Personal, + Repository: c.Repository, + FromFile: c.FromFile, + Registry: c.Registry, + DockerconfigPath: cfg.DockerconfigPath, + Path: c.Path, + CommitMessageAppendix: c.CommitMessageAppendix, + Hostname: c.Hostname, + Components: append(env.InstallComponents, c.Components...), + CaFile: c.CaFile, + } + + token := os.Getenv(env.GitlabTokenVar) + if token == "" { + token, err = passwdFromStdin("Gitlab token: ") + if err != nil { + return fmt.Errorf("failed to read token from stdin: %w", err) + } + } + b.Token = token + + if b.Owner == "" { + return fmt.Errorf("owner must be set") + } + + if b.Repository == "" { + return fmt.Errorf("repository must be set") + } + + if b.Registry == "" { + return fmt.Errorf("registry must be set") + } + + b.Timeout, err = time.ParseDuration(cfg.Timeout) + if err != nil { + return err + } + + b.Interval, err = time.ParseDuration(c.Interval) + if err != nil { + return err + } + + return b.Execute(cmd.Context(), cfg) + + }, + } + + c.AddFlags(cmd.Flags()) + + return cmd +} + // passwdFromStdin reads a password from stdin. func passwdFromStdin(prompt string) (string, error) { // Get the initial state of the terminal. diff --git a/cmd/mpas/bootstrap/bootstrap_gitlab.go b/cmd/mpas/bootstrap/bootstrap_gitlab.go new file mode 100644 index 0000000..a18995a --- /dev/null +++ b/cmd/mpas/bootstrap/bootstrap_gitlab.go @@ -0,0 +1,133 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Gardener contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package bootstrap + +import ( + "context" + "time" + + "github.com/open-component-model/mpas/cmd/mpas/config" + "github.com/open-component-model/mpas/internal/bootstrap" + "github.com/open-component-model/mpas/internal/bootstrap/provider" + "github.com/open-component-model/mpas/internal/env" + "github.com/open-component-model/mpas/internal/kubeutils" +) + +// GitlabCmd is a command for bootstrapping a Gitlab repository +type GitlabCmd struct { + // Owner is the owner of the repository + Owner string + // Token is the token to use for authentication + Token string + // Personal indicates whether the repository is a personal repository + Personal bool + // Hostname is the hostname of the Gitlab instance + Hostname string + // Repository is the name of the repository + Repository string + // FromFile is the path to a file archive to use for bootstrapping + FromFile string + // Registry is the registry to use for the bootstrap components + Registry string + // DockerconfigPath is the path to the docker config file + DockerconfigPath string + // Path is the path in the repository to use to host the bootstrapped components yaml files + Path string + // CommitMessageAppendix is the appendix to add to the commit message + // for example to skip CI + CommitMessageAppendix string + // Private indicates whether the repository is private + Private bool + // Interval is the interval to use for reconciling + Interval time.Duration + // Timeout is the timeout to use for operations + Timeout time.Duration + // Components is the list of components to install + Components []string + // DestructiveActions indicates whether destructive actions are allowed + DestructiveActions bool + // TestURL is the URL to use for testing the management repository + TestURL string + // CaFile defines and optional root certificate for the git repository used by flux. + CaFile string + bootstrapper *bootstrap.Bootstrap +} + +// Execute executes the command and returns an error if one occurred. +func (b *GitlabCmd) Execute(ctx context.Context, cfg *config.MpasConfig) error { + t, err := time.ParseDuration(cfg.Timeout) + if err != nil { + return err + } + ctx, cancel := context.WithTimeout(ctx, t) + defer cancel() + + //if b.Hostname == "" { + // return fmt.Errorf("hostname must be specified") + //} + + providerOpts := provider.ProviderOptions{ + Provider: env.ProviderGitlab, + Hostname: b.Hostname, + Token: b.Token, + DestructiveActions: b.DestructiveActions, + } + + providerClient, err := provider.New().Build(providerOpts) + if err != nil { + return err + } + + kubeClient, err := kubeutils.KubeClient(cfg.KubeConfigArgs) + if err != nil { + return err + } + + visibility := "public" + if b.Private { + visibility = "private" + } + + transport := "https" + if cfg.PlainHTTP { + transport = "http" + } + + b.bootstrapper, err = bootstrap.New(providerClient, + bootstrap.WithOwner(b.Owner), + bootstrap.WithRepositoryName(b.Repository), + bootstrap.WithPersonal(b.Personal), + bootstrap.WithFromFile(b.FromFile), + bootstrap.WithRegistry(b.Registry), + bootstrap.WithPrinter(cfg.Printer), + bootstrap.WithComponents(b.Components), + bootstrap.WithToken(b.Token), + bootstrap.WithTransportType(transport), + bootstrap.WithDockerConfigPath(b.DockerconfigPath), + bootstrap.WithTarget(b.Path), + bootstrap.WithKubeClient(kubeClient), + bootstrap.WithRESTClientGetter(cfg.KubeConfigArgs), + bootstrap.WithInterval(b.Interval), + bootstrap.WithTimeout(b.Timeout), + bootstrap.WithCommitMessageAppendix(b.CommitMessageAppendix), + bootstrap.WithVisibility(visibility), + bootstrap.WithTestURL(b.TestURL), + bootstrap.WithRootFile(b.CaFile), + ) + + if err != nil { + return err + } + + return b.bootstrapper.Run(ctx) +} + +// Cleanup cleans up the resources created by the command. +func (b *GitlabCmd) Cleanup(ctx context.Context) error { + if b.bootstrapper != nil { + return b.bootstrapper.DeleteManagementRepository(ctx) + } + return nil +} diff --git a/cmd/mpas/config/config.go b/cmd/mpas/config/config.go index 3ae792f..b95c513 100644 --- a/cmd/mpas/config/config.go +++ b/cmd/mpas/config/config.go @@ -84,19 +84,19 @@ func (m *BootstrapConfig) AddFlags(flags *pflag.FlagSet) { flags.StringVar(&m.CaFile, "ca-file", "", "Root certificate for the remote git server.") } -// GithubConfig is the configuration for the Github bootstrap command. +// GithubConfig is the configuration for the GitHub bootstrap command. type GithubConfig struct { BootstrapConfig Personal bool } -// AddFlags adds the Github bootstrap flags to the given flag set. +// AddFlags adds the GitHub bootstrap flags to the given flag set. func (g *GithubConfig) AddFlags(flags *pflag.FlagSet) { flags.BoolVar(&g.Personal, "personal", false, "The personal access token to use to access the Github API") g.BootstrapConfig.AddFlags(flags) } -// GiteaConfig is the configuration for the Github bootstrap command. +// GiteaConfig is the configuration for the GitHub bootstrap command. type GiteaConfig struct { BootstrapConfig Personal bool @@ -108,6 +108,18 @@ func (g *GiteaConfig) AddFlags(flags *pflag.FlagSet) { g.BootstrapConfig.AddFlags(flags) } +// GitlabConfig is the configuration for the Gitlab bootstrap command. +type GitlabConfig struct { + BootstrapConfig + Personal bool +} + +// AddFlags adds the Gitea bootstrap flags to the given flag set. +func (g *GitlabConfig) AddFlags(flags *pflag.FlagSet) { + flags.BoolVar(&g.Personal, "personal", false, "The personal access token to use to access the Gitlab API") + g.BootstrapConfig.AddFlags(flags) +} + // CreateConfig is the configuration shared by the create commands. type CreateConfig struct { Prune bool diff --git a/internal/bootstrap/provider/provider.go b/internal/bootstrap/provider/provider.go index ac53872..b38ce31 100644 --- a/internal/bootstrap/provider/provider.go +++ b/internal/bootstrap/provider/provider.go @@ -9,6 +9,7 @@ import ( "github.com/fluxcd/go-git-providers/gitea" "github.com/fluxcd/go-git-providers/github" + "github.com/fluxcd/go-git-providers/gitlab" "github.com/fluxcd/go-git-providers/gitprovider" "github.com/open-component-model/mpas/internal/env" ) @@ -26,6 +27,7 @@ func init() { providers = make(providerMap) providers.register(env.ProviderGithub, githubProviderFunc) providers.register(env.ProviderGitea, giteaProviderFunc) + providers.register(env.ProviderGitlab, gitlabProviderFunc) } // ProviderOptions contains the options for the provider @@ -83,6 +85,16 @@ func giteaProviderFunc(opts ProviderOptions) (gitprovider.Client, error) { return client, err } +func gitlabProviderFunc(opts ProviderOptions) (gitprovider.Client, error) { + o := makeProviderOpts(opts) + // TODO: Put that into an option somewhere. + client, err := gitlab.NewClient(opts.Token, "oauth2", o...) + if err != nil { + return nil, err + } + return client, err +} + func makeProviderOpts(opts ProviderOptions) []gitprovider.ClientOption { o := []gitprovider.ClientOption{ gitprovider.WithOAuth2Token(opts.Token), diff --git a/internal/env/env.go b/internal/env/env.go index d1e55c5..c36f953 100644 --- a/internal/env/env.go +++ b/internal/env/env.go @@ -67,6 +67,8 @@ const ( GithubTokenVar = "GITHUB_TOKEN" // GiteaTokenVar is the name of the environment variable to use to get the gitea token. GiteaTokenVar = "GITEA_TOKEN" + // GitlabTokenVar is the name of the environment variable to use to get the gitlab token. + GitlabTokenVar = "GITLAB_TOKEN" ) const ( @@ -74,6 +76,8 @@ const ( ProviderGithub = "github" // ProviderGitea is the gitea provider. ProviderGitea = "gitea" + // ProviderGitlab is the gitlab provider. + ProviderGitlab = "gitlab" ) const (