diff --git a/CHANGELOG.md b/CHANGELOG.md index ec1e8917..3e4c7c4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [0.9.1] - 2020-01-15 + +### Added + +- `helm version` now has optional flag `--mode` that additionally prints the mode (Helm version) in which the plugin operates, +either v2 or v3. +- Added `HELM_S3_MODE` that can be used to forcefully change the mode (Helm version), in case when the plugin does not detect Helm version properly. + +### Changed + +- Changed the way the plugin detects Helm version. Now it parses `helm version` output instead of checking `helm env` +command existence. + ## [0.9.0] - 2019-12-27 ### Added diff --git a/README.md b/README.md index b153c01b..21973d10 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,27 @@ for a CI that builds and pushes charts to your repository. } ``` +### Helm version mode + +The plugin is able to detect if you are using Helm v2 or v3 automatically. If, for some reason, the plugin does not +detect Helm version properly, you can set `HELM_S3_MODE` environment variable to value `2` or `3` to force the mode. + +Example: + + # We have Helm version 3: + $ helm version --short + v3.0.2+g19e47ee + + # For some reason, the plugin detects Helm version badly: + $ helm s3 version --mode + helm-s3 plugin version: 0.9.1 + Helm version mode: v2 + + # Force the plugin to operate in v3 mode: + $ HELM_S3_MODE=3 helm s3 version --mode + helm-s3 plugin version: 0.9.1 + Helm version mode: v3 + ## Usage *Note: some Helm CLI commands in v3 are incompatible with v2. Example commands below are provided for v2. For commands diff --git a/cmd/helms3/main.go b/cmd/helms3/main.go index 6a94f95b..30dce800 100644 --- a/cmd/helms3/main.go +++ b/cmd/helms3/main.go @@ -7,8 +7,9 @@ import ( "os" "time" - "github.com/hypnoglow/helm-s3/internal/helmutil" "gopkg.in/alecthomas/kingpin.v2" + + "github.com/hypnoglow/helm-s3/internal/helmutil" ) var ( @@ -64,7 +65,9 @@ func main() { } cli := kingpin.New("helm s3", "") - cli.Command(actionVersion, "Show plugin version.") + versionCmd := cli.Command(actionVersion, "Show plugin version.") + versionMode := versionCmd.Flag("mode", "Also print Helm version mode in which the plugin operates, either v2 or v3. In case when the plugin does not detect Helm version properly, you can forcefully change the mode: set HELM_S3_MODE environment variable to either 2 or 3."). + Bool() timeout := cli.Flag("timeout", helpFlagTimeout). Default(defaultTimeoutString). @@ -123,6 +126,15 @@ func main() { var act Action switch action { case actionVersion: + if *versionMode { + mode := "v2" + if helmutil.IsHelm3() { + mode = "v3" + } + fmt.Printf("helm-s3 plugin version: %s\n", version) + fmt.Printf("Helm version mode: %s\n", mode) + return + } fmt.Print(version) return diff --git a/internal/helmutil/testing_test.go b/internal/helmutil/testing_test.go index 57650b45..ddf19f0b 100644 --- a/internal/helmutil/testing_test.go +++ b/internal/helmutil/testing_test.go @@ -22,6 +22,38 @@ func mockEnv(t *testing.T, name, value string) func() { } } +func mockEnvs(t *testing.T, nameValue ...string) func() { + if len(nameValue)%2 != 0 { + t.Fatal("mockEnvs: must have even number of arguments") + } + + tearDowns := make([]func(), 0, len(nameValue)/2) + for i := 0; i < len(nameValue); i++ { + if i%2 == 1 { + continue + } + + name := nameValue[i] + value := nameValue[i+1] + + old := os.Getenv(name) + + err := os.Setenv(name, value) + require.NoError(t, err) + + tearDowns = append(tearDowns, func() { + err := os.Setenv(name, old) + require.NoError(t, err) + }) + } + + return func() { + for _, td := range tearDowns { + td() + } + } +} + func assertError(t *testing.T, err error, expected bool) { if expected { assert.Error(t, err) diff --git a/internal/helmutil/version.go b/internal/helmutil/version.go index 03353a8f..4278e2f4 100644 --- a/internal/helmutil/version.go +++ b/internal/helmutil/version.go @@ -3,10 +3,21 @@ package helmutil import ( "os" "os/exec" + "strings" ) // IsHelm3 returns true if helm is version 3+. func IsHelm3() bool { + // Support explicit mode configuration via environment variable. + switch strings.TrimSpace(os.Getenv("HELM_S3_MODE")) { + case "2", "v2": + return false + case "3", "v3": + return true + default: + // continue to other detection methods. + } + if os.Getenv("TILLER_HOST") != "" { return false } @@ -17,12 +28,19 @@ func IsHelm3() bool { // helm3Detected returns true if helm is v3. var helm3Detected func() bool -func helmEnvCommand() bool { - cmd := exec.Command("helm", "env") - return cmd.Run() == nil +func helmVersionCommand() bool { + cmd := exec.Command("helm", "version", "--short", "--client") + out, err := cmd.CombinedOutput() + if err != nil { + // Should not happen in normal cases (when helm is properly installed). + // Anyway, for now fallback to v2 for backward compatibility for helm-s3 users that are still on v2. + return false + } + + return strings.HasPrefix(string(out), "v3.") } // setupHelmVersionDetection sets up the command used to detect helm version. func setupHelmVersionDetection() { - helm3Detected = helmEnvCommand + helm3Detected = helmVersionCommand } diff --git a/internal/helmutil/version_test.go b/internal/helmutil/version_test.go index 548eb4d4..806f6feb 100644 --- a/internal/helmutil/version_test.go +++ b/internal/helmutil/version_test.go @@ -11,6 +11,54 @@ func TestIsHelm3(t *testing.T) { setup func() func() isHelm3 bool }{ + "HELM_S3_MODE is set to 2": { + setup: func() func() { + return mockEnv(t, "HELM_S3_MODE", "2") + }, + isHelm3: false, + }, + "HELM_S3_MODE is set to 3": { + setup: func() func() { + return mockEnv(t, "HELM_S3_MODE", "v3") + }, + isHelm3: true, + }, + "HELM_S3_MODE is set to any other value, TILLER_HOST is empty, helm command detects v2": { + setup: func() func() { + helm3Detected = func() bool { + return false + } + return mockEnvs(t, + "HELM_S3_MODE", "abc", + "TILLER_HOST", "", + ) + }, + isHelm3: false, + }, + "HELM_S3_MODE is empty, TILLER_HOST is empty, helm command detects v2": { + setup: func() func() { + helm3Detected = func() bool { + return false + } + return mockEnvs(t, + "HELM_S3_MODE", "", + "TILLER_HOST", "", + ) + }, + isHelm3: false, + }, + "HELM_S3_MODE is empty, TILLER_HOST is empty, helm command detects v3": { + setup: func() func() { + helm3Detected = func() bool { + return true + } + return mockEnvs(t, + "HELM_S3_MODE", "", + "TILLER_HOST", "", + ) + }, + isHelm3: true, + }, "TILLER_HOST is set": { setup: func() func() { return mockEnv(t, "TILLER_HOST", "1")