Skip to content

Commit

Permalink
Fix namespace management (#579)
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew Suderman committed Apr 25, 2022
1 parent 3d5f94e commit de1ad16
Show file tree
Hide file tree
Showing 5 changed files with 478 additions and 42 deletions.
46 changes: 4 additions & 42 deletions pkg/course/course.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ type FileV2 struct {
// Hooks is a set of scripts to be run before or after the release is installed.
Hooks Hooks `yaml:"hooks,omitempty" json:"hooks,omitempty"`
// NamespaceMgmt contains the default namespace config for all namespaces managed by this course.
NamespaceMgmt NamespaceMgmt `yaml:"namespace_management,omitempty" json:"namespace_management,omitempty"`
Secrets SecretsList `yaml:"secrets,omitempty" json:"secrets,omitempty"`
NamespaceMgmt *NamespaceMgmt `yaml:"namespace_management,omitempty" json:"namespace_management,omitempty"`
Secrets SecretsList `yaml:"secrets,omitempty" json:"secrets,omitempty"`
// Releases is the list of releases that should be maintained by this course file.
Releases []*Release `yaml:"releases,omitempty" json:"releases,omitempty"`
// HelmArgs is a list of arguments to pass to helm commands
Expand Down Expand Up @@ -192,11 +192,8 @@ type FileV1 struct {
// Hooks is a set of scripts to be run before or after the release is installed.
Hooks Hooks `yaml:"hooks" json:"hooks"`
// NamespaceMgmt contains the default namespace config for all namespaces managed by this course.
NamespaceMgmt struct {
// Default is the default namespace config for this course
Default *NamespaceConfig `yaml:"default" json:"default"`
} `yaml:"namespace_management" json:"namespace_management"`
Secrets SecretsList `yaml:"secrets,omitempty" json:"secrets,omitempty"`
NamespaceMgmt *NamespaceMgmt `yaml:"namespace_management" json:"namespace_management"`
Secrets SecretsList `yaml:"secrets,omitempty" json:"secrets,omitempty"`
// Charts is the list of releases. In the actual file this will be a map, but we must convert to a list to preserve order.
// This conversion is done in the ChartsListV1 UnmarshalYAML function.
Charts ChartsListV1 `yaml:"charts" json:"charts"`
Expand Down Expand Up @@ -549,41 +546,6 @@ func (f *FileV2) populateEmptyChartNames() {
}
}

// populateNamespaceManagement populates each release with the default namespace management settings if they are not set
func (f *FileV2) populateNamespaceManagement() {
var emptyNamespaceMgmt NamespaceConfig
if f.NamespaceMgmt.Default == nil {
f.NamespaceMgmt.Default = &emptyNamespaceMgmt
f.NamespaceMgmt.Default.Settings.Overwrite = boolPtr(false)
} else if f.NamespaceMgmt.Default.Settings.Overwrite == nil {
f.NamespaceMgmt.Default.Settings.Overwrite = boolPtr(false)
}
for releaseIndex, release := range f.Releases {
if release.NamespaceMgmt == nil {
klog.V(5).Infof("using default namespace management for release: %s", release.Name)
release.NamespaceMgmt = f.NamespaceMgmt.Default
f.Releases[releaseIndex] = release
} else {
release.NamespaceMgmt = mergeNamespaceManagement(f.NamespaceMgmt.Default, release.NamespaceMgmt)
}
}
}

func mergeNamespaceManagement(defaults *NamespaceConfig, mergeInto *NamespaceConfig) *NamespaceConfig {
d := defaults
for k, v := range mergeInto.Metadata.Annotations {
d.Metadata.Annotations[k] = v
}
for k, v := range mergeInto.Metadata.Labels {
d.Metadata.Labels[k] = v
}
if mergeInto.Settings.Overwrite != nil {
d.Settings.Overwrite = mergeInto.Settings.Overwrite
}
mergeInto = d
return mergeInto
}

func (f *FileV2) validateJsonSchema(schemaData []byte) error {
klog.V(10).Infof("validating course file against schema: \n%s", string(schemaData))
schema, err := gojsonschema.NewSchema(gojsonschema.NewBytesLoader(schemaData))
Expand Down
58 changes: 58 additions & 0 deletions pkg/course/namespace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package course

import (
"k8s.io/klog/v2"
)

// populateNamespaceManagement populates each release with the default namespace management settings if they are not set
func (f *FileV2) populateNamespaceManagement() {
var emptyNamespaceMgmt NamespaceConfig
if f.NamespaceMgmt == nil {
f.NamespaceMgmt = &NamespaceMgmt{}
}
if f.NamespaceMgmt.Default == nil {
f.NamespaceMgmt.Default = &emptyNamespaceMgmt
f.NamespaceMgmt.Default.Settings.Overwrite = boolPtr(false)
} else if f.NamespaceMgmt.Default.Settings.Overwrite == nil {
f.NamespaceMgmt.Default.Settings.Overwrite = boolPtr(false)
}

for releaseIndex, release := range f.Releases {
newRelease := *release
if newRelease.NamespaceMgmt == nil {
klog.V(5).Infof("using default namespace management for release: %s", release.Name)
newRelease.NamespaceMgmt = f.NamespaceMgmt.Default
} else {
newRelease.NamespaceMgmt = mergeNamespaceManagement(*f.NamespaceMgmt.Default, *newRelease.NamespaceMgmt)

}
f.Releases[releaseIndex] = &newRelease
}
}

// mergeNamespaceManagement merges the default namespace management settings with the release specific settings
func mergeNamespaceManagement(defaults NamespaceConfig, mergeInto NamespaceConfig) *NamespaceConfig {
for k, v := range defaults.Metadata.Annotations {
if mergeInto.Metadata.Annotations == nil {
mergeInto.Metadata.Annotations = map[string]string{}
}
if mergeInto.Metadata.Annotations[k] == "" {
mergeInto.Metadata.Annotations[k] = v
}
}

for k, v := range defaults.Metadata.Labels {
if mergeInto.Metadata.Labels == nil {
mergeInto.Metadata.Labels = map[string]string{}
}
if mergeInto.Metadata.Labels[k] == "" {
mergeInto.Metadata.Labels[k] = v
}
}

if mergeInto.Settings.Overwrite == nil {
mergeInto.Settings.Overwrite = defaults.Settings.Overwrite
}

return &mergeInto
}
252 changes: 252 additions & 0 deletions pkg/course/namespace_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
package course

import (
"testing"

"github.com/stretchr/testify/assert"
)

func Test_mergeNamespaceManagement(t *testing.T) {
type args struct {
defaults NamespaceConfig
mergeInto NamespaceConfig
}
tests := []struct {
name string
args args
want *NamespaceConfig
}{
{
name: "basic merge",
args: args{
defaults: NamespaceConfig{
Metadata: NSMetadata{
Annotations: map[string]string{
"default-annotation": "default-value",
},
Labels: map[string]string{
"default-label": "default-value",
},
},
Settings: NSSettings{
Overwrite: boolPtr(false),
},
},
mergeInto: NamespaceConfig{
Metadata: NSMetadata{
Annotations: map[string]string{
"merge-annotation": "merge-value",
},
Labels: map[string]string{
"merge-label": "merge-value",
},
},
Settings: NSSettings{
Overwrite: boolPtr(false),
},
},
},
want: &NamespaceConfig{
Metadata: NSMetadata{
Annotations: map[string]string{
"default-annotation": "default-value",
"merge-annotation": "merge-value",
},
Labels: map[string]string{
"default-label": "default-value",
"merge-label": "merge-value",
},
},
Settings: NSSettings{
Overwrite: boolPtr(false),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := mergeNamespaceManagement(tt.args.defaults, tt.args.mergeInto)
assert.EqualValues(t, tt.want, got)
})
}
}

func TestFileV2_populateNamespaceManagement(t *testing.T) {
tests := []struct {
name string
file *FileV2
want *FileV2
}{
{
name: "empty default",
file: &FileV2{
Releases: []*Release{},
NamespaceMgmt: &NamespaceMgmt{},
},
want: &FileV2{
NamespaceMgmt: &NamespaceMgmt{
Default: &NamespaceConfig{
Settings: NSSettings{
Overwrite: boolPtr(false),
},
},
},
Releases: []*Release{},
},
},
{
name: "empty default overwrite",
file: &FileV2{
Releases: []*Release{},
NamespaceMgmt: &NamespaceMgmt{
Default: &NamespaceConfig{
Settings: NSSettings{
Overwrite: nil,
},
},
},
},
want: &FileV2{
NamespaceMgmt: &NamespaceMgmt{
Default: &NamespaceConfig{
Settings: NSSettings{
Overwrite: boolPtr(false),
},
},
},
Releases: []*Release{},
},
},
{
name: "release default",
file: &FileV2{
Releases: []*Release{
{
Name: "default",
},
},
NamespaceMgmt: &NamespaceMgmt{
Default: nil,
},
},
want: &FileV2{
NamespaceMgmt: &NamespaceMgmt{
Default: &NamespaceConfig{
Settings: NSSettings{
Overwrite: boolPtr(false),
},
},
},
Releases: []*Release{
{
Name: "default",
NamespaceMgmt: &NamespaceConfig{
Settings: NSSettings{
Overwrite: boolPtr(false),
},
},
},
},
},
},
{
name: "release specific",
file: &FileV2{
Releases: []*Release{
{
Name: "default",
NamespaceMgmt: &NamespaceConfig{
Settings: NSSettings{
Overwrite: boolPtr(false),
},
Metadata: NSMetadata{
Annotations: map[string]string{
"release-annotation": "release-value",
},
Labels: map[string]string{
"release-label": "release-value",
},
},
},
},
{
Name: "release2",
NamespaceMgmt: &NamespaceConfig{},
},
},
NamespaceMgmt: &NamespaceMgmt{
Default: &NamespaceConfig{
Settings: NSSettings{},
Metadata: NSMetadata{
Annotations: map[string]string{
"course-annotation": "course-value",
},
Labels: map[string]string{
"course-label": "course-value",
},
},
},
},
},
want: &FileV2{
NamespaceMgmt: &NamespaceMgmt{
Default: &NamespaceConfig{
Settings: NSSettings{
Overwrite: boolPtr(false),
},
Metadata: NSMetadata{
Annotations: map[string]string{
"course-annotation": "course-value",
},
Labels: map[string]string{
"course-label": "course-value",
},
},
},
},
Releases: []*Release{
{
Name: "default",
NamespaceMgmt: &NamespaceConfig{
Settings: NSSettings{
Overwrite: boolPtr(false),
},
Metadata: NSMetadata{
Annotations: map[string]string{
"course-annotation": "course-value",
"release-annotation": "release-value",
},
Labels: map[string]string{
"course-label": "course-value",
"release-label": "release-value",
},
},
},
},
{
Name: "release2",
NamespaceMgmt: &NamespaceConfig{
Settings: NSSettings{
Overwrite: boolPtr(false),
},
Metadata: NSMetadata{
Annotations: map[string]string{
"course-annotation": "course-value",
},
Labels: map[string]string{
"course-label": "course-value",
},
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.file.populateNamespaceManagement()
assert.EqualValues(t, tt.want, tt.file)
})
}
}
Loading

0 comments on commit de1ad16

Please sign in to comment.