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

Add support for triggering workspace runs through matching Git tags #434

Merged
merged 2 commits into from Jul 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,7 @@
# Unreleased

* [beta] Add support for triggering Workspace runs through matching Git tags [#434](https://github.com/hashicorp/go-tfe/pull/434)

# v1.4.0

## Enhancements
Expand Down
6 changes: 6 additions & 0 deletions errors.go
Expand Up @@ -26,6 +26,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
8 changes: 4 additions & 4 deletions helper_test.go
Expand Up @@ -1420,12 +1420,12 @@ func createWorkspaceWithVCS(t *testing.T, client *Client, org *Organization, opt
}

if options.VCSRepo == nil {
options.VCSRepo = &VCSRepoOptions{
Identifier: String(githubIdentifier),
OAuthTokenID: String(oc.ID),
}
options.VCSRepo = &VCSRepoOptions{}
}

options.VCSRepo.Identifier = String(githubIdentifier)
options.VCSRepo.OAuthTokenID = String(oc.ID)

ctx := context.Background()
w, err := client.Workspaces.Create(ctx, org.Name, options)
if err != nil {
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 @@ -357,6 +358,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 @@ -1070,6 +1072,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 @@ -1089,6 +1103,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
153 changes: 153 additions & 0 deletions workspace_integration_test.go
Expand Up @@ -344,6 +344,76 @@ 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) {
skipIfBeta(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] + "-git-tag-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, wTestCleanup := createWorkspaceWithVCS(t, client, orgTest, options)
defer wTestCleanup()
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 @@ -762,6 +832,89 @@ 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) {
skipIfBeta(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] + "-git-tag-ff-on"),
Email: String(fmt.Sprintf("%s@tfe.local", randomString(t))),
})
defer orgTestCleanup()

createOptions := WorkspaceCreateOptions{
Name: String("foobar"),
FileTriggersEnabled: Bool(false),
VCSRepo: &VCSRepoOptions{
TagsRegex: String("barfoo")},
}

wTest, wTestCleanup := createWorkspaceWithVCS(t, client, orgTest, createOptions)
defer wTestCleanup()
assert.Equal(t, *createOptions.VCSRepo.TagsRegex, wTest.VCSRepo.TagsRegex)

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)

assert.Equal(t, w.VCSRepo.TagsRegex, *String("foobar")) // Sanity test

// 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.Empty(t, options.TriggerPatterns, item.TriggerPatterns)
}
})

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, ErrUnsupportedBothTagsRegexAndTriggerPatterns.Error())
})
}

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