Merge pull request #963 from frontapp/auto-helm3
feat: Automatically enable Helm v3 mode
This commit is contained in:
commit
990d31c667
|
|
@ -1994,6 +1994,9 @@ func (helm *mockHelmExec) Fetch(chart string, flags ...string) error {
|
||||||
func (helm *mockHelmExec) Lint(name, chart string, flags ...string) error {
|
func (helm *mockHelmExec) Lint(name, chart string, flags ...string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func (helm *mockHelmExec) IsHelm3() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func TestTemplate_SingleStateFile(t *testing.T) {
|
func TestTemplate_SingleStateFile(t *testing.T) {
|
||||||
files := map[string]string{
|
files := map[string]string{
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,10 @@ func (helm *Helm) TemplateRelease(name, chart string, flags ...string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (helm *Helm) IsHelm3() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (helm *Helm) sync(m *sync.Mutex, f func()) {
|
func (helm *Helm) sync(m *sync.Mutex, f func()) {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ type decryptedSecret struct {
|
||||||
|
|
||||||
type execer struct {
|
type execer struct {
|
||||||
helmBinary string
|
helmBinary string
|
||||||
|
isHelm3 bool
|
||||||
runner Runner
|
runner Runner
|
||||||
logger *zap.SugaredLogger
|
logger *zap.SugaredLogger
|
||||||
kubeContext string
|
kubeContext string
|
||||||
|
|
@ -45,15 +46,31 @@ func NewLogger(writer io.Writer, logLevel string) *zap.SugaredLogger {
|
||||||
return zap.New(core).Sugar()
|
return zap.New(core).Sugar()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func detectHelm3(helmBinary string, logger *zap.SugaredLogger, runner Runner) bool {
|
||||||
|
// Support explicit opt-in via environment variable
|
||||||
|
if os.Getenv("HELMFILE_HELM3") != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Autodetect from `helm verison`
|
||||||
|
bytes, err := runner.Execute(helmBinary, []string{"version", "--client", "--short"}, nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return strings.HasPrefix(string(bytes), "v3.")
|
||||||
|
}
|
||||||
|
|
||||||
// New for running helm commands
|
// New for running helm commands
|
||||||
func New(helmBinary string, logger *zap.SugaredLogger, kubeContext string, runner Runner) *execer {
|
func New(helmBinary string, logger *zap.SugaredLogger, kubeContext string, runner Runner) *execer {
|
||||||
return &execer{
|
return &execer{
|
||||||
helmBinary: helmBinary,
|
helmBinary: helmBinary,
|
||||||
|
isHelm3: detectHelm3(helmBinary, logger, runner),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
kubeContext: kubeContext,
|
kubeContext: kubeContext,
|
||||||
runner: runner,
|
runner: runner,
|
||||||
decryptedSecrets: make(map[string]*decryptedSecret),
|
decryptedSecrets: make(map[string]*decryptedSecret),
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (helm *execer) SetExtraArgs(args ...string) {
|
func (helm *execer) SetExtraArgs(args ...string) {
|
||||||
|
|
@ -126,7 +143,7 @@ func (helm *execer) List(context HelmContext, filter string, flags ...string) (s
|
||||||
preArgs := context.GetTillerlessArgs(helm.helmBinary)
|
preArgs := context.GetTillerlessArgs(helm.helmBinary)
|
||||||
env := context.getTillerlessEnv()
|
env := context.getTillerlessEnv()
|
||||||
var args []string
|
var args []string
|
||||||
if helm.isHelm3() {
|
if helm.IsHelm3() {
|
||||||
args = []string{"list", "--filter", filter}
|
args = []string{"list", "--filter", filter}
|
||||||
} else {
|
} else {
|
||||||
args = []string{"list", filter}
|
args = []string{"list", filter}
|
||||||
|
|
@ -139,7 +156,7 @@ func (helm *execer) List(context HelmContext, filter string, flags ...string) (s
|
||||||
// of the release to exist.
|
// of the release to exist.
|
||||||
//
|
//
|
||||||
// This fixes it by removing the header from the v3 output, so that the output is formatted the same as that of v2.
|
// This fixes it by removing the header from the v3 output, so that the output is formatted the same as that of v2.
|
||||||
if helm.isHelm3() {
|
if helm.IsHelm3() {
|
||||||
lines := strings.Split(string(out), "\n")
|
lines := strings.Split(string(out), "\n")
|
||||||
lines = lines[1:]
|
lines = lines[1:]
|
||||||
out = []byte(strings.Join(lines, "\n"))
|
out = []byte(strings.Join(lines, "\n"))
|
||||||
|
|
@ -219,7 +236,7 @@ func (helm *execer) DecryptSecret(context HelmContext, name string, flags ...str
|
||||||
func (helm *execer) TemplateRelease(name string, chart string, flags ...string) error {
|
func (helm *execer) TemplateRelease(name string, chart string, flags ...string) error {
|
||||||
helm.logger.Infof("Templating release=%v, chart=%v", name, chart)
|
helm.logger.Infof("Templating release=%v, chart=%v", name, chart)
|
||||||
var args []string
|
var args []string
|
||||||
if helm.isHelm3() {
|
if helm.IsHelm3() {
|
||||||
args = []string{"template", name, chart}
|
args = []string{"template", name, chart}
|
||||||
} else {
|
} else {
|
||||||
args = []string{"template", chart, "--name", name}
|
args = []string{"template", chart, "--name", name}
|
||||||
|
|
@ -286,7 +303,7 @@ func (helm *execer) TestRelease(context HelmContext, name string, flags ...strin
|
||||||
preArgs := context.GetTillerlessArgs(helm.helmBinary)
|
preArgs := context.GetTillerlessArgs(helm.helmBinary)
|
||||||
env := context.getTillerlessEnv()
|
env := context.getTillerlessEnv()
|
||||||
var args []string
|
var args []string
|
||||||
if helm.isHelm3() {
|
if helm.IsHelm3() {
|
||||||
args = []string{"test", "run", name}
|
args = []string{"test", "run", name}
|
||||||
} else {
|
} else {
|
||||||
args = []string{"test", name}
|
args = []string{"test", name}
|
||||||
|
|
@ -323,6 +340,6 @@ func (helm *execer) write(out []byte) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (helm *execer) isHelm3() bool {
|
func (helm *execer) IsHelm3() bool {
|
||||||
return os.Getenv("HELMFILE_HELM3") != ""
|
return helm.isHelm3
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ type mockRunner struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string) ([]byte, error) {
|
func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string) ([]byte, error) {
|
||||||
return []byte{}, nil
|
return mock.output, mock.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func MockExecer(logger *zap.SugaredLogger, kubeContext string) *execer {
|
func MockExecer(logger *zap.SugaredLogger, kubeContext string) *execer {
|
||||||
|
|
@ -32,10 +32,7 @@ func MockExecer(logger *zap.SugaredLogger, kubeContext string) *execer {
|
||||||
|
|
||||||
func TestNewHelmExec(t *testing.T) {
|
func TestNewHelmExec(t *testing.T) {
|
||||||
buffer := bytes.NewBufferString("something")
|
buffer := bytes.NewBufferString("something")
|
||||||
logger := NewLogger(buffer, "debug")
|
helm := MockExecer(NewLogger(buffer, "debug"), "dev")
|
||||||
helm := New("helm", logger, "dev", &ShellRunner{
|
|
||||||
Logger: logger,
|
|
||||||
})
|
|
||||||
if helm.kubeContext != "dev" {
|
if helm.kubeContext != "dev" {
|
||||||
t.Error("helmexec.New() - kubeContext")
|
t.Error("helmexec.New() - kubeContext")
|
||||||
}
|
}
|
||||||
|
|
@ -48,11 +45,7 @@ func TestNewHelmExec(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_SetExtraArgs(t *testing.T) {
|
func Test_SetExtraArgs(t *testing.T) {
|
||||||
buffer := bytes.NewBufferString("something")
|
helm := MockExecer(NewLogger(os.Stdout, "info"), "dev")
|
||||||
logger := NewLogger(buffer, "debug")
|
|
||||||
helm := New("helm", NewLogger(os.Stdout, "info"), "dev", &ShellRunner{
|
|
||||||
Logger: logger,
|
|
||||||
})
|
|
||||||
helm.SetExtraArgs()
|
helm.SetExtraArgs()
|
||||||
if len(helm.extra) != 0 {
|
if len(helm.extra) != 0 {
|
||||||
t.Error("helmexec.SetExtraArgs() - passing no arguments should not change extra field")
|
t.Error("helmexec.SetExtraArgs() - passing no arguments should not change extra field")
|
||||||
|
|
@ -68,11 +61,7 @@ func Test_SetExtraArgs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_SetHelmBinary(t *testing.T) {
|
func Test_SetHelmBinary(t *testing.T) {
|
||||||
buffer := bytes.NewBufferString("something")
|
helm := MockExecer(NewLogger(os.Stdout, "info"), "dev")
|
||||||
logger := NewLogger(buffer, "debug")
|
|
||||||
helm := New("helm", NewLogger(os.Stdout, "info"), "dev", &ShellRunner{
|
|
||||||
Logger: logger,
|
|
||||||
})
|
|
||||||
if helm.helmBinary != "helm" {
|
if helm.helmBinary != "helm" {
|
||||||
t.Error("helmexec.command - default command is not helm")
|
t.Error("helmexec.command - default command is not helm")
|
||||||
}
|
}
|
||||||
|
|
@ -517,3 +506,17 @@ exec: helm template path/to/chart --name release --values file.yml --kube-contex
|
||||||
t.Errorf("helmexec.Template()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
t.Errorf("helmexec.Template()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_IsHelm3(t *testing.T) {
|
||||||
|
helm2Runner := mockRunner{output: []byte("Client: v2.16.0+ge13bc94\n")}
|
||||||
|
helm := New("helm", NewLogger(os.Stdout, "info"), "dev", &helm2Runner)
|
||||||
|
if helm.IsHelm3() {
|
||||||
|
t.Error("helmexec.IsHelm3() - Detected Helm 3 with Helm 2 version")
|
||||||
|
}
|
||||||
|
|
||||||
|
helm3Runner := mockRunner{output: []byte("v3.0.0+ge29ce2a\n")}
|
||||||
|
helm = New("helm", NewLogger(os.Stdout, "info"), "dev", &helm3Runner)
|
||||||
|
if !helm.IsHelm3() {
|
||||||
|
t.Error("helmexec.IsHelm3() - Failed to detect Helm 3")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,10 @@ type Interface interface {
|
||||||
TestRelease(context HelmContext, name string, flags ...string) error
|
TestRelease(context HelmContext, name string, flags ...string) error
|
||||||
List(context HelmContext, filter string, flags ...string) (string, error)
|
List(context HelmContext, filter string, flags ...string) (string, error)
|
||||||
DecryptSecret(context HelmContext, name string, flags ...string) (string, error)
|
DecryptSecret(context HelmContext, name string, flags ...string) (string, error)
|
||||||
|
IsHelm3() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type DependencyUpdater interface {
|
type DependencyUpdater interface {
|
||||||
UpdateDeps(chart string) error
|
UpdateDeps(chart string) error
|
||||||
|
IsHelm3() bool
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -280,7 +280,7 @@ func (m *chartDependencyManager) lockFileName() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *chartDependencyManager) Update(shell helmexec.DependencyUpdater, wd string, unresolved *UnresolvedDependencies) (*ResolvedDependencies, error) {
|
func (m *chartDependencyManager) Update(shell helmexec.DependencyUpdater, wd string, unresolved *UnresolvedDependencies) (*ResolvedDependencies, error) {
|
||||||
if isHelm3() {
|
if shell.IsHelm3() {
|
||||||
return m.updateHelm3(shell, wd, unresolved)
|
return m.updateHelm3(shell, wd, unresolved)
|
||||||
}
|
}
|
||||||
return m.updateHelm2(shell, wd, unresolved)
|
return m.updateHelm2(shell, wd, unresolved)
|
||||||
|
|
@ -330,7 +330,7 @@ func (m *chartDependencyManager) doUpdate(chartLockFile string, unresolved *Unre
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if isHelm3() && originalLockFileContent != nil {
|
if shell.IsHelm3() && originalLockFileContent != nil {
|
||||||
if err := m.writeBytes(filepath.Join(wd, chartLockFile), originalLockFileContent); err != nil {
|
if err := m.writeBytes(filepath.Join(wd, chartLockFile), originalLockFileContent); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -357,7 +357,7 @@ func (m *chartDependencyManager) doUpdate(chartLockFile string, unresolved *Unre
|
||||||
})
|
})
|
||||||
|
|
||||||
// Don't update lock file if no dependency updated.
|
// Don't update lock file if no dependency updated.
|
||||||
if !isHelm3() && originalLockFileContent != nil {
|
if !shell.IsHelm3() && originalLockFileContent != nil {
|
||||||
originalLockedReqs := &ChartLockedRequirements{}
|
originalLockedReqs := &ChartLockedRequirements{}
|
||||||
if err := yaml.Unmarshal(originalLockFileContent, originalLockedReqs); err != nil {
|
if err := yaml.Unmarshal(originalLockFileContent, originalLockedReqs); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -477,7 +477,7 @@ func (st *HelmState) DeleteReleasesForSync(affectedReleases *AffectedReleases, h
|
||||||
relErr = newReleaseError(release, err)
|
relErr = newReleaseError(release, err)
|
||||||
} else {
|
} else {
|
||||||
var args []string
|
var args []string
|
||||||
if isHelm3() {
|
if helm.IsHelm3() {
|
||||||
args = []string{}
|
args = []string{}
|
||||||
if release.Namespace != "" {
|
if release.Namespace != "" {
|
||||||
args = append(args, "--namespace", release.Namespace)
|
args = append(args, "--namespace", release.Namespace)
|
||||||
|
|
@ -577,7 +577,7 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme
|
||||||
relErr = newReleaseError(release, err)
|
relErr = newReleaseError(release, err)
|
||||||
} else if installed {
|
} else if installed {
|
||||||
var args []string
|
var args []string
|
||||||
if isHelm3() {
|
if helm.IsHelm3() {
|
||||||
args = []string{}
|
args = []string{}
|
||||||
} else {
|
} else {
|
||||||
args = []string{"--purge"}
|
args = []string{"--purge"}
|
||||||
|
|
@ -646,7 +646,7 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme
|
||||||
|
|
||||||
func (st *HelmState) listReleases(context helmexec.HelmContext, helm helmexec.Interface, release *ReleaseSpec) (string, error) {
|
func (st *HelmState) listReleases(context helmexec.HelmContext, helm helmexec.Interface, release *ReleaseSpec) (string, error) {
|
||||||
flags := st.connectionFlags(release)
|
flags := st.connectionFlags(release)
|
||||||
if isHelm3() && release.Namespace != "" {
|
if helm.IsHelm3() && release.Namespace != "" {
|
||||||
flags = append(flags, "--namespace", release.Namespace)
|
flags = append(flags, "--namespace", release.Namespace)
|
||||||
}
|
}
|
||||||
return helm.List(context, "^"+release.Name+"$", flags...)
|
return helm.List(context, "^"+release.Name+"$", flags...)
|
||||||
|
|
@ -1161,11 +1161,11 @@ func (st *HelmState) DeleteReleases(affectedReleases *AffectedReleases, helm hel
|
||||||
st.ApplyOverrides(&release)
|
st.ApplyOverrides(&release)
|
||||||
|
|
||||||
flags := []string{}
|
flags := []string{}
|
||||||
if purge && !isHelm3() {
|
if purge && !helm.IsHelm3() {
|
||||||
flags = append(flags, "--purge")
|
flags = append(flags, "--purge")
|
||||||
}
|
}
|
||||||
flags = st.appendConnectionFlags(flags, &release)
|
flags = st.appendConnectionFlags(flags, &release)
|
||||||
if isHelm3() && release.Namespace != "" {
|
if helm.IsHelm3() && release.Namespace != "" {
|
||||||
flags = append(flags, "--namespace", release.Namespace)
|
flags = append(flags, "--namespace", release.Namespace)
|
||||||
}
|
}
|
||||||
context := st.createHelmContext(&release, workerIndex)
|
context := st.createHelmContext(&release, workerIndex)
|
||||||
|
|
@ -1192,7 +1192,7 @@ func (st *HelmState) TestReleases(helm helmexec.Interface, cleanup bool, timeout
|
||||||
flags = append(flags, "--cleanup")
|
flags = append(flags, "--cleanup")
|
||||||
}
|
}
|
||||||
duration := strconv.Itoa(timeout)
|
duration := strconv.Itoa(timeout)
|
||||||
if isHelm3() {
|
if helm.IsHelm3() {
|
||||||
duration += "s"
|
duration += "s"
|
||||||
}
|
}
|
||||||
flags = append(flags, "--timeout", duration)
|
flags = append(flags, "--timeout", duration)
|
||||||
|
|
@ -1202,10 +1202,6 @@ func (st *HelmState) TestReleases(helm helmexec.Interface, cleanup bool, timeout
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func isHelm3() bool {
|
|
||||||
return os.Getenv("HELMFILE_HELM3") != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean will remove any generated secrets
|
// Clean will remove any generated secrets
|
||||||
func (st *HelmState) Clean() []error {
|
func (st *HelmState) Clean() []error {
|
||||||
errs := []error{}
|
errs := []error{}
|
||||||
|
|
@ -1532,7 +1528,7 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp
|
||||||
}
|
}
|
||||||
if timeout != 0 {
|
if timeout != 0 {
|
||||||
duration := strconv.Itoa(timeout)
|
duration := strconv.Itoa(timeout)
|
||||||
if isHelm3() {
|
if helm.IsHelm3() {
|
||||||
duration += "s"
|
duration += "s"
|
||||||
}
|
}
|
||||||
flags = append(flags, "--timeout", duration)
|
flags = append(flags, "--timeout", duration)
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,14 @@ func boolValue(v bool) *bool {
|
||||||
return &v
|
return &v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mocking the command-line runner
|
||||||
|
|
||||||
|
type mockRunner struct{}
|
||||||
|
|
||||||
|
func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string) ([]byte, error) {
|
||||||
|
return []byte{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestHelmState_flagsForUpgrade(t *testing.T) {
|
func TestHelmState_flagsForUpgrade(t *testing.T) {
|
||||||
enable := true
|
enable := true
|
||||||
disable := false
|
disable := false
|
||||||
|
|
@ -524,9 +532,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
|
||||||
HelmDefaults: tt.defaults,
|
HelmDefaults: tt.defaults,
|
||||||
valsRuntime: valsRuntime,
|
valsRuntime: valsRuntime,
|
||||||
}
|
}
|
||||||
helm := helmexec.New("helm", logger, "default", &helmexec.ShellRunner{
|
helm := helmexec.New("helm", logger, "default", &mockRunner{})
|
||||||
Logger: logger,
|
|
||||||
})
|
|
||||||
args, err := state.flagsForUpgrade(helm, tt.release, 0)
|
args, err := state.flagsForUpgrade(helm, tt.release, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error flagsForUpgade: %v", err)
|
t.Errorf("unexpected error flagsForUpgade: %v", err)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue