feat: adding ability for for charts to be pulled with plain HTTP (#1672)

* eat: adding ability for for charts to be pulled without HTTPS

accomplished by:

- Adding PlainHttp attribute to RepositorySpec., HelmDefault, ReleaseSpec
- Adding UnitTests for getOCIChart Flags.
- Adding funciton and unitTests for getChartDownload
- Changing and refactoring how Flags are added to getOCIChart.

Resolves #1224

Signed-off-by: Peter Halliday <peter.halliday@servicenow.com>

* Pass PlainHttp to OCI repo options, fix unit test

Signed-off-by: Pascal Rivard <privard@rbbn.com>

* Fix doc

Signed-off-by: Pascal Rivard <privard@rbbn.com>

* Use repository fields in non-OCI chart download options

Signed-off-by: Pascal Rivard <privard@rbbn.com>

* Update hashes in TestGenerateID

Signed-off-by: Pascal Rivard <privard@rbbn.com>

* Make sure repo exists when using its options

Signed-off-by: Pascal Rivard <privard@rbbn.com>

* Do not add TLS options if PlainHttp is set, adapt unit tests

Signed-off-by: Pascal Rivard <privard@rbbn.com>

* Fix doc

Signed-off-by: Pascal Rivard <privard@rbbn.com>

* Remove 'else if' from appendChartDownloadFlags

Signed-off-by: Pascal Rivard <privard@rbbn.com>

---------

Signed-off-by: Peter Halliday <peter.halliday@servicenow.com>
Signed-off-by: Pascal Rivard <privard@rbbn.com>
Co-authored-by: Peter Halliday <peter.halliday@servicenow.com>
Co-authored-by: Pascal Rivard <privard@rbbn.com>
This commit is contained in:
ennekein 2024-08-26 20:00:20 -04:00 committed by GitHub
parent ebba6a0452
commit 2cc995e508
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 406 additions and 68 deletions

View File

@ -161,6 +161,10 @@ repositories:
- name: skipTLS - name: skipTLS
url: https://ss.my-insecure-domain.com url: https://ss.my-insecure-domain.com
skipTLSVerify: true skipTLSVerify: true
# Advanced configuration: Connect to a repo served over plain http
- name: plainHTTP
url: http://just.http.domain.com
plainHttp: true
# context: kube-context # this directive is deprecated, please consider using helmDefaults.kubeContext # context: kube-context # this directive is deprecated, please consider using helmDefaults.kubeContext
@ -223,6 +227,8 @@ helmDefaults:
cascade: "background" cascade: "background"
# insecureSkipTLSVerify is true if the TLS verification should be skipped when fetching remote chart # insecureSkipTLSVerify is true if the TLS verification should be skipped when fetching remote chart
insecureSkipTLSVerify: false insecureSkipTLSVerify: false
# plainHttp is true if fetching the remote chart should be done using HTTP
plainHttp: false
# --wait flag for destroy/delete, if set to true, will wait until all resources are deleted before mark delete command as successful # --wait flag for destroy/delete, if set to true, will wait until all resources are deleted before mark delete command as successful
deleteWait: false deleteWait: false
# Timeout is the time in seconds to wait for helmfile destroy/delete (default 300) # Timeout is the time in seconds to wait for helmfile destroy/delete (default 300)
@ -339,6 +345,8 @@ releases:
cascade: "background" cascade: "background"
# insecureSkipTLSVerify is true if the TLS verification should be skipped when fetching remote chart # insecureSkipTLSVerify is true if the TLS verification should be skipped when fetching remote chart
insecureSkipTLSVerify: false insecureSkipTLSVerify: false
# plainHttp is true if fetching the remote chart should be done using HTTP
plainHttp: false
# suppressDiff skip the helm diff output. Useful for charts which produces large not helpful diff, default: false # suppressDiff skip the helm diff output. Useful for charts which produces large not helpful diff, default: false
suppressDiff: false suppressDiff: false
# suppressOutputLineRegex is a list of regex patterns to suppress output lines from helm diff (default []), available in helmfile v0.162.0 # suppressOutputLineRegex is a list of regex patterns to suppress output lines from helm diff (default []), available in helmfile v0.162.0

View File

@ -199,6 +199,8 @@ type HelmSpec struct {
DisableOpenAPIValidation *bool `yaml:"disableOpenAPIValidation,omitempty"` DisableOpenAPIValidation *bool `yaml:"disableOpenAPIValidation,omitempty"`
// InsecureSkipTLSVerify is true if the TLS verification should be skipped when fetching remote chart // InsecureSkipTLSVerify is true if the TLS verification should be skipped when fetching remote chart
InsecureSkipTLSVerify bool `yaml:"insecureSkipTLSVerify,omitempty"` InsecureSkipTLSVerify bool `yaml:"insecureSkipTLSVerify,omitempty"`
// PlainHttp is true if the remote charte should be fetched using HTTP and not HTTPS
PlainHttp bool `yaml:"plainHttp,omitempty"`
// Wait, if set to true, will wait until all resources are deleted before mark delete command as successful // Wait, if set to true, will wait until all resources are deleted before mark delete command as successful
DeleteWait bool `yaml:"deleteWait"` DeleteWait bool `yaml:"deleteWait"`
// Timeout is the time in seconds to wait for helmfile delete command (default 300) // Timeout is the time in seconds to wait for helmfile delete command (default 300)
@ -221,6 +223,7 @@ type RepositorySpec struct {
Keyring string `yaml:"keyring,omitempty"` Keyring string `yaml:"keyring,omitempty"`
PassCredentials bool `yaml:"passCredentials,omitempty"` PassCredentials bool `yaml:"passCredentials,omitempty"`
SkipTLSVerify bool `yaml:"skipTLSVerify,omitempty"` SkipTLSVerify bool `yaml:"skipTLSVerify,omitempty"`
PlainHttp bool `yaml:"plainHttp,omitempty"`
} }
type Inherit struct { type Inherit struct {
@ -328,6 +331,9 @@ type ReleaseSpec struct {
// InsecureSkipTLSVerify is true if the TLS verification should be skipped when fetching remote chart. // InsecureSkipTLSVerify is true if the TLS verification should be skipped when fetching remote chart.
InsecureSkipTLSVerify bool `yaml:"insecureSkipTLSVerify,omitempty"` InsecureSkipTLSVerify bool `yaml:"insecureSkipTLSVerify,omitempty"`
// PlainHttp is true if the remote charte should be fetched using HTTP and not HTTPS
PlainHttp bool `yaml:"plainHttp,omitempty"`
// These values are used in templating // These values are used in templating
VerifyTemplate *string `yaml:"verifyTemplate,omitempty"` VerifyTemplate *string `yaml:"verifyTemplate,omitempty"`
WaitTemplate *string `yaml:"waitTemplate,omitempty"` WaitTemplate *string `yaml:"waitTemplate,omitempty"`
@ -2194,7 +2200,7 @@ func (st *HelmState) TestReleases(helm helmexec.Interface, cleanup bool, timeout
} }
flags = st.appendConnectionFlags(flags, &release) flags = st.appendConnectionFlags(flags, &release)
flags = st.appendChartDownloadTLSFlags(flags, &release) flags = st.appendChartDownloadFlags(flags, &release)
return helm.TestRelease(st.createHelmContext(&release, workerIndex), release.Name, flags...) return helm.TestRelease(st.createHelmContext(&release, workerIndex), release.Name, flags...)
}) })
@ -2597,13 +2603,24 @@ func (st *HelmState) kubeConnectionFlags(release *ReleaseSpec) []string {
return flags return flags
} }
func (st *HelmState) appendChartDownloadTLSFlags(flags []string, release *ReleaseSpec) []string { func (st *HelmState) appendChartDownloadFlags(flags []string, release *ReleaseSpec) []string {
switch { var repoSkipTLSVerify, repoPlainHttp bool
case release.InsecureSkipTLSVerify: repo, _ := st.GetRepositoryAndNameFromChartName(release.Chart)
flags = append(flags, "--insecure-skip-tls-verify") if repo != nil {
case st.HelmDefaults.InsecureSkipTLSVerify: repoPlainHttp = repo.PlainHttp
repoSkipTLSVerify = repo.SkipTLSVerify
}
if release.PlainHttp || st.HelmDefaults.PlainHttp || repoPlainHttp {
flags = append(flags, "--plain-http")
// --insecure-skip-tls-verify nullifies --plain-http in helm, omit it if PlainHttp is specified
return flags
}
if release.InsecureSkipTLSVerify || st.HelmDefaults.InsecureSkipTLSVerify || repoSkipTLSVerify {
flags = append(flags, "--insecure-skip-tls-verify") flags = append(flags, "--insecure-skip-tls-verify")
} }
return flags return flags
} }
@ -2673,7 +2690,7 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp
} }
flags = st.appendConnectionFlags(flags, release) flags = st.appendConnectionFlags(flags, release)
flags = st.appendChartDownloadTLSFlags(flags, release) flags = st.appendChartDownloadFlags(flags, release)
flags = st.appendHelmXFlags(flags, release) flags = st.appendHelmXFlags(flags, release)
@ -2718,7 +2735,7 @@ func (st *HelmState) flagsForTemplate(helm helmexec.Interface, release *ReleaseS
flags = st.appendPostRenderFlags(flags, release, postRenderer) flags = st.appendPostRenderFlags(flags, release, postRenderer)
flags = st.appendPostRenderArgsFlags(flags, release, postRendererArgs) flags = st.appendPostRenderArgsFlags(flags, release, postRendererArgs)
flags = st.appendApiVersionsFlags(flags, release, kubeVersion) flags = st.appendApiVersionsFlags(flags, release, kubeVersion)
flags = st.appendChartDownloadTLSFlags(flags, release) flags = st.appendChartDownloadFlags(flags, release)
flags = st.appendShowOnlyFlags(flags, showOnly) flags = st.appendShowOnlyFlags(flags, showOnly)
common, files, err := st.namespaceAndValuesFlags(helm, release, workerIndex) common, files, err := st.namespaceAndValuesFlags(helm, release, workerIndex)
@ -2761,17 +2778,22 @@ func (st *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec,
flags = st.appendConnectionFlags(flags, release) flags = st.appendConnectionFlags(flags, release)
if st.HelmDefaults.InsecureSkipTLSVerify || release.InsecureSkipTLSVerify { flags = st.appendChartDownloadFlags(flags, release)
diffVersion, err := helmexec.GetPluginVersion("diff", settings.PluginsDirectory)
if err != nil {
return nil, nil, err
}
dv, _ := semver.NewVersion("v3.8.1")
if diffVersion.LessThan(dv) { for _, flag := range flags {
return nil, nil, fmt.Errorf("insecureSkipTLSVerify is not supported by helm-diff plugin version %s, please use at least v3.8.1", diffVersion) if flag == "--insecure-skip-tls-verify" {
diffVersion, err := helmexec.GetPluginVersion("diff", settings.PluginsDirectory)
if err != nil {
return nil, nil, err
}
dv, _ := semver.NewVersion("v3.8.1")
if diffVersion.LessThan(dv) {
return nil, nil, fmt.Errorf("insecureSkipTLSVerify is not supported by helm-diff plugin version %s, please use at least v3.8.1", diffVersion)
}
break
} }
flags = st.appendChartDownloadTLSFlags(flags, release)
} }
flags = st.appendHelmXFlags(flags, release) flags = st.appendHelmXFlags(flags, release)
@ -2827,6 +2849,38 @@ func (st *HelmState) chartVersionFlags(release *ReleaseSpec) []string {
return flags return flags
} }
func (st *HelmState) chartOCIFlags(r *ReleaseSpec) []string {
flags := []string{}
repo, _ := st.GetRepositoryAndNameFromChartName(r.Chart)
if repo != nil {
if repo.PlainHttp {
flags = append(flags, "--plain-http")
} else {
// TLS options will nullify --plain-http in helm if passed, omit it them PlainHttp is specified
if repo.SkipTLSVerify {
flags = append(flags, "--insecure-skip-tls-verify")
}
if repo.CaFile != "" {
flags = append(flags, "--ca-file", repo.CaFile)
}
if repo.CertFile != "" && repo.KeyFile != "" {
flags = append(flags, "--cert-file", repo.CertFile, "--key-file", repo.KeyFile)
}
}
if repo.Verify {
flags = append(flags, "--verify")
}
if repo.Keyring != "" {
flags = append(flags, "--keyring", repo.Keyring)
}
if repo.RegistryConfig != "" {
flags = append(flags, "--registry-config", repo.RegistryConfig)
}
}
return flags
}
func (st *HelmState) appendValuesControlModeFlag(flags []string, reuseValues bool, resetValues bool) []string { func (st *HelmState) appendValuesControlModeFlag(flags []string, reuseValues bool, resetValues bool) []string {
if !resetValues && (st.HelmDefaults.ReuseValues || reuseValues) { if !resetValues && (st.HelmDefaults.ReuseValues || reuseValues) {
flags = append(flags, "--reuse-values") flags = append(flags, "--reuse-values")
@ -3661,29 +3715,7 @@ func (st *HelmState) getOCIChart(release *ReleaseSpec, tempDir string, helm helm
if st.fs.DirectoryExistsAt(chartPath) { if st.fs.DirectoryExistsAt(chartPath) {
st.logger.Debugf("chart already exists at %s", chartPath) st.logger.Debugf("chart already exists at %s", chartPath)
} else { } else {
flags := []string{} flags := st.chartOCIFlags(release)
repo, _ := st.GetRepositoryAndNameFromChartName(release.Chart)
if repo != nil {
if repo.CaFile != "" {
flags = append(flags, "--ca-file", repo.CaFile)
}
if repo.CertFile != "" && repo.KeyFile != "" {
flags = append(flags, "--cert-file", repo.CertFile, "--key-file", repo.KeyFile)
}
if repo.SkipTLSVerify {
flags = append(flags, "--insecure-skip-tls-verify")
}
if repo.Verify {
flags = append(flags, "--verify")
}
if repo.Keyring != "" {
flags = append(flags, "--keyring", repo.Keyring)
}
if repo.RegistryConfig != "" {
flags = append(flags, "--registry-config", repo.RegistryConfig)
}
}
err := helm.ChartPull(qualifiedChartName, chartPath, flags...) err := helm.ChartPull(qualifiedChartName, chartPath, flags...)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -3456,41 +3456,126 @@ func TestCommonDiffFlags(t *testing.T) {
} }
} }
func TestAppendChartDownloadTLSFlags(t *testing.T) { func TestAppendChartDownloadFlags(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
defaultInsecureSkipTLSVerify bool spec *ReleaseSetSpec
releaseInsecureSkipTLSVerify bool expected []string
expected []string
}{ }{
{ {
name: "defaultInsecureSkipTLSVerify is true and releaseInsecureSkipTLSVerify is false", name: "SkipTLSVerify in repo",
defaultInsecureSkipTLSVerify: true, spec: &ReleaseSetSpec{
releaseInsecureSkipTLSVerify: false, Repositories: []RepositorySpec{
expected: []string{"--insecure-skip-tls-verify"}, {
Name: "testrepo",
URL: "registry/repo-path",
SkipTLSVerify: true,
},
},
Releases: []ReleaseSpec{
{
Chart: "testrepo/chartname",
},
},
},
expected: []string{
"--insecure-skip-tls-verify",
},
}, },
{ {
name: "defaultInsecureSkipTLSVerify is false and releaseInsecureSkipTLSVerify is true", name: "PlainHttp in repo, SkipTLSVerify everywhere",
defaultInsecureSkipTLSVerify: false, spec: &ReleaseSetSpec{
releaseInsecureSkipTLSVerify: true, HelmDefaults: HelmSpec{
expected: []string{"--insecure-skip-tls-verify"}, InsecureSkipTLSVerify: true,
},
Repositories: []RepositorySpec{
{
Name: "testrepo",
URL: "registry/repo-path",
SkipTLSVerify: true,
PlainHttp: true,
},
},
Releases: []ReleaseSpec{
{
Chart: "testrepo/chartname",
InsecureSkipTLSVerify: true,
},
},
},
expected: []string{
"--plain-http",
},
}, },
{ {
name: "defaultInsecureSkipTLSVerify is false and releaseInsecureSkipTLSVerify is false", name: "SkipTLSVerify in defaults",
defaultInsecureSkipTLSVerify: false, spec: &ReleaseSetSpec{
releaseInsecureSkipTLSVerify: false, HelmDefaults: HelmSpec{
expected: []string{}, InsecureSkipTLSVerify: true,
},
Releases: []ReleaseSpec{
{
Chart: "chartname",
},
},
},
expected: []string{
"--insecure-skip-tls-verify",
},
},
{
name: "SkipTLSVerify in release",
spec: &ReleaseSetSpec{
Releases: []ReleaseSpec{
{
Chart: "chartname",
InsecureSkipTLSVerify: true,
},
},
},
expected: []string{
"--insecure-skip-tls-verify",
},
},
{
name: "PlainHttp in defaults",
spec: &ReleaseSetSpec{
HelmDefaults: HelmSpec{
PlainHttp: true,
},
Releases: []ReleaseSpec{
{
Chart: "chartname",
},
},
},
expected: []string{
"--plain-http",
},
},
{
name: "PlainHttp in release",
spec: &ReleaseSetSpec{
Releases: []ReleaseSpec{
{
Chart: "chartname",
PlainHttp: true,
},
},
},
expected: []string{
"--plain-http",
},
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
st := &HelmState{} st := &HelmState{
release := &ReleaseSpec{} ReleaseSetSpec: *tt.spec,
st.HelmDefaults.InsecureSkipTLSVerify = tt.defaultInsecureSkipTLSVerify }
release.InsecureSkipTLSVerify = tt.releaseInsecureSkipTLSVerify
result := st.appendChartDownloadTLSFlags([]string{}, release) result := st.appendChartDownloadFlags([]string{}, &st.Releases[0])
require.Equal(t, tt.expected, result) require.Equal(t, tt.expected, result)
}) })
@ -3790,3 +3875,216 @@ func TestGetOCIChartPath(t *testing.T) {
}) })
} }
} }
func TestHelmState_chartOCIFlags(t *testing.T) {
tests := []struct {
name string
spec *ReleaseSetSpec
expected []string
}{
{
name: "CaFile enabled",
spec: &ReleaseSetSpec{
Repositories: []RepositorySpec{
{
Name: "oci-repo",
URL: "registry/repo-path",
OCI: true,
CaFile: "cafile",
},
},
Releases: []ReleaseSpec{
{
Chart: "oci-repo/chartname",
},
},
},
expected: []string{
"--ca-file",
"cafile",
},
},
{
name: "CertFile enabled and KeyFile disabled",
spec: &ReleaseSetSpec{
Repositories: []RepositorySpec{
{
Name: "oci-repo",
URL: "registry/repo-path",
OCI: true,
CertFile: "certfile",
},
},
Releases: []ReleaseSpec{
{
Chart: "oci-repo/chartname",
},
},
},
expected: []string{},
},
{
name: "CertFile disabled and KeyFile enabled",
spec: &ReleaseSetSpec{
Repositories: []RepositorySpec{
{
Name: "oci-repo",
URL: "registry/repo-path",
OCI: true,
KeyFile: "keyfile",
},
},
Releases: []ReleaseSpec{
{
Chart: "oci-repo/chartname",
},
},
},
expected: []string{},
},
{
name: "CertFile and KeyFile enabled",
spec: &ReleaseSetSpec{
Repositories: []RepositorySpec{
{
Name: "oci-repo",
URL: "registry/repo-path",
OCI: true,
CertFile: "certfile",
KeyFile: "keyfile",
},
},
Releases: []ReleaseSpec{
{
Chart: "oci-repo/chartname",
},
},
},
expected: []string{
"--cert-file",
"certfile",
"--key-file",
"keyfile",
},
},
{
name: "SkipTLSVerify enabled",
spec: &ReleaseSetSpec{
Repositories: []RepositorySpec{
{
Name: "oci-repo",
URL: "registry/repo-path",
OCI: true,
SkipTLSVerify: true,
},
},
Releases: []ReleaseSpec{
{
Chart: "oci-repo/chartname",
},
},
},
expected: []string{
"--insecure-skip-tls-verify",
},
},
{
name: "Verify enabled",
spec: &ReleaseSetSpec{
Repositories: []RepositorySpec{
{
Name: "oci-repo",
URL: "registry/repo-path",
OCI: true,
Verify: true,
},
},
Releases: []ReleaseSpec{
{
Chart: "oci-repo/chartname",
},
},
},
expected: []string{
"--verify",
},
},
{
name: "Keyring enabled",
spec: &ReleaseSetSpec{
Repositories: []RepositorySpec{
{
Name: "oci-repo",
URL: "registry/repo-path",
OCI: true,
Keyring: "keyring.pgp",
},
},
Releases: []ReleaseSpec{
{
Chart: "oci-repo/chartname",
},
},
},
expected: []string{
"--keyring",
"keyring.pgp",
},
},
{
name: "PlainHttp enabled",
spec: &ReleaseSetSpec{
Repositories: []RepositorySpec{
{
Name: "oci-repo",
URL: "registry/repo-path",
OCI: true,
PlainHttp: true,
},
},
Releases: []ReleaseSpec{
{
Chart: "oci-repo/chartname",
},
},
},
expected: []string{
"--plain-http",
},
},
{
name: "PlainHttp and TLS options enabled",
spec: &ReleaseSetSpec{
Repositories: []RepositorySpec{
{
Name: "oci-repo",
URL: "registry/repo-path",
OCI: true,
PlainHttp: true,
CaFile: "cafile",
CertFile: "certfile",
KeyFile: "keyfile",
SkipTLSVerify: true,
},
},
Releases: []ReleaseSpec{
{
Chart: "oci-repo/chartname",
},
},
},
expected: []string{
"--plain-http",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
st := &HelmState{
ReleaseSetSpec: *tt.spec,
}
flags := st.chartOCIFlags(&tt.spec.Releases[0])
require.Equal(t, tt.expected, flags)
})
}
}

View File

@ -38,39 +38,39 @@ func TestGenerateID(t *testing.T) {
run(testcase{ run(testcase{
subject: "baseline", subject: "baseline",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
want: "foo-values-85888d5bcb", want: "foo-values-867bdd68d",
}) })
run(testcase{ run(testcase{
subject: "different bytes content", subject: "different bytes content",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
data: []byte(`{"k":"v"}`), data: []byte(`{"k":"v"}`),
want: "foo-values-77d5b4b567", want: "foo-values-55d7dc5f87",
}) })
run(testcase{ run(testcase{
subject: "different map content", subject: "different map content",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
data: map[string]any{"k": "v"}, data: map[string]any{"k": "v"},
want: "foo-values-6b5c7d665c", want: "foo-values-58bdcf44f7",
}) })
run(testcase{ run(testcase{
subject: "different chart", subject: "different chart",
release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"}, release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"},
want: "foo-values-5b86b7bc7", want: "foo-values-67dcddd85",
}) })
run(testcase{ run(testcase{
subject: "different name", subject: "different name",
release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"}, release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"},
want: "bar-values-bcb888989", want: "bar-values-6dd4989f97",
}) })
run(testcase{ run(testcase{
subject: "specific ns", subject: "specific ns",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"}, release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"},
want: "myns-foo-values-7599b8cdcf", want: "myns-foo-values-755d4487dc",
}) })
for id, n := range ids { for id, n := range ids {