From 4173e0cfded9d19693ade3f80c201d68a8560503 Mon Sep 17 00:00:00 2001 From: Jeff Wong Date: Fri, 15 Nov 2024 14:52:21 -0800 Subject: [PATCH] Move source for launcher2 to its own repository. --- .github/workflows/build.yml | 6 - .github/workflows/launcher_go.yml | 45 -- launcher_go/README.md | 134 ------ launcher_go/v2/Makefile | 10 - launcher_go/v2/bin/.gitignore | 1 - launcher_go/v2/cli_build.go | 180 ------- launcher_go/v2/cli_build_test.go | 254 ---------- launcher_go/v2/cli_runtime.go | 373 --------------- launcher_go/v2/cli_runtime_test.go | 204 -------- launcher_go/v2/config/config.go | 231 --------- launcher_go/v2/config/config_suite_test.go | 13 - launcher_go/v2/config/config_test.go | 61 --- launcher_go/v2/docker/commands.go | 324 ------------- launcher_go/v2/docker/commands_test.go | 43 -- launcher_go/v2/docker/docker_suite_test.go | 13 - launcher_go/v2/go.mod | 30 -- launcher_go/v2/go.sum | 64 --- launcher_go/v2/go_suite_test.go | 18 - launcher_go/v2/main.go | 92 ---- launcher_go/v2/test/containers/standalone.yml | 109 ----- launcher_go/v2/test/containers/test.yml | 121 ----- launcher_go/v2/test/containers/test2.yaml | 0 .../v2/test/containers/test3.not-a-yaml | 0 launcher_go/v2/test/containers/web_only.yml | 114 ----- .../v2/test/templates/web.template.yml | 443 ------------------ launcher_go/v2/test_utils/utils.go | 42 -- launcher_go/v2/utils/cmd_runner.go | 28 -- launcher_go/v2/utils/consts.go | 50 -- launcher_go/v2/utils/find_config.go | 51 -- launcher_go/v2/utils/find_config_test.go | 39 -- launcher_go/v2/utils/utils_suite_test.go | 13 - 31 files changed, 3106 deletions(-) delete mode 100644 .github/workflows/launcher_go.yml delete mode 100644 launcher_go/README.md delete mode 100644 launcher_go/v2/Makefile delete mode 100644 launcher_go/v2/bin/.gitignore delete mode 100644 launcher_go/v2/cli_build.go delete mode 100644 launcher_go/v2/cli_build_test.go delete mode 100644 launcher_go/v2/cli_runtime.go delete mode 100644 launcher_go/v2/cli_runtime_test.go delete mode 100644 launcher_go/v2/config/config.go delete mode 100644 launcher_go/v2/config/config_suite_test.go delete mode 100644 launcher_go/v2/config/config_test.go delete mode 100644 launcher_go/v2/docker/commands.go delete mode 100644 launcher_go/v2/docker/commands_test.go delete mode 100644 launcher_go/v2/docker/docker_suite_test.go delete mode 100644 launcher_go/v2/go.mod delete mode 100644 launcher_go/v2/go.sum delete mode 100644 launcher_go/v2/go_suite_test.go delete mode 100644 launcher_go/v2/main.go delete mode 100644 launcher_go/v2/test/containers/standalone.yml delete mode 100644 launcher_go/v2/test/containers/test.yml delete mode 100644 launcher_go/v2/test/containers/test2.yaml delete mode 100644 launcher_go/v2/test/containers/test3.not-a-yaml delete mode 100644 launcher_go/v2/test/containers/web_only.yml delete mode 100644 launcher_go/v2/test/templates/web.template.yml delete mode 100644 launcher_go/v2/test_utils/utils.go delete mode 100644 launcher_go/v2/utils/cmd_runner.go delete mode 100644 launcher_go/v2/utils/consts.go delete mode 100644 launcher_go/v2/utils/find_config.go delete mode 100644 launcher_go/v2/utils/find_config_test.go delete mode 100644 launcher_go/v2/utils/utils_suite_test.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9331a0c9b..1a742d90d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,13 +2,7 @@ on: push: branches: - main - paths-ignore: - - "launcher_go/**" - - ".github/workflows/launcher_go.yml" pull_request: - paths-ignore: - - "launcher_go/**" - - ".github/workflows/launcher_go.yml" schedule: - cron: "0 0 * * *" diff --git a/.github/workflows/launcher_go.yml b/.github/workflows/launcher_go.yml deleted file mode 100644 index fa238315e..000000000 --- a/.github/workflows/launcher_go.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: launcher_go - -on: - push: - branches: - - main - paths: - - "launcher_go/**" - - ".github/workflows/launcher_go.yml" - pull_request: - paths: - - "launcher_go/**" - - ".github/workflows/launcher_go.yml" - -jobs: - lint: - runs-on: ubuntu-latest - timeout-minutes: 5 - strategy: - fail-fast: true - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: ">=1.22.0" - - name: Run gofmt - working-directory: ./launcher_go/v2 - run: | - if [ "$(gofmt -l . | wc -l)" -gt 0 ]; then - exit 1 - fi - - test: - runs-on: ubuntu-latest - timeout-minutes: 5 - strategy: - fail-fast: true - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: ">=1.22.0" - - name: Run tests - working-directory: ./launcher_go/v2 - run: go test ./... diff --git a/launcher_go/README.md b/launcher_go/README.md deleted file mode 100644 index a3f7baa28..000000000 --- a/launcher_go/README.md +++ /dev/null @@ -1,134 +0,0 @@ -# Launcher2 - -Build and run discourse images. Drop in replacement for launcher the shell script. - -## Changes from launcher - -No software prerequisites are checked here. It assumes you have docker set up and whatever minimum requirements setup for Discourse: namely a recent enough version of docker, git. - -Some things are not implemented from launcher1. - -* `DOCKER_HOST_IP` - container can use `host.docker.internal` in most cases. Supported on mac and windows... can also be [added on linux via docker args](https://stackoverflow.com/questions/72827527/what-is-running-on-host-docker-internal-host). -* debug containers - not implemented. No debug containers saved on build. Under the hood, launcher2 uses docker build which does not allow images to be saved along the way. -* stable `mac-address` - not implemented. - -## New features - -In a nutshell: split bootstrap/rebuild process up into distinct parts to allow for greater flexibility in how we build and deploy Discourse containers. - -### Separates bootstrap process into distinct build, configure, and migrate steps. - -Separating the larger bootstrap process into separate steps allows us to break up the work. - -`bootstrap` becomes an alias for: `build`, `migrate`, `configure`. There are multiple benefits to this. - -#### Build: Easier creation for prebuilt docker images - -Share built docker images by only running a `build` step - this build step does not need to connect to a database. -It does not need postgres or redis running. This makes for a simple way to install custom plugins to your Discourse image. - -The resulting image is able to be used in Kubernetes and other docker environments. - -This is done by deferring finishing the build step, to a later configure step -- which boostraps the db, and precompiles assets. - -The `configure` and `migrate` steps can now be done on boot through use of env vars set in the `app.yml` config: `CREATE_DB_ON_BOOT`, `MIGRATE_ON_BOOT`, and `PRECOMPILE_ON_BOOT`, which allows for more portable containers able to drop in and bootstrap themselves and the database as they come into service. - -#### Build: Better environment management - -The resulting image from a build is a container with no environment (unless `--bake-env` is specified). Additionally, well-known secrets are excluded from the build environment, resulting in a clean history of the prebuilt image that may be more easily shared. - -Environment is only bound to a container either with `--bake-env` on build, or on a subsequent `configure` step. - -#### Migrate: Adds support to *when* migrations are run - -`Build` and `Configure` steps do not run migrations, allowing for external tooling to specify exactly when migrations are run. - -`Migrate`, (and`bootstrap`, and `rebuild`) steps are the only ones that run migrations. - -#### Migrate: Adds support for *how* migrations are run: `SKIP_POST_DEPLOYMENT_MIGRATIONS` support - -the `migrate` step exposes env vars that turn on separate post deploy migration steps. - -Allows the ability to turn on and skip post migration steps from launcher when running a stand-alone migrate step. - -#### Rebuild: Minimize downtime - -Both standalone and multi-container setups' downtime have been minimized for rebuilds - -##### Standalone -On standalone builds, only stop the running container after the base build is done. -Standalone sites will only need to be offline during migration and configure steps. - -For standalone, `rebuild` runs `build`, `stop`, `migrate`, `configure`, `destroy`, `start`. - -##### Multiple container, web only -On multi-container setups or setups with a configured external database using web only containers, rebuilds attempt to run migrations without stopping the container. -A multi-container stays up as migration (skipping post deployment migrations) and as any necessary configuration steps are run. After deploy, post deployment migrations are run to clean up any destructive migrations. - -For web-only, `rebuild` runs `build`, `migrate (skip post migrations)`, `configure`, `destroy`, `start`, `migrate`. - -#### Rebuild: Serve offline page during downtime - -Adds the ability to build and run an image that finishes a build on boot, allowing the server to display an offline page. -For standalone builds above, this allows for the accrued downtime from migration and configure steps to happen more gracefully. - -Additional container env vars get turned on by adding the `offline-page.template.yml` template: - * `CREATE_DB_ON_BOOT` - * `MIGRATE_ON_BOOT` - * `PRECOMPILE_ON_BOOT` - -These allow containers to boot cleanly from a cold state, and complete db creation, migration, and precompile steps on boot. - -During this time, nginx can be up which allows standalone builds to display an offline page. - -These variables may also be used for other applications where more flexible bootstrapping is desired. - -##### Standalone -On rebuild, a standalone site will skip migration if it detects the presence of `MIGRATE_ON_BOOT` in the app config, and will skip configure steps if it detects the presence of `PRECOMPILE_ON_BOOT` in the app config. - -For standalone, `rebuild` runs `build`, `destroy`, `start`, skipping `migrate` and `configure`. The started container then serves an offline page, and runs migrate and precompiles assets before fully entering service. - -##### Multiple container, web only -On rebuild, a web only container will act in the same way as a standalone container. This may result in the same downtime as standalone services, as the containers are swapped, and the new container is still responsible for migration and precompiling before serving traffic. - -For web-only containers, it may be desired to either ensure that `MIGRATE_ON_BOOT` and `PRECOMPILE_ON_BOOT` are false. Alternatively, you may run with `--full-build` which will ensure that migration and precompile steps are not deferred for the 'live' deploy. - -### Multiline env support - -Allows the use of multiline env vars so this is valid config, and is passed through to the container as expected: -``` -env: - SECRET_KEY: | - ---START OF SECRET KEY--- - 123456 - 78910 - ---END OF SECRET KEY--- -``` - -### More dependable SIGINT/SIGTERM handling. - -Launcher wraps docker run commands, which run as children in process trees. Launcher2 does the same, but attempts to kill or stop the underlying docker processes from interrupt signals. - -Tools that extend or depend on launcher should be able to send SIGINT/SIGTERM signals to tell launcher to shut down, and launcher should clean up child processes appropriately. - -### Docker compose generation. - -Allows easier exporting of configuration from discourse's pups configuration to a docker compose configuration. - -### Autocomplete support - -Run `source <(./launcher2 sh)` to activate completions for the current shell, or add the results of `./launcher2 sh` to your dotfiles - -Autocompletes commands, subcommands, and suggests `app` config files from your containers directory. Having a long site name should not feel like a pain to type. - -## Maintainability - -Golang is well suited as a drop in replacement as just like a shellscript, the deployed binary can still carry minimal assumptions about a particular platform to run. (IE, no dependency on ruby, python, etc) - -Golang allows us to use a fully fleshed out programming language to run native yaml parsing: Calling out to ruby through a docker container worked well enough, but got complicated shuffling results through stdout into shell variables. - -Launcher has outgrown being a simple wrapper script around Docker. Golang has good support for tests and breaking up code into separate modules to better support further growth around additional subcommands we may wish to add. - -## Roadmap - -Scaffolding out subcommands, possibly as a later rewrite for `discourse-setup` as having native YAML libraries should make config parsing and editing simpler to do. diff --git a/launcher_go/v2/Makefile b/launcher_go/v2/Makefile deleted file mode 100644 index cd2e89246..000000000 --- a/launcher_go/v2/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -.PHONY: default -default: build - -.PHONY: build -build: - go build -o bin/launcher2 - -.PHONY: test -test: - go test ./... diff --git a/launcher_go/v2/bin/.gitignore b/launcher_go/v2/bin/.gitignore deleted file mode 100644 index 89f3d385c..000000000 --- a/launcher_go/v2/bin/.gitignore +++ /dev/null @@ -1 +0,0 @@ -launcher2* \ No newline at end of file diff --git a/launcher_go/v2/cli_build.go b/launcher_go/v2/cli_build.go deleted file mode 100644 index b6f9294f4..000000000 --- a/launcher_go/v2/cli_build.go +++ /dev/null @@ -1,180 +0,0 @@ -package main - -import ( - "context" - "errors" - "flag" - "os" - "strings" - - "github.com/discourse/discourse_docker/launcher_go/v2/config" - "github.com/discourse/discourse_docker/launcher_go/v2/docker" - "github.com/discourse/discourse_docker/launcher_go/v2/utils" - "github.com/google/uuid" -) - -/* - * build - * migrate - * configure - * bootstrap - */ -type DockerBuildCmd struct { - BakeEnv bool `short:"e" help:"Bake in the configured environment to image after build."` - Tag string `default:"latest" help:"Resulting image tag."` - - Config string `arg:"" name:"config" help:"configuration" predictor:"config"` -} - -func (r *DockerBuildCmd) 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.") - } - - dir := cli.BuildDir + "/" + r.Config - if err := os.MkdirAll(dir, 0755); err != nil && !os.IsExist(err) { - return err - } - if err := config.WriteYamlConfig(dir); err != nil { - return err - } - - namespace := cli.Namespace - if namespace == "" { - namespace = utils.DefaultNamespace - } - pupsArgs := "--skip-tags=precompile,migrate,db" - builder := docker.DockerBuilder{ - Config: config, - Ctx: ctx, - Stdin: strings.NewReader(config.Dockerfile(pupsArgs, r.BakeEnv)), - Dir: dir, - Namespace: namespace, - ImageTag: r.Tag, - } - if err := builder.Run(); err != nil { - return err - } - cleaner := CleanCmd{Config: r.Config} - cleaner.Run(cli) - - return nil -} - -type DockerConfigureCmd struct { - SourceTag string `help:"Source image tag to build from."` - TargetTag string `help:"Target image tag to save as."` - Config string `arg:"" name:"config" help:"config" predictor:"config"` -} - -func (r *DockerConfigureCmd) 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.") - } - - var uuidString string - - if flag.Lookup("test.v") == nil { - uuidString = uuid.NewString() - } else { - uuidString = "test" - } - - containerId := "discourse-build-" + uuidString - namespace := cli.Namespace - if namespace == "" { - namespace = utils.DefaultNamespace - } - sourceTag := "" - if len(r.SourceTag) > 0 { - sourceTag = ":" + r.SourceTag - } - targetTag := "" - if len(r.TargetTag) > 0 { - targetTag = ":" + r.TargetTag - } - - pups := docker.DockerPupsRunner{ - Config: config, - PupsArgs: "--tags=db,precompile", - FromImageName: namespace + "/" + r.Config + sourceTag, - SavedImageName: namespace + "/" + r.Config + targetTag, - ExtraEnv: []string{"SKIP_EMBER_CLI_COMPILE=1"}, - Ctx: ctx, - ContainerId: containerId, - } - - return pups.Run() -} - -type DockerMigrateCmd struct { - Config string `arg:"" name:"config" help:"config" predictor:"config"` - Tag string `default:"latest" help:"Image tag to migrate."` - 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") - } - - namespace := cli.Namespace - if namespace == "" { - namespace = utils.DefaultNamespace - } - tag := "" - if len(r.Tag) > 0 { - tag = ":" + r.Tag - } - pups := docker.DockerPupsRunner{ - Config: config, - PupsArgs: "--tags=db,migrate", - FromImageName: namespace + "/" + r.Config + tag, - 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"` -} - -func (r *CleanCmd) Run(cli *Cli) error { - dir := cli.BuildDir + "/" + r.Config - os.Remove(dir + "/config.yaml") - if err := os.Remove(dir); err != nil { - return err - } - return nil -} diff --git a/launcher_go/v2/cli_build_test.go b/launcher_go/v2/cli_build_test.go deleted file mode 100644 index 475a4a523..000000000 --- a/launcher_go/v2/cli_build_test.go +++ /dev/null @@ -1,254 +0,0 @@ -package main_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "bytes" - "context" - "io" - "os" - "os/exec" - "strings" - - ddocker "github.com/discourse/discourse_docker/launcher_go/v2" - . "github.com/discourse/discourse_docker/launcher_go/v2/test_utils" - "github.com/discourse/discourse_docker/launcher_go/v2/utils" -) - -var _ = Describe("Build", func() { - var testDir string - var out *bytes.Buffer - var cli *ddocker.Cli - var ctx context.Context - - BeforeEach(func() { - utils.DockerPath = "docker" - out = &bytes.Buffer{} - utils.Out = out - testDir, _ = os.MkdirTemp("", "ddocker-test") - - ctx = context.Background() - - cli = &ddocker.Cli{ - ConfDir: "./test/containers", - TemplatesDir: "./test", - BuildDir: testDir, - } - - utils.CmdRunner = CreateNewFakeCmdRunner() - }) - - AfterEach(func() { - os.RemoveAll(testDir) - }) - - Context("When running build commands", func() { - var checkBuildCmd = func(cmd exec.Cmd) { - Expect(cmd.String()).To(ContainSubstring("docker build")) - Expect(cmd.String()).To(ContainSubstring("--build-arg DISCOURSE_DEVELOPER_EMAILS")) - Expect(cmd.Dir).To(Equal(testDir + "/test")) - - //db password is ignored - Expect(cmd.Env).ToNot(ContainElement("DISCOURSE_DB_PASSWORD=SOME_SECRET")) - Expect(cmd.Env).ToNot(ContainElement("DISCOURSEDB_SOCKET=")) - buf := new(strings.Builder) - io.Copy(buf, cmd.Stdin) - // docker build's stdin is a dockerfile - Expect(buf.String()).To(ContainSubstring("COPY config.yaml /temp-config.yaml")) - Expect(buf.String()).To(ContainSubstring("--skip-tags=precompile,migrate,db")) - 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(ContainSubstring( - "docker run " + - "--env DISCOURSE_DB_HOST " + - "--env DISCOURSE_DB_PASSWORD " + - "--env DISCOURSE_DB_PORT " + - "--env DISCOURSE_DB_SOCKET " + - "--env DISCOURSE_DEVELOPER_EMAILS " + - "--env DISCOURSE_HOSTNAME " + - "--env DISCOURSE_REDIS_HOST " + - "--env DISCOURSE_SMTP_ADDRESS " + - "--env DISCOURSE_SMTP_PASSWORD " + - "--env DISCOURSE_SMTP_USER_NAME " + - "--env LANG " + - "--env LANGUAGE " + - "--env LC_ALL " + - "--env MULTI " + - "--env RAILS_ENV " + - "--env REPLACED " + - "--env RUBY_GC_HEAP_GROWTH_MAX_SLOTS " + - "--env RUBY_GC_HEAP_INIT_SLOTS " + - "--env RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR " + - "--env UNICORN_SIDEKIQS " + - "--env UNICORN_WORKERS " + - "--env SKIP_EMBER_CLI_COMPILE=1 " + - "--volume /var/discourse/shared/web-only:/shared " + - "--volume /var/discourse/shared/web-only/log/var-log:/var/log " + - "--link data:data " + - "--shm-size=512m " + - "--restart=no " + - "--interactive " + - "--expose 100 " + - "--name discourse-build-test " + - "local_discourse/test /bin/bash -c /usr/local/bin/pups --stdin --tags=db,precompile", - )) - - Expect(cmd.Env).To(Equal([]string{ - "DISCOURSE_DB_HOST=data", - "DISCOURSE_DB_PASSWORD=SOME_SECRET", - "DISCOURSE_DB_PORT=", - "DISCOURSE_DB_SOCKET=", - "DISCOURSE_DEVELOPER_EMAILS=me@example.com,you@example.com", - "DISCOURSE_HOSTNAME=discourse.example.com", - "DISCOURSE_REDIS_HOST=data", - "DISCOURSE_SMTP_ADDRESS=smtp.example.com", - "DISCOURSE_SMTP_PASSWORD=pa$$word", - "DISCOURSE_SMTP_USER_NAME=user@example.com", - "LANG=en_US.UTF-8", - "LANGUAGE=en_US.UTF-8", - "LC_ALL=en_US.UTF-8", - "MULTI=test\nmultiline with some spaces\nvar\n", - "RAILS_ENV=production", - "REPLACED=test/test/test", - "RUBY_GC_HEAP_GROWTH_MAX_SLOTS=40000", - "RUBY_GC_HEAP_INIT_SLOTS=400000", - "RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR=1.5", - "UNICORN_SIDEKIQS=1", - "UNICORN_WORKERS=3", - })) - - buf := new(strings.Builder) - io.Copy(buf, cmd.Stdin) - // docker run's stdin is a pups config - - // web.template.yml is merged with the test config - Expect(buf.String()).To(ContainSubstring("path: /etc/service/nginx/run")) - Expect(buf.String()).To(ContainSubstring(`exec: echo "custom test command"`)) - } - - // commit on configure - var checkConfigureCommit = func(cmd exec.Cmd) { - Expect(cmd.String()).To(MatchRegexp( - "docker commit " + - `--change LABEL org\.opencontainers\.image\.created="[\d\-T:Z]+" ` + - `--change CMD \["/sbin/boot"\] ` + - "discourse-build-test local_discourse/test", - )) - - Expect(cmd.Env).To(BeNil()) - } - - // configure also cleans up - var checkConfigureClean = func(cmd exec.Cmd) { - Expect(cmd.String()).To(ContainSubstring("docker rm --force discourse-build-test")) - } - - It("Should run docker build with correct arguments", func() { - runner := ddocker.DockerBuildCmd{Config: "test"} - runner.Run(cli, &ctx) - Expect(len(RanCmds)).To(Equal(1)) - 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]) - }) - - Context("With a custom namespace", func() { - BeforeEach(func() { - cli.Namespace = "testnamespace" - }) - - It("Should run docker build with correct namespace and custom flags", func() { - runner := ddocker.DockerBuildCmd{Config: "test", Tag: "testtag"} - runner.Run(cli, &ctx) - Expect(len(RanCmds)).To(Equal(1)) - checkBuildCmd(RanCmds[0]) - Expect(RanCmds[0].String()).To(ContainSubstring("testnamespace/test:testtag")) - }) - - It("Should run docker configure with correct namespace and tags", func() { - runner := ddocker.DockerConfigureCmd{Config: "test", SourceTag: "build", TargetTag: "configure"} - runner.Run(cli, &ctx) - Expect(len(RanCmds)).To(Equal(3)) - - Expect(RanCmds[0].String()).To(MatchRegexp( - "--name discourse-build-test " + - "testnamespace/test:build /bin/bash -c /usr/local/bin/pups --stdin --tags=db,precompile", - )) - Expect(RanCmds[1].String()).To(MatchRegexp( - "docker commit " + - `--change LABEL org\.opencontainers\.image\.created="[\d\-T:Z]+" ` + - `--change CMD \["/sbin/boot"\] ` + - "discourse-build-test testnamespace/test:configure", - )) - checkConfigureClean(RanCmds[2]) - }) - - It("Should run docker migrate with correct namespace", func() { - runner := ddocker.DockerMigrateCmd{Config: "test"} - runner.Run(cli, &ctx) - Expect(len(RanCmds)).To(Equal(1)) - Expect(RanCmds[0].String()).To(ContainSubstring("testnamespace/test ")) - }) - }) - - 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) - Expect(len(RanCmds)).To(Equal(3)) - - checkConfigureCmd(RanCmds[0]) - 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/cli_runtime.go b/launcher_go/v2/cli_runtime.go deleted file mode 100644 index 97a594f84..000000000 --- a/launcher_go/v2/cli_runtime.go +++ /dev/null @@ -1,373 +0,0 @@ -package main - -import ( - "bufio" - "context" - "errors" - "fmt" - "os" - "os/exec" - "runtime" - "strings" - "syscall" - "time" - - "github.com/discourse/discourse_docker/launcher_go/v2/config" - "github.com/discourse/discourse_docker/launcher_go/v2/docker" - "github.com/discourse/discourse_docker/launcher_go/v2/utils" - - "golang.org/x/sys/unix" -) - -/* - * start - * run - * stop - * cleanup - * destroy - * logs - * enter - * rebuild - * restart - */ - -type StartCmd struct { - Config string `arg:"" name:"config" help:"config" predictor:"config"` - DryRun bool `name:"dry-run" short:"n" help:"Do not start, print docker start command and exit."` - DockerArgs string `name:"docker-args" help:"Extra arguments to pass when running docker."` - RunImage string `name:"run-image" help:"Start with a custom image."` - Supervised bool `name:"supervised" env:"SUPERVISED" help:"Attach the running container on start."` - - extraEnv []string -} - -func (r *StartCmd) Run(cli *Cli, ctx *context.Context) error { - //start stopped container first if exists - running, _ := docker.ContainerRunning(r.Config) - - if running && !r.DryRun { - fmt.Fprintln(utils.Out, "Nothing to do, your container has already started!") - return nil - } - - exists, _ := docker.ContainerExists(r.Config) - - if exists && !r.DryRun { - fmt.Fprintln(utils.Out, "starting up existing container") - cmd := exec.CommandContext(*ctx, utils.DockerPath, "start", r.Config) - - if r.Supervised { - cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} - - cmd.Cancel = func() error { - if runtime.GOOS == "darwin" { - runCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - stopCmd := exec.CommandContext(runCtx, utils.DockerPath, "stop", r.Config) - utils.CmdRunner(stopCmd).Run() - cancel() - } - return unix.Kill(-cmd.Process.Pid, unix.SIGINT) - } - - cmd.Args = append(cmd.Args, "--attach") - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - } - - fmt.Fprintln(utils.Out, cmd) - - if err := utils.CmdRunner(cmd).Run(); err != nil { - return err - } - - return nil - } - - 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.") - } - - defaultHostname, _ := os.Hostname() - defaultHostname = defaultHostname + "-" + r.Config - hostname := config.DockerHostname(defaultHostname) - - restart := true - detatch := true - - if r.Supervised { - restart = false - detatch = false - } - - extraFlags := strings.Fields(r.DockerArgs) - bootCmd := config.BootCommand() - - runner := docker.DockerRunner{ - Config: config, - Ctx: ctx, - ContainerId: r.Config, - DryRun: r.DryRun, - CustomImage: r.RunImage, - Restart: restart, - Detatch: detatch, - ExtraFlags: extraFlags, - ExtraEnv: r.extraEnv, - Hostname: hostname, - Cmd: []string{bootCmd}, - } - - fmt.Fprintln(utils.Out, "starting new container...") - return runner.Run() -} - -type RunCmd struct { - RunImage string `name:"run-image" help:"Override the image used for running the container."` - DockerArgs string `name:"docker-args" help:"Extra arguments to pass when running docker"` - Config string `arg:"" name:"config" help:"config" predictor:"config"` - Cmd []string `arg:"" help:"command to run" passthrough:""` -} - -func (r *RunCmd) 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.") - } - extraFlags := strings.Fields(r.DockerArgs) - runner := docker.DockerRunner{ - Config: config, - Ctx: ctx, - CustomImage: r.RunImage, - SkipPorts: true, - Rm: true, - Cmd: r.Cmd, - ExtraFlags: extraFlags, - } - return runner.Run() - return nil -} - -type StopCmd struct { - Config string `arg:"" name:"config" help:"config" predictor:"config"` -} - -func (r *StopCmd) Run(cli *Cli, ctx *context.Context) error { - exists, _ := docker.ContainerExists(r.Config) - if !exists { - fmt.Fprintln(utils.Out, r.Config+" was not found") - return nil - } - cmd := exec.CommandContext(*ctx, utils.DockerPath, "stop", "--time", "600", r.Config) - - fmt.Fprintln(utils.Out, cmd) - if err := utils.CmdRunner(cmd).Run(); err != nil { - return err - } - return nil -} - -type RestartCmd struct { - Config string `arg:"" name:"config" help:"config" predictor:"config"` - DockerArgs string `name:"docker-args" help:"Extra arguments to pass when running docker."` - RunImage string `name:"run-image" help:"Override the image used for running the container."` -} - -func (r *RestartCmd) Run(cli *Cli, ctx *context.Context) error { - start := StartCmd{Config: r.Config, DockerArgs: r.DockerArgs, RunImage: r.RunImage} - stop := StopCmd{Config: r.Config} - - if err := stop.Run(cli, ctx); err != nil { - return err - } - - if err := start.Run(cli, ctx); err != nil { - return err - } - - return nil -} - -type DestroyCmd struct { - Config string `arg:"" name:"config" help:"config" predictor:"config"` -} - -func (r *DestroyCmd) Run(cli *Cli, ctx *context.Context) error { - exists, _ := docker.ContainerExists(r.Config) - - if !exists { - fmt.Fprintln(utils.Out, r.Config+" was not found") - return nil - } - - cmd := exec.CommandContext(*ctx, utils.DockerPath, "stop", "--time", "600", r.Config) - fmt.Fprintln(utils.Out, cmd) - - if err := utils.CmdRunner(cmd).Run(); err != nil { - return err - } - - cmd = exec.CommandContext(*ctx, utils.DockerPath, "rm", r.Config) - fmt.Fprintln(utils.Out, cmd) - - if err := utils.CmdRunner(cmd).Run(); err != nil { - return err - } - - return nil -} - -type EnterCmd struct { - Config string `arg:"" name:"config" help:"config" predictor:"config"` -} - -func (r *EnterCmd) Run(cli *Cli, ctx *context.Context) error { - cmd := exec.CommandContext(*ctx, utils.DockerPath, "exec", "-it", r.Config, "/bin/bash", "--login") - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - if err := utils.CmdRunner(cmd).Run(); err != nil { - return err - } - - return nil -} - -type LogsCmd struct { - Config string `arg:"" name:"config" help:"config" predictor:"config"` -} - -func (r *LogsCmd) Run(cli *Cli, ctx *context.Context) error { - cmd := exec.CommandContext(*ctx, utils.DockerPath, "logs", r.Config) - output, err := utils.CmdRunner(cmd).Output() - - if err != nil { - return err - } - - fmt.Fprintln(utils.Out, string(output[:])) - return nil -} - -type RebuildCmd struct { - Config string `arg:"" name:"config" help:"config" predictor:"config"` - FullBuild bool `name:"full-build" help:"Run a full build image even when migrate on boot and precompile on boot are present in the config. Saves a fully built image with environment baked in. Without this flag, if MIGRATE_ON_BOOT is set in config it will defer migration until container start, and if PRECOMPILE_ON_BOOT is set in the config, it will defer configure step until container start."` - Clean bool `help:"also runs clean"` -} - -func (r *RebuildCmd) 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.") - } - - // if we're not in an all-in-one setup, we can run migrations while the app is running - externalDb := config.Env["DISCOURSE_DB_SOCKET"] == "" && config.Env["DISCOURSE_DB_HOST"] != "" - - build := DockerBuildCmd{Config: r.Config} - configure := DockerConfigureCmd{Config: r.Config} - stop := StopCmd{Config: r.Config} - destroy := DestroyCmd{Config: r.Config} - clean := CleanupCmd{} - extraEnv := []string{} - - if err := build.Run(cli, ctx); err != nil { - return err - } - - if !externalDb { - if err := stop.Run(cli, ctx); err != nil { - return err - } - } - - _, migrateOnBoot := config.Env["MIGRATE_ON_BOOT"] - - if !migrateOnBoot || r.FullBuild { - migrate := DockerMigrateCmd{Config: r.Config} - - if externalDb { - // defer post deploy migrations until after reboot - migrate.SkipPostDeploymentMigrations = true - } - - if err := migrate.Run(cli, ctx); err != nil { - return err - } - - extraEnv = append(extraEnv, "MIGRATE_ON_BOOT=0") - } - - _, precompileOnBoot := config.Env["PRECOMPILE_ON_BOOT"] - - if !precompileOnBoot || r.FullBuild { - if err := configure.Run(cli, ctx); err != nil { - return err - } - - extraEnv = append(extraEnv, "PRECOMPILE_ON_BOOT=0") - } - - if err := destroy.Run(cli, ctx); err != nil { - return err - } - - start := StartCmd{Config: r.Config, extraEnv: extraEnv} - - if err := start.Run(cli, ctx); err != nil { - return err - } - - // run post deploy migrations since we've rebooted - if externalDb { - migrate := DockerMigrateCmd{Config: r.Config} - if err := migrate.Run(cli, ctx); err != nil { - return err - } - } - - if r.Clean { - if err := clean.Run(cli, ctx); err != nil { - return err - } - } - - return nil -} - -type CleanupCmd struct{} - -func (r *CleanupCmd) Run(cli *Cli, ctx *context.Context) error { - cmd := exec.CommandContext(*ctx, utils.DockerPath, "container", "prune", "--filter", "until=1h") - - if err := utils.CmdRunner(cmd).Run(); err != nil { - return err - } - - cmd = exec.CommandContext(*ctx, utils.DockerPath, "image", "prune", "--all", "--filter", "until=1h") - - if err := utils.CmdRunner(cmd).Run(); err != nil { - return err - } - - _, err := os.Stat("/var/discourse/shared/standalone/postgres_data_old") - - if !os.IsNotExist(err) { - fmt.Fprintln(utils.Out, "Old PostgreSQL backup data cluster detected") - fmt.Fprintln(utils.Out, "Would you like to remove it? (y/N)") - scanner := bufio.NewScanner(os.Stdin) - scanner.Scan() - reply := scanner.Text() - if reply == "y" || reply == "Y" { - fmt.Fprintln(utils.Out, "removing old PostgreSQL data cluster at /var/discourse/shared/standalone/postgres_data_old...") - os.RemoveAll("/var/discourse/shared/standalone/postgres_data_old") - } else { - return errors.New("Cancelled") - } - } - - return nil -} diff --git a/launcher_go/v2/cli_runtime_test.go b/launcher_go/v2/cli_runtime_test.go deleted file mode 100644 index c5ff5a9af..000000000 --- a/launcher_go/v2/cli_runtime_test.go +++ /dev/null @@ -1,204 +0,0 @@ -package main_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "bytes" - "context" - "os" - - ddocker "github.com/discourse/discourse_docker/launcher_go/v2" - . "github.com/discourse/discourse_docker/launcher_go/v2/test_utils" - "github.com/discourse/discourse_docker/launcher_go/v2/utils" -) - -var _ = Describe("Runtime", func() { - var testDir string - var out *bytes.Buffer - var cli *ddocker.Cli - var ctx context.Context - - BeforeEach(func() { - utils.DockerPath = "docker" - out = &bytes.Buffer{} - utils.Out = out - testDir, _ = os.MkdirTemp("", "ddocker-test") - ctx = context.Background() - - cli = &ddocker.Cli{ - ConfDir: "./test/containers", - TemplatesDir: "./test", - BuildDir: testDir, - } - - utils.CmdRunner = CreateNewFakeCmdRunner() - }) - - AfterEach(func() { - os.RemoveAll(testDir) - }) - - Context("When running run commands", func() { - var checkStartCmd = func() { - Expect(len(RanCmds)).To(Equal(3)) - - cmd := GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker ps --quiet --filter name=test")) - - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker ps --all --quiet --filter name=test")) - - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker run")) - Expect(cmd.String()).To(ContainSubstring("--detach")) - Expect(cmd.String()).To(ContainSubstring("--restart=always")) - Expect(cmd.String()).To(ContainSubstring("--name test local_discourse/test /sbin/boot")) - } - - var checkStartCmdWhenStarted = func() { - Expect(len(RanCmds)).To(Equal(1)) - - cmd := GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker ps --quiet --filter name=test")) - } - - var checkStopCmd = func() { - Expect(len(RanCmds)).To(Equal(2)) - - cmd := GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker ps --all --quiet --filter name=test")) - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker stop --time 600 test")) - } - - var checkStopCmdWhenMissing = func() { - Expect(len(RanCmds)).To(Equal(1)) - - cmd := GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker ps --all --quiet --filter name=test")) - } - - Context("without a running container", func() { - It("should run start commands", func() { - runner := ddocker.StartCmd{Config: "test"} - runner.Run(cli, &ctx) - checkStartCmd() - }) - - It("should not run stop commands", func() { - runner := ddocker.StopCmd{Config: "test"} - runner.Run(cli, &ctx) - checkStopCmdWhenMissing() - }) - }) - - Context("with a running container", func() { - BeforeEach(func() { - //response should be non-empty, indicating a running container - response := []byte{123} - CmdOutputResponse = response - }) - - It("should not run start commands", func() { - runner := ddocker.StartCmd{Config: "test"} - runner.Run(cli, &ctx) - checkStartCmdWhenStarted() - }) - - It("should run stop commands", func() { - runner := ddocker.StopCmd{Config: "test"} - runner.Run(cli, &ctx) - checkStopCmd() - }) - - It("should keep running during commits, and be post-deploy migration aware when using a web only container", func() { - runner := ddocker.RebuildCmd{Config: "web_only"} - runner.Run(cli, &ctx) - - //initial build - cmd := GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker build")) - - //migrate, skipping post deployment migrations - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker run")) - Expect(cmd.String()).To(ContainSubstring("--tags=db,migrate")) - Expect(cmd.String()).To(ContainSubstring("--env SKIP_POST_DEPLOYMENT_MIGRATIONS=1")) - - // precompile - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker run")) - Expect(cmd.String()).To(ContainSubstring("--tags=db,precompile")) - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker commit")) - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker rm")) - - // destroying - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker ps --all --quiet --filter name=web_only")) - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker stop --time 600 web_only")) - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker rm")) - - // starting container --run command won't run because - // tests already believe we're running - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker ps --quiet")) - - // run post-deploy migrations - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker run")) - Expect(cmd.String()).To(ContainSubstring("--tags=db,migrate")) - Expect(len(RanCmds)).To(Equal(0)) - }) - - It("should stop with standalone", func() { - runner := ddocker.RebuildCmd{Config: "standalone"} - - runner.Run(cli, &ctx) - - //initial build - cmd := GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker build")) - cmd = GetLastCommand() - - // stop - Expect(cmd.String()).To(ContainSubstring("docker ps --all --quiet --filter name=standalone")) - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker stop")) - - // run migrate - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker run")) - Expect(cmd.String()).To(ContainSubstring("--tags=db,migrate")) - Expect(cmd.String()).ToNot(ContainSubstring("--env SKIP_POST_DEPLOYMENT_MIGRATIONS=1")) - - // run configure - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker run")) - Expect(cmd.String()).To(ContainSubstring("--tags=db,precompile")) - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker commit")) - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker rm")) - - // run destroy - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker ps --all --quiet")) - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker stop")) - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker rm standalone")) - - // run start (we think we're already started here so this is just ps) - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker ps --quiet")) - Expect(len(RanCmds)).To(Equal(0)) - }) - }) - - }) -}) diff --git a/launcher_go/v2/config/config.go b/launcher_go/v2/config/config.go deleted file mode 100644 index 41e57188b..000000000 --- a/launcher_go/v2/config/config.go +++ /dev/null @@ -1,231 +0,0 @@ -package config - -import ( - "errors" - "fmt" - "os" - "regexp" - "slices" - "strings" - - "dario.cat/mergo" - "github.com/discourse/discourse_docker/launcher_go/v2/utils" - - "gopkg.in/yaml.v3" -) - -const defaultBootCommand = "/sbin/boot" - -type Config struct { - Name string `yaml:-` - rawYaml []string - Base_Image string `yaml:,omitempty` - Update_Pups bool `yaml:,omitempty` - Run_Image string `yaml:,omitempty` - Boot_Command string `yaml:,omitempty` - No_Boot_Command bool `yaml:,omitempty` - Docker_Args string `yaml:,omitempty` - Templates []string `yaml:templates,omitempty` - Expose []string `yaml:expose,omitempty` - Params map[string]string `yaml:params,omitempty` - Env map[string]string `yaml:env,omitempty` - Labels map[string]string `yaml:labels,omitempty` - Volumes []struct { - Volume struct { - Host string `yaml:host` - Guest string `yaml:guest` - } `yaml:volume` - } `yaml:volumes,omitempty` - Links []struct { - Link struct { - Name string `yaml:name` - Alias string `yaml:alias` - } `yaml:link` - } `yaml:links,omitempty` -} - -func (config *Config) loadTemplate(templateDir string, template string) error { - template_filename := strings.TrimRight(templateDir, "/") + "/" + string(template) - content, err := os.ReadFile(template_filename) - if err != nil { - if os.IsNotExist(err) { - fmt.Println("template file does not exist: " + template_filename) - } - return err - } - templateConfig := &Config{} - if err := yaml.Unmarshal(content, templateConfig); err != nil { - return err - } - if err := mergo.Merge(config, templateConfig, mergo.WithOverride); err != nil { - return err - } - config.rawYaml = append(config.rawYaml, string(content[:])) - return nil -} - -func LoadConfig(dir string, configName string, includeTemplates bool, templatesDir string) (*Config, error) { - config := &Config{ - Name: configName, - Boot_Command: defaultBootCommand, - Base_Image: utils.DefaultBaseImage, - } - - matched, _ := regexp.MatchString("[[:upper:]/ !@#$%^&*()+~`=]", configName) - - if matched { - msg := "ERROR: Config name '" + configName + "' must not contain upper case characters, spaces or special characters. Correct config name and rerun." - fmt.Println(msg) - return nil, errors.New(msg) - } - - config_filename := string(strings.TrimRight(dir, "/") + "/" + config.Name + ".yml") - content, err := os.ReadFile(config_filename) - - if err != nil { - if os.IsNotExist(err) { - fmt.Println("config file does not exist: " + config_filename) - } - return nil, err - } - - baseConfig := &Config{} - - if err := yaml.Unmarshal(content, baseConfig); err != nil { - return nil, err - } - - if includeTemplates { - for _, t := range baseConfig.Templates { - if err := config.loadTemplate(templatesDir, t); err != nil { - return nil, err - } - } - } - - if err := mergo.Merge(config, baseConfig, mergo.WithOverride); err != nil { - return nil, err - } - - config.rawYaml = append(config.rawYaml, string(content[:])) - - if err != nil { - return nil, err - } - - for k, v := range config.Labels { - val := strings.ReplaceAll(v, "{{config}}", config.Name) - config.Labels[k] = val - } - - for k, v := range config.Env { - val := strings.ReplaceAll(v, "{{config}}", config.Name) - config.Env[k] = val - } - - return config, nil -} - -func (config *Config) Yaml() string { - return strings.Join(config.rawYaml, "_FILE_SEPERATOR_") -} - -func (config *Config) Dockerfile(pupsArgs string, bakeEnv bool) string { - builder := strings.Builder{} - builder.WriteString("ARG dockerfile_from_image=" + config.Base_Image + "\n") - builder.WriteString("FROM ${dockerfile_from_image}\n") - builder.WriteString(config.dockerfileArgs() + "\n") - if bakeEnv { - builder.WriteString(config.dockerfileEnvs() + "\n") - } - builder.WriteString(config.dockerfileExpose() + "\n") - builder.WriteString("COPY config.yaml /temp-config.yaml\n") - builder.WriteString("RUN " + - "cat /temp-config.yaml | /usr/local/bin/pups " + pupsArgs + " --stdin " + - "&& rm /temp-config.yaml\n") - builder.WriteString("CMD [\"" + config.BootCommand() + "\"]") - return builder.String() -} - -func (config *Config) WriteYamlConfig(dir string) error { - file := strings.TrimRight(dir, "/") + "/config.yaml" - if err := os.WriteFile(file, []byte(config.Yaml()), 0660); err != nil { - return errors.New("error writing config file " + file) - } - return nil -} - -func (config *Config) BootCommand() string { - if len(config.Boot_Command) > 0 { - return config.Boot_Command - } else if config.No_Boot_Command { - return "" - } else { - return defaultBootCommand - } -} - -func (config *Config) EnvArray(includeKnownSecrets bool) []string { - envs := []string{} - for k, v := range config.Env { - if !includeKnownSecrets && slices.Contains(utils.KnownSecrets, k) { - continue - } - envs = append(envs, k+"="+v) - } - slices.Sort(envs) - return envs -} - -func (config *Config) DockerArgs() []string { - return strings.Fields(config.Docker_Args) -} - -func (config *Config) dockerfileEnvs() string { - builder := []string{} - for k, _ := range config.Env { - builder = append(builder, "ENV "+k+"=${"+k+"}") - } - slices.Sort(builder) - return strings.Join(builder, "\n") -} - -func (config *Config) dockerfileArgs() string { - builder := []string{} - for k, _ := range config.Env { - builder = append(builder, "ARG "+k) - } - slices.Sort(builder) - return strings.Join(builder, "\n") -} - -func (config *Config) dockerfileExpose() string { - builder := []string{} - for _, p := range config.Expose { - port := p - if strings.Contains(p, ":") { - _, port, _ = strings.Cut(p, ":") - } - builder = append(builder, "EXPOSE "+port) - } - slices.Sort(builder) - return strings.Join(builder, "\n") -} - -func (config *Config) RunImage() string { - if len(config.Run_Image) > 0 { - return config.Run_Image - } - return "local_discourse/" + config.Name -} - -func (config *Config) DockerHostname(defaultHostname string) string { - _, exists := config.Env["DOCKER_USE_HOSTNAME"] - re := regexp.MustCompile(`[^a-zA-Z-]`) - hostname := defaultHostname - if exists { - hostname = config.Env["DISCOURSE_HOSTNAME"] - } - hostname = string(re.ReplaceAll([]byte(hostname), []byte("-"))[:]) - return hostname -} diff --git a/launcher_go/v2/config/config_suite_test.go b/launcher_go/v2/config/config_suite_test.go deleted file mode 100644 index c6e29ba71..000000000 --- a/launcher_go/v2/config/config_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package config_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestConfig(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Config Suite") -} diff --git a/launcher_go/v2/config/config_test.go b/launcher_go/v2/config/config_test.go deleted file mode 100644 index 85ce46ef8..000000000 --- a/launcher_go/v2/config/config_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package config_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/discourse/discourse_docker/launcher_go/v2/config" - "os" - "strings" -) - -var _ = Describe("Config", func() { - var testDir string - var conf *config.Config - BeforeEach(func() { - testDir, _ = os.MkdirTemp("", "ddocker-test") - conf, _ = config.LoadConfig("../test/containers", "test", true, "../test") - }) - AfterEach(func() { - os.RemoveAll(testDir) - }) - It("should be able to run LoadConfig to load yaml configuration", func() { - conf, err := config.LoadConfig("../test/containers", "test", true, "../test") - Expect(err).To(BeNil()) - result := conf.Yaml() - Expect(result).To(ContainSubstring("DISCOURSE_DEVELOPER_EMAILS: 'me@example.com,you@example.com'")) - Expect(result).To(ContainSubstring("_FILE_SEPERATOR_")) - Expect(result).To(ContainSubstring("version: tests-passed")) - }) - - It("can write raw yaml config", func() { - err := conf.WriteYamlConfig(testDir) - Expect(err).To(BeNil()) - out, err := os.ReadFile(testDir + "/config.yaml") - Expect(err).To(BeNil()) - Expect(strings.Contains(string(out[:]), "")) - Expect(string(out[:])).To(ContainSubstring("DISCOURSE_DEVELOPER_EMAILS: 'me@example.com,you@example.com'")) - }) - - It("can convert pups config to dockerfile format", func() { - dockerfile := conf.Dockerfile("", false) - Expect(dockerfile).To(ContainSubstring("ARG DISCOURSE_DEVELOPER_EMAILS")) - Expect(dockerfile).To(ContainSubstring("RUN cat /temp-config.yaml")) - Expect(dockerfile).To(ContainSubstring("EXPOSE 80")) - }) - - Context("hostname tests", func() { - It("replaces hostname", func() { - config := config.Config{Env: map[string]string{"DOCKER_USE_HOSTNAME": "true", "DISCOURSE_HOSTNAME": "asdfASDF"}} - Expect(config.DockerHostname("")).To(Equal("asdfASDF")) - }) - It("replaces hostname", func() { - config := config.Config{Env: map[string]string{"DOCKER_USE_HOSTNAME": "true", "DISCOURSE_HOSTNAME": "asdf!@#$%^&*()ASDF"}} - Expect(config.DockerHostname("")).To(Equal("asdf----------ASDF")) - }) - It("replaces a default hostnamehostname", func() { - config := config.Config{} - Expect(config.DockerHostname("asdf!@#")).To(Equal("asdf---")) - }) - }) -}) diff --git a/launcher_go/v2/docker/commands.go b/launcher_go/v2/docker/commands.go deleted file mode 100644 index 58ec47487..000000000 --- a/launcher_go/v2/docker/commands.go +++ /dev/null @@ -1,324 +0,0 @@ -package docker - -import ( - "context" - "fmt" - "io" - "os" - "os/exec" - "runtime" - "sort" - "strings" - "syscall" - "time" - - "github.com/Wing924/shellwords" - "github.com/discourse/discourse_docker/launcher_go/v2/config" - "github.com/discourse/discourse_docker/launcher_go/v2/utils" - "golang.org/x/sys/unix" -) - -type DockerBuilder struct { - Config *config.Config - Ctx *context.Context - Stdin io.Reader - Dir string - Namespace string - ImageTag string -} - -func (r *DockerBuilder) Run() error { - if r.ImageTag == "" { - r.ImageTag = "latest" - } - cmd := exec.CommandContext(*r.Ctx, utils.DockerPath, "build") - cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} - cmd.Cancel = func() error { - return unix.Kill(-cmd.Process.Pid, unix.SIGINT) - } - cmd.Dir = r.Dir - cmd.Env = r.Config.EnvArray(false) - cmd.Env = append(cmd.Env, "BUILDKIT_PROGRESS=plain") - for k, _ := range r.Config.Env { - cmd.Args = append(cmd.Args, "--build-arg") - cmd.Args = append(cmd.Args, k) - } - cmd.Args = append(cmd.Args, "--no-cache") - cmd.Args = append(cmd.Args, "--pull") - cmd.Args = append(cmd.Args, "--force-rm") - cmd.Args = append(cmd.Args, "-t") - cmd.Args = append(cmd.Args, r.Namespace+"/"+r.Config.Name+":"+r.ImageTag) - cmd.Args = append(cmd.Args, "--shm-size=512m") - cmd.Args = append(cmd.Args, "-f") - cmd.Args = append(cmd.Args, "-") - cmd.Args = append(cmd.Args, ".") - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Stdin = r.Stdin - if err := utils.CmdRunner(cmd).Run(); err != nil { - return err - } - return nil -} - -type DockerRunner struct { - Config *config.Config - Ctx *context.Context - ExtraEnv []string - ExtraFlags []string - Rm bool - ContainerId string - CustomImage string - Cmd []string - Stdin io.Reader - SkipPorts bool - DryRun bool - Restart bool - Detatch bool - Hostname string -} - -func (r *DockerRunner) Run() error { - cmd := exec.CommandContext(*r.Ctx, utils.DockerPath, "run") - - // Detatch signifies we do not want to supervise - if !r.Detatch { - cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} - cmd.Cancel = func() error { - if runtime.GOOS == "darwin" { - runCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - stopCmd := exec.CommandContext(runCtx, utils.DockerPath, "stop", r.ContainerId) - utils.CmdRunner(stopCmd).Run() - cancel() - } - return unix.Kill(-cmd.Process.Pid, unix.SIGINT) - } - } - - cmd.Env = r.Config.EnvArray(true) - envKeys := make([]string, 0, len(r.Config.Env)) - - for envKey := range r.Config.Env { - envKeys = append(envKeys, envKey) - } - - sort.Strings(envKeys) - - if r.DryRun { - // multi-line env doesn't work super great from CLI, but we can print out the rest. - for _, envKey := range envKeys { - value := r.Config.Env[envKey] - - if !strings.Contains(value, "\n") { - cmd.Args = append(cmd.Args, "--env") - cmd.Args = append(cmd.Args, envKey+"="+shellwords.Escape(value)) - } - } - } else { - for _, envKey := range envKeys { - cmd.Args = append(cmd.Args, "--env") - cmd.Args = append(cmd.Args, envKey) - } - } - - // Order is important here, we add extra env after config's env to override anything set in env. - for _, e := range r.ExtraEnv { - cmd.Args = append(cmd.Args, "--env") - cmd.Args = append(cmd.Args, e) - } - - for k, v := range r.Config.Labels { - cmd.Args = append(cmd.Args, "--label") - cmd.Args = append(cmd.Args, k+"="+v) - } - - if !r.SkipPorts { - for _, v := range r.Config.Expose { - if strings.Contains(v, ":") { - cmd.Args = append(cmd.Args, "--publish") - cmd.Args = append(cmd.Args, v) - } else { - cmd.Args = append(cmd.Args, "--expose") - cmd.Args = append(cmd.Args, v) - } - } - } - - for _, v := range r.Config.Volumes { - cmd.Args = append(cmd.Args, "--volume") - cmd.Args = append(cmd.Args, v.Volume.Host+":"+v.Volume.Guest) - } - - for _, v := range r.Config.Links { - cmd.Args = append(cmd.Args, "--link") - cmd.Args = append(cmd.Args, v.Link.Name+":"+v.Link.Alias) - } - - cmd.Args = append(cmd.Args, "--shm-size=512m") - - if r.Rm { - cmd.Args = append(cmd.Args, "--rm") - } - - if r.Restart { - cmd.Args = append(cmd.Args, "--restart=always") - } else { - cmd.Args = append(cmd.Args, "--restart=no") - } - - if r.Detatch { - cmd.Args = append(cmd.Args, "--detach") - } - - cmd.Args = append(cmd.Args, "--interactive") - - // Docker args override settings above - for _, f := range r.Config.DockerArgs() { - cmd.Args = append(cmd.Args, f) - } - - for _, f := range r.ExtraFlags { - cmd.Args = append(cmd.Args, f) - } - - if r.Hostname != "" { - cmd.Args = append(cmd.Args, "--hostname") - cmd.Args = append(cmd.Args, r.Hostname) - } - - cmd.Args = append(cmd.Args, "--name") - cmd.Args = append(cmd.Args, r.ContainerId) - - if len(r.CustomImage) > 0 { - cmd.Args = append(cmd.Args, r.CustomImage) - } else { - cmd.Args = append(cmd.Args, r.Config.RunImage()) - } - - for _, c := range r.Cmd { - cmd.Args = append(cmd.Args, c) - } - - if !r.Detatch { - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Stdin = r.Stdin - } - - runner := utils.CmdRunner(cmd) - - if r.DryRun { - fmt.Println(cmd) - } else { - if err := runner.Run(); err != nil { - return err - } - } - return nil -} - -type DockerPupsRunner struct { - Config *config.Config - PupsArgs string - FromImageName string - SavedImageName string - ExtraEnv []string - Ctx *context.Context - ContainerId string -} - -func (r *DockerPupsRunner) Run() error { - rm := false - // remove : in case docker tag is blank, and use default latest tag - r.SavedImageName = strings.TrimRight(r.SavedImageName, ":") - - if r.SavedImageName == "" { - rm = true - } - - defer func(rm bool) { - if !rm { - time.Sleep(utils.CommitWait) - runCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - cmd := exec.CommandContext(runCtx, utils.DockerPath, "rm", "--force", r.ContainerId) - utils.CmdRunner(cmd).Run() - cancel() - } - }(rm) - - commands := []string{ - "/bin/bash", - "-c", - "/usr/local/bin/pups --stdin " + r.PupsArgs, - } - - runner := DockerRunner{Config: r.Config, - Ctx: r.Ctx, - ExtraEnv: r.ExtraEnv, - Rm: rm, - CustomImage: r.FromImageName, - ContainerId: r.ContainerId, - Cmd: commands, - Stdin: strings.NewReader(r.Config.Yaml()), - SkipPorts: true, //pups runs don't need to expose ports - } - - if err := runner.Run(); err != nil { - return err - } - - if len(r.SavedImageName) > 0 { - time.Sleep(utils.CommitWait) - - cmd := exec.Command("docker", - "commit", - "--change", - "LABEL org.opencontainers.image.created=\""+time.Now().UTC().Format(time.RFC3339)+"\"", - "--change", - "CMD [\""+r.Config.BootCommand()+"\"]", - r.ContainerId, - r.SavedImageName, - ) - - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - fmt.Fprintln(utils.Out, cmd) - - if err := utils.CmdRunner(cmd).Run(); err != nil { - return err - } - } - - return nil -} - -func ContainerExists(container string) (bool, error) { - cmd := exec.Command(utils.DockerPath, "ps", "--all", "--quiet", "--filter", "name="+container) - result, err := utils.CmdRunner(cmd).Output() - - if err != nil { - return false, err - } - - if len(result) > 0 { - return true, nil - } - - return false, nil -} - -func ContainerRunning(container string) (bool, error) { - cmd := exec.Command(utils.DockerPath, "ps", "--quiet", "--filter", "name="+container) - result, err := utils.CmdRunner(cmd).Output() - - if err != nil { - return false, err - } - - if len(result) > 0 { - return true, nil - } - - return false, nil -} diff --git a/launcher_go/v2/docker/commands_test.go b/launcher_go/v2/docker/commands_test.go deleted file mode 100644 index aa7078cf7..000000000 --- a/launcher_go/v2/docker/commands_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package docker_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "bytes" - "context" - "github.com/discourse/discourse_docker/launcher_go/v2/config" - "github.com/discourse/discourse_docker/launcher_go/v2/docker" - . "github.com/discourse/discourse_docker/launcher_go/v2/test_utils" - "github.com/discourse/discourse_docker/launcher_go/v2/utils" - "strings" -) - -var _ = Describe("Commands", func() { - Context("under normal conditions", func() { - var conf *config.Config - var out *bytes.Buffer - var ctx context.Context - - BeforeEach(func() { - utils.DockerPath = "docker" - out = &bytes.Buffer{} - utils.Out = out - utils.CommitWait = 0 - conf = &config.Config{Name: "test"} - ctx = context.Background() - utils.CmdRunner = CreateNewFakeCmdRunner() - }) - It("Removes unspecified image tags on commit", func() { - runner := docker.DockerPupsRunner{Config: conf, ContainerId: "123", Ctx: &ctx, SavedImageName: "local_discourse/test:"} - runner.Run() - cmd := GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker run")) - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker commit")) - Expect(strings.HasSuffix(cmd.String(), ":")).To(BeFalse()) - cmd = GetLastCommand() - Expect(cmd.String()).To(ContainSubstring("docker rm")) - }) - }) -}) diff --git a/launcher_go/v2/docker/docker_suite_test.go b/launcher_go/v2/docker/docker_suite_test.go deleted file mode 100644 index 4001151b1..000000000 --- a/launcher_go/v2/docker/docker_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package docker_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestDocker(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Docker Suite") -} diff --git a/launcher_go/v2/go.mod b/launcher_go/v2/go.mod deleted file mode 100644 index eee727986..000000000 --- a/launcher_go/v2/go.mod +++ /dev/null @@ -1,30 +0,0 @@ -module github.com/discourse/discourse_docker/launcher_go/v2 - -go 1.22 - -require ( - dario.cat/mergo v1.0.1 - github.com/Wing924/shellwords v1.1.0 - github.com/alecthomas/kong v0.9.0 - github.com/google/uuid v1.6.0 - github.com/onsi/ginkgo/v2 v2.20.1 - github.com/onsi/gomega v1.34.1 - github.com/posener/complete v1.2.3 - github.com/willabides/kongplete v0.4.0 - golang.org/x/sys v0.25.0 - gopkg.in/yaml.v3 v3.0.1 -) - -require ( - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/google/go-cmp v0.6.0 // indirect - github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/text v0.17.0 // indirect - golang.org/x/tools v0.24.0 // indirect -) diff --git a/launcher_go/v2/go.sum b/launcher_go/v2/go.sum deleted file mode 100644 index dfdb9ff95..000000000 --- a/launcher_go/v2/go.sum +++ /dev/null @@ -1,64 +0,0 @@ -dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= -dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/Wing924/shellwords v1.1.0 h1:dSiaG54kIH5pP636vlQSnRFhnSrFBrDPokMUj1CwySU= -github.com/Wing924/shellwords v1.1.0/go.mod h1:VWXBb1GU2vKj0ts/tn+TkAIs/uTn60rYcclSv02wSQg= -github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU= -github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA= -github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os= -github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= -github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= -github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= -github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= -github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/onsi/ginkgo/v2 v2.20.1 h1:YlVIbqct+ZmnEph770q9Q7NVAz4wwIiVNahee6JyUzo= -github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= -github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= -github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= -github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab h1:ZjX6I48eZSFetPb41dHudEyVr5v953N15TsNZXlkcWY= -github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab/go.mod h1:/PfPXh0EntGc3QAAyUaviy4S9tzy4Zp0e2ilq4voC6E= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/willabides/kongplete v0.4.0 h1:eivXxkp5ud5+4+NVN9e4goxC5mSh3n1RHov+gsblM2g= -github.com/willabides/kongplete v0.4.0/go.mod h1:0P0jtWD9aTsqPSUAl4de35DLghrr57XcayPyvqSi2X8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/launcher_go/v2/go_suite_test.go b/launcher_go/v2/go_suite_test.go deleted file mode 100644 index 8ca16477a..000000000 --- a/launcher_go/v2/go_suite_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package main_test - -import ( - "testing" - - "github.com/discourse/discourse_docker/launcher_go/v2/utils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestMain(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Main Suite") -} - -var _ = BeforeSuite(func() { - utils.CommitWait = 0 -}) diff --git a/launcher_go/v2/main.go b/launcher_go/v2/main.go deleted file mode 100644 index 6f982cd28..000000000 --- a/launcher_go/v2/main.go +++ /dev/null @@ -1,92 +0,0 @@ -package main - -import ( - "context" - "fmt" - "github.com/alecthomas/kong" - "github.com/discourse/discourse_docker/launcher_go/v2/utils" - "github.com/posener/complete" - "github.com/willabides/kongplete" - "golang.org/x/sys/unix" - "os" - "os/exec" - "os/signal" -) - -type Cli struct { - Version kong.VersionFlag `help:"Show version."` - ConfDir string `default:"./containers" hidden:"" help:"Discourse pups config directory." predictor:"dir"` - TemplatesDir string `default:"." hidden:"" help:"Home project directory containing a templates/ directory which in turn contains pups yaml templates." predictor:"dir"` - BuildDir string `default:"./tmp" hidden:"" help:"Temporary build folder for building images." predictor:"dir"` - Namespace string `default:"local_discourse" env:"DISCOURSE_NAMESPACE" help:"image namespace."` - 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."` - - DestroyCmd DestroyCmd `cmd:"" alias:"rm" name:"destroy" help:"Shutdown and destroy container."` - LogsCmd LogsCmd `cmd:"" name:"logs" help:"Print logs for container."` - CleanupCmd CleanupCmd `cmd:"" name:"cleanup" help:"Cleanup unused containers."` - EnterCmd EnterCmd `cmd:"" name:"enter" help:"Connects to a shell running in the container."` - RunCmd RunCmd `cmd:"" name:"run" help:"Runs the specified command in context of a docker container."` - StartCmd StartCmd `cmd:"" name:"start" help:"Starts container."` - StopCmd StopCmd `cmd:"" name:"stop" help:"Stops container."` - RestartCmd RestartCmd `cmd:"" name:"restart" help:"Stops then starts container."` - RebuildCmd RebuildCmd `cmd:"" name:"rebuild" help:"Builds new image, then destroys old container, and starts new container."` - - InstallCompletions kongplete.InstallCompletions `cmd:"" aliases:"sh" help:"Print shell autocompletions. Add output to dotfiles, or 'source <(./launcher2 sh)'."` -} - -func main() { - cli := Cli{} - runCtx, cancel := context.WithCancel(context.Background()) - - // pre parse to get config dir for prediction of conf dir - confFiles := utils.FindConfigNames() - - parser := kong.Must(&cli, kong.UsageOnError(), kong.Bind(&runCtx), kong.Vars{"version": utils.Version}) - - // Run kongplete.Complete to handle completion requests - kongplete.Complete(parser, - kongplete.WithPredictor("config", complete.PredictSet(confFiles...)), - kongplete.WithPredictor("file", complete.PredictFiles("*")), - kongplete.WithPredictor("dir", complete.PredictDirs("*")), - ) - - ctx, err := parser.Parse(os.Args[1:]) - parser.FatalIfErrorf(err) - - defer cancel() - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, unix.SIGTERM) - signal.Notify(sigChan, unix.SIGINT) - done := make(chan struct{}) - defer close(done) - go func() { - select { - case <-sigChan: - fmt.Fprintln(utils.Out, "Command interrupted") - cancel() - case <-done: - } - }() - err = ctx.Run() - if err == nil { - return - } - if exiterr, ok := err.(*exec.ExitError); ok { - // Magic exit code that indicates a retry - if exiterr.ExitCode() == 77 { - os.Exit(77) - } else if runCtx.Err() != nil { - fmt.Fprintln(utils.Out, "Aborted with exit code", exiterr.ExitCode()) - } else { - ctx.Fatalf( - "run failed with exit code %v\n"+ - "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one.\n"+ - "./discourse-doctor may help diagnose the problem.", exiterr.ExitCode()) - } - } else { - ctx.FatalIfErrorf(err) - } -} diff --git a/launcher_go/v2/test/containers/standalone.yml b/launcher_go/v2/test/containers/standalone.yml deleted file mode 100644 index aef14df4d..000000000 --- a/launcher_go/v2/test/containers/standalone.yml +++ /dev/null @@ -1,109 +0,0 @@ -## this is the all-in-one, standalone Discourse Docker container template -## -## After making changes to this file, you MUST rebuild -## /var/discourse/launcher rebuild app -## -## BE *VERY* CAREFUL WHEN EDITING! -## YAML FILES ARE SUPER SUPER SENSITIVE TO MISTAKES IN WHITESPACE OR ALIGNMENT! -## visit http://www.yamllint.com/ to validate this file as needed - -templates: - #- "templates/postgres.template.yml" - #- "templates/redis.template.yml" - - "templates/web.template.yml" - ## Uncomment the next line to enable the IPv6 listener - #- "templates/web.ipv6.template.yml" - #- "templates/web.ratelimited.template.yml" - ## Uncomment these two lines if you wish to add Lets Encrypt (https) - #- "templates/web.ssl.template.yml" - #- "templates/web.letsencrypt.ssl.template.yml" - -## which TCP/IP ports should this container expose? -## If you want Discourse to share a port with another webserver like Apache or nginx, -## see https://meta.discourse.org/t/17247 for details -expose: - - "80:80" # http - - "443:443" # https - -params: - db_default_text_search_config: "pg_catalog.english" - - ## Set db_shared_buffers to a max of 25% of the total memory. - ## will be set automatically by bootstrap based on detected RAM, or you can override - #db_shared_buffers: "256MB" - - ## can improve sorting performance, but adds memory usage per-connection - #db_work_mem: "40MB" - - ## Which Git revision should this container use? (default: tests-passed) - #version: tests-passed - -env: - LC_ALL: en_US.UTF-8 - LANG: en_US.UTF-8 - LANGUAGE: en_US.UTF-8 - # DISCOURSE_DEFAULT_LOCALE: en - - ## How many concurrent web requests are supported? Depends on memory and CPU cores. - ## will be set automatically by bootstrap based on detected CPUs, or you can override - #UNICORN_WORKERS: 3 - - ## TODO: The domain name this Discourse instance will respond to - ## Required. Discourse will not work with a bare IP number. - DISCOURSE_HOSTNAME: 'discourse.example.com' - - ## Uncomment if you want the container to be started with the same - ## hostname (-h option) as specified above (default "$hostname-$config") - #DOCKER_USE_HOSTNAME: true - - ## TODO: List of comma delimited emails that will be made admin and developer - ## on initial signup example 'user1@example.com,user2@example.com' - DISCOURSE_DEVELOPER_EMAILS: 'me@example.com,you@example.com' - - ## TODO: The SMTP mail server used to validate new accounts and send notifications - # SMTP ADDRESS, username, and password are required - # WARNING the char '#' in SMTP password can cause problems! - DISCOURSE_SMTP_ADDRESS: smtp.example.com - #DISCOURSE_SMTP_PORT: 587 - DISCOURSE_SMTP_USER_NAME: user@example.com - DISCOURSE_SMTP_PASSWORD: pa$$word - #DISCOURSE_SMTP_ENABLE_START_TLS: true # (optional, default true) - #DISCOURSE_SMTP_DOMAIN: discourse.example.com # (required by some providers) - #DISCOURSE_NOTIFICATION_EMAIL: noreply@discourse.example.com # (address to send notifications from) - - ## If you added the Lets Encrypt template, uncomment below to get a free SSL certificate - #LETSENCRYPT_ACCOUNT_EMAIL: me@example.com - - ## The http or https CDN address for this Discourse instance (configured to pull) - ## see https://meta.discourse.org/t/14857 for details - #DISCOURSE_CDN_URL: https://discourse-cdn.example.com - - ## The maxmind geolocation IP address key for IP address lookup - ## see https://meta.discourse.org/t/-/137387/23 for details - #DISCOURSE_MAXMIND_LICENSE_KEY: 1234567890123456 - -## The Docker container is stateless; all data is stored in /shared -volumes: - - volume: - host: /var/discourse/shared/standalone - guest: /shared - - volume: - host: /var/discourse/shared/standalone/log/var-log - guest: /var/log - -## Plugins go here -## see https://meta.discourse.org/t/19157 for details -hooks: - after_code: - - exec: - cd: $home/plugins - cmd: - - git clone https://github.com/discourse/docker_manager.git - -## Any custom commands to run after building -run: - - exec: echo "Beginning of custom commands" - ## If you want to set the 'From' email address for your first registration, uncomment and change: - ## After getting the first signup email, re-comment the line. It only needs to run once. - #- exec: rails r "SiteSetting.notification_email='info@unconfigured.discourse.org'" - - exec: echo "End of custom commands" diff --git a/launcher_go/v2/test/containers/test.yml b/launcher_go/v2/test/containers/test.yml deleted file mode 100644 index 3737d9aa5..000000000 --- a/launcher_go/v2/test/containers/test.yml +++ /dev/null @@ -1,121 +0,0 @@ -# IMPORTANT: SET A SECRET PASSWORD in Postgres for the Discourse User -# TODO: change SOME_SECRET in this template - -templates: - - "templates/web.template.yml" - ## Uncomment the next line to enable the IPv6 listener - #- "templates/web.ipv6.template.yml" - #- "templates/web.ratelimited.template.yml" - ## Uncomment these two lines if you wish to add Lets Encrypt (https) - #- "templates/web.ssl.template.yml" - #- "templates/web.letsencrypt.ssl.template.yml" - -## which TCP/IP ports should this container expose? -## If you want Discourse to share a port with another webserver like Apache or nginx, -## see https://meta.discourse.org/t/17247 for details -expose: - - "80:80" # http - - "443:443" # https - - 90 - -# Use 'links' key to link containers together, aka use Docker --link flag. -links: - - link: - name: data - alias: data - -# any extra arguments for Docker? -docker_args: "--expose 100" - -params: - ## Which Git revision should this container use? (default: tests-passed) - #version: tests-passed - -env: - LC_ALL: en_US.UTF-8 - LANG: en_US.UTF-8 - LANGUAGE: en_US.UTF-8 - # DISCOURSE_DEFAULT_LOCALE: en - REPLACED: "{{config}}/{{config}}/{{config}}" - MULTI: | - test - multiline with some spaces - var - - ## How many concurrent web requests are supported? Depends on memory and CPU cores. - ## will be set automatically by bootstrap based on detected CPUs, or you can override - #UNICORN_WORKERS: 3 - - ## TODO: The domain name this Discourse instance will respond to - DISCOURSE_HOSTNAME: 'discourse.example.com' - - ## Uncomment if you want the container to be started with the same - ## hostname (-h option) as specified above (default "$hostname-$config") - #DOCKER_USE_HOSTNAME: true - - ## TODO: List of comma delimited emails that will be made admin and developer - ## on initial signup example 'user1@example.com,user2@example.com' - DISCOURSE_DEVELOPER_EMAILS: 'me@example.com,you@example.com' - - ## TODO: The SMTP mail server used to validate new accounts and send notifications - # SMTP ADDRESS, username, and password are required - # WARNING the char '#' in SMTP password can cause problems! - DISCOURSE_SMTP_ADDRESS: smtp.example.com - #DISCOURSE_SMTP_PORT: 587 - DISCOURSE_SMTP_USER_NAME: user@example.com - DISCOURSE_SMTP_PASSWORD: pa$$word - #DISCOURSE_SMTP_ENABLE_START_TLS: true # (optional, default true) - #DISCOURSE_SMTP_DOMAIN: discourse.example.com # (required by some providers) - #DISCOURSE_NOTIFICATION_EMAIL: noreply@discourse.example.com # (address to send notifications from) - - ## If you added the Lets Encrypt template, uncomment below to get a free SSL certificate - #LETSENCRYPT_ACCOUNT_EMAIL: me@example.com - - ## TODO: configure connectivity to the databases - DISCOURSE_DB_SOCKET: '' - #DISCOURSE_DB_USERNAME: discourse - DISCOURSE_DB_PASSWORD: SOME_SECRET - DISCOURSE_DB_HOST: data - DISCOURSE_REDIS_HOST: data - - ## The http or https CDN address for this Discourse instance (configured to pull) - ## see https://meta.discourse.org/t/14857 for details - #DISCOURSE_CDN_URL: https://discourse-cdn.example.com - - ## The maxmind geolocation IP address key for IP address lookup - ## see https://meta.discourse.org/t/-/137387/23 for details - #DISCOURSE_MAXMIND_LICENSE_KEY: 1234567890123456 - -volumes: - - volume: - host: /var/discourse/shared/web-only - guest: /shared - - volume: - host: /var/discourse/shared/web-only/log/var-log - guest: /var/log - -## Plugins go here -## see https://meta.discourse.org/t/19157 for details -hooks: - after_code: - - exec: - cd: $home/plugins - cmd: - - git clone https://github.com/discourse/docker_manager.git - -## Remember, this is YAML syntax - you can only have one block with a name -run: - - exec: echo "custom test command" - - exec: echo "Beginning of custom commands" - - ## If you want to configure password login for root, uncomment and change: - ## Use only one of the following lines: - #- exec: /usr/sbin/usermod -p 'PASSWORD_HASH' root - #- exec: /usr/sbin/usermod -p "$(mkpasswd -m sha-256 'RAW_PASSWORD')" root - - ## If you want to authorized additional users, uncomment and change: - #- exec: ssh-import-id username - #- exec: ssh-import-id anotherusername - - - exec: echo "End of custom commands" - - exec: awk -F\# '{print $1;}' ~/.ssh/authorized_keys | awk 'BEGIN { print "Authorized SSH keys for this container:"; } NF>=2 {print $NF;}' diff --git a/launcher_go/v2/test/containers/test2.yaml b/launcher_go/v2/test/containers/test2.yaml deleted file mode 100644 index e69de29bb..000000000 diff --git a/launcher_go/v2/test/containers/test3.not-a-yaml b/launcher_go/v2/test/containers/test3.not-a-yaml deleted file mode 100644 index e69de29bb..000000000 diff --git a/launcher_go/v2/test/containers/web_only.yml b/launcher_go/v2/test/containers/web_only.yml deleted file mode 100644 index 5cf77ff42..000000000 --- a/launcher_go/v2/test/containers/web_only.yml +++ /dev/null @@ -1,114 +0,0 @@ -# IMPORTANT: SET A SECRET PASSWORD in Postgres for the Discourse User -# TODO: change SOME_SECRET in this template - -templates: - - "templates/web.template.yml" - ## Uncomment the next line to enable the IPv6 listener - #- "templates/web.ipv6.template.yml" - #- "templates/web.ratelimited.template.yml" - ## Uncomment these two lines if you wish to add Lets Encrypt (https) - #- "templates/web.ssl.template.yml" - #- "templates/web.letsencrypt.ssl.template.yml" - -## which TCP/IP ports should this container expose? -## If you want Discourse to share a port with another webserver like Apache or nginx, -## see https://meta.discourse.org/t/17247 for details -expose: - - "80:80" # http - - "443:443" # https - -# Use 'links' key to link containers together, aka use Docker --link flag. -links: - - link: - name: data - alias: data - -# any extra arguments for Docker? -# docker_args: - -params: - ## Which Git revision should this container use? (default: tests-passed) - #version: tests-passed - -env: - LC_ALL: en_US.UTF-8 - LANG: en_US.UTF-8 - LANGUAGE: en_US.UTF-8 - # DISCOURSE_DEFAULT_LOCALE: en - - ## How many concurrent web requests are supported? Depends on memory and CPU cores. - ## will be set automatically by bootstrap based on detected CPUs, or you can override - #UNICORN_WORKERS: 3 - - ## TODO: The domain name this Discourse instance will respond to - DISCOURSE_HOSTNAME: 'discourse.example.com' - - ## Uncomment if you want the container to be started with the same - ## hostname (-h option) as specified above (default "$hostname-$config") - #DOCKER_USE_HOSTNAME: true - - ## TODO: List of comma delimited emails that will be made admin and developer - ## on initial signup example 'user1@example.com,user2@example.com' - DISCOURSE_DEVELOPER_EMAILS: 'me@example.com,you@example.com' - - ## TODO: The SMTP mail server used to validate new accounts and send notifications - # SMTP ADDRESS, username, and password are required - # WARNING the char '#' in SMTP password can cause problems! - DISCOURSE_SMTP_ADDRESS: smtp.example.com - #DISCOURSE_SMTP_PORT: 587 - DISCOURSE_SMTP_USER_NAME: user@example.com - DISCOURSE_SMTP_PASSWORD: pa$$word - #DISCOURSE_SMTP_ENABLE_START_TLS: true # (optional, default true) - #DISCOURSE_SMTP_DOMAIN: discourse.example.com # (required by some providers) - #DISCOURSE_NOTIFICATION_EMAIL: noreply@discourse.example.com # (address to send notifications from) - - ## If you added the Lets Encrypt template, uncomment below to get a free SSL certificate - #LETSENCRYPT_ACCOUNT_EMAIL: me@example.com - - ## TODO: configure connectivity to the databases - DISCOURSE_DB_SOCKET: '' - #DISCOURSE_DB_USERNAME: discourse - DISCOURSE_DB_PASSWORD: SOME_SECRET - DISCOURSE_DB_HOST: data - DISCOURSE_REDIS_HOST: data - - ## The http or https CDN address for this Discourse instance (configured to pull) - ## see https://meta.discourse.org/t/14857 for details - #DISCOURSE_CDN_URL: https://discourse-cdn.example.com - - ## The maxmind geolocation IP address key for IP address lookup - ## see https://meta.discourse.org/t/-/137387/23 for details - #DISCOURSE_MAXMIND_LICENSE_KEY: 1234567890123456 - -volumes: - - volume: - host: /var/discourse/shared/web-only - guest: /shared - - volume: - host: /var/discourse/shared/web-only/log/var-log - guest: /var/log - -## Plugins go here -## see https://meta.discourse.org/t/19157 for details -hooks: - after_code: - - exec: - cd: $home/plugins - cmd: - - git clone https://github.com/discourse/docker_manager.git - -## Remember, this is YAML syntax - you can only have one block with a name -run: - - exec: echo "Beginning of custom commands" - - ## If you want to configure password login for root, uncomment and change: - ## Use only one of the following lines: - #- exec: /usr/sbin/usermod -p 'PASSWORD_HASH' root - #- exec: /usr/sbin/usermod -p "$(mkpasswd -m sha-256 'RAW_PASSWORD')" root - - ## If you want to authorized additional users, uncomment and change: - #- exec: ssh-import-id username - #- exec: ssh-import-id anotherusername - - - exec: echo "End of custom commands" - - exec: awk -F\# '{print $1;}' ~/.ssh/authorized_keys | awk 'BEGIN { print "Authorized SSH keys for this container:"; } NF>=2 {print $NF;}' diff --git a/launcher_go/v2/test/templates/web.template.yml b/launcher_go/v2/test/templates/web.template.yml deleted file mode 100644 index b0794d85d..000000000 --- a/launcher_go/v2/test/templates/web.template.yml +++ /dev/null @@ -1,443 +0,0 @@ -env: - # You can have redis on a different box - RAILS_ENV: 'production' - UNICORN_WORKERS: 3 - UNICORN_SIDEKIQS: 1 - # stop heap doubling in size so aggressively, this conserves memory - RUBY_GC_HEAP_GROWTH_MAX_SLOTS: 40000 - RUBY_GC_HEAP_INIT_SLOTS: 400000 - RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR: 1.5 - - DISCOURSE_DB_SOCKET: /var/run/postgresql - DISCOURSE_DB_HOST: - DISCOURSE_DB_PORT: - - -params: - version: tests-passed - - home: /var/www/discourse - upload_size: 10m - nginx_worker_connections: 4000 - -run: - - exec: thpoff echo "thpoff is installed!" - - exec: - tag: precompile - cmd: - - /usr/local/bin/ruby -e 'if ENV["DISCOURSE_SMTP_ADDRESS"] == "smtp.example.com"; puts "Aborting! Mail is not configured!"; exit 1; end' - - /usr/local/bin/ruby -e 'if ENV["DISCOURSE_HOSTNAME"] == "discourse.example.com"; puts "Aborting! Domain is not configured!"; exit 1; end' - - /usr/local/bin/ruby -e 'if (ENV["DISCOURSE_CDN_URL"] || "")[0..1] == "//"; puts "Aborting! CDN must have a protocol specified. Once fixed you should rebake your posts now to correct all posts."; exit 1; end' - # TODO: move to base image (anacron can not be fired up using rc.d) - - exec: rm -f /etc/cron.d/anacron - - file: - path: /etc/cron.d/anacron - contents: | - SHELL=/bin/sh - PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin - - 30 7 * * * root /usr/sbin/anacron -s >/dev/null - - file: - path: /etc/runit/1.d/copy-env - chmod: "+x" - contents: | - #!/bin/bash - env > ~/boot_env - conf=/var/www/discourse/config/discourse.conf - - # find DISCOURSE_ env vars, strip the leader, lowercase the key - /usr/local/bin/ruby -e 'ENV.each{|k,v| puts "#{$1.downcase} = '\''#{v}'\''" if k =~ /^DISCOURSE_(.*)/}' > $conf - - - file: - path: /etc/service/unicorn/run - chmod: "+x" - contents: | - #!/bin/bash - exec 2>&1 - # redis - # postgres - cd $home - chown -R discourse:www-data /shared/log/rails - if [[ -z "$PRECOMPILE_ON_BOOT" ]]; then - PRECOMPILE_ON_BOOT=1 - fi - if [ -f /usr/local/bin/create_db ] && [ "$CREATE_DB_ON_BOOT" = "1" ]; then /usr/local/bin/create_db; rm -f /usr/local/bin/create_db; fi; - if [ "$MIGRATE_ON_BOOT" = "1" ]; then su discourse -c 'bundle exec rake db:migrate'; fi - if [ "$PRECOMPILE_ON_BOOT" = "1" ]; then SKIP_EMBER_CLI_COMPILE=1 su discourse -c 'bundle exec rake assets:precompile'; fi - LD_PRELOAD=$RUBY_ALLOCATOR HOME=/home/discourse USER=discourse exec thpoff chpst -u discourse:www-data -U discourse:www-data bundle exec config/unicorn_launcher -E production -c config/unicorn.conf.rb - - - file: - path: /etc/service/nginx/run - chmod: "+x" - contents: | - #!/bin/sh - exec 2>&1 - exec /usr/sbin/nginx - - - file: - path: /etc/runit/3.d/01-nginx - chmod: "+x" - contents: | - #!/bin/bash - sv stop nginx - - - file: - path: /etc/runit/3.d/02-unicorn - chmod: "+x" - contents: | - #!/bin/bash - sv stop unicorn - - - exec: - cd: $home - hook: code - cmd: - - sudo -H -E -u discourse git reset --hard - - sudo -H -E -u discourse git clean -f - # TODO Remove the special handling of shallow clones when everyone uses images without that clone type - - |- - sudo -H -E -u discourse bash -c ' - set -o errexit - if [ $(git rev-parse --is-shallow-repository) == "true" ]; then - git remote set-branches --add origin main - git remote set-branches origin $version - git fetch --depth 1 origin $version - else - git fetch --tags --prune-tags --prune --force origin - fi - ' - - |- - sudo -H -E -u discourse bash -c ' - set -o errexit - if [[ $(git symbolic-ref --short HEAD) == $version ]] ; then - git pull - else - git -c advice.detachedHead=false checkout $version - fi - ' - - sudo -H -E -u discourse git config user.discourse-version $version - - mkdir -p tmp - - chown discourse:www-data tmp - - mkdir -p tmp/pids - - mkdir -p tmp/sockets - - touch tmp/.gitkeep - - mkdir -p /shared/log/rails - - bash -c "touch -a /shared/log/rails/{production,production_errors,unicorn.stdout,unicorn.stderr,sidekiq}.log" - - bash -c "ln -s /shared/log/rails/{production,production_errors,unicorn.stdout,unicorn.stderr,sidekiq}.log $home/log" - - bash -c "mkdir -p /shared/{uploads,backups}" - - bash -c "ln -s /shared/{uploads,backups} $home/public" - - bash -c "mkdir -p /shared/tmp/{backups,restores}" - - bash -c "ln -s /shared/tmp/{backups,restores} $home/tmp" - - chown -R discourse:www-data /shared/log/rails /shared/uploads /shared/backups /shared/tmp - # scrub broken symlinks from plugins that have been removed - - "[ ! -d public/plugins ] || find public/plugins/ -maxdepth 1 -xtype l -delete" - - - exec: - cmd: - - "cp $home/config/nginx.sample.conf /etc/nginx/conf.d/discourse.conf" - - "rm /etc/nginx/sites-enabled/default" - - "mkdir -p /var/nginx/cache" - - - replace: - filename: /etc/nginx/nginx.conf - from: pid /run/nginx.pid; - to: daemon off; - - - replace: - filename: "/etc/nginx/conf.d/discourse.conf" - from: /upstream[^\}]+\}/m - to: "upstream discourse { - server 127.0.0.1:3000; - }" - - - replace: - filename: "/etc/nginx/conf.d/discourse.conf" - from: /server_name.+$/ - to: server_name _ ; - - - replace: - filename: "/etc/nginx/conf.d/discourse.conf" - from: /client_max_body_size.+$/ - to: client_max_body_size $upload_size ; - - - replace: - filename: "/etc/nginx/nginx.conf" - from: /worker_connections.+$/ - to: worker_connections $nginx_worker_connections ; - - - exec: - cmd: echo "done configuring web" - hook: web_config - - - exec: - cd: $home - hook: web - cmd: - # install bundler version to match Gemfile.lock - - gem install bundler --conservative -v $(awk '/BUNDLED WITH/ { getline; gsub(/ /,""); print $0 }' Gemfile.lock) - - find $home ! -user discourse -exec chown discourse {} \+ - - - exec: - cd: $home - cmd: - - |- - if [ "$version" != "tests-passed" ]; then - rm -rf app/assets/javascripts/node_modules - fi - - |- - if [ -f yarn.lock ]; then - su discourse -c 'yarn install --frozen-lockfile && yarn cache clean' - else - su discourse -c 'CI=1 pnpm install --frozen-lockfile' - fi - - - exec: - cd: $home - hook: bundle_exec - cmd: - - su discourse -c 'bundle config --local deployment true' - - su discourse -c 'bundle config --local without "development test"' - - su discourse -c 'bundle install --retry 3 --jobs 4' - - - exec: - cd: $home - cmd: - - su discourse -c 'LOAD_PLUGINS=0 bundle exec rake plugin:pull_compatible_all' - raise_on_fail: false - - - exec: - cd: $home - tag: migrate - hook: db_migrate - cmd: - - su discourse -c 'bundle exec rake db:migrate' - - exec: - cd: $home - tag: build - hook: assets_precompile_build - cmd: - - su discourse -c 'bundle exec rake assets:precompile:build' - - exec: - cd: $home - tag: precompile - hook: assets_precompile - cmd: - - su discourse -c 'SKIP_EMBER_CLI_COMPILE=1 bundle exec rake themes:update assets:precompile' - - replace: - tag: precompile - filename: /etc/service/unicorn/run - from: PRECOMPILE_ON_BOOT=1 - to: "PRECOMPILE_ON_BOOT=0" - - - file: - path: /usr/local/bin/discourse - chmod: +x - contents: | - #!/bin/bash - (cd /var/www/discourse && RAILS_ENV=production sudo -H -E -u discourse bundle exec script/discourse "$@") - - - file: - path: /usr/local/bin/rails - chmod: +x - contents: | - #!/bin/bash - # If they requested a console, load pry instead - if [ "$*" == "c" -o "$*" == "console" ] - then - (cd /var/www/discourse && RAILS_ENV=production sudo -H -E -u discourse bundle exec pry -r ./config/environment) - else - (cd /var/www/discourse && RAILS_ENV=production sudo -H -E -u discourse bundle exec script/rails "$@") - fi - - - file: - path: /usr/local/bin/rake - chmod: +x - contents: | - #!/bin/bash - (cd /var/www/discourse && RAILS_ENV=production sudo -H -E -u discourse bundle exec bin/rake "$@") - - - file: - path: /usr/local/bin/rbtrace - chmod: +x - contents: | - #!/bin/bash - (cd /var/www/discourse && RAILS_ENV=production sudo -H -E -u discourse bundle exec rbtrace "$@") - - - file: - path: /usr/local/bin/stackprof - chmod: +x - contents: | - #!/bin/bash - (cd /var/www/discourse && RAILS_ENV=production sudo -H -E -u discourse bundle exec stackprof "$@") - - - file: - path: /etc/update-motd.d/10-web - chmod: +x - contents: | - #!/bin/bash - echo - echo Use: rails, rake or discourse to execute commands in production - echo - - - file: - path: /etc/logrotate.d/rails - contents: | - /shared/log/rails/*.log - { - rotate 7 - dateext - daily - missingok - delaycompress - compress - postrotate - sv 1 unicorn - endscript - } - - - file: - path: /etc/logrotate.d/nginx - contents: | - /var/log/nginx/*.log { - daily - missingok - rotate 7 - compress - delaycompress - create 0644 www-data www-data - sharedscripts - postrotate - sv 1 nginx - endscript - } - - # move state out of the container this fancy is done to support rapid rebuilds of containers, - # we store anacron and logrotate state outside the container to ensure its maintained across builds - # later move this snipped into an initialization script - # we also ensure all the symlinks we need to /shared are in place in the correct structure - # this allows us to bootstrap on one machine and then run on another - - file: - path: /etc/runit/1.d/00-ensure-links - chmod: +x - contents: | - #!/bin/bash - if [[ ! -L /var/lib/logrotate ]]; then - rm -fr /var/lib/logrotate - mkdir -p /shared/state/logrotate - ln -s /shared/state/logrotate /var/lib/logrotate - fi - if [[ ! -L /var/spool/anacron ]]; then - rm -fr /var/spool/anacron - mkdir -p /shared/state/anacron-spool - ln -s /shared/state/anacron-spool /var/spool/anacron - fi - if [[ ! -d /shared/log/rails ]]; then - mkdir -p /shared/log/rails - chown -R discourse:www-data /shared/log/rails - fi - if [[ ! -d /shared/uploads ]]; then - mkdir -p /shared/uploads - chown -R discourse:www-data /shared/uploads - fi - if [[ ! -d /shared/backups ]]; then - mkdir -p /shared/backups - chown -R discourse:www-data /shared/backups - fi - - rm -rf /shared/tmp/{backups,restores} - mkdir -p /shared/tmp/{backups,restores} - chown -R discourse:www-data /shared/tmp/{backups,restores} - - file: - path: /etc/runit/1.d/01-cleanup-web-pids - chmod: +x - contents: | - #!/bin/bash - /bin/rm -f /var/www/discourse/tmp/pids/*.pid - # change login directory to Discourse home - - file: - path: /root/.bash_profile - chmod: 644 - contents: | - cd $home - - - file: - path: /usr/local/etc/ImageMagick-7/policy.xml - contents: | - - - - - - ]> - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/launcher_go/v2/test_utils/utils.go b/launcher_go/v2/test_utils/utils.go deleted file mode 100644 index c1c1ee816..000000000 --- a/launcher_go/v2/test_utils/utils.go +++ /dev/null @@ -1,42 +0,0 @@ -package test_utils - -import ( - "github.com/discourse/discourse_docker/launcher_go/v2/utils" - "os/exec" -) - -var RanCmds []exec.Cmd -var CmdOutputResponse []byte -var CmdOutputError error - -type FakeCmdRunner struct { - Cmd *exec.Cmd -} - -func (r FakeCmdRunner) Run() error { - RanCmds = append(RanCmds, *r.Cmd) - return CmdOutputError -} - -func (r FakeCmdRunner) Output() ([]byte, error) { - RanCmds = append(RanCmds, *r.Cmd) - return CmdOutputResponse, CmdOutputError -} - -// Swap out CmdRunner with a fake instance that also returns created ICmdRunners on a channel -// so tests can inspect commands after they're run -func CreateNewFakeCmdRunner() func(cmd *exec.Cmd) utils.ICmdRunner { - RanCmds = []exec.Cmd{} - CmdOutputResponse = []byte{} - CmdOutputError = nil - return func(cmd *exec.Cmd) utils.ICmdRunner { - cmdRunner := &FakeCmdRunner{Cmd: cmd} - return cmdRunner - } -} - -func GetLastCommand() exec.Cmd { - cmd := RanCmds[0] - RanCmds = RanCmds[1:] - return cmd -} diff --git a/launcher_go/v2/utils/cmd_runner.go b/launcher_go/v2/utils/cmd_runner.go deleted file mode 100644 index 765bee1a1..000000000 --- a/launcher_go/v2/utils/cmd_runner.go +++ /dev/null @@ -1,28 +0,0 @@ -package utils - -import ( - "os/exec" -) - -type ICmdRunner interface { - Run() error - Output() ([]byte, error) -} - -type ExecCmdRunner struct { - Cmd *exec.Cmd -} - -func (r *ExecCmdRunner) Run() error { - return r.Cmd.Run() -} - -func (r *ExecCmdRunner) Output() ([]byte, error) { - return r.Cmd.Output() -} - -func NewExecCmdRunner(cmd *exec.Cmd) ICmdRunner { - return &ExecCmdRunner{Cmd: cmd} -} - -var CmdRunner = NewExecCmdRunner diff --git a/launcher_go/v2/utils/consts.go b/launcher_go/v2/utils/consts.go deleted file mode 100644 index 01e0bcdbc..000000000 --- a/launcher_go/v2/utils/consts.go +++ /dev/null @@ -1,50 +0,0 @@ -package utils - -import ( - "io" - "os" - "os/exec" - "time" -) - -const Version = "v2.0.0" - -const DefaultNamespace = "local_discourse" -const DefaultBaseImage = "discourse/base:2.0.20240825-0027" - -// Known secrets, or otherwise not public info from config so we can build public images -var KnownSecrets = []string{ - "DISCOURSE_DB_HOST", - "DISCOURSE_DB_PORT", - "DISCOURSE_DB_REPLICA_HOST", - "DISCOURSE_DB_REPLICA_PORT", - "DISCOURSE_DB_PASSWORD", - "DISCOURSE_REDIS_HOST", - "DISCOURSE_REDIS_REPLICA_HOST", - "DISCOURSE_REDIS_PASSWORD", - "DISCOURSE_SMTP_ADDRESS", - "DISCOURSE_SMTP_USER_NAME", - "DISCOURSE_SMTP_PASSWORD", - "DISCOURSE_DEVELOPER_EMAILS", - "DISCOURSE_SECRET_KEY_BASE", - "DISCOURSE_HOSTNAME", - "DISCOURSE_SAML_CERT", - "DISCOURSE_SAML_TITLE", - "DISCOURSE_SAML_TARGET_URL", - "DISCOURSE_SAML_NAME_IDENTIFIER_FORMAT", -} - -func findDockerPath() string { - location, err := exec.LookPath("docker.io") - if err != nil { - location, _ := exec.LookPath("docker") - return location - } - return location -} - -var DockerPath = findDockerPath() - -var Out io.Writer = os.Stdout - -var CommitWait = 2 * time.Second diff --git a/launcher_go/v2/utils/find_config.go b/launcher_go/v2/utils/find_config.go deleted file mode 100644 index 85a1cae08..000000000 --- a/launcher_go/v2/utils/find_config.go +++ /dev/null @@ -1,51 +0,0 @@ -package utils - -import ( - "bytes" - "flag" - "io/ioutil" - "os" - "strings" -) - -// Find config names for autocomplete, given the current --conf-dir argument. -func FindConfigNames() []string { - compLine := os.Getenv("COMP_LINE") - flagLine := []string{} - found := false - // the flag package wants a valid flag first - // drop all COMP_LINE args until we find something that starts with --conf-dir - for _, s := range strings.Fields(compLine) { - if found { - flagLine = append(flagLine, s) - } - if strings.HasPrefix(s, "--conf-dir") { - flagLine = append(flagLine, s) - found = true - } - } - flags := flag.NewFlagSet("f", flag.ContinueOnError) - //squelch helptext - flags.SetOutput(&bytes.Buffer{}) - confDirArg := flags.String("conf-dir", "./containers", "conf dir") - flags.Parse(flagLine) - - // search in the current conf dir for any files - confDir := strings.TrimRight(*confDirArg, "/") + "/" - confFiles := []string{} - files, err := ioutil.ReadDir(confDir) - if err == nil { - for _, file := range files { - if !file.IsDir() { - if strings.HasSuffix(file.Name(), ".yml") { - confName, _ := strings.CutSuffix(file.Name(), ".yml") - confFiles = append(confFiles, confName) - } else if strings.HasSuffix(file.Name(), ".yaml") { - confName, _ := strings.CutSuffix(file.Name(), ".yaml") - confFiles = append(confFiles, confName) - } - } - } - } - return confFiles -} diff --git a/launcher_go/v2/utils/find_config_test.go b/launcher_go/v2/utils/find_config_test.go deleted file mode 100644 index 1d6340dd5..000000000 --- a/launcher_go/v2/utils/find_config_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package utils_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/discourse/discourse_docker/launcher_go/v2/utils" - "os" -) - -var _ = Describe("FindConfig", func() { - - It("Parses and returns yml or yaml files", func() { - os.Setenv("COMP_LINE", "launcher2 build --conf-dir ../test/containers") - Expect(utils.FindConfigNames()).To(ContainElements("test", "test2")) - }) - - It("Parses and returns yml or yaml files with trailing slash", func() { - os.Setenv("COMP_LINE", "launcher2 build --conf-dir ../test/containers/") - Expect(utils.FindConfigNames()).To(ContainElements("test", "test2")) - }) - - It("Parses and returns yml or yaml files on equals", func() { - os.Setenv("COMP_LINE", "launcher2 --conf-dir=../test/containers other args") - Expect(utils.FindConfigNames()).To(ContainElements("test", "test2")) - }) - - It("doesn't error when dir does not exist when set", func() { - os.Setenv("COMP_LINE", "launcher2 --conf-dir=./does-not-exist") - Expect(utils.FindConfigNames()).To(BeEmpty()) - }) - - It("doesn't error when dir does not exist", func() { - //by default it look is in ./containers directory, which does not exist - // in this directory - os.Setenv("COMP_LINE", "launcher2") - Expect(utils.FindConfigNames()).To(BeEmpty()) - }) -}) diff --git a/launcher_go/v2/utils/utils_suite_test.go b/launcher_go/v2/utils/utils_suite_test.go deleted file mode 100644 index 9ca82ff0d..000000000 --- a/launcher_go/v2/utils/utils_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package utils_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestUtils(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Utils Suite") -}