From 446c62f1770b47fb5124d52fb196720906a6fe0f Mon Sep 17 00:00:00 2001 From: SinghAman21 Date: Sun, 16 Aug 2026 20:34:55 +0530 Subject: [PATCH] fix(action): skip existing release namespace creation Signed-off-by: SinghAman21 --- pkg/action/install.go | 69 +++++++++++++++++++++++------------ pkg/action/install_test.go | 75 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 24 deletions(-) diff --git a/pkg/action/install.go b/pkg/action/install.go index 605c423bc..a4df044de 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -426,30 +426,7 @@ func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[st } if i.CreateNamespace { - ns := &v1.Namespace{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "Namespace", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: i.Namespace, - Labels: map[string]string{ - "name": i.Namespace, - }, - }, - } - buf, err := yaml.Marshal(ns) - if err != nil { - return nil, err - } - resourceList, err := i.cfg.KubeClient.Build(bytes.NewBuffer(buf), true) - if err != nil { - return nil, err - } - - if _, err := i.cfg.KubeClient.Create( - resourceList, - kube.ClientCreateOptionServerSideApply(i.ServerSideApply, false)); err != nil && !apierrors.IsAlreadyExists(err) { + if err := i.createReleaseNamespace(); err != nil { return nil, err } } @@ -578,6 +555,50 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource return rel, nil } +func (i *Install) createReleaseNamespace() error { + ns := &v1.Namespace{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Namespace", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: i.Namespace, + Labels: map[string]string{ + "name": i.Namespace, + }, + }, + } + buf, err := yaml.Marshal(ns) + if err != nil { + return err + } + resourceList, err := i.cfg.KubeClient.Build(bytes.NewBuffer(buf), true) + if err != nil { + return err + } + + err = resourceList.Visit(func(info *resource.Info, err error) error { + if err != nil { + return err + } + _, err = resource.NewHelper(info.Client, info.Mapping).Get(info.Namespace, info.Name) + return err + }) + if err == nil { + return nil + } + if !apierrors.IsNotFound(err) && !apierrors.IsForbidden(err) { + return err + } + + if _, err := i.cfg.KubeClient.Create( + resourceList, + kube.ClientCreateOptionServerSideApply(i.ServerSideApply, false)); err != nil && !apierrors.IsAlreadyExists(err) { + return err + } + return nil +} + func (i *Install) failRelease(rel *release.Release, err error) (*release.Release, error) { rel.SetStatus(rcommon.StatusFailed, fmt.Sprintf("Release %q failed: %s", i.ReleaseName, err.Error())) if i.RollbackOnFailure { diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 2d83abe27..801372926 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -35,6 +35,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -177,6 +178,48 @@ func installAction(t *testing.T) *Install { return instAction } +type namespaceKubeClient struct { + kubefake.FailingKubeClient + namespace string + getStatus int + createCalls int +} + +func (c *namespaceKubeClient) Build(_ io.Reader, _ bool) (kube.ResourceList, error) { + obj := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: c.namespace, + }, + } + + body := kuberuntime.EncodeOrDie(corev1Codec, obj) + if c.getStatus != http.StatusOK { + body = fmt.Sprintf(`{"apiVersion":"v1","kind":"Status","status":"Failure","reason":"%s","code":%d}`, + http.StatusText(c.getStatus), c.getStatus) + } + + return kube.ResourceList{{ + Name: c.namespace, + Mapping: &meta.RESTMapping{ + Resource: schema.GroupVersionResource{Version: "v1", Resource: "namespaces"}, + GroupVersionKind: schema.GroupVersionKind{Version: "v1", Kind: "Namespace"}, + Scope: meta.RESTScopeRoot, + }, + Object: obj, + Client: fakeClientWith(c.getStatus, coreV1GV, body), + }}, nil +} + +func (c *namespaceKubeClient) Create(resources kube.ResourceList, options ...kube.ClientCreateOption) (*kube.Result, error) { + c.createCalls++ + return c.FailingKubeClient.Create(resources, options...) +} + +var ( + coreV1GV = schema.GroupVersion{Version: "v1"} + corev1Codec = scheme.Codecs.CodecForVersions(scheme.Codecs.LegacyCodec(coreV1GV), scheme.Codecs.UniversalDecoder(coreV1GV), coreV1GV, coreV1GV) +) + func TestInstallRelease(t *testing.T) { is := assert.New(t) req := require.New(t) @@ -218,6 +261,38 @@ func TestInstallRelease(t *testing.T) { is.Equal(rcommon.StatusDeployed, lrel.Info.Status) } +func TestInstallCreateReleaseNamespaceAlreadyExists(t *testing.T) { + config := actionConfigFixture(t) + kubeClient := &namespaceKubeClient{ + FailingKubeClient: kubefake.FailingKubeClient{ + PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, + }, + namespace: "spaced", + getStatus: http.StatusOK, + } + config.KubeClient = kubeClient + instAction := installActionWithConfig(config) + + require.NoError(t, instAction.createReleaseNamespace()) + assert.Zero(t, kubeClient.createCalls) +} + +func TestInstallCreateReleaseNamespaceNotFound(t *testing.T) { + config := actionConfigFixture(t) + kubeClient := &namespaceKubeClient{ + FailingKubeClient: kubefake.FailingKubeClient{ + PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, + }, + namespace: "spaced", + getStatus: http.StatusNotFound, + } + config.KubeClient = kubeClient + instAction := installActionWithConfig(config) + + require.NoError(t, instAction.createReleaseNamespace()) + assert.Equal(t, 1, kubeClient.createCalls) +} + 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