Skip to content
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

fix(dependency_resolution): Dashboard-Share-Settings can no longer be referenced from dashboards. #1614

Open
wants to merge 1 commit into
base: main
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
10 changes: 5 additions & 5 deletions pkg/download/dependency_resolution/dependency_resolution.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ package dependency_resolution

import (
"fmt"
"sync"
"sync/atomic"

"github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/featureflags"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/idutils"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/log"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/log/field"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/download/dependency_resolution/resolver"
"sync"
"sync/atomic"

"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/download/dependency_resolution/resolver"
project "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/project/v2"
)

Expand Down Expand Up @@ -82,7 +82,7 @@ func getResolver(configs project.ConfigsPerType) dependencyResolver {
configsById := collectConfigsById(configs)

if featureflags.Permanent[featureflags.FastDependencyResolver].Enabled() {
log.Debug("Using fast but memory intensive dependency resolution. Can be deactivated using '%s=false' env var.", featureflags.Permanent[featureflags.FastDependencyResolver].EnvName())
log.Debug("Using fast but memory intensive dependency resolution. Can be deactivated by setting the environment variable '%s' to 'false'.", featureflags.Permanent[featureflags.FastDependencyResolver].EnvName())
r, err := resolver.AhoCorasickResolver(configsById)
if err != nil {
log.WithFields(field.Error(err)).Error("Failed to initialize fast dependency resolution, falling back to slow resolution: %v", err)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ package dependency_resolution

import (
"fmt"
"testing"

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

"github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/featureflags"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/coordinate"
Expand All @@ -29,9 +34,6 @@ import (
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/template"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/download/dependency_resolution/resolver"
project "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/project/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)

func TestDependencyResolution(t *testing.T) {
Expand Down Expand Up @@ -677,6 +679,61 @@ func TestDependencyResolution(t *testing.T) {
},
},
},
{
name: "Dashboards should not be able to reference a dashboard-share-setting, even if it's the dashboard's share setting",
setup: project.ConfigsPerType{
"dashboard": []config.Config{
{
Template: template.NewInMemoryTemplate("t1", "dashboard-id"),
Coordinate: coordinate.Coordinate{Project: "project", Type: "dashboard", ConfigId: "dashboard-id"},
Type: config.ClassicApiType{Api: "dashboard"},
Parameters: config.Parameters{},
},
{
Template: template.NewInMemoryTemplate("t3", "dashboard-id"), // referencing the above dashboard
Coordinate: coordinate.Coordinate{Project: "project", Type: "dashboard", ConfigId: "dashboard-id2"},
Type: config.ClassicApiType{Api: "dashboard"},
Parameters: config.Parameters{},
},
},
"dashboard-share-settings": []config.Config{
{
Template: template.NewInMemoryTemplate("t2", ""),
Coordinate: coordinate.Coordinate{Project: "project", Type: "dashboard-share-setting", ConfigId: "dashboard-id"},
Type: config.ClassicApiType{Api: "dashboard-share-setting"},
Parameters: config.Parameters{
config.ScopeParameter: refParam.New("project", "dashboard", "dashboard-id", "id"),
},
},
},
},
expected: project.ConfigsPerType{
"dashboard": []config.Config{
{
Template: template.NewInMemoryTemplate("t1", "dashboard-id"),
Coordinate: coordinate.Coordinate{Project: "project", Type: "dashboard", ConfigId: "dashboard-id"},
Type: config.ClassicApiType{Api: "dashboard"},
Parameters: config.Parameters{},
},
{
Template: template.NewInMemoryTemplate("t3", "dashboard-id"),
Coordinate: coordinate.Coordinate{Project: "project", Type: "dashboard", ConfigId: "dashboard-id2"},
Type: config.ClassicApiType{Api: "dashboard"},
Parameters: config.Parameters{}, // no references to the other dashboard or dashboard-share-setting is allowed
},
},
"dashboard-share-settings": []config.Config{
{
Template: template.NewInMemoryTemplate("t2", ""),
Coordinate: coordinate.Coordinate{Project: "project", Type: "dashboard-share-setting", ConfigId: "dashboard-id"},
Type: config.ClassicApiType{Api: "dashboard-share-setting"},
Parameters: config.Parameters{
config.ScopeParameter: refParam.New("project", "dashboard", "dashboard-id", "id"),
},
},
},
},
},
}
for _, test := range tests {
t.Run(test.name+"_BasicResolver", func(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ package resolver

import (
"fmt"

goaho "github.com/anknown/ahocorasick"
"golang.org/x/exp/maps"

"github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/log"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/parameter/reference"
"golang.org/x/exp/maps"
)

// dependencyResolutionContext holds the important information for dependency resolution.
Expand Down Expand Up @@ -103,12 +105,8 @@ func findAndReplaceIDs(apiName string, configToBeUpdated config.Config, c depend
panic(fmt.Sprintf("No config found for given key %q", key))
}

if configToBeUpdated.Coordinate.Type == "dashboard" && conf.Coordinate.Type == "dashboard" {
continue // dashboards can not actually reference each other, but often contain a link to another inside a markdown tile
}

if conf.Coordinate == configToBeUpdated.Coordinate {
continue // skip self referencing configs
if !canReference(configToBeUpdated, conf) {
Laubi marked this conversation as resolved.
Show resolved Hide resolved
continue
}

log.Debug("\treference: '%v/%v' referencing '%v' in coordinate '%v' ", apiName, configToBeUpdated.Template.ID(), key, conf.Coordinate)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
package resolver

import (
"strings"

"golang.org/x/exp/maps"

"github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/log"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/parameter/reference"
"golang.org/x/exp/maps"
"strings"
)

type basicResolver struct {
Expand Down Expand Up @@ -75,9 +77,9 @@ func basicFindAndReplaceIDs(apiName string, configToBeUpdated config.Config, con
// in case two configs are actually the same, or if they are both dashboards no replacement will happen as in these
// cases there is no real valid reference even if the key is found in the content.
func shouldReplaceReference(configToBeUpdated config.Config, configToUpdateFrom config.Config, contentToBeUpdated, keyToReplace string) bool {
if configToBeUpdated.Coordinate.Type == "dashboard" && configToUpdateFrom.Coordinate.Type == "dashboard" {
return false //dashboards can not actually reference each other, but often contain a link to another inside a markdown tile
if !canReference(configToBeUpdated, configToUpdateFrom) {
return false
}

return configToUpdateFrom.Template.ID() != configToBeUpdated.Template.ID() && strings.Contains(contentToBeUpdated, keyToReplace)
return strings.Contains(contentToBeUpdated, keyToReplace)
Laubi marked this conversation as resolved.
Show resolved Hide resolved
}
30 changes: 28 additions & 2 deletions pkg/download/dependency_resolution/resolver/shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,17 @@ package resolver

import (
"fmt"
"regexp"
"strings"

"github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/log"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/parameter/reference"
valueParam "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/parameter/value"
"regexp"
"strings"
)

// resolveScope updates the `scope` parameter of the config and converts it to a reference parameter iff the scope
// is a known id of another downloaded config.
func resolveScope(configToBeUpdated *config.Config, ids map[string]config.Config) {
if configToBeUpdated.Type.ID() != config.SettingsTypeId {
return
Expand Down Expand Up @@ -77,3 +80,26 @@ func replaceAll(content string, key string, s string) string {

return re.ReplaceAllString(content, fmt.Sprintf("$1%s$3", s))
}

// canReference verifies whether configToUpdateFrom can actually reference configToBeUpdated.
//
// configToUpdateFrom can not reference configToBeUpdated if either
// - they are the same config (coordinate matches)
// - they are both dashboards (remove cyclic dependencies)
// - configToUpdateFrom is a dashboard-share-setting (can not be referenced)
func canReference(configToBeUpdated config.Config, configToUpdateFrom config.Config) bool {
if configToBeUpdated.Coordinate == configToUpdateFrom.Coordinate {
return false // they are the same config
}

if configToBeUpdated.Coordinate.Type == "dashboard" && configToUpdateFrom.Coordinate.Type == "dashboard" {
return false //dashboards can not actually reference each other, but often contain a link to another inside a markdown tile
}

if configToUpdateFrom.Coordinate.Type == "dashboard-share-setting" {
// dashboard share settings can not be referenced, but since they have the same id as their parent dashboard, dashboards suddenly reference them
return false
}

return true
}
Loading