Skip to content

Commit e66ee80

Browse files
authored
Fix app-namespace usage for cluster options (#1333)
During the introduction of defaultNamespace feature, we started using --app-namespace flag from kapp which should be used carefully when using cluster options instead of service account Signed-off-by: Praveen Rewar <[email protected]>
1 parent 1185416 commit e66ee80

File tree

7 files changed

+206
-12
lines changed

7 files changed

+206
-12
lines changed

pkg/deploy/kapp.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func (a *Kapp) Deploy(tplOutput string, startedApplyingFunc func(),
6363

6464
metadataFile := filepath.Join(tmpMetadataDir.Path(), "app-metadata.yml")
6565

66-
args, err := a.addDeployArgs([]string{"deploy", "--app-metadata-file-output", metadataFile, "--prev-app", a.oldManagedName(), "-f", "-", "--app-namespace", a.appNamespace})
66+
args, err := a.addDeployArgs([]string{"deploy", "--app-metadata-file-output", metadataFile, "--prev-app", a.oldManagedName(), "-f", "-"})
6767
if err != nil {
6868
return exec.NewCmdRunResultWithErr(err)
6969
}
@@ -90,7 +90,7 @@ func (a *Kapp) Deploy(tplOutput string, startedApplyingFunc func(),
9090

9191
// Delete takes the app name, it shells out, running kapp delete ...
9292
func (a *Kapp) Delete(startedApplyingFunc func(), changedFunc func(exec.CmdRunResult)) exec.CmdRunResult {
93-
args, err := a.addDeleteArgs([]string{"delete", "--prev-app", a.oldManagedName(), "--app-namespace", a.appNamespace})
93+
args, err := a.addDeleteArgs([]string{"delete", "--prev-app", a.oldManagedName()})
9494
if err != nil {
9595
return exec.NewCmdRunResultWithErr(err)
9696
}
@@ -120,7 +120,6 @@ func (a *Kapp) Inspect() exec.CmdRunResult {
120120
// TODO is there a better way to deal with this?
121121
"--filter", `{"not":{"resource":{"kinds":["PodMetrics"]}}}`,
122122
"--tty",
123-
"--app-namespace", a.appNamespace,
124123
})
125124
if err != nil {
126125
return exec.NewCmdRunResultWithErr(err)
@@ -260,6 +259,10 @@ func (a *Kapp) addGenericArgs(args []string, appName string) ([]string, []string
260259
args = append(args, []string{"--namespace", a.clusterAccess.Namespace}...)
261260
}
262261

262+
if len(a.clusterAccess.DeployNamespace) > 0 {
263+
args = append(args, []string{"--app-namespace", a.clusterAccess.DeployNamespace}...)
264+
}
265+
263266
switch {
264267
case a.clusterAccess.Kubeconfig != nil:
265268
env = append(env, "KAPP_KUBECONFIG_YAML="+a.clusterAccess.Kubeconfig.AsYAML())

pkg/kubeconfig/kubeconfig.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ type AccessInfo struct {
3232
Name string
3333
Namespace string
3434

35+
DeployNamespace string
36+
3537
Kubeconfig *Restricted
3638
DangerousUsePodServiceAccount bool
3739
}
@@ -47,7 +49,7 @@ func NewKubeconfig(coreClient kubernetes.Interface, log logr.Logger) *Kubeconfig
4749

4850
// ClusterAccess takes cluster info and a ServiceAccount Name, and returns a populated kubeconfig that can connect to a cluster.
4951
// if the saName is empty then you'll connect to a cluster via the clusterOpts inside the genericOpts, otherwise you'll use the specified SA.
50-
func (k Kubeconfig) ClusterAccess(saName string, clusterOpts *v1alpha1.AppCluster, accessLocation AccessLocation, preferredNamespace string) (AccessInfo, error) {
52+
func (k Kubeconfig) ClusterAccess(saName string, clusterOpts *v1alpha1.AppCluster, accessLocation AccessLocation, defaultNamespace string) (AccessInfo, error) {
5153
var err error
5254
var clusterAccessInfo AccessInfo
5355

@@ -63,13 +65,19 @@ func (k Kubeconfig) ClusterAccess(saName string, clusterOpts *v1alpha1.AppCluste
6365
if err != nil {
6466
return AccessInfo{}, err
6567
}
68+
// Use kubeconfig preferred namespace as deploy namespace if
69+
// defaultNamespace is provided and cluster.namespace is not provided,
70+
if defaultNamespace != "" && clusterAccessInfo.DeployNamespace == "" {
71+
clusterAccessInfo.DeployNamespace = clusterAccessInfo.Kubeconfig.defaultNamespace
72+
}
6673

6774
default:
6875
return AccessInfo{}, fmt.Errorf("Expected service account or cluster specified")
6976
}
7077

71-
// If preferredNamespace is "", then kubeconfig preferred namespace will be used
72-
clusterAccessInfo.Namespace = preferredNamespace
78+
if defaultNamespace != "" {
79+
clusterAccessInfo.Namespace = defaultNamespace
80+
}
7381

7482
return clusterAccessInfo, nil
7583
}

pkg/kubeconfig/kubeconfig_restricted.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
// Restricted contains a kubernetes kubeconfig as a string
1414
type Restricted struct {
1515
result string
16+
17+
defaultNamespace string
1618
}
1719

1820
// NewKubeconfigRestricted takes kubeconfig yaml as input and returns kubeconfig yaml with certain fields restricted (removed).
@@ -65,6 +67,7 @@ func NewKubeconfigRestricted(input string) (*Restricted, error) {
6567
})
6668
}
6769

70+
defaultNamespace := "default" // TODO: Use the value from client-go directly
6871
for _, inputCtx := range inputConfig.Contexts {
6972
resultConfig.Contexts = append(resultConfig.Contexts, clientcmd.NamedContext{
7073
Name: inputCtx.Name,
@@ -74,14 +77,17 @@ func NewKubeconfigRestricted(input string) (*Restricted, error) {
7477
Namespace: inputCtx.Context.Namespace,
7578
},
7679
})
80+
if inputCtx.Name == inputConfig.CurrentContext && inputCtx.Context.Namespace != "" {
81+
defaultNamespace = inputCtx.Context.Namespace
82+
}
7783
}
7884

7985
bs, err := yaml.Marshal(resultConfig)
8086
if err != nil {
8187
return nil, fmt.Errorf("Marshaling kubeconfig: %s", err)
8288
}
8389

84-
return &Restricted{string(bs)}, nil
90+
return &Restricted{string(bs), defaultNamespace}, nil
8591
}
8692

8793
// AsYAML returns a string formatted kubernetes kubeconfig

pkg/kubeconfig/kubeconfig_secrets.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,10 @@ func (s *Secrets) Find(accessLocation AccessLocation,
4949
Name: accessLocation.Name,
5050
// Override destination namespace; if it's empty
5151
// assume kubeconfig contains preferred namespace
52-
Namespace: clusterOpts.Namespace,
53-
Kubeconfig: kubeconfigRestricted,
52+
Namespace: clusterOpts.Namespace,
53+
// Use provided namespace as app namespace
54+
DeployNamespace: clusterOpts.Namespace,
55+
Kubeconfig: kubeconfigRestricted,
5456
}
5557

5658
return pgoForCluster, nil

pkg/kubeconfig/service_accounts.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,10 @@ func (s *ServiceAccounts) Find(accessLocation AccessLocation, saName string) (Ac
5151
}
5252

5353
pgoForSA := AccessInfo{
54-
Name: accessLocation.Name,
55-
Namespace: "", // Assume kubeconfig contains preferred namespace from SA
56-
Kubeconfig: kubeconfigRestricted,
54+
Name: accessLocation.Name,
55+
Namespace: "", // Assume kubeconfig contains preferred namespace from SA
56+
DeployNamespace: accessLocation.Namespace, // App namespace is same as SA namespace
57+
Kubeconfig: kubeconfigRestricted,
5758
}
5859

5960
return pgoForSA, nil

test/e2e/kappcontroller/default_namespace_test.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package kappcontroller
55

66
import (
77
"fmt"
8+
"os"
89
"strings"
910
"testing"
1011

@@ -71,6 +72,138 @@ spec:
7172
kubectl.RunWithOpts([]string{"get", "configmap", name + ".app", "-n", env.Namespace}, e2e.RunOpts{NoNamespace: true})
7273
}
7374

75+
func Test_AppDefaultNamespace_WithTargetCluster(t *testing.T) {
76+
targetClusterKubeconfig := os.Getenv("TEST_E2E_TARGET_CLUSTER_KUBECONFIG")
77+
if targetClusterKubeconfig == "" {
78+
t.Skip("Skipping test as target cluster kubeconfig is not set")
79+
}
80+
81+
kubeconfigFile, err := os.CreateTemp("", "e2e-kubeconfig-*")
82+
assert.NoError(t, err)
83+
defer os.Remove(kubeconfigFile.Name())
84+
85+
_, err = kubeconfigFile.Write([]byte(targetClusterKubeconfig))
86+
assert.NoError(t, err)
87+
88+
env := e2e.BuildEnv(t)
89+
logger := e2e.Logger{}
90+
kapp := e2e.Kapp{t, env.Namespace, logger}
91+
kubectl := e2e.Kubectl{t, env.Namespace, logger}
92+
93+
name := "app-default-namespace-target-cluster"
94+
defaultNamespace := "e2e-default-namespace"
95+
clusterNamespace := "e2e-cluster-namespace"
96+
namespaceApp := "namespace-app"
97+
secretName := "e2e-kubeconfig-secret"
98+
99+
cleanUp := func() {
100+
kapp.Run([]string{"delete", "-a", name})
101+
102+
}
103+
cleanUpTargetCluster := func() {
104+
kapp.RunWithOpts([]string{"delete", "-a", namespaceApp, "--kubeconfig", kubeconfigFile.Name()}, e2e.RunOpts{NoNamespace: true})
105+
}
106+
107+
cleanUp()
108+
cleanUpTargetCluster()
109+
defer cleanUp()
110+
defer cleanUpTargetCluster()
111+
112+
namespaceYAML := fmt.Sprintf(`---
113+
apiVersion: v1
114+
kind: Namespace
115+
metadata:
116+
name: %s
117+
---
118+
apiVersion: v1
119+
kind: Namespace
120+
metadata:
121+
name: %s`, defaultNamespace, clusterNamespace)
122+
123+
secret := e2e.Secrets{secretName, env.Namespace, targetClusterKubeconfig}
124+
appYAML := `---
125+
apiVersion: kappctrl.k14s.io/v1alpha1
126+
kind: App
127+
metadata:
128+
name: %s
129+
namespace: %s
130+
annotations:
131+
kapp.k14s.io/change-group: "kappctrl-e2e.k14s.io/apps"
132+
spec:
133+
cluster:
134+
namespace: %s
135+
kubeconfigSecretRef:
136+
name: %s
137+
defaultNamespace: %s
138+
fetch:
139+
- inline:
140+
paths:
141+
file.yml: |
142+
apiVersion: v1
143+
kind: ConfigMap
144+
metadata:
145+
name: my-cm
146+
data:
147+
key: value
148+
template:
149+
- ytt: {}
150+
deploy:
151+
- kapp: {}`
152+
153+
// create test namespaces on target cluster
154+
kapp.RunWithOpts([]string{"deploy", "-a", namespaceApp, "-f", "-", "--kubeconfig", kubeconfigFile.Name()}, e2e.RunOpts{NoNamespace: true, StdinReader: strings.NewReader(namespaceYAML)})
155+
156+
// Provide both _defaultNamespace_ and _cluster.namespace_
157+
_, err = kapp.RunWithOpts([]string{"deploy", "-a", name, "-f", "-"}, e2e.RunOpts{AllowError: true,
158+
StdinReader: strings.NewReader(fmt.Sprintf(appYAML, name, env.Namespace, clusterNamespace, secretName, defaultNamespace) + secret.ForTargetCluster())})
159+
assert.NoError(t, err, "Expected app deploy to succeed, it did not")
160+
161+
// Assert that app resources are in defaultNamespace
162+
kubectl.RunWithOpts([]string{"get", "configmap", "my-cm", "-n", defaultNamespace, "--kubeconfig", kubeconfigFile.Name()}, e2e.RunOpts{NoNamespace: true})
163+
164+
// Assert that kapp metaconfigmap is in cluster.namespace
165+
kubectl.RunWithOpts([]string{"get", "configmap", name + ".app", "-n", clusterNamespace, "--kubeconfig", kubeconfigFile.Name()}, e2e.RunOpts{NoNamespace: true})
166+
167+
cleanUp()
168+
169+
// Provide only _cluster.namespace_
170+
_, err = kapp.RunWithOpts([]string{"deploy", "-a", name, "-f", "-"}, e2e.RunOpts{AllowError: true,
171+
StdinReader: strings.NewReader(fmt.Sprintf(appYAML, name, env.Namespace, clusterNamespace, secretName, "") + secret.ForTargetCluster())})
172+
assert.NoError(t, err, "Expected app deploy to succeed, it did not")
173+
174+
// Assert that app resources are in cluster.namespace
175+
kubectl.RunWithOpts([]string{"get", "configmap", "my-cm", "-n", clusterNamespace, "--kubeconfig", kubeconfigFile.Name()}, e2e.RunOpts{NoNamespace: true})
176+
177+
// Assert that kapp metaconfigmap is in cluster.namespace
178+
kubectl.RunWithOpts([]string{"get", "configmap", name + ".app", "-n", clusterNamespace, "--kubeconfig", kubeconfigFile.Name()}, e2e.RunOpts{NoNamespace: true})
179+
180+
cleanUp()
181+
182+
// Provide only _defaultNamespace_
183+
_, err = kapp.RunWithOpts([]string{"deploy", "-a", name, "-f", "-"}, e2e.RunOpts{AllowError: true,
184+
StdinReader: strings.NewReader(fmt.Sprintf(appYAML, name, env.Namespace, "", secretName, defaultNamespace) + secret.ForTargetCluster())})
185+
assert.NoError(t, err, "Expected app deploy to succeed, it did not")
186+
187+
// Assert that app resources are in defaultNamespace
188+
kubectl.RunWithOpts([]string{"get", "configmap", "my-cm", "-n", defaultNamespace, "--kubeconfig", kubeconfigFile.Name()}, e2e.RunOpts{NoNamespace: true})
189+
190+
// Assert that kapp metaconfigmap is in kubeconfig preferred namespace (default)
191+
kubectl.RunWithOpts([]string{"get", "configmap", name + ".app", "-n", "default", "--kubeconfig", kubeconfigFile.Name()}, e2e.RunOpts{NoNamespace: true})
192+
193+
cleanUp()
194+
195+
// Do not provide _defaultNamespace_ and _cluster.namespace_
196+
_, err = kapp.RunWithOpts([]string{"deploy", "-a", name, "-f", "-"}, e2e.RunOpts{AllowError: true,
197+
StdinReader: strings.NewReader(fmt.Sprintf(appYAML, name, env.Namespace, "", secretName, "") + secret.ForTargetCluster())})
198+
assert.NoError(t, err, "Expected app deploy to succeed, it did not")
199+
200+
// Assert that app resources are in kubeconfig preferred namespace (default)
201+
kubectl.RunWithOpts([]string{"get", "configmap", "my-cm", "-n", "default", "--kubeconfig", kubeconfigFile.Name()}, e2e.RunOpts{NoNamespace: true})
202+
203+
// Assert that kapp metaconfigmap is in kubeconfig preferred namespace (default)
204+
kubectl.RunWithOpts([]string{"get", "configmap", name + ".app", "-n", "default", "--kubeconfig", kubeconfigFile.Name()}, e2e.RunOpts{NoNamespace: true})
205+
}
206+
74207
func Test_PackageInstall_DefaultNamespace(t *testing.T) {
75208
env := e2e.BuildEnv(t)
76209
logger := e2e.Logger{}

test/e2e/secrets.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2023 VMware, Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package e2e
5+
6+
import (
7+
"fmt"
8+
"strings"
9+
)
10+
11+
// Secrets represents a secret with target cluster kubeconfig
12+
type Secrets struct {
13+
Name string
14+
Namespace string
15+
Kubeconfig string
16+
}
17+
18+
// ForTargetCluster can be used to get secret with target cluster kubeconfig
19+
func (s Secrets) ForTargetCluster() string {
20+
indentedKubeconfig := ""
21+
for _, line := range strings.Split(s.Kubeconfig, "\n") {
22+
if line != "" {
23+
indentedKubeconfig += " " + line + "\n"
24+
}
25+
}
26+
return fmt.Sprintf(`
27+
---
28+
apiVersion: v1
29+
kind: Secret
30+
metadata:
31+
name: %s
32+
namespace: %s
33+
annotations:
34+
kapp.k14s.io/change-rule.apps: "delete after deleting kappctrl-e2e.k14s.io/apps"
35+
kapp.k14s.io/change-rule.instpkgs: "delete after deleting kappctrl-e2e.k14s.io/packageinstalls"
36+
type: Opaque
37+
stringData:
38+
value: |
39+
%s
40+
`, s.Name, s.Namespace, indentedKubeconfig)
41+
}

0 commit comments

Comments
 (0)