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

feat: load and parse account management resources #1235

Merged
merged 6 commits into from
Oct 18, 2023
Merged
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
39 changes: 39 additions & 0 deletions cmd/monaco/deploy/acc/deploy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* @license
* Copyright 2023 Dynatrace LLC
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package acc

import (
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/persistence/account"
"github.com/spf13/afero"
)

// deployAcc triggers the deployment of account management resources for a given monaco project
func deployAcc(fs afero.Fs, projectName string) error { // nolint:unused
accResources, err := account.Load(fs, projectName)
if err != nil {
return err
}

err = account.Validate(accResources)
if err != nil {
return err
}

// (tbd) convert acc resources to internal representation to be deployable and pass to pkg/deploy/acc::Deploy()

return nil
}
36 changes: 36 additions & 0 deletions internal/files/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package files

import (
"os"
"path"
"path/filepath"
"strings"

"github.com/spf13/afero"
Expand Down Expand Up @@ -57,6 +59,40 @@ func IsYamlFileExtension(file string) bool {
return false
}

// FindYamlFiles finds all YAML files within the given root directory.
// Hidden directories (start with a dot (.)) are excluded.
// Directories marked as hidden on Windows are not excluded.
// If root is not existent the function returns nil, nil
func FindYamlFiles(fs afero.Fs, root string) ([]string, error) {
var configFiles []string

exists, err := afero.Exists(fs, root)
if err != nil {
return configFiles, err
}

if !exists {
return nil, nil
}

err = afero.Walk(fs, root, func(curPath string, info os.FileInfo, err error) error {
name := info.Name()

if info.IsDir() {
if strings.HasPrefix(name, ".") {
return filepath.SkipDir
}
}

if IsYamlFileExtension(name) {
configFiles = append(configFiles, path.Join(curPath))
}
return nil
})

return configFiles, err
}

func ReplacePathSeparators(path string) (newPath string) {
newPath = strings.ReplaceAll(path, "\\", string(os.PathSeparator))
newPath = strings.ReplaceAll(newPath, "/", string(os.PathSeparator))
Expand Down
23 changes: 23 additions & 0 deletions pkg/persistence/account/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* @license
* Copyright 2023 Dynatrace LLC
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package account

import "errors"

var ErrRefMissing = errors.New("no referenced target found")
var ErrIdFieldMissing = errors.New("no ref id field found")
var ErrIdFieldNoString = errors.New("ref id field is not a string")
100 changes: 100 additions & 0 deletions pkg/persistence/account/load.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* @license
* Copyright 2023 Dynatrace LLC
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package account

import (
"fmt"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/files"
"github.com/mitchellh/mapstructure"
"github.com/spf13/afero"
"gopkg.in/yaml.v2"
"io"
)

// Load loads account management resources from YAML configuration files
// located within the specified root directory path. It parses the YAML files, extracts policies,
// groups, and users data, and organizes them into a AMResources struct, which is then returned.
func Load(fs afero.Fs, rootPath string) (*AMResources, error) {

Check failure on line 31 in pkg/persistence/account/load.go

View workflow job for this annotation

GitHub Actions / Verify

cognitive complexity 27 of func `Load` is high (> 25) (gocognit)

Check failure on line 31 in pkg/persistence/account/load.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] pkg/persistence/account/load.go#L31

cognitive complexity 27 of func `Load` is high (> 25) (gocognit)
Raw output
pkg/persistence/account/load.go:31:1: cognitive complexity 27 of func `Load` is high (> 25) (gocognit)
func Load(fs afero.Fs, rootPath string) (*AMResources, error) {
^
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd mark this as excluded from the gocognit check.
I've quickly tried if it would actually become nicer to read if we split out the parts into loadPolicies, loadGroups, .. methods. But with error handling still needed there's not much of a difference at all, actually it becomes messier to read.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes the "complexity" is absolutely manageable

resources := &AMResources{
Policies: make(map[string]Policy, 0),
Groups: make(map[string]Group, 0),
Users: make(map[string]User, 0),
}

yamlFilePaths, err := files.FindYamlFiles(fs, rootPath)
if err != nil {
return nil, err
}

for _, yamlFilePath := range yamlFilePaths {
yamlFile, err := fs.Open(yamlFilePath)
if err != nil {
return nil, err
}
content, err := decode(yamlFile)
if err != nil {
return nil, err
}

var policies Policies
err = mapstructure.Decode(content, &policies)
if err != nil {
return nil, err
}
for _, pol := range policies.Policies {
if _, exists := resources.Policies[pol.ID]; exists {
return nil, fmt.Errorf("found duplicate policy with id %q", pol.ID)
}
resources.Policies[pol.ID] = pol
}

var groups Groups
err = mapstructure.Decode(content, &groups)
if err != nil {
return nil, err
}
for _, gr := range groups.Groups {
if _, exists := resources.Groups[gr.ID]; exists {
return nil, fmt.Errorf("found duplicate group with id %q", gr.ID)
}
resources.Groups[gr.ID] = gr
}

var users Users
err = mapstructure.Decode(content, &users)
if err != nil {
return nil, err
}
for _, us := range users.Users {
if _, exists := resources.Users[us.Email]; exists {
return nil, fmt.Errorf("found duplicate user with id %q", us.Email)
}
resources.Users[us.Email] = us
}
}

return resources, nil
}

func decode(in io.ReadCloser) (map[string]any, error) {
defer in.Close()
warber marked this conversation as resolved.
Show resolved Hide resolved
var content map[string]interface{}
if err := yaml.NewDecoder(in).Decode(&content); err != nil {
return content, err
}
return content, nil
}
67 changes: 67 additions & 0 deletions pkg/persistence/account/load_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* @license
* Copyright 2023 Dynatrace LLC
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package account

import (
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"testing"
)

func TestLoad(t *testing.T) {
t.Run("Load single file", func(t *testing.T) {
loaded, err := Load(afero.NewOsFs(), "test-resources/valid.yaml")
assert.NoError(t, err)
assert.Len(t, loaded.Users, 1)
assert.Len(t, loaded.Groups, 1)
assert.Len(t, loaded.Policies, 1)
})

t.Run("Load multiple files", func(t *testing.T) {
loaded, err := Load(afero.NewOsFs(), "test-resources/multi")
assert.NoError(t, err)
assert.Len(t, loaded.Users, 1)
assert.Len(t, loaded.Groups, 1)
assert.Len(t, loaded.Policies, 1)
})

t.Run("Duplicate group", func(t *testing.T) {
_, err := Load(afero.NewOsFs(), "test-resources/duplicate-group.yaml")
assert.Error(t, err)
})

t.Run("Duplicate user", func(t *testing.T) {
_, err := Load(afero.NewOsFs(), "test-resources/duplicate-user.yaml")
assert.Error(t, err)
})

t.Run("Duplicate policy", func(t *testing.T) {
_, err := Load(afero.NewOsFs(), "test-resources/duplicate-policy.yaml")
assert.Error(t, err)
})

t.Run("root folder not found", func(t *testing.T) {
result, err := Load(afero.NewOsFs(), "test-resources/non-existent-folder")
assert.Equal(t, &AMResources{
Policies: make(map[string]Policy, 0),
Groups: make(map[string]Group, 0),
Users: make(map[string]User, 0),
}, result)
assert.NoError(t, err)
})

}
65 changes: 65 additions & 0 deletions pkg/persistence/account/test-resources/duplicate-group.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
users:
- email: [email protected]
groups:
- type: reference
id: my-group
- Log viewer


groups:
- name: My Group
id: my-group
description: This is my group
account:
permissions:
- View my Group Stuff
policies:
- Request My Group Stuff

environment:
- name: myenv123
permissions:
- View environment
policies:
- View environment
- type: reference
id: my-policy

managementZone:
- environment: env12345
managementZone: Mzone
permissions:
- View environment
- name: My Group
id: my-group
description: This is my group
account:
permissions:
- View my Group Stuff
policies:
- Request My Group Stuff

environment:
- name: myenv123
permissions:
- View environment
policies:
- View environment
- type: reference
id: my-policy

managementZone:
- environment: env12345
managementZone: Mzone
permissions:
- View environment

policies:
- name: My Policy
id: my-policy
level:
type: account
description: abcde
policy: |-
ALLOW a:b:c;

17 changes: 17 additions & 0 deletions pkg/persistence/account/test-resources/duplicate-policy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
policies:
- name: My Policy
id: my-policy
level:
type: account
description: abcde
policy: |-
ALLOW a:b:c;
- name: My Policy
id: my-policy
level:
type: account
description: abcde
policy: |-
ALLOW a:b:c;


11 changes: 11 additions & 0 deletions pkg/persistence/account/test-resources/duplicate-user.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
users:
- email: [email protected]
groups:
- type: reference
id: my-group
- Log viewer
- email: [email protected]
groups:
- type: reference
id: my-group
- Log viewer
6 changes: 6 additions & 0 deletions pkg/persistence/account/test-resources/multi/a/a.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
users:
- email: [email protected]
groups:
- type: reference
id: my-group
- Log viewer
Loading
Loading