Skip to content

Commit

Permalink
Validate namespaceConfig with the apps that sharing the target namesp…
Browse files Browse the repository at this point in the history
…ace (#95)

* import apiextensions master branch

* adding new key

* validate namespaceConfig

* update unitest

* sort go import

* fmt

* fmt (2)

* address reviews

* CHANGELOG.md

* address reviews

* update apiextensions
  • Loading branch information
tomahawk28 committed Mar 16, 2021
1 parent 3c9d1ac commit 1d88514
Show file tree
Hide file tree
Showing 6 changed files with 245 additions and 3 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add `namespaceConfig` validation.

## [4.7.0] - 2021-03-05

### Added
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/giantswarm/app/v4
go 1.15

require (
github.com/giantswarm/apiextensions/v3 v3.19.0
github.com/giantswarm/apiextensions/v3 v3.21.0
github.com/giantswarm/microerror v0.3.0
github.com/giantswarm/micrologger v0.5.0
github.com/google/go-cmp v0.5.5
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/giantswarm/apiextensions/v3 v3.19.0 h1:+rw3omC3pKtxLi53gc46sMuQ3LnYNXuPDK3SLS+mnug=
github.com/giantswarm/apiextensions/v3 v3.19.0/go.mod h1:6KBexBTOZJ+amkorxw722t2HS57f+rf/8LeSSFfQNmo=
github.com/giantswarm/apiextensions/v3 v3.21.0 h1:i1T94/Ln1QjFZJmAOHzUla467cZoJxtcSFTN+EtMneg=
github.com/giantswarm/apiextensions/v3 v3.21.0/go.mod h1:6KBexBTOZJ+amkorxw722t2HS57f+rf/8LeSSFfQNmo=
github.com/giantswarm/cluster-api v0.3.10-gs h1:l2LpZlN97t7RRwKf4OBfNA2h1oVnZXWC68TFRfBMu5c=
github.com/giantswarm/cluster-api v0.3.10-gs/go.mod h1:878STePVJcBNDYFY2eCsLuHXWs6qiH3INFItEwdfWaE=
github.com/giantswarm/microerror v0.3.0 h1:V/9cXlIEddNGaRYiA0vYJmgM2+sTQy+k8M7kVjOy4XM=
Expand Down
12 changes: 12 additions & 0 deletions pkg/key/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ func AppName(customResource v1alpha1.App) string {
return customResource.Spec.Name
}

func AppNamespace(customResource v1alpha1.App) string {
return customResource.Spec.Namespace
}

func AppKubernetesNameLabel(customResource v1alpha1.App) string {
if val, ok := customResource.ObjectMeta.Labels[label.AppKubernetesName]; ok {
return val
Expand All @@ -43,6 +47,14 @@ func AppLabel(customResource v1alpha1.App) string {
return ""
}

func AppNamespaceAnnotations(customResource v1alpha1.App) map[string]string {
return customResource.Spec.NamespaceConfig.Annotations
}

func AppNamespaceLabels(customResource v1alpha1.App) map[string]string {
return customResource.Spec.NamespaceConfig.Labels
}

func AppSecretName(customResource v1alpha1.App) string {
return customResource.Spec.Config.Secret.Name
}
Expand Down
58 changes: 58 additions & 0 deletions pkg/validation/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ func (v *Validator) ValidateApp(ctx context.Context, app v1alpha1.App) (bool, er
return false, microerror.Mask(err)
}

err = v.validateNamespaceConfig(ctx, app)
if err != nil {
return false, microerror.Mask(err)
}

err = v.validateUserConfig(ctx, app)
if err != nil {
return false, microerror.Mask(err)
Expand Down Expand Up @@ -103,6 +108,59 @@ func (v *Validator) validateConfig(ctx context.Context, cr v1alpha1.App) error {
return nil
}

func (v *Validator) validateNamespaceConfig(ctx context.Context, cr v1alpha1.App) error {
annotations := key.AppNamespaceAnnotations(cr)
labels := key.AppNamespaceLabels(cr)

if annotations == nil && labels == nil {
// no-op
return nil
}

var apps []v1alpha1.App
{
lo := metav1.ListOptions{
FieldSelector: fmt.Sprintf("metadata.name!=%s", cr.Name),
}
appList, err := v.g8sClient.ApplicationV1alpha1().Apps(cr.Namespace).List(ctx, lo)
if err != nil {
return microerror.Mask(err)
}

apps = appList.Items
}

for _, app := range apps {
if key.AppNamespace(cr) != key.AppNamespace(app) {
continue
}

targetAnnotations := key.AppNamespaceAnnotations(app)
if targetAnnotations != nil && annotations != nil {
for k, v := range targetAnnotations {
originalValue, ok := annotations[k]
if ok && originalValue != v {
return microerror.Maskf(validationError, "app %#q annotation %#q for target namespace %#q collides with value %#q for app %#q",
key.AppName(cr), k, key.AppNamespace(cr), v, app.Name)
}
}
}

targetLabels := key.AppNamespaceLabels(app)
if targetLabels != nil && labels != nil {
for k, v := range targetLabels {
originalValue, ok := labels[k]
if ok && originalValue != v {
return microerror.Maskf(validationError, "app %#q label %#q for target namespace %#q collides with value %#q for app %#q",
key.AppName(cr), k, key.AppNamespace(cr), v, app.Name)
}
}
}
}

return nil
}

func (v *Validator) validateKubeConfig(ctx context.Context, cr v1alpha1.App) error {
if !key.InCluster(cr) {
ns := key.KubeConfigSecretNamespace(cr)
Expand Down
168 changes: 168 additions & 0 deletions pkg/validation/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,174 @@ func Test_ValidateMetadataConstraints(t *testing.T) {
}
}

func Test_ValidateNamespace(t *testing.T) {
ctx := context.Background()

tests := []struct {
name string
obj v1alpha1.App
apps []*v1alpha1.App
expectedErr string
}{
{
name: "case 0: flawless",
obj: v1alpha1.App{
ObjectMeta: metav1.ObjectMeta{
Name: "kiam",
Namespace: "eggs2",
},
Spec: v1alpha1.AppSpec{
Catalog: "giantswarm",
Name: "kiam",
Namespace: "kube-system",
NamespaceConfig: v1alpha1.AppSpecNamespaceConfig{
Annotations: map[string]string{
"linkerd.io/inject": "enabled",
},
},
Version: "1.4.0",
},
},
apps: []*v1alpha1.App{
{
ObjectMeta: metav1.ObjectMeta{
Name: "another-kiam-1",
Namespace: "eggs2",
},
Spec: v1alpha1.AppSpec{
Catalog: "giantswarm",
Name: "kiam",
Namespace: "kube-system",
NamespaceConfig: v1alpha1.AppSpecNamespaceConfig{
Annotations: map[string]string{
"linkerd.io/inject": "enabled",
},
},
Version: "1.3.0-rc1",
},
},
},
},
{
name: "case 1: namespace annotation collision",
obj: v1alpha1.App{
ObjectMeta: metav1.ObjectMeta{
Name: "kiam",
Namespace: "eggs2",
},
Spec: v1alpha1.AppSpec{
Catalog: "giantswarm",
Name: "kiam",
Namespace: "kube-system",
NamespaceConfig: v1alpha1.AppSpecNamespaceConfig{
Annotations: map[string]string{
"linkerd.io/inject": "enabled",
},
},
Version: "1.4.0",
},
},
apps: []*v1alpha1.App{
{
ObjectMeta: metav1.ObjectMeta{
Name: "another-kiam-1",
Namespace: "eggs2",
},
Spec: v1alpha1.AppSpec{
Catalog: "giantswarm",
Name: "kiam",
Namespace: "kube-system",
NamespaceConfig: v1alpha1.AppSpecNamespaceConfig{
Annotations: map[string]string{
"linkerd.io/inject": "disabled",
},
},
Version: "1.3.0-rc1",
},
},
},
expectedErr: "app `kiam` annotation `linkerd.io/inject` for target namespace `kube-system` collides with value `disabled` for app `another-kiam-1`",
},
{
name: "case 2: namespace label collision",
obj: v1alpha1.App{
ObjectMeta: metav1.ObjectMeta{
Name: "kiam",
Namespace: "eggs2",
},
Spec: v1alpha1.AppSpec{
Catalog: "giantswarm",
Name: "kiam",
Namespace: "kube-system",
NamespaceConfig: v1alpha1.AppSpecNamespaceConfig{
Labels: map[string]string{
"monitoring": "enabled",
},
},
Version: "1.4.0",
},
},
apps: []*v1alpha1.App{
{
ObjectMeta: metav1.ObjectMeta{
Name: "another-kiam-1",
Namespace: "eggs2",
},
Spec: v1alpha1.AppSpec{
Catalog: "giantswarm",
Name: "kiam",
Namespace: "kube-system",
NamespaceConfig: v1alpha1.AppSpecNamespaceConfig{
Labels: map[string]string{
"monitoring": "disabled",
},
},
Version: "1.3.0-rc1",
},
},
},
expectedErr: "app `kiam` label `monitoring` for target namespace `kube-system` collides with value `disabled` for app `another-kiam-1`",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
g8sObjs := make([]runtime.Object, 0)

for _, app := range tc.apps {
g8sObjs = append(g8sObjs, app)
}

c := Config{
G8sClient: fake.NewSimpleClientset(g8sObjs...),
K8sClient: clientgofake.NewSimpleClientset(),
Logger: microloggertest.New(),

Provider: "aws",
}
r, err := NewValidator(c)
if err != nil {
t.Fatalf("error == %#v, want nil", err)
}

err = r.validateNamespaceConfig(ctx, tc.obj)
switch {
case err != nil && tc.expectedErr == "":
t.Fatalf("error == %#v, want nil", err)
case err == nil && tc.expectedErr != "":
t.Fatalf("error == nil, want non-nil")
}

if err != nil && tc.expectedErr != "" {
if !strings.Contains(err.Error(), tc.expectedErr) {
t.Fatalf("error == %#v, want %#v ", err.Error(), tc.expectedErr)
}

}
})
}
}

func newTestCatalog(name string) *v1alpha1.AppCatalog {
return &v1alpha1.AppCatalog{
ObjectMeta: metav1.ObjectMeta{
Expand Down

0 comments on commit 1d88514

Please sign in to comment.