diff --git a/README.md b/README.md index 6900962..27e63e4 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This plugin provides an interface between [Drone](https://drone.io/) and [Helm 3 * Deploy your service * Delete your service -The plugin is inpsired by [drone-helm](https://github.com/ipedrazas/drone-helm), which fills the same role for Helm 2. It provides a comparable feature-set and the configuration settings are backwards-compatible. +The plugin is inpsired by [drone-helm](https://github.com/ipedrazas/drone-helm), which fills the same role for Helm 2. It provides a comparable feature-set and the configuration settings are backward-compatible. ## Example configuration @@ -23,7 +23,7 @@ steps: - name: lint image: pelotech/drone-helm3 settings: - helm_command: lint + mode: lint chart: ./ ``` @@ -34,12 +34,12 @@ steps: - name: deploy image: pelotech/drone-helm3 settings: - helm_command: upgrade + mode: upgrade chart: ./ release: my-project environment: - API_SERVER: https://my.kubernetes.installation/clusters/a-1234 - KUBERNETES_TOKEN: + KUBE_API_SERVER: https://my.kubernetes.installation/clusters/a-1234 + KUBE_TOKEN: from_secret: kubernetes_token ``` @@ -50,17 +50,17 @@ steps: - name: uninstall image: pelotech/drone-helm3 settings: - helm_command: uninstall + mode: uninstall release: my-project environment: - API_SERVER: https://my.kubernetes.installation/clusters/a-1234 - KUBERNETES_TOKEN: + KUBE_API_SERVER: https://my.kubernetes.installation/clusters/a-1234 + KUBE_TOKEN: from_secret: kubernetes_token ``` ## Upgrading from drone-helm -drone-helm3 is largely backwards-compatible with drone-helm. There are some known differences: +drone-helm3 is largely backward-compatible with drone-helm. There are some known differences: * You'll need to migrate the deployments in the cluster [helm-v2-to-helm-v3](https://helm.sh/blog/migrate-from-helm-v2-to-helm-v3/). * EKS is not supported. See [#5](https://github.com/pelotech/drone-helm3/issues/5) for more information. @@ -74,6 +74,15 @@ drone-helm3 is largely backwards-compatible with drone-helm. There are some know * `canary_image` * `client_only` * `stable_repo_url` +* Several settings have been renamed, to clarify their purpose and provide a more consistent naming scheme. For backward-compatibility, the old names are still available as aliases. If the old and new names are both present, the updated form takes priority. Conflicting settings will make your `.drone.yml` harder to understand, so we recommend updating to the new names: + * `helm_command` is now `mode` + ° `helm_repos` is now `add_repos` + * `api_server` is now `kube_api_server` + * `service_account` is now `kube_service_account` + * `kubernetes_token` is now `kube_token` + * `kubernetes_certificate` is now `kube_certificate` + * `wait` is now `wait_for_upgrade` + * `force` is now `force_upgrade` Since helm 3 does not require Tiller, we also recommend switching to a service account with less-expansive permissions. diff --git a/docs/parameter_reference.md b/docs/parameter_reference.md index 6ef32a5..1379ba5 100644 --- a/docs/parameter_reference.md +++ b/docs/parameter_reference.md @@ -1,17 +1,17 @@ # Parameter reference ## Global -| Param name | Type | Purpose | -|---------------------|-----------------|---------| -| helm_command | string | Indicates the operation to perform. Recommended, but not required. Valid options are `upgrade`, `uninstall`, `lint`, and `help`. | -| update_dependencies | boolean | Calls `helm dependency update` before running the main command.| -| helm_repos | list\ | Calls `helm repo add $repo` before running the main command. Each string should be formatted as `repo_name=https://repo.url/`. | -| namespace | string | Kubernetes namespace to use for this operation. | -| debug | boolean | Generate debug output within drone-helm3 and pass `--debug` to all helm commands. Use with care, since the debug output may include secrets. | +| Param name | Type | Alias | Purpose | +|---------------------|-----------------|--------------|---------| +| mode | string | helm_command | Indicates the operation to perform. Recommended, but not required. Valid options are `upgrade`, `uninstall`, `lint`, and `help`. | +| update_dependencies | boolean | | Calls `helm dependency update` before running the main command.| +| add_repos | list\ | helm_repos | Calls `helm repo add $repo` before running the main command. Each string should be formatted as `repo_name=https://repo.url/`. | +| namespace | string | | Kubernetes namespace to use for this operation. | +| debug | boolean | | Generate debug output within drone-helm3 and pass `--debug` to all helm commands. Use with care, since the debug output may include secrets. | ## Linting -Linting is only triggered when the `helm_command` setting is "lint". +Linting is only triggered when the `mode` setting is "lint". | Param name | Type | Required | Purpose | |---------------|----------------|----------|---------| @@ -23,45 +23,45 @@ Linting is only triggered when the `helm_command` setting is "lint". ## Installation -Installations are triggered when the `helm_command` setting is "upgrade." They can also be triggered when the build was triggered by a `push`, `tag`, `deployment`, `pull_request`, `promote`, or `rollback` Drone event. +Installations are triggered when the `mode` setting is "upgrade." They can also be triggered when the build was triggered by a `push`, `tag`, `deployment`, `pull_request`, `promote`, or `rollback` Drone event. -| Param name | Type | Required | Purpose | -|------------------------|----------------|----------|---------| -| chart | string | yes | The chart to use for this installation. | -| release | string | yes | The release name for helm to use. | -| api_server | string | yes | API endpoint for the Kubernetes cluster. | -| kubernetes_token | string | yes | Token for authenticating to Kubernetes. | -| service_account | string | | Service account for authenticating to Kubernetes. Default is `helm`. | -| kubernetes_certificate | string | | Base64 encoded TLS certificate used by the Kubernetes cluster's certificate authority. | -| chart_version | string | | Specific chart version to install. | -| dry_run | boolean | | Pass `--dry-run` to `helm upgrade`. | -| wait | boolean | | Wait until kubernetes resources are in a ready state before marking the installation successful. | -| timeout | duration | | Timeout for any *individual* Kubernetes operation. The installation's full runtime may exceed this duration. | -| force | boolean | | Pass `--force` to `helm upgrade`. | -| atomic_upgrade | boolean | | Pass `--atomic` to `helm upgrade`. | -| cleanup_failed_upgrade | boolean | | Pass `--cleanup-on-fail` to `helm upgrade`. | -| values | list\ | | Chart values to use as the `--set` argument to `helm upgrade`. | -| string_values | list\ | | Chart values to use as the `--set-string` argument to `helm upgrade`. | -| values_files | list\ | | Values to use as `--values` arguments to `helm upgrade`. | -| reuse_values | boolean | | Reuse the values from a previous release. | -| skip_tls_verify | boolean | | Connect to the Kubernetes cluster without checking for a valid TLS certificate. Not recommended in production. | +| Param name | Type | Required | Alias | Purpose | +|------------------------|----------------|----------|------------------------|---------| +| chart | string | yes | | The chart to use for this installation. | +| release | string | yes | | The release name for helm to use. | +| kube_api_server | string | yes | api_server | API endpoint for the Kubernetes cluster. | +| kube_token | string | yes | kubernetes_token | Token for authenticating to Kubernetes. | +| kube_service_account | string | | service_account | Service account for authenticating to Kubernetes. Default is `helm`. | +| kube_certificate | string | | kubernetes_certificate | Base64 encoded TLS certificate used by the Kubernetes cluster's certificate authority. | +| chart_version | string | | | Specific chart version to install. | +| dry_run | boolean | | | Pass `--dry-run` to `helm upgrade`. | +| wait_for_upgrade | boolean | | wait | Wait until kubernetes resources are in a ready state before marking the installation successful. | +| timeout | duration | | | Timeout for any *individual* Kubernetes operation. The installation's full runtime may exceed this duration. | +| force_upgrade | boolean | | force | Pass `--force` to `helm upgrade`. | +| atomic_upgrade | boolean | | | Pass `--atomic` to `helm upgrade`. | +| cleanup_failed_upgrade | boolean | | | Pass `--cleanup-on-fail` to `helm upgrade`. | +| values | list\ | | | Chart values to use as the `--set` argument to `helm upgrade`. | +| string_values | list\ | | | Chart values to use as the `--set-string` argument to `helm upgrade`. | +| values_files | list\ | | | Values to use as `--values` arguments to `helm upgrade`. | +| reuse_values | boolean | | | Reuse the values from a previous release. | +| skip_tls_verify | boolean | | | Connect to the Kubernetes cluster without checking for a valid TLS certificate. Not recommended in production. | ## Uninstallation -Uninstallations are triggered when the `helm_command` setting is "uninstall" or "delete." They can also be triggered when the build was triggered by a `delete` Drone event. +Uninstallations are triggered when the `mode` setting is "uninstall" or "delete." They can also be triggered when the build was triggered by a `delete` Drone event. -| Param name | Type | Required | Purpose | -|------------------------|----------|----------|---------| -| release | string | yes | The release name for helm to use. | -| api_server | string | yes | API endpoint for the Kubernetes cluster. | -| kubernetes_token | string | yes | Token for authenticating to Kubernetes. | -| service_account | string | | Service account for authenticating to Kubernetes. Default is `helm`. | -| kubernetes_certificate | string | | Base64 encoded TLS certificate used by the Kubernetes cluster's certificate authority. | -| keep_history | boolean | | Pass `--keep-history` to `helm uninstall`, to retain the release history. | -| dry_run | boolean | | Pass `--dry-run` to `helm uninstall`. | -| timeout | duration | | Timeout for any *individual* Kubernetes operation. The uninstallation's full runtime may exceed this duration. | -| skip_tls_verify | boolean | | Connect to the Kubernetes cluster without checking for a valid TLS certificate. Not recommended in production. | -| chart | string | | Required when the global `update_dependencies` parameter is true. No effect otherwise. | +| Param name | Type | Required | Alias | Purpose | +|------------------------|----------|----------|------------------------|---------| +| release | string | yes | | The release name for helm to use. | +| kube_api_server | string | yes | api_server | API endpoint for the Kubernetes cluster. | +| kube_token | string | yes | kubernetes_token | Token for authenticating to Kubernetes. | +| kube_service_account | string | | service_account | Service account for authenticating to Kubernetes. Default is `helm`. | +| kube_certificate | string | | kubernetes_certificate | Base64 encoded TLS certificate used by the Kubernetes cluster's certificate authority. | +| keep_history | boolean | | | Pass `--keep-history` to `helm uninstall`, to retain the release history. | +| dry_run | boolean | | | Pass `--dry-run` to `helm uninstall`. | +| timeout | duration | | | Timeout for any *individual* Kubernetes operation. The uninstallation's full runtime may exceed this duration. | +| skip_tls_verify | boolean | | | Connect to the Kubernetes cluster without checking for a valid TLS certificate. Not recommended in production. | +| chart | string | | | Required when the global `update_dependencies` parameter is true. No effect otherwise. | ### Where to put settings @@ -92,3 +92,18 @@ Note that **list members must not contain commas**. Both of the following are eq values_files: [ "./over_9,000.yml" ] values_files: [ "./over_9", "000.yml" ] ``` + +### Backward-compatibility aliases + +Some settings have alternate names, for backward-compatibility with drone-helm. We recommend using the canonical name unless you require the backward-compatible form. + +| Canonical name | Alias | +|----------------------|-------| +| mode | helm_command | +| add_repos | helm_repos | +| kube_api_server | api_server | +| kube_service_account | service_account | +| kube_token | kubernetes_token | +| kube_certificate | kubernetes_certificate | +| wait_for_upgrade | wait | +| force_upgrade | force | diff --git a/go.mod b/go.mod index e3fc56f..3d45e7f 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.13 require ( github.com/golang/mock v1.3.1 - github.com/joho/godotenv v1.3.0 // indirect + github.com/joho/godotenv v1.3.0 github.com/kelseyhightower/envconfig v1.4.0 github.com/stretchr/testify v1.4.0 golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f // indirect diff --git a/internal/helm/config.go b/internal/helm/config.go index 1c8a393..b633439 100644 --- a/internal/helm/config.go +++ b/internal/helm/config.go @@ -20,29 +20,29 @@ var ( // not have the `PLUGIN_` prefix. type Config struct { // Configuration for drone-helm itself - Command string `envconfig:"HELM_COMMAND"` // Helm command to run + Command string `envconfig:"mode"` // Helm command to run DroneEvent string `envconfig:"DRONE_BUILD_EVENT"` // Drone event that invoked this plugin. UpdateDependencies bool `split_words:"true"` // Call `helm dependency update` before the main command - AddRepos []string `envconfig:"HELM_REPOS"` // Call `helm repo add` before the main command + AddRepos []string `split_words:"true"` // Call `helm repo add` before the main command Debug bool `` // Generate debug output and pass --debug to all helm commands Values string `` // Argument to pass to --set in applicable helm commands StringValues string `split_words:"true"` // Argument to pass to --set-string in applicable helm commands ValuesFiles []string `split_words:"true"` // Arguments to pass to --values in applicable helm commands Namespace string `` // Kubernetes namespace for all helm commands - KubeToken string `envconfig:"KUBERNETES_TOKEN"` // Kubernetes authentication token to put in .kube/config + KubeToken string `split_words:"true"` // Kubernetes authentication token to put in .kube/config SkipTLSVerify bool `envconfig:"SKIP_TLS_VERIFY"` // Put insecure-skip-tls-verify in .kube/config - Certificate string `envconfig:"KUBERNETES_CERTIFICATE"` // The Kubernetes cluster CA's self-signed certificate (must be base64-encoded) - APIServer string `envconfig:"API_SERVER"` // The Kubernetes cluster's API endpoint - ServiceAccount string `split_words:"true"` // Account to use for connecting to the Kubernetes cluster + Certificate string `envconfig:"kube_certificate"` // The Kubernetes cluster CA's self-signed certificate (must be base64-encoded) + APIServer string `envconfig:"kube_api_server"` // The Kubernetes cluster's API endpoint + ServiceAccount string `envconfig:"kube_service_account"` // Account to use for connecting to the Kubernetes cluster ChartVersion string `split_words:"true"` // Specific chart version to use in `helm upgrade` DryRun bool `split_words:"true"` // Pass --dry-run to applicable helm commands - Wait bool `` // Pass --wait to applicable helm commands + Wait bool `envconfig:"wait_for_upgrade"` // Pass --wait to applicable helm commands ReuseValues bool `split_words:"true"` // Pass --reuse-values to `helm upgrade` KeepHistory bool `split_words:"true"` // Pass --keep-history to `helm uninstall` Timeout string `` // Argument to pass to --timeout in applicable helm commands Chart string `` // Chart argument to use in applicable helm commands Release string `` // Release argument to use in applicable helm commands - Force bool `` // Pass --force to applicable helm commands + Force bool `envconfig:"force_upgrade"` // Pass --force to applicable helm commands AtomicUpgrade bool `split_words:"true"` // Pass --atomic to `helm upgrade` CleanupOnFail bool `envconfig:"CLEANUP_FAILED_UPGRADE"` // Pass --cleanup-on-fail to `helm upgrade` LintStrictly bool `split_words:"true"` // Pass --strict to `helm lint` @@ -53,7 +53,25 @@ type Config struct { // NewConfig creates a Config and reads environment variables into it, accounting for several possible formats. func NewConfig(stdout, stderr io.Writer) (*Config, error) { + var aliases settingAliases + if err := envconfig.Process("plugin", &aliases); err != nil { + return nil, err + } + + if err := envconfig.Process("", &aliases); err != nil { + return nil, err + } + cfg := Config{ + Command: aliases.Command, + AddRepos: aliases.AddRepos, + APIServer: aliases.APIServer, + ServiceAccount: aliases.ServiceAccount, + Wait: aliases.Wait, + Force: aliases.Force, + KubeToken: aliases.KubeToken, + Certificate: aliases.Certificate, + Stdout: stdout, Stderr: stderr, } @@ -94,3 +112,14 @@ func (cfg *Config) deprecationWarn() { } } } + +type settingAliases struct { + Command string `envconfig:"helm_command"` + AddRepos []string `envconfig:"helm_repos"` + APIServer string `envconfig:"api_server"` + ServiceAccount string `split_words:"true"` + Wait bool `` + Force bool `` + KubeToken string `envconfig:"kubernetes_token"` + Certificate string `envconfig:"kubernetes_certificate"` +} diff --git a/internal/helm/config_test.go b/internal/helm/config_test.go index 6cad789..13bf22a 100644 --- a/internal/helm/config_test.go +++ b/internal/helm/config_test.go @@ -20,47 +20,47 @@ func TestConfigTestSuite(t *testing.T) { } func (suite *ConfigTestSuite) TestNewConfigWithPluginPrefix() { - suite.unsetenv("HELM_COMMAND") + suite.unsetenv("MODE") suite.unsetenv("UPDATE_DEPENDENCIES") suite.unsetenv("DEBUG") - suite.setenv("PLUGIN_HELM_COMMAND", "execute order 66") + suite.setenv("PLUGIN_MODE", "iambic") suite.setenv("PLUGIN_UPDATE_DEPENDENCIES", "true") suite.setenv("PLUGIN_DEBUG", "true") cfg, err := NewConfig(&strings.Builder{}, &strings.Builder{}) suite.Require().NoError(err) - suite.Equal("execute order 66", cfg.Command) + suite.Equal("iambic", cfg.Command) suite.True(cfg.UpdateDependencies) suite.True(cfg.Debug) } func (suite *ConfigTestSuite) TestNewConfigWithNoPrefix() { - suite.unsetenv("PLUGIN_HELM_COMMAND") + suite.unsetenv("PLUGIN_MODE") suite.unsetenv("PLUGIN_UPDATE_DEPENDENCIES") suite.unsetenv("PLUGIN_DEBUG") - suite.setenv("HELM_COMMAND", "execute order 66") + suite.setenv("MODE", "iambic") suite.setenv("UPDATE_DEPENDENCIES", "true") suite.setenv("DEBUG", "true") cfg, err := NewConfig(&strings.Builder{}, &strings.Builder{}) suite.Require().NoError(err) - suite.Equal("execute order 66", cfg.Command) + suite.Equal("iambic", cfg.Command) suite.True(cfg.UpdateDependencies) suite.True(cfg.Debug) } func (suite *ConfigTestSuite) TestNewConfigWithConflictingVariables() { - suite.setenv("PLUGIN_HELM_COMMAND", "execute order 66") - suite.setenv("HELM_COMMAND", "defend the jedi") // values from the `environment` block override those from `settings` + suite.setenv("PLUGIN_MODE", "iambic") + suite.setenv("MODE", "haiku") // values from the `environment` block override those from `settings` cfg, err := NewConfig(&strings.Builder{}, &strings.Builder{}) suite.Require().NoError(err) - suite.Equal("defend the jedi", cfg.Command) + suite.Equal("haiku", cfg.Command) } func (suite *ConfigTestSuite) TestNewConfigInfersNumbersAreSeconds() { @@ -70,6 +70,62 @@ func (suite *ConfigTestSuite) TestNewConfigInfersNumbersAreSeconds() { suite.Equal("42s", cfg.Timeout) } +func (suite *ConfigTestSuite) TestNewConfigWithAliases() { + for _, varname := range []string{ + "MODE", + "ADD_REPOS", + "KUBE_API_SERVER", + "KUBE_SERVICE_ACCOUNT", + "WAIT_FOR_UPGRADE", + "FORCE_UPGRADE", + "KUBE_TOKEN", + "KUBE_CERTIFICATE", + } { + suite.unsetenv(varname) + suite.unsetenv("PLUGIN_" + varname) + } + suite.setenv("PLUGIN_HELM_COMMAND", "beware the jabberwock") + suite.setenv("PLUGIN_HELM_REPOS", "chortle=http://calloo.callay/frabjous/day") + suite.setenv("PLUGIN_API_SERVER", "http://tumtum.tree") + suite.setenv("PLUGIN_SERVICE_ACCOUNT", "tulgey") + suite.setenv("PLUGIN_WAIT", "true") + suite.setenv("PLUGIN_FORCE", "true") + suite.setenv("PLUGIN_KUBERNETES_TOKEN", "Y29tZSB0byBteSBhcm1z") + suite.setenv("PLUGIN_KUBERNETES_CERTIFICATE", "d2l0aCBpdHMgaGVhZA==") + + cfg, err := NewConfig(&strings.Builder{}, &strings.Builder{}) + suite.Require().NoError(err) + suite.Equal("beware the jabberwock", cfg.Command) + suite.Equal([]string{"chortle=http://calloo.callay/frabjous/day"}, cfg.AddRepos) + suite.Equal("http://tumtum.tree", cfg.APIServer) + suite.Equal("tulgey", cfg.ServiceAccount) + suite.True(cfg.Wait, "Wait should be aliased") + suite.True(cfg.Force, "Force should be aliased") + suite.Equal("Y29tZSB0byBteSBhcm1z", cfg.KubeToken, "KubeToken should be aliased") + suite.Equal("d2l0aCBpdHMgaGVhZA==", cfg.Certificate, "Certificate should be aliased") +} + +func (suite *ConfigTestSuite) TestAliasedSettingWithoutPluginPrefix() { + suite.unsetenv("FORCE_UPGRADE") + suite.unsetenv("PLUGIN_FORCE_UPGRADE") + suite.unsetenv("PLUGIN_FORCE") + suite.setenv("FORCE", "true") + + cfg, err := NewConfig(&strings.Builder{}, &strings.Builder{}) + suite.Require().NoError(err) + suite.True(cfg.Force) +} + +func (suite *ConfigTestSuite) TestNewConfigWithAliasConflicts() { + suite.unsetenv("FORCE_UPGRADE") + suite.setenv("PLUGIN_FORCE", "true") + suite.setenv("PLUGIN_FORCE_UPGRADE", "false") // should override even when set to the zero value + + cfg, err := NewConfig(&strings.Builder{}, &strings.Builder{}) + suite.NoError(err) + suite.False(cfg.Force, "official names should override alias names") +} + func (suite *ConfigTestSuite) TestNewConfigSetsWriters() { stdout := &strings.Builder{} stderr := &strings.Builder{} @@ -100,7 +156,7 @@ func (suite *ConfigTestSuite) TestDeprecatedSettingWarnings() { func (suite *ConfigTestSuite) TestLogDebug() { suite.setenv("DEBUG", "true") - suite.setenv("HELM_COMMAND", "upgrade") + suite.setenv("MODE", "upgrade") stderr := strings.Builder{} stdout := strings.Builder{} diff --git a/internal/helm/plan_test.go b/internal/helm/plan_test.go index f6cf9bb..1bc3e11 100644 --- a/internal/helm/plan_test.go +++ b/internal/helm/plan_test.go @@ -370,7 +370,7 @@ func (suite *PlanTestSuite) TestDeterminePlanUninstallCommand() { suite.Same(&uninstall, stepsMaker) } -// helm_command = delete is provided as an alias for backwards-compatibility with drone-helm +// helm_command = delete is provided as an alias for backward-compatibility with drone-helm func (suite *PlanTestSuite) TestDeterminePlanDeleteCommand() { cfg := Config{ Command: "delete",