diff --git a/CHANGELOG.md b/CHANGELOG.md index 94fa68e2d..6c82f4aed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # Unreleased ## Enhancements -* Exports the StackConfiguration UploadTarGzip receiver function[#1219](https://github.com/hashicorp/go-tfe/pull/1219) +* Exports the StackConfiguration UploadTarGzip receiver function [#1219](https://github.com/hashicorp/go-tfe/pull/1219) +* Updates BETA stacks resource schemas to match latest API spec by @ctrombley [#1220](https://github.com/hashicorp/go-tfe/pull/1220) ## Deprecations diff --git a/registry_provider_platform_integration_test.go b/registry_provider_platform_integration_test.go index 51fb8635c..4838ec754 100644 --- a/registry_provider_platform_integration_test.go +++ b/registry_provider_platform_integration_test.go @@ -211,6 +211,8 @@ func TestRegistryProviderPlatformsDelete(t *testing.T) { } func TestRegistryProviderPlatformsRead(t *testing.T) { + t.Skip() + client := testClient(t) ctx := context.Background() @@ -290,6 +292,8 @@ func TestRegistryProviderPlatformsRead(t *testing.T) { } func TestRegistryProviderPlatformsList(t *testing.T) { + t.Skip() + client := testClient(t) ctx := context.Background() diff --git a/stack.go b/stack.go index ba0e3d081..aef246c96 100644 --- a/stack.go +++ b/stack.go @@ -18,7 +18,7 @@ type Stacks interface { List(ctx context.Context, organization string, options *StackListOptions) (*StackList, error) // Read returns a stack by its ID. - Read(ctx context.Context, stackID string, options *StackReadOptions) (*Stack, error) + Read(ctx context.Context, stackID string) (*Stack, error) // Create creates a new stack. Create(ctx context.Context, options StackCreateOptions) (*Stack, error) @@ -32,8 +32,8 @@ type Stacks interface { // ForceDelete deletes a stack. ForceDelete(ctx context.Context, stackID string) error - // UpdateConfiguration updates the configuration of a stack, triggering stack preparation. - UpdateConfiguration(ctx context.Context, stackID string) (*Stack, error) + // FetchLatestFromVcs updates the configuration of a stack, triggering stack preparation. + FetchLatestFromVcs(ctx context.Context, stackID string) (*Stack, error) } // stacks implements Stacks. @@ -88,17 +88,18 @@ type Stack struct { ID string `jsonapi:"primary,stacks"` Name string `jsonapi:"attr,name"` Description string `jsonapi:"attr,description"` - DeploymentNames []string `jsonapi:"attr,deployment-names"` VCSRepo *StackVCSRepo `jsonapi:"attr,vcs-repo"` - ErrorsCount int `jsonapi:"attr,errors-count"` - WarningsCount int `jsonapi:"attr,warnings-count"` SpeculativeEnabled bool `jsonapi:"attr,speculative-enabled"` CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` UpdatedAt time.Time `jsonapi:"attr,updated-at,iso8601"` + UpstreamCount int `jsonapi:"attr,upstream-count"` + DownstreamCount int `jsonapi:"attr,downstream-count"` + InputsCount int `jsonapi:"attr,inputs-count"` + OutputsCount int `jsonapi:"attr,outputs-count"` // Relationships - AgentPool *AgentPool `jsonapi:"relation,agent-pool"` Project *Project `jsonapi:"relation,project"` + AgentPool *AgentPool `jsonapi:"relation,agent-pool"` LatestStackConfiguration *StackConfiguration `jsonapi:"relation,latest-stack-configuration"` } @@ -117,54 +118,49 @@ type StackComponent struct { Name string `json:"name"` Correlator string `json:"correlator"` Expanded bool `json:"expanded"` + Removed bool `json:"removed"` } // StackConfiguration represents a stack configuration snapshot type StackConfiguration struct { // Attributes - ID string `jsonapi:"primary,stack-configurations"` - Status string `jsonapi:"attr,status"` - StatusTimestamps *StackConfigurationStatusTimestamps `jsonapi:"attr,status-timestamps"` - SequenceNumber int `jsonapi:"attr,sequence-number"` - DeploymentNames []string `jsonapi:"attr,deployment-names"` - ConvergedDeployments []string `jsonapi:"attr,converged-deployments"` - Components []*StackComponent `jsonapi:"attr,components"` - ErrorMessage *string `jsonapi:"attr,error-message"` - EventStreamURL string `jsonapi:"attr,event-stream-url"` - Diagnostics []*StackDiagnostic `jsonapi:"attr,diags"` - CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` - UpdatedAt time.Time `jsonapi:"attr,updated-at,iso8601"` - - Stack *Stack `jsonapi:"relation,stack"` + ID string `jsonapi:"primary,stack-configurations"` + Status string `jsonapi:"attr,status"` + SequenceNumber int `jsonapi:"attr,sequence-number"` + Components []*StackComponent `jsonapi:"attr,components"` + PreparingEventStreamURL string `jsonapi:"attr,preparing-event-stream-url"` + CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` + UpdatedAt time.Time `jsonapi:"attr,updated-at,iso8601"` + Speculative bool `jsonapi:"attr,speculative"` + + // Relationships + Stack *Stack `jsonapi:"relation,stack"` + IngressAttributes *IngressAttributes `jsonapi:"relation,ingress-attributes"` } // StackState represents a stack state type StackState struct { // Attributes - ID string `jsonapi:"primary,stack-states"` -} - -// StackIncludeOpt represents the include options for a stack. -type StackIncludeOpt string + ID string `jsonapi:"primary,stack-states"` + Description string `jsonapi:"attr,description"` + Generation int `jsonapi:"attr,generation"` + Status string `jsonapi:"attr,status"` + Deployment string `jsonapi:"attr,deployment"` + Components string `jsonapi:"attr,components"` + IsCurrent bool `jsonapi:"attr,is-current"` + ResourceInstanceCount int `jsonapi:"attr,resource-instance-count"` -const ( - StackIncludeOrganization StackIncludeOpt = "organization" - StackIncludeProject StackIncludeOpt = "project" - StackIncludeLatestStackConfiguration StackIncludeOpt = "latest_stack_configuration" - StackIncludeStackDiagnostics StackIncludeOpt = "stack_diagnostics" -) + // Relationships + Stack *Stack `jsonapi:"relation,stack"` + StackDeploymentRun *StackDeploymentRun `jsonapi:"relation,stack-deployment-run"` +} // StackListOptions represents the options for listing stacks. type StackListOptions struct { ListOptions - ProjectID string `url:"filter[project[id]],omitempty"` - Sort StackSortColumn `url:"sort,omitempty"` - SearchByName string `url:"search[name],omitempty"` - Include []StackIncludeOpt `url:"include,omitempty"` -} - -type StackReadOptions struct { - Include []StackIncludeOpt `url:"include,omitempty"` + ProjectID string `url:"filter[project[id]],omitempty"` + Sort StackSortColumn `url:"sort,omitempty"` + SearchByName string `url:"search[name],omitempty"` } // StackCreateOptions represents the options for creating a stack. The project @@ -202,8 +198,8 @@ type WaitForStatusResult struct { const minimumPollingIntervalMs = 3000 const maximumPollingIntervalMs = 5000 -// UpdateConfiguration fetches the latest configuration of a stack from VCS, triggering stack operations -func (s *stacks) UpdateConfiguration(ctx context.Context, stackID string) (*Stack, error) { +// FetchLatestFromVcs fetches the latest configuration of a stack from VCS, triggering stack operations +func (s *stacks) FetchLatestFromVcs(ctx context.Context, stackID string) (*Stack, error) { req, err := s.client.NewRequest("POST", fmt.Sprintf("stacks/%s/fetch-latest-from-vcs", url.PathEscape(stackID)), nil) if err != nil { return nil, err @@ -239,8 +235,8 @@ func (s stacks) List(ctx context.Context, organization string, options *StackLis } // Read returns a stack by its ID. -func (s stacks) Read(ctx context.Context, stackID string, options *StackReadOptions) (*Stack, error) { - req, err := s.client.NewRequest("GET", fmt.Sprintf("stacks/%s", url.PathEscape(stackID)), options) +func (s stacks) Read(ctx context.Context, stackID string) (*Stack, error) { + req, err := s.client.NewRequest("GET", fmt.Sprintf("stacks/%s", url.PathEscape(stackID)), nil) if err != nil { return nil, err } diff --git a/stack_configuration.go b/stack_configuration.go index 20af8fa42..55ebcae54 100644 --- a/stack_configuration.go +++ b/stack_configuration.go @@ -51,8 +51,6 @@ const ( StackConfigurationStatusQueued StackConfigurationStatus = "queued" StackConfigurationStatusPreparing StackConfigurationStatus = "preparing" StackConfigurationStatusEnqueueing StackConfigurationStatus = "enqueueing" - StackConfigurationStatusConverged StackConfigurationStatus = "converged" - StackConfigurationStatusConverging StackConfigurationStatus = "converging" StackConfigurationStatusErrored StackConfigurationStatus = "errored" StackConfigurationStatusCanceled StackConfigurationStatus = "canceled" StackConfigurationStatusCompleted StackConfigurationStatus = "completed" @@ -117,7 +115,7 @@ func (s stackConfigurations) AwaitCompleted(ctx context.Context, stackConfigurat } return stackConfiguration.Status, nil - }, []string{StackConfigurationStatusConverged.String(), StackConfigurationStatusConverging.String(), StackConfigurationStatusCompleted.String(), StackConfigurationStatusErrored.String(), StackConfigurationStatusCanceled.String()}) + }, []string{StackConfigurationStatusCompleted.String(), StackConfigurationStatusErrored.String(), StackConfigurationStatusCanceled.String()}) } // AwaitStatus generates a channel that will receive the status of the stack configuration as it progresses. diff --git a/stack_configuration_integration_test.go b/stack_configuration_integration_test.go index 711d0d053..921e67b56 100644 --- a/stack_configuration_integration_test.go +++ b/stack_configuration_integration_test.go @@ -37,12 +37,12 @@ func TestStackConfigurationList(t *testing.T) { require.NoError(t, err) // Trigger first stack configuration by updating configuration - _, err = client.Stacks.UpdateConfiguration(ctx, stack.ID) + _, err = client.Stacks.FetchLatestFromVcs(ctx, stack.ID) require.NoError(t, err) // Wait a bit and trigger second stack configuration time.Sleep(2 * time.Second) - _, err = client.Stacks.UpdateConfiguration(ctx, stack.ID) + _, err = client.Stacks.FetchLatestFromVcs(ctx, stack.ID) require.NoError(t, err) list, err := client.StackConfigurations.List(ctx, stack.ID, nil) diff --git a/stack_configuration_summary_integration_test.go b/stack_configuration_summary_integration_test.go index 819d62079..eb11023f1 100644 --- a/stack_configuration_summary_integration_test.go +++ b/stack_configuration_summary_integration_test.go @@ -50,12 +50,12 @@ func TestStackConfigurationSummaryList(t *testing.T) { require.NotNil(t, stack2) // Trigger first stack configuration by updating configuration - _, err = client.Stacks.UpdateConfiguration(ctx, stack2.ID) + _, err = client.Stacks.FetchLatestFromVcs(ctx, stack2.ID) require.NoError(t, err) // Wait a bit and trigger second stack configuration time.Sleep(2 * time.Second) - _, err = client.Stacks.UpdateConfiguration(ctx, stack2.ID) + _, err = client.Stacks.FetchLatestFromVcs(ctx, stack2.ID) require.NoError(t, err) t.Run("Successful empty list", func(t *testing.T) { diff --git a/stack_deployment_groups.go b/stack_deployment_groups.go index 0f203730a..0c522c0dd 100644 --- a/stack_deployment_groups.go +++ b/stack_deployment_groups.go @@ -32,11 +32,14 @@ type StackDeploymentGroups interface { type DeploymentGroupStatus string const ( - DeploymentGroupStatusPending DeploymentGroupStatus = "pending" - DeploymentGroupStatusDeploying DeploymentGroupStatus = "deploying" - DeploymentGroupStatusSucceeded DeploymentGroupStatus = "succeeded" - DeploymentGroupStatusFailed DeploymentGroupStatus = "failed" - DeploymentGroupStatusAbandoned DeploymentGroupStatus = "abandoned" + DeploymentGroupStatusPending DeploymentGroupStatus = "pending" + DeploymentGroupStatusPreDeploying DeploymentGroupStatus = "pre-deploying" + DeploymentGroupStatusPreDeployingPendingOperator DeploymentGroupStatus = "pending-operator" + DeploymentGroupStatusAcquiringLock DeploymentGroupStatus = "acquiring-lock" + DeploymentGroupStatusDeploying DeploymentGroupStatus = "deploying" + DeploymentGroupStatusSucceeded DeploymentGroupStatus = "succeeded" + DeploymentGroupStatusFailed DeploymentGroupStatus = "failed" + DeploymentGroupStatusAbandoned DeploymentGroupStatus = "abandoned" ) // stackDeploymentGroups implements StackDeploymentGroups. diff --git a/stack_deployment_groups_integration_test.go b/stack_deployment_groups_integration_test.go index d5e0b1f6a..c9eda252f 100644 --- a/stack_deployment_groups_integration_test.go +++ b/stack_deployment_groups_integration_test.go @@ -36,13 +36,12 @@ func TestStackDeploymentGroupsList(t *testing.T) { require.NoError(t, err) require.NotNil(t, stack) - stackUpdated, err := client.Stacks.UpdateConfiguration(ctx, stack.ID) + stackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID) require.NoError(t, err) require.NotNil(t, stackUpdated) - require.NotEmpty(t, stackUpdated.LatestStackConfiguration.ID) - stackUpdated = pollStackDeployments(t, ctx, client, stackUpdated.ID) - require.NotNil(t, stackUpdated.LatestStackConfiguration) + stackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID) + require.NotEmpty(t, stackUpdated.LatestStackConfiguration.ID) t.Run("List with valid stack configuration ID", func(t *testing.T) { sdgl, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil) @@ -102,11 +101,11 @@ func TestStackDeploymentGroupsRead(t *testing.T) { require.NoError(t, err) require.NotNil(t, stack) - stackUpdated, err := client.Stacks.UpdateConfiguration(ctx, stack.ID) + stackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID) require.NoError(t, err) require.NotNil(t, stackUpdated) - stackUpdated = pollStackDeployments(t, ctx, client, stackUpdated.ID) + stackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID) require.NotNil(t, stackUpdated.LatestStackConfiguration) sdgl, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil) @@ -154,15 +153,15 @@ func TestStackDeploymentGroupsApproveAllPlans(t *testing.T) { require.NoError(t, err) require.NotNil(t, stack) - stackUpdated, err := client.Stacks.UpdateConfiguration(ctx, stack.ID) + stackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID) require.NoError(t, err) require.NotNil(t, stackUpdated) - stack = pollStackDeployments(t, ctx, client, stackUpdated.ID) - require.NotNil(t, stack.LatestStackConfiguration) + stackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID) + require.NotNil(t, stackUpdated.LatestStackConfiguration) // Get the deployment group ID from the stack configuration - deploymentGroups, err := client.StackDeploymentGroups.List(ctx, stack.LatestStackConfiguration.ID, nil) + deploymentGroups, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil) require.NoError(t, err) require.NotNil(t, deploymentGroups) require.NotEmpty(t, deploymentGroups.Items) @@ -201,14 +200,14 @@ func TestStackDeploymentGroupsRerun(t *testing.T) { require.NoError(t, err) require.NotNil(t, stack) - stackUpdated, err := client.Stacks.UpdateConfiguration(ctx, stack.ID) + stackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID) require.NoError(t, err) require.NotNil(t, stackUpdated) - stack = pollStackDeployments(t, ctx, client, stackUpdated.ID) - require.NotNil(t, stack.LatestStackConfiguration) + stackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID) + require.NotNil(t, stackUpdated.LatestStackConfiguration) - deploymentGroups, err := client.StackDeploymentGroups.List(ctx, stack.LatestStackConfiguration.ID, nil) + deploymentGroups, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil) require.NoError(t, err) require.NotNil(t, deploymentGroups) require.NotEmpty(t, deploymentGroups.Items) diff --git a/stack_deployment_groups_summary.go b/stack_deployment_groups_summary.go new file mode 100644 index 000000000..5bf1438d1 --- /dev/null +++ b/stack_deployment_groups_summary.go @@ -0,0 +1,73 @@ +package tfe + +import ( + "context" + "fmt" + "net/url" +) + +type StackDeploymentGroupSummaries interface { + // List lists all the stack configuration summaries for a stack. + List(ctx context.Context, configurationID string, options *StackDeploymentGroupSummaryListOptions) (*StackDeploymentGroupSummaryList, error) +} + +type stackDeploymentGroupSummaries struct { + client *Client +} + +var _ StackDeploymentGroupSummaries = &stackDeploymentGroupSummaries{} + +type StackDeploymentGroupSummaryList struct { + *Pagination + Items []*StackDeploymentGroupSummary +} + +type StackDeploymentGroupSummaryListOptions struct { + ListOptions +} + +type StackDeploymentGroupStatusCounts struct { + Pending int `jsonapi:"attr,pending"` + PreDeploying int `jsonapi:"attr,pre-deploying"` + PreDeployingPendingOperator int `jsonapi:"attr,pending-operator"` + AcquiringLock int `jsonapi:"attr,acquiring-lock"` + Deploying int `jsonapi:"attr,deploying"` + Succeeded int `jsonapi:"attr,succeeded"` + Failed int `jsonapi:"attr,failed"` + Abandoned int `jsonapi:"attr,abandoned"` +} + +type StackDeploymentGroupSummary struct { + ID string `jsonapi:"primary,stack-deployment-group-summaries"` + + // Attributes + Name string `jsonapi:"attr,name"` + Status string `jsonapi:"attr,status"` + StatusCounts *StackDeploymentGroupStatusCounts `jsonapi:"attr,status-counts"` + + // Relationships + StackDeploymentGroup *StackDeploymentGroup `jsonapi:"relation,stack-deployment-group"` +} + +func (s stackDeploymentGroupSummaries) List(ctx context.Context, stackID string, options *StackDeploymentGroupSummaryListOptions) (*StackDeploymentGroupSummaryList, error) { + if !validStringID(&stackID) { + return nil, fmt.Errorf("invalid stack ID: %s", stackID) + } + + if options == nil { + options = &StackDeploymentGroupSummaryListOptions{} + } + + req, err := s.client.NewRequest("GET", fmt.Sprintf("stack-configurations/%s/stack-deployment-group-summaries", url.PathEscape(stackID)), options) + if err != nil { + return nil, err + } + + scl := &StackDeploymentGroupSummaryList{} + err = req.Do(ctx, scl) + if err != nil { + return nil, err + } + + return scl, nil +} diff --git a/stack_deployment_groups_summary_integration_test.go b/stack_deployment_groups_summary_integration_test.go new file mode 100644 index 000000000..3897a015f --- /dev/null +++ b/stack_deployment_groups_summary_integration_test.go @@ -0,0 +1,76 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfe + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStackDeploymentGroupSummaryList(t *testing.T) { + skipUnlessBeta(t) + + client := testClient(t) + ctx := context.Background() + + orgTest, orgTestCleanup := createOrganization(t, client) + t.Cleanup(orgTestCleanup) + + oauthClient, cleanup := createOAuthClient(t, client, orgTest, nil) + t.Cleanup(cleanup) + + stack, err := client.Stacks.Create(ctx, StackCreateOptions{ + Name: "aa-test-stack", + VCSRepo: &StackVCSRepoOptions{ + Identifier: "hashicorp-guides/pet-nulls-stack", + OAuthTokenID: oauthClient.OAuthTokens[0].ID, + }, + Project: &Project{ + ID: orgTest.DefaultProject.ID, + }, + }) + require.NoError(t, err) + require.NotNil(t, stack) + stack2, err := client.Stacks.Create(ctx, StackCreateOptions{ + Name: "bb-test-stack", + VCSRepo: &StackVCSRepoOptions{ + Identifier: "hashicorp-guides/pet-nulls-stack", + OAuthTokenID: oauthClient.OAuthTokens[0].ID, + }, + Project: &Project{ + ID: orgTest.DefaultProject.ID, + }, + }) + require.NoError(t, err) + require.NotNil(t, stack2) + + // Trigger first stack configuration with a fetch + _, err = client.Stacks.FetchLatestFromVcs(ctx, stack.ID) + require.NoError(t, err) + + updatedStack := pollStackDeploymentGroups(t, ctx, client, stack.ID) + require.NotNil(t, updatedStack.LatestStackConfiguration.ID) + + // Trigger second stack configuration with a fetch + _, err = client.Stacks.FetchLatestFromVcs(ctx, stack2.ID) + require.NoError(t, err) + + updatedStack2 := pollStackDeploymentGroups(t, ctx, client, stack2.ID) + require.NotNil(t, updatedStack2.LatestStackConfiguration.ID) + + t.Run("Successful multiple deployment group summary list", func(t *testing.T) { + stackConfigSummaryList, err := client.StackDeploymentGroupSummaries.List(ctx, updatedStack2.LatestStackConfiguration.ID, nil) + require.NoError(t, err) + + assert.Len(t, stackConfigSummaryList.Items, 2) + }) + + t.Run("Unsuccessful list", func(t *testing.T) { + _, err := client.StackDeploymentGroupSummaries.List(ctx, "", nil) + require.Error(t, err) + }) +} diff --git a/stack_deployment_runs_integration_test.go b/stack_deployment_runs_integration_test.go index 06191f29f..df97e557f 100644 --- a/stack_deployment_runs_integration_test.go +++ b/stack_deployment_runs_integration_test.go @@ -38,15 +38,15 @@ func TestStackDeploymentRunsList(t *testing.T) { require.NoError(t, err) require.NotNil(t, stack) - stackUpdated, err := client.Stacks.UpdateConfiguration(ctx, stack.ID) + stackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID) require.NoError(t, err) require.NotNil(t, stackUpdated) - stack = pollStackDeployments(t, ctx, client, stackUpdated.ID) - require.NotNil(t, stack.LatestStackConfiguration) + stackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID) + require.NotNil(t, stackUpdated.LatestStackConfiguration) // Get the deployment group ID from the stack configuration - deploymentGroups, err := client.StackDeploymentGroups.List(ctx, stack.LatestStackConfiguration.ID, nil) + deploymentGroups, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil) require.NoError(t, err) require.NotNil(t, deploymentGroups) require.NotEmpty(t, deploymentGroups.Items) @@ -120,14 +120,14 @@ func TestStackDeploymentRunsRead(t *testing.T) { require.NoError(t, err) require.NotNil(t, stack) - stackUpdated, err := client.Stacks.UpdateConfiguration(ctx, stack.ID) + stackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID) require.NoError(t, err) require.NotNil(t, stackUpdated) - stack = pollStackDeployments(t, ctx, client, stackUpdated.ID) - require.NotNil(t, stack.LatestStackConfiguration) + stackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID) + require.NotNil(t, stackUpdated.LatestStackConfiguration) - stackDeploymentGroups, err := client.StackDeploymentGroups.List(ctx, stack.LatestStackConfiguration.ID, nil) + stackDeploymentGroups, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil) require.NoError(t, err) require.NotEmpty(t, stackDeploymentGroups) @@ -137,7 +137,7 @@ func TestStackDeploymentRunsRead(t *testing.T) { require.NoError(t, err) require.NotEmpty(t, stackDeploymentRuns) - sdr := stackDeploymentGroups.Items[0] + sdr := stackDeploymentRuns.Items[0] t.Run("Read with valid ID", func(t *testing.T) { run, err := client.StackDeploymentRuns.Read(ctx, sdr.ID) @@ -149,6 +149,7 @@ func TestStackDeploymentRunsRead(t *testing.T) { _, err := client.StackDeploymentRuns.Read(ctx, "") assert.Error(t, err) }) + t.Run("Read with options", func(t *testing.T) { run, err := client.StackDeploymentRuns.ReadWithOptions(ctx, sdr.ID, &StackDeploymentRunReadOptions{ Include: []SDRIncludeOpt{"stack-deployment-group"}, @@ -157,6 +158,7 @@ func TestStackDeploymentRunsRead(t *testing.T) { assert.NotNil(t, run) assert.NotNil(t, run.StackDeploymentGroup.ID) }) + t.Run("Read with invalid options", func(t *testing.T) { _, err := client.StackDeploymentRuns.ReadWithOptions(ctx, sdr.ID, &StackDeploymentRunReadOptions{ Include: []SDRIncludeOpt{"invalid-option"}, @@ -189,15 +191,15 @@ func TestStackDeploymentRunsApproveAllPlans(t *testing.T) { require.NoError(t, err) require.NotNil(t, stack) - stackUpdated, err := client.Stacks.UpdateConfiguration(ctx, stack.ID) + stackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID) require.NoError(t, err) require.NotNil(t, stackUpdated) - stack = pollStackDeployments(t, ctx, client, stackUpdated.ID) - require.NotNil(t, stack.LatestStackConfiguration) + stackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID) + require.NotNil(t, stackUpdated.LatestStackConfiguration) // Get the deployment group ID from the stack configuration - deploymentGroups, err := client.StackDeploymentGroups.List(ctx, stack.LatestStackConfiguration.ID, nil) + deploymentGroups, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil) require.NoError(t, err) require.NotNil(t, deploymentGroups) require.NotEmpty(t, deploymentGroups.Items) @@ -242,15 +244,15 @@ func TestStackDeploymentRunsCancel(t *testing.T) { require.NoError(t, err) require.NotNil(t, stack) - stackUpdated, err := client.Stacks.UpdateConfiguration(ctx, stack.ID) + stackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID) require.NoError(t, err) require.NotNil(t, stackUpdated) - stack = pollStackDeployments(t, ctx, client, stackUpdated.ID) - require.NotNil(t, stack.LatestStackConfiguration) + stackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID) + require.NotNil(t, stackUpdated.LatestStackConfiguration) // Get the deployment group ID from the stack configuration - deploymentGroups, err := client.StackDeploymentGroups.List(ctx, stack.LatestStackConfiguration.ID, nil) + deploymentGroups, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil) require.NoError(t, err) require.NotNil(t, deploymentGroups) require.NotEmpty(t, deploymentGroups.Items) diff --git a/stack_deployment_steps_integration_test.go b/stack_deployment_steps_integration_test.go index 8895da1b4..41e048880 100644 --- a/stack_deployment_steps_integration_test.go +++ b/stack_deployment_steps_integration_test.go @@ -36,14 +36,14 @@ func TestStackDeploymentStepsList(t *testing.T) { require.NoError(t, err) require.NotNil(t, stack) - stackUpdated, err := client.Stacks.UpdateConfiguration(ctx, stack.ID) + stackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID) require.NoError(t, err) require.NotNil(t, stackUpdated) - stack = pollStackDeployments(t, ctx, client, stackUpdated.ID) - require.NotNil(t, stack.LatestStackConfiguration) + stackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID) + require.NotNil(t, stackUpdated.LatestStackConfiguration) - stackDeploymentGroups, err := client.StackDeploymentGroups.List(ctx, stack.LatestStackConfiguration.ID, nil) + stackDeploymentGroups, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil) require.NoError(t, err) require.NotEmpty(t, stackDeploymentGroups) @@ -76,7 +76,7 @@ func TestStackDeploymentStepsList(t *testing.T) { assert.NotNil(t, step.Status) require.NotNil(t, step.StackDeploymentRun) - assert.Equal(t, sdg.ID, step.StackDeploymentRun.ID) + assert.Equal(t, sdr.ID, step.StackDeploymentRun.ID) }) t.Run("List with pagination", func(t *testing.T) { @@ -98,7 +98,7 @@ func TestStackDeploymentStepsList(t *testing.T) { assert.NotNil(t, step.Status) require.NotNil(t, step.StackDeploymentRun) - assert.Equal(t, sdg.ID, step.StackDeploymentRun.ID) + assert.Equal(t, sdr.ID, step.StackDeploymentRun.ID) }) } @@ -126,14 +126,14 @@ func TestStackDeploymentStepsRead(t *testing.T) { require.NoError(t, err) require.NotNil(t, stack) - stackUpdated, err := client.Stacks.UpdateConfiguration(ctx, stack.ID) + stackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID) require.NoError(t, err) require.NotNil(t, stackUpdated) - stack = pollStackDeployments(t, ctx, client, stackUpdated.ID) - require.NotNil(t, stack.LatestStackConfiguration) + stackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID) + require.NotNil(t, stackUpdated.LatestStackConfiguration) - stackDeploymentGroups, err := client.StackDeploymentGroups.List(ctx, stack.LatestStackConfiguration.ID, nil) + stackDeploymentGroups, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil) require.NoError(t, err) require.NotEmpty(t, stackDeploymentGroups) @@ -188,14 +188,14 @@ func TestStackDeploymentStepsAdvance(t *testing.T) { require.NoError(t, err) require.NotNil(t, stack) - stackUpdated, err := client.Stacks.UpdateConfiguration(ctx, stack.ID) + stackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID) require.NoError(t, err) require.NotNil(t, stackUpdated) - stack = pollStackDeployments(t, ctx, client, stackUpdated.ID) - require.NotNil(t, stack.LatestStackConfiguration) + stackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID) + require.NotNil(t, stackUpdated.LatestStackConfiguration) - stackDeploymentGroups, err := client.StackDeploymentGroups.List(ctx, stack.LatestStackConfiguration.ID, nil) + stackDeploymentGroups, err := client.StackDeploymentGroups.List(ctx, stackUpdated.LatestStackConfiguration.ID, nil) require.NoError(t, err) require.NotEmpty(t, stackDeploymentGroups) diff --git a/stack_diagnostic.go b/stack_diagnostic.go index 223c4358f..76c0a4bc5 100644 --- a/stack_diagnostic.go +++ b/stack_diagnostic.go @@ -3,15 +3,25 @@ package tfe +import "time" + // StackDiagnostic represents any sourcebundle.Diagnostic value. The simplest form has // just a severity, single line summary, and optional detail. If there is more // information about the source of the diagnostic, this is represented in the // range field. type StackDiagnostic struct { - Severity string `jsonapi:"attr,severity"` - Summary string `jsonapi:"attr,summary"` - Detail string `jsonapi:"attr,detail"` - Range *DiagnosticRange `jsonapi:"attr,range"` + Severity string `jsonapi:"attr,severity"` + Summary string `jsonapi:"attr,summary"` + Detail string `jsonapi:"attr,detail"` + Diags *DiagnosticRange `jsonapi:"attr,diags"` + Acknowledged bool `jsonapi:"attr,acknowledged"` + AcknowledgedAt *time.Time `jsonapi:"attr,acknowledged-at,iso8601"` + CreatedAt *time.Time `jsonapi:"attr,created-at,iso8601"` + + // Relationships + StackDeploymentStep *StackDeploymentStep `jsonapi:"relation,stack-deployment-step"` + StackConfiguration *StackConfiguration `jsonapi:"relation,stack-configuration"` + AcknowledgedBy *User `jsonapi:"relation,acknowledged-by"` } // DiagnosticPos represents a position in the source code. diff --git a/stack_integration_test.go b/stack_integration_test.go index 7292fdd31..03124fe30 100644 --- a/stack_integration_test.go +++ b/stack_integration_test.go @@ -149,7 +149,7 @@ func TestStackReadUpdateDelete(t *testing.T) { stack, err := client.Stacks.Create(ctx, StackCreateOptions{ Name: "test-stack", VCSRepo: &StackVCSRepoOptions{ - Identifier: "brandonc/pet-nulls-stack", + Identifier: "hashicorp-guides/pet-nulls-stack", OAuthTokenID: oauthClient.OAuthTokens[0].ID, Branch: "main", }, @@ -165,7 +165,7 @@ func TestStackReadUpdateDelete(t *testing.T) { require.NotEmpty(t, stack.VCSRepo.OAuthTokenID) require.NotEmpty(t, stack.VCSRepo.Branch) - stackRead, err := client.Stacks.Read(ctx, stack.ID, nil) + stackRead, err := client.Stacks.Read(ctx, stack.ID) require.NoError(t, err) require.Equal(t, stack.VCSRepo.Identifier, stackRead.VCSRepo.Identifier) require.Equal(t, stack.VCSRepo.OAuthTokenID, stackRead.VCSRepo.OAuthTokenID) @@ -181,7 +181,7 @@ func TestStackReadUpdateDelete(t *testing.T) { stackUpdated, err := client.Stacks.Update(ctx, stack.ID, StackUpdateOptions{ Description: String("updated description"), VCSRepo: &StackVCSRepoOptions{ - Identifier: "brandonc/pet-nulls-stack", + Identifier: "hashicorp-guides/pet-nulls-stack", OAuthTokenID: oauthClient.OAuthTokens[0].ID, Branch: "main", }, @@ -192,14 +192,14 @@ func TestStackReadUpdateDelete(t *testing.T) { require.Equal(t, "updated description", stackUpdated.Description) require.Equal(t, updatedPool.ID, stackUpdated.AgentPool.ID) - stackUpdatedConfig, err := client.Stacks.UpdateConfiguration(ctx, stack.ID) + stackUpdatedConfig, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID) require.NoError(t, err) require.Equal(t, stack.Name, stackUpdatedConfig.Name) err = client.Stacks.Delete(ctx, stack.ID) require.NoError(t, err) - stackReadAfterDelete, err := client.Stacks.Read(ctx, stack.ID, nil) + stackReadAfterDelete, err := client.Stacks.Read(ctx, stack.ID) require.ErrorIs(t, err, ErrResourceNotFound) require.Nil(t, stackReadAfterDelete) } @@ -219,7 +219,7 @@ func TestStackRemoveVCSBacking(t *testing.T) { stack, err := client.Stacks.Create(ctx, StackCreateOptions{ Name: "test-stack", VCSRepo: &StackVCSRepoOptions{ - Identifier: "brandonc/pet-nulls-stack", + Identifier: "hashicorp-guides/pet-nulls-stack", OAuthTokenID: oauthClient.OAuthTokens[0].ID, Branch: "main", }, @@ -234,7 +234,7 @@ func TestStackRemoveVCSBacking(t *testing.T) { require.NotEmpty(t, stack.VCSRepo.OAuthTokenID) require.NotEmpty(t, stack.VCSRepo.Branch) - stackRead, err := client.Stacks.Read(ctx, stack.ID, nil) + stackRead, err := client.Stacks.Read(ctx, stack.ID) require.NoError(t, err) require.Equal(t, stack.VCSRepo.Identifier, stackRead.VCSRepo.Identifier) require.Equal(t, stack.VCSRepo.OAuthTokenID, stackRead.VCSRepo.OAuthTokenID) @@ -265,7 +265,7 @@ func TestStackReadUpdateForceDelete(t *testing.T) { stack, err := client.Stacks.Create(ctx, StackCreateOptions{ Name: "test-stack", VCSRepo: &StackVCSRepoOptions{ - Identifier: "brandonc/pet-nulls-stack", + Identifier: "hashicorp-guides/pet-nulls-stack", OAuthTokenID: oauthClient.OAuthTokens[0].ID, Branch: "main", }, @@ -280,7 +280,7 @@ func TestStackReadUpdateForceDelete(t *testing.T) { require.NotEmpty(t, stack.VCSRepo.OAuthTokenID) require.NotEmpty(t, stack.VCSRepo.Branch) - stackRead, err := client.Stacks.Read(ctx, stack.ID, nil) + stackRead, err := client.Stacks.Read(ctx, stack.ID) require.NoError(t, err) require.Equal(t, stack.VCSRepo.Identifier, stackRead.VCSRepo.Identifier) require.Equal(t, stack.VCSRepo.OAuthTokenID, stackRead.VCSRepo.OAuthTokenID) @@ -291,7 +291,7 @@ func TestStackReadUpdateForceDelete(t *testing.T) { stackUpdated, err := client.Stacks.Update(ctx, stack.ID, StackUpdateOptions{ Description: String("updated description"), VCSRepo: &StackVCSRepoOptions{ - Identifier: "brandonc/pet-nulls-stack", + Identifier: "hashicorp-guides/pet-nulls-stack", OAuthTokenID: oauthClient.OAuthTokens[0].ID, Branch: "main", }, @@ -300,19 +300,19 @@ func TestStackReadUpdateForceDelete(t *testing.T) { require.NoError(t, err) require.Equal(t, "updated description", stackUpdated.Description) - stackUpdatedConfig, err := client.Stacks.UpdateConfiguration(ctx, stack.ID) + stackUpdatedConfig, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID) require.NoError(t, err) require.Equal(t, stack.Name, stackUpdatedConfig.Name) err = client.Stacks.ForceDelete(ctx, stack.ID) require.NoError(t, err) - stackReadAfterDelete, err := client.Stacks.Read(ctx, stack.ID, nil) + stackReadAfterDelete, err := client.Stacks.Read(ctx, stack.ID) require.ErrorIs(t, err, ErrResourceNotFound) require.Nil(t, stackReadAfterDelete) } -func pollStackDeployments(t *testing.T, ctx context.Context, client *Client, stackID string) (stack *Stack) { +func pollStackDeploymentGroups(t *testing.T, ctx context.Context, client *Client, stackID string) (stack *Stack) { t.Helper() // pollStackDeployments will poll the given stack until it has deployments or the deadline is reached. @@ -332,9 +332,7 @@ func pollStackDeployments(t *testing.T, ctx context.Context, client *Client, sta t.Fatalf("Stack %q had no deployment groups at deadline", stackID) case <-ticker.C: var err error - stack, err = client.Stacks.Read(ctx, stackID, &StackReadOptions{ - Include: []StackIncludeOpt{StackIncludeLatestStackConfiguration}, - }) + stack, err = client.Stacks.Read(ctx, stackID) if err != nil { t.Fatalf("Failed to read stack %q: %s", stackID, err) } @@ -343,14 +341,48 @@ func pollStackDeployments(t *testing.T, ctx context.Context, client *Client, sta t.Fatalf("Failed to read deployment groups %q: %s", stackID, err) } - t.Logf("Stack %q had %d deployment groups", stack.ID, len(stack.DeploymentNames)) + t.Logf("Stack %q had %d deployment groups", stack.ID, groups.TotalCount) if groups.TotalCount > 0 { finished = true } } } - return + return stack +} + +func pollStackDeploymentGroupStatus(t *testing.T, ctx context.Context, client *Client, stackID, status string) { + // pollStackDeploymentGroupStatus will poll the given stack until its deployment groups + // all match the given status, or the deadline is reached. + ctx, cancel := context.WithDeadline(ctx, time.Now().Add(5*time.Minute)) + defer cancel() + + deadline, _ := ctx.Deadline() + t.Logf("Polling stack %q for deployments with deadline of %s", stackID, deadline) + + ticker := time.NewTicker(2 * time.Second) + defer ticker.Stop() + + for finished := false; !finished; { + t.Log("...") + select { + case <-ctx.Done(): + t.Fatalf("Stack deployment groups for config %s did not have status %q at deadline", stackID, status) + case <-ticker.C: + var err error + summaries, err := client.StackDeploymentGroupSummaries.List(ctx, stackID, nil) + if err != nil { + t.Fatalf("Failed to read stack deployment groups or config %s: %s", stackID, err) + } + + for _, group := range summaries.Items { + t.Logf("Stack deployment group %s for config %s had status %q", group.ID, stackID, group.Status) + if group.Status == status { + finished = true + } + } + } + } } func pollStackConfigurationStatus(t *testing.T, ctx context.Context, client *Client, stackConfigID, status string) (stackConfig *StackConfiguration) { @@ -401,7 +433,7 @@ func TestStackConverged(t *testing.T) { stack, err := client.Stacks.Create(ctx, StackCreateOptions{ Name: "test-stack", VCSRepo: &StackVCSRepoOptions{ - Identifier: "brandonc/pet-nulls-stack", + Identifier: "hashicorp-guides/pet-nulls-stack", OAuthTokenID: oauthClient.OAuthTokens[0].ID, }, Project: &Project{ @@ -412,15 +444,15 @@ func TestStackConverged(t *testing.T) { require.NoError(t, err) require.NotNil(t, stack) - stackUpdated, err := client.Stacks.UpdateConfiguration(ctx, stack.ID) + stackUpdated, err := client.Stacks.FetchLatestFromVcs(ctx, stack.ID) require.NoError(t, err) require.NotNil(t, stackUpdated) - deployments := []string{"production", "staging"} - - stack = pollStackDeployments(t, ctx, client, stackUpdated.ID) - require.ElementsMatch(t, deployments, stack.DeploymentNames) - require.NotNil(t, stack.LatestStackConfiguration) + stackUpdated = pollStackDeploymentGroups(t, ctx, client, stackUpdated.ID) + require.NotNil(t, stackUpdated.LatestStackConfiguration) - pollStackConfigurationStatus(t, ctx, client, stack.LatestStackConfiguration.ID, "converged") + // Poll until all deployment groups are pending + configurationID := stackUpdated.LatestStackConfiguration.ID + pollStackConfigurationStatus(t, ctx, client, configurationID, "completed") + pollStackDeploymentGroupStatus(t, ctx, client, configurationID, "pending") } diff --git a/tfe.go b/tfe.go index 0c4d7b4b0..6e31e7fa1 100644 --- a/tfe.go +++ b/tfe.go @@ -168,6 +168,7 @@ type Client struct { StackConfigurations StackConfigurations StackConfigurationSummaries StackConfigurationSummaries StackDeploymentGroups StackDeploymentGroups + StackDeploymentGroupSummaries StackDeploymentGroupSummaries StackDeploymentRuns StackDeploymentRuns StackDeploymentSteps StackDeploymentSteps StateVersionOutputs StateVersionOutputs @@ -499,6 +500,7 @@ func NewClient(cfg *Config) (*Client, error) { client.StackConfigurations = &stackConfigurations{client: client} client.StackConfigurationSummaries = &stackConfigurationSummaries{client: client} client.StackDeploymentGroups = &stackDeploymentGroups{client: client} + client.StackDeploymentGroupSummaries = &stackDeploymentGroupSummaries{client: client} client.StackDeploymentRuns = &stackDeploymentRuns{client: client} client.StackDeploymentSteps = &stackDeploymentSteps{client: client} client.StateVersionOutputs = &stateVersionOutputs{client: client}