Skip to content

Commit

Permalink
feat: add dry run (#116)
Browse files Browse the repository at this point in the history
* feat: add dry run

* feat: add swagger

* test: add dry run tests

* refactor: use option pattern

* tests: fix call

* tests: fix params

---------

Co-authored-by: Ishan Arya <[email protected]>
  • Loading branch information
ishanarya0 and Ishan Arya authored Oct 16, 2024
1 parent eb750e2 commit bac23e0
Show file tree
Hide file tree
Showing 10 changed files with 551 additions and 269 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
NAME=github.com/goto/entropy
VERSION=$(shell git describe --tags --always --first-parent 2>/dev/null)
COMMIT=$(shell git rev-parse --short HEAD)
PROTON_COMMIT="564a3d2fa0aa14e435dc4f264fbf63ff2dcf09c2"
PROTON_COMMIT="6c5bc2b621abe2812cc8288a5f6363570bab911a"
BUILD_TIME=$(shell date)
COVERAGE_DIR=coverage
BUILD_DIR=dist
Expand Down
41 changes: 31 additions & 10 deletions core/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@ import (
"github.com/goto/entropy/pkg/errors"
)

func (svc *Service) CreateResource(ctx context.Context, res resource.Resource) (*resource.Resource, error) {
type Options struct {
DryRun bool
}

func WithDryRun(dryRun bool) Options {
return Options{DryRun: dryRun}
}

func (svc *Service) CreateResource(ctx context.Context, res resource.Resource, resourceOpts ...Options) (*resource.Resource, error) {
if err := res.Validate(true); err != nil {
return nil, err
}
Expand All @@ -22,10 +30,15 @@ func (svc *Service) CreateResource(ctx context.Context, res resource.Resource) (
}
res.Spec.Configs = nil

return svc.execAction(ctx, res, act)
dryRun := false
for _, opt := range resourceOpts {
dryRun = opt.DryRun
}

return svc.execAction(ctx, res, act, dryRun)
}

func (svc *Service) UpdateResource(ctx context.Context, urn string, req resource.UpdateRequest) (*resource.Resource, error) {
func (svc *Service) UpdateResource(ctx context.Context, urn string, req resource.UpdateRequest, resourceOpts ...Options) (*resource.Resource, error) {
if len(req.Spec.Dependencies) != 0 {
return nil, errors.ErrUnsupported.WithMsgf("updating dependencies is not supported")
} else if len(req.Spec.Configs) == 0 {
Expand All @@ -37,17 +50,17 @@ func (svc *Service) UpdateResource(ctx context.Context, urn string, req resource
Params: req.Spec.Configs,
Labels: req.Labels,
UserID: req.UserID,
})
}, resourceOpts...)
}

func (svc *Service) DeleteResource(ctx context.Context, urn string) error {
_, actionErr := svc.ApplyAction(ctx, urn, module.ActionRequest{
Name: module.DeleteAction,
})
}, WithDryRun(false))
return actionErr
}

func (svc *Service) ApplyAction(ctx context.Context, urn string, act module.ActionRequest) (*resource.Resource, error) {
func (svc *Service) ApplyAction(ctx context.Context, urn string, act module.ActionRequest, resourceOpts ...Options) (*resource.Resource, error) {
res, err := svc.GetResource(ctx, urn)
if err != nil {
return nil, err
Expand All @@ -56,10 +69,15 @@ func (svc *Service) ApplyAction(ctx context.Context, urn string, act module.Acti
WithMsgf("cannot perform '%s' on resource in '%s'", act.Name, res.State.Status)
}

return svc.execAction(ctx, *res, act)
dryRun := false
for _, opt := range resourceOpts {
dryRun = opt.DryRun
}

return svc.execAction(ctx, *res, act, dryRun)
}

func (svc *Service) execAction(ctx context.Context, res resource.Resource, act module.ActionRequest) (*resource.Resource, error) {
func (svc *Service) execAction(ctx context.Context, res resource.Resource, act module.ActionRequest, dryRun bool) (*resource.Resource, error) {
planned, err := svc.planChange(ctx, res, act)
if err != nil {
return nil, err
Expand All @@ -77,8 +95,11 @@ func (svc *Service) execAction(ctx context.Context, res resource.Resource, act m
}

reason := fmt.Sprintf("action:%s", act.Name)
if err := svc.upsert(ctx, *planned, isCreate(act.Name), true, reason); err != nil {
return nil, err

if !dryRun {
if err := svc.upsert(ctx, *planned, isCreate(act.Name), true, reason); err != nil {
return nil, err
}
}
return planned, nil
}
Expand Down
155 changes: 152 additions & 3 deletions core/write_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func TestService_CreateResource(t *testing.T) {
setup func(t *testing.T) *core.Service
res resource.Resource
want *resource.Resource
options []core.Options
wantErr error
}{
{
Expand Down Expand Up @@ -278,6 +279,41 @@ func TestService_CreateResource(t *testing.T) {
},
wantErr: nil,
},
{
name: "AlreadyExistsWithDryRun",
setup: func(t *testing.T) *core.Service {
t.Helper()
mod := &mocks.ModuleService{}
mod.EXPECT().
PlanAction(mock.Anything, mock.Anything, mock.Anything).
Return(&resource.Resource{
Kind: "mock",
Name: "child",
Project: "project",
State: resource.State{Status: resource.StatusCompleted},
}, nil).Once()

resourceRepo := &mocks.ResourceStore{}

return core.New(resourceRepo, mod, deadClock, defaultSyncBackoff, defaultMaxRetries)
},
res: resource.Resource{
Kind: "mock",
Name: "child",
Project: "project",
},
want: &resource.Resource{
URN: "orn:entropy:mock:project:child",
Kind: "mock",
Name: "child",
Project: "project",
State: resource.State{Status: resource.StatusCompleted},
CreatedAt: frozenTime,
UpdatedAt: frozenTime,
},
options: []core.Options{core.WithDryRun(true)},
wantErr: nil,
},
}

for _, tt := range tests {
Expand All @@ -286,7 +322,7 @@ func TestService_CreateResource(t *testing.T) {
t.Parallel()
svc := tt.setup(t)

got, err := svc.CreateResource(context.Background(), tt.res)
got, err := svc.CreateResource(context.Background(), tt.res, tt.options...)
if tt.wantErr != nil {
assert.Error(t, err)
assert.True(t, errors.Is(err, tt.wantErr))
Expand All @@ -310,12 +346,25 @@ func TestService_UpdateResource(t *testing.T) {
CreatedAt: frozenTime,
}

testResourceForDryRun := resource.Resource{
URN: "orn:entropy:mock:project:childtwo",
Kind: "mock",
Name: "childtwo",
Project: "project",
State: resource.State{Status: resource.StatusCompleted},
Spec: resource.Spec{
Configs: []byte(`{"foo": "bar-old"}`),
},
CreatedAt: frozenTime,
}

tests := []struct {
name string
setup func(t *testing.T) *core.Service
urn string
update resource.UpdateRequest
want *resource.Resource
options []core.Options
wantErr error
}{
{
Expand Down Expand Up @@ -472,6 +521,57 @@ func TestService_UpdateResource(t *testing.T) {
},
wantErr: nil,
},
{
name: "SuccessWithDryRun",
setup: func(t *testing.T) *core.Service {
t.Helper()
mod := &mocks.ModuleService{}
mod.EXPECT().
PlanAction(mock.Anything, mock.Anything, mock.Anything).
Return(&resource.Resource{
URN: "orn:entropy:mock:project:childtwo",
Kind: "mock",
Name: "childtwo",
Project: "project",
Spec: resource.Spec{
Configs: []byte(`{"foo": "bar"}`),
},
State: resource.State{Status: resource.StatusPending},
CreatedAt: frozenTime,
}, nil).Once()
mod.EXPECT().
GetOutput(mock.Anything, mock.Anything).
Return(nil, nil).
Once()

resourceRepo := &mocks.ResourceStore{}
resourceRepo.EXPECT().
GetByURN(mock.Anything, "orn:entropy:mock:project:childtwo").
Return(&testResourceForDryRun, nil).Once()

return core.New(resourceRepo, mod, deadClock, defaultSyncBackoff, defaultMaxRetries)
},
urn: "orn:entropy:mock:project:childtwo",
update: resource.UpdateRequest{
Spec: resource.Spec{Configs: []byte(`{"foo": "bar"}`)},
Labels: map[string]string{"created_by": "test_user", "group": "test_group"},
},
want: &resource.Resource{
URN: "orn:entropy:mock:project:childtwo",
Kind: "mock",
Name: "childtwo",
Project: "project",
CreatedAt: frozenTime,
UpdatedAt: frozenTime,
State: resource.State{Status: resource.StatusPending},
Labels: map[string]string{"created_by": "test_user", "group": "test_group"},
Spec: resource.Spec{
Configs: []byte(`{"foo": "bar"}`),
},
},
options: []core.Options{core.WithDryRun(true)},
wantErr: nil,
},
}

for _, tt := range tests {
Expand All @@ -480,7 +580,7 @@ func TestService_UpdateResource(t *testing.T) {
t.Parallel()
svc := tt.setup(t)

got, err := svc.UpdateResource(context.Background(), tt.urn, tt.update)
got, err := svc.UpdateResource(context.Background(), tt.urn, tt.update, tt.options...)
if tt.wantErr != nil {
assert.Error(t, err)
assert.True(t, errors.Is(err, tt.wantErr))
Expand Down Expand Up @@ -641,6 +741,7 @@ func TestService_ApplyAction(t *testing.T) {
urn string
action module.ActionRequest
want *resource.Resource
options []core.Options
wantErr error
}{
{
Expand Down Expand Up @@ -771,6 +872,54 @@ func TestService_ApplyAction(t *testing.T) {
},
wantErr: nil,
},
{
name: "SuccessWithDryRun",
setup: func(t *testing.T) *core.Service {
t.Helper()
mod := &mocks.ModuleService{}
mod.EXPECT().
PlanAction(mock.Anything, mock.Anything, sampleAction).
Return(&resource.Resource{
URN: "orn:entropy:mock:foo:bar",
Kind: "mock",
Project: "foo",
Name: "bar",
State: resource.State{Status: resource.StatusPending},
}, nil).Once()
mod.EXPECT().
GetOutput(mock.Anything, mock.Anything).
Return(nil, nil).
Once()

resourceRepo := &mocks.ResourceStore{}
resourceRepo.EXPECT().
GetByURN(mock.Anything, "orn:entropy:mock:foo:bar").
Return(&resource.Resource{
URN: "orn:entropy:mock:foo:bar",
Kind: "mock",
Project: "foo",
Name: "bar",
CreatedAt: frozenTime,
State: resource.State{Status: resource.StatusCompleted},
}, nil).
Once()

return core.New(resourceRepo, mod, deadClock, defaultSyncBackoff, defaultMaxRetries)
},
urn: "orn:entropy:mock:foo:bar",
action: sampleAction,
want: &resource.Resource{
URN: "orn:entropy:mock:foo:bar",
Kind: "mock",
Project: "foo",
Name: "bar",
State: resource.State{Status: resource.StatusPending},
CreatedAt: frozenTime,
UpdatedAt: frozenTime,
},
wantErr: nil,
options: []core.Options{core.WithDryRun(true)},
},
}

for _, tt := range tests {
Expand All @@ -779,7 +928,7 @@ func TestService_ApplyAction(t *testing.T) {
t.Parallel()
svc := tt.setup(t)

got, err := svc.ApplyAction(context.Background(), tt.urn, tt.action)
got, err := svc.ApplyAction(context.Background(), tt.urn, tt.action, tt.options...)
if tt.wantErr != nil {
assert.Error(t, err)
assert.True(t, errors.Is(err, tt.wantErr), cmp.Diff(tt.want, err))
Expand Down
Loading

0 comments on commit bac23e0

Please sign in to comment.