Skip to content
Closed
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
4 changes: 4 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ var (

ErrRequiredProjectID = errors.New("project ID is required")

ErrRequiredStackID = errors.New("stack ID is required")

ErrWorkspacesRequired = errors.New("workspaces is required")

ErrWorkspaceMinLimit = errors.New("must provide at least one workspace")
Expand Down Expand Up @@ -379,6 +381,8 @@ var (

ErrRequiredWorkspacesList = errors.New("no workspaces list provided")

ErrRequiredStacksList = errors.New("no stacks list provided")

ErrCommentBody = errors.New("comment body is required")

ErrEmptyTeamName = errors.New("team name can not be empty")
Expand Down
133 changes: 133 additions & 0 deletions variable_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,17 @@ type VariableSets interface {
// Remove variable set from projects in the supplied list.
RemoveFromProjects(ctx context.Context, variableSetID string, options VariableSetRemoveFromProjectsOptions) error

// Apply variable set to stacks in the supplied list.
ApplyToStacks(ctx context.Context, variableSetID string, options *VariableSetApplyToStacksOptions) error

// Remove variable set from stacks in the supplied list.
RemoveFromStacks(ctx context.Context, variableSetID string, options *VariableSetRemoveFromStacksOptions) error

// Update list of workspaces to which the variable set is applied to match the supplied list.
UpdateWorkspaces(ctx context.Context, variableSetID string, options *VariableSetUpdateWorkspacesOptions) (*VariableSet, error)

// Update list of stacks to which the variable set is applied to match the supplied list.
UpdateStacks(ctx context.Context, variableSetID string, options *VariableSetUpdateStacksOptions) (*VariableSet, error)
}

// variableSets implements VariableSets.
Expand Down Expand Up @@ -87,6 +96,7 @@ type VariableSet struct {
Parent *Parent `jsonapi:"polyrelation,parent"`
Workspaces []*Workspace `jsonapi:"relation,workspaces,omitempty"`
Projects []*Project `jsonapi:"relation,projects,omitempty"`
Stacks []*Stack `jsonapi:"relation,stacks,omitempty"`
Variables []*VariableSetVariable `jsonapi:"relation,vars,omitempty"`
}

Expand All @@ -98,6 +108,7 @@ const (
VariableSetWorkspaces VariableSetIncludeOpt = "workspaces"
VariableSetProjects VariableSetIncludeOpt = "projects"
VariableSetVars VariableSetIncludeOpt = "vars"
VariableSetStacks VariableSetIncludeOpt = "stacks"
)

// VariableSetListOptions represents the options for listing variable sets.
Expand Down Expand Up @@ -191,6 +202,18 @@ type VariableSetRemoveFromProjectsOptions struct {
Projects []*Project
}

// VariableSetApplyToStacksOptions represents the options for applying variable sets to stacks.
type VariableSetApplyToStacksOptions struct {
// The stacks to apply the variable set to (additive).
Stacks []*Stack
}

// VariableSetRemoveFromStacksOptions represents the options for removing variable sets from stacks.
type VariableSetRemoveFromStacksOptions struct {
// The stacks to remove the variable set from.
Stacks []*Stack
}

// VariableSetUpdateWorkspacesOptions represents a subset of update options specifically for applying variable sets to workspaces
type VariableSetUpdateWorkspacesOptions struct {
// Type is a public field utilized by JSON:API to
Expand All @@ -209,6 +232,24 @@ type privateVariableSetUpdateWorkspacesOptions struct {
Workspaces []*Workspace `jsonapi:"relation,workspaces"`
}

// VariableSetUpdateStacksOptions represents a subset of update options specifically for applying variable sets to stacks
type VariableSetUpdateStacksOptions struct {
// Type is a public field utilized by JSON:API to
// set the resource type via the field tag.
// It is not a user-defined value and does not need to be set.
// https://jsonapi.org/format/#crud-creating
Type string `jsonapi:"primary,varsets"`

// The stacks to be applied to. An empty set means remove all applied
Stacks []*Stack `jsonapi:"relation,stacks"`
}

type privateVariableSetUpdateStacksOptions struct {
Type string `jsonapi:"primary,varsets"`
Global bool `jsonapi:"attr,global"`
Stacks []*Stack `jsonapi:"relation,stacks"`
}

// List all Variable Sets in the organization
func (s *variableSets) List(ctx context.Context, organization string, options *VariableSetListOptions) (*VariableSetList, error) {
if !validStringID(&organization) {
Expand Down Expand Up @@ -444,6 +485,45 @@ func (s variableSets) RemoveFromProjects(ctx context.Context, variableSetID stri
return req.Do(ctx, nil)
}

// ApplyToStacks applies the variable set to stacks in the supplied list.
// This method will return an error if the variable set has global = true.
func (s *variableSets) ApplyToStacks(ctx context.Context, variableSetID string, options *VariableSetApplyToStacksOptions) error {
if !validStringID(&variableSetID) {
return ErrInvalidVariableSetID
}
if err := options.valid(); err != nil {
return err
}

u := fmt.Sprintf("varsets/%s/relationships/stacks", url.PathEscape(variableSetID))
req, err := s.client.NewRequest("POST", u, options.Stacks)
if err != nil {
return err
}
a := req.Do(ctx, nil)
fmt.Printf("a: %v\n", a)
return a
}

// RemoveFromStacks removes the variable set from stacks in the supplied list.
// This method will return an error if the variable set has global = true.
func (s *variableSets) RemoveFromStacks(ctx context.Context, variableSetID string, options *VariableSetRemoveFromStacksOptions) error {
if !validStringID(&variableSetID) {
return ErrInvalidVariableSetID
}
if err := options.valid(); err != nil {
return err
}

u := fmt.Sprintf("varsets/%s/relationships/stacks", url.PathEscape(variableSetID))
req, err := s.client.NewRequest("DELETE", u, options.Stacks)
if err != nil {
return err
}

return req.Do(ctx, nil)
}

// Update variable set to be applied to only the workspaces in the supplied list.
func (s *variableSets) UpdateWorkspaces(ctx context.Context, variableSetID string, options *VariableSetUpdateWorkspacesOptions) (*VariableSet, error) {
if err := options.valid(); err != nil {
Expand Down Expand Up @@ -472,6 +552,34 @@ func (s *variableSets) UpdateWorkspaces(ctx context.Context, variableSetID strin
return v, nil
}

// Update variable set to be applied to only the stacks in the supplied list.
func (s *variableSets) UpdateStacks(ctx context.Context, variableSetID string, options *VariableSetUpdateStacksOptions) (*VariableSet, error) {
if err := options.valid(); err != nil {
return nil, err
}

// Use private struct to ensure global is set to false when applying to stacks
o := privateVariableSetUpdateStacksOptions{
Global: bool(false),
Stacks: options.Stacks,
}

// We force inclusion of stacks as that is the primary data for which we are concerned with confirming changes.
u := fmt.Sprintf("varsets/%s?include=%s", url.PathEscape(variableSetID), VariableSetStacks)
req, err := s.client.NewRequest("PATCH", u, &o)
if err != nil {
return nil, err
}

v := &VariableSet{}
err = req.Do(ctx, v)
if err != nil {
return nil, err
}

return v, nil
}

func (o *VariableSetListOptions) valid() error {
return nil
}
Expand Down Expand Up @@ -525,9 +633,34 @@ func (o VariableSetRemoveFromProjectsOptions) valid() error {
return nil
}

func (o VariableSetApplyToStacksOptions) valid() error {
for _, s := range o.Stacks {
if !validStringID(&s.ID) {
return ErrRequiredStackID
}
}
return nil
}

func (o VariableSetRemoveFromStacksOptions) valid() error {
for _, s := range o.Stacks {
if !validStringID(&s.ID) {
return ErrRequiredStackID
}
}
return nil
}

func (o *VariableSetUpdateWorkspacesOptions) valid() error {
if o == nil || o.Workspaces == nil {
return ErrRequiredWorkspacesList
}
return nil
}

func (o *VariableSetUpdateStacksOptions) valid() error {
if o == nil || o.Stacks == nil {
return ErrRequiredStacksList
}
return nil
}
152 changes: 152 additions & 0 deletions variable_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import (
"context"
"fmt"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -564,6 +565,157 @@
})
}

func TestVariableSetsApplyToAndRemoveFromStacks(t *testing.T) {
client := testClient(t)
ctx := context.Background()

t.Log("Creating organization...")
orgTest, orgTestCleanup := createOrganization(t, client)
t.Cleanup(orgTestCleanup)
t.Logf("Created org: %s", orgTest.Name)

t.Log("Creating variable set...")
vsTest, vsTestCleanup := createVariableSet(t, client, orgTest, VariableSetCreateOptions{})
t.Cleanup(vsTestCleanup)
t.Logf("Created variable set: %s", vsTest.ID)

t.Log("Creating OAuth client...")
oauthClient, cleanup := createOAuthClient(t, client, orgTest, nil)
t.Cleanup(cleanup)
t.Log("Created OAuth client")

t.Log("Creating first stack...")
stackTest1, err := client.Stacks.Create(ctx, StackCreateOptions{
Name: "test-stack-1",
VCSRepo: &StackVCSRepoOptions{
Identifier: "nithishravindra/pet-nulls-stack",
OAuthTokenID: oauthClient.OAuthTokens[0].ID,
},
Project: &Project{
ID: orgTest.DefaultProject.ID,
},
})
require.NoError(t, err)
t.Logf("Created stack 1: %s", stackTest1.ID)
t.Cleanup(func() {
if err := client.Stacks.Delete(ctx, stackTest1.ID); err != nil {
t.Logf("Failed to cleanup stack %s: %v", stackTest1.ID, err)
}
})

// Wait for stack to be ready by triggering configuration update
_, err = client.Stacks.FetchLatestFromVcs(ctx, stackTest1.ID)

Check failure on line 607 in variable_set_test.go

View workflow job for this annotation

GitHub Actions / Lint

ineffectual assignment to err (ineffassign)
// Don't require this to succeed as it might not be needed

stackTest2, err := client.Stacks.Create(ctx, StackCreateOptions{
Name: "test-stack-2",
VCSRepo: &StackVCSRepoOptions{
Identifier: "nithishravindra/pet-nulls-stack",
OAuthTokenID: oauthClient.OAuthTokens[0].ID,
},
Project: &Project{
ID: orgTest.DefaultProject.ID,
},
})
require.NoError(t, err)
t.Cleanup(func() {
if err := client.Stacks.Delete(ctx, stackTest2.ID); err != nil {
t.Logf("Failed to cleanup stack %s: %v", stackTest2.ID, err)
}
})

// Wait for stack to be ready by triggering configuration update
_, err = client.Stacks.FetchLatestFromVcs(ctx, stackTest2.ID)
// Don't require this to succeed as it might not be needed

t.Run("with first stack added", func(t *testing.T) {
options := VariableSetApplyToStacksOptions{
Stacks: []*Stack{{ID: stackTest1.ID}}, // Use minimal stack object with just ID
}
fmt.Printf("ctx: %v\n", ctx)
fmt.Printf("vsTest.ID: %v\n", vsTest.ID)
fmt.Printf("vsTest: %v\n", vsTest)
fmt.Printf("options: %v\n", options)
err = client.VariableSets.ApplyToStacks(ctx, vsTest.ID, &options)
fmt.Printf("err: %v\n", err)
require.NoError(t, err)

readOpts := &VariableSetReadOptions{Include: &[]VariableSetIncludeOpt{VariableSetStacks}}
vsAfter, err := client.VariableSets.Read(ctx, vsTest.ID, readOpts)
require.NoError(t, err)

assert.Equal(t, 1, len(vsAfter.Stacks))
assert.Equal(t, stackTest1.ID, vsAfter.Stacks[0].ID)
})

t.Run("with second stack added", func(t *testing.T) {
options := VariableSetApplyToStacksOptions{
Stacks: []*Stack{stackTest2},
}

err := client.VariableSets.ApplyToStacks(ctx, vsTest.ID, &options)
require.NoError(t, err)

readOpts := &VariableSetReadOptions{Include: &[]VariableSetIncludeOpt{VariableSetStacks}}
vsAfter, err := client.VariableSets.Read(ctx, vsTest.ID, readOpts)
require.NoError(t, err)

assert.Equal(t, 2, len(vsAfter.Stacks))
stackIDs := []string{vsAfter.Stacks[0].ID, vsAfter.Stacks[1].ID}
assert.Contains(t, stackIDs, stackTest1.ID)
assert.Contains(t, stackIDs, stackTest2.ID)
})

t.Run("with first stack removed", func(t *testing.T) {
options := VariableSetRemoveFromStacksOptions{
Stacks: []*Stack{stackTest1},
}

err := client.VariableSets.RemoveFromStacks(ctx, vsTest.ID, &options)
require.NoError(t, err)

readOpts := &VariableSetReadOptions{Include: &[]VariableSetIncludeOpt{VariableSetStacks}}
vsAfter, err := client.VariableSets.Read(ctx, vsTest.ID, readOpts)
require.NoError(t, err)

assert.Equal(t, 1, len(vsAfter.Stacks))
assert.Equal(t, stackTest2.ID, vsAfter.Stacks[0].ID)
})

t.Run("when variable set ID is invalid", func(t *testing.T) {
applyOptions := VariableSetApplyToStacksOptions{
Stacks: []*Stack{stackTest1},
}
err := client.VariableSets.ApplyToStacks(ctx, badIdentifier, &applyOptions)
assert.EqualError(t, err, ErrInvalidVariableSetID.Error())

removeOptions := VariableSetRemoveFromStacksOptions{
Stacks: []*Stack{stackTest1},
}
err = client.VariableSets.RemoveFromStacks(ctx, badIdentifier, &removeOptions)
assert.EqualError(t, err, ErrInvalidVariableSetID.Error())
})

t.Run("when stack ID is invalid", func(t *testing.T) {
badStack := &Stack{
ID: badIdentifier,
}

applyOptions := VariableSetApplyToStacksOptions{
Stacks: []*Stack{badStack},
}
err := client.VariableSets.ApplyToStacks(ctx, vsTest.ID, &applyOptions)
assert.EqualError(t, err, ErrRequiredStackID.Error())

removeOptions := VariableSetRemoveFromStacksOptions{
Stacks: []*Stack{badStack},
}
err = client.VariableSets.RemoveFromStacks(ctx, vsTest.ID, &removeOptions)
assert.EqualError(t, err, ErrRequiredStackID.Error())
})

}

Check failure on line 717 in variable_set_test.go

View workflow job for this annotation

GitHub Actions / Lint

unnecessary trailing newline (whitespace)

func TestVariableSetsUpdateWorkspaces(t *testing.T) {
client := testClient(t)
ctx := context.Background()
Expand Down
Loading