Skip to content

feat(config): Warn when undefined environments or groups are used in overrides #1880

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: feat/manifest-loads-all-environments
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/config/errors/loader_errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package errors

import (
"fmt"

"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/coordinate"
)

Expand Down
13 changes: 13 additions & 0 deletions pkg/config/loader/config_entry_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

"github.com/spf13/afero"

"github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/log"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/api"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/coordinate"
Expand Down Expand Up @@ -55,7 +56,19 @@ func parseConfigEntry(
return nil, []error{newDefinitionParserError(configId, singleConfigContext, err.Error())}
}

for _, group := range definition.GroupOverrides {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move each check into a separate function to decrease the complexity and have an opportunity to comment on what is checked under which assumption.

My opinion you decide if it makes sense.

if _, exists := loaderContext.KnownGroups[group.Group]; !exists {
log.Warn("unknown group '%s'", group.Group)
}
}

groupOverrideMap := toGroupOverrideMap(definition.GroupOverrides)

for _, env := range definition.EnvironmentOverrides {
if _, exists := loaderContext.KnownEnvironments[env.Environment]; !exists {
log.Warn("unknown environment '%s'", env.Environment)
}
}
environmentOverrideMap := toEnvironmentOverrideMap(definition.EnvironmentOverrides)

var results []config.Config
Expand Down
12 changes: 7 additions & 5 deletions pkg/config/loader/config_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ import (
)

type LoaderContext struct {
ProjectId string
Path string
Environments []manifest.EnvironmentDefinition
KnownApis map[string]struct{}
ParametersSerDe map[string]parameter.ParameterSerDe
ProjectId string
Path string
Environments []manifest.EnvironmentDefinition
KnownApis map[string]struct{}
KnownEnvironments map[string]struct{}
KnownGroups map[string]struct{}
ParametersSerDe map[string]parameter.ParameterSerDe
}

// configFileLoaderContext is a context for each config-file
Expand Down
28 changes: 21 additions & 7 deletions pkg/project/project_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,13 +323,7 @@ func loadConfigsOfProject(ctx context.Context, fs afero.Fs, loadingContext Proje
var configs []config.Config
var errs []error

loaderContext := &loader.LoaderContext{
ProjectId: projectDefinition.Name,
Environments: environments,
Path: projectDefinition.Path,
KnownApis: loadingContext.KnownApis,
ParametersSerDe: loadingContext.ParametersSerde,
}
loaderContext := newLoaderContext(loadingContext, projectDefinition, environments)

for _, file := range configFiles {
log.WithFields(field.F("file", file)).Debug("Loading configuration file %s", file)
Expand All @@ -341,6 +335,26 @@ func loadConfigsOfProject(ctx context.Context, fs afero.Fs, loadingContext Proje
return configs, errs
}

func newLoaderContext(loadingContext ProjectLoaderContext, projectDefinition manifest.ProjectDefinition,
environments []manifest.EnvironmentDefinition) *loader.LoaderContext {
knownEnvironments := map[string]struct{}{}
knownGroups := map[string]struct{}{}
for _, env := range environments {
knownEnvironments[env.Name] = struct{}{}
knownGroups[env.Group] = struct{}{}
}

return &loader.LoaderContext{
ProjectId: projectDefinition.Name,
Environments: environments,
Path: projectDefinition.Path,
KnownApis: loadingContext.KnownApis,
KnownEnvironments: knownEnvironments,
KnownGroups: knownGroups,
ParametersSerDe: loadingContext.ParametersSerde,
}
}

func findDuplicatedConfigIdentifiers(ctx context.Context, configs []config.Config, configErrorMap map[coordinate.Coordinate]struct{}) []error {
var errs []error
coordinates := make(map[string]struct{})
Expand Down
108 changes: 108 additions & 0 deletions pkg/project/project_loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package project

import (
"bytes"
"fmt"
"io/fs"
"reflect"
Expand All @@ -27,6 +28,7 @@ import (
"github.com/stretchr/testify/require"

"github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/errutils"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/log"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/testutils"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/coordinate"
Expand Down Expand Up @@ -1481,6 +1483,112 @@ func TestLoadProjects_NetworkZonesContainsParameterToSetting(t *testing.T) {
assert.Contains(t, networkZone2.Parameters, "__MONACO_NZONE_ENABLED__")
}

// TestLoadProjects_EnvironmentOverrideWithUndefinedEnvironmentProducesWarning tests that referencing an undefined environment in an environment override produces a warning.
func TestLoadProjects_EnvironmentOverrideWithUndefinedEnvironmentProducesWarning(t *testing.T) {
managementZoneConfig := []byte(`configs:
- id: mz
config:
template: mz.json
type:
settings:
schema: builtin:management-zones
scope: environment
environmentOverrides:
- environment: prod
override:
skip: true
`)

managementZoneJSON := []byte(`{ "name": "", "rules": [] }`)

testFs := testutils.TempFs(t)
logSpy := bytes.Buffer{}
log.PrepareLogging(t.Context(), afero.NewMemMapFs(), false, &logSpy, false, false)

require.NoError(t, testFs.MkdirAll("a/builtinmanagement-zones", testDirectoryFileMode))
require.NoError(t, afero.WriteFile(testFs, "a/builtinmanagement-zones/config.yaml", managementZoneConfig, testFileFileMode))
require.NoError(t, afero.WriteFile(testFs, "a/builtinmanagement-zones/mz.json", managementZoneJSON, testFileFileMode))

testContext := ProjectLoaderContext{
KnownApis: map[string]struct{}{"builtin:management-zones": {}},
WorkingDir: ".",
Manifest: manifest.Manifest{
Projects: manifest.ProjectDefinitionByProjectID{
"a": {
Name: "a",
Path: "a/",
},
},
Environments: manifest.Environments{
"dev": {
Name: "dev",
Auth: manifest.Auth{Token: &manifest.AuthSecret{Name: "ENV_VAR"}},
},
},
},
ParametersSerde: config.DefaultParameterParsers,
}

gotProjects, gotErrs := LoadProjects(t.Context(), testFs, testContext, nil)
assert.Len(t, gotErrs, 0, "Expected no errors loading dependent projects ")
assert.Len(t, gotProjects, 1)

assert.Contains(t, logSpy.String(), "unknown environment")
}

// TestLoadProjects_GroupOverrideWithUndefinedGroupProducesWarning tests that referencing an undefined environment group in a group override produces a warning.
func TestLoadProjects_GroupOverrideWithUndefinedGroupProducesWarning(t *testing.T) {
managementZoneConfig := []byte(`configs:
- id: mz
config:
template: mz.json
type:
settings:
schema: builtin:management-zones
scope: environment
groupOverrides:
- group: prod
override:
skip: true
`)

managementZoneJSON := []byte(`{ "name": "", "rules": [] }`)

testFs := testutils.TempFs(t)

logSpy := bytes.Buffer{}
log.PrepareLogging(t.Context(), afero.NewMemMapFs(), false, &logSpy, false, false)

require.NoError(t, testFs.MkdirAll("a/builtinmanagement-zones", testDirectoryFileMode))
require.NoError(t, afero.WriteFile(testFs, "a/builtinmanagement-zones/config.yaml", managementZoneConfig, testFileFileMode))
require.NoError(t, afero.WriteFile(testFs, "a/builtinmanagement-zones/mz.json", managementZoneJSON, testFileFileMode))
testContext := ProjectLoaderContext{
KnownApis: map[string]struct{}{"builtin:management-zones": {}},
WorkingDir: ".",
Manifest: manifest.Manifest{
Projects: manifest.ProjectDefinitionByProjectID{
"a": {
Name: "a",
Path: "a/",
},
},
Environments: manifest.Environments{
"dev": {
Name: "dev",
Auth: manifest.Auth{Token: &manifest.AuthSecret{Name: "ENV_VAR"}},
},
},
},
ParametersSerde: config.DefaultParameterParsers,
}

gotProjects, gotErrs := LoadProjects(t.Context(), testFs, testContext, nil)
assert.Len(t, gotErrs, 0, "Expected no errors loading dependent projects ")
assert.Len(t, gotProjects, 1)

assert.Contains(t, logSpy.String(), "unknown group")
}

type propResolver func(coordinate.Coordinate, string) (any, bool)

func (p propResolver) GetResolvedProperty(coordinate coordinate.Coordinate, propertyName string) (any, bool) {
Expand Down