From b7e54edb040240f6e5fcd7d0e279b4622d86d62b Mon Sep 17 00:00:00 2001 From: Jeff Wong Date: Wed, 28 Aug 2024 20:01:28 -0700 Subject: [PATCH] FEATURE: add migrate and bootstrap commands (#842) Adds command for migrate only. Adds batch commands for bootstrap (build+migrate+configure) --- launcher_go/v2/cli_build.go | 45 +++++++++++++++++++++++++++++ launcher_go/v2/cli_build_test.go | 49 ++++++++++++++++++++++++++++++++ launcher_go/v2/main.go | 2 ++ 3 files changed, 96 insertions(+) diff --git a/launcher_go/v2/cli_build.go b/launcher_go/v2/cli_build.go index 8d0c17a..af59b75 100644 --- a/launcher_go/v2/cli_build.go +++ b/launcher_go/v2/cli_build.go @@ -90,6 +90,51 @@ func (r *DockerConfigureCmd) Run(cli *Cli, ctx *context.Context) error { return pups.Run() } +type DockerMigrateCmd struct { + Config string `arg:"" name:"config" help:"config" predictor:"config"` + SkipPostDeploymentMigrations bool `env:"SKIP_POST_DEPLOYMENT_MIGRATIONS" help:"Skip post-deployment migrations. Runs safe migrations only. Defers breaking-change migrations. Make sure you run post-deployment migrations after a full deploy is complete if you use this option."` +} + +func (r *DockerMigrateCmd) Run(cli *Cli, ctx *context.Context) error { + config, err := config.LoadConfig(cli.ConfDir, r.Config, true, cli.TemplatesDir) + if err != nil { + return errors.New("YAML syntax error. Please check your containers/*.yml config files.") + } + containerId := "discourse-build-" + uuid.NewString() + env := []string{"SKIP_EMBER_CLI_COMPILE=1"} + if r.SkipPostDeploymentMigrations { + env = append(env, "SKIP_POST_DEPLOYMENT_MIGRATIONS=1") + } + pups := docker.DockerPupsRunner{ + Config: config, + PupsArgs: "--tags=db,migrate", + ExtraEnv: env, + Ctx: ctx, + ContainerId: containerId, + } + return pups.Run() +} + +type DockerBootstrapCmd struct { + Config string `arg:"" name:"config" help:"config" predictor:"config"` +} + +func (r *DockerBootstrapCmd) Run(cli *Cli, ctx *context.Context) error { + buildStep := DockerBuildCmd{Config: r.Config, BakeEnv: false} + migrateStep := DockerMigrateCmd{Config: r.Config} + configureStep := DockerConfigureCmd{Config: r.Config} + if err := buildStep.Run(cli, ctx); err != nil { + return err + } + if err := migrateStep.Run(cli, ctx); err != nil { + return err + } + if err := configureStep.Run(cli, ctx); err != nil { + return err + } + return nil +} + type CleanCmd struct { Config string `arg:"" name:"config" help:"config to clean" predictor:"config"` } diff --git a/launcher_go/v2/cli_build_test.go b/launcher_go/v2/cli_build_test.go index 1d50318..35fe175 100644 --- a/launcher_go/v2/cli_build_test.go +++ b/launcher_go/v2/cli_build_test.go @@ -58,6 +58,19 @@ var _ = Describe("Build", func() { Expect(buf.String()).ToNot(ContainSubstring("SKIP_EMBER_CLI_COMPILE=1")) } + var checkMigrateCmd = func(cmd exec.Cmd) { + Expect(cmd.String()).To(ContainSubstring("docker run")) + Expect(cmd.String()).To(ContainSubstring("--env DISCOURSE_DEVELOPER_EMAILS")) + Expect(cmd.String()).To(ContainSubstring("--env SKIP_EMBER_CLI_COMPILE=1")) + // no commit after, we expect an --rm as the container isn't needed after it is stopped + Expect(cmd.String()).To(ContainSubstring("--rm")) + Expect(cmd.Env).To(ContainElement("DISCOURSE_DB_PASSWORD=SOME_SECRET")) + buf := new(strings.Builder) + io.Copy(buf, cmd.Stdin) + // docker run's stdin is a pups config + Expect(buf.String()).To(ContainSubstring("path: /etc/service/nginx/run")) + } + var checkConfigureCmd = func(cmd exec.Cmd) { Expect(cmd.String()).To(Equal( "/usr/local/bin/docker run " + @@ -151,6 +164,31 @@ var _ = Describe("Build", func() { checkBuildCmd(RanCmds[0]) }) + It("Should run docker migrate with correct arguments", func() { + runner := ddocker.DockerMigrateCmd{Config: "test"} + runner.Run(cli, &ctx) + Expect(len(RanCmds)).To(Equal(1)) + checkMigrateCmd(RanCmds[0]) + }) + + It("Should allow skip post deployment migrations", func() { + runner := ddocker.DockerMigrateCmd{Config: "test", SkipPostDeploymentMigrations: true} + runner.Run(cli, &ctx) + Expect(len(RanCmds)).To(Equal(1)) + cmd := RanCmds[0] + Expect(cmd.String()).To(ContainSubstring("docker run")) + Expect(cmd.String()).To(ContainSubstring("--env DISCOURSE_DEVELOPER_EMAILS")) + Expect(cmd.String()).To(ContainSubstring("--env SKIP_POST_DEPLOYMENT_MIGRATIONS=1")) + Expect(cmd.String()).To(ContainSubstring("--env SKIP_EMBER_CLI_COMPILE=1")) + // no commit after, we expect an --rm as the container isn't needed after it is stopped + Expect(cmd.String()).To(ContainSubstring("--rm")) + Expect(cmd.Env).To(ContainElement("DISCOURSE_DB_PASSWORD=SOME_SECRET")) + buf := new(strings.Builder) + io.Copy(buf, cmd.Stdin) + // docker run's stdin is a pups config + Expect(buf.String()).To(ContainSubstring("path: /etc/service/nginx/run")) + }) + It("Should run docker run followed by docker commit and rm container when configuring", func() { runner := ddocker.DockerConfigureCmd{Config: "test"} runner.Run(cli, &ctx) @@ -160,5 +198,16 @@ var _ = Describe("Build", func() { checkConfigureCommit(RanCmds[1]) checkConfigureClean(RanCmds[2]) }) + + It("Should run all docker commands for full bootstrap", func() { + runner := ddocker.DockerBootstrapCmd{Config: "test"} + runner.Run(cli, &ctx) + Expect(len(RanCmds)).To(Equal(5)) + checkBuildCmd(RanCmds[0]) + checkMigrateCmd(RanCmds[1]) + checkConfigureCmd(RanCmds[2]) + checkConfigureCommit(RanCmds[3]) + checkConfigureClean(RanCmds[4]) + }) }) }) diff --git a/launcher_go/v2/main.go b/launcher_go/v2/main.go index 165c8e8..3ea0cee 100644 --- a/launcher_go/v2/main.go +++ b/launcher_go/v2/main.go @@ -18,6 +18,8 @@ type Cli struct { BuildDir string `default:"./tmp" hidden:"" help:"Temporary build folder for building images." predictor:"dir"` BuildCmd DockerBuildCmd `cmd:"" name:"build" help:"Build a base image. This command does not need a running database. Saves resulting container."` ConfigureCmd DockerConfigureCmd `cmd:"" name:"configure" help:"Configure and save an image with all dependencies and environment baked in. Updates themes and precompiles all assets. Saves resulting container."` + MigrateCmd DockerMigrateCmd `cmd:"" name:"migrate" help:"Run migration tasks for a site. Running container is temporary and is not saved."` + BootstrapCmd DockerBootstrapCmd `cmd:"" name:"bootstrap" help:"Builds, migrates, and configures an image. Resulting image is a fully built and configured Discourse image."` } func main() { -- 2.25.1