From 47c04cc7152081ee50b8e898c8d0e0e1e9facb83 Mon Sep 17 00:00:00 2001 From: Matthieu Huin Date: Tue, 23 Jan 2024 21:39:43 +0100 Subject: [PATCH] CLI: dev create demo-env Use the command to set up the test environment, and use the cli config file created in https://softwarefactory-project.io/r/c/sf-zuul-jobs-config/+/30574 Move bootstrap-tenant-config-repo to `SF bootstrap-tenant`. Change-Id: I13c73ed5cf7a5fd175c5ef53201c015d9433b0c9 --- .../bootstrap-tenant-config-repo.go | 111 ++++++++-------- cli/cmd/dev/dev.go | 119 ++++++++++++++---- cli/cmd/dev/gerrit/gerrit.go | 50 +++++++- cli/cmd/dev/runTests.go | 85 +++++++++++++ cli/cmd/nodepool.go | 10 +- cli/cmd/sf.go | 2 + cli/cmd/utils/utils.go | 5 +- cli/sfconfig/cmd/dev/run.go | 2 +- cli/sfconfig/cmd/root.go | 2 - doc/reference/cli/index.md | 21 ---- doc/reference/cli/main.md | 44 +++++++ doc/user/zuul_config_repository.md | 4 +- main.go | 11 +- playbooks/group_vars/all.yaml | 6 +- roles/clean-installations-cli/tasks/main.yaml | 2 +- .../tasks/main.yaml | 2 +- .../tasks/install_sf_ssl_cert.yaml | 2 +- .../tasks/main.yaml | 9 +- .../defaults/main.yaml | 1 - roles/run-operator-standalone/tasks/main.yaml | 2 +- .../tasks/deploy-fluentbit.yaml | 2 +- .../tasks/deploy-loki.yaml | 2 +- roles/setup-nodepool-ns/tasks/main.yaml | 2 +- roles/sfconfig-dev-prepare/tasks/main.yaml | 7 +- 24 files changed, 364 insertions(+), 139 deletions(-) rename cli/{sfconfig => }/cmd/bootstrap-tenant-config-repo/bootstrap-tenant-config-repo.go (70%) diff --git a/cli/sfconfig/cmd/bootstrap-tenant-config-repo/bootstrap-tenant-config-repo.go b/cli/cmd/bootstrap-tenant-config-repo/bootstrap-tenant-config-repo.go similarity index 70% rename from cli/sfconfig/cmd/bootstrap-tenant-config-repo/bootstrap-tenant-config-repo.go rename to cli/cmd/bootstrap-tenant-config-repo/bootstrap-tenant-config-repo.go index f0064707..06460469 100644 --- a/cli/sfconfig/cmd/bootstrap-tenant-config-repo/bootstrap-tenant-config-repo.go +++ b/cli/cmd/bootstrap-tenant-config-repo/bootstrap-tenant-config-repo.go @@ -3,34 +3,34 @@ package bootstraptenantconfigrepo import ( - "fmt" + "errors" "os" "path/filepath" utils "github.com/softwarefactory-project/sf-operator/controllers/libs/zuulcf" "github.com/spf13/cobra" "gopkg.in/yaml.v3" + ctrl "sigs.k8s.io/controller-runtime" ) var zuuldropindir = "zuul.d" var zuulplaybooks = "playbooks" -func createDirectoryStructure(path string) error { +func createDirectoryStructureOrDie(path string) { for _, dir := range []string{path, filepath.Join(path, zuulplaybooks), filepath.Join(path, zuuldropindir)} { if err := os.MkdirAll(dir, 0755); err != nil { - return fmt.Errorf(err.Error()) + ctrl.Log.Error(err, "Unable to create directory structure") + os.Exit(1) } } - return nil } -func writeFile[F any](filestructure F, path string) error { +func writeFileOrDie[F any](filestructure F, path string) { dataOutput, _ := yaml.Marshal(filestructure) if err := os.WriteFile(path, dataOutput, 0666); err != nil { - return fmt.Errorf(err.Error()) + ctrl.Log.Error(err, "Unable to write file "+path) } - return nil } func getAnsibleIncludeRole(rolename string) map[string]any { @@ -43,77 +43,87 @@ func getAnsibleIncludeRole(rolename string) map[string]any { // BootstrapTenantConfigRepoCmd command var BootstrapTenantConfigRepoCmd = &cobra.Command{ - Use: "bootstrap-tenant-config-repo", - Short: "Zuul Config Generate command", - Long: `Zuul Config Generate command -Expands sfconfig command tool -Generates Base Zuul Configurations + Use: "bootstrap-tenant", + Short: "bootstrap a tenant's config repository", + Long: `Initialize a Zuul tenant's config repository +with boilerplate code that define standard pipelines: -This will generate the the following files: +* "check" for pre-commit validation +* "gate" for approved commits gating +* "post for post-commit actions + +it also includes a boilerplate job and pre-run playbook. + +This will generate the following files: /zuul.d/-base-jobs.yaml /zuul.d/-pipeline.yaml /playbooks/-pre.yaml -Note: If the directories does not exit they will be created +Note: If the directories does not exit they will be created. `, - Example: ` - ./tools/sfconfig bootstrap-tenant-config-repo --connection gerrit-conn --driver gerrit --outpath / - ./tools/sfconfig bootstrap-tenant-config-repo --connection github-conn --driver github --outpath / - `, - Aliases: []string{"boot"}, Run: func(cmd *cobra.Command, args []string) { connection, _ := cmd.Flags().GetString("connection") driver, _ := cmd.Flags().GetString("driver") - outpath, _ := cmd.Flags().GetString("outpath") + + if len(args) != 1 { + ctrl.Log.Error(errors.New("incorrect argument"), "the command accepts only one argument as destination path") + os.Exit(1) + } + outpath := args[0] InitConfigRepo(driver, connection, outpath) - fmt.Println("Files generated at ", outpath) + ctrl.Log.Info("Repository bootstrapped at " + outpath) }, } func InitConfigRepo(driver string, connection string, zuulrootdir string) { - if err := createDirectoryStructure(zuulrootdir); err != nil { - fmt.Println(err) - } + createDirectoryStructureOrDie(zuulrootdir) // Check Pipeline requireCheck, err := utils.GetRequireCheckByDriver(driver, connection) if err != nil { - fmt.Println(err) + ctrl.Log.Error(err, "Could not define check pipeline require config for "+driver) + os.Exit(1) } triggerCheck, err := utils.GetTriggerCheckByDriver(driver, connection) if err != nil { - fmt.Println(err) + ctrl.Log.Error(err, "Could not define check pipeline trigger config for "+driver) + os.Exit(1) } reportersCheck, err := utils.GetReportersCheckByDriver(driver, connection) if err != nil { - fmt.Println(err) + ctrl.Log.Error(err, "Could not define check pipeline reporters config for "+driver) + os.Exit(1) } // Gate Pipeline requireGate, err := utils.GetRequireGateByDriver(driver, connection) if err != nil { - fmt.Println(err) + ctrl.Log.Error(err, "Could not define gate pipeline require config for "+driver) + os.Exit(1) } triggerGate, err := utils.GetTriggerGateByDriver(driver, connection) if err != nil { - fmt.Println(err) + ctrl.Log.Error(err, "Could not define gate pipeline trigger config for "+driver) + os.Exit(1) } reportersGate, err := utils.GetReportersGateByDriver(driver, connection) if err != nil { - fmt.Println(err) + ctrl.Log.Error(err, "Could not define gate pipeline trigger config for "+driver) + os.Exit(1) } // Post Pipeline triggerPost, err := utils.GetTriggerPostByDriver(driver, connection) if err != nil { - fmt.Println(err) + ctrl.Log.Error(err, "Could not define post pipeline trigger config for "+driver) + os.Exit(1) } zuulpipelinefilepath := filepath.Join(zuulrootdir, zuuldropindir, connection+"-pipeline.yaml") - if err := writeFile(utils.PipelineConfig{ + writeFileOrDie(utils.PipelineConfig{ { Pipeline: utils.PipelineBody{ Name: "check", @@ -154,12 +164,10 @@ pipeline to receive an initial +/-1 Verified vote.`, Trigger: triggerPost, }, }, - }, zuulpipelinefilepath); err != nil { - fmt.Println(err) - } + }, zuulpipelinefilepath) zuuljobfilepath := filepath.Join(zuulrootdir, zuuldropindir, connection+"-base-jobs.yaml") - if err := writeFile(utils.JobConfig{ + writeFileOrDie(utils.JobConfig{ { Job: utils.JobBody{ Name: "base", @@ -177,12 +185,10 @@ pipeline to receive an initial +/-1 Verified vote.`, Attempts: 3, }, }, - }, zuuljobfilepath); err != nil { - fmt.Println(err) - } + }, zuuljobfilepath) zuuljobplaybookfilepath := filepath.Join(zuulrootdir, zuulplaybooks, connection+"-pre.yaml") - if err := writeFile(utils.AnsiblePlayBook{ + writeFileOrDie(utils.AnsiblePlayBook{ { Hosts: "localhost", Tasks: []map[string]any{ @@ -222,12 +228,10 @@ pipeline to receive an initial +/-1 Verified vote.`, }, }, }, - }, zuuljobplaybookfilepath); err != nil { - fmt.Println(err) - } + }, zuuljobplaybookfilepath) zuulppfilepath := filepath.Join(zuulrootdir, zuuldropindir, connection+"-project-pipeline.yaml") - if err := writeFile(utils.ProjectConfig{{ + writeFileOrDie(utils.ProjectConfig{{ Project: utils.ZuulProjectBody{ Pipeline: utils.ZuulProjectPipelineMap{ "check": utils.ZuulProjectPipeline{ @@ -241,16 +245,15 @@ pipeline to receive an initial +/-1 Verified vote.`, }, }, }, - }}}, zuulppfilepath); err != nil { - fmt.Println(err) - } + }}}, zuulppfilepath) } -func init() { - BootstrapTenantConfigRepoCmd.Flags().String("connection", "", "Name of the connection or a source") - BootstrapTenantConfigRepoCmd.MarkFlagRequired("connection") - BootstrapTenantConfigRepoCmd.Flags().String("driver", "", "Driver type of the connection") - BootstrapTenantConfigRepoCmd.MarkFlagRequired("driver") - BootstrapTenantConfigRepoCmd.Flags().String("outpath", "", "Path to create file structure") - BootstrapTenantConfigRepoCmd.MarkFlagRequired("outpath") +func MkBootstrapCmd() *cobra.Command { + var ( + connection string + driver string + ) + BootstrapTenantConfigRepoCmd.Flags().StringVar(&connection, "connection", "", "Name of the connection or a source") + BootstrapTenantConfigRepoCmd.Flags().StringVar(&driver, "driver", "", "Driver type of the connection") + return BootstrapTenantConfigRepoCmd } diff --git a/cli/cmd/dev/dev.go b/cli/cmd/dev/dev.go index 32d60dfb..efe364a5 100644 --- a/cli/cmd/dev/dev.go +++ b/cli/cmd/dev/dev.go @@ -21,17 +21,20 @@ import ( "context" "errors" "os" + "path/filepath" "strings" "github.com/softwarefactory-project/sf-operator/cli/cmd/dev/gerrit" ms "github.com/softwarefactory-project/sf-operator/cli/cmd/dev/microshift" cliutils "github.com/softwarefactory-project/sf-operator/cli/cmd/utils" + "github.com/softwarefactory-project/sf-operator/controllers" + "k8s.io/client-go/rest" "github.com/spf13/cobra" ctrl "sigs.k8s.io/controller-runtime" ) -var devCreateAllowedArgs = []string{"gerrit", "microshift", "standalone-sf"} +var devCreateAllowedArgs = []string{"gerrit", "microshift", "standalone-sf", "demo-env"} var devWipeAllowedArgs = []string{"gerrit"} var devRunTestsAllowedArgs = []string{"olm", "standalone", "upgrade"} @@ -40,6 +43,23 @@ var defaultDiskSpace = "20G" var errMissingArg = errors.New("missing argument") +func createDemoEnv(env cliutils.ENV, restConfig *rest.Config, fqdn string, reposPath, sfOperatorRepoPath string, keepDemoTenantDefinition bool) { + + gerrit.EnsureGerrit(&env, fqdn) + ctrl.Log.Info("Making sure Gerrit is up and ready...") + gerrit.EnsureGerritAccess(fqdn) + for _, repo := range []string{ + "config", "demo-tenant-config", "demo-project", + } { + ctrl.Log.Info("Cloning " + repo + "...") + path := filepath.Join(reposPath, repo) + gerrit.CloneAsAdmin(&env, fqdn, repo, path, false) + } + SetupDemoConfigRepo(reposPath, "gerrit", "gerrit", !keepDemoTenantDefinition) + ctrl.Log.Info("Applying CRDs (did you run \"make manifests\" first?)...") + ApplyCRDs(restConfig, sfOperatorRepoPath) +} + func createMicroshift(kmd *cobra.Command, cliCtx cliutils.SoftwareFactoryConfigContext) { skipLocalSetup, _ := kmd.Flags().GetBool("skip-local-setup") skipDeploy, _ := kmd.Flags().GetBool("skip-deploy") @@ -119,10 +139,10 @@ func devRunTests(kmd *cobra.Command, args []string) { cliCtx := cliutils.GetCLIctxOrDie(kmd, args, runTestsAllowedArgs) target := args[0] sfOperatorRepositoryPath := cliCtx.Dev.SFOperatorRepositoryPath - extraVars := cliCtx.Dev.Tests.ExtraVars + vars, _ := kmd.Flags().GetStringSlice("extra-var") + extraVars := cliutils.VarListToMap(vars) if len(extraVars) == 0 { - vars, _ := kmd.Flags().GetStringSlice("extra-var") - extraVars = cliutils.VarListToMap(vars) + extraVars = cliCtx.Dev.Tests.ExtraVars } if sfOperatorRepositoryPath == "" { ctrl.Log.Error(errMissingArg, "The path to the sf-operator repository must be set in `dev` section of the configuration") @@ -131,12 +151,35 @@ func devRunTests(kmd *cobra.Command, args []string) { var verbosity string verbose, _ := kmd.Flags().GetBool("v") debug, _ := kmd.Flags().GetBool("vvv") + prepareDemoEnv, _ := kmd.Flags().GetBool("prepare-demo-env") if verbose { verbosity = "verbose" } if debug { verbosity = "debug" } + if prepareDemoEnv { + ns := cliCtx.Namespace + kubeContext := cliCtx.KubeContext + restConfig := controllers.GetConfigContextOrDie(kubeContext) + fqdn := cliCtx.FQDN + env := cliutils.ENV{ + Cli: cliutils.CreateKubernetesClientOrDie(kubeContext), + Ctx: context.TODO(), + Ns: ns, + } + reposPath := cliCtx.Dev.Tests.DemoReposPath + if reposPath == "" { + ctrl.Log.Info("Demo repos path unset; repos will be cloned into ./deploy") + reposPath = "deploy" + } + // overwrite demo_repos_path ansible variable + if extraVars == nil { + extraVars = make(map[string]string) + } + extraVars["demo_repos_path"] = reposPath + createDemoEnv(env, restConfig, fqdn, reposPath, sfOperatorRepositoryPath, false) + } if target == "olm" { runTestOLM(extraVars, sfOperatorRepositoryPath, verbosity) } else if target == "standalone" { @@ -152,15 +195,18 @@ func devCreate(kmd *cobra.Command, args []string) { ns := cliCtx.Namespace kubeContext := cliCtx.KubeContext fqdn := cliCtx.FQDN + // we can't initialize an env if deploying microshift, so deal this case first and exit early + if target == "microshift" { + createMicroshift(kmd, cliCtx) + return + } + env := cliutils.ENV{ + Cli: cliutils.CreateKubernetesClientOrDie(kubeContext), + Ctx: context.TODO(), + Ns: ns, + } if target == "gerrit" { - env := cliutils.ENV{ - Cli: cliutils.CreateKubernetesClientOrDie(kubeContext), - Ctx: context.TODO(), - Ns: ns, - } gerrit.EnsureGerrit(&env, fqdn) - } else if target == "microshift" { - createMicroshift(kmd, cliCtx) } else if target == "standalone-sf" { sfResource, _ := kmd.Flags().GetString("cr") hasManifest := &cliCtx.Manifest @@ -173,6 +219,24 @@ func devCreate(kmd *cobra.Command, args []string) { os.Exit(1) } applyStandalone(ns, sfResource, kubeContext) + } else if target == "demo-env" { + restConfig := controllers.GetConfigContextOrDie(kubeContext) + reposPath, _ := kmd.Flags().GetString("repos-path") + if reposPath == "" { + reposPath = cliCtx.Dev.Tests.DemoReposPath + } + if reposPath == "" { + ctrl.Log.Info("Demo repos path unset; repos will be cloned into ./deploy") + reposPath = "deploy" + } + sfOperatorRepositoryPath := cliCtx.Dev.SFOperatorRepositoryPath + if sfOperatorRepositoryPath == "" { + ctrl.Log.Error(errMissingArg, "The path to the sf-operator repository must be set in `dev` section of the configuration") + os.Exit(1) + } + keepDemoTenantDefinition, _ := kmd.Flags().GetBool("keep-demo-tenant") + createDemoEnv(env, restConfig, fqdn, reposPath, sfOperatorRepositoryPath, keepDemoTenantDefinition) + } else { ctrl.Log.Error(errors.New("unsupported target"), "Invalid argument '"+target+"'") } @@ -220,24 +284,27 @@ func devCloneAsAdmin(kmd *cobra.Command, args []string) { func MkDevCmd() *cobra.Command { var ( - deleteData bool - verifyCloneSSL bool - msSkipDeploy bool - msSkipLocalSetup bool - msSkipPostInstall bool - msDryRun bool - sfResource string - extraVars []string - testVerbose bool - testDebug bool - devCmd = &cobra.Command{ + deleteData bool + verifyCloneSSL bool + msSkipDeploy bool + msSkipLocalSetup bool + msSkipPostInstall bool + msDryRun bool + sfResource string + extraVars []string + testVerbose bool + testDebug bool + demoEnvReposPath string + demoEnvKeepTenantConfig bool + prepareDemoEnv bool + devCmd = &cobra.Command{ Use: "dev", Short: "development subcommands", Long: "These subcommands can be used to manage a dev environment and streamline recurrent development tasks like running the operator's test suite.", } createCmd = &cobra.Command{ Use: "create {" + strings.Join(devCreateAllowedArgs, ", ") + "}", - Long: "Create a development resource. The resource can be a MicroShift cluster, a standalone SF deployment, or a gerrit instance", + Long: "Create a development resource. The resource can be a MicroShift cluster, a standalone SF deployment, a demo environment or a gerrit instance", ValidArgs: devCreateAllowedArgs, Run: devCreate, } @@ -254,7 +321,7 @@ func MkDevCmd() *cobra.Command { } runTestsCmd = &cobra.Command{ Use: "run-tests TESTNAME", - Long: "Runs a test suite locally. TESTNAME can be `olm`, `standalone` or `upgrade`", + Long: "Runs a test suite locally. TESTNAME can be `olm`, `standalone` or `upgrade`. A demo environment must be ready before running the tests, either by invoking `dev create demo-env` or using the `--prepare-demo-env` flag", ValidArgs: devRunTestsAllowedArgs, Run: devRunTests, } @@ -271,9 +338,13 @@ func MkDevCmd() *cobra.Command { createCmd.Flags().StringVar(&sfResource, "cr", "", "The path to the CR defining the Software Factory deployment.") + createCmd.Flags().BoolVar(&demoEnvKeepTenantConfig, "keep-tenant-config", false, "(demo-env) Do not update the demo tenant configuration") + createCmd.Flags().StringVar(&demoEnvReposPath, "repos-path", "", "(demo-env) the path to clone demo repos at") + runTestsCmd.Flags().StringSliceVar(&extraVars, "extra-var", []string{}, "Set an extra variable in the form `key=value` to pass to the test playbook. Repeatable") runTestsCmd.Flags().BoolVar(&testVerbose, "v", false, "run ansible in verbose mode") runTestsCmd.Flags().BoolVar(&testDebug, "vvv", false, "run ansible in debug mode") + runTestsCmd.Flags().BoolVar(&prepareDemoEnv, "prepare-demo-env", false, "prepare demo environment") devCmd.AddCommand(createCmd) devCmd.AddCommand(wipeCmd) diff --git a/cli/cmd/dev/gerrit/gerrit.go b/cli/cmd/dev/gerrit/gerrit.go index a3b2adb7..6134ea70 100644 --- a/cli/cmd/dev/gerrit/gerrit.go +++ b/cli/cmd/dev/gerrit/gerrit.go @@ -18,10 +18,14 @@ limitations under the License. package gerrit import ( + "crypto/tls" "errors" "fmt" + "io" + "net/http" "os" "path/filepath" + "strconv" "time" _ "embed" @@ -498,7 +502,7 @@ func CloneAsAdmin(env *cliutils.ENV, fqdn string, repoName string, dest string, ) repoURL := GetAdminRepoURL(env, fqdn, repoName) if _, err := os.Stat(filepath.Join(dest, ".git")); os.IsNotExist(err) { - ctrl.Log.Info("Cloning repo at: " + repoURL) + ctrl.Log.Info("Cloning repo " + repoURL + " in " + dest) args := []string{} if !verify { args = append(args, "-c", "http.sslVerify=false") @@ -545,3 +549,47 @@ func CloneAsAdmin(env *cliutils.ENV, fqdn string, repoName string, dest string, } } } + +func EnsureGerritAccess(fqdn string) { + attempt := 1 + maxTries := 10 + delay := 6 * time.Second + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + client := &http.Client{Transport: tr} + var ( + resp *http.Response + err error + bodyBytes []byte + ) + for { + if attempt > maxTries { + endpointError := errors.New("endpoint failure") + ctrl.Log.Error(endpointError, "Could not reach gerrit after "+strconv.Itoa(maxTries)+" tries") + defer resp.Body.Close() + bodyBytes, err = io.ReadAll(resp.Body) + if err != nil { + ctrl.Log.Error(err, "Error reading Gerrit response") + } else { + ctrl.Log.Error(endpointError, fmt.Sprintf("Last status:%d - Last response body:\"%s\"", resp.StatusCode, string(bodyBytes))) + } + os.Exit(1) + } + url := fmt.Sprintf("https://gerrit.%s/projects/", fqdn) + ctrl.Log.Info(fmt.Sprintf("Querying Gerrit projects endpoint... [attempt %d/%d]", attempt, maxTries)) + resp, err := client.Get(url) + if err != nil { + ctrl.Log.Error(err, "Redirect failure or HTTP protocol error") + os.Exit(1) + } + if resp.StatusCode < 400 { + ctrl.Log.Info("Gerrit is up and available") + break + } + attempt += 1 + time.Sleep(delay) + } +} diff --git a/cli/cmd/dev/runTests.go b/cli/cmd/dev/runTests.go index 2d85cd30..c49c7195 100644 --- a/cli/cmd/dev/runTests.go +++ b/cli/cmd/dev/runTests.go @@ -8,13 +8,22 @@ import ( "context" _ "embed" "os" + "os/exec" "path/filepath" + "gopkg.in/yaml.v3" + "k8s.io/client-go/rest" + "github.com/apenella/go-ansible/pkg/execute" "github.com/apenella/go-ansible/pkg/options" "github.com/apenella/go-ansible/pkg/playbook" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/envtest" + + bootstraptenantconfigrepo "github.com/softwarefactory-project/sf-operator/cli/cmd/bootstrap-tenant-config-repo" + cliutils "github.com/softwarefactory-project/sf-operator/cli/cmd/utils" + "github.com/softwarefactory-project/sf-operator/controllers/libs/zuulcf" ) var runTestsAllowedArgs = []string{"standalone", "olm", "upgrade"} @@ -81,3 +90,79 @@ func runTestUpgrade(extraVars map[string]string, sfOperatorRepoPath string, verb os.Exit(1) } } + +// Prepare test environment helper functions + +func PushRepoIfNeeded(path string) { + out, err := exec.Command("git", "-C", path, "status", "--porcelain").Output() + if err != nil { + ctrl.Log.Error(err, "Could not fetch repo's status") + } + if len(out) > 0 { + ctrl.Log.Info("Pushing local repo state to origin...") + cliutils.RunCmdOrDie("git", "-C", path, "commit", "-m", "Automatic update", "-a") + cliutils.RunCmdOrDie("git", "-C", path, "push", "origin") + } +} + +// ApplyCRDs assumes that "make manifest" was run prior to being invoked. +func ApplyCRDs(config *rest.Config, sfOperatorRepoPath string) { + crdInstallOptions := envtest.CRDInstallOptions{ + Paths: []string{ + filepath.Join(sfOperatorRepoPath, "config/crd/bases/sf.softwarefactory-project.io_softwarefactories.yaml"), + filepath.Join(sfOperatorRepoPath, "config/crd/bases/sf.softwarefactory-project.io_logservers.yaml"), + }, + } + _, err := envtest.InstallCRDs(config, crdInstallOptions) + if err != nil { + ctrl.Log.Error(err, "Could not install CRDs") + } +} + +func SetupDemoConfigRepo(reposPath, zuulDriver, zuulConnection string, updateDemoTenantDefinition bool) { + var ( + configRepoPath = filepath.Join(reposPath, "config") + demoConfigRepoPath = filepath.Join(reposPath, "demo-tenant-config") + ) + // Setup demo-tenant-config + ctrl.Log.Info("Initialize demo-tenant's pipelines and jobs... ") + bootstraptenantconfigrepo.InitConfigRepo(zuulDriver, zuulConnection, demoConfigRepoPath) + cliutils.RunCmdOrDie("git", "-C", demoConfigRepoPath, "add", "zuul.d/", "playbooks/") + PushRepoIfNeeded(demoConfigRepoPath) + // Update config if needed + if updateDemoTenantDefinition { + tenantDir := filepath.Join(configRepoPath, "zuul") + if err := os.MkdirAll(tenantDir, 0755); err != nil { + ctrl.Log.Error(err, "Could not create zuul dir in config repo") + os.Exit(1) + } + tenantFile := filepath.Join(tenantDir, "main.yaml") + + tenantData := zuulcf.TenantConfig{ + { + Tenant: zuulcf.TenantBody{ + Name: "demo-tenant", + Source: zuulcf.TenantConnectionSource{ + "opendev.org": { + UntrustedProjects: []string{"zuul/zuul-jobs"}, + }, + zuulConnection: { + ConfigProjects: []string{"demo-tenant-config"}, + UntrustedProjects: []string{"demo-project"}, + }, + }, + }, + }, + } + + templateDataOutput, _ := yaml.Marshal(tenantData) + + if err := os.WriteFile(tenantFile, []byte(templateDataOutput), 0644); err != nil { + ctrl.Log.Error(err, "Could not write configuration to file") + os.Exit(1) + } + ctrl.Log.Info("Creating or updating tenant demo-tenant... ") + cliutils.RunCmdOrDie("git", "-C", configRepoPath, "add", "zuul/main.yaml") + PushRepoIfNeeded(configRepoPath) + } +} diff --git a/cli/cmd/nodepool.go b/cli/cmd/nodepool.go index 062e7238..e8d7b1b0 100644 --- a/cli/cmd/nodepool.go +++ b/cli/cmd/nodepool.go @@ -217,8 +217,8 @@ func getProvidersSecret(ns string, kubeContext string, cloudsFile string, kubeFi if cliutils.GetMOrDie(&sfEnv, controllers.NodepoolProvidersSecretsName, &secret) { if len(secret.Data["clouds.yaml"]) > 0 { if cloudsFile == "" { - println("clouds.yaml:") - println(string(secret.Data["clouds.yaml"])) + fmt.Println("clouds.yaml:") + fmt.Println(string(secret.Data["clouds.yaml"])) } else { // TODO before we write to file, we should ensure the file, if it exists, is older than // the upstream secret to avoid losing more recent secrets. @@ -228,8 +228,8 @@ func getProvidersSecret(ns string, kubeContext string, cloudsFile string, kubeFi } if len(secret.Data["kube.config"]) > 0 { if kubeFile == "" { - println("kube.config:") - println(string(secret.Data["kube.config"])) + fmt.Println("kube.config:") + fmt.Println(string(secret.Data["kube.config"])) } else { os.WriteFile(kubeFile, secret.Data["kube.config"], 0644) ctrl.Log.Info("File " + kubeFile + " updated") @@ -251,7 +251,7 @@ func getBuilderSSHKey(ns string, kubeContext string, pubKey string) { var secret apiv1.Secret if cliutils.GetMOrDie(&sfEnv, "nodepool-builder-ssh-key", &secret) { if pubKey == "" { - println(string(secret.Data["pub"])) + fmt.Println(string(secret.Data["pub"])) } else { os.WriteFile(pubKey, secret.Data["pub"], 0600) ctrl.Log.Info("File " + pubKey + " saved") diff --git a/cli/cmd/sf.go b/cli/cmd/sf.go index d40fd16b..a407c6f4 100644 --- a/cli/cmd/sf.go +++ b/cli/cmd/sf.go @@ -24,6 +24,7 @@ import ( "errors" "os" + bootstraptenantconfigrepo "github.com/softwarefactory-project/sf-operator/cli/cmd/bootstrap-tenant-config-repo" "github.com/spf13/cobra" ctrl "sigs.k8s.io/controller-runtime" ) @@ -66,6 +67,7 @@ func MkSFCmd() *cobra.Command { sfCmd.AddCommand(MkRestoreCmd()) sfCmd.AddCommand(configureCmd) sfCmd.AddCommand(MkWipeCmd()) + sfCmd.AddCommand(bootstraptenantconfigrepo.MkBootstrapCmd()) return sfCmd } diff --git a/cli/cmd/utils/utils.go b/cli/cmd/utils/utils.go index 574ceac0..1e89fde2 100644 --- a/cli/cmd/utils/utils.go +++ b/cli/cmd/utils/utils.go @@ -64,7 +64,8 @@ type SoftwareFactoryConfigContext struct { DiskFileSize string `json:"disk-file-size" mapstructure:"disk-file-size"` } `json:"microshift" mapstructure:"microshift"` Tests struct { - ExtraVars map[string]string `json:"extra-vars" mapstructure:"extra-vars"` + DemoReposPath string `json:"demo-repos-path" mapstructure:"demo-repos-path"` + ExtraVars map[string]string `json:"extra-vars" mapstructure:"extra-vars"` } `json:"tests" mapstructure:"tests"` } `json:"development" mapstructure:"development"` Components struct { @@ -118,7 +119,7 @@ func GetCLIContext(command *cobra.Command) (SoftwareFactoryConfigContext, error) if err != nil { ctrl.Log.Error(err, "Could not load config file") } else { - ctrl.Log.Info("Using configuration context " + ctxName) + ctrl.Log.V(5).Info("Using configuration context " + ctxName) } } // Override with defaults diff --git a/cli/sfconfig/cmd/dev/run.go b/cli/sfconfig/cmd/dev/run.go index 788d6597..08bf24b7 100644 --- a/cli/sfconfig/cmd/dev/run.go +++ b/cli/sfconfig/cmd/dev/run.go @@ -20,7 +20,7 @@ import ( cligerrit "github.com/softwarefactory-project/sf-operator/cli/cmd/dev/gerrit" cliutils "github.com/softwarefactory-project/sf-operator/cli/cmd/utils" - bootstraptenantconfigrepo "github.com/softwarefactory-project/sf-operator/cli/sfconfig/cmd/bootstrap-tenant-config-repo" + bootstraptenantconfigrepo "github.com/softwarefactory-project/sf-operator/cli/cmd/bootstrap-tenant-config-repo" "github.com/softwarefactory-project/sf-operator/cli/sfconfig/cmd/sfprometheus" "github.com/softwarefactory-project/sf-operator/cli/sfconfig/cmd/utils" "github.com/softwarefactory-project/sf-operator/cli/sfconfig/config" diff --git a/cli/sfconfig/cmd/root.go b/cli/sfconfig/cmd/root.go index 5b39dea6..ed09494b 100644 --- a/cli/sfconfig/cmd/root.go +++ b/cli/sfconfig/cmd/root.go @@ -21,7 +21,6 @@ import ( "fmt" "os" - bootstrap "github.com/softwarefactory-project/sf-operator/cli/sfconfig/cmd/bootstrap-tenant-config-repo" cli "github.com/softwarefactory-project/sf-operator/cli/sfconfig/cmd/dev" "github.com/softwarefactory-project/sf-operator/cli/sfconfig/cmd/gerrit" "github.com/softwarefactory-project/sf-operator/cli/sfconfig/cmd/nodepool" @@ -67,7 +66,6 @@ func init() { rootCmd.AddCommand(sfprometheus.PrometheusCmd) rootCmd.AddCommand(nodepool.ProvidersSecretsCmd) rootCmd.AddCommand(zuul.ZuulCmd) - rootCmd.AddCommand(bootstrap.BootstrapTenantConfigRepoCmd) rootCmd.AddCommand(cli.DevCmd) } diff --git a/doc/reference/cli/index.md b/doc/reference/cli/index.md index 41277cdd..dc22a1ce 100644 --- a/doc/reference/cli/index.md +++ b/doc/reference/cli/index.md @@ -13,7 +13,6 @@ of the operator, of a deployment, or also to help with the development of the op 1. [sfconfig.yaml](#sfconfigyaml) 1. [Operator management commands](#operator-management-commands) 1. [Deployment management commands](#deployment-management-commands) -1. [Deployment user commands](#deployment-user-commands) 1. [Development-related commands](#development-related-commands) ## Running sfconfig @@ -219,26 +218,6 @@ sfconfig zuul-client [...] See the [zuul-client documentation](https://zuul-ci.org/docs/zuul-client/) for more details. -## Deployment user commands - -### bootstrap-tenant-config-repo - -This command creates a scaffolding in a repository that can be modified to enable Zuul to trigger jobs on various git events. - -Usage: -```sh -sfconfig bootstrap-tenant-config-repo [...] -``` - -Flags: - -| Argument | Type | Description | Default | -|----------|------|-------|----| -| --connection |string | Name of the connection or a source|-| -| --outpath |string | Path to create file structure|-| - -See the [user documemtation](./../user/index.md) for more details. - ## Development-related commands ### gerrit diff --git a/doc/reference/cli/main.md b/doc/reference/cli/main.md index b1473ea9..2d8ab11c 100644 --- a/doc/reference/cli/main.md +++ b/doc/reference/cli/main.md @@ -11,6 +11,7 @@ deployments, beyond what can be defined in a custom resource manifest. 1. [Subcommands](#subcommands) 1. [Dev](#dev) 1. [cloneAsAdmin](#cloneasadmin) + 1. [create demo-env](#create-demo-env) 1. [create gerrit](#create-gerrit) 1. [create microshift](#create-microshift) 1. [create standalone-sf](#create-standalone-sf) @@ -25,6 +26,7 @@ deployments, beyond what can be defined in a custom resource manifest. 1. [Operator](#apply) 1. [SF](#sf) 1. [backup](#backup) + 1. [bootstrap-tenant]() 1. [configure TLS](#configure-tls) 1. [restore](#restore) 1. [wipe](#wipe) @@ -96,6 +98,8 @@ contexts: disk-file-size: 30G # Settings used when running the test suite locally tests: + # where to check out/create the demo repositories used by tests + demo-repos-path: deploy/ # Ansible extra variables to pass to the testing playbooks extra-vars: key1: value1 @@ -134,6 +138,24 @@ Flags: |----------|------|-------|----|----| | --verify | boolean | Enforce SSL validation | yes | False | +#### create demo-env + +Create a Gerrit instance if needed, then clone and populate demo repositories that will set up +a demo tenant. + +> ⚠️ This command will also install the operator's Custom Resource Definitions, so you need to run `make manifests` beforehand. + +```sh +go run ./main.go [GLOBAL FLAGS] dev create demo-env [FLAGS] +``` + +Flags: + +| Argument | Type | Description | Optional | Default | +|----------|------|-------|----|----| +| --keep-tenant-config | boolean | Do not update the demo tenant configuration | yes | False | +| --repos-path | string | Where to clone the demo repositories | yes | ./deploy/ | + #### create gerrit Create a Gerrit stateful set that can be used to host repositories and code reviews with a SF deployment. @@ -220,6 +242,7 @@ Flags: |--extra-var | string | Set an extra variable in the form `key=value` to pass to the test playbook. Repeatable | Yes | - | |--v | boolean | Run playbook in verbose mode | Yes | false | |--vvv | boolean | Run playbook in debug mode | Yes | false | +|--prepare-demo-env | boolean | Prepare a demo environment before running the test suite (see [dev create demo-env](#create-demo-env)) | Yes | false | #### wipe gerrit @@ -368,6 +391,27 @@ The following subcommands can be used to manage a Software Factory deployment an Not implemented yet +#### bootstrap-tenant + +Initialize a Zuul tenant's config repository with boilerplate code that define standard pipelines: + +* "check" for pre-commit validation +* "gate" for approved commits gating +* "post for post-commit actions + +it also includes a boilerplate job and pre-run playbook. + +```sh +go run ./main.go SF bootstrap-tenant [FLAGS] /path/to/tenant-config-repo +``` + +Flags: + +| Argument | Type | Description | Optional | Default | +|----------|------|-------|----|----| +|--connection |string | The name of the Zuul connection to use for pipelines | No | - | +|--driver |string | The driver used by the Zuul connection | No | - | + #### configure TLS The `configure TLS` subcommand can be used to inject a pre-existing set of certificates to secure diff --git a/doc/user/zuul_config_repository.md b/doc/user/zuul_config_repository.md index 6bdf7b45..0dfe81ba 100644 --- a/doc/user/zuul_config_repository.md +++ b/doc/user/zuul_config_repository.md @@ -49,7 +49,7 @@ This tenant's config project (`my-tenant-config-repo` in the example above) defi While the tenant's config project could be setup manually, we also provide a `cli` command to scaffold its content. -> Note that [zuul/zuul-jobs](https://zuul-ci.org/docs/zuul-jobs/latest/) should be part of a new tenant. The `bootstrap-tenant-config-repo` command expects that +> Note that [zuul/zuul-jobs](https://zuul-ci.org/docs/zuul-jobs/latest/) should be part of a new tenant. The `SF boostrap-tenant` command expects that this repository is part of the tenant. > The `opendev.org` connection is available by default on any Software-Factory deployment. @@ -66,7 +66,7 @@ sfconfig allows you to create a scaffolding for a new tenant's config repository Get a local checkout of the tenant's config project/repository then run: ```sh -./tools/sfconfig bootstrap-tenant-config-repo --connection [connection] --outpath [/path/to/repository] +go run ./main.go SF bootstrap-tenant --connection [connection] [/path/to/repository] ``` ### Modify and merge diff --git a/main.go b/main.go index 34680aca..cdbe6f68 100644 --- a/main.go +++ b/main.go @@ -57,6 +57,12 @@ func operatorCmd(kmd *cobra.Command, args []string) { } func main() { + opts := zap.Options{ + Development: true, + DestWriter: os.Stderr, + } + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + var ( metricsAddr string enableLeaderElection bool @@ -106,10 +112,5 @@ func main() { rootCmd.AddCommand(c) } - opts := zap.Options{ - Development: true, - } - ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) - rootCmd.Execute() } diff --git a/playbooks/group_vars/all.yaml b/playbooks/group_vars/all.yaml index c89a2556..7cb7229b 100644 --- a/playbooks/group_vars/all.yaml +++ b/playbooks/group_vars/all.yaml @@ -1,6 +1,8 @@ --- -config_path: "{{ zuul.project.src_dir }}/deploy/config" -demo_project_path: "{{ zuul.project.src_dir }}/deploy/demo-project" +demo_repos_path: "deploy" +config_path: "{{ zuul.project.src_dir }}/{{ demo_repos_path }}/config" +demo_project_path: "{{ zuul.project.src_dir }}/{{ demo_repos_path }}/demo-project" + fqdn: sfop.me validate_certs: false diff --git a/roles/clean-installations-cli/tasks/main.yaml b/roles/clean-installations-cli/tasks/main.yaml index b8bcbf02..bcdc3cca 100644 --- a/roles/clean-installations-cli/tasks/main.yaml +++ b/roles/clean-installations-cli/tasks/main.yaml @@ -2,6 +2,6 @@ - name: Clean up previous operator installation when: remote_os_host ansible.builtin.shell: > - go run ./main.go --namespace sf SF wipe --all + go run ./main.go --config /home/zuul-worker/sfcontext.yaml SF wipe --all args: chdir: "{{ zuul.project.src_dir | default(src_dir) }}" diff --git a/roles/health-check/config-update-nodepool-builder/tasks/main.yaml b/roles/health-check/config-update-nodepool-builder/tasks/main.yaml index 7453c4df..aecbefcc 100644 --- a/roles/health-check/config-update-nodepool-builder/tasks/main.yaml +++ b/roles/health-check/config-update-nodepool-builder/tasks/main.yaml @@ -41,7 +41,7 @@ # As nodepool builder will connected to the image-builder node (which is the microshift node in CI usecase) # here we ensure that the nodepool-builder pod can connect - name: Get nodepool-builder public SSH key - ansible.builtin.command: go run ./main.go --namespace sf nodepool get builder-ssh-key + ansible.builtin.command: go run ./main.go --config /home/zuul-worker/sfcontext.yaml nodepool get builder-ssh-key args: chdir: "{{ zuul.project.src_dir }}" register: nodepool_get_key diff --git a/roles/health-check/test-custom-certs/tasks/install_sf_ssl_cert.yaml b/roles/health-check/test-custom-certs/tasks/install_sf_ssl_cert.yaml index 42bc6e3f..cee7e111 100644 --- a/roles/health-check/test-custom-certs/tasks/install_sf_ssl_cert.yaml +++ b/roles/health-check/test-custom-certs/tasks/install_sf_ssl_cert.yaml @@ -1,6 +1,6 @@ --- - name: Add custom self signed cert ansible.builtin.shell: > - go run ./main.go --namespace sf SF configure TLS --CA {{ ssl_path }}/localCA.pem --cert {{ ssl_path }}/ssl.crt --key {{ ssl_path }}/ssl.key + go run ./main.go --config /home/zuul-worker/sfcontext.yaml SF configure TLS --CA {{ ssl_path }}/localCA.pem --cert {{ ssl_path }}/ssl.crt --key {{ ssl_path }}/ssl.key args: chdir: "{{ zuul.project.src_dir | default(src_dir) }}" diff --git a/roles/health-check/test-nodepool-providers-secrets/tasks/main.yaml b/roles/health-check/test-nodepool-providers-secrets/tasks/main.yaml index 8d05658a..b61aebc9 100644 --- a/roles/health-check/test-nodepool-providers-secrets/tasks/main.yaml +++ b/roles/health-check/test-nodepool-providers-secrets/tasks/main.yaml @@ -1,12 +1,7 @@ --- -- name: Prepare a minimal sfconfig.yaml file - ansible.builtin.copy: - content: "{{ sf_config_yaml | to_yaml }}" - dest: /tmp/sfconfig.yaml - - name: Dump current secrets from nodepool command: > - go run ./main.go --namespace sf nodepool get providers-secrets --clouds /tmp/clouds.yaml --kube /tmp/kubeconfig.yaml + go run ./main.go --config /home/zuul-worker/sfcontext.yaml nodepool get providers-secrets --clouds /tmp/clouds.yaml --kube /tmp/kubeconfig.yaml args: chdir: "{{ zuul.project.src_dir }}" @@ -17,7 +12,7 @@ - name: Upload clouds secrets to nodepool command: > - go run ./main.go --namespace sf nodepool configure providers-secrets --clouds /tmp/clouds.yaml --kube /tmp/kubeconfig.yaml + go run ./main.go --config /home/zuul-worker/sfcontext.yaml nodepool configure providers-secrets --clouds /tmp/clouds.yaml --kube /tmp/kubeconfig.yaml args: chdir: "{{ zuul.project.src_dir }}" diff --git a/roles/health-check/zuul-demo-tenant-workflow/defaults/main.yaml b/roles/health-check/zuul-demo-tenant-workflow/defaults/main.yaml index 9aa7f046..1984661a 100644 --- a/roles/health-check/zuul-demo-tenant-workflow/defaults/main.yaml +++ b/roles/health-check/zuul-demo-tenant-workflow/defaults/main.yaml @@ -3,4 +3,3 @@ fqdn: sfop.me zuul_endpoint: "{{ fqdn }}/zuul" zuul_api_delay: 10 zuul_api_retries: 60 -demo_config_path: deploy/demo-tenant-config diff --git a/roles/run-operator-standalone/tasks/main.yaml b/roles/run-operator-standalone/tasks/main.yaml index a35efe11..af20d7f9 100644 --- a/roles/run-operator-standalone/tasks/main.yaml +++ b/roles/run-operator-standalone/tasks/main.yaml @@ -17,7 +17,7 @@ - name: Run the operator in standalone mode ansible.builtin.shell: | set -o pipefail - go run ./main.go --namespace sf dev create standalone-sf --cr {{ cr_path }} 2>&1 | tee -a ~/zuul-output/logs/sf-operator.log + go run ./main.go --config /home/zuul-worker/sfcontext.yaml dev create standalone-sf --cr {{ cr_path }} 2>&1 | tee -a ~/zuul-output/logs/sf-operator.log args: chdir: "{{ zuul.project.src_dir }}" diff --git a/roles/setup-log-forwarding/tasks/deploy-fluentbit.yaml b/roles/setup-log-forwarding/tasks/deploy-fluentbit.yaml index 49d9c594..9c6fb3e6 100644 --- a/roles/setup-log-forwarding/tasks/deploy-fluentbit.yaml +++ b/roles/setup-log-forwarding/tasks/deploy-fluentbit.yaml @@ -6,7 +6,7 @@ chdir: "{{ loki_dir }}" - name: Wait for Fluent Bit pod to be ready - ansible.builtin.command: kubectl get pod test-fluentbit -o=jsonpath='{.status.phase}' + ansible.builtin.command: kubectl get pod test-fluentbit -n {{ namespace }} -o=jsonpath='{.status.phase}' register: fb_ready until: - fb_ready.stdout != "" diff --git a/roles/setup-log-forwarding/tasks/deploy-loki.yaml b/roles/setup-log-forwarding/tasks/deploy-loki.yaml index 3b71cd2d..c5181ab3 100644 --- a/roles/setup-log-forwarding/tasks/deploy-loki.yaml +++ b/roles/setup-log-forwarding/tasks/deploy-loki.yaml @@ -6,7 +6,7 @@ chdir: "{{ loki_dir }}" - name: Wait for Loki deployment to be ready - ansible.builtin.command: kubectl get deployment test-loki -o=jsonpath='{.status.readyReplicas}' + ansible.builtin.command: kubectl get deployment test-loki -n {{ namespace }} -o=jsonpath='{.status.readyReplicas}' register: loki_ready until: - loki_ready.stdout != "" diff --git a/roles/setup-nodepool-ns/tasks/main.yaml b/roles/setup-nodepool-ns/tasks/main.yaml index 80908b17..c0961394 100644 --- a/roles/setup-nodepool-ns/tasks/main.yaml +++ b/roles/setup-nodepool-ns/tasks/main.yaml @@ -1,6 +1,6 @@ --- - name: Set up nodepool namespace ansible.builtin.shell: > - go run ./main.go --namespace sf nodepool create openshiftpods-namespace + go run ./main.go --config /home/zuul-worker/sfcontext.yaml nodepool create openshiftpods-namespace args: chdir: "{{ zuul.project.src_dir | default(src_dir) }}" \ No newline at end of file diff --git a/roles/sfconfig-dev-prepare/tasks/main.yaml b/roles/sfconfig-dev-prepare/tasks/main.yaml index ecaa7025..1fd1de8f 100644 --- a/roles/sfconfig-dev-prepare/tasks/main.yaml +++ b/roles/sfconfig-dev-prepare/tasks/main.yaml @@ -1,8 +1,5 @@ --- -- set_fact: - with_prometheus_flag: "{{ '--with-prometheus' if test_monitoring == 'true' else '' }}" - -- name: Run the sfconfig dev prepare - command: "tools/sfconfig dev prepare {{ with_prometheus_flag }}" +- name: Run the CLI dev create demo-env + command: "go run ./main.go --config /home/zuul-worker/sfcontext.yaml dev create demo-env --repos-path {{ demo_repos_path }}" args: chdir: "{{ zuul.project.src_dir }}"