Skip to content

Commit

Permalink
Add support for triggering workspace runs through matching Git tags
Browse files Browse the repository at this point in the history
  • Loading branch information
hashimoon committed Jun 17, 2022
1 parent 3fbaf67 commit 6a41021
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 0 deletions.
6 changes: 6 additions & 0 deletions errors.go
Expand Up @@ -22,6 +22,12 @@ var (

ErrUnsupportedPrivateKey = errors.New("private Key can only be present with Azure DevOps Server service provider")

ErrUnsupportedBothTagsRegexAndFileTriggersEnabled = errors.New(`"TagsRegex" cannot be populated when "FileTriggersEnabled" is true`)

ErrUnsupportedBothTagsRegexAndTriggerPatterns = errors.New(`"TagsRegex" and "TriggerPrefixes" cannot be populated at the same time`)

ErrUnsupportedBothTagsRegexAndTriggerPrefixes = errors.New(`"TagsRegex" and "TriggerPatterns" cannot be populated at the same time`)

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

ErrUnsupportedBothTriggerPatternsAndPrefixes = errors.New(`"TriggerPatterns" and "TriggerPrefixes" cannot be populated at the same time`)
Expand Down
27 changes: 27 additions & 0 deletions workspace.go
Expand Up @@ -182,6 +182,7 @@ type VCSRepo struct {
OAuthTokenID string `jsonapi:"attr,oauth-token-id"`
RepositoryHTTPURL string `jsonapi:"attr,repository-http-url"`
ServiceProvider string `jsonapi:"attr,service-provider"`
TagsRegex string `jsonapi:"attr,tags-regex"`
WebhookURL string `jsonapi:"attr,webhook-url"`
}

Expand Down Expand Up @@ -354,6 +355,7 @@ type VCSRepoOptions struct {
Identifier *string `json:"identifier,omitempty"`
IngressSubmodules *bool `json:"ingress-submodules,omitempty"`
OAuthTokenID *string `json:"oauth-token-id,omitempty"`
TagsRegex *string `json:"tags-regex,omitempty"`
}

// WorkspaceUpdateOptions represents the options for updating a workspace.
Expand Down Expand Up @@ -1067,6 +1069,18 @@ func (o WorkspaceCreateOptions) valid() error {
o.TriggerPatterns != nil && len(o.TriggerPatterns) > 0 {
return ErrUnsupportedBothTriggerPatternsAndPrefixes
}
if o.VCSRepo!= nil && o.VCSRepo.TagsRegex != nil &&
o.TriggerPatterns != nil && len(o.TriggerPatterns) > 0 {
return ErrUnsupportedBothTagsRegexAndTriggerPatterns
}
if o.VCSRepo!= nil && o.VCSRepo.TagsRegex != nil &&
o.TriggerPrefixes != nil && len(o.TriggerPrefixes) > 0 {
return ErrUnsupportedBothTagsRegexAndTriggerPrefixes
}
if o.VCSRepo != nil && o.VCSRepo.TagsRegex != nil &&
o.FileTriggersEnabled != nil && *o.FileTriggersEnabled {
return ErrUnsupportedBothTagsRegexAndFileTriggersEnabled
}

return nil
}
Expand All @@ -1086,6 +1100,19 @@ func (o WorkspaceUpdateOptions) valid() error {
return ErrUnsupportedBothTriggerPatternsAndPrefixes
}

if o.VCSRepo != nil && o.VCSRepo.TagsRegex != nil &&
o.TriggerPatterns != nil && len(o.TriggerPatterns) > 0 {
return ErrUnsupportedBothTagsRegexAndTriggerPatterns
}
if o.VCSRepo != nil && o.VCSRepo.TagsRegex != nil &&
o.TriggerPrefixes != nil && len(o.TriggerPrefixes) > 0 {
return ErrUnsupportedBothTagsRegexAndTriggerPrefixes
}
if o.VCSRepo != nil && o.VCSRepo.TagsRegex != nil &&
o.FileTriggersEnabled != nil && *o.FileTriggersEnabled {
return ErrUnsupportedBothTagsRegexAndFileTriggersEnabled
}

return nil
}

Expand Down
141 changes: 141 additions & 0 deletions workspace_integration_test.go
Expand Up @@ -322,6 +322,74 @@ func TestWorkspacesCreate(t *testing.T) {
assert.EqualError(t, err, ErrUnsupportedBothTriggerPatternsAndPrefixes.Error())
})

t.Run("when options include tags-regex(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(false),
VCSRepo: &VCSRepoOptions{TagsRegex: String("barfoo")},
}
w, err := client.Workspaces.Create(ctx, orgTest.Name, options)

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

// 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.VCSRepo.TagsRegex, item.VCSRepo.TagsRegex)
}
})

t.Run("when options include both non-empty tags-regex and trigger-patterns error is returned", func(t *testing.T) {
options := WorkspaceCreateOptions{
Name: String("foobar"),
FileTriggersEnabled: Bool(false),
VCSRepo: &VCSRepoOptions{TagsRegex: String("foobar")},
TriggerPatterns: []string{"/module-1/**/*", "/**/networking/*"},
}
w, err := client.Workspaces.Create(ctx, orgTest.Name, options)

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

t.Run("when options include both non-empty tags-regex and trigger-prefixes error is returned", func(t *testing.T) {
options := WorkspaceCreateOptions{
Name: String("foobar"),
FileTriggersEnabled: Bool(false),
VCSRepo: &VCSRepoOptions{TagsRegex: String("foobar")},
TriggerPrefixes: []string{"/module-1", "/module-2"},
}
w, err := client.Workspaces.Create(ctx, orgTest.Name, options)

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

t.Run("when options include both non-empty tags-regex and file-triggers-enabled as true an error is returned", func(t *testing.T) {
options := WorkspaceCreateOptions{
Name: String("foobar"),
FileTriggersEnabled: Bool(true),
VCSRepo: &VCSRepoOptions{TagsRegex: String("foobar")},
}
w, err := client.Workspaces.Create(ctx, orgTest.Name, options)

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

t.Run("when options include trigger-patterns populated and empty trigger-paths workspace is created", 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{
Expand Down Expand Up @@ -727,6 +795,79 @@ func TestWorkspacesUpdate(t *testing.T) {
assert.Equal(t, options.TriggerPatterns, item.TriggerPatterns)
}
})

t.Run("when options include VCSRepo tags-regex (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)),
FileTriggersEnabled: Bool(false),
VCSRepo: &VCSRepoOptions{TagsRegex: String("barfoo")},
})
assert.Equal(t, wTest.VCSRepo.TagsRegex, String("barfoo")) // Sanity test

options := WorkspaceUpdateOptions{
Name: String("foobar"),
FileTriggersEnabled: Bool(false),
VCSRepo: &VCSRepoOptions{TagsRegex: String("foobar")},
}
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.Equal(t, options.VCSRepo.TagsRegex, item.VCSRepo.TagsRegex)
}
})

t.Run("when options include tags-regex and file-triggers-enabled is true an error is returned", func(t *testing.T) {
options := WorkspaceUpdateOptions{
Name: String("foobar"),
FileTriggersEnabled: Bool(true),
VCSRepo: &VCSRepoOptions{TagsRegex: String("foobar")},
}
w, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, options)

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

t.Run("when options include both tags-regex and trigger-prefixes an error is returned", func(t *testing.T) {
options := WorkspaceUpdateOptions{
Name: String("foobar"),
FileTriggersEnabled: Bool(false),
TriggerPrefixes: []string{"/module-1", "/module-2"},
VCSRepo: &VCSRepoOptions{TagsRegex: String("foobar")},
}
w, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, options)

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

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

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

func TestWorkspacesUpdateByID(t *testing.T) {
Expand Down

0 comments on commit 6a41021

Please sign in to comment.