pull/32591/merge
MrJack 13 hours ago committed by GitHub
commit 866812e695
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -87,6 +87,9 @@ type Install struct {
// see: https://kubernetes.io/docs/reference/using-api/server-side-apply/
ServerSideApply bool
CreateNamespace bool
// NamespaceLabels are extra labels applied to the namespace when it is created via CreateNamespace.
// When nil the default label {"name": <namespace>} is used. When set, it replaces the default entirely.
NamespaceLabels map[string]string
// DryRunStrategy can be set to prepare, but not execute the operation and whether or not to interact with the remote cluster
DryRunStrategy DryRunStrategy
// HideSecret can be set to true when DryRun is enabled in order to hide
@ -426,16 +429,18 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st
}
if i.CreateNamespace {
nsLabels := i.NamespaceLabels
if nsLabels == nil {
nsLabels = map[string]string{"name": i.Namespace}
}
ns := &v1.Namespace{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Namespace",
},
ObjectMeta: metav1.ObjectMeta{
Name: i.Namespace,
Labels: map[string]string{
"name": i.Namespace,
},
Name: i.Namespace,
Labels: nsLabels,
},
}
buf, err := yaml.Marshal(ns)

@ -57,6 +57,17 @@ import (
"helm.sh/helm/v4/pkg/storage/driver"
)
// findNamespaceBuildInput returns the first Build input that contains a Namespace kind,
// or empty string if none is found.
func findNamespaceBuildInput(inputs [][]byte) string {
for _, b := range inputs {
if strings.Contains(string(b), "kind: Namespace") {
return string(b)
}
}
return ""
}
type nameTemplateTestCase struct {
tpl string
expected string
@ -218,6 +229,80 @@ func TestInstallRelease(t *testing.T) {
is.Equal(rcommon.StatusDeployed, lrel.Info.Status)
}
func TestInstallRelease_CreateNamespace_DefaultLabels(t *testing.T) {
req := require.New(t)
is := assert.New(t)
config := actionConfigFixture(t)
instAction := NewInstall(config)
instAction.Namespace = "mynamespace"
instAction.ReleaseName = "test-ns-labels"
instAction.CreateNamespace = true
vals := map[string]any{}
ctx, done := context.WithCancel(t.Context())
_, err := instAction.RunWithContext(ctx, buildChart(), vals)
done()
req.NoError(err)
fakeClient := config.KubeClient.(*kubefake.FailingKubeClient)
nsYAML := findNamespaceBuildInput(fakeClient.RecordedBuildInputs)
req.NotEmpty(nsYAML, "expected Build to be called for namespace creation")
is.Contains(nsYAML, "name: mynamespace", "expected default label name=<namespace>")
is.NotContains(nsYAML, "custom", "expected no custom labels")
}
func TestInstallRelease_CreateNamespace_CustomLabels(t *testing.T) {
req := require.New(t)
is := assert.New(t)
config := actionConfigFixture(t)
instAction := NewInstall(config)
instAction.Namespace = "mynamespace"
instAction.ReleaseName = "test-ns-custom-labels"
instAction.CreateNamespace = true
instAction.NamespaceLabels = map[string]string{
"env": "production",
"team": "platform",
}
vals := map[string]any{}
ctx, done := context.WithCancel(t.Context())
_, err := instAction.RunWithContext(ctx, buildChart(), vals)
done()
req.NoError(err)
fakeClient := config.KubeClient.(*kubefake.FailingKubeClient)
nsYAML := findNamespaceBuildInput(fakeClient.RecordedBuildInputs)
req.NotEmpty(nsYAML, "expected Build to be called for namespace creation")
is.Contains(nsYAML, "env: production")
is.Contains(nsYAML, "team: platform")
// The default label key "name" must not appear indented under labels (4 spaces).
is.NotContains(nsYAML, " name: mynamespace", "custom labels should replace the default, not add to it")
}
func TestInstallRelease_CreateNamespace_LabelsIgnoredWithoutCreateNamespace(t *testing.T) {
req := require.New(t)
config := actionConfigFixture(t)
instAction := NewInstall(config)
instAction.Namespace = "mynamespace"
instAction.ReleaseName = "test-ns-labels-ignored"
instAction.CreateNamespace = false
instAction.NamespaceLabels = map[string]string{"env": "production"}
vals := map[string]any{}
ctx, done := context.WithCancel(t.Context())
_, err := instAction.RunWithContext(ctx, buildChart(), vals)
done()
req.NoError(err)
fakeClient := config.KubeClient.(*kubefake.FailingKubeClient)
for _, input := range fakeClient.RecordedBuildInputs {
req.NotContains(string(input), "kind: Namespace", "Build should not be called for namespace when CreateNamespace is false")
}
}
func TestInstallReleaseWithTakeOwnership_ResourceNotOwned(t *testing.T) {
// This test will test checking ownership of a resource
// returned by the fake client. If the resource is not

@ -186,6 +186,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) {
f.BoolVar(&client.CreateNamespace, "create-namespace", false, "create the release namespace if not present")
f.StringToStringVar(&client.NamespaceLabels, "namespace-labels", nil, "labels to apply to the namespace when --create-namespace is set. Comma-separated key=value pairs. When not set the default label name=<namespace> is applied.")
f.BoolVar(&client.ForceReplace, "force-replace", false, "force resource updates by replacement")
f.BoolVar(&client.ForceReplace, "force", false, "deprecated")
f.MarkDeprecated("force", "use --force-replace instead")

@ -88,6 +88,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
valueOpts := &values.Options{}
var outfmt output.Format
var createNamespace bool
var namespaceLabels map[string]string
cmd := &cobra.Command{
Use: "upgrade [RELEASE] [CHART]",
@ -133,6 +134,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
}
instClient := action.NewInstall(cfg)
instClient.CreateNamespace = createNamespace
instClient.NamespaceLabels = namespaceLabels
instClient.ChartPathOptions = client.ChartPathOptions
instClient.ForceReplace = client.ForceReplace
instClient.DryRunStrategy = client.DryRunStrategy
@ -278,6 +280,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f := cmd.Flags()
f.BoolVar(&createNamespace, "create-namespace", false, "if --install is set, create the release namespace if not present")
f.StringToStringVar(&namespaceLabels, "namespace-labels", nil, "labels to apply to the namespace when --create-namespace is set. Comma-separated key=value pairs. When not set the default label name=<namespace> is applied.")
f.BoolVarP(&client.Install, "install", "i", false, "if a release by this name doesn't already exist, run an install")
f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored")
f.BoolVar(&client.HideSecret, "hide-secret", false, "hide Kubernetes Secrets when also using the --dry-run flag")

@ -18,6 +18,7 @@ limitations under the License.
package fake
import (
"bytes"
"io"
"sync"
"time"
@ -50,6 +51,8 @@ type FailingKubeClient struct {
WaitDuration time.Duration
// RecordedWaitOptions stores the WaitOptions passed to GetWaiter for testing
RecordedWaitOptions []kube.WaitOption
// RecordedBuildInputs stores a copy of every reader passed to Build, in order.
RecordedBuildInputs [][]byte
mu sync.Mutex
}
@ -132,7 +135,11 @@ func (f *FailingKubeClient) Update(r, modified kube.ResourceList, options ...kub
}
// Build returns the configured error if set or prints
func (f *FailingKubeClient) Build(r io.Reader, _ bool) (kube.ResourceList, error) {
func (f *FailingKubeClient) Build(r io.Reader, validate bool) (kube.ResourceList, error) {
data, _ := io.ReadAll(r)
f.mu.Lock()
f.RecordedBuildInputs = append(f.RecordedBuildInputs, data)
f.mu.Unlock()
if f.BuildError != nil {
return []*resource.Info{}, f.BuildError
}
@ -142,7 +149,7 @@ func (f *FailingKubeClient) Build(r io.Reader, _ bool) (kube.ResourceList, error
if f.BuildDummy {
return createDummyResourceList(), nil
}
return f.PrintingKubeClient.Build(r, false)
return f.PrintingKubeClient.Build(bytes.NewReader(data), validate)
}
// BuildTable returns the configured error if set or prints

Loading…
Cancel
Save