Skip to content

Commit

Permalink
Add installationNamespace in pkgi/app crs
Browse files Browse the repository at this point in the history
Signed-off-by: Praveen Rewar <[email protected]>
  • Loading branch information
praveenrewar committed Sep 4, 2023
1 parent 2165849 commit 74567a5
Show file tree
Hide file tree
Showing 18 changed files with 380 additions and 183 deletions.
9 changes: 9 additions & 0 deletions config/config/crds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,9 @@ spec:
type: string
type: object
type: array
installationNamespace:
description: Namespace to install the App resources, by default they are installed in the App namespace (optional; v0.48.0+)
type: string
noopDelete:
description: Deletion requests for the App will result in the App CR being deleted, but its associated resources will not be deleted (optional; default=false; v0.18.0+)
type: boolean
Expand Down Expand Up @@ -995,6 +998,9 @@ spec:
type: string
type: object
type: array
installationNamespace:
description: Namespace to install the App resources, by default they are installed in the App namespace (optional; v0.48.0+)
type: string
noopDelete:
description: Deletion requests for the App will result in the App CR being deleted, but its associated resources will not be deleted (optional; default=false; v0.18.0+)
type: boolean
Expand Down Expand Up @@ -1502,6 +1508,9 @@ spec:
description: Specifies namespace in destination cluster (optional)
type: string
type: object
installationNamespace:
description: Namespace to install the Package resources, by default they are installed in the PackageInstall namespace (optional; v0.48.0+)
type: string
noopDelete:
description: When NoopDelete set to true, PackageInstall deletion should delete PackageInstall/App CR but preserve App's associated resources.
type: boolean
Expand Down
15 changes: 8 additions & 7 deletions hack/dependencies.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,19 @@
repo: vmware-tanzu/carvel-kbld
urlTemplate: https://github.com/vmware-tanzu/carvel-{{.Name}}/releases/download/{{.Version}}/{{.Name}}-{{.OS}}-{{.Arch}}
version: v0.37.4
# To be updated after official kapp release
- checksums:
darwin:
amd64: e71048d2b11a2c10258079cc134d7d2c2b6584429202a6212306380d3a8c0a30
arm64: 3660dd8efe83c1356e05255307fa6f65825ba694d96b93bc38c6a43d7e6d7a8c
amd64: 1fee9ff094dfc6750f879df6c111048d20392931d03a41d6c7f68d5db3d05e2d
arm64: c2c61530fbcb8812001338eb5c2c41289f810efc5aafb957ff21ac7611d0d9e4
linux:
amd64: b253ea9cf6add07f9497955147dc12e8612c24c36dc9929c9a4fecdc76752bd3
arm64: 25491298f6783a8b337d2ebdecf749f7750cf10260fe37086315a9c7da0b558f
amd64: f70119163d84b188b818ea7cdf6a88b4482e4e51248d30593a0b47399a1657e6
arm64: b1efb38cbc93630e377e74203e64f90b8cdffab2ff2e24c2365baec179a05e3b
dev: true
name: kapp
repo: vmware-tanzu/carvel-kapp
urlTemplate: https://github.com/vmware-tanzu/carvel-{{.Name}}/releases/download/{{.Version}}/{{.Name}}-{{.OS}}-{{.Arch}}
version: v0.58.0
repo: praveenrewar/kapp
urlTemplate: https://github.com/praveenrewar/{{.Name}}/releases/download/{{.Version}}/{{.Name}}-{{.OS}}-{{.Arch}}
version: v0.58.0-alpha2
- checksums:
darwin:
amd64: 6a5290066d8fbe26aa0603902825bbca55b97f011e97949677eb937ace2d2e3e
Expand Down
361 changes: 201 additions & 160 deletions pkg/apis/kappctrl/v1alpha1/generated.pb.go

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions pkg/apis/kappctrl/v1alpha1/generated.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions pkg/apis/kappctrl/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ type AppSpec struct {
// (optional; default=false; v0.18.0+)
// +optional
NoopDelete bool `json:"noopDelete,omitempty" protobuf:"varint,9,opt,name=noopDelete"`
// Specifies the default namespace to install the App resources, by default this is
// same as the App's namespace (optional; v0.48.0+)
// +optional
InstallationNamespace string `json:"installationNamespace,omitempty" protobuf:"bytes,10,opt,name=installationNamespace"`
}

// +k8s:openapi-gen=true
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/packaging/v1alpha1/package_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ type PackageInstallSpec struct {
// associated resources.
// +optional
NoopDelete bool `json:"noopDelete,omitempty"`
// Specifies the default namespace to install the Package resources, by default this is
// same as the PackageInstall namespace
// +optional
InstallationNamespace string `json:"installationNamespace,omitempty"`
}

type PackageRef struct {
Expand Down
7 changes: 7 additions & 0 deletions pkg/apiserver/openapi/zz_generated.openapi.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion pkg/app/app_deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,9 @@ func (a *App) trySaveMetadata(kapp *ctldep.Kapp) {
func (a *App) newKapp(kapp v1alpha1.AppDeployKapp, cancelCh chan struct{}) (*ctldep.Kapp, error) {

return a.deployFactory.NewKapp(kapp, a.app.Spec.ServiceAccountName,
a.app.Spec.Cluster, cancelCh, kubeconfig.AccessLocation{Name: a.app.Name, Namespace: a.app.Namespace})
a.app.Spec.Cluster, cancelCh, kubeconfig.AccessLocation{Name: a.app.Name, Namespace: a.app.Namespace},
a.app.Spec.InstallationNamespace, a.app.Namespace,
)
}

type cancelCondition func(v1alpha1.App) bool
Expand Down
2 changes: 1 addition & 1 deletion pkg/componentinfo/component_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (ci *ComponentInfo) KubernetesVersion(serviceAccountName string, specCluste
return ci.parseAndScrubVersion(v.String())

case specCluster != nil:
accessInfo, err := ci.clusterAccess.ClusterAccess(serviceAccountName, specCluster, kubeconfig.AccessLocation{Name: objMeta.Name, Namespace: objMeta.Namespace})
accessInfo, err := ci.clusterAccess.ClusterAccess(serviceAccountName, specCluster, kubeconfig.AccessLocation{Name: objMeta.Name, Namespace: objMeta.Namespace}, "")
if err != nil {
return semver.Version{}, err
}
Expand Down
11 changes: 6 additions & 5 deletions pkg/deploy/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,22 @@ func NewFactory(coreClient kubernetes.Interface, kubeconfig *kubeconfig.Kubeconf

// NewKapp configures and returns a deployer of type Kapp
func (f Factory) NewKapp(opts v1alpha1.AppDeployKapp, saName string,
clusterOpts *v1alpha1.AppCluster, cancelCh chan struct{}, location kubeconfig.AccessLocation) (*Kapp, error) {
clusterOpts *v1alpha1.AppCluster, cancelCh chan struct{}, location kubeconfig.AccessLocation,
installationNamespace string, appNamespace string) (*Kapp, error) {

clusterAccess, err := f.kubeconfig.ClusterAccess(saName, clusterOpts, location)
clusterAccess, err := f.kubeconfig.ClusterAccess(saName, clusterOpts, location, installationNamespace)
if err != nil {
return nil, err
}

const suffix string = ".app"
return NewKapp(suffix, opts, clusterAccess,
f.globalKappDeployRawOpts(), cancelCh, f.cmdRunner), nil
f.globalKappDeployRawOpts(), cancelCh, f.cmdRunner, appNamespace), nil
}

// NewKappPrivileged is used for package repositories where users aren't required to provide
// a service account, so it will install resources using its own privileges.
func (f Factory) NewKappPrivilegedForPackageRepository(opts v1alpha1.AppDeployKapp, clusterAccess kubeconfig.AccessInfo, cancelCh chan struct{}) (*Kapp, error) {
func (f Factory) NewKappPrivilegedForPackageRepository(opts v1alpha1.AppDeployKapp, clusterAccess kubeconfig.AccessInfo, cancelCh chan struct{}, appNamespace string) (*Kapp, error) {

const suffix string = ".pkgr"

Expand All @@ -61,7 +62,7 @@ func (f Factory) NewKappPrivilegedForPackageRepository(opts v1alpha1.AppDeployKa
DangerousUsePodServiceAccount: true,
}

return NewKapp(suffix, opts, kconfAccess, f.globalKappDeployRawOpts(), cancelCh, f.cmdRunner), nil
return NewKapp(suffix, opts, kconfAccess, f.globalKappDeployRawOpts(), cancelCh, f.cmdRunner, appNamespace), nil
}

func (f Factory) globalKappDeployRawOpts() []string {
Expand Down
10 changes: 6 additions & 4 deletions pkg/deploy/kapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type Kapp struct {
cancelCh chan struct{}
cmdRunner exec.CmdRunner
appMeta *Meta
appNamespace string
}

var _ Deploy = &Kapp{}
Expand All @@ -42,9 +43,9 @@ var _ Deploy = &Kapp{}
// additional info from the larger app resource (e.g. service account, name, namespace) as genericOpts,
// and a cancel channel that gets passed through to the exec call that runs kapp.
func NewKapp(appSuffix string, opts v1alpha1.AppDeployKapp, clusterAccess kubeconfig.AccessInfo,
globalDeployRawOpts []string, cancelCh chan struct{}, cmdRunner exec.CmdRunner) *Kapp {
globalDeployRawOpts []string, cancelCh chan struct{}, cmdRunner exec.CmdRunner, appNamespace string) *Kapp {

return &Kapp{appSuffix, opts, clusterAccess, globalDeployRawOpts, cancelCh, cmdRunner, nil}
return &Kapp{appSuffix, opts, clusterAccess, globalDeployRawOpts, cancelCh, cmdRunner, nil, appNamespace}
}

// Deploy takes the output from templating, and the app name,
Expand All @@ -62,7 +63,7 @@ func (a *Kapp) Deploy(tplOutput string, startedApplyingFunc func(),

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

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

// Delete takes the app name, it shells out, running kapp delete ...
func (a *Kapp) Delete(startedApplyingFunc func(), changedFunc func(exec.CmdRunResult)) exec.CmdRunResult {
args, err := a.addDeleteArgs([]string{"delete", "--prev-app", a.oldManagedName()})
args, err := a.addDeleteArgs([]string{"delete", "--prev-app", a.oldManagedName(), "--app-namespace", a.appNamespace})
if err != nil {
return exec.NewCmdRunResultWithErr(err)
}
Expand Down Expand Up @@ -119,6 +120,7 @@ func (a *Kapp) Inspect() exec.CmdRunResult {
// TODO is there a better way to deal with this?
"--filter", `{"not":{"resource":{"kinds":["PodMetrics"]}}}`,
"--tty",
"--app-namespace", a.appNamespace,
})
if err != nil {
return exec.NewCmdRunResultWithErr(err)
Expand Down
4 changes: 2 additions & 2 deletions pkg/kubeconfig/kubeconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,13 @@ func NewKubeconfig(coreClient kubernetes.Interface, log logr.Logger) *Kubeconfig

// ClusterAccess takes cluster info and a ServiceAccount Name, and returns a populated kubeconfig that can connect to a cluster.
// 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.
func (k Kubeconfig) ClusterAccess(saName string, clusterOpts *v1alpha1.AppCluster, accessLocation AccessLocation) (AccessInfo, error) {
func (k Kubeconfig) ClusterAccess(saName string, clusterOpts *v1alpha1.AppCluster, accessLocation AccessLocation, preferredNamespace string) (AccessInfo, error) {
var err error
var clusterAccessInfo AccessInfo

switch {
case len(saName) > 0:
clusterAccessInfo, err = k.serviceAccounts.Find(accessLocation, saName)
clusterAccessInfo, err = k.serviceAccounts.Find(accessLocation, saName, preferredNamespace)
if err != nil {
return AccessInfo{}, err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/kubeconfig/service_accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func NewServiceAccounts(coreClient kubernetes.Interface, log logr.Logger) *Servi
}

// Find takes the location of the credentials service account and returns information to access the cluster
func (s *ServiceAccounts) Find(accessLocation AccessLocation, saName string) (AccessInfo, error) {
func (s *ServiceAccounts) Find(accessLocation AccessLocation, saName string, preferredNamespace string) (AccessInfo, error) {
kubeconfigYAML, err := s.fetchServiceAccount(accessLocation.Namespace, saName)
if err != nil {
return AccessInfo{}, err
Expand All @@ -52,7 +52,7 @@ func (s *ServiceAccounts) Find(accessLocation AccessLocation, saName string) (Ac

pgoForSA := AccessInfo{
Name: accessLocation.Name,
Namespace: "", // Assume kubeconfig contains preferred namespace from SA
Namespace: preferredNamespace, // If preferredNamespace is "", then kubeconfig preferred namespace will be used
Kubeconfig: kubeconfigRestricted,
}

Expand Down
1 change: 1 addition & 0 deletions pkg/packageinstall/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func NewApp(existingApp *v1alpha1.App, pkgInstall *pkgingv1alpha1.PackageInstall
desiredApp.Spec.Paused = pkgInstall.Spec.Paused
desiredApp.Spec.Canceled = pkgInstall.Spec.Canceled
desiredApp.Spec.Cluster = pkgInstall.Spec.Cluster
desiredApp.Spec.InstallationNamespace = pkgInstall.Spec.InstallationNamespace

err := controllerutil.SetControllerReference(pkgInstall, desiredApp, scheme.Scheme)
if err != nil {
Expand Down
39 changes: 39 additions & 0 deletions pkg/packageinstall/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -677,3 +677,42 @@ func TestAppPackageDetailsAnnotations(t *testing.T) {

require.Equal(t, expectedObjectMeta, app.ObjectMeta)
}

// TestAppPackageIntallInstallationNamespace tests the creation of an App when using PackageInstall with a installationNamespace defined.
func TestAppPackageIntallInstallationNamespace(t *testing.T) {
ipkg := &pkgingv1alpha1.PackageInstall{
ObjectMeta: metav1.ObjectMeta{
Name: "app",
Namespace: "default",
},
Spec: pkgingv1alpha1.PackageInstallSpec{
InstallationNamespace: "installation-namespace",
SyncPeriod: &metav1.Duration{100 * time.Second},
},
}

pkgVersion := datapkgingv1alpha1.Package{
Spec: datapkgingv1alpha1.PackageSpec{
RefName: "expec-pkg",
Version: "1.5.0",
Template: datapkgingv1alpha1.AppTemplateSpec{
Spec: &kcv1alpha1.AppSpec{},
},
},
}

app, err := packageinstall.NewApp(&kcv1alpha1.App{}, ipkg, pkgVersion, packageinstall.Opts{})
require.NoError(t, err)

expectedApp := &kcv1alpha1.App{
Spec: kcv1alpha1.AppSpec{
InstallationNamespace: "installation-namespace",
SyncPeriod: &metav1.Duration{100 * time.Second},
},
}

// Not interested in metadata in this test
app.ObjectMeta = metav1.ObjectMeta{}

require.Equal(t, expectedApp, app, "App does not match expected app")
}
4 changes: 4 additions & 0 deletions pkg/packageinstall/packageinstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,10 @@ func (pi *PackageInstallCR) reconcileDelete(modelStatus *reconciler.Status) (rec
existingApp.Spec.Canceled = pi.model.Spec.Canceled
}

if existingApp.Spec.InstallationNamespace != pi.model.Spec.InstallationNamespace {
existingApp.Spec.InstallationNamespace = pi.model.Spec.InstallationNamespace
}

if !equality.Semantic.DeepEqual(existingApp, unchangeExistingApp) {
existingApp, err = pi.kcclient.KappctrlV1alpha1().Apps(existingApp.Namespace).Update(
context.Background(), existingApp, metav1.UpdateOptions{})
Expand Down
2 changes: 1 addition & 1 deletion pkg/pkgrepository/app_deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,5 @@ func (a *App) delete() exec.CmdRunResult {

func (a *App) newKapp(kapp v1alpha1.AppDeployKapp, cancelCh chan struct{}) (*ctldep.Kapp, error) {
genericOpts := kubeconfig.AccessInfo{Name: a.app.Name, Namespace: a.app.Namespace}
return a.deployFactory.NewKappPrivilegedForPackageRepository(kapp, genericOpts, cancelCh)
return a.deployFactory.NewKappPrivilegedForPackageRepository(kapp, genericOpts, cancelCh, a.app.Namespace)
}
77 changes: 77 additions & 0 deletions test/e2e/kappcontroller/installation_namespace_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2023 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0

package kappcontroller

import (
"fmt"
"strings"
"testing"

"github.com/vmware-tanzu/carvel-kapp-controller/test/e2e"
)

func Test_AppInstallationNamespace(t *testing.T) {
env := e2e.BuildEnv(t)
logger := e2e.Logger{}
kapp := e2e.Kapp{t, env.Namespace, logger}
kubectl := e2e.Kubectl{t, env.Namespace, logger}
name := "app-installation-namespace"
installationNamespace := "installation-namespace"
installationNamespaceApp := "installation-namespace-app"

cleanUp := func() {
kapp.Run([]string{"delete", "-a", name})
kapp.Run([]string{"delete", "-a", installationNamespaceApp})
}
cleanUp()
defer cleanUp()

namespaceYAML := fmt.Sprintf(`---
apiVersion: v1
kind: Namespace
metadata:
name: %s`, installationNamespace)

sas := e2e.ServiceAccounts{env.Namespace}
appYAML := fmt.Sprintf(`---
apiVersion: kappctrl.k14s.io/v1alpha1
kind: App
metadata:
name: %s
namespace: %s
spec:
serviceAccountName: kappctrl-e2e-ns-sa
installationNamespace: %s
fetch:
- inline:
paths:
file.yml: |
apiVersion: v1
kind: ConfigMap
metadata:
name: my-cm
data:
key: value
template:
- ytt: {}
deploy:
- kapp: {}`, name, env.Namespace, installationNamespace) + sas.ForClusterYAML()

kapp.RunWithOpts([]string{"deploy", "-a", installationNamespaceApp, "-f", "-"}, e2e.RunOpts{StdinReader: strings.NewReader(namespaceYAML)})

_, err := kapp.RunWithOpts([]string{"deploy", "-a", name, "-f", "-"}, e2e.RunOpts{StdinReader: strings.NewReader(appYAML)})
if err != nil {
t.Fatalf("Expected app deploy to succeed, it did not: %v", err)
}

_, err = kubectl.RunWithOpts([]string{"get", "configmap", "my-cm", "-n", installationNamespace}, e2e.RunOpts{AllowError: true, NoNamespace: true})
if err != nil {
t.Fatalf("Expected to find app resources in %s namespace, but did not: %v", installationNamespace, err)
}

_, err = kubectl.RunWithOpts([]string{"get", "configmap", name+".app", "-n", env.Namespace}, e2e.RunOpts{AllowError: true, NoNamespace: true})
if err != nil {
t.Fatalf("Expected to find kapp app configmap in %s namespace, but did not: %v", env.Namespace, err)
}
}

0 comments on commit 74567a5

Please sign in to comment.