Skip to content

Commit

Permalink
Feature/add trigger patterns (#400)
Browse files Browse the repository at this point in the history
Add trigger patterns
  • Loading branch information
matejrisek committed May 16, 2022
1 parent 6585360 commit e7a3c10
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 6 deletions.
2 changes: 2 additions & 0 deletions errors.go
Expand Up @@ -23,6 +23,8 @@ var (
ErrUnsupportedPrivateKey = errors.New("private Key can only be present with Azure DevOps Server service provider")

ErrUnsupportedRunTriggerType = errors.New(`"RunTriggerType" must be "inbound" when requesting "include" query params`)

ErrUnsupportedBothTriggerPatternsAndPrefixes = errors.New(`"TriggerPatterns" and "TriggerPrefixes"" cannot be used in conjunction`)
)

// Library errors that usually indicate a bug in the implementation of go-tfe
Expand Down
18 changes: 13 additions & 5 deletions helper_test.go
Expand Up @@ -390,11 +390,15 @@ func createOAuthToken(t *testing.T, client *Client, org *Organization) (*OAuthTo
}

func createOrganization(t *testing.T, client *Client) (*Organization, func()) {
ctx := context.Background()
org, err := client.Organizations.Create(ctx, OrganizationCreateOptions{
return createOrganizationWithOptions(t, client, OrganizationCreateOptions{
Name: String("tst-" + randomString(t)),
Email: String(fmt.Sprintf("%s@tfe.local", randomString(t))),
})
}

func createOrganizationWithOptions(t *testing.T, client *Client, options OrganizationCreateOptions) (*Organization, func()) {
ctx := context.Background()
org, err := client.Organizations.Create(ctx, options)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -957,16 +961,20 @@ func createVariable(t *testing.T, client *Client, w *Workspace) (*Variable, func
}

func createWorkspace(t *testing.T, client *Client, org *Organization) (*Workspace, func()) {
return createWorkspaceWithOptions(t, client, org, WorkspaceCreateOptions{
Name: String(randomString(t)),
})
}

func createWorkspaceWithOptions(t *testing.T, client *Client, org *Organization, options WorkspaceCreateOptions) (*Workspace, func()) {
var orgCleanup func()

if org == nil {
org, orgCleanup = createOrganization(t, client)
}

ctx := context.Background()
w, err := client.Workspaces.Create(ctx, org.Name, WorkspaceCreateOptions{
Name: String(randomString(t)),
})
w, err := client.Workspaces.Create(ctx, org.Name, options)
if err != nil {
t.Fatal(err)
}
Expand Down
15 changes: 15 additions & 0 deletions workspace.go
Expand Up @@ -131,6 +131,7 @@ type Workspace struct {
StructuredRunOutputEnabled bool `jsonapi:"attr,structured-run-output-enabled"`
TerraformVersion string `jsonapi:"attr,terraform-version"`
TriggerPrefixes []string `jsonapi:"attr,trigger-prefixes"`
TriggerPatterns []string `jsonapi:"attr,trigger-patterns"`
VCSRepo *VCSRepo `jsonapi:"attr,vcs-repo"`
WorkingDirectory string `jsonapi:"attr,working-directory"`
UpdatedAt time.Time `jsonapi:"attr,updated-at,iso8601"`
Expand Down Expand Up @@ -326,6 +327,10 @@ type WorkspaceCreateOptions struct {
// tracked for changes. See FileTriggersEnabled above for more details.
TriggerPrefixes []string `jsonapi:"attr,trigger-prefixes,omitempty"`

// Optional: List of patterns used to match against changed files in order
// to decide whether to trigger a run or not.
TriggerPatterns []string `jsonapi:"attr,trigger-patterns,omitempty"`

// Settings for the workspace's VCS repository. If omitted, the workspace is
// created without a VCS repo. If included, you must specify at least the
// oauth-token-id and identifier keys below.
Expand Down Expand Up @@ -420,6 +425,10 @@ type WorkspaceUpdateOptions struct {
// tracked for changes. See FileTriggersEnabled above for more details.
TriggerPrefixes []string `jsonapi:"attr,trigger-prefixes,omitempty"`

// Optional: List of patterns used to match against changed files in order
// to decide whether to trigger a run or not.
TriggerPatterns []string `jsonapi:"attr,trigger-patterns,omitempty"`

// Optional: To delete a workspace's existing VCS repo, specify null instead of an
// object. To modify a workspace's existing VCS repo, include whichever of
// the keys below you wish to modify. To add a new VCS repo to a workspace
Expand Down Expand Up @@ -1053,6 +1062,9 @@ func (o WorkspaceCreateOptions) valid() error {
if o.AgentPoolID == nil && (o.ExecutionMode != nil && *o.ExecutionMode == "agent") {
return ErrRequiredAgentPoolID
}
if o.TriggerPrefixes != nil && o.TriggerPatterns != nil {
return ErrUnsupportedBothTriggerPatternsAndPrefixes
}

return nil
}
Expand All @@ -1067,6 +1079,9 @@ func (o WorkspaceUpdateOptions) valid() error {
if o.AgentPoolID == nil && (o.ExecutionMode != nil && *o.ExecutionMode == "agent") {
return ErrRequiredAgentPoolID
}
if o.TriggerPrefixes != nil && o.TriggerPatterns != nil {
return ErrUnsupportedBothTriggerPatternsAndPrefixes
}

return nil
}
Expand Down
97 changes: 96 additions & 1 deletion workspace_integration_test.go
Expand Up @@ -7,6 +7,7 @@ import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"sort"
"strings"
Expand Down Expand Up @@ -277,6 +278,49 @@ func TestWorkspacesCreate(t *testing.T) {
assert.Nil(t, w)
assert.Error(t, err)
})

t.Run("when options include trigger-patterns (behind a feature flag)", func(t *testing.T) {
// Remove the below organization creation and use the one from the outer scope once the feature flag is removed
orgTest, orgTestCleanup := createOrganizationWithOptions(t, client, OrganizationCreateOptions{
Name: String("tst-" + randomString(t)[0:20] + "-ff-on"),
Email: String(fmt.Sprintf("%s@tfe.local", randomString(t))),
})
defer orgTestCleanup()

options := WorkspaceCreateOptions{
Name: String("foobar"),
FileTriggersEnabled: Bool(true),
TriggerPatterns: []string{"/module-1/**/*", "/**/networking/*"},
}
w, err := client.Workspaces.Create(ctx, orgTest.Name, options)

require.NoError(t, err)
assert.Equal(t, options.TriggerPatterns, w.TriggerPatterns)

// Get a refreshed view from the API.
refreshed, err := client.Workspaces.Read(ctx, orgTest.Name, *options.Name)
require.NoError(t, err)

for _, item := range []*Workspace{
w,
refreshed,
} {
assert.Equal(t, options.TriggerPatterns, item.TriggerPatterns)
}
})

t.Run("when options include both trigger-patterns and trigger-paths error is returned", func(t *testing.T) {
options := WorkspaceCreateOptions{
Name: String("foobar"),
FileTriggersEnabled: Bool(true),
TriggerPrefixes: []string{"/module-1", "/module-2"},
TriggerPatterns: []string{"/module-1/**/*", "/**/networking/*"},
}
w, err := client.Workspaces.Create(ctx, orgTest.Name, options)

assert.Nil(t, w)
assert.EqualError(t, err, ErrUnsupportedBothTriggerPatternsAndPrefixes.Error())
})
}

func TestWorkspacesRead(t *testing.T) {
Expand Down Expand Up @@ -579,6 +623,54 @@ func TestWorkspacesUpdate(t *testing.T) {
assert.Nil(t, w)
assert.EqualError(t, err, ErrInvalidOrg.Error())
})

t.Run("when options include trigger-patterns (behind a feature flag)", func(t *testing.T) {
// Remove the below organization and workspace creation and use the one from the outer scope once the feature flag is removed
orgTest, orgTestCleanup := createOrganizationWithOptions(t, client, OrganizationCreateOptions{
Name: String("tst-" + randomString(t)[0:20] + "-ff-on"),
Email: String(fmt.Sprintf("%s@tfe.local", randomString(t))),
})
defer orgTestCleanup()

wTest, _ := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{
Name: String(randomString(t)),
TriggerPrefixes: []string{"/prefix-1/", "/prefix-2/"},
})
assert.Equal(t, wTest.TriggerPrefixes, []string{"/prefix-1/", "/prefix-2/"}) // Sanity test

options := WorkspaceUpdateOptions{
Name: String("foobar"),
FileTriggersEnabled: Bool(true),
TriggerPatterns: []string{"/module-1/**/*", "/**/networking/*"},
}
w, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, options)
require.NoError(t, err)

// Get a refreshed view from the API.
refreshed, err := client.Workspaces.Read(ctx, orgTest.Name, *options.Name)
require.NoError(t, err)

for _, item := range []*Workspace{
w,
refreshed,
} {
assert.Empty(t, options.TriggerPrefixes)
assert.Equal(t, options.TriggerPatterns, item.TriggerPatterns)
}
})

t.Run("when options include both trigger-patterns and trigger-paths error is returned", func(t *testing.T) {
options := WorkspaceUpdateOptions{
Name: String("foobar"),
FileTriggersEnabled: Bool(true),
TriggerPrefixes: []string{"/module-1", "/module-2"},
TriggerPatterns: []string{"/module-1/**/*", "/**/networking/*"},
}
w, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, options)

assert.Nil(t, w)
assert.EqualError(t, err, ErrUnsupportedBothTriggerPatternsAndPrefixes.Error())
})
}

func TestWorkspacesUpdateByID(t *testing.T) {
Expand Down Expand Up @@ -1359,6 +1451,7 @@ func TestWorkspace_Unmarshal(t *testing.T) {
"is-destroyable": true,
},
"trigger-prefixes": []string{"prefix-"},
"trigger-patterns": []string{"pattern1/**/*", "pattern2/**/submodule/*"},
},
},
}
Expand Down Expand Up @@ -1390,13 +1483,15 @@ func TestWorkspace_Unmarshal(t *testing.T) {
assert.Equal(t, ws.VCSRepo.ServiceProvider, "github")
assert.Equal(t, ws.Actions.IsDestroyable, true)
assert.Equal(t, ws.TriggerPrefixes, []string{"prefix-"})
assert.Equal(t, ws.TriggerPatterns, []string{"pattern1/**/*", "pattern2/**/submodule/*"})
}

func TestWorkspaceCreateOptions_Marshal(t *testing.T) {
opts := WorkspaceCreateOptions{
AllowDestroyPlan: Bool(true),
Name: String("my-workspace"),
TriggerPrefixes: []string{"prefix-"},
TriggerPatterns: []string{"pattern1/**/*", "pattern2/**/*"},
VCSRepo: &VCSRepoOptions{
Identifier: String("id"),
OAuthTokenID: String("token"),
Expand All @@ -1410,7 +1505,7 @@ func TestWorkspaceCreateOptions_Marshal(t *testing.T) {
bodyBytes, err := req.BodyBytes()
require.NoError(t, err)

expectedBody := `{"data":{"type":"workspaces","attributes":{"allow-destroy-plan":true,"name":"my-workspace","trigger-prefixes":["prefix-"],"vcs-repo":{"identifier":"id","oauth-token-id":"token"}}}}
expectedBody := `{"data":{"type":"workspaces","attributes":{"allow-destroy-plan":true,"name":"my-workspace","trigger-patterns":["pattern1/**/*","pattern2/**/*"],"trigger-prefixes":["prefix-"],"vcs-repo":{"identifier":"id","oauth-token-id":"token"}}}}
`
assert.Equal(t, expectedBody, string(bodyBytes))
}
Expand Down

0 comments on commit e7a3c10

Please sign in to comment.