Skip to content

Commit 8bbf8ae

Browse files
feat: add PermissionsDefinition to SettingsDefinition to enable m… (#1775)
**What this PR does / Why we need it:** Introduction of config type Permission and introduction of the FF to toggle the behavior of it. _Feature flag added:_ ``` // AccessControlSettings toggles whether settings enable the access control // Introduced: v2.21.0 AccessControlSettings FeatureFlag = "MONACO_ACC_CONTROL_SETTINGS" ``` _Example yml file:_ ``` configs: - id: some-long-id-of-my-config type: settings: schema: builtin:alerting.profile schemaVersion: "8.5" scope: "some-scope" permissions: allUsers: read config: template: 'template.json' ``` **Special notes for your reviewer:** **Does this PR introduce a user-facing change?** Currently, the FF is disabled, so no user-facing changes --------- Co-authored-by: Arthur Pitman <arthur.pitman@dynatrace.com>
1 parent ad72aae commit 8bbf8ae

File tree

6 files changed

+247
-14
lines changed

6 files changed

+247
-14
lines changed

internal/featureflags/temporary.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ const (
4343
// ServiceLevelObjective toggles whether slo configurations are downloaded and / or deployed.
4444
// Introduced: v2.19.0
4545
ServiceLevelObjective FeatureFlag = "MONACO_FEAT_SLO_V2"
46+
// AccessControlSettings toggles whether settings enable the access control
47+
// Introduced: v2.21.0
48+
AccessControlSettings FeatureFlag = "MONACO_FEAT_ACCESS_CONTROL_SETTINGS"
4649
)
4750

4851
// temporaryDefaultValues defines temporary feature flags and their default values.
@@ -57,4 +60,5 @@ var temporaryDefaultValues = map[FeatureFlag]defaultValue{
5760
ServiceUsers: false,
5861
OnlyCreateReferencesInStringValues: false,
5962
ServiceLevelObjective: false,
63+
AccessControlSettings: false,
6064
}

internal/strings/strings.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,7 @@ import "fmt"
2121
func ToString(v interface{}) string {
2222
return fmt.Sprintf("%v", v)
2323
}
24+
25+
func Pointer(str string) *string {
26+
return &str
27+
}

pkg/config/types.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,19 @@ const (
3030

3131
var _ Type = SettingsType{}
3232

33+
type AllUserPermissionKind = string
34+
35+
const (
36+
ReadPermission AllUserPermissionKind = "read"
37+
WritePermission AllUserPermissionKind = "write"
38+
NonePermission AllUserPermissionKind = "none"
39+
)
40+
41+
var KnownAllUserPermissionKind = []AllUserPermissionKind{ReadPermission, WritePermission, NonePermission}
42+
3343
type SettingsType struct {
3444
SchemaId, SchemaVersion string
45+
AllUserPermission *AllUserPermissionKind
3546
}
3647

3748
func (SettingsType) ID() TypeID {

pkg/persistence/config/internal/persistence/type_definition.go

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,15 @@ type ComplexApiDefinition struct {
4646
}
4747

4848
type SettingsDefinition struct {
49-
Schema string `yaml:"schema,omitempty" json:"schema,omitempty" jsonschema:"required,description=The Settings 2.0 schema of this config."`
50-
SchemaVersion string `yaml:"schemaVersion,omitempty" json:"schemaVersion,omitempty" jsonschema:"description=This optionally informs the Settings API that a specific schema version was used for this config."`
51-
Scope ConfigParameter `yaml:"scope,omitempty" json:"scope,omitempty" jsonschema:"required,description=This defines the scope in which this Setting applies."`
52-
InsertAfter ConfigParameter `yaml:"insertAfter,omitempty" json:"insertAfter,omitempty" jsonschema:"description=This optionally informs the settings API that this particular objects needs to be inserted after the referenced one."`
49+
Schema string `yaml:"schema,omitempty" json:"schema,omitempty" jsonschema:"required,description=The Settings 2.0 schema of this config."`
50+
SchemaVersion string `yaml:"schemaVersion,omitempty" json:"schemaVersion,omitempty" jsonschema:"description=This optionally informs the Settings API that a specific schema version was used for this config."`
51+
Scope ConfigParameter `yaml:"scope,omitempty" json:"scope,omitempty" jsonschema:"required,description=This defines the scope in which this Setting applies."`
52+
InsertAfter ConfigParameter `yaml:"insertAfter,omitempty" json:"insertAfter,omitempty" jsonschema:"description=This optionally informs the settings API that this particular objects needs to be inserted after the referenced one."`
53+
Permissions *PermissionDefinition `yaml:"permissions,omitempty" json:"permissions,omitempty" jsonschema:"description=The optional permissions to be applied to this config."`
54+
}
55+
56+
type PermissionDefinition struct {
57+
AllUsers *string `yaml:"allUsers,omitempty" json:"allUsers" jsonschema:"required,enum=read,enum=write,enum=none,description=All users can use this permission." mapstructure:"allUsers"`
5358
}
5459

5560
type AutomationDefinition struct {
@@ -156,9 +161,18 @@ func (c *TypeDefinition) parseSettingsType(a any) error {
156161
return fmt.Errorf("failed to unmarshal settings-type: %w", err)
157162
}
158163

164+
if !featureflags.AccessControlSettings.Enabled() && r.Permissions != nil {
165+
return fmt.Errorf("unknown settings configuration property 'permissions'")
166+
}
167+
168+
var allUserPermission *config.AllUserPermissionKind
169+
if r.Permissions != nil {
170+
allUserPermission = r.Permissions.AllUsers
171+
}
159172
c.Type = config.SettingsType{
160-
SchemaId: r.Schema,
161-
SchemaVersion: r.SchemaVersion,
173+
SchemaId: r.Schema,
174+
SchemaVersion: r.SchemaVersion,
175+
AllUserPermission: allUserPermission,
162176
}
163177
c.Scope = r.Scope
164178
c.InsertAfter = r.InsertAfter
@@ -223,6 +237,12 @@ func (c *TypeDefinition) Validate(apis map[string]struct{}) error {
223237
return errors.New("missing settings scope")
224238
}
225239

240+
if featureflags.AccessControlSettings.Enabled() {
241+
if t.AllUserPermission != nil && !slices.Contains(config.KnownAllUserPermissionKind, *t.AllUserPermission) {
242+
return fmt.Errorf("unknown allUsers value: '%s', allowed: %v", *t.AllUserPermission, config.KnownAllUserPermissionKind)
243+
}
244+
}
245+
226246
case config.AutomationType:
227247
switch t.Resource {
228248
case "":
@@ -303,14 +323,22 @@ func (c TypeDefinition) MarshalYAML() (interface{}, error) {
303323
insertAfterValue = c.InsertAfter
304324
}
305325

306-
return map[string]any{
307-
"settings": SettingsDefinition{
308-
Schema: t.SchemaId,
309-
SchemaVersion: t.SchemaVersion,
310-
Scope: c.Scope,
311-
InsertAfter: insertAfterValue,
312-
},
313-
}, nil
326+
setDefinition := SettingsDefinition{
327+
Schema: t.SchemaId,
328+
SchemaVersion: t.SchemaVersion,
329+
Scope: c.Scope,
330+
InsertAfter: insertAfterValue,
331+
}
332+
333+
if featureflags.AccessControlSettings.Enabled() {
334+
if t.AllUserPermission != nil {
335+
setDefinition.Permissions = &PermissionDefinition{
336+
AllUsers: t.AllUserPermission,
337+
}
338+
}
339+
}
340+
341+
return map[string]any{"settings": setDefinition}, nil
314342

315343
case config.AutomationType:
316344
return map[string]any{

pkg/persistence/config/loader/config_loader_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/stretchr/testify/assert"
3030

3131
"github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/featureflags"
32+
strUtils "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/strings"
3233
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/api"
3334
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config"
3435
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/coordinate"
@@ -720,6 +721,91 @@ configs:
720721
},
721722
},
722723
},
724+
{
725+
name: "loads settings 2.0 config with all properties and allUsers permission with FF on",
726+
envVars: map[string]string{featureflags.AccessControlSettings.EnvName(): "true"},
727+
filePathArgument: "test-file.yaml",
728+
filePathOnDisk: "test-file.yaml",
729+
fileContentOnDisk: `
730+
configs:
731+
- id: profile-id
732+
config:
733+
name: 'Star Trek > Star Wars'
734+
template: 'profile.json'
735+
originObjectId: origin-object-id
736+
type:
737+
settings:
738+
schema: 'builtin:profile.test'
739+
schemaVersion: '1.0'
740+
scope: 'tenant'
741+
permissions:
742+
allUsers: 'read'`,
743+
wantConfigs: []config.Config{
744+
{
745+
Coordinate: coordinate.Coordinate{
746+
Project: "project",
747+
Type: "builtin:profile.test",
748+
ConfigId: "profile-id",
749+
},
750+
Type: config.SettingsType{
751+
SchemaId: "builtin:profile.test",
752+
SchemaVersion: "1.0",
753+
AllUserPermission: strUtils.Pointer(config.ReadPermission),
754+
},
755+
Template: template.NewInMemoryTemplate("profile.json", "{}"),
756+
Parameters: config.Parameters{
757+
"name": &value.ValueParameter{Value: "Star Trek > Star Wars"},
758+
config.ScopeParameter: &value.ValueParameter{Value: "tenant"},
759+
},
760+
Skip: false,
761+
Environment: "env name",
762+
Group: "default",
763+
OriginObjectId: "origin-object-id",
764+
},
765+
},
766+
},
767+
{
768+
name: "loads settings 2.0 config allUsers permission with invalid value with FF on",
769+
envVars: map[string]string{featureflags.AccessControlSettings.EnvName(): "true"},
770+
filePathArgument: "test-file.yaml",
771+
filePathOnDisk: "test-file.yaml",
772+
fileContentOnDisk: `
773+
configs:
774+
- id: profile-id
775+
config:
776+
name: 'Star Trek > Star Wars'
777+
template: 'profile.json'
778+
originObjectId: origin-object-id
779+
type:
780+
settings:
781+
schema: 'builtin:profile.test'
782+
schemaVersion: '1.0'
783+
scope: 'tenant'
784+
permissions:
785+
allUsers: 'wrong-value'`,
786+
wantErrorsContain: []string{"cannot parse definition in `test-file.yaml`: unknown allUsers value: 'wrong-value', allowed: [read write none]"},
787+
},
788+
{
789+
name: "loads settings 2.0 config with all properties and allUsers permission with FF off",
790+
envVars: map[string]string{featureflags.AccessControlSettings.EnvName(): "false"},
791+
filePathArgument: "test-file.yaml",
792+
filePathOnDisk: "test-file.yaml",
793+
fileContentOnDisk: `
794+
configs:
795+
- id: profile-id
796+
config:
797+
name: 'Star Trek > Star Wars'
798+
template: 'profile.json'
799+
originObjectId: origin-object-id
800+
type:
801+
settings:
802+
schema: 'builtin:profile.test'
803+
schemaVersion: '1.0'
804+
scope: 'tenant'
805+
permissions:
806+
allUsers: 'read'`,
807+
wantErrorsContain: []string{"unknown settings configuration property 'permissions'"},
808+
},
723809
{
724810
name: "loading a config without type content",
725811
filePathArgument: "test-file.yaml",

pkg/persistence/config/writer/config_writer_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"gopkg.in/yaml.v2"
3131

3232
"github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/featureflags"
33+
strUtils "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/strings"
3334
"github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/testutils"
3435
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config"
3536
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/coordinate"
@@ -869,6 +870,105 @@ func TestWriteConfigs(t *testing.T) {
869870
"project/schemaid/a.json",
870871
},
871872
},
873+
{
874+
name: "Simple settings 2.0 write with all-user permissions FF on",
875+
envVars: map[string]string{featureflags.AccessControlSettings.EnvName(): "true"},
876+
configs: []config.Config{
877+
{
878+
Template: template.NewInMemoryTemplateWithPath("project/schemaid/a.json", ""),
879+
Coordinate: coordinate.Coordinate{
880+
Project: "project",
881+
Type: "schemaid",
882+
ConfigId: "configId",
883+
},
884+
Type: config.SettingsType{
885+
SchemaId: "schemaid",
886+
SchemaVersion: "1.2.3",
887+
AllUserPermission: strUtils.Pointer(config.ReadPermission),
888+
},
889+
Parameters: map[string]parameter.Parameter{
890+
config.ScopeParameter: &value.ValueParameter{Value: "scope"},
891+
config.NameParameter: &value.ValueParameter{Value: "name"},
892+
},
893+
Skip: true,
894+
},
895+
},
896+
expectedConfigs: map[string]persistence.TopLevelDefinition{
897+
"schemaid": {
898+
Configs: []persistence.TopLevelConfigDefinition{
899+
{
900+
Id: "configId",
901+
Config: persistence.ConfigDefinition{
902+
Name: "name",
903+
Parameters: nil,
904+
Template: "a.json",
905+
Skip: true,
906+
},
907+
Type: persistence.TypeDefinition{
908+
Type: config.SettingsType{
909+
SchemaId: "schemaid",
910+
SchemaVersion: "1.2.3",
911+
AllUserPermission: strUtils.Pointer(config.ReadPermission),
912+
},
913+
Scope: "scope",
914+
},
915+
},
916+
},
917+
},
918+
},
919+
expectedTemplatePaths: []string{
920+
"project/schemaid/a.json",
921+
},
922+
},
923+
{
924+
name: "Simple settings 2.0 write with all-user permissions with FF off",
925+
configs: []config.Config{
926+
{
927+
Template: template.NewInMemoryTemplateWithPath("project/schemaid/a.json", ""),
928+
Coordinate: coordinate.Coordinate{
929+
Project: "project",
930+
Type: "schemaid",
931+
ConfigId: "configId",
932+
},
933+
Type: config.SettingsType{
934+
SchemaId: "schemaid",
935+
SchemaVersion: "1.2.3",
936+
AllUserPermission: strUtils.Pointer(config.ReadPermission),
937+
},
938+
Parameters: map[string]parameter.Parameter{
939+
config.ScopeParameter: &value.ValueParameter{Value: "scope"},
940+
config.NameParameter: &value.ValueParameter{Value: "name"},
941+
},
942+
Skip: true,
943+
},
944+
},
945+
envVars: map[string]string{featureflags.AccessControlSettings.EnvName(): "false"},
946+
expectedConfigs: map[string]persistence.TopLevelDefinition{
947+
"schemaid": {
948+
Configs: []persistence.TopLevelConfigDefinition{
949+
{
950+
Id: "configId",
951+
Config: persistence.ConfigDefinition{
952+
Name: "name",
953+
Parameters: nil,
954+
Template: "a.json",
955+
Skip: true,
956+
},
957+
Type: persistence.TypeDefinition{
958+
Type: config.SettingsType{
959+
SchemaId: "schemaid",
960+
SchemaVersion: "1.2.3",
961+
},
962+
Scope: "scope",
963+
},
964+
},
965+
},
966+
},
967+
},
968+
expectedTemplatePaths: []string{
969+
"project/schemaid/a.json",
970+
},
971+
},
872972
{
873973
name: "Automation resources",
874974
configs: []config.Config{

0 commit comments

Comments
 (0)