From 5ecdbffdb1ae66c672abd6b52c4fdc3375e673d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Sat, 11 Jan 2025 23:33:24 +0100 Subject: [PATCH] Changed name of new flags. Restructured code. Enabled semi-generic builtin templates. --- pkg/config/config.go | 201 ++++++--- pkg/config/config_test.go | 28 +- .../docker-compose/docker-compose.yml | 421 ------------------ pkg/config/templates/kubernetes/README.md | 2 +- pkg/setup/setup.go | 7 +- 5 files changed, 164 insertions(+), 495 deletions(-) delete mode 100644 pkg/config/templates/docker-compose/docker-compose.yml diff --git a/pkg/config/config.go b/pkg/config/config.go index ede79c0..5ea8889 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -50,13 +50,16 @@ func Cmd() *cobra.Command { Args: cobra.ExactArgs(1), } - tech := FlagTech(cmd) + builtinTemplate := FlagBuiltinTemplate(cmd) tplFileOrDirName := FlagTpl(cmd) configFileNames := FlagConfig(cmd) cmd.RunE = func(cmd *cobra.Command, args []string) error { + if *tplFileOrDirName != "" && *builtinTemplate != BuiltinTemplateDefault { + return fmt.Errorf("flag --builtin-template must not be used together with flag --template") + } dir := args[0] - if err := Config(dir, *tech, *tplFileOrDirName, *configFileNames); err != nil { + if err := Config(dir, *builtinTemplate, *tplFileOrDirName, *configFileNames); err != nil { return fmt.Errorf("running Config(): %w", err) } return nil @@ -64,11 +67,21 @@ func Cmd() *cobra.Command { return cmd } -const techMsg = " must bei either \"docker-compose\" or \"kubernetes\"" +// BuiltinTemplateDefault is the default builtin template which is used if the +// user does neigther provide a custom template nor give a builtin template. +const BuiltinTemplateDefault = "docker-compose" -// FlagTech setups the technology flag to the given cobra command. -func FlagTech(cmd *cobra.Command) *string { - return cmd.Flags().String("technology", "docker-compose", "create files for this deployment technology,"+techMsg) +func getBuiltinTemplateMsg() string { + var allNames []string + for _, obj := range allBuiltinTemplates { + allNames = append(allNames, obj.name) + } + return fmt.Sprintf(" must be one of the following: %s", strings.Join(allNames, ", ")) +} + +// FlagBuiltinTemplate setups the builtin-template flag to the given cobra command. +func FlagBuiltinTemplate(cmd *cobra.Command) *string { + return cmd.Flags().String("builtin-template", BuiltinTemplateDefault, "create files for this builtin deployment variant,"+getBuiltinTemplateMsg()) } // FlagTpl setups the template flag to the given cobra command. @@ -98,79 +111,129 @@ func Config(baseDir string, tech string, tplFileOrDirName string, configFileName return nil } -// CreateDirAndFiles creates the base directory and (re-)creates the deployment files -// according to the given technology and the given template. If tplFileOrDirName -// is empty, the default deployment file or directory is used. Use a truthy -// value for force to override existing files. -func CreateDirAndFiles(baseDir string, force bool, tech string, tplFileOrDirName string, cfg *YmlConfig) error { - switch tech { - case "docker-compose": - // Get template file from command line option or default - var tplFile []byte - var err error - if tplFileOrDirName == "" { - tplFile, err = deploymentTemplates.ReadFile(path.Join("templates", "docker-compose", "docker-compose.yml")) - if err != nil { - return fmt.Errorf("reading template file: %w", err) - } - } else { - tplFile, err = os.ReadFile(tplFileOrDirName) - if err != nil { - return fmt.Errorf("reading file %q: %w", tplFileOrDirName, err) - } +type builtinTemplateFunc struct { + name string + fn func(baseDir string, force bool, cfg *YmlConfig) error +} + +var allBuiltinTemplates = []builtinTemplateFunc{ + {name: "docker-compose", fn: builtinTemplateDockerCompose}, + {name: "kubernetes", fn: builtinTemplateKubernetes}, +} + +func builtinTemplateByName(name string) (builtinTemplateFunc, bool) { + for _, obj := range allBuiltinTemplates { + if obj.name == name { + return obj, true } + } + return builtinTemplateFunc{}, false +} - // Create directory - if err := os.MkdirAll(baseDir, os.ModePerm); err != nil { - return fmt.Errorf("creating directory at %q: %w", baseDir, err) +// CreateDirAndFiles creates the base directory and (re-)creates the deployment +// files according to the given template. If tplFileOrDirName is empty, the +// given builtin template file or directory is used. Use a truthy value for +// force to override existing files. +func CreateDirAndFiles(baseDir string, force bool, builtinTemplate string, tplFileOrDirName string, cfg *YmlConfig) error { + if tplFileOrDirName == "" { + obj, found := builtinTemplateByName(builtinTemplate) + if !found { + return fmt.Errorf("unknown builtin template %q,"+getBuiltinTemplateMsg(), builtinTemplate) } + return obj.fn(baseDir, force, cfg) + } - // Create deployment file for Docker Compose - filename := filepath.Join(baseDir, cfg.Filename) - if err := CreateDeploymentFile(filename, force, tplFile, cfg); err != nil { - return fmt.Errorf("creating deployment file %q: %w", filename, err) + fileInfo, err := os.Stat(tplFileOrDirName) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("template file or directory %q does not exist", tplFileOrDirName) } + return fmt.Errorf("checking file info of %q: %w", tplFileOrDirName, err) + } - return nil + if !fileInfo.IsDir() { + return customTemplateSingleFile(baseDir, force, tplFileOrDirName, cfg) + } - case "kubernetes": - // Get template directory from command line option or default - var tplDir fs.FS - var err error - if tplFileOrDirName == "" { - tplDir, err = fs.Sub(deploymentTemplates, path.Join("templates", "kubernetes")) - if err != nil { - return fmt.Errorf("retrieving subtree: %w", err) - } - } else { - fileInfo, err := os.Stat(tplFileOrDirName) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - return fmt.Errorf("template file or directory %q does not exist", tplFileOrDirName) - } - return fmt.Errorf("checking file info of %q: %w", tplFileOrDirName, err) - } - if !fileInfo.IsDir() { - return fmt.Errorf("%q is not a directory", tplFileOrDirName) - } - tplDir = os.DirFS(tplFileOrDirName) - } + return customTemplateDirectory(baseDir, force, tplFileOrDirName, cfg) +} - // Create directory - if err := os.MkdirAll(baseDir, os.ModePerm); err != nil { - return fmt.Errorf("creating directory at %q: %w", baseDir, err) - } +func builtinTemplateDockerCompose(baseDir string, force bool, cfg *YmlConfig) error { + // Get default template file + tplFile, err := deploymentTemplates.ReadFile(path.Join("templates", "docker-compose.yml")) + if err != nil { + return fmt.Errorf("reading template file: %w", err) + } - // Create the deployment directory for Kubernetes - if err := CreateDeploymentFilesFromTree(baseDir, force, tplDir, cfg); err != nil { - return fmt.Errorf("creating deployment files at %q: %w", baseDir, err) - } + // Create directory + if err := os.MkdirAll(baseDir, os.ModePerm); err != nil { + return fmt.Errorf("creating directory at %q: %w", baseDir, err) + } - return nil + // Create deployment file for Docker Compose + filename := filepath.Join(baseDir, cfg.Filename) + if err := CreateDeploymentFile(filename, force, tplFile, cfg); err != nil { + return fmt.Errorf("creating deployment file %q: %w", filename, err) + } + + return nil +} - default: - return fmt.Errorf("unknown technology %q,"+techMsg, tech) +func builtinTemplateKubernetes(baseDir string, force bool, cfg *YmlConfig) error { + // Get default template directory + tplDir, err := fs.Sub(deploymentTemplates, path.Join("templates", "kubernetes")) + if err != nil { + return fmt.Errorf("retrieving subtree: %w", err) + } + + // Create directory + if err := os.MkdirAll(baseDir, os.ModePerm); err != nil { + return fmt.Errorf("creating directory at %q: %w", baseDir, err) + } + + // Create the deployment directory for Kubernetes + if err := CreateDeploymentFilesFromTree(baseDir, force, tplDir, cfg); err != nil { + return fmt.Errorf("creating deployment files at %q: %w", baseDir, err) + } + + return nil +} + +func customTemplateSingleFile(baseDir string, force bool, tplFilename string, cfg *YmlConfig) error { + // Get file content + tplFile, err := os.ReadFile(tplFilename) + if err != nil { + return fmt.Errorf("reading file %q: %w", tplFilename, err) } + + // Create directory + if err := os.MkdirAll(baseDir, os.ModePerm); err != nil { + return fmt.Errorf("creating directory at %q: %w", baseDir, err) + } + + // Create deployment file + filename := filepath.Join(baseDir, cfg.Filename) + if err := CreateDeploymentFile(filename, force, tplFile, cfg); err != nil { + return fmt.Errorf("creating deployment file %q: %w", filename, err) + } + + return nil +} + +func customTemplateDirectory(baseDir string, force bool, tplDirname string, cfg *YmlConfig) error { + tplDir := os.DirFS(tplDirname) + + // Create directory + if err := os.MkdirAll(baseDir, os.ModePerm); err != nil { + return fmt.Errorf("creating directory at %q: %w", baseDir, err) + } + + // Create the deployment directory for Kubernetes + if err := CreateDeploymentFilesFromTree(baseDir, force, tplDir, cfg); err != nil { + return fmt.Errorf("creating deployment files at %q: %w", baseDir, err) + } + + return nil } // CreateDeploymentFilesFromTree walks through the FS containing templates and @@ -208,8 +271,8 @@ func CreateDeploymentFilesFromTree(baseDir string, force bool, tplDir fs.FS, cfg return nil } -// CreateDeploymentFile builds a single deployment file to the given path. Use a truthy value for force -// to override an existing file. +// CreateDeploymentFile builds a single deployment file to the given path. Use a +// truthy value for force to override an existing file. func CreateDeploymentFile(filename string, force bool, tplFile []byte, cfg *YmlConfig) error { tmpl, err := template.New("Deployment File").Funcs(funcMap).Parse(string(tplFile)) if err != nil { diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 2ecef84..5a85c12 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -11,7 +11,7 @@ import ( ) func TestCmd(t *testing.T) { - t.Run("executing setup.Cmd() with existing directory", func(t *testing.T) { + t.Run("executing config.Cmd() with existing directory", func(t *testing.T) { testDir, err := os.MkdirTemp("", "openslides-manage-service-") if err != nil { t.Fatalf("generating temporary directory failed: %v", err) @@ -30,7 +30,31 @@ func TestCmd(t *testing.T) { } }) - t.Run("executing setup.CmdCreateDefault() with existing directory", func(t *testing.T) { + t.Run("executing config.Cmd() with existing directory with builtin-template flag and template flag", func(t *testing.T) { + testDir, err := os.MkdirTemp("", "openslides-manage-service-") + if err != nil { + t.Fatalf("generating temporary directory failed: %v", err) + } + defer os.RemoveAll(testDir) + + templateFilePath := path.Join(testDir, "some-template.yml") + if err := os.WriteFile(templateFilePath, []byte(""), os.ModePerm); err != nil { + t.Fatalf("writing custom template failed: %v", err) + } + cmd := config.Cmd() + cmd.SetArgs([]string{testDir, "--builtin-template", "kubernetes", "--template", templateFilePath}) + + err = cmd.Execute() + if err == nil { + t.Fatalf("executing config subcommand: expected error but err is nil") + } + errMsg := "flag --builtin-template must not be used together with flag --template" + if err.Error() != errMsg { + t.Fatalf("wrong error message, expected %q, got %q", errMsg, err.Error()) + } + }) + + t.Run("executing config.CmdCreateDefault() with existing directory", func(t *testing.T) { testDir, err := os.MkdirTemp("", "openslides-manage-service-") if err != nil { t.Fatalf("generating temporary directory failed: %v", err) diff --git a/pkg/config/templates/docker-compose/docker-compose.yml b/pkg/config/templates/docker-compose/docker-compose.yml deleted file mode 100644 index 377bb24..0000000 --- a/pkg/config/templates/docker-compose/docker-compose.yml +++ /dev/null @@ -1,421 +0,0 @@ ---- -version: "3.4" - -x-default-environment: &default-environment - {{- marshalContent 2 .DefaultEnvironment }} - -services: - - {{- with .Services.proxy }} - proxy: - image: {{ .ContainerRegistry }}/openslides-proxy:{{ .Tag }} - {{- if checkFlag $.DisableDependsOn }}{{ else }} - depends_on: - - client - - backendAction - - backendPresenter - - autoupdate - - search - - auth - - media - - icc - - vote - {{- end }} - environment: - << : *default-environment - {{- with .Environment }}{{ marshalContent 6 . }}{{- end }} - {{- if checkFlag $.EnableLocalHTTPS }} - ENABLE_LOCAL_HTTPS: 1 - HTTPS_CERT_FILE: /run/secrets/cert_crt - HTTPS_KEY_FILE: /run/secrets/cert_key - {{- end }} - {{- if checkFlag $.EnableAutoHTTPS }} - ENABLE_AUTO_HTTPS: 1 - {{- end }} - networks: - - uplink - - frontend - ports: - - {{ $.Host }}:{{ $.Port }}:8000 - {{- if checkFlag $.EnableLocalHTTPS }} - secrets: - - cert_crt - - cert_key - {{- end }} - {{- with .AdditionalContent }}{{ marshalContent 4 . }}{{- end }} - {{- end }} - - - {{- with .Services.client }} - - client: - image: {{ .ContainerRegistry }}/openslides-client:{{ .Tag }} - {{- if checkFlag $.DisableDependsOn }}{{ else }} - depends_on: - - backendAction - - backendPresenter - - autoupdate - - search - - auth - - media - - icc - - vote - {{- end }} - environment: - << : *default-environment - {{- with .Environment }}{{ marshalContent 6 . }}{{- end }} - networks: - - frontend - {{- with .AdditionalContent }}{{ marshalContent 4 . }}{{ end }} - {{- end }} - - - {{- with .Services.backendAction }} - - backendAction: - image: {{ .ContainerRegistry }}/openslides-backend:{{ .Tag }} - {{- if checkFlag $.DisableDependsOn }}{{ else }} - depends_on: - - datastoreWriter - - auth - - media - - vote - - postgres - {{- end }} - environment: - << : *default-environment - {{- with .Environment }}{{ marshalContent 6 . }}{{- end }} - OPENSLIDES_BACKEND_COMPONENT: action - networks: - - frontend - - data - - email - secrets: - - auth_token_key - - auth_cookie_key - - internal_auth_password - - postgres_password - {{- with .AdditionalContent }}{{ marshalContent 4 . }}{{- end }} - {{- end }} - - - {{- with .Services.backendPresenter }} - - backendPresenter: - image: {{ .ContainerRegistry }}/openslides-backend:{{ .Tag }} - {{- if checkFlag $.DisableDependsOn }}{{ else }} - depends_on: - - auth - - postgres - {{- end }} - environment: - << : *default-environment - {{- with .Environment }}{{ marshalContent 6 . }}{{- end }} - OPENSLIDES_BACKEND_COMPONENT: presenter - networks: - - frontend - - data - secrets: - - auth_token_key - - auth_cookie_key - - postgres_password - {{- with .AdditionalContent }}{{ marshalContent 4 . }}{{- end }} - {{- end }} - - - {{- with .Services.backendManage }} - - backendManage: - image: {{ .ContainerRegistry }}/openslides-backend:{{ .Tag }} - {{- if checkFlag $.DisableDependsOn }}{{ else }} - depends_on: - - datastoreWriter - - postgres - {{- end }} - environment: - << : *default-environment - {{- with .Environment }}{{ marshalContent 6 . }}{{- end }} - OPENSLIDES_BACKEND_COMPONENT: action - networks: - - data - - email - secrets: - - auth_token_key - - auth_cookie_key - - internal_auth_password - - postgres_password - - superadmin - {{- with .AdditionalContent }}{{ marshalContent 4 . }}{{- end }} - {{- end }} - - - {{- with .Services.datastoreReader }} - - datastoreReader: - image: {{ .ContainerRegistry }}/openslides-datastore-reader:{{ .Tag }} - {{- if checkFlag $.DisableDependsOn }}{{ else }} - depends_on: - - postgres - {{- end }} - environment: - << : *default-environment - {{- with .Environment }}{{ marshalContent 6 . }}{{- end }} - networks: - - data - secrets: - - postgres_password - {{- with .AdditionalContent }}{{ marshalContent 4 . }}{{- end }} - {{- end }} - - - {{- with .Services.datastoreWriter }} - - datastoreWriter: - image: {{ .ContainerRegistry }}/openslides-datastore-writer:{{ .Tag }} - {{- if checkFlag $.DisableDependsOn }}{{ else }} - depends_on: - - postgres - - redis - {{- end }} - environment: - << : *default-environment - {{- with .Environment }}{{ marshalContent 6 . }}{{- end }} - networks: - - data - secrets: - - postgres_password - {{- with .AdditionalContent }}{{ marshalContent 4 . }}{{- end }} - {{- end }} - - - {{- if checkFlag .DisablePostgres }}{{ else }}{{- with .Services.postgres }} - - postgres: - image: postgres:15 - environment: - << : *default-environment - {{- with .Environment }}{{ marshalContent 6 . }}{{- end }} - POSTGRES_DB: openslides - POSTGRES_USER: openslides - POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password - volumes: - - postgres-data:/var/lib/postgresql/data - networks: - - data - secrets: - - postgres_password - {{- with .AdditionalContent }}{{ marshalContent 4 . }}{{- end }} - {{- end }}{{- end }} - - - {{- with .Services.autoupdate }} - - autoupdate: - image: {{ .ContainerRegistry }}/openslides-autoupdate:{{ .Tag }} - {{- if checkFlag $.DisableDependsOn }}{{ else }} - depends_on: - - datastoreReader - - redis - {{- end }} - environment: - << : *default-environment - {{- with .Environment }}{{ marshalContent 6 . }}{{- end }} - networks: - - frontend - - data - secrets: - - auth_token_key - - auth_cookie_key - - postgres_password - {{- with .AdditionalContent }}{{ marshalContent 4 . }}{{- end }} - {{- end }} - - - {{- with .Services.search }} - - search: - image: {{ .ContainerRegistry }}/openslides-search:{{ .Tag }} - {{- if checkFlag $.DisableDependsOn }}{{ else }} - depends_on: - - datastoreReader - - postgres - - autoupdate - {{- end }} - environment: - << : *default-environment - {{- with .Environment }}{{ marshalContent 6 . }}{{- end }} - networks: - - frontend - - data - secrets: - - auth_token_key - - auth_cookie_key - - postgres_password - {{- with .AdditionalContent }}{{ marshalContent 4 . }}{{- end }} - {{- end }} - - - {{- with .Services.auth }} - - auth: - image: {{ .ContainerRegistry }}/openslides-auth:{{ .Tag }} - {{- if checkFlag $.DisableDependsOn }}{{ else }} - depends_on: - - datastoreReader - - redis - {{- end }} - environment: - << : *default-environment - {{- with .Environment }}{{ marshalContent 6 . }}{{- end }} - networks: - - frontend - - data - secrets: - - auth_token_key - - auth_cookie_key - - internal_auth_password - {{- with .AdditionalContent }}{{ marshalContent 4 . }}{{- end }} - {{- end }} - - {{- with .Services.vote }} - - vote: - image: {{ .ContainerRegistry }}/openslides-vote:{{ .Tag }} - {{- if checkFlag $.DisableDependsOn }}{{ else }} - depends_on: - - datastoreReader - - auth - - autoupdate - - redis - {{- end }} - environment: - << : *default-environment - {{- with .Environment }}{{ marshalContent 6 . }}{{- end }} - networks: - - frontend - - data - secrets: - - auth_token_key - - auth_cookie_key - - postgres_password - {{- with .AdditionalContent }}{{ marshalContent 4 . }}{{- end }} - {{- end }} - - {{- with .Services.redis }} - - redis: - image: redis:alpine - command: redis-server --save "" - environment: - << : *default-environment - {{- with .Environment }}{{ marshalContent 6 . }}{{- end }} - networks: - - data - {{- with .AdditionalContent }}{{ marshalContent 4 . }}{{- end }} - {{- end }} - - - {{- with .Services.media }} - - media: - image: {{ .ContainerRegistry }}/openslides-media:{{ .Tag }} - {{- if checkFlag $.DisableDependsOn }}{{ else }} - depends_on: - - postgres - {{- end }} - environment: - << : *default-environment - {{- with .Environment }}{{ marshalContent 6 . }}{{- end }} - networks: - - frontend - - data - secrets: - - auth_token_key - - auth_cookie_key - - postgres_password - {{- with .AdditionalContent }}{{ marshalContent 4 . }}{{- end }} - {{- end }} - - - {{- with .Services.icc }} - - icc: - image: {{ .ContainerRegistry }}/openslides-icc:{{ .Tag }} - {{- if checkFlag $.DisableDependsOn }}{{ else }} - depends_on: - - datastoreReader - - postgres - - redis - {{- end }} - environment: - << : *default-environment - {{- with .Environment }}{{ marshalContent 6 . }}{{- end }} - networks: - - frontend - - data - secrets: - - auth_token_key - - auth_cookie_key - - postgres_password - {{- with .AdditionalContent }}{{ marshalContent 4 . }}{{- end }} - {{- end }} - - - {{- with .Services.manage }} - - manage: - image: {{ .ContainerRegistry }}/openslides-manage:{{ .Tag }} - {{- if checkFlag $.DisableDependsOn }}{{ else }} - depends_on: - - datastoreReader - - backendManage - {{- end }} - environment: - << : *default-environment - {{- with .Environment }}{{ marshalContent 6 . }}{{- end }} - networks: - - frontend - - data - secrets: - - superadmin - - manage_auth_password - - internal_auth_password - {{- with .AdditionalContent }}{{ marshalContent 4 . }}{{- end }} - {{- end }} - -networks: - uplink: - internal: false - email: - internal: false - frontend: - internal: true - data: - internal: true - - -{{- if not (checkFlag .DisablePostgres) }} - -volumes: - postgres-data: -{{- end }} - -secrets: - auth_token_key: - file: ./secrets/auth_token_key - auth_cookie_key: - file: ./secrets/auth_cookie_key - superadmin: - file: ./secrets/superadmin - manage_auth_password: - file: ./secrets/manage_auth_password - internal_auth_password: - file: ./secrets/internal_auth_password - postgres_password: - file: ./secrets/postgres_password -{{- if checkFlag $.EnableLocalHTTPS }} - cert_crt: - file: ./secrets/cert_crt - cert_key: - file: ./secrets/cert_key -{{- end }} diff --git a/pkg/config/templates/kubernetes/README.md b/pkg/config/templates/kubernetes/README.md index 11f9722..7041350 100644 --- a/pkg/config/templates/kubernetes/README.md +++ b/pkg/config/templates/kubernetes/README.md @@ -1,3 +1,3 @@ # Running OpenSlides using Kubernetes -TODO: Add a nice help text here. +This template is not implemented yet. TODO: Do it. diff --git a/pkg/setup/setup.go b/pkg/setup/setup.go index 486e640..b36c905 100644 --- a/pkg/setup/setup.go +++ b/pkg/setup/setup.go @@ -61,13 +61,16 @@ func Cmd() *cobra.Command { } force := cmd.Flags().BoolP("force", "f", false, "do not skip existing files but overwrite them") - tech := config.FlagTech(cmd) + builtinTemplate := config.FlagBuiltinTemplate(cmd) tplFileOrDirName := config.FlagTpl(cmd) configFileNames := config.FlagConfig(cmd) cmd.RunE = func(cmd *cobra.Command, args []string) error { + if *tplFileOrDirName != "" && *builtinTemplate != config.BuiltinTemplateDefault { + return fmt.Errorf("flag --builtin-template must not be used together with flag --template") + } dir := args[0] - if err := Setup(dir, *force, *tech, *tplFileOrDirName, *configFileNames); err != nil { + if err := Setup(dir, *force, *builtinTemplate, *tplFileOrDirName, *configFileNames); err != nil { return fmt.Errorf("running Setup(): %w", err) } return nil