From 2bdcbb17d0c222de770a00eb27876d1333b7d857 Mon Sep 17 00:00:00 2001 From: James Rawlings Date: Fri, 24 Jan 2020 14:48:05 +0000 Subject: [PATCH] feat: experiment, adds a command to generate a helmfile.yaml from a jx-applications.yml this is an experimenal feature which uses a seperate jx-apps.yml file to incorporate adding apps to a boot install. relates to #6442 Signed-off-by: James Rawlings --- .secrets.baseline | 2 +- docs/contributing/experiments.md | 18 +- pkg/cmd/cmd.go | 1 + pkg/cmd/create/create.go | 2 + pkg/cmd/create/helmfile/create_helmfile.go | 185 ++++++++++++++ .../create/helmfile/create_helmfile_test.go | 130 ++++++++++ .../extra-values/apps/velero/values.yaml | 1 + .../test_data/extra-values/jx-apps.yml | 5 + pkg/cmd/create/helmfile/test_data/jx-apps.yml | 14 ++ pkg/cmd/opts/common.go | 13 +- pkg/config/install_applications.go | 70 ++++++ pkg/config/install_applications_test.go | 17 ++ pkg/config/test_data/jx-apps.yml | 14 ++ pkg/helm/helm_cli.go | 12 +- pkg/helmfile/helmfile.go | 237 ++++++++++++++++++ 15 files changed, 712 insertions(+), 9 deletions(-) create mode 100644 pkg/cmd/create/helmfile/create_helmfile.go create mode 100644 pkg/cmd/create/helmfile/create_helmfile_test.go create mode 100644 pkg/cmd/create/helmfile/test_data/extra-values/apps/velero/values.yaml create mode 100644 pkg/cmd/create/helmfile/test_data/extra-values/jx-apps.yml create mode 100644 pkg/cmd/create/helmfile/test_data/jx-apps.yml create mode 100644 pkg/config/install_applications.go create mode 100644 pkg/config/install_applications_test.go create mode 100644 pkg/config/test_data/jx-apps.yml create mode 100644 pkg/helmfile/helmfile.go diff --git a/.secrets.baseline b/.secrets.baseline index ae93a42da8..8f79a6a921 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "^pkg/jenkins/test_data/update_center.json.*$|^.secrets.baseline$|^.*test.*$", "lines": null }, - "generated_at": "2020-01-20T14:45:13Z", + "generated_at": "2020-01-25T11:13:56Z", "plugins_used": [ { "name": "AWSKeyDetector" diff --git a/docs/contributing/experiments.md b/docs/contributing/experiments.md index efd9db9fc3..04b83760c9 100644 --- a/docs/contributing/experiments.md +++ b/docs/contributing/experiments.md @@ -3,12 +3,20 @@ This page contains a list of experiments that are being worked on and how to enable them. This list will be maintained and it will likely change how features are enabled as they mature past an experiment. +Experiments tend to go hand in hand with the Jenkins X enhancement process of which more details can be found in the git +repository [https://github.com/jenkins-x/enhancements](https://github.com/jenkins-x/enhancements) + +# Current Experiments + ## Helmfile +https://github.com/jenkins-x/enhancements/pull/1 + We are experimenting with [Helmfile](https://github.com/roboll/helmfile) to see if we can make the `jx boot` implementation -a bit more modular and leverage some of the extra fetures the OSS project has. To enable this feature set the `JX_HELMFILE` -environment variable: +a bit more modular and leverage some of the extra fetures the OSS project has. To enable this feature set a top level +jx requirements value: + +```yaml +helmfile: true +``` -```bash -export JX_HELMFILE=true -``` \ No newline at end of file diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index fb0a0ab3a7..14d167b650 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -91,6 +91,7 @@ func NewJXCommand(f clients.Factory, in terminal.FileReader, out terminal.FileWr addCommands := add.NewCmdAdd(commonOpts) createCommands := create.NewCmdCreate(commonOpts) deleteCommands := deletecmd.NewCmdDelete(commonOpts) + getCommands := get.NewCmdGet(commonOpts) editCommands := edit.NewCmdEdit(commonOpts) updateCommands := update.NewCmdUpdate(commonOpts) diff --git a/pkg/cmd/create/create.go b/pkg/cmd/create/create.go index fd16226f7a..468a339f2a 100644 --- a/pkg/cmd/create/create.go +++ b/pkg/cmd/create/create.go @@ -1,6 +1,7 @@ package create import ( + "github.com/jenkins-x/jx/pkg/cmd/create/helmfile" "github.com/jenkins-x/jx/pkg/cmd/create/options" "github.com/jenkins-x/jx/pkg/cmd/create/vault" "github.com/jenkins-x/jx/pkg/cmd/helper" @@ -70,6 +71,7 @@ func NewCmdCreate(commonOpts *opts.CommonOptions) *cobra.Command { cmd.AddCommand(NewCmdCreateEtcHosts(commonOpts)) cmd.AddCommand(NewCmdCreateGkeServiceAccount(commonOpts)) cmd.AddCommand(NewCmdCreateGit(commonOpts)) + cmd.AddCommand(helmfile.NewCmdCreateHelmfile(commonOpts)) cmd.AddCommand(NewCmdCreateIssue(commonOpts)) cmd.AddCommand(NewCmdCreateJenkins(commonOpts)) cmd.AddCommand(NewCmdCreateJHipster(commonOpts)) diff --git a/pkg/cmd/create/helmfile/create_helmfile.go b/pkg/cmd/create/helmfile/create_helmfile.go new file mode 100644 index 0000000000..afbade854a --- /dev/null +++ b/pkg/cmd/create/helmfile/create_helmfile.go @@ -0,0 +1,185 @@ +package helmfile + +import ( + "fmt" + "io/ioutil" + "net/url" + "path" + + "github.com/jenkins-x/jx/pkg/config" + helmfile2 "github.com/jenkins-x/jx/pkg/helmfile" + + "github.com/google/uuid" + "github.com/jenkins-x/jx/pkg/util" + + "github.com/ghodss/yaml" + + "github.com/jenkins-x/jx/pkg/cmd/create/options" + "github.com/jenkins-x/jx/pkg/cmd/helper" + "github.com/jenkins-x/jx/pkg/cmd/opts" + "github.com/jenkins-x/jx/pkg/cmd/templates" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +const ( + helmfile = "helmfile.yaml" +) + +var ( + createHelmfileLong = templates.LongDesc(` + ** EXPERIMENTAL COMMAND ** + + Creates a new helmfile.yaml from a jx-apps.yaml +`) + + createHelmfileExample = templates.Examples(` + ** EXPERIMENTAL COMMAND ** + + # Create a new helmfile.yaml from a jx-apps.yaml + jx create helmfile + `) +) + +// CreateHelmfileOptions the options for the create helmfile command +type CreateHelmfileOptions struct { + options.CreateOptions + outputDir string + dir string + valueFiles []string +} + +// NewCmdCreateHelmfile creates a command object for the "create" command +func NewCmdCreateHelmfile(commonOpts *opts.CommonOptions) *cobra.Command { + o := &CreateHelmfileOptions{ + CreateOptions: options.CreateOptions{ + CommonOptions: commonOpts, + }, + } + + cmd := &cobra.Command{ + Use: "helmfile", + Short: "Create a new helmfile", + Long: createHelmfileLong, + Example: createHelmfileExample, + Run: func(cmd *cobra.Command, args []string) { + o.Cmd = cmd + o.Args = args + err := o.Run() + helper.CheckErr(err) + }, + } + cmd.Flags().StringVarP(&o.dir, "dir", "", ".", "the directory to look for a 'jx-apps.yml' file") + cmd.Flags().StringVarP(&o.outputDir, "outputDir", "", "", "The directory to write the helmfile.yaml file") + cmd.Flags().StringArrayVarP(&o.valueFiles, "values", "", []string{""}, "specify values in a YAML file or a URL(can specify multiple)") + + return cmd +} + +// Run implements the command +func (o *CreateHelmfileOptions) Run() error { + + apps, err := config.LoadApplicationsConfig(o.dir) + if err != nil { + return errors.Wrap(err, "failed to load applications") + } + + helm := o.Helm() + localHelmRepos, err := helm.ListRepos() + if err != nil { + return errors.Wrap(err, "failed listing helm repos") + } + + // contains the repo url and name to reference it by in the release spec + // use a map to dedupe repositories + repos := make(map[string]string) + for _, app := range apps.Applications { + _, err = url.ParseRequestURI(app.Repository) + if err != nil { + // if the repository isn't a valid URL lets just use whatever was supplied in the application repository field, probably it is a directory path + repos[app.Repository] = app.Repository + } else { + matched := false + // check if URL matches a repo in helms local list + for key, value := range localHelmRepos { + if app.Repository == value { + repos[app.Repository] = key + matched = true + } + } + if !matched { + repos[app.Repository] = uuid.New().String() + } + } + } + + var repositories []helmfile2.RepositorySpec + for repoURL, name := range repos { + _, err = url.ParseRequestURI(repoURL) + // skip non URLs as they're probably local directories which don't need to be in the helmfile.repository section + if err == nil { + repository := helmfile2.RepositorySpec{ + Name: name, + URL: repoURL, + } + repositories = append(repositories, repository) + } + + } + + var releases []helmfile2.ReleaseSpec + for _, app := range apps.Applications { + if app.Namespace == "" { + app.Namespace = apps.DefaultNamespace + } + + // check if a local directory and values file exists for the app + extraValuesFiles := o.valueFiles + extraValuesFiles = o.addExtraAppValues(app, extraValuesFiles, "values.yaml") + extraValuesFiles = o.addExtraAppValues(app, extraValuesFiles, "values.yaml.gotmpl") + + chartName := fmt.Sprintf("%s/%s", repos[app.Repository], app.Name) + release := helmfile2.ReleaseSpec{ + Name: app.Name, + Namespace: app.Namespace, + Chart: chartName, + Values: extraValuesFiles, + } + releases = append(releases, release) + } + + h := helmfile2.HelmState{ + Bases: []string{"../environments.yaml"}, + HelmDefaults: helmfile2.HelmSpec{ + Atomic: true, + Verify: false, + Wait: true, + Timeout: 180, + // need Force to be false https://github.com/helm/helm/issues/6378 + Force: false, + }, + Repositories: repositories, + Releases: releases, + } + + data, err := yaml.Marshal(h) + if err != nil { + return err + } + + err = ioutil.WriteFile(path.Join(o.outputDir, helmfile), data, util.DefaultWritePermissions) + if err != nil { + return errors.Wrapf(err, "failed to save file %s", helmfile) + } + + return nil +} + +func (o *CreateHelmfileOptions) addExtraAppValues(app config.Application, newValuesFiles []string, valuesFilename string) []string { + fileName := path.Join(o.dir, "apps", app.Name, valuesFilename) + exists, _ := util.FileExists(fileName) + if exists { + newValuesFiles = append(newValuesFiles, path.Join(app.Name, valuesFilename)) + } + return newValuesFiles +} diff --git a/pkg/cmd/create/helmfile/create_helmfile_test.go b/pkg/cmd/create/helmfile/create_helmfile_test.go new file mode 100644 index 0000000000..a8c840a059 --- /dev/null +++ b/pkg/cmd/create/helmfile/create_helmfile_test.go @@ -0,0 +1,130 @@ +package helmfile + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + "testing" + + helmfile2 "github.com/jenkins-x/jx/pkg/helmfile" + + "github.com/jenkins-x/jx/pkg/cmd/create/options" + "github.com/jenkins-x/jx/pkg/cmd/opts" + helm_test "github.com/jenkins-x/jx/pkg/helm/mocks" + + "github.com/jenkins-x/jx/pkg/util" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v2" +) + +func TestDedupeRepositories(t *testing.T) { + tempDir, err := ioutil.TempDir("", "test-applications-config") + assert.NoError(t, err, "should create a temporary config dir") + + o := &CreateHelmfileOptions{ + outputDir: tempDir, + dir: "test_data", + CreateOptions: *getCreateOptions(), + } + err = o.Run() + assert.NoError(t, err) + + h, err := loadHelmfile(tempDir) + assert.NoError(t, err) + + // assert there are 3 repos and not 4 as one of them in the jx-applications.yaml is a duplicate + assert.Equal(t, 3, len(h.Repositories)) + +} + +func TestExtraAppValues(t *testing.T) { + tempDir, err := ioutil.TempDir("", "test-applications-config") + assert.NoError(t, err, "should create a temporary config dir") + + o := &CreateHelmfileOptions{ + outputDir: tempDir, + dir: path.Join("test_data", "extra-values"), + CreateOptions: *getCreateOptions(), + } + err = o.Run() + assert.NoError(t, err) + + h, err := loadHelmfile(tempDir) + assert.NoError(t, err) + + // assert we added the local values.yaml for the velero app + assert.Equal(t, "velero/values.yaml", h.Releases[0].Values[0]) + +} + +func TestExtraFlagValues(t *testing.T) { + tempDir, err := ioutil.TempDir("", "test-applications-config") + assert.NoError(t, err, "should create a temporary config dir") + + o := &CreateHelmfileOptions{ + outputDir: tempDir, + dir: path.Join("test_data"), + valueFiles: []string{"foo/bar.yaml"}, + CreateOptions: *getCreateOptions(), + } + err = o.Run() + assert.NoError(t, err) + + h, err := loadHelmfile(tempDir) + assert.NoError(t, err) + + // assert we added the values file passed in as a CLI flag + assert.Equal(t, "foo/bar.yaml", h.Releases[0].Values[0]) + +} + +func loadHelmfile(dir string) (*helmfile2.HelmState, error) { + + fileName := helmfile + if dir != "" { + fileName = filepath.Join(dir, helmfile) + } + + exists, err := util.FileExists(fileName) + if err != nil || !exists { + return nil, errors.Errorf("no %s found in directory %s", fileName, dir) + } + + config := &helmfile2.HelmState{} + + data, err := ioutil.ReadFile(fileName) + if err != nil { + return config, fmt.Errorf("Failed to load file %s due to %s", fileName, err) + } + validationErrors, err := util.ValidateYaml(config, data) + if err != nil { + return config, fmt.Errorf("failed to validate YAML file %s due to %s", fileName, err) + } + if len(validationErrors) > 0 { + return config, fmt.Errorf("Validation failures in YAML file %s:\n%s", fileName, strings.Join(validationErrors, "\n")) + } + err = yaml.Unmarshal(data, config) + if err != nil { + return config, fmt.Errorf("Failed to unmarshal YAML file %s due to %s", fileName, err) + } + + return config, err +} + +func getCreateOptions() *options.CreateOptions { + + helmer := helm_test.NewMockHelmer() + co := &opts.CommonOptions{ + In: os.Stdin, + Out: os.Stdout, + Err: os.Stderr, + } + co.SetHelm(helmer) + return &options.CreateOptions{ + CommonOptions: co, + } +} diff --git a/pkg/cmd/create/helmfile/test_data/extra-values/apps/velero/values.yaml b/pkg/cmd/create/helmfile/test_data/extra-values/apps/velero/values.yaml new file mode 100644 index 0000000000..7daacd5db8 --- /dev/null +++ b/pkg/cmd/create/helmfile/test_data/extra-values/apps/velero/values.yaml @@ -0,0 +1 @@ +foo: bar \ No newline at end of file diff --git a/pkg/cmd/create/helmfile/test_data/extra-values/jx-apps.yml b/pkg/cmd/create/helmfile/test_data/extra-values/jx-apps.yml new file mode 100644 index 0000000000..b7fedb6937 --- /dev/null +++ b/pkg/cmd/create/helmfile/test_data/extra-values/jx-apps.yml @@ -0,0 +1,5 @@ +defaultNamespace: jx +applications: +- name: velero + repository: https://kubernetes-charts.storage.googleapis.com + namespace: velero \ No newline at end of file diff --git a/pkg/cmd/create/helmfile/test_data/jx-apps.yml b/pkg/cmd/create/helmfile/test_data/jx-apps.yml new file mode 100644 index 0000000000..bca5d60095 --- /dev/null +++ b/pkg/cmd/create/helmfile/test_data/jx-apps.yml @@ -0,0 +1,14 @@ +defaultNamespace: jx +applications: +- name: velero + repository: https://kubernetes-charts.storage.googleapis.com + namespace: velero +- name: external-dns + repository: https://kubernetes-charts.storage.googleapis.com + namespace: jx +- name: nexus + repository: https://charts.bitnami.com/bitnami + namespace: jx +- name: cert-manager + repository: https://charts.jetstack.io + namespace: cert-manager diff --git a/pkg/cmd/opts/common.go b/pkg/cmd/opts/common.go index e582aa266f..1183dab3f0 100644 --- a/pkg/cmd/opts/common.go +++ b/pkg/cmd/opts/common.go @@ -8,6 +8,8 @@ import ( "strings" "time" + "github.com/jenkins-x/jx/pkg/config" + "github.com/jenkins-x/jx/pkg/kube/cluster" gojenkins "github.com/jenkins-x/golang-jenkins" @@ -547,7 +549,16 @@ func (o *CommonOptions) Helm() helm.Helmer { // let disable loading/modifying team environments as we typically install on empty k8s clusters o.ModifyEnvironmentFn = o.IgnoreModifyEnvironment o.ModifyDevEnvironmentFn = o.IgnoreModifyDevEnvironment - helmer := o.NewHelm(false, "helm3", true, false) + + // check helmfile featureflag but default to existing behaviour if there's any issues + var helmer helm.Helmer + r, _, _ := config.LoadRequirementsConfig("") + if r.Helmfile { + helmer = o.NewHelm(false, "helm", true, false) + } else { + helmer = o.NewHelm(false, "helm3", true, false) + } + o.SetHelm(helmer) return helmer } diff --git a/pkg/config/install_applications.go b/pkg/config/install_applications.go new file mode 100644 index 0000000000..3113d8676a --- /dev/null +++ b/pkg/config/install_applications.go @@ -0,0 +1,70 @@ +package config + +import ( + "fmt" + "io/ioutil" + "path/filepath" + "strings" + + "github.com/jenkins-x/jx/pkg/util" + "github.com/pkg/errors" + "gopkg.in/yaml.v2" +) + +const ( + // ApplicationsConfigFileName is the name of the applications configuration file + ApplicationsConfigFileName = "jx-apps.yml" +) + +// ApplicationConfig contains applications to install during boot +type ApplicationConfig struct { + // Applications of applications + Applications []Application `json:"applications"` + // DefaultNamespace the default namespace to install applications into + DefaultNamespace string `json:"defaultNamespace"` +} + +// Application is an application to install during boot +type Application struct { + // Name of the application / helm chart + Name string `json:"name"` + // Repository the helm repository + Repository string `json:"repository"` + // Namespace to install the application into + Namespace string `json:"namespace"` +} + +// LoadApplicationsConfig loads the boot applications configuration file +// if there is not a file called `jx-apps.yml` in the given dir we will scan up the parent +// directories looking for the requirements file as we often run 'jx' steps in sub directories. +func LoadApplicationsConfig(dir string) (*ApplicationConfig, error) { + fileName := ApplicationsConfigFileName + if dir != "" { + fileName = filepath.Join(dir, fileName) + } + + exists, err := util.FileExists(fileName) + if err != nil || !exists { + return nil, errors.Errorf("no %s found in directory %s", fileName, dir) + } + + config := &ApplicationConfig{} + + data, err := ioutil.ReadFile(fileName) + if err != nil { + return config, fmt.Errorf("Failed to load file %s due to %s", fileName, err) + } + validationErrors, err := util.ValidateYaml(config, data) + if err != nil { + return config, fmt.Errorf("failed to validate YAML file %s due to %s", fileName, err) + } + if len(validationErrors) > 0 { + return config, fmt.Errorf("Validation failures in YAML file %s:\n%s", fileName, strings.Join(validationErrors, "\n")) + } + err = yaml.Unmarshal(data, config) + if err != nil { + return config, fmt.Errorf("Failed to unmarshal YAML file %s due to %s", fileName, err) + } + + return config, err +} diff --git a/pkg/config/install_applications_test.go b/pkg/config/install_applications_test.go new file mode 100644 index 0000000000..da0a10f8dc --- /dev/null +++ b/pkg/config/install_applications_test.go @@ -0,0 +1,17 @@ +package config + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestJenkinsXAppsUnmarshalling(t *testing.T) { + apps, err := LoadApplicationsConfig(path.Join("test_data")) + assert.NoError(t, err) + + // assert marshalling of a jx-apps.yaml + assert.Equal(t, 4, len(apps.Applications)) + assert.Equal(t, "cert-manager", apps.Applications[3].Namespace) +} diff --git a/pkg/config/test_data/jx-apps.yml b/pkg/config/test_data/jx-apps.yml new file mode 100644 index 0000000000..bca5d60095 --- /dev/null +++ b/pkg/config/test_data/jx-apps.yml @@ -0,0 +1,14 @@ +defaultNamespace: jx +applications: +- name: velero + repository: https://kubernetes-charts.storage.googleapis.com + namespace: velero +- name: external-dns + repository: https://kubernetes-charts.storage.googleapis.com + namespace: jx +- name: nexus + repository: https://charts.bitnami.com/bitnami + namespace: jx +- name: cert-manager + repository: https://charts.jetstack.io + namespace: cert-manager diff --git a/pkg/helm/helm_cli.go b/pkg/helm/helm_cli.go index 016b08f79f..b610a60ec8 100644 --- a/pkg/helm/helm_cli.go +++ b/pkg/helm/helm_cli.go @@ -89,9 +89,17 @@ func (h *HelmCLI) checkCompatibility() { log.Logger().Warnf("Unable to parse the current helm version: %s", version) return } - if v.Major > 2 { - log.Logger().Fatalf("Your current helm version v%d is not supported. Please downgrade to helm v2.", v.Major) + + if os.Getenv("JX_HELM3") == "true" { + if v.Major != 3 { + log.Logger().Fatalf("You have set $JX_HELM3=true but your helm client version is %d", v.Major) + } + } else { + if v.Major > 2 { + log.Logger().Fatalf("Your current helm version v%d is not supported. Please downgrade to helm v2.", v.Major) + } } + } // SetHost is used to point at a locally running tiller diff --git a/pkg/helmfile/helmfile.go b/pkg/helmfile/helmfile.go new file mode 100644 index 0000000000..b2a1151d6b --- /dev/null +++ b/pkg/helmfile/helmfile.go @@ -0,0 +1,237 @@ +package helmfile + +// Originally authored in the roboll/helmfile repo https://github.com/roboll/helmfile/blob/fc75f25293055003d8159a841940313e56a164c6/pkg/state/state.go +// copied here to avoid issues go module dependency issues +// changed from yaml: to json: annotations so we can marshal struct and omit unset values + +// HelmState structure for the helmfile +type HelmState struct { + FilePath string `json:"filePath,omitempty"` + + // DefaultValues is the default values to be overrode by environment values and command-line overrides + DefaultValues []interface{} `json:"values,omitempty"` + + Environments map[string]EnvironmentSpec `json:"environments,omitempty"` + + Bases []string `json:"bases,omitempty"` + HelmDefaults HelmSpec `json:"helmDefaults,omitempty"` + Helmfiles []SubHelmfileSpec `json:"helmfiles,omitempty"` + DeprecatedContext string `json:"context,omitempty"` + DeprecatedReleases []ReleaseSpec `json:"charts,omitempty"` + OverrideNamespace string `json:"namespace,omitempty"` + Repositories []RepositorySpec `json:"repositories,omitempty"` + Releases []ReleaseSpec `json:"releases,omitempty"` + Selectors []string `json:"-"` + APIVersions []string `json:"apiVersions,omitempty"` + + Templates map[string]TemplateSpec `json:"templates,omitempty"` + + Env Environment `json:"-"` +} + +// SubHelmfileSpec defines the subhelmfile path and options +type SubHelmfileSpec struct { + //path or glob pattern for the sub helmfiles + Path string `json:"path,omitempty"` + //chosen selectors for the sub helmfiles + Selectors []string `json:"selectors,omitempty"` + //do the sub helmfiles inherits from parent selectors + SelectorsInherited bool `json:"selectorsInherited,omitempty"` + + Environment SubhelmfileEnvironmentSpec +} + +// SubhelmfileEnvironmentSpec overrides +type SubhelmfileEnvironmentSpec struct { + OverrideValues []interface{} `json:"values,omitempty"` +} + +// HelmSpec to defines helmDefault values +type HelmSpec struct { + KubeContext string `json:"kubeContext,omitempty"` + TillerNamespace string `json:"tillerNamespace,omitempty"` + Tillerless bool `json:"tillerless"` + Args []string `json:"args,omitempty"` + Verify bool `json:"verify"` + // Devel, when set to true, use development versions, too. Equivalent to version '>0.0.0-0' + Devel bool `json:"devel"` + // Wait, if set to true, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful + Wait bool `json:"wait"` + // Timeout is the time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks, and waits on pod/pvc/svc/deployment readiness) (default 300) + Timeout int `json:"timeout"` + // RecreatePods, when set to true, instruct helmfile to perform pods restart for the resource if applicable + RecreatePods bool `json:"recreatePods"` + // Force, when set to true, forces resource update through delete/recreate if needed + Force bool `json:"force"` + // Atomic, when set to true, restore previous state in case of a failed install/upgrade attempt + Atomic bool `json:"atomic"` + // CleanupOnFail, when set to true, the --cleanup-on-fail helm flag is passed to the upgrade command + CleanupOnFail bool `json:"cleanupOnFail,omitempty"` + // HistoryMax, limit the maximum number of revisions saved per release. Use 0 for no limit (default 10) + HistoryMax *int `json:"historyMax,omitempty"` + + TLS bool `json:"tls"` + TLSCACert string `json:"tlsCACert,omitempty"` + TLSKey string `json:"tlsKey,omitempty"` + TLSCert string `json:"tlsCert,omitempty"` +} + +// RepositorySpec that defines values for a helm repo +type RepositorySpec struct { + Name string `json:"name,omitempty"` + URL string `json:"url,omitempty"` + CaFile string `json:"caFile,omitempty"` + CertFile string `json:"certFile,omitempty"` + KeyFile string `json:"keyFile,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` +} + +// ReleaseSpec defines the structure of a helm release +type ReleaseSpec struct { + // Chart is the name of the chart being installed to create this release + Chart string `json:"chart,omitempty"` + Version string `json:"version,omitempty"` + Verify *bool `json:"verify,omitempty"` + // Devel, when set to true, use development versions, too. Equivalent to version '>0.0.0-0' + Devel *bool `json:"devel,omitempty"` + // Wait, if set to true, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful + Wait *bool `json:"wait,omitempty"` + // Timeout is the time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks, and waits on pod/pvc/svc/deployment readiness) (default 300) + Timeout *int `json:"timeout,omitempty"` + // RecreatePods, when set to true, instruct helmfile to perform pods restart for the resource if applicable + RecreatePods *bool `json:"recreatePods,omitempty"` + // Force, when set to true, forces resource update through delete/recreate if needed + Force *bool `json:"force,omitempty"` + // Installed, when set to true, `delete --purge` the release + Installed *bool `json:"installed,omitempty"` + // Atomic, when set to true, restore previous state in case of a failed install/upgrade attempt + Atomic *bool `json:"atomic,omitempty"` + // CleanupOnFail, when set to true, the --cleanup-on-fail helm flag is passed to the upgrade command + CleanupOnFail *bool `json:"cleanupOnFail,omitempty"` + // HistoryMax, limit the maximum number of revisions saved per release. Use 0 for no limit (default 10) + HistoryMax *int `json:"historyMax,omitempty"` + + // MissingFileHandler is set to either "Error" or "Warn". "Error" instructs helmfile to fail when unable to find a values or secrets file. When "Warn", it prints the file and continues. + // The default value for MissingFileHandler is "Error". + MissingFileHandler *string `json:"missingFileHandler,omitempty"` + // Needs is the [TILLER_NS/][NS/]NAME representations of releases that this release depends on. + Needs []string `json:"needs,omitempty"` + + // Hooks is a list of extension points paired with operations, that are executed in specific points of the lifecycle of releases defined in helmfile + Hooks []Hook `json:"hooks,omitempty"` + + // Name is the name of this release + Name string `json:"name,omitempty"` + Namespace string `json:"namespace,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + // JENKINS X comment - changed from the original []interface{} so we can unmarshall string array + Values []string `json:"values,omitempty"` + Secrets []string `json:"secrets,omitempty"` + SetValues []SetValue `json:"set,omitempty"` + + ValuesTemplate []interface{} `json:"valuesTemplate,omitempty"` + SetValuesTemplate []SetValue `json:"setTemplate,omitempty"` + + // The 'env' section is not really necessary any longer, as 'set' would now provide the same functionality + EnvValues []SetValue `json:"env,omitempty"` + + ValuesPathPrefix string `json:"valuesPathPrefix,omitempty"` + + TillerNamespace string `json:"tillerNamespace,omitempty"` + Tillerless *bool `json:"tillerless,omitempty"` + + KubeContext string `json:"kubeContext,omitempty"` + + TLS *bool `json:"tls,omitempty"` + TLSCACert string `json:"tlsCACert,omitempty"` + TLSKey string `json:"tlsKey,omitempty"` + TLSCert string `json:"tlsCert,omitempty"` + + // These values are used in templating + TillerlessTemplate *string `json:"tillerlessTemplate,omitempty"` + VerifyTemplate *string `json:"verifyTemplate,omitempty"` + WaitTemplate *string `json:"waitTemplate,omitempty"` + InstalledTemplate *string `json:"installedTemplate,omitempty"` + + // These settings requires helm-x integration to work + Dependencies []Dependency `json:"dependencies,omitempty"` + JSONPatches []interface{} `json:"jsonPatches,omitempty"` + StrategicMergePatches []interface{} `json:"strategicMergePatches,omitempty"` + Adopt []string `json:"adopt,omitempty"` +} + +// Release for helm +type Release struct { + ReleaseSpec + + Filtered bool +} + +// SetValue are the key values to set on a helm release +type SetValue struct { + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` + File string `json:"file,omitempty"` + Values []string `json:"values,omitempty"` +} + +// AffectedReleases hold the list of released that where updated, deleted, or in error +type AffectedReleases struct { + Upgraded []*ReleaseSpec + Deleted []*ReleaseSpec + Failed []*ReleaseSpec +} + +// EnvironmentSpec envs +type EnvironmentSpec struct { + Values []interface{} `json:"values,omitempty"` + Secrets []string `json:"secrets,omitempty"` + + // MissingFileHandler instructs helmfile to fail when unable to find a environment values file listed + // under `environments.NAME.values`. + // + // Possible values are "Error", "Warn", "Info", "Debug". The default is "Error". + // + // Use "Warn", "Info", or "Debug" if you want helmfile to not fail when a values file is missing, while just leaving + // a message about the missing file at the log-level. + MissingFileHandler *string `json:"missingFileHandler,omitempty"` +} + +// TemplateSpec defines the structure of a reusable and composable template for helm releases. +type TemplateSpec struct { + ReleaseSpec `json:",inline"` +} + +// EnvironmentTemplateData provides variables accessible while executing golang text/template expressions in helmfile and values YAML files +type EnvironmentTemplateData struct { + // Environment is accessible as `.Environment` from any template executed by the renderer + Environment Environment + // Namespace is accessible as `.Namespace` from any non-values template executed by the renderer + Namespace string + // Values is accessible as `.Values` and it contains default state values overrode by environment values and override values. + Values map[string]interface{} +} + +// Dependency deps +type Dependency struct { + Chart string `json:"chart"` + Version string `json:"version"` + Alias string `json:"alias"` +} + +// Hook hooks +type Hook struct { + Name string `json:"name"` + Events []string `json:"events"` + Command string `json:"command"` + Args []string `json:"args"` + ShowLogs bool `json:"showlogs"` +} + +// Environment vars +type Environment struct { + Name string + Values map[string]interface{} + Defaults map[string]interface{} +}