diff --git a/pkg/converter/converter_test.go b/pkg/converter/converter_test.go index aba030b44..2b002219d 100644 --- a/pkg/converter/converter_test.go +++ b/pkg/converter/converter_test.go @@ -18,13 +18,17 @@ package converter import ( "fmt" + "reflect" + "testing" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/template" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/parameter" compoundParam "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/parameter/compound" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/converter/v1environment" - "reflect" - "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/api" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/coordinate" @@ -34,8 +38,6 @@ import ( valueParam "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/parameter/value" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/manifest" projectV1 "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/project/v1" - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" ) const simpleParameterName = "randomValue" @@ -130,8 +132,8 @@ func TestConvertParameters(t *testing.T) { envParameter, found := parameters[envParameterName] assert.Equal(t, true, found) assert.Equal(t, "SOME_ENV_VAR", envParameter.(*envParam.EnvironmentVariableParameter).Name) - //assert.Len(t, envParameter.(*compoundParam.CompoundParameter).GetReferences(), 1) - //assert.Equal(t, "__ENV_SOME_ENV_VAR__", envParameter.(*compoundParam.CompoundParameter).GetReferences()[0].Property) + // assert.Len(t, envParameter.(*compoundParam.CompoundParameter).GetReferences(), 1) + // assert.Equal(t, "__ENV_SOME_ENV_VAR__", envParameter.(*compoundParam.CompoundParameter).GetReferences()[0].Property) compound := parameters["nickname"] @@ -875,7 +877,7 @@ func TestConvertProjects(t *testing.T) { assert.Equal(t, projectId, projectDefinition.Name) assert.Equal(t, projectId, projectDefinition.Path) - assert.Nil(t, convertedProject.Dependencies, "Dependencies should not be resolved") + // assert.Empty(t, projectV2.ToDependenciesPerEnvironment(convertedProject), "Dependencies should not be resolved") convertedConfigs := convertedProject.Configs @@ -1161,7 +1163,7 @@ func Test_parseReference(t *testing.T) { "returns error for unknown api reference", "test-param", "/some-project/alerting-profile/some-configV1.id", - api.APIs{}, //no APIs known + api.APIs{}, // no APIs known nil, true, }, diff --git a/pkg/deploy/deploy_test.go b/pkg/deploy/deploy_test.go index 35406957f..807b14841 100644 --- a/pkg/deploy/deploy_test.go +++ b/pkg/deploy/deploy_test.go @@ -518,11 +518,6 @@ func TestDeployConfigGraph_DoesNotDeployConfigsDependingOnSkippedConfigs(t *test }, }, }, - Dependencies: project.DependenciesPerEnvironment{ - environmentName: []string{ - referencedProjectId, - }, - }, }, { Id: referencedProjectId, @@ -631,11 +626,6 @@ func TestDeployConfigGraph_DeploysIndependentConfigurations(t *testing.T) { }, }, }, - Dependencies: project.DependenciesPerEnvironment{ - environmentName: []string{ - referencedProjectId, - }, - }, }, { Id: referencedProjectId, @@ -750,11 +740,6 @@ func TestDeployConfigGraph_DeploysIndependentConfigurations_IfContinuingAfterFai }, }, }, - Dependencies: project.DependenciesPerEnvironment{ - environmentName: []string{ - referencedProjectId, - }, - }, }, { Id: referencedProjectId, diff --git a/pkg/graph/graph_test.go b/pkg/graph/graph_test.go index d94e9f628..1686b3971 100644 --- a/pkg/graph/graph_test.go +++ b/pkg/graph/graph_test.go @@ -19,15 +19,17 @@ package graph_test import ( + "testing" + + "github.com/stretchr/testify/assert" + graph2 "gonum.org/v1/gonum/graph" + "gonum.org/v1/gonum/graph/simple" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/coordinate" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/parameter" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/graph" project "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/project/v2" - "github.com/stretchr/testify/assert" - graph2 "gonum.org/v1/gonum/graph" - "gonum.org/v1/gonum/graph/simple" - "testing" ) func TestConfigGraphPerEnvironment_GetConnectedConfigs(t *testing.T) { @@ -90,11 +92,6 @@ func TestConfigGraphPerEnvironment_GetConnectedConfigs(t *testing.T) { }, }, }, - Dependencies: project.DependenciesPerEnvironment{ - environmentName: []string{ - referencedProjectId, - }, - }, }, { Id: referencedProjectId, @@ -198,11 +195,6 @@ func TestGraphExport(t *testing.T) { }, }, }, - Dependencies: project.DependenciesPerEnvironment{ - environmentName: []string{ - referencedProjectId, - }, - }, }, { Id: referencedProjectId, @@ -388,11 +380,6 @@ func TestGraphCycleErrors(t *testing.T) { }, }, }, - Dependencies: project.DependenciesPerEnvironment{ - environmentName: []string{ - referencedProjectId, - }, - }, }, { Id: referencedProjectId, diff --git a/pkg/project/v2/project.go b/pkg/project/v2/project.go index 06b8c6d1b..251c8e997 100644 --- a/pkg/project/v2/project.go +++ b/pkg/project/v2/project.go @@ -16,6 +16,8 @@ package v2 import ( "fmt" + "maps" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/coordinate" ) @@ -32,10 +34,6 @@ type ( // ConfigsPerEnvironment is a map of EnvironmentName to configs. This is a flattened version of ConfigsPerTypePerEnvironments ConfigsPerEnvironment map[EnvironmentName][]config.Config - ProjectID = string - // DependenciesPerEnvironment is a map of EnvironmentName to project IDs - DependenciesPerEnvironment map[EnvironmentName][]ProjectID - // ActionOverConfig is a function that will be performed over each config that is part of a project via a Project.ForEveryConfigDo method ActionOverConfig func(c config.Config) ) @@ -48,26 +46,16 @@ type Project struct { // Configs are the configurations within this Project Configs ConfigsPerTypePerEnvironments - - // Dependencies of this project to other projects - Dependencies DependenciesPerEnvironment } -// HasDependencyOn returns whether the project it is called on, has a dependency on the given project, for the given environment -func (p Project) HasDependencyOn(environment string, project Project) bool { - dependencies, found := p.Dependencies[environment] - - if !found { - return false - } - - for _, dep := range dependencies { - if dep == project.Id { - return true +func (p Project) ConfigList() []config.Config { + var configs []config.Config + for x := range maps.Values(p.Configs) { + for y := range maps.Values(x) { + configs = append(configs, y...) } } - - return false + return configs } // GetConfigFor searches a config object for matching the given coordinate in the diff --git a/pkg/project/v2/project_loader.go b/pkg/project/v2/project_loader.go index bf4549c0c..c1441bcef 100644 --- a/pkg/project/v2/project_loader.go +++ b/pkg/project/v2/project_loader.go @@ -114,9 +114,7 @@ func LoadProjects(fs afero.Fs, context ProjectLoaderContext, specificProjectName loadedProjects = append(loadedProjects, project) - for _, environment := range environments { - projectNamesToLoad = append(projectNamesToLoad, project.Dependencies[environment.Name]...) - } + projectNamesToLoad = append(projectNamesToLoad, getConnectedProjects(project)...) } if len(errors) > 0 { @@ -126,6 +124,26 @@ func LoadProjects(fs afero.Fs, context ProjectLoaderContext, specificProjectName return loadedProjects, nil } +func getConnectedProjects(p Project) []string { + var result []string + + for _, c := range p.ConfigList() { + // ignore skipped configs + if c.Skip { + continue + } + + for _, r := range c.References() { + // ignore project on same project + if p.Id == r.Project { + continue + } + result = append(result, r.Project) + } + } + return result +} + // Gets full project names to load specified by project or grouping project names. If none are specified, all project names are returned. Errors are returned for any project names that do not exist. func getProjectNamesToLoad(allProjectsDefinitions manifest.ProjectDefinitionByProjectID, specificProjectNames []string) ([]string, []error) { projectNamesToLoad := make([]string, 0, len(specificProjectNames)) @@ -185,10 +203,9 @@ func loadProject(fs afero.Fs, context ProjectLoaderContext, projectDefinition ma insertNetworkZoneParameter(configs) return Project{ - Id: projectDefinition.Name, - GroupId: projectDefinition.Group, - Configs: toConfigMap(configs), - Dependencies: toDependenciesMap(projectDefinition.Name, configs), + Id: projectDefinition.Name, + GroupId: projectDefinition.Group, + Configs: toConfigMap(configs), }, nil } @@ -327,27 +344,3 @@ func findDuplicatedConfigIdentifiers(configs []config.Config) []error { func toFullyQualifiedConfigIdentifier(config config.Config) string { return fmt.Sprintf("%s:%s:%s", config.Group, config.Environment, config.Coordinate) } - -func toDependenciesMap(projectId string, configs []config.Config) DependenciesPerEnvironment { - result := make(DependenciesPerEnvironment) - - for _, c := range configs { - // ignore skipped configs - if c.Skip { - continue - } - - for _, ref := range c.References() { - // ignore project on same project - if projectId == ref.Project { - continue - } - - if !slices.Contains(result[c.Environment], ref.Project) { - result[c.Environment] = append(result[c.Environment], ref.Project) - } - } - } - - return result -} diff --git a/pkg/project/v2/project_test.go b/pkg/project/v2/project_test.go index 40f0a36b7..e40c053ce 100644 --- a/pkg/project/v2/project_test.go +++ b/pkg/project/v2/project_test.go @@ -17,11 +17,13 @@ package v2_test import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/coordinate" project "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/project/v2" - "github.com/stretchr/testify/assert" - "testing" ) func TestGetConfigFor(t *testing.T) { @@ -86,64 +88,6 @@ func TestGetConfigFor(t *testing.T) { }) } } -func TestHasDependencyOn(t *testing.T) { - environment := "dev" - referencedProjectId := "projct2" - - p := project.Project{ - Id: "project1", - Dependencies: project.DependenciesPerEnvironment{ - environment: []string{ - referencedProjectId, - }, - }, - } - - referencedProject := project.Project{ - Id: referencedProjectId, - } - - result := p.HasDependencyOn(environment, referencedProject) - - assert.True(t, result, "should have dependency") -} - -func TestHasDependencyOnShouldReturnFalseIfNoDependenciesForEnvironmentAreDefined(t *testing.T) { - environment := "dev" - - p := project.Project{ - Id: "project1", - } - - p2 := project.Project{ - Id: "project2", - } - - result := p.HasDependencyOn(environment, p2) - - assert.False(t, result, "should not have dependency") -} - -func TestHasDependencyOnShouldReturnFalseIfNoDependencyDefined(t *testing.T) { - environment := "dev" - - p := project.Project{ - Id: "project1", - Dependencies: project.DependenciesPerEnvironment{ - environment: []string{ - "project3", - }, - }, - } - - project2 := project.Project{ - Id: "project2", - } - - result := p.HasDependencyOn(environment, project2) - - assert.False(t, result, "should not have dependency") -} func TestProject_ForEveryConfigDo(t *testing.T) { t.Run("simple case", func(t *testing.T) { diff --git a/pkg/project/v2/sort/sort_test.go b/pkg/project/v2/sort/sort_test.go index 4cfffb683..573b7e9c8 100644 --- a/pkg/project/v2/sort/sort_test.go +++ b/pkg/project/v2/sort/sort_test.go @@ -19,13 +19,15 @@ package sort_test import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/coordinate" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/parameter" project "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/project/v2" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/project/v2/sort" - "github.com/stretchr/testify/assert" - "testing" ) func TestGetSortedConfigsForEnvironments(t *testing.T) { @@ -86,11 +88,6 @@ func TestGetSortedConfigsForEnvironments(t *testing.T) { }, }, }, - Dependencies: project.DependenciesPerEnvironment{ - environmentName: []string{ - referencedProjectId, - }, - }, }, { Id: referencedProjectId, diff --git a/pkg/project/v2/sort/topologysort/topologysort.go b/pkg/project/v2/sort/topologysort/topologysort.go index ceb3c94cf..6a9d31a05 100644 --- a/pkg/project/v2/sort/topologysort/topologysort.go +++ b/pkg/project/v2/sort/topologysort/topologysort.go @@ -17,13 +17,14 @@ package topologysort import ( - "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/log" - "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/topologysort" - errors2 "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/project/v2/sort/errors" + "slices" + s "sort" "strings" "sync" - s "sort" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/log" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/topologysort" + errors2 "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/project/v2/sort/errors" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/coordinate" @@ -203,7 +204,7 @@ func sortProjects(projects []project.Project, environments []string) (projectsPe errs = append(errs, &errors2.CircualDependencyProjectSortError{ Environment: env, Project: p.Id, - DependsOnProjects: p.Dependencies[env], + DependsOnProjects: toDependenciesPerEnvironment(p)[env], }) } } @@ -224,6 +225,35 @@ func sortProjects(projects []project.Project, environments []string) (projectsPe return resultByEnvironment, nil } +type projectID = string + +// dependenciesPerEnvironment is a map of EnvironmentName to project IDs +type dependenciesPerEnvironment map[project.EnvironmentName][]projectID + +func toDependenciesPerEnvironment(p project.Project) dependenciesPerEnvironment { + result := make(dependenciesPerEnvironment) + + for _, c := range p.ConfigList() { + // ignore skipped configs + if c.Skip { + continue + } + + for _, ref := range c.References() { + // ignore project on same project + if p.Id == ref.Project { + continue + } + + if !slices.Contains(result[c.Environment], ref.Project) { + result[c.Environment] = append(result[c.Environment], ref.Project) + } + } + } + + return result +} + func projectsToSortData(projects []project.Project, environment string) ([][]bool, []int) { numProjects := len(projects) matrix := make([][]bool, numProjects) @@ -237,7 +267,7 @@ func projectsToSortData(projects []project.Project, environment string) ([][]boo continue } - if p.HasDependencyOn(environment, prj) { + if hasDependencyOn(p, environment, prj) { logDependency("Project", p.Id, prj.Id) matrix[i][j] = true inDegrees[i]++ @@ -251,3 +281,17 @@ func projectsToSortData(projects []project.Project, environment string) ([][]boo func logDependency(prefix string, depending string, dependedOn string) { log.Debug("%s: %s has dependency on %s", prefix, depending, dependedOn) } + +// hasDependencyOn returns whether the project it is called on, has a dependency on the given project, for the given environment +func hasDependencyOn(orig project.Project, environment string, project project.Project) bool { + for c := range slices.Values(orig.ConfigList()) { + if c.Environment == environment { + for r := range slices.Values(c.References()) { + if r.Project == project.Id { + return true + } + } + } + } + return false +} diff --git a/pkg/project/v2/sort/topologysort/topologysort_test.go b/pkg/project/v2/sort/topologysort/topologysort_test.go index 3105ac53f..1e3cfec3e 100644 --- a/pkg/project/v2/sort/topologysort/topologysort_test.go +++ b/pkg/project/v2/sort/topologysort/topologysort_test.go @@ -19,15 +19,18 @@ package topologysort import ( + "testing" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/topologysort" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/parameter/reference" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/project/v2/sort/errors" - "testing" + + "github.com/stretchr/testify/assert" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/coordinate" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/parameter" project "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/project/v2" - "github.com/stretchr/testify/assert" ) func TestSortConfigs(t *testing.T) { @@ -234,9 +237,16 @@ func TestSortProjects(t *testing.T) { projects := []project.Project{ { Id: projectId, - Dependencies: project.DependenciesPerEnvironment{ - environmentName: []string{ - referencedProjectId, + Configs: project.ConfigsPerTypePerEnvironments{ + environmentName: project.ConfigsPerType{ + "some_type": []config.Config{ + { + Environment: environmentName, + Parameters: map[string]parameter.Parameter{ + "parameter_name": &reference.ReferenceParameter{parameter.ParameterReference{Config: coordinate.Coordinate{Project: referencedProjectId}}}, + }, + }, + }, }, }, }, @@ -277,17 +287,31 @@ func TestSortProjectsShouldFailOnCyclicDependency(t *testing.T) { projects := []project.Project{ { Id: projectId, - Dependencies: project.DependenciesPerEnvironment{ - environmentName: []string{ - referencedProjectId, + Configs: project.ConfigsPerTypePerEnvironments{ + environmentName: project.ConfigsPerType{ + "some_type": []config.Config{ + { + Environment: environmentName, + Parameters: map[string]parameter.Parameter{ + "parameter_name": &reference.ReferenceParameter{parameter.ParameterReference{Config: coordinate.Coordinate{Project: referencedProjectId}}}, + }, + }, + }, }, }, }, { Id: referencedProjectId, - Dependencies: project.DependenciesPerEnvironment{ - environmentName: []string{ - projectId, + Configs: project.ConfigsPerTypePerEnvironments{ + environmentName: project.ConfigsPerType{ + "some_type_2": []config.Config{ + { + Environment: environmentName, + Parameters: map[string]parameter.Parameter{ + "parameter_name": &reference.ReferenceParameter{parameter.ParameterReference{Config: coordinate.Coordinate{Project: projectId}}}, + }, + }, + }, }, }, }, @@ -399,3 +423,96 @@ func Test_parseConfigSortErrors(t *testing.T) { }) } } + +func TestHasDependencyOn(t *testing.T) { + t.Run("positive case", func(t *testing.T) { + environment := "dev" + referencedProjectId := "projct2" + + p := project.Project{ + Id: "project1", + Configs: project.ConfigsPerTypePerEnvironments{ + environment: project.ConfigsPerType{ + "something": []config.Config{ + { + Environment: environment, + Parameters: map[string]parameter.Parameter{ + "parameter_name": &reference.ReferenceParameter{parameter.ParameterReference{Config: coordinate.Coordinate{Project: referencedProjectId}}}, + }, + }, + }, + }, + }, + } + + referencedProject := project.Project{Id: referencedProjectId} + + assert.True(t, hasDependencyOn(p, environment, referencedProject), "should have dependency") + }) + + t.Run("negative case", func(t *testing.T) { + environment := "dev" + referencedProjectId := "projct2" + + p := project.Project{ + Id: "project1", + Configs: project.ConfigsPerTypePerEnvironments{ + environment: project.ConfigsPerType{ + "something": []config.Config{ + { + Environment: environment, + }, + }, + }, + }, + } + + referencedProject := project.Project{Id: referencedProjectId} + + assert.False(t, hasDependencyOn(p, environment, referencedProject), "should have dependency") + }) +} + +func TestHasDependencyOnShouldReturnFalseIfNoDependenciesForEnvironmentAreDefined(t *testing.T) { + environment := "dev" + + p := project.Project{ + Id: "project1", + } + + p2 := project.Project{ + Id: "project2", + } + + result := hasDependencyOn(p, environment, p2) + + assert.False(t, result, "should not have dependency") +} + +func TestHasDependencyOnShouldReturnFalseIfNoDependencyDefined(t *testing.T) { + environment := "dev" + + p := project.Project{ + Id: "project1", + Configs: project.ConfigsPerTypePerEnvironments{ + environment: project.ConfigsPerType{ + "some_type": []config.Config{ + { + Environment: environment, + Parameters: map[string]parameter.Parameter{ + "parameter_name": &reference.ReferenceParameter{parameter.ParameterReference{Config: coordinate.Coordinate{Project: "project3"}}}, + }, + }, + }, + }, + }, + } + + project2 := project.Project{ + Id: "project2", + } + + result := hasDependencyOn(p, environment, project2) + + assert.False(t, result, "should not have dependency") +} diff --git a/pkg/writer/writer.go b/pkg/writer/writer.go index 722cc71ea..8b2f27811 100644 --- a/pkg/writer/writer.go +++ b/pkg/writer/writer.go @@ -15,16 +15,17 @@ package writer import ( + "path/filepath" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/log" manifestwriter "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/manifest/writer" configwriter "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/persistence/config/writer" - "path/filepath" - "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config" + "github.com/spf13/afero" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/parameter" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/manifest" project "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/project/v2" - "github.com/spf13/afero" ) type WriterContext struct { @@ -74,7 +75,7 @@ func writeProjects(context *WriterContext, projectDefinitions manifest.ProjectDe continue } - configs := collectAllConfigs(p) + configs := p.ConfigList() errs := configwriter.WriteConfigs(&configwriter.WriterContext{ Fs: context.Fs, @@ -92,13 +93,3 @@ func writeProjects(context *WriterContext, projectDefinitions manifest.ProjectDe return nil } - -func collectAllConfigs(p project.Project) (result []config.Config) { - for _, configsPerApi := range p.Configs { - for _, configs := range configsPerApi { - result = append(result, configs...) - } - } - - return result -}