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(segments): deploy support for segments API #1660

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d3c2f79
chore: wip commit
tomazpu Dec 31, 2024
12544b2
feat: wip core implementation working
tomazpu Jan 2, 2025
bc6835f
feat: Refactor and improve error handling of segments deploy
tomazpu Jan 7, 2025
8c3364e
feat: Fix issue with originObject id being wrong and matching over ex…
tomazpu Jan 7, 2025
71214a7
chore: Fix issue after rebase, remove duplicated code
tomazpu Jan 7, 2025
9a908a5
chore: Fix issue after merge of `delete`, where wrong client is used …
tomazpu Jan 8, 2025
f3686e5
feat: Add tests for segments.go
tomazpu Jan 9, 2025
942131a
feat: Add tests for segments.go
tomazpu Jan 10, 2025
1f6fbde
feat: Add tests for segment feature flag in deploy.go
tomazpu Jan 10, 2025
c432857
chore: update segments deploy after core lib update
tomazpu Jan 17, 2025
5c26976
chore: fix tests
tomazpu Jan 17, 2025
91837cd
chore: fix unused variable
tomazpu Jan 17, 2025
4aa32ff
chore: fix unused variable
tomazpu Jan 17, 2025
5b46a35
refactor: Split up DummyClient & fakeClient for testing
Laubi Jan 21, 2025
2bb6076
chore: move `TestSegmentsClient` into separate file and reuse it in d…
tomazpu Jan 22, 2025
25a2958
chore: fix dry-run for `segments` and introduce unit test for it
tomazpu Jan 22, 2025
55a8234
chore: switch externalId generator
tomazpu Jan 23, 2025
b52f10c
chore: fix deploySegmentClient interface to not be exported
tomazpu Jan 23, 2025
150f36a
fix: introduce a `get` call before upsert for deployment strategy in …
tomazpu Jan 23, 2025
2d7baa2
fix: fix typo in comment
tomazpu Jan 23, 2025
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
2 changes: 2 additions & 0 deletions pkg/client/clientset.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ type SegmentClient interface {
List(ctx context.Context) (segments.Response, error)
GetAll(ctx context.Context) ([]segments.Response, error)
Delete(ctx context.Context, id string) (segments.Response, error)
Upsert(ctx context.Context, id string, data []byte) (segments.Response, error)
Get(ctx context.Context, id string) (segments.Response, error)
}

var DefaultMonacoUserAgent = "Dynatrace Monitoring as Code/" + version.MonitoringAsCode + " " + (runtime.GOOS + " " + runtime.GOARCH)
Expand Down
34 changes: 29 additions & 5 deletions pkg/client/dummy_clientset.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,18 @@
package client

import (
context "context"
"context"
"fmt"
"net/http"

coreapi "github.com/dynatrace/dynatrace-configuration-as-code-core/api"
automationApi "github.com/dynatrace/dynatrace-configuration-as-code-core/api/clients/automation"
"github.com/dynatrace/dynatrace-configuration-as-code-core/clients/automation"
buckets "github.com/dynatrace/dynatrace-configuration-as-code-core/clients/buckets"
documents "github.com/dynatrace/dynatrace-configuration-as-code-core/clients/documents"
openpipeline "github.com/dynatrace/dynatrace-configuration-as-code-core/clients/openpipeline"
dtclient "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client/dtclient"
"github.com/dynatrace/dynatrace-configuration-as-code-core/clients/buckets"
"github.com/dynatrace/dynatrace-configuration-as-code-core/clients/documents"
"github.com/dynatrace/dynatrace-configuration-as-code-core/clients/openpipeline"
"github.com/dynatrace/dynatrace-configuration-as-code-core/clients/segments"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client/dtclient"
)

var DummyClientSet = ClientSet{
Expand All @@ -37,6 +38,7 @@ var DummyClientSet = ClientSet{
BucketClient: &DummyBucketClient{},
DocumentClient: &DummyDocumentClient{},
OpenPipelineClient: &DummyOpenPipelineClient{},
SegmentClient: &DummySegmentClient{},
}

var _ AutomationClient = (*DummyAutomationClient)(nil)
Expand Down Expand Up @@ -155,3 +157,25 @@ func (c *DummyOpenPipelineClient) GetAll(ctx context.Context) ([]coreapi.Respons
func (c *DummyOpenPipelineClient) Update(_ context.Context, _ string, _ []byte) (openpipeline.Response, error) {
return openpipeline.Response{}, nil
}

type DummySegmentClient struct{}

func (c *DummySegmentClient) List(_ context.Context) (segments.Response, error) {
return segments.Response{}, nil
}

func (c *DummySegmentClient) GetAll(_ context.Context) ([]segments.Response, error) {
return []segments.Response{}, nil
}

func (c *DummySegmentClient) Delete(_ context.Context, _ string) (segments.Response, error) {
return segments.Response{}, nil
}

func (c *DummySegmentClient) Upsert(_ context.Context, _ string, _ []byte) (segments.Response, error) {
return segments.Response{}, nil
}

func (c *DummySegmentClient) Get(_ context.Context, _ string) (segments.Response, error) {
return segments.Response{}, nil
}
46 changes: 46 additions & 0 deletions pkg/client/test_clientset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* @license
* Copyright 2025 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 client

import (
"context"
"fmt"

"github.com/dynatrace/dynatrace-configuration-as-code-core/clients/segments"
)

type TestSegmentsClient struct{}

func (TestSegmentsClient) List(ctx context.Context) (segments.Response, error) {
return segments.Response{}, fmt.Errorf("unimplemented")
}

func (TestSegmentsClient) GetAll(ctx context.Context) ([]segments.Response, error) {
return []segments.Response{}, fmt.Errorf("unimplemented")
}

func (TestSegmentsClient) Delete(ctx context.Context, id string) (segments.Response, error) {
return segments.Response{}, fmt.Errorf("unimplemented")
}

func (TestSegmentsClient) Upsert(ctx context.Context, id string, data []byte) (segments.Response, error) {
return segments.Response{}, fmt.Errorf("unimplemented")
}

func (TestSegmentsClient) Get(ctx context.Context, id string) (segments.Response, error) {
return segments.Response{}, fmt.Errorf("unimplemented")
}
86 changes: 20 additions & 66 deletions pkg/delete/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import (
"github.com/dynatrace/dynatrace-configuration-as-code-core/clients/automation"
"github.com/dynatrace/dynatrace-configuration-as-code-core/clients/buckets"
"github.com/dynatrace/dynatrace-configuration-as-code-core/clients/documents"
"github.com/dynatrace/dynatrace-configuration-as-code-core/clients/segments"
"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/testutils/matcher"
Expand Down Expand Up @@ -1149,93 +1148,48 @@ func TestDelete_Documents(t *testing.T) {
})
}

type segmentStubClient struct {
called bool
list func() (segments.Response, error)
getAll func() ([]segments.Response, error)
delete func() (segments.Response, error)
}

func (c *segmentStubClient) List(_ context.Context) (segments.Response, error) {
return c.list()
}

func (c *segmentStubClient) GetAll(_ context.Context) ([]segments.Response, error) {
return c.getAll()
}

func (c *segmentStubClient) Delete(_ context.Context, _ string) (segments.Response, error) {
c.called = true
return c.delete()
}

func TestDelete_Segments(t *testing.T) {
t.Run("simple case", func(t *testing.T) {
t.Setenv(featureflags.Segments.EnvName(), "true")

c := segmentStubClient{
delete: func() (segments.Response, error) {
return segments.Response{StatusCode: http.StatusOK}, nil
c := client.TestSegmentsClient{}
given := delete.DeleteEntries{
"segment": {
{
Type: "segment",
OriginObjectId: "originObjectID",
},
}
},
}

t.Run("With Enabled Segment FF", func(t *testing.T) {
t.Setenv(featureflags.Segments.EnvName(), "true")

given := delete.DeleteEntries{
"segment": {
{
Type: "segment",
OriginObjectId: "originObjectID",
},
},
}
err := delete.Configs(context.TODO(), client.ClientSet{SegmentClient: &c}, given)
assert.NoError(t, err)
assert.True(t, c.called, "delete should have been called")
//DummyClient returns unimplemented error on every execution of any method
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we need to update this too:

Suggested change
//DummyClient returns unimplemented error on every execution of any method
// fakeClient returns unimplemented error on every execution of any method

assert.Error(t, err, "unimplemented")
})

t.Run("simple case with FF turned off", func(t *testing.T) {
t.Run("With Disabled Segment FF", func(t *testing.T) {
t.Setenv(featureflags.Segments.EnvName(), "false")

c := segmentStubClient{}

given := delete.DeleteEntries{
"segment": {
{
Type: "segment",
OriginObjectId: "originObjectID",
},
},
}
err := delete.Configs(context.TODO(), client.ClientSet{SegmentClient: &c}, given)
assert.NoError(t, err)
assert.False(t, c.called, "delete should not have been called")
})
}

func TestDeleteAll_Segments(t *testing.T) {
t.Run("simple case", func(t *testing.T) {
t.Setenv(featureflags.Segments.EnvName(), "true")
c := client.TestSegmentsClient{}

c := segmentStubClient{
list: func() (segments.Response, error) {
return segments.Response{StatusCode: http.StatusOK, Data: []byte(`[{"uid": "uid_1"},{"uid": "uid_2"},{"uid": "uid_3"}]`)}, nil
},
delete: func() (segments.Response, error) {
return segments.Response{StatusCode: http.StatusOK}, nil
},
}
t.Run("With Enabled Segment FF", func(t *testing.T) {
t.Setenv(featureflags.Segments.EnvName(), "true")

err := delete.All(context.TODO(), client.ClientSet{SegmentClient: &c}, api.APIs{})
assert.NoError(t, err)
assert.True(t, c.called, "delete should have been called")
//Dummy client returns unimplemented error on every execution of any method
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
//Dummy client returns unimplemented error on every execution of any method
// fakeClient returns unimplemented error on every execution of any method

assert.Error(t, err, "unimplemented")
})

t.Run("FF is turned off", func(t *testing.T) {
t.Run("With Disabled Segment FF", func(t *testing.T) {
t.Setenv(featureflags.Segments.EnvName(), "false")

c := segmentStubClient{}

err := delete.All(context.TODO(), client.ClientSet{SegmentClient: &c}, api.APIs{})
assert.NoError(t, err)
assert.False(t, c.called, "delete should not have been called")
})
}
8 changes: 8 additions & 0 deletions pkg/deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"context"
"errors"
"fmt"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/deploy/internal/segment"
"sync"
"time"

Expand Down Expand Up @@ -309,6 +310,13 @@ func deployConfig(ctx context.Context, c *config.Config, clientset *client.Clien
deployErr = fmt.Errorf("unknown config-type (ID: %q)", c.Type.ID())
}

case config.Segment:
if featureflags.Segments.Enabled() {
resolvedEntity, deployErr = segment.Deploy(ctx, clientset.SegmentClient, properties, renderedConfig, c)
} else {
deployErr = fmt.Errorf("unknown config-type (ID: %q)", c.Type.ID())
}

default:
deployErr = fmt.Errorf("unknown config-type (ID: %q)", c.Type.ID())
}
Expand Down
86 changes: 86 additions & 0 deletions pkg/deploy/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"fmt"
"testing"

"github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/featureflags"

"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"

Expand Down Expand Up @@ -1298,3 +1300,87 @@ func TestDeployConfigGraph_CollectsAllErrors(t *testing.T) {
})

}

func TestDeployConfigFF(t *testing.T) {
dummyClientSet := client.ClientSet{SegmentClient: client.TestSegmentsClient{}}
c := dynatrace.EnvironmentClients{
dynatrace.EnvironmentInfo{Name: "env"}: &dummyClientSet,
}
tests := []struct {
name string
projects []project.Project
featureFlag string
configType config.TypeID
expectedErrString string
}{
{
name: "segments FF test",
projects: []project.Project{
{
Configs: project.ConfigsPerTypePerEnvironments{
"env": project.ConfigsPerType{
"p1": {
config.Config{
Type: config.Segment{},
Environment: "env",
Coordinate: coordinate.Coordinate{
Project: "p1",
Type: "type",
ConfigId: "config1",
},
},
},
},
},
},
},
featureFlag: featureflags.Segments.EnvName(),
configType: config.SegmentID,
},
}

for _, tt := range tests {
t.Run(tt.name+" | FF Enabled", func(t *testing.T) {
t.Setenv(tt.featureFlag, "true")
err := deploy.Deploy(context.Background(), tt.projects, c, deploy.DeployConfigsOptions{})
//Dummy client returns unimplemented error on every execution of any method
assert.Errorf(t, err, "unimplemented")
})
t.Run(tt.name+" | FF Disabled", func(t *testing.T) {
t.Setenv(tt.featureFlag, "false")
err := deploy.Deploy(context.Background(), tt.projects, c, deploy.DeployConfigsOptions{})
assert.Errorf(t, err, fmt.Sprintf("unknown config-type (ID: %q)", tt.configType))
})
}
}

func TestDeployDryRun(t *testing.T) {
c := dynatrace.EnvironmentClients{
dynatrace.EnvironmentInfo{Name: "env", Group: "group"}: &client.DummyClientSet,
}
projects := []project.Project{
{
Configs: project.ConfigsPerTypePerEnvironments{
"env": project.ConfigsPerType{
"p1": {
config.Config{
Type: config.Segment{},
Environment: "env",
Coordinate: coordinate.Coordinate{
Project: "p1",
Type: "segment",
ConfigId: "config1",
},
Template: testutils.GenerateDummyTemplate(t),
},
},
},
},
},
}
t.Setenv(featureflags.Segments.EnvName(), "true")
t.Run("dry-run", func(t *testing.T) {
err := deploy.Deploy(context.Background(), projects, c, deploy.DeployConfigsOptions{DryRun: true})
assert.Empty(t, err)
})
}
Loading
Loading