Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(helm): add depBuild and template flags to HelmDeployFlags schema #9696

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion docs-v2/content/en/schemas/v4beta12.json
Original file line number Diff line number Diff line change
Expand Up @@ -2300,6 +2300,15 @@
},
"HelmDeployFlags": {
"properties": {
"depBuild": {
"items": {
"type": "string"
},
"type": "array",
"description": "additional flags passed to (`helm dep build`).",
"x-intellij-html-description": "additional flags passed to (<code>helm dep build</code>).",
"default": "[]"
},
"global": {
"items": {
"type": "string"
Expand All @@ -2318,6 +2327,15 @@
"x-intellij-html-description": "additional flags passed to (<code>helm install</code>).",
"default": "[]"
},
"template": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you need this, or did you add it just in case? if the second one, then it'll be better to remove it, because the more things you add - the more things that should be support, it is better to add as needed

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's added here for consitency. If we don't add this for template then it means the commands helm upgrade and helm install behave differenly from helm template. The latter is used primarily in gitops workflows. Leaving out template would mean that we don't have feature parity when we use skaffold to ask helm to apply the changes directly and when we use helm to template only.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you need this - keep it) I meant that there is no need to add something if you don't it, because less code - easy to maintain)

"items": {
"type": "string"
},
"type": "array",
"description": "additional flags passed to (`helm template`).",
"x-intellij-html-description": "additional flags passed to (<code>helm template</code>).",
"default": "[]"
},
"upgrade": {
"items": {
"type": "string"
Expand All @@ -2331,7 +2349,9 @@
"preferredOrder": [
"global",
"install",
"upgrade"
"upgrade",
"depBuild",
"template"
],
"additionalProperties": false,
"type": "object",
Expand Down
58 changes: 58 additions & 0 deletions pkg/skaffold/render/renderer/helm/args.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
Copyright 2025 The Skaffold Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package helm

import (
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/graph"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/helm"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
)

func (h Helm) depBuildArgs(chartPath string) []string {
args := []string{"dep", "build", chartPath}
args = append(args, h.config.Flags.DepBuild...)
return args
}

func (h Helm) templateArgs(releaseName string, release latest.HelmRelease, builds []graph.Artifact, namespace string, additionalArgs []string) ([]string, error) {
var err error
args := []string{"template", releaseName, helm.ChartSource(release)}
args = append(args, h.config.Flags.Template...)
args = append(args, additionalArgs...)

if release.Packaged == nil && release.Version != "" {
args = append(args, "--version", release.Version)
}

args, err = helm.ConstructOverrideArgs(&release, builds, args, h.manifestOverrides)
if err != nil {
return nil, helm.UserErr("construct override args", err)
}

if namespace != "" {
args = append(args, "--namespace", namespace)
}
if release.Repo != "" {
args = append(args, "--repo")
args = append(args, release.Repo)
}
if release.SkipTests {
args = append(args, "--skip-tests")
}

return args, nil
}
151 changes: 151 additions & 0 deletions pkg/skaffold/render/renderer/helm/args_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
Copyright 2025 The Skaffold Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package helm

import (
"testing"

"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/graph"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
"github.com/GoogleContainerTools/skaffold/v2/testutil"
)

func TestDepBuildArgs(t *testing.T) {
tests := []struct {
description string
chartPath string
flags latest.HelmDeployFlags
expected []string
}{
{
description: "basic",
chartPath: "chart/path",
expected: []string{"dep", "build", "chart/path"},
},
{
description: "with flags",
chartPath: "chart/path",
flags: latest.HelmDeployFlags{
DepBuild: []string{"--skip-refresh"},
},
expected: []string{"dep", "build", "chart/path", "--skip-refresh"},
},
{
description: "with multiple flags",
chartPath: "chart/path",
flags: latest.HelmDeployFlags{
DepBuild: []string{"--skip-refresh", "--debug"},
},
expected: []string{"dep", "build", "chart/path", "--skip-refresh", "--debug"},
},
}

for _, test := range tests {
testutil.Run(t, test.description, func(t *testutil.T) {
h := Helm{
config: &latest.Helm{
Flags: test.flags,
},
}
args := h.depBuildArgs(test.chartPath)
t.CheckDeepEqual(test.expected, args)
})
}
}

func TestTemplateArgs(t *testing.T) {
tests := []struct {
description string
releaseName string
release latest.HelmRelease
builds []graph.Artifact
namespace string
additionalArgs []string
flags latest.HelmDeployFlags
expected []string
shouldErr bool
}{
{
description: "basic template",
releaseName: "release",
release: latest.HelmRelease{
ChartPath: "chart/path",
},
expected: []string{"template", "release", "chart/path"},
},
{
description: "with version",
releaseName: "release",
release: latest.HelmRelease{
ChartPath: "chart/path",
Version: "1.2.3",
},
expected: []string{"template", "release", "chart/path", "--version", "1.2.3"},
},
{
description: "with namespace",
releaseName: "release",
release: latest.HelmRelease{
ChartPath: "chart/path",
},
namespace: "namespace",
expected: []string{"template", "release", "chart/path", "--namespace", "namespace"},
},
{
description: "with repo",
releaseName: "release",
release: latest.HelmRelease{
ChartPath: "chart/path",
Repo: "repo-url",
},
expected: []string{"template", "release", "chart/path", "--repo", "repo-url"},
},
{
description: "with skipTests",
releaseName: "release",
release: latest.HelmRelease{
ChartPath: "chart/path",
SkipTests: true,
},
expected: []string{"template", "release", "chart/path", "--skip-tests"},
},
{
description: "with additional args",
releaseName: "release",
release: latest.HelmRelease{ChartPath: "chart/path"},
additionalArgs: []string{"--foo", "bar"},
expected: []string{"template", "release", "chart/path", "--foo", "bar"},
},
}

for _, test := range tests {
testutil.Run(t, test.description, func(t *testutil.T) {
h := Helm{
config: &latest.Helm{
Flags: test.flags,
},
}
args, err := h.templateArgs(test.releaseName, test.release, test.builds, test.namespace, test.additionalArgs)
if test.shouldErr {
t.CheckError(true, err)
} else {
t.CheckError(false, err)
t.CheckDeepEqual(test.expected, args)
}
})
}
}
33 changes: 8 additions & 25 deletions pkg/skaffold/render/renderer/helm/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,17 +154,6 @@ func (h Helm) generateHelmManifest(ctx context.Context, builds []graph.Artifact,
return nil, helm.UserErr(fmt.Sprintf("cannot expand chart path %q", release.ChartPath), err)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it'll be better to write tests for the generateHelmManifest and refactor it after

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I'd agree. However, the method generateHelmManifest is not atomic. This means the tests would be rather involved, including mocks of charts we pass in. This is exactly why I extracted the functionality that I was adding/modifying to a new method: it lets the tests be written in a saner manner

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it was a good idea to extract them and test independently, but at the same time, you've changed the logic as I've explained here #9696 (comment)
let's wait for the mainainers)

args := []string{"template", releaseName, helm.ChartSource(release)}
args = append(args, additionalArgs...)
if release.Packaged == nil && release.Version != "" {
args = append(args, "--version", release.Version)
}

args, err = helm.ConstructOverrideArgs(&release, builds, args, h.manifestOverrides)
if err != nil {
return nil, helm.UserErr("construct override args", err)
}

if len(release.Overrides.Values) > 0 {
overrides, err := yaml.Marshal(release.Overrides)
if err != nil {
Expand All @@ -179,11 +168,7 @@ func (h Helm) generateHelmManifest(ctx context.Context, builds []graph.Artifact,
os.Remove(constants.HelmOverridesFilename)
}()

args = append(args, "-f", constants.HelmOverridesFilename)
}

if release.SkipTests {
args = append(args, "--skip-tests")
additionalArgs = append(additionalArgs, "-f", constants.HelmOverridesFilename)
}

namespace, err := helm.ReleaseNamespace(h.namespace, release)
Expand All @@ -193,22 +178,20 @@ func (h Helm) generateHelmManifest(ctx context.Context, builds []graph.Artifact,
if h.namespace != "" {
namespace = h.namespace
}
if namespace != "" {
args = append(args, "--namespace", namespace)
}

if release.Repo != "" {
args = append(args, "--repo")
args = append(args, release.Repo)
}

outBuffer := new(bytes.Buffer)
errBuffer := new(bytes.Buffer)

args, err := h.templateArgs(releaseName, release, builds, namespace, additionalArgs)
if err != nil {
return nil, helm.UserErr("cannot construct helm template args", err)
}

// Build Chart dependencies, but allow a user to skip it.
if !release.SkipBuildDependencies && release.ChartPath != "" {
log.Entry(ctx).Info("Building helm dependencies...")
if err := helm.ExecWithStdoutAndStderr(ctx, h, io.Discard, errBuffer, false, env, "dep", "build", release.ChartPath); err != nil {
args := h.depBuildArgs(release.ChartPath)
if err := helm.ExecWithStdoutAndStderr(ctx, h, io.Discard, errBuffer, false, env, args...); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before: it can exit before dep build on lines https://github.com/GoogleContainerTools/skaffold/pull/9696/files#diff-85ea74a2a849b3a19f3a1d48895e2b217f8b1105400c66cae3463582329d07faL165, 171, 175
after: it can exit after dep build, because you moved all the errors inside the templateArgs and call it after dep build.

I'm not sure if it's ok or not, but to preserve the old behavior you can move the templateArgs call before the dep build.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

log.Entry(ctx).Info(errBuffer.String())
return nil, helm.UserErr("building helm dependencies", err)
}
Expand Down
8 changes: 6 additions & 2 deletions pkg/skaffold/schema/latest/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,12 @@ type HelmDeployFlags struct {

// Upgrade are additional flags passed to (`helm upgrade`).
Upgrade []string `yaml:"upgrade,omitempty"`

// DepBuild are additional flags passed to (`helm dep build`).
DepBuild []string `yaml:"depBuild,omitempty"`

// Template are additional flags passed to (`helm template`).
Template []string `yaml:"template,omitempty"`
}

// HelmRelease describes a helm release to be deployed.
Expand Down Expand Up @@ -1856,7 +1862,6 @@ func (clusterDetails *ClusterDetails) UnmarshalYAML(value *yaml.Node) error {
// Unmarshal the remaining values
aux := (*ClusterDetailsForUnmarshaling)(clusterDetails)
err = yaml.Unmarshal(remaining, aux)

if err != nil {
return err
}
Expand All @@ -1883,7 +1888,6 @@ func (ka *KanikoArtifact) UnmarshalYAML(value *yaml.Node) error {
// Unmarshal the remaining values
aux := (*KanikoArtifactForUnmarshaling)(ka)
err = yaml.Unmarshal(remaining, aux)

if err != nil {
return err
}
Expand Down