From 665de385b0635276d6370fa47b90223a4392bc1a Mon Sep 17 00:00:00 2001 From: Viktor Nagy Date: Sat, 2 Mar 2024 18:05:15 -0500 Subject: [PATCH 1/5] Create environment --- api/environment.go | 29 +++++++++ .../environment/create/environment_create.go | 62 +++++++++++++++++++ commands/environment/environment.go | 21 +++++++ commands/root.go | 2 + 4 files changed, 114 insertions(+) create mode 100644 api/environment.go create mode 100644 commands/environment/create/environment_create.go create mode 100644 commands/environment/environment.go diff --git a/api/environment.go b/api/environment.go new file mode 100644 index 000000000..03074aaad --- /dev/null +++ b/api/environment.go @@ -0,0 +1,29 @@ +package api + +import "github.com/xanzy/go-gitlab" + +var CreateEnvironment = func(client *gitlab.Client, projectID interface{}, opts *gitlab.CreateEnvironmentOptions) (*gitlab.Environment, error) { + if client == nil { + client = apiClient.Lab() + } + + environment, _, err := client.Environments.CreateEnvironment(projectID, opts) + if err != nil { + return nil, err + } + + return environment, nil +} + +var GetEnvironment = func(client *gitlab.Client, projectID interface{}, environmentID int) (*gitlab.Environment, error) { + if client == nil { + client = apiClient.Lab() + } + + agent, _, err := client.Environments.GetEnvironment(projectID, environmentID) + if err != nil { + return nil, err + } + + return agent, nil +} diff --git a/commands/environment/create/environment_create.go b/commands/environment/create/environment_create.go new file mode 100644 index 000000000..451ab5648 --- /dev/null +++ b/commands/environment/create/environment_create.go @@ -0,0 +1,62 @@ +package list + +import ( + "fmt" + + "gitlab.com/gitlab-org/cli/api" + "gitlab.com/gitlab-org/cli/commands/cmdutils" + + "github.com/spf13/cobra" + "github.com/xanzy/go-gitlab" +) + +var factory *cmdutils.Factory + +func NewCmdEnvironmentCreate(f *cmdutils.Factory) *cobra.Command { + factory = f + environmentCreateCmd := &cobra.Command{ + Use: "create [flags]", + Short: `Create a project-level environment`, + Long: ``, + Args: cobra.MaximumNArgs(3), + RunE: func(cmd *cobra.Command, args []string) error { + factory = f + name, err := cmd.Flags().GetString("name") + if err != nil { + return err + } + url, err := cmd.Flags().GetString("url") + if err != nil { + return err + } + return createEnvironment(name, url) + }, + } + environmentCreateCmd.Flags().StringP("name", "n", "", "Name of the new environment") + environmentCreateCmd.Flags().StringP("url", "e", "", "External URL of the new environment") + + return environmentCreateCmd +} + +func createEnvironment(name, url string) error { + apiClient, err := factory.HttpClient() + if err != nil { + return err + } + + repo, err := factory.BaseRepo() + if err != nil { + return err + } + + _, err = api.CreateEnvironment(apiClient, repo.FullName(), &gitlab.CreateEnvironmentOptions{ + Name: &name, + ExternalURL: &url, + }) + if err != nil { + return err + } + + fmt.Fprintf(factory.IO.StdOut, "%s\n%s\n", "environment", name) + return nil +} diff --git a/commands/environment/environment.go b/commands/environment/environment.go new file mode 100644 index 000000000..283203584 --- /dev/null +++ b/commands/environment/environment.go @@ -0,0 +1,21 @@ +package cluster + +import ( + "github.com/spf13/cobra" + "gitlab.com/gitlab-org/cli/commands/cmdutils" + createCmd "gitlab.com/gitlab-org/cli/commands/environment/create" +) + +func NewCmdEnvironment(f *cmdutils.Factory) *cobra.Command { + environmentCmd := &cobra.Command{ + Use: "environment [flags]", + Short: `Manage project-level Environments`, + Long: ``, + } + + cmdutils.EnableRepoOverride(environmentCmd, f) + + environmentCmd.AddCommand(createCmd.NewCmdEnvironmentCreate(f)) + + return environmentCmd +} diff --git a/commands/root.go b/commands/root.go index 69b6ace53..6f5e4b0b2 100644 --- a/commands/root.go +++ b/commands/root.go @@ -16,6 +16,7 @@ import ( "gitlab.com/gitlab-org/cli/commands/cmdutils" completionCmd "gitlab.com/gitlab-org/cli/commands/completion" configCmd "gitlab.com/gitlab-org/cli/commands/config" + environmentCmd "gitlab.com/gitlab-org/cli/commands/environment" "gitlab.com/gitlab-org/cli/commands/help" incidentCmd "gitlab.com/gitlab-org/cli/commands/incident" issueCmd "gitlab.com/gitlab-org/cli/commands/issue" @@ -112,6 +113,7 @@ func NewCmdRoot(f *cmdutils.Factory, version, buildDate string) *cobra.Command { rootCmd.AddCommand(changelogCmd.NewCmdChangelog(f)) rootCmd.AddCommand(clusterCmd.NewCmdCluster(f)) + rootCmd.AddCommand(environmentCmd.NewCmdEnvironment(f)) rootCmd.AddCommand(issueCmd.NewCmdIssue(f)) rootCmd.AddCommand(incidentCmd.NewCmdIncident(f)) rootCmd.AddCommand(labelCmd.NewCmdLabel(f)) -- GitLab From c548342710edffab646622bc54f7d483206f5acd Mon Sep 17 00:00:00 2001 From: Viktor Nagy Date: Sat, 2 Mar 2024 21:09:46 -0500 Subject: [PATCH 2/5] Spaghetti code for the first part of agent bootstrap --- api/project_access_token.go | 16 ++ commands/cluster/agent/agent.go | 2 + .../cluster/agent/agentutils/agentutils.go | 52 ++++ .../agent/bootstrap/agent_bootstrap.go | 241 ++++++++++++++++++ 4 files changed, 311 insertions(+) create mode 100644 api/project_access_token.go create mode 100644 commands/cluster/agent/bootstrap/agent_bootstrap.go diff --git a/api/project_access_token.go b/api/project_access_token.go new file mode 100644 index 000000000..9b2a4c12b --- /dev/null +++ b/api/project_access_token.go @@ -0,0 +1,16 @@ +package api + +import "github.com/xanzy/go-gitlab" + +var CreateProjectAccessToken = func(client *gitlab.Client, projectID interface{}, opts *gitlab.CreateProjectAccessTokenOptions) (*gitlab.ProjectAccessToken, error) { + if client == nil { + client = apiClient.Lab() + } + + pat, _, err := client.ProjectAccessTokens.CreateProjectAccessToken(projectID, opts) + if err != nil { + return nil, err + } + + return pat, nil +} diff --git a/commands/cluster/agent/agent.go b/commands/cluster/agent/agent.go index 15180c515..fe952dc23 100644 --- a/commands/cluster/agent/agent.go +++ b/commands/cluster/agent/agent.go @@ -2,6 +2,7 @@ package cluster import ( "github.com/spf13/cobra" + agentBootstrapCmd "gitlab.com/gitlab-org/cli/commands/cluster/agent/bootstrap" agentGetTokenCmd "gitlab.com/gitlab-org/cli/commands/cluster/agent/get_token" agentListCmd "gitlab.com/gitlab-org/cli/commands/cluster/agent/list" agentUpdateKubeconfigCmd "gitlab.com/gitlab-org/cli/commands/cluster/agent/update_kubeconfig" @@ -17,6 +18,7 @@ func NewCmdAgent(f *cmdutils.Factory) *cobra.Command { cmdutils.EnableRepoOverride(agentCmd, f) + agentCmd.AddCommand(agentBootstrapCmd.AgentBootstrapCmd(f)) agentCmd.AddCommand(agentListCmd.NewCmdAgentList(f)) agentCmd.AddCommand(agentGetTokenCmd.NewCmdAgentGetToken(f)) agentCmd.AddCommand(agentUpdateKubeconfigCmd.NewCmdAgentUpdateKubeconfig(f)) diff --git a/commands/cluster/agent/agentutils/agentutils.go b/commands/cluster/agent/agentutils/agentutils.go index 4f25a387d..a574d5125 100644 --- a/commands/cluster/agent/agentutils/agentutils.go +++ b/commands/cluster/agent/agentutils/agentutils.go @@ -1,6 +1,10 @@ package agentutils import ( + "os" + "os/exec" + "path/filepath" + "github.com/xanzy/go-gitlab" "gitlab.com/gitlab-org/cli/pkg/iostreams" "gitlab.com/gitlab-org/cli/pkg/tableprinter" @@ -16,3 +20,51 @@ func DisplayAllAgents(io *iostreams.IOStreams, agents []*gitlab.Agent) string { } return table.Render() } + +func RunCommandToFile(cmd *exec.Cmd, p string) error { + if err := os.MkdirAll(filepath.Dir(p), 0770); err != nil { + return err + } + + outFile, err := os.Create(p) + if err != nil { + return err + } + defer outFile.Close() + + cmd.Stdout = outFile + err = cmd.Start() + if err != nil { + panic(err) + } + cmd.Wait() + + return nil +} + +func KubectlApply(io *iostreams.IOStreams, path string) error { + cmd := exec.Command("kubectl", "apply", "-f", path) + cmd.Stdout = io.StdOut + cmd.Stderr = io.StdErr + err := cmd.Run() + if err != nil { + return err + } + return nil +} + +func WriteToFile(p string, content string) error { + if err := os.MkdirAll(filepath.Dir(p), 0770); err != nil { + return err + } + outFile, err := os.Create(p) + if err != nil { + return err + } + defer outFile.Close() + _, err = outFile.WriteString(content) + if err != nil { + return err + } + return nil +} diff --git a/commands/cluster/agent/bootstrap/agent_bootstrap.go b/commands/cluster/agent/bootstrap/agent_bootstrap.go new file mode 100644 index 000000000..2124afa6f --- /dev/null +++ b/commands/cluster/agent/bootstrap/agent_bootstrap.go @@ -0,0 +1,241 @@ +package list + +import ( + "fmt" + "os/exec" + "time" + + "gitlab.com/gitlab-org/cli/api" + "gitlab.com/gitlab-org/cli/commands/cluster/agent/agentutils" + "gitlab.com/gitlab-org/cli/commands/cmdutils" + + "github.com/spf13/cobra" + "github.com/xanzy/go-gitlab" +) + +var factory *cmdutils.Factory + +func AgentBootstrapCmd(f *cmdutils.Factory) *cobra.Command { + factory = f + environmentCreateCmd := &cobra.Command{ + Use: "bootstrap [flags]", + Short: `Bootstrap a GitLab - cluster connection`, + Long: `Bootstraps a cluster connection including: + + - uses External Secrets and masked environment variables to synchronize tokens from Gitlab to the cluster + - uses Flux for GitOps application deployment + - uses the agent for Kubernetes for bidirectional GitLab - cluster communication + `, + Args: cobra.MaximumNArgs(4), + RunE: func(cmd *cobra.Command, args []string) error { + factory = f + name, err := cmd.Flags().GetString("name") + if err != nil { + return err + } + manifestDir, err := cmd.Flags().GetString("manifest-dir") + if err != nil { + return err + } + skipExternalSecrets, err := cmd.Flags().GetBool("skip-external-secrets") + if err != nil { + return err + } + return bootstrapAgent(name, manifestDir, skipExternalSecrets) + }, + } + environmentCreateCmd.Flags().StringP("name", "n", "", "Name of the new environment") + environmentCreateCmd.Flags().StringP("manifest-dir", "", "manifests", "Base directory for manifests") + environmentCreateCmd.Flags().BoolP("skip-external-secrets", "", false, "Skips creation of External Secrets") + + return environmentCreateCmd +} + +func bootstrapAgent(name, manifestDir string, skipExternalSecrets bool) error { + apiClient, err := factory.HttpClient() + if err != nil { + return err + } + + repo, err := factory.BaseRepo() + if err != nil { + return err + } + project, err := repo.Project(apiClient) + if err != nil { + return err + } + + // TODO: Validation - https://gitlab.com/groups/gitlab-org/-/epics/12594#validations + + // Create an environment to store related secrets + _, err = api.CreateEnvironment(apiClient, repo.FullName(), &gitlab.CreateEnvironmentOptions{ + Name: &name, + }) + if err != nil { + return err + } + fmt.Fprintf(factory.IO.StdOut, "Created environment: %s\n", name) + agentManifestDir := fmt.Sprintf("%s/%s", manifestDir, name) + + if !skipExternalSecrets { + // Create a project access token for the External Secrets controller to retrieve secrets + pat_name := fmt.Sprintf("external_secrets_pat_%s", name) + now := time.Now() + expires_at := gitlab.ISOTime(now.AddDate(0, 0, 90)) + accessLevel := gitlab.MaintainerPermissions + pat, err := api.CreateProjectAccessToken(apiClient, repo.FullName(), &gitlab.CreateProjectAccessTokenOptions{ + Name: &pat_name, + Scopes: &[]string{"api"}, + AccessLevel: &accessLevel, + ExpiresAt: &expires_at, + }) + if err != nil { + return err + } + _, err = api.CreateProjectVariable(apiClient, repo.FullName(), &gitlab.CreateProjectVariableOptions{ + Key: &pat.Name, + Value: &pat.Token, + Masked: gitlab.Bool(true), + Protected: gitlab.Bool(true), + EnvironmentScope: &name, + }) + if err != nil { + return err + } + fmt.Fprintf(factory.IO.StdOut, "Created and saved project access token in environment variable: %s\n", pat.Name) + + // Create the external-secrets es_namespace + es_namespace := "external-secrets" + es_secret_ns_cmd := exec.Command("kubectl", "create", "namespace", es_namespace) + es_secret_ns_cmd.Stdout = factory.IO.StdOut + es_secret_ns_cmd.Stderr = factory.IO.StdErr + err = es_secret_ns_cmd.Run() + if err != nil { + return err + } + fmt.Fprintf(factory.IO.StdOut, "Created "+es_namespace+" namespace\n") + + // Applies the External Secret token to Kubernetes + es_gitlab_secret_name := "external-secrets-token" + es_secret_cmd := exec.Command("kubectl", "create", "secret", "generic", "-n", es_namespace, "--from-literal=token="+pat.Token, es_gitlab_secret_name) + es_secret_cmd.Stdout = factory.IO.StdOut + es_secret_cmd.Stderr = factory.IO.StdErr + err = es_secret_cmd.Run() + if err != nil { + return err + } + fmt.Fprintf(factory.IO.StdOut, "Applied External Secrets token to Kubernetes\n") + + // Generates the YAML to install the External Secrets controller to /manifests/demo-agent/external-secrets.yaml + es_helm_add_cmd := exec.Command("helm", "repo", "add", "external-secrets", "https://charts.external-secrets.io") + es_helm_add_cmd.Stdout = factory.IO.StdOut + es_helm_add_cmd.Stderr = factory.IO.StdErr + err = es_helm_add_cmd.Run() + if err != nil { + return err + } + + es_template_cmd := exec.Command("helm", "template", "external-secrets", "external-secrets/external-secrets", "-n", es_namespace) + agentutils.RunCommandToFile(es_template_cmd, agentManifestDir+"/external-secrets.yaml") + fmt.Fprintf(factory.IO.StdOut, "Generated external secrets manifests to %s\n", agentManifestDir+"/external-secrets.yaml") + + // Applies /manifests/demo-agent/external-secrets.yaml in the cluster + agentutils.KubectlApply(factory.IO, agentManifestDir+"/external-secrets.yaml") + fmt.Fprintf(factory.IO.StdOut, "Applied External Secrets manifests to Kubernetes\n") + + // TODO: Wait for external secrets to be ready + + // Generates the YAML to configure the External Secrets controller to retrieve its own token from GitLab (to allow the rotation of the token) under /manifests/demo-agent/external-secrets-gitlab.yaml + externalSecretStoreYAML := fmt.Sprintf(`apiVersion: external-secrets.io/v1beta1 +kind: SecretStore +metadata: + name: gitlab-secret-store + namespace: %s +spec: + provider: + # provider type: gitlab + gitlab: + # url: https://gitlab.mydomain.com/ + auth: + SecretRef: + accessToken: + name: %s + key: token + projectID: "%d" + environment: "%s"`, es_namespace, es_gitlab_secret_name, project.ID, name) + agentutils.WriteToFile(agentManifestDir+"/external-secrets-gitlab.yaml", externalSecretStoreYAML) + fmt.Fprintf(factory.IO.StdOut, "Generated external secrets gitlab store manifests to %s\n", agentManifestDir+"/external-secrets-gitlab.yaml") + + // Applies /manifests/demo-agent/external-secrets-gitlab.yaml in the cluster + agentutils.KubectlApply(factory.IO, agentManifestDir+"/external-secrets-gitlab.yaml") + fmt.Fprintf(factory.IO.StdOut, "Applied External Secrets gitlab store manifests to Kubernetes\n") + + // Retrieve the external secrets own token by external secrets - YAML + externalSecretOwnYAML := fmt.Sprintf(`apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: %s + namespace: %s +spec: + refreshInterval: 1h + + secretStoreRef: + kind: SecretStore + name: gitlab-secret-store # Must match SecretStore on the cluster + + target: + name: %s # Name for the secret to be created on the cluster + creationPolicy: Owner + + data: + - secretKey: token # Key given to the secret to be created on the cluster + remoteRef: + key: external_secrets_pat_%s # Key of the variable on Gitlab`, es_gitlab_secret_name, es_namespace, es_gitlab_secret_name, name) + agentutils.WriteToFile(agentManifestDir+"/external-secrets-token.yaml", externalSecretOwnYAML) + fmt.Fprintf(factory.IO.StdOut, "Generated external secrets manifests to %s\n", agentManifestDir+"/external-secrets-token.yaml") + + // Applies /manifests/demo-agent/external-secrets-token.yaml in the cluster + agentutils.KubectlApply(factory.IO, agentManifestDir+"/external-secrets-token.yaml") + fmt.Fprintf(factory.IO.StdOut, "Configured External Secrets to update its own token\n") + } + + // Creates a deployment token with read_registry rights for Flux and store it as a masked variable for the environment + + // Applies /manifests/demo-agent/external-secrets-flux.yaml in the cluster + + // Generates the YAML for Flux under /manifests/demo-agent/flux-system/ (equivalent to flux install --export > /manifests/demo-agent/flux-system/gotk-components.yaml + + // Generates the YAML for Flux to watch the OCI image registry.gitlab.com//demo-agent/flux-manifests:latest + + // Generates the YAML for Flux to watch /manifests/demo-agent/ under /manifests/demo-agent/flux-system/gotk-sync.yaml + + // Builds and pushes the registry.gitlab.com//demo-agent/flux-manifests:latest image including /manifests/demo-agent + + // Generates a kustomization to use both files in /manifests/demo-agent/flux-system/kustomization.yaml + + // Adds a CI component to /.gitlab-ci.yml to build the OCI image when any file changes under /manifests/demo-agent on the default branch + + // Applies /manifests/demo-agent/flux-system/kustomization.yaml in the cluster (with kubectl -k) + + // Commits and pushes all the above to the Gitlab project (from this point on everything is deployed by Flux through building the OCI image) + + // Registers the demo-agent with GitLab and stores the registration token as an environment variable under the demo-agent environment + + // Generates the YAML to configure the External Secrets controller to retrieve the agent registration token from GitLab to /manifests/demo-agent/external-secrets-agentk.yaml + + // Generates the YAML to deploy the most recent agentk with a Flux HelmRelease + + // Generates an agent config under /.gitlab/agents/demo-agent/config.yaml that enables user access for the current project + + // generate /manifests/demo-agent/auth/project-owner-is-namespace-admin.yaml that sets up the current project's GitLab owners as namespace admins with the necessary RoleBindings + + // Commits and pushes all the above to the Gitlab project + + // Prints the URL to the terminal to access the configured environment and check the status of the cluster + + // Prints instructions to configure local access with glab (glab cluster agent update-kubeconfig --repo '/' --agent '' --use-context) + + fmt.Fprintf(factory.IO.StdOut, "Bootstrap done\n") + return nil +} -- GitLab From 763e674b703e37f71077e341c9460b6236eba05d Mon Sep 17 00:00:00 2001 From: Viktor Nagy Date: Sun, 3 Mar 2024 10:19:42 -0500 Subject: [PATCH 3/5] Moved file generation into template files, and templates to GitLab snippets. --- .../cluster/agent/agentutils/agentutils.go | 43 +++++++- .../agent/bootstrap/agent_bootstrap.go | 104 ++++++++++-------- 2 files changed, 98 insertions(+), 49 deletions(-) diff --git a/commands/cluster/agent/agentutils/agentutils.go b/commands/cluster/agent/agentutils/agentutils.go index a574d5125..849b288bb 100644 --- a/commands/cluster/agent/agentutils/agentutils.go +++ b/commands/cluster/agent/agentutils/agentutils.go @@ -1,9 +1,12 @@ package agentutils import ( + "io" + "net/http" "os" "os/exec" "path/filepath" + "text/template" "github.com/xanzy/go-gitlab" "gitlab.com/gitlab-org/cli/pkg/iostreams" @@ -53,18 +56,54 @@ func KubectlApply(io *iostreams.IOStreams, path string) error { return nil } -func WriteToFile(p string, content string) error { +func WriteTemplateToFile(t *template.Template, p string, data interface{}) error { if err := os.MkdirAll(filepath.Dir(p), 0770); err != nil { return err } + outFile, err := os.Create(p) if err != nil { return err } defer outFile.Close() - _, err = outFile.WriteString(content) + + err = t.Execute(outFile, data) if err != nil { return err } return nil } + +func DownloadFile(url string, p string, isTemp bool) (string, error) { + var outFile io.Writer + if isTemp { + outFile, err := os.CreateTemp("", p) + if err != nil { + return "", err + } + p = outFile.Name() + defer os.Remove(p) + defer outFile.Close() + } else { + if err := os.MkdirAll(filepath.Dir(p), 0770); err != nil { + return "", err + } + outFile, err := os.Create(p) + if err != nil { + return "", err + } + defer outFile.Close() + } + + client := http.Client{} + resp, err := client.Get(url) + if err != nil { + return "", err + } + defer resp.Body.Close() + _, err = io.Copy(outFile, resp.Body) + if err != nil { + return "", err + } + return p, nil +} diff --git a/commands/cluster/agent/bootstrap/agent_bootstrap.go b/commands/cluster/agent/bootstrap/agent_bootstrap.go index 2124afa6f..a355af190 100644 --- a/commands/cluster/agent/bootstrap/agent_bootstrap.go +++ b/commands/cluster/agent/bootstrap/agent_bootstrap.go @@ -3,6 +3,7 @@ package list import ( "fmt" "os/exec" + "text/template" "time" "gitlab.com/gitlab-org/cli/api" @@ -51,6 +52,22 @@ func AgentBootstrapCmd(f *cmdutils.Factory) *cobra.Command { return environmentCreateCmd } +type SecretStore struct { + SecretStoreName string + Namespace string + ProjectID int + SecretName string +} + +type ExternalSecret struct { + ExternalSecretName string + Namespace string + SecretStoreName string + TargetSecretName string + SecretKey string + GitLabVariableName string +} + func bootstrapAgent(name, manifestDir string, skipExternalSecrets bool) error { apiClient, err := factory.HttpClient() if err != nil { @@ -65,6 +82,7 @@ func bootstrapAgent(name, manifestDir string, skipExternalSecrets bool) error { if err != nil { return err } + var snippetsBase = "https://gitlab.com/-/snippets/3682432/raw/main/" // TODO: Validation - https://gitlab.com/groups/gitlab-org/-/epics/12594#validations @@ -128,17 +146,11 @@ func bootstrapAgent(name, manifestDir string, skipExternalSecrets bool) error { fmt.Fprintf(factory.IO.StdOut, "Applied External Secrets token to Kubernetes\n") // Generates the YAML to install the External Secrets controller to /manifests/demo-agent/external-secrets.yaml - es_helm_add_cmd := exec.Command("helm", "repo", "add", "external-secrets", "https://charts.external-secrets.io") - es_helm_add_cmd.Stdout = factory.IO.StdOut - es_helm_add_cmd.Stderr = factory.IO.StdErr - err = es_helm_add_cmd.Run() + _, err = agentutils.DownloadFile(snippetsBase+"/external-secrets.yaml", agentManifestDir+"/external-secrets.yaml", false) if err != nil { return err } - - es_template_cmd := exec.Command("helm", "template", "external-secrets", "external-secrets/external-secrets", "-n", es_namespace) - agentutils.RunCommandToFile(es_template_cmd, agentManifestDir+"/external-secrets.yaml") - fmt.Fprintf(factory.IO.StdOut, "Generated external secrets manifests to %s\n", agentManifestDir+"/external-secrets.yaml") + fmt.Fprintf(factory.IO.StdOut, "Saved external secrets manifests to %s\n", agentManifestDir+"/external-secrets.yaml") // Applies /manifests/demo-agent/external-secrets.yaml in the cluster agentutils.KubectlApply(factory.IO, agentManifestDir+"/external-secrets.yaml") @@ -147,24 +159,24 @@ func bootstrapAgent(name, manifestDir string, skipExternalSecrets bool) error { // TODO: Wait for external secrets to be ready // Generates the YAML to configure the External Secrets controller to retrieve its own token from GitLab (to allow the rotation of the token) under /manifests/demo-agent/external-secrets-gitlab.yaml - externalSecretStoreYAML := fmt.Sprintf(`apiVersion: external-secrets.io/v1beta1 -kind: SecretStore -metadata: - name: gitlab-secret-store - namespace: %s -spec: - provider: - # provider type: gitlab - gitlab: - # url: https://gitlab.mydomain.com/ - auth: - SecretRef: - accessToken: - name: %s - key: token - projectID: "%d" - environment: "%s"`, es_namespace, es_gitlab_secret_name, project.ID, name) - agentutils.WriteToFile(agentManifestDir+"/external-secrets-gitlab.yaml", externalSecretStoreYAML) + // TODO: get template file from GitLab instance + externalSecretStoreYAMLTemplateFile, err := agentutils.DownloadFile(snippetsBase+"/secret-store.gotmpl", "secret_store_gotmpl-", true) + if err != nil { + return err + } + externalSecretStoreYAMLTemplate, err := template.New(externalSecretStoreYAMLTemplateFile).ParseFiles(externalSecretStoreYAMLTemplateFile) + if err != nil { + return err + } + err = agentutils.WriteTemplateToFile(externalSecretStoreYAMLTemplate, agentManifestDir+"/external-secrets-gitlab.yaml", SecretStore{ + SecretStoreName: "gitlab-secret-store", + Namespace: es_namespace, + ProjectID: project.ID, + SecretName: es_gitlab_secret_name, + }) + if err != nil { + return err + } fmt.Fprintf(factory.IO.StdOut, "Generated external secrets gitlab store manifests to %s\n", agentManifestDir+"/external-secrets-gitlab.yaml") // Applies /manifests/demo-agent/external-secrets-gitlab.yaml in the cluster @@ -172,27 +184,25 @@ spec: fmt.Fprintf(factory.IO.StdOut, "Applied External Secrets gitlab store manifests to Kubernetes\n") // Retrieve the external secrets own token by external secrets - YAML - externalSecretOwnYAML := fmt.Sprintf(`apiVersion: external-secrets.io/v1beta1 -kind: ExternalSecret -metadata: - name: %s - namespace: %s -spec: - refreshInterval: 1h - - secretStoreRef: - kind: SecretStore - name: gitlab-secret-store # Must match SecretStore on the cluster - - target: - name: %s # Name for the secret to be created on the cluster - creationPolicy: Owner - - data: - - secretKey: token # Key given to the secret to be created on the cluster - remoteRef: - key: external_secrets_pat_%s # Key of the variable on Gitlab`, es_gitlab_secret_name, es_namespace, es_gitlab_secret_name, name) - agentutils.WriteToFile(agentManifestDir+"/external-secrets-token.yaml", externalSecretOwnYAML) + externalSecretYAMLTemplateFile, err := agentutils.DownloadFile(snippetsBase+"/external-secret.gotmpl", "external_secret_gotmpl-", true) + if err != nil { + return err + } + externalSecretYAMLTemplate, err := template.New(externalSecretYAMLTemplateFile).ParseFiles(externalSecretStoreYAMLTemplateFile) + if err != nil { + return err + } + err = agentutils.WriteTemplateToFile(externalSecretYAMLTemplate, agentManifestDir+"/external-secrets-token.yaml", ExternalSecret{ + ExternalSecretName: es_gitlab_secret_name, + Namespace: es_namespace, + SecretStoreName: "gitlab-secret-store", + TargetSecretName: es_gitlab_secret_name, + SecretKey: "token", + GitLabVariableName: "external_secrets_pat_" + name, + }) + if err != nil { + return err + } fmt.Fprintf(factory.IO.StdOut, "Generated external secrets manifests to %s\n", agentManifestDir+"/external-secrets-token.yaml") // Applies /manifests/demo-agent/external-secrets-token.yaml in the cluster -- GitLab From 53243f4b49dc6782ff54e9391d9f5b45a0492ece Mon Sep 17 00:00:00 2001 From: Viktor Nagy Date: Sun, 3 Mar 2024 11:27:55 -0500 Subject: [PATCH 4/5] Ugly fix for file downloads; fixed template generation --- .../cluster/agent/agentutils/agentutils.go | 23 +++++++++++++++---- .../agent/bootstrap/agent_bootstrap.go | 21 +++++++++++++---- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/commands/cluster/agent/agentutils/agentutils.go b/commands/cluster/agent/agentutils/agentutils.go index 849b288bb..6c162da6d 100644 --- a/commands/cluster/agent/agentutils/agentutils.go +++ b/commands/cluster/agent/agentutils/agentutils.go @@ -75,15 +75,18 @@ func WriteTemplateToFile(t *template.Template, p string, data interface{}) error } func DownloadFile(url string, p string, isTemp bool) (string, error) { - var outFile io.Writer if isTemp { outFile, err := os.CreateTemp("", p) if err != nil { return "", err } p = outFile.Name() - defer os.Remove(p) defer outFile.Close() + + err = _DownloadFile(url, outFile) + if err != nil { + return "", err + } } else { if err := os.MkdirAll(filepath.Dir(p), 0770); err != nil { return "", err @@ -93,17 +96,27 @@ func DownloadFile(url string, p string, isTemp bool) (string, error) { return "", err } defer outFile.Close() + + err = _DownloadFile(url, outFile) + if err != nil { + return "", err + } } + return p, nil +} + +func _DownloadFile(url string, outFile *os.File) error { client := http.Client{} resp, err := client.Get(url) if err != nil { - return "", err + return err } defer resp.Body.Close() + _, err = io.Copy(outFile, resp.Body) if err != nil { - return "", err + return err } - return p, nil + return nil } diff --git a/commands/cluster/agent/bootstrap/agent_bootstrap.go b/commands/cluster/agent/bootstrap/agent_bootstrap.go index a355af190..5f0cdbefb 100644 --- a/commands/cluster/agent/bootstrap/agent_bootstrap.go +++ b/commands/cluster/agent/bootstrap/agent_bootstrap.go @@ -2,7 +2,9 @@ package list import ( "fmt" + "os" "os/exec" + "path" "text/template" "time" @@ -156,15 +158,25 @@ func bootstrapAgent(name, manifestDir string, skipExternalSecrets bool) error { agentutils.KubectlApply(factory.IO, agentManifestDir+"/external-secrets.yaml") fmt.Fprintf(factory.IO.StdOut, "Applied External Secrets manifests to Kubernetes\n") - // TODO: Wait for external secrets to be ready + fmt.Fprintf(factory.IO.StdOut, "Waiting for External Secrets controller to start\n") + // Wait for external secrets to be ready + es_apply_wait := exec.Command("kubectl", "wait", "--for=condition=ready", "pod", "-n", es_namespace, "-l", "app.kubernetes.io/instance=external-secrets", "--timeout=3m") + es_apply_wait.Stdout = factory.IO.StdOut + es_apply_wait.Stderr = factory.IO.StdErr + err = es_apply_wait.Run() + if err != nil { + return err + } + fmt.Fprintf(factory.IO.StdOut, "External Secrets controller started\n") // Generates the YAML to configure the External Secrets controller to retrieve its own token from GitLab (to allow the rotation of the token) under /manifests/demo-agent/external-secrets-gitlab.yaml - // TODO: get template file from GitLab instance externalSecretStoreYAMLTemplateFile, err := agentutils.DownloadFile(snippetsBase+"/secret-store.gotmpl", "secret_store_gotmpl-", true) if err != nil { return err } - externalSecretStoreYAMLTemplate, err := template.New(externalSecretStoreYAMLTemplateFile).ParseFiles(externalSecretStoreYAMLTemplateFile) + defer os.Remove(externalSecretStoreYAMLTemplateFile) + + externalSecretStoreYAMLTemplate, err := template.New(path.Base(externalSecretStoreYAMLTemplateFile)).ParseFiles(externalSecretStoreYAMLTemplateFile) if err != nil { return err } @@ -188,7 +200,8 @@ func bootstrapAgent(name, manifestDir string, skipExternalSecrets bool) error { if err != nil { return err } - externalSecretYAMLTemplate, err := template.New(externalSecretYAMLTemplateFile).ParseFiles(externalSecretStoreYAMLTemplateFile) + defer os.Remove(externalSecretYAMLTemplateFile) + externalSecretYAMLTemplate, err := template.New(path.Base(externalSecretYAMLTemplateFile)).ParseFiles(externalSecretYAMLTemplateFile) if err != nil { return err } -- GitLab From 46ba6fefaefbb458bcc36a687e679c5b512f2cf6 Mon Sep 17 00:00:00 2001 From: Viktor Nagy Date: Sun, 3 Mar 2024 19:59:26 -0500 Subject: [PATCH 5/5] Started working on Flux setup. Flux deploy token is initialized in the cluster. Moved external secrets to `gitlab-tools` namespace --- api/deploy_token.go | 16 ++ .../agent/bootstrap/agent_bootstrap.go | 198 ++++++++++++++---- 2 files changed, 170 insertions(+), 44 deletions(-) create mode 100644 api/deploy_token.go diff --git a/api/deploy_token.go b/api/deploy_token.go new file mode 100644 index 000000000..ec3874332 --- /dev/null +++ b/api/deploy_token.go @@ -0,0 +1,16 @@ +package api + +import "github.com/xanzy/go-gitlab" + +var CreateProjectDeployToken = func(client *gitlab.Client, projectID interface{}, opts *gitlab.CreateProjectDeployTokenOptions) (*gitlab.DeployToken, error) { + if client == nil { + client = apiClient.Lab() + } + + environment, _, err := client.DeployTokens.CreateProjectDeployToken(projectID, opts) + if err != nil { + return nil, err + } + + return environment, nil +} diff --git a/commands/cluster/agent/bootstrap/agent_bootstrap.go b/commands/cluster/agent/bootstrap/agent_bootstrap.go index 5f0cdbefb..8fd42bdf5 100644 --- a/commands/cluster/agent/bootstrap/agent_bootstrap.go +++ b/commands/cluster/agent/bootstrap/agent_bootstrap.go @@ -1,6 +1,8 @@ package list import ( + "encoding/base64" + "encoding/json" "fmt" "os" "os/exec" @@ -44,12 +46,17 @@ func AgentBootstrapCmd(f *cmdutils.Factory) *cobra.Command { if err != nil { return err } - return bootstrapAgent(name, manifestDir, skipExternalSecrets) + skipFlux, err := cmd.Flags().GetBool("skip-flux") + if err != nil { + return err + } + return bootstrapAgent(name, manifestDir, skipExternalSecrets, skipFlux) }, } environmentCreateCmd.Flags().StringP("name", "n", "", "Name of the new environment") environmentCreateCmd.Flags().StringP("manifest-dir", "", "manifests", "Base directory for manifests") environmentCreateCmd.Flags().BoolP("skip-external-secrets", "", false, "Skips creation of External Secrets") + environmentCreateCmd.Flags().BoolP("skip-flux", "", false, "Skips the Flux setup") return environmentCreateCmd } @@ -61,16 +68,20 @@ type SecretStore struct { SecretName string } +type ExternalSecretData struct { + SecretKey string + GitLabVariableName string +} + type ExternalSecret struct { ExternalSecretName string Namespace string SecretStoreName string TargetSecretName string - SecretKey string - GitLabVariableName string + Data []ExternalSecretData } -func bootstrapAgent(name, manifestDir string, skipExternalSecrets bool) error { +func bootstrapAgent(name, manifestDir string, skipExternalSecrets, skipFlux bool) error { apiClient, err := factory.HttpClient() if err != nil { return err @@ -85,6 +96,11 @@ func bootstrapAgent(name, manifestDir string, skipExternalSecrets bool) error { return err } var snippetsBase = "https://gitlab.com/-/snippets/3682432/raw/main/" + now := time.Now() + // Sometimes we need time.Time + expires_at_time := now.AddDate(0, 0, 90) + // Sometimes we need gitlab.ISOTime + expires_at := gitlab.ISOTime(expires_at_time) // TODO: Validation - https://gitlab.com/groups/gitlab-org/-/epics/12594#validations @@ -97,12 +113,41 @@ func bootstrapAgent(name, manifestDir string, skipExternalSecrets bool) error { } fmt.Fprintf(factory.IO.StdOut, "Created environment: %s\n", name) agentManifestDir := fmt.Sprintf("%s/%s", manifestDir, name) + bootstrap_namespace := "gitlab-tools" + + // Initialize some templates + externalSecretYAMLTemplateFile, err := agentutils.DownloadFile(snippetsBase+"/external-secret.gotmpl", "external_secret_gotmpl-", true) + if err != nil { + return err + } + defer os.Remove(externalSecretYAMLTemplateFile) + externalSecretYAMLTemplate, err := template.New(path.Base(externalSecretYAMLTemplateFile)).ParseFiles(externalSecretYAMLTemplateFile) + if err != nil { + return err + } + + // Create the bootstrap_namespace + es_secret_ns_cmd := exec.Command("kubectl", "create", "namespace", bootstrap_namespace) + es_secret_ns_cmd.Stdout = factory.IO.StdOut + es_secret_ns_cmd.Stderr = factory.IO.StdErr + err = es_secret_ns_cmd.Run() + if err != nil { + return err + } + + type DockerRegistryAuth struct { + Username string `json:"username"` + Password string `json:"password"` + Auth string `json:"auth"` + } + + type DockerRegistryConfig struct { + Auths map[string]DockerRegistryAuth `json:"auths"` + } if !skipExternalSecrets { // Create a project access token for the External Secrets controller to retrieve secrets pat_name := fmt.Sprintf("external_secrets_pat_%s", name) - now := time.Now() - expires_at := gitlab.ISOTime(now.AddDate(0, 0, 90)) accessLevel := gitlab.MaintainerPermissions pat, err := api.CreateProjectAccessToken(apiClient, repo.FullName(), &gitlab.CreateProjectAccessTokenOptions{ Name: &pat_name, @@ -125,20 +170,11 @@ func bootstrapAgent(name, manifestDir string, skipExternalSecrets bool) error { } fmt.Fprintf(factory.IO.StdOut, "Created and saved project access token in environment variable: %s\n", pat.Name) - // Create the external-secrets es_namespace - es_namespace := "external-secrets" - es_secret_ns_cmd := exec.Command("kubectl", "create", "namespace", es_namespace) - es_secret_ns_cmd.Stdout = factory.IO.StdOut - es_secret_ns_cmd.Stderr = factory.IO.StdErr - err = es_secret_ns_cmd.Run() - if err != nil { - return err - } - fmt.Fprintf(factory.IO.StdOut, "Created "+es_namespace+" namespace\n") + fmt.Fprintf(factory.IO.StdOut, "Created "+bootstrap_namespace+" namespace\n") // Applies the External Secret token to Kubernetes es_gitlab_secret_name := "external-secrets-token" - es_secret_cmd := exec.Command("kubectl", "create", "secret", "generic", "-n", es_namespace, "--from-literal=token="+pat.Token, es_gitlab_secret_name) + es_secret_cmd := exec.Command("kubectl", "create", "secret", "generic", "-n", bootstrap_namespace, "--from-literal=token="+pat.Token, es_gitlab_secret_name) es_secret_cmd.Stdout = factory.IO.StdOut es_secret_cmd.Stderr = factory.IO.StdErr err = es_secret_cmd.Run() @@ -148,7 +184,19 @@ func bootstrapAgent(name, manifestDir string, skipExternalSecrets bool) error { fmt.Fprintf(factory.IO.StdOut, "Applied External Secrets token to Kubernetes\n") // Generates the YAML to install the External Secrets controller to /manifests/demo-agent/external-secrets.yaml - _, err = agentutils.DownloadFile(snippetsBase+"/external-secrets.yaml", agentManifestDir+"/external-secrets.yaml", false) + externalSecretControllerTemplateFile, err := agentutils.DownloadFile(snippetsBase+"/external-secrets-controller.gotmpl", "external_secrets_controller_gotmpl-", true) + if err != nil { + return err + } + defer os.Remove(externalSecretControllerTemplateFile) + + externalSecretControllerYAMLTemplate, err := template.New(path.Base(externalSecretControllerTemplateFile)).ParseFiles(externalSecretControllerTemplateFile) + if err != nil { + return err + } + err = agentutils.WriteTemplateToFile(externalSecretControllerYAMLTemplate, agentManifestDir+"/external-secrets.yaml", SecretStore{ + Namespace: bootstrap_namespace, + }) if err != nil { return err } @@ -160,7 +208,7 @@ func bootstrapAgent(name, manifestDir string, skipExternalSecrets bool) error { fmt.Fprintf(factory.IO.StdOut, "Waiting for External Secrets controller to start\n") // Wait for external secrets to be ready - es_apply_wait := exec.Command("kubectl", "wait", "--for=condition=ready", "pod", "-n", es_namespace, "-l", "app.kubernetes.io/instance=external-secrets", "--timeout=3m") + es_apply_wait := exec.Command("kubectl", "wait", "--for=condition=ready", "pod", "-n", bootstrap_namespace, "-l", "app.kubernetes.io/instance=external-secrets", "--timeout=3m") es_apply_wait.Stdout = factory.IO.StdOut es_apply_wait.Stderr = factory.IO.StdErr err = es_apply_wait.Run() @@ -182,7 +230,7 @@ func bootstrapAgent(name, manifestDir string, skipExternalSecrets bool) error { } err = agentutils.WriteTemplateToFile(externalSecretStoreYAMLTemplate, agentManifestDir+"/external-secrets-gitlab.yaml", SecretStore{ SecretStoreName: "gitlab-secret-store", - Namespace: es_namespace, + Namespace: bootstrap_namespace, ProjectID: project.ID, SecretName: es_gitlab_secret_name, }) @@ -195,23 +243,18 @@ func bootstrapAgent(name, manifestDir string, skipExternalSecrets bool) error { agentutils.KubectlApply(factory.IO, agentManifestDir+"/external-secrets-gitlab.yaml") fmt.Fprintf(factory.IO.StdOut, "Applied External Secrets gitlab store manifests to Kubernetes\n") - // Retrieve the external secrets own token by external secrets - YAML - externalSecretYAMLTemplateFile, err := agentutils.DownloadFile(snippetsBase+"/external-secret.gotmpl", "external_secret_gotmpl-", true) - if err != nil { - return err - } - defer os.Remove(externalSecretYAMLTemplateFile) - externalSecretYAMLTemplate, err := template.New(path.Base(externalSecretYAMLTemplateFile)).ParseFiles(externalSecretYAMLTemplateFile) - if err != nil { - return err - } + // Retrieve the external secrets own token with external secrets - YAML err = agentutils.WriteTemplateToFile(externalSecretYAMLTemplate, agentManifestDir+"/external-secrets-token.yaml", ExternalSecret{ ExternalSecretName: es_gitlab_secret_name, - Namespace: es_namespace, + Namespace: bootstrap_namespace, SecretStoreName: "gitlab-secret-store", TargetSecretName: es_gitlab_secret_name, - SecretKey: "token", - GitLabVariableName: "external_secrets_pat_" + name, + Data: []ExternalSecretData{ + { + SecretKey: "token", + GitLabVariableName: "external_secrets_pat_" + name, + }, + }, }) if err != nil { return err @@ -223,26 +266,91 @@ func bootstrapAgent(name, manifestDir string, skipExternalSecrets bool) error { fmt.Fprintf(factory.IO.StdOut, "Configured External Secrets to update its own token\n") } - // Creates a deployment token with read_registry rights for Flux and store it as a masked variable for the environment + if !skipFlux { + + // Creates a deployment token with read_registry rights for Flux and store it as a masked variable for the environment + fluxToken, err := api.CreateProjectDeployToken(apiClient, repo.FullName(), &gitlab.CreateProjectDeployTokenOptions{ + Name: &name, + ExpiresAt: &expires_at_time, + Scopes: &[]string{"read_registry"}, + }) + if err != nil { + return err + } + var fluxVarName = "flux_deploy_token_" + name + var dockerConfig = DockerRegistryConfig{ + Auths: map[string]DockerRegistryAuth{ + "registry.gitlab.com": { + Username: fluxToken.Username, + Password: fluxToken.Token, + Auth: base64.StdEncoding.EncodeToString([]byte(fluxToken.Username + ":" + fluxToken.Token)), + }, + }, + } + dockerConfigJSON, err := json.Marshal(dockerConfig) + if err != nil { + return err + } + var dockerConfigJSONString = string(dockerConfigJSON) + _, err = api.CreateProjectVariable(apiClient, repo.FullName(), &gitlab.CreateProjectVariableOptions{ + Key: &fluxVarName, + Value: &dockerConfigJSONString, + Masked: gitlab.Bool(true), + Protected: gitlab.Bool(true), + EnvironmentScope: &name, + }) + if err != nil { + return err + } + fmt.Fprintf(factory.IO.StdOut, "Created and saved deploy token for Flux in environment variable: %s\n", fluxVarName) + + // Retrieve the flux token with external secrets - YAML + externalSecretDockerConfigYAMLTemplateFile, err := agentutils.DownloadFile(snippetsBase+"/external-secret-dockerconfig", "external_secret_gotmpl-", true) + if err != nil { + return err + } + defer os.Remove(externalSecretDockerConfigYAMLTemplateFile) + externalSecretDockerConfigYAMLTemplate, err := template.New(path.Base(externalSecretDockerConfigYAMLTemplateFile)).ParseFiles(externalSecretDockerConfigYAMLTemplateFile) + if err != nil { + return err + } + err = agentutils.WriteTemplateToFile(externalSecretDockerConfigYAMLTemplate, agentManifestDir+"/flux-token.yaml", ExternalSecret{ + ExternalSecretName: "flux-deploy-token", + Namespace: bootstrap_namespace, + SecretStoreName: "gitlab-secret-store", + TargetSecretName: "flux-gitlab-access-token", + Data: []ExternalSecretData{ + { + GitLabVariableName: fluxVarName, + }, + }, + }) + if err != nil { + return err + } + fmt.Fprintf(factory.IO.StdOut, "Generated external secrets manifests to %s\n", agentManifestDir+"/flux-token.yaml") - // Applies /manifests/demo-agent/external-secrets-flux.yaml in the cluster + // Applies /manifests/demo-agent/flux-token.yaml in the cluster + agentutils.KubectlApply(factory.IO, agentManifestDir+"flux-token.yaml") + fmt.Fprintf(factory.IO.StdOut, "Configured External Secrets to retrieve the flux token\n") - // Generates the YAML for Flux under /manifests/demo-agent/flux-system/ (equivalent to flux install --export > /manifests/demo-agent/flux-system/gotk-components.yaml + // Generates the YAML for Flux under /manifests/demo-agent/flux-system/ (equivalent to flux install --export > /manifests/demo-agent/flux-system/gotk-components.yaml - // Generates the YAML for Flux to watch the OCI image registry.gitlab.com//demo-agent/flux-manifests:latest + // Generates the YAML for Flux to watch the OCI image registry.gitlab.com//demo-agent/flux-manifests:latest - // Generates the YAML for Flux to watch /manifests/demo-agent/ under /manifests/demo-agent/flux-system/gotk-sync.yaml + // Generates a kustomization to use both files in /manifests/demo-agent/flux-system/kustomization.yaml - // Builds and pushes the registry.gitlab.com//demo-agent/flux-manifests:latest image including /manifests/demo-agent + // Builds and pushes /manifests/demo-agent to registry.gitlab.com//demo-agent/flux-manifests:latest OCI image - // Generates a kustomization to use both files in /manifests/demo-agent/flux-system/kustomization.yaml + // TODO: Adds a CI component to /.gitlab-ci.yml to build the OCI image when any file changes under /manifests/demo-agent on the default branch - // Adds a CI component to /.gitlab-ci.yml to build the OCI image when any file changes under /manifests/demo-agent on the default branch + // Applies /manifests/demo-agent/flux-system/kustomization.yaml in the cluster (with kubectl -k) to start Flux - // Applies /manifests/demo-agent/flux-system/kustomization.yaml in the cluster (with kubectl -k) + // TODO: Ask the user to continue with commit and push - // Commits and pushes all the above to the Gitlab project (from this point on everything is deployed by Flux through building the OCI image) + // Commits and pushes all the above to the Gitlab project (from this point on everything is deployed by Flux through building the OCI image) + } // Registers the demo-agent with GitLab and stores the registration token as an environment variable under the demo-agent environment // Generates the YAML to configure the External Secrets controller to retrieve the agent registration token from GitLab to /manifests/demo-agent/external-secrets-agentk.yaml @@ -251,7 +359,9 @@ func bootstrapAgent(name, manifestDir string, skipExternalSecrets bool) error { // Generates an agent config under /.gitlab/agents/demo-agent/config.yaml that enables user access for the current project - // generate /manifests/demo-agent/auth/project-owner-is-namespace-admin.yaml that sets up the current project's GitLab owners as namespace admins with the necessary RoleBindings + // Generate /manifests/demo-agent/auth/project-owner-is-namespace-admin.yaml that sets up the current project's GitLab owners as namespace admins with the necessary RoleBindings + + // TODO: Ask the user to continue with commit and push // Commits and pushes all the above to the Gitlab project -- GitLab