From 573e9a667218127e0c32e9a82fefd9e714638d90 Mon Sep 17 00:00:00 2001 From: hashimoon <98980386+hashimoon@users.noreply.github.com> Date: Thu, 16 Jun 2022 17:51:36 -0700 Subject: [PATCH] Add support for triggering workspace runs through matching Git tags --- errors.go | 6 ++ helper_test.go | 8 +- workspace.go | 27 ++++++ workspace_integration_test.go | 153 ++++++++++++++++++++++++++++++++++ 4 files changed, 190 insertions(+), 4 deletions(-) diff --git a/errors.go b/errors.go index a04a4d17e..1a8e63aff 100644 --- a/errors.go +++ b/errors.go @@ -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`) diff --git a/helper_test.go b/helper_test.go index 227796696..3bda755bc 100644 --- a/helper_test.go +++ b/helper_test.go @@ -1267,12 +1267,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 { diff --git a/workspace.go b/workspace.go index eb8f9c9b8..9ec604243 100644 --- a/workspace.go +++ b/workspace.go @@ -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"` } @@ -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. @@ -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 } @@ -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 } diff --git a/workspace_integration_test.go b/workspace_integration_test.go index 0a9dbf476..2d8bc2eaa 100644 --- a/workspace_integration_test.go +++ b/workspace_integration_test.go @@ -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{ @@ -749,6 +819,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) {