From 9455b259aed0c9b5b071e9e2cf1385c65c8bdbe4 Mon Sep 17 00:00:00 2001 From: tuunit Date: Fri, 1 Dec 2023 17:19:05 +0100 Subject: [PATCH 1/7] basic templating logic --- cmd/template.go | 46 ++++++++++++++++ internal/compose/compose.go | 14 +++++ internal/compose/helm.go | 106 ++++++++++++++++++++++++++++++++++++ plugin.yaml | 2 +- 4 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 cmd/template.go diff --git a/cmd/template.go b/cmd/template.go new file mode 100644 index 0000000..95ac43a --- /dev/null +++ b/cmd/template.go @@ -0,0 +1,46 @@ +/* +Copyright © 2023 The Helm Compose Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package cmd + +import ( + "github.com/seacrew/helm-compose/internal/compose" + "github.com/seacrew/helm-compose/internal/config" + "github.com/spf13/cobra" +) + +var templateCmd = &cobra.Command{ + Use: "template", + Short: "Render templates for all releases locally and display the output.", + Long: ``, + RunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + + if err := compose.CompatibleHelmVersion(); err != nil { + return err + } + + config, err := config.ParseComposeFile(composeFile) + if err != nil { + return err + } + + return compose.Template(config) + }, +} + +func init() { + rootCmd.AddCommand(templateCmd) +} diff --git a/internal/compose/compose.go b/internal/compose/compose.go index 498d081..d3c96c4 100644 --- a/internal/compose/compose.go +++ b/internal/compose/compose.go @@ -126,3 +126,17 @@ func GetRevision(rev int, config *cfg.Config) error { return nil } + +func Template(config *cfg.Config) error { + for name, url := range config.Repositories { + if err := addHelmRepository(name, url); err != nil { + return err + } + } + + for name, release := range config.Releases { + templateHelmRelease(name, &release) + } + + return nil +} diff --git a/internal/compose/helm.go b/internal/compose/helm.go index 2cad205..047d82a 100644 --- a/internal/compose/helm.go +++ b/internal/compose/helm.go @@ -233,3 +233,109 @@ func helmExec(name string, args []string) { cp.Printf(err.Error()) } } + +func templateHelmRelease(name string, release *cfg.Release) { + var args []string + + args = append(args, "template") + + if release.ChartVersion != "" { + args = append(args, fmt.Sprintf("--version=%s", release.ChartVersion)) + } + + if release.Namespace != "" { + args = append(args, fmt.Sprintf("--namespace=%s", release.Namespace)) + } + + if release.ForceUpdate { + args = append(args, "--force") + } + + if release.HistoryMax < 0 { + args = append(args, fmt.Sprintf("--history-max=%d", 0)) + } else if release.HistoryMax > 0 { + args = append(args, fmt.Sprintf("--history-max=%d", release.HistoryMax)) + } + + if release.CreateNamespace { + args = append(args, "--create-namespace") + } + + if release.CleanUpOnFail { + args = append(args, "--cleanup-on-fail") + } + + if release.DependencyUpdate { + args = append(args, "--dependency-update") + } + + if release.SkipTLSVerify { + args = append(args, "--insecure-skip-tls-verify") + } + + if release.SkipCRDs { + args = append(args, "--skip-crds") + } + + if release.PostRenderer != "" { + args = append(args, fmt.Sprintf("--post-renderer=%s", release.PostRenderer)) + } + + if len(release.PostRendererArgs) > 0 { + args = append(args, fmt.Sprintf("--post-renderer-args=[%s]", strings.Join(release.PostRendererArgs, ","))) + } + + if release.CAFile != "" { + args = append(args, fmt.Sprintf("--ca-file=%s", release.CAFile)) + } + + if release.CertFile != "" { + args = append(args, fmt.Sprintf("--cert-file=%s", release.CertFile)) + } + + if release.KeyFile != "" { + args = append(args, fmt.Sprintf("--key-file=%s", release.KeyFile)) + } + + if release.Timeout != "" { + args = append(args, fmt.Sprintf("--timeout=%s", release.Timeout)) + } + + if release.Wait { + args = append(args, "--wait") + } + + if release.KubeConfig != "" { + args = append(args, fmt.Sprintf("--kubeconfig=%s", release.KubeConfig)) + } + + if release.KubeContext != "" { + args = append(args, fmt.Sprintf("--kube-context=%s", release.KubeContext)) + } + + for _, file := range release.ValueFiles { + args = append(args, fmt.Sprintf("--values=%s", file)) + } + + var jsonValues []string + for key := range release.Values { + data := util.ConvertJson(release.Values[key]) + values, err := json.Marshal(data) + if err != nil { + cp := util.NewColorPrinter(name) + cp.Printf("%s |\t\t%s", name, err) + return + } + + jsonValues = append(jsonValues, fmt.Sprintf("%s=%s", key, values)) + } + + if len(jsonValues) > 0 { + args = append(args, fmt.Sprintf("--set-json=%s", strings.Join(jsonValues, ","))) + } + + args = append(args, name) + args = append(args, release.Chart) + + helmExec(name, args) +} diff --git a/plugin.yaml b/plugin.yaml index 0b06294..45cbbad 100644 --- a/plugin.yaml +++ b/plugin.yaml @@ -1,5 +1,5 @@ name: compose -version: 1.2.0 +version: 1.3.0 usage: Compose is a helm plugin to define and manage multiple helm releases as single entity. command: $HELM_PLUGIN_DIR/bin/compose hooks: From 14298a762562ccdf2da2e28ef3d214dc148eb86a Mon Sep 17 00:00:00 2001 From: tuunit Date: Fri, 1 Dec 2023 17:38:39 +0100 Subject: [PATCH 2/7] reduce code duplication --- internal/compose/helm.go | 141 +++++++++------------------------------ 1 file changed, 32 insertions(+), 109 deletions(-) diff --git a/internal/compose/helm.go b/internal/compose/helm.go index 047d82a..0f8e289 100644 --- a/internal/compose/helm.go +++ b/internal/compose/helm.go @@ -36,25 +36,33 @@ var ( minVersion = semver.MustParse("v3.0.0") ) +type HelmCommand string + +const ( + HelmInstall HelmCommand = "upgrade" + HelmUninstall HelmCommand = "uninstall" + HelmTemplate HelmCommand = "template" +) + func CompatibleHelmVersion() error { cmd := exec.Command(helm, "version") util.DebugPrint("Executing %s", strings.Join(cmd.Args, " ")) output, err := cmd.CombinedOutput() if err != nil { - return fmt.Errorf("Failed to run `%s version`: %v", os.Getenv("HELM_BIN"), err) + return fmt.Errorf("failed to run `%s version`: %v", os.Getenv("HELM_BIN"), err) } versionOutput := string(output) matches := versionRE.FindStringSubmatch(versionOutput) if matches == nil { - return fmt.Errorf("Failed to find version in output %#v", versionOutput) + return fmt.Errorf("failed to find version in output %#v", versionOutput) } helmVersion, err := semver.NewVersion(matches[1]) if err != nil { - return fmt.Errorf("Failed to parse version %#v: %v", matches[1], err) + return fmt.Errorf("failed to parse version %#v: %v", matches[1], err) } if minVersion.GreaterThan(helmVersion) { @@ -74,109 +82,12 @@ func addHelmRepository(name string, url string) error { } func installHelmRelease(name string, release *cfg.Release) { - var args []string - - args = append(args, "upgrade") - args = append(args, "--install") - - if release.ChartVersion != "" { - args = append(args, fmt.Sprintf("--version=%s", release.ChartVersion)) - } - - if release.Namespace != "" { - args = append(args, fmt.Sprintf("--namespace=%s", release.Namespace)) - } - - if release.ForceUpdate { - args = append(args, "--force") - } - - if release.HistoryMax < 0 { - args = append(args, fmt.Sprintf("--history-max=%d", 0)) - } else if release.HistoryMax > 0 { - args = append(args, fmt.Sprintf("--history-max=%d", release.HistoryMax)) - } - - if release.CreateNamespace { - args = append(args, "--create-namespace") - } - - if release.CleanUpOnFail { - args = append(args, "--cleanup-on-fail") - } - - if release.DependencyUpdate { - args = append(args, "--dependency-update") - } - - if release.SkipTLSVerify { - args = append(args, "--insecure-skip-tls-verify") - } - - if release.SkipCRDs { - args = append(args, "--skip-crds") - } - - if release.PostRenderer != "" { - args = append(args, fmt.Sprintf("--post-renderer=%s", release.PostRenderer)) - } - - if len(release.PostRendererArgs) > 0 { - args = append(args, fmt.Sprintf("--post-renderer-args=[%s]", strings.Join(release.PostRendererArgs, ","))) - } - - if release.CAFile != "" { - args = append(args, fmt.Sprintf("--ca-file=%s", release.CAFile)) - } - - if release.CertFile != "" { - args = append(args, fmt.Sprintf("--cert-file=%s", release.CertFile)) - } - - if release.KeyFile != "" { - args = append(args, fmt.Sprintf("--key-file=%s", release.KeyFile)) - } - - if release.Timeout != "" { - args = append(args, fmt.Sprintf("--timeout=%s", release.Timeout)) - } - - if release.Wait { - args = append(args, "--wait") - } - - if release.KubeConfig != "" { - args = append(args, fmt.Sprintf("--kubeconfig=%s", release.KubeConfig)) - } - - if release.KubeContext != "" { - args = append(args, fmt.Sprintf("--kube-context=%s", release.KubeContext)) - } - - for _, file := range release.ValueFiles { - args = append(args, fmt.Sprintf("--values=%s", file)) - } - - var jsonValues []string - for key := range release.Values { - data := util.ConvertJson(release.Values[key]) - values, err := json.Marshal(data) - if err != nil { - cp := util.NewColorPrinter(name) - cp.Printf("%s |\t\t%s", name, err) - return - } - - jsonValues = append(jsonValues, fmt.Sprintf("%s=%s", key, values)) - } - - if len(jsonValues) > 0 { - args = append(args, fmt.Sprintf("--set-json=%s", strings.Join(jsonValues, ","))) + args, err := createHelmArguments(HelmInstall, name, release) + if err != nil { + cp := util.NewColorPrinter(name) + cp.Printf("%s |\t\t%s", name, err) } - args = append(args, name) - args = append(args, release.Chart) - helmExec(name, args) } @@ -235,9 +146,23 @@ func helmExec(name string, args []string) { } func templateHelmRelease(name string, release *cfg.Release) { + args, err := createHelmArguments(HelmTemplate, name, release) + if err != nil { + cp := util.NewColorPrinter(name) + cp.Printf("%s |\t\t%s", name, err) + } + + helmExec(name, args) +} + +func createHelmArguments(command HelmCommand, name string, release *cfg.Release) ([]string, error) { var args []string - args = append(args, "template") + args = append(args, string(command)) + + if command == HelmInstall { + args = append(args, "--install") + } if release.ChartVersion != "" { args = append(args, fmt.Sprintf("--version=%s", release.ChartVersion)) @@ -322,9 +247,7 @@ func templateHelmRelease(name string, release *cfg.Release) { data := util.ConvertJson(release.Values[key]) values, err := json.Marshal(data) if err != nil { - cp := util.NewColorPrinter(name) - cp.Printf("%s |\t\t%s", name, err) - return + return nil, err } jsonValues = append(jsonValues, fmt.Sprintf("%s=%s", key, values)) @@ -337,5 +260,5 @@ func templateHelmRelease(name string, release *cfg.Release) { args = append(args, name) args = append(args, release.Chart) - helmExec(name, args) + return args, nil } From 06e6ac1b240a07b3d50e92c61b908c1895c91209 Mon Sep 17 00:00:00 2001 From: tuunit Date: Sun, 3 Dec 2023 18:01:03 +0100 Subject: [PATCH 3/7] add docs for templating --- docs/commands/template.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 docs/commands/template.md diff --git a/docs/commands/template.md b/docs/commands/template.md new file mode 100644 index 0000000..8696ebd --- /dev/null +++ b/docs/commands/template.md @@ -0,0 +1,21 @@ +# helm compose template + +Template all kubernetes resources for the releases specified in your `helm-compose.yaml` and print them out to stdout. + +## Usage + +The following command will print out all kubernetes resources that would be installed or upgraded on stdout. + +``` +helm compose template [releases...] [flags] +``` + +## Options + +``` +Flags: + -h, --help help for template + +Global Flags: + -f, --file string Compose configuration file +``` From afaa5ed57bbe16ff13acc105984ceac715062bdc Mon Sep 17 00:00:00 2001 From: tuunit Date: Sun, 3 Dec 2023 18:01:30 +0100 Subject: [PATCH 4/7] slight refactoring and add release list handling --- cmd/template.go | 4 +-- internal/compose/compose.go | 13 ++++++-- internal/compose/helm.go | 62 ++++++++++++++++++------------------- 3 files changed, 44 insertions(+), 35 deletions(-) diff --git a/cmd/template.go b/cmd/template.go index 95ac43a..1c755b9 100644 --- a/cmd/template.go +++ b/cmd/template.go @@ -22,7 +22,7 @@ import ( ) var templateCmd = &cobra.Command{ - Use: "template", + Use: "template [RELEASES...]", Short: "Render templates for all releases locally and display the output.", Long: ``, RunE: func(cmd *cobra.Command, args []string) error { @@ -37,7 +37,7 @@ var templateCmd = &cobra.Command{ return err } - return compose.Template(config) + return compose.Template(config, args) }, } diff --git a/internal/compose/compose.go b/internal/compose/compose.go index d3c96c4..0f7b8f2 100644 --- a/internal/compose/compose.go +++ b/internal/compose/compose.go @@ -127,7 +127,7 @@ func GetRevision(rev int, config *cfg.Config) error { return nil } -func Template(config *cfg.Config) error { +func Template(config *cfg.Config, releases []string) error { for name, url := range config.Repositories { if err := addHelmRepository(name, url); err != nil { return err @@ -135,7 +135,16 @@ func Template(config *cfg.Config) error { } for name, release := range config.Releases { - templateHelmRelease(name, &release) + if len(releases) == 0 { + templateHelmRelease(name, &release) + continue + } + + for _, rel := range releases { + if rel == name { + templateHelmRelease(name, &release) + } + } } return nil diff --git a/internal/compose/helm.go b/internal/compose/helm.go index 0f8e289..2c5beb5 100644 --- a/internal/compose/helm.go +++ b/internal/compose/helm.go @@ -39,9 +39,9 @@ var ( type HelmCommand string const ( - HelmInstall HelmCommand = "upgrade" - HelmUninstall HelmCommand = "uninstall" - HelmTemplate HelmCommand = "template" + HELM_UPGRADE HelmCommand = "upgrade" + HELM_UNINSTALL HelmCommand = "uninstall" + HELM_TEMPLATE HelmCommand = "template" ) func CompatibleHelmVersion() error { @@ -82,7 +82,17 @@ func addHelmRepository(name string, url string) error { } func installHelmRelease(name string, release *cfg.Release) { - args, err := createHelmArguments(HelmInstall, name, release) + args, err := createHelmArguments(HELM_UPGRADE, name, release) + if err != nil { + cp := util.NewColorPrinter(name) + cp.Printf("%s |\t\t%s", name, err) + } + + helmExec(name, args) +} + +func templateHelmRelease(name string, release *cfg.Release) { + args, err := createHelmArguments(HELM_TEMPLATE, name, release) if err != nil { cp := util.NewColorPrinter(name) cp.Printf("%s |\t\t%s", name, err) @@ -129,38 +139,12 @@ func uninstallHelmRelease(name string, release *cfg.Release) { helmExec(name, args) } -func helmExec(name string, args []string) { - cp := util.NewColorPrinter(name) - output, _ := util.Execute(helm, args...) - - scanner := bufio.NewScanner(strings.NewReader(output)) - for scanner.Scan() { - cp.Printf("%s |\t\t%s", name, scanner.Text()) - } - - err := scanner.Err() - - if err != nil { - cp.Printf(err.Error()) - } -} - -func templateHelmRelease(name string, release *cfg.Release) { - args, err := createHelmArguments(HelmTemplate, name, release) - if err != nil { - cp := util.NewColorPrinter(name) - cp.Printf("%s |\t\t%s", name, err) - } - - helmExec(name, args) -} - func createHelmArguments(command HelmCommand, name string, release *cfg.Release) ([]string, error) { var args []string args = append(args, string(command)) - if command == HelmInstall { + if command == HELM_UPGRADE { args = append(args, "--install") } @@ -262,3 +246,19 @@ func createHelmArguments(command HelmCommand, name string, release *cfg.Release) return args, nil } + +func helmExec(name string, args []string) { + cp := util.NewColorPrinter(name) + output, _ := util.Execute(helm, args...) + + scanner := bufio.NewScanner(strings.NewReader(output)) + for scanner.Scan() { + cp.Printf("%s |\t\t%s", name, scanner.Text()) + } + + err := scanner.Err() + + if err != nil { + cp.Printf(err.Error()) + } +} From 64fc86f4353cde6436db35f82f7e32ada76e2136 Mon Sep 17 00:00:00 2001 From: tuunit Date: Sun, 3 Dec 2023 18:26:07 +0100 Subject: [PATCH 5/7] remove color and name printing based on release for template command --- internal/compose/compose.go | 3 +++ internal/compose/helm.go | 10 +++++++--- internal/util/colors.go | 9 ++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/internal/compose/compose.go b/internal/compose/compose.go index 0f7b8f2..a2a7573 100644 --- a/internal/compose/compose.go +++ b/internal/compose/compose.go @@ -21,6 +21,7 @@ import ( cfg "github.com/seacrew/helm-compose/internal/config" prov "github.com/seacrew/helm-compose/internal/provider" + "github.com/seacrew/helm-compose/internal/util" ) func RunUp(config *cfg.Config) error { @@ -128,6 +129,8 @@ func GetRevision(rev int, config *cfg.Config) error { } func Template(config *cfg.Config, releases []string) error { + util.PrintColors = false + for name, url := range config.Repositories { if err := addHelmRepository(name, url); err != nil { return err diff --git a/internal/compose/helm.go b/internal/compose/helm.go index 2c5beb5..a4bee3e 100644 --- a/internal/compose/helm.go +++ b/internal/compose/helm.go @@ -95,10 +95,10 @@ func templateHelmRelease(name string, release *cfg.Release) { args, err := createHelmArguments(HELM_TEMPLATE, name, release) if err != nil { cp := util.NewColorPrinter(name) - cp.Printf("%s |\t\t%s", name, err) + cp.Printf("# %s |\t\t%s", name, err) } - helmExec(name, args) + helmExec("", args) } func uninstallHelmRelease(name string, release *cfg.Release) { @@ -253,7 +253,11 @@ func helmExec(name string, args []string) { scanner := bufio.NewScanner(strings.NewReader(output)) for scanner.Scan() { - cp.Printf("%s |\t\t%s", name, scanner.Text()) + if len(name) == 0 { + fmt.Printf("%s\n", scanner.Text()) + } else { + cp.Printf("%s |\t\t%s", name, scanner.Text()) + } } err := scanner.Err() diff --git a/internal/util/colors.go b/internal/util/colors.go index 820458c..7bc900f 100644 --- a/internal/util/colors.go +++ b/internal/util/colors.go @@ -27,6 +27,8 @@ type ColorPrinter struct { colorFunc func(...interface{}) string } +var PrintColors = true + func NewColorPrinter(s string) *ColorPrinter { c := hashColor(s) return &ColorPrinter{ @@ -35,7 +37,12 @@ func NewColorPrinter(s string) *ColorPrinter { } func (c ColorPrinter) Printf(format string, a ...any) { - fmt.Printf(c.colorFunc(format)+"\n", a...) + if PrintColors { + fmt.Printf(c.colorFunc(format)+"\n", a...) + return + } + + fmt.Printf(format+"\n", a...) } var colorFuncs [](func(...interface{}) string) = [](func(...interface{}) string){ From 11042b7d232f71e53bd5b0ad5ee30ca52ebba112 Mon Sep 17 00:00:00 2001 From: tuunit Date: Mon, 4 Dec 2023 21:32:52 +0000 Subject: [PATCH 6/7] slightly change the use descriptioN --- cmd/template.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/template.go b/cmd/template.go index 1c755b9..7a3613e 100644 --- a/cmd/template.go +++ b/cmd/template.go @@ -22,7 +22,7 @@ import ( ) var templateCmd = &cobra.Command{ - Use: "template [RELEASES...]", + Use: "template [RELEASE ...]", Short: "Render templates for all releases locally and display the output.", Long: ``, RunE: func(cmd *cobra.Command, args []string) error { From 1a4a96582e458c61867322b9d9ad935c4cbf3a36 Mon Sep 17 00:00:00 2001 From: tuunit Date: Mon, 4 Dec 2023 21:35:09 +0000 Subject: [PATCH 7/7] add changelog entry --- CHANGELOG.md | 2 ++ plugin.yaml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 227c69a..1bdae28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ # Changes since 1.2.0 +- feat: add templating command + # 1.2.0 - feat: add wait option - feat: apiVersion 1.1 for wait option and add better version handling diff --git a/plugin.yaml b/plugin.yaml index 45cbbad..0b06294 100644 --- a/plugin.yaml +++ b/plugin.yaml @@ -1,5 +1,5 @@ name: compose -version: 1.3.0 +version: 1.2.0 usage: Compose is a helm plugin to define and manage multiple helm releases as single entity. command: $HELM_PLUGIN_DIR/bin/compose hooks: