diff --git a/backend/remote/backend_mock.go b/backend/remote/backend_mock.go index 7ef2709f8..d337d976e 100644 --- a/backend/remote/backend_mock.go +++ b/backend/remote/backend_mock.go @@ -21,6 +21,7 @@ import ( type mockClient struct { Applies *mockApplies ConfigurationVersions *mockConfigurationVersions + CostEstimates *mockCostEstimates Organizations *mockOrganizations Plans *mockPlans PolicyChecks *mockPolicyChecks @@ -730,6 +731,11 @@ func (m *mockRuns) Create(ctx context.Context, options tfe.RunCreateOptions) (*t return nil, err } + ce, err := m.client.CostEstimates.create(options.ConfigurationVersion.ID, options.Workspace.ID) + if err != nil { + return nil, err + } + p, err := m.client.Plans.create(options.ConfigurationVersion.ID, options.Workspace.ID) if err != nil { return nil, err @@ -741,13 +747,14 @@ func (m *mockRuns) Create(ctx context.Context, options tfe.RunCreateOptions) (*t } r := &tfe.Run{ - ID: generateID("run-"), - Actions: &tfe.RunActions{IsCancelable: true}, - Apply: a, - HasChanges: false, - Permissions: &tfe.RunPermissions{}, - Plan: p, - Status: tfe.RunPending, + ID: generateID("run-"), + Actions: &tfe.RunActions{IsCancelable: true}, + Apply: a, + CostEstimate: ce, + HasChanges: false, + Permissions: &tfe.RunPermissions{}, + Plan: p, + Status: tfe.RunPending, } if pc != nil { @@ -1043,6 +1050,14 @@ func (m *mockWorkspaces) Read(ctx context.Context, organization, workspace strin return w, nil } +func (m *mockWorkspaces) ReadByID(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { + w, ok := m.workspaceIDs[workspaceID] + if !ok { + return nil, tfe.ErrResourceNotFound + } + return w, nil +} + func (m *mockWorkspaces) Update(ctx context.Context, organization, workspace string, options tfe.WorkspaceUpdateOptions) (*tfe.Workspace, error) { w, ok := m.workspaceNames[workspace] if !ok { @@ -1065,6 +1080,28 @@ func (m *mockWorkspaces) Update(ctx context.Context, organization, workspace str return w, nil } +func (m *mockWorkspaces) UpdateByID(ctx context.Context, workspaceID string, options tfe.WorkspaceUpdateOptions) (*tfe.Workspace, error) { + w, ok := m.workspaceIDs[workspaceID] + if !ok { + return nil, tfe.ErrResourceNotFound + } + + if options.Name != nil { + w.Name = *options.Name + } + if options.TerraformVersion != nil { + w.TerraformVersion = *options.TerraformVersion + } + if options.WorkingDirectory != nil { + w.WorkingDirectory = *options.WorkingDirectory + } + + delete(m.workspaceNames, w.Name) + m.workspaceNames[w.Name] = w + + return w, nil +} + func (m *mockWorkspaces) Delete(ctx context.Context, organization, workspace string) error { if w, ok := m.workspaceNames[workspace]; ok { delete(m.workspaceIDs, w.ID) @@ -1073,6 +1110,14 @@ func (m *mockWorkspaces) Delete(ctx context.Context, organization, workspace str return nil } +func (m *mockWorkspaces) DeleteByID(ctx context.Context, workspaceID string) error { + if w, ok := m.workspaceIDs[workspaceID]; ok { + delete(m.workspaceIDs, w.Name) + } + delete(m.workspaceIDs, workspaceID) + return nil +} + func (m *mockWorkspaces) RemoveVCSConnection(ctx context.Context, organization, workspace string) (*tfe.Workspace, error) { w, ok := m.workspaceNames[workspace] if !ok { @@ -1082,6 +1127,15 @@ func (m *mockWorkspaces) RemoveVCSConnection(ctx context.Context, organization, return w, nil } +func (m *mockWorkspaces) RemoveVCSConnectionByID(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { + w, ok := m.workspaceIDs[workspaceID] + if !ok { + return nil, tfe.ErrResourceNotFound + } + w.VCSRepo = nil + return w, nil +} + func (m *mockWorkspaces) Lock(ctx context.Context, workspaceID string, options tfe.WorkspaceLockOptions) (*tfe.Workspace, error) { w, ok := m.workspaceIDs[workspaceID] if !ok { diff --git a/backend/remote/backend_plan_test.go b/backend/remote/backend_plan_test.go index cb5bbe8bb..767b501d4 100644 --- a/backend/remote/backend_plan_test.go +++ b/backend/remote/backend_plan_test.go @@ -59,7 +59,7 @@ func TestRemote_planBasic(t *testing.T) { t.Fatalf("expected remote backend header in output: %s", output) } if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("expected plan summery in output: %s", output) + t.Fatalf("expected plan summary in output: %s", output) } } @@ -113,7 +113,7 @@ func TestRemote_planLongLine(t *testing.T) { t.Fatalf("expected remote backend header in output: %s", output) } if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("expected plan summery in output: %s", output) + t.Fatalf("expected plan summary in output: %s", output) } } @@ -374,7 +374,7 @@ func TestRemote_planNoChanges(t *testing.T) { output := b.CLI.(*cli.MockUi).OutputWriter.String() if !strings.Contains(output, "No changes. Infrastructure is up-to-date.") { - t.Fatalf("expected no changes in plan summery: %s", output) + t.Fatalf("expected no changes in plan summary: %s", output) } if !strings.Contains(output, "Sentinel Result: true") { t.Fatalf("expected policy check result in output: %s", output) @@ -415,7 +415,7 @@ func TestRemote_planForceLocal(t *testing.T) { t.Fatalf("unexpected remote backend header in output: %s", output) } if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("expected plan summery in output: %s", output) + t.Fatalf("expected plan summary in output: %s", output) } } @@ -446,7 +446,7 @@ func TestRemote_planWithoutOperationsEntitlement(t *testing.T) { t.Fatalf("unexpected remote backend header in output: %s", output) } if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("expected plan summery in output: %s", output) + t.Fatalf("expected plan summary in output: %s", output) } } @@ -491,7 +491,7 @@ func TestRemote_planWorkspaceWithoutOperations(t *testing.T) { t.Fatalf("unexpected remote backend header in output: %s", output) } if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("expected plan summery in output: %s", output) + t.Fatalf("expected plan summary in output: %s", output) } } @@ -562,7 +562,7 @@ func TestRemote_planLockTimeout(t *testing.T) { t.Fatalf("expected lock timout error in output: %s", output) } if strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("unexpected plan summery in output: %s", output) + t.Fatalf("unexpected plan summary in output: %s", output) } } @@ -654,7 +654,7 @@ func TestRemote_planWithWorkingDirectory(t *testing.T) { t.Fatalf("expected remote backend header in output: %s", output) } if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("expected plan summery in output: %s", output) + t.Fatalf("expected plan summary in output: %s", output) } } @@ -709,7 +709,7 @@ func TestRemote_planWithWorkingDirectoryFromCurrentPath(t *testing.T) { t.Fatalf("expected remote backend header in output: %s", output) } if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("expected plan summery in output: %s", output) + t.Fatalf("expected plan summary in output: %s", output) } } @@ -777,7 +777,7 @@ func TestRemote_planPolicyPass(t *testing.T) { t.Fatalf("expected policy check result in output: %s", output) } if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("expected plan summery in output: %s", output) + t.Fatalf("expected plan summary in output: %s", output) } } @@ -816,7 +816,7 @@ func TestRemote_planPolicyHardFail(t *testing.T) { t.Fatalf("expected policy check result in output: %s", output) } if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("expected plan summery in output: %s", output) + t.Fatalf("expected plan summary in output: %s", output) } } @@ -855,7 +855,7 @@ func TestRemote_planPolicySoftFail(t *testing.T) { t.Fatalf("expected policy check result in output: %s", output) } if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("expected plan summery in output: %s", output) + t.Fatalf("expected plan summary in output: %s", output) } } diff --git a/backend/remote/testing.go b/backend/remote/testing.go index 09c541897..11197487a 100644 --- a/backend/remote/testing.go +++ b/backend/remote/testing.go @@ -115,6 +115,7 @@ func testBackend(t *testing.T, obj cty.Value) (*Remote, func()) { b.CLI = cli.NewMockUi() b.client.Applies = mc.Applies b.client.ConfigurationVersions = mc.ConfigurationVersions + b.client.CostEstimates = mc.CostEstimates b.client.Organizations = mc.Organizations b.client.Plans = mc.Plans b.client.PolicyChecks = mc.PolicyChecks diff --git a/vendor/github.com/hashicorp/go-tfe/README.md b/vendor/github.com/hashicorp/go-tfe/README.md index 6a5559682..83245e48c 100644 --- a/vendor/github.com/hashicorp/go-tfe/README.md +++ b/vendor/github.com/hashicorp/go-tfe/README.md @@ -136,6 +136,7 @@ tests: $ export TFE_ADDRESS=https://tfe.local $ export TFE_TOKEN=xxxxxxxxxxxxxxxxxxx $ export GITHUB_TOKEN=xxxxxxxxxxxxxxxx +$ export GITHUB_IDENTIFIER=xxxxxxxxxxx ``` In order for the tests relating to queuing and capacity to pass, FRQ should be diff --git a/vendor/github.com/hashicorp/go-tfe/cost_estimation.go b/vendor/github.com/hashicorp/go-tfe/cost_estimation.go deleted file mode 100644 index acb038554..000000000 --- a/vendor/github.com/hashicorp/go-tfe/cost_estimation.go +++ /dev/null @@ -1,121 +0,0 @@ -package tfe - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - "net/url" - "time" -) - -// Compile-time proof of interface implementation. -var _ CostEstimations = (*costEstimations)(nil) - -// CostEstimations describes all the costEstimation related methods that -// the Terraform Enterprise API supports. -// -// TFE API docs: https://www.terraform.io/docs/enterprise/api/ (TBD) -type CostEstimations interface { - // Read a costEstimation by its ID. - Read(ctx context.Context, costEstimationID string) (*CostEstimation, error) - - // Logs retrieves the logs of a costEstimation. - Logs(ctx context.Context, costEstimationID string) (io.Reader, error) -} - -// costEstimations implements CostEstimations. -type costEstimations struct { - client *Client -} - -// CostEstimationStatus represents a costEstimation state. -type CostEstimationStatus string - -//List all available costEstimation statuses. -const ( - CostEstimationCanceled CostEstimationStatus = "canceled" - CostEstimationErrored CostEstimationStatus = "errored" - CostEstimationFinished CostEstimationStatus = "finished" - CostEstimationQueued CostEstimationStatus = "queued" -) - -// CostEstimation represents a Terraform Enterprise costEstimation. -type CostEstimation struct { - ID string `jsonapi:"primary,cost-estimations"` - ErrorMessage string `jsonapi:"attr,error-message"` - Status CostEstimationStatus `jsonapi:"attr,status"` - StatusTimestamps *CostEstimationStatusTimestamps `jsonapi:"attr,status-timestamps"` -} - -// CostEstimationStatusTimestamps holds the timestamps for individual costEstimation statuses. -type CostEstimationStatusTimestamps struct { - CanceledAt time.Time `json:"canceled-at"` - ErroredAt time.Time `json:"errored-at"` - FinishedAt time.Time `json:"finished-at"` - QueuedAt time.Time `json:"queued-at"` -} - -// Read a costEstimation by its ID. -func (s *costEstimations) Read(ctx context.Context, costEstimationID string) (*CostEstimation, error) { - if !validStringID(&costEstimationID) { - return nil, errors.New("invalid value for cost estimation ID") - } - - u := fmt.Sprintf("cost-estimations/%s", url.QueryEscape(costEstimationID)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - ce := &CostEstimation{} - err = s.client.do(ctx, req, ce) - if err != nil { - return nil, err - } - - return ce, nil -} - -// Logs retrieves the logs of a costEstimation. -func (s *costEstimations) Logs(ctx context.Context, costEstimationID string) (io.Reader, error) { - if !validStringID(&costEstimationID) { - return nil, errors.New("invalid value for cost estimation ID") - } - - // Loop until the context is canceled or the cost estimation is finished - // running. The cost estimation logs are not streamed and so only available - // once the estimation is finished. - for { - // Get the costEstimation to make sure it exists. - ce, err := s.Read(ctx, costEstimationID) - if err != nil { - return nil, err - } - - switch ce.Status { - case CostEstimationQueued: - select { - case <-ctx.Done(): - return nil, ctx.Err() - case <-time.After(500 * time.Millisecond): - continue - } - } - - u := fmt.Sprintf("cost-estimations/%s/output", url.QueryEscape(costEstimationID)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - logs := bytes.NewBuffer(nil) - err = s.client.do(ctx, req, logs) - if err != nil { - return nil, err - } - - return logs, nil - } -} diff --git a/vendor/github.com/hashicorp/go-tfe/plan.go b/vendor/github.com/hashicorp/go-tfe/plan.go index 31aef138d..328380f45 100644 --- a/vendor/github.com/hashicorp/go-tfe/plan.go +++ b/vendor/github.com/hashicorp/go-tfe/plan.go @@ -55,6 +55,9 @@ type Plan struct { ResourceDestructions int `jsonapi:"attr,resource-destructions"` Status PlanStatus `jsonapi:"attr,status"` StatusTimestamps *PlanStatusTimestamps `jsonapi:"attr,status-timestamps"` + + // Relations + Exports []*PlanExport `jsonapi:"relation,exports"` } // PlanStatusTimestamps holds the timestamps for individual plan statuses. diff --git a/vendor/github.com/hashicorp/go-tfe/plan_export.go b/vendor/github.com/hashicorp/go-tfe/plan_export.go new file mode 100644 index 000000000..cf9e9ced8 --- /dev/null +++ b/vendor/github.com/hashicorp/go-tfe/plan_export.go @@ -0,0 +1,175 @@ +package tfe + +import ( + "bytes" + "context" + "errors" + "fmt" + "net/url" + "time" +) + +// Compile-time proof of interface implementation. +var _ PlanExports = (*planExports)(nil) + +// PlanExports describes all the plan export related methods that the Terraform +// Enterprise API supports. +// +// TFE API docs: https://www.terraform.io/docs/enterprise/api/plan-exports.html +type PlanExports interface { + // Export a plan by its ID with the given options. + Create(ctx context.Context, options PlanExportCreateOptions) (*PlanExport, error) + + // Read a plan export by its ID. + Read(ctx context.Context, planExportID string) (*PlanExport, error) + + // Delete a plan export by its ID. + Delete(ctx context.Context, planExportID string) error + + // Download the data of an plan export. + Download(ctx context.Context, planExportID string) ([]byte, error) +} + +// planExports implements PlanExports. +type planExports struct { + client *Client +} + +// PlanExportDataType represents the type of data exported from a plan. +type PlanExportDataType string + +// List all available plan export data types. +const ( + PlanExportSentinelMockBundleV0 PlanExportDataType = "sentinel-mock-bundle-v0" +) + +// PlanExportStatus represents a plan export state. +type PlanExportStatus string + +// List all available plan export statuses. +const ( + PlanExportCanceled PlanExportStatus = "canceled" + PlanExportErrored PlanExportStatus = "errored" + PlanExportExpired PlanExportStatus = "expired" + PlanExportFinished PlanExportStatus = "finished" + PlanExportPending PlanExportStatus = "pending" + PlanExportQueued PlanExportStatus = "queued" +) + +// PlanExportStatusTimestamps holds the timestamps for plan export statuses. +type PlanExportStatusTimestamps struct { + CanceledAt time.Time `json:"canceled-at"` + ErroredAt time.Time `json:"errored-at"` + ExpiredAt time.Time `json:"expired-at"` + FinishedAt time.Time `json:"finished-at"` + QueuedAt time.Time `json:"queued-at"` +} + +// PlanExport represents an export of Terraform Enterprise plan data. +type PlanExport struct { + ID string `jsonapi:"primary,plan-exports"` + DataType PlanExportDataType `jsonapi:"attr,data-type"` + Status PlanExportStatus `jsonapi:"attr,status"` + StatusTimestamps *PlanExportStatusTimestamps `jsonapi:"attr,status-timestamps"` +} + +// PlanExportCreateOptions represents the options for exporting data from a plan. +type PlanExportCreateOptions struct { + // For internal use only! + ID string `jsonapi:"primary,plan-exports"` + + // The plan to export. + Plan *Plan `jsonapi:"relation,plan"` + + // The name of the policy set. + DataType *PlanExportDataType `jsonapi:"attr,data-type"` +} + +func (o PlanExportCreateOptions) valid() error { + if o.Plan == nil { + return errors.New("plan is required") + } + if o.DataType == nil { + return errors.New("data type is required") + } + return nil +} + +func (s *planExports) Create(ctx context.Context, options PlanExportCreateOptions) (*PlanExport, error) { + if err := options.valid(); err != nil { + return nil, err + } + + // Make sure we don't send a user provided ID. + options.ID = "" + + req, err := s.client.newRequest("POST", "plan-exports", &options) + if err != nil { + return nil, err + } + + pe := &PlanExport{} + err = s.client.do(ctx, req, pe) + if err != nil { + return nil, err + } + + return pe, err +} + +// Read a plan export by its ID. +func (s *planExports) Read(ctx context.Context, planExportID string) (*PlanExport, error) { + if !validStringID(&planExportID) { + return nil, errors.New("invalid value for plan export ID") + } + + u := fmt.Sprintf("plan-exports/%s", url.QueryEscape(planExportID)) + req, err := s.client.newRequest("GET", u, nil) + if err != nil { + return nil, err + } + + pe := &PlanExport{} + err = s.client.do(ctx, req, pe) + if err != nil { + return nil, err + } + + return pe, nil +} + +// Delete a plan export by ID. +func (s *planExports) Delete(ctx context.Context, planExportID string) error { + if !validStringID(&planExportID) { + return errors.New("invalid value for plan export ID") + } + + u := fmt.Sprintf("plan-exports/%s", url.QueryEscape(planExportID)) + req, err := s.client.newRequest("DELETE", u, nil) + if err != nil { + return err + } + + return s.client.do(ctx, req, nil) +} + +// Download a plan export's data. Data is exported in a .tar.gz format. +func (s *planExports) Download(ctx context.Context, planExportID string) ([]byte, error) { + if !validStringID(&planExportID) { + return nil, errors.New("invalid value for plan export ID") + } + + u := fmt.Sprintf("plan-exports/%s/download", url.QueryEscape(planExportID)) + req, err := s.client.newRequest("GET", u, nil) + if err != nil { + return nil, err + } + + var buf bytes.Buffer + err = s.client.do(ctx, req, &buf) + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/policy_set.go b/vendor/github.com/hashicorp/go-tfe/policy_set.go index d70ad0835..b49e1bd2c 100644 --- a/vendor/github.com/hashicorp/go-tfe/policy_set.go +++ b/vendor/github.com/hashicorp/go-tfe/policy_set.go @@ -28,10 +28,12 @@ type PolicySets interface { // Update an existing policy set. Update(ctx context.Context, policySetID string, options PolicySetUpdateOptions) (*PolicySet, error) - // Add policies to a policy set. + // Add policies to a policy set. This function can only be used when + // there is no VCS repository associated with the policy set. AddPolicies(ctx context.Context, policySetID string, options PolicySetAddPoliciesOptions) error - // Remove policies from a policy set. + // Remove policies from a policy set. This function can only be used + // when there is no VCS repository associated with the policy set. RemovePolicies(ctx context.Context, policySetID string, options PolicySetRemovePoliciesOptions) error // Add workspaces to a policy set. @@ -61,7 +63,9 @@ type PolicySet struct { Name string `jsonapi:"attr,name"` Description string `jsonapi:"attr,description"` Global bool `jsonapi:"attr,global"` + PoliciesPath string `jsonapi:"attr,policies-path"` PolicyCount int `jsonapi:"attr,policy-count"` + VCSRepo *VCSRepo `jsonapi:"attr,vcs-repo"` WorkspaceCount int `jsonapi:"attr,workspace-count"` CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` UpdatedAt time.Time `jsonapi:"attr,updated-at,iso8601"` @@ -115,9 +119,21 @@ type PolicySetCreateOptions struct { // Whether or not the policy set is global. Global *bool `jsonapi:"attr,global,omitempty"` + // The sub-path within the attached VCS repository to ingress. All + // files and directories outside of this sub-path will be ignored. + // This option may only be specified when a VCS repo is present. + PoliciesPath *string `jsonapi:"attr,policies-path,omitempty"` + // The initial members of the policy set. Policies []*Policy `jsonapi:"relation,policies,omitempty"` + // VCS repository information. When present, the policies and + // configuration will be sourced from the specified VCS repository + // instead of being defined within the policy set itself. Note that + // this option is mutually exclusive with the Policies option and + // both cannot be used at the same time. + VCSRepo *VCSRepoOptions `jsonapi:"attr,vcs-repo,omitempty"` + // The initial list of workspaces for which the policy set should be enforced. Workspaces []*Workspace `jsonapi:"relation,workspaces,omitempty"` } diff --git a/vendor/github.com/hashicorp/go-tfe/run.go b/vendor/github.com/hashicorp/go-tfe/run.go index 702a3de81..4517bf262 100644 --- a/vendor/github.com/hashicorp/go-tfe/run.go +++ b/vendor/github.com/hashicorp/go-tfe/run.go @@ -102,7 +102,7 @@ type Run struct { // Relations Apply *Apply `jsonapi:"relation,apply"` ConfigurationVersion *ConfigurationVersion `jsonapi:"relation,configuration-version"` - CostEstimation *CostEstimation `jsonapi:"relation,cost-estimation"` + CostEstimate *CostEstimate `jsonapi:"relation,cost-estimate"` Plan *Plan `jsonapi:"relation,plan"` PolicyChecks []*PolicyCheck `jsonapi:"relation,policy-checks"` Workspace *Workspace `jsonapi:"relation,workspace"` diff --git a/vendor/github.com/hashicorp/go-tfe/tfe.go b/vendor/github.com/hashicorp/go-tfe/tfe.go index c9d173b6a..da3435a65 100644 --- a/vendor/github.com/hashicorp/go-tfe/tfe.go +++ b/vendor/github.com/hashicorp/go-tfe/tfe.go @@ -32,6 +32,8 @@ const ( DefaultAddress = "https://app.terraform.io" // DefaultBasePath on which the API is served. DefaultBasePath = "/api/v2/" + // No-op API endpoint used to configure the rate limiter + PingEndpoint = "ping" ) var ( @@ -106,13 +108,14 @@ type Client struct { Applies Applies ConfigurationVersions ConfigurationVersions - CostEstimations CostEstimations + CostEstimates CostEstimates NotificationConfigurations NotificationConfigurations OAuthClients OAuthClients OAuthTokens OAuthTokens Organizations Organizations OrganizationTokens OrganizationTokens Plans Plans + PlanExports PlanExports Policies Policies PolicyChecks PolicyChecks PolicySets PolicySets @@ -196,13 +199,14 @@ func NewClient(cfg *Config) (*Client, error) { // Create the services. client.Applies = &applies{client: client} client.ConfigurationVersions = &configurationVersions{client: client} - client.CostEstimations = &costEstimations{client: client} + client.CostEstimates = &costEstimates{client: client} client.NotificationConfigurations = ¬ificationConfigurations{client: client} client.OAuthClients = &oAuthClients{client: client} client.OAuthTokens = &oAuthTokens{client: client} client.Organizations = &organizations{client: client} client.OrganizationTokens = &organizationTokens{client: client} client.Plans = &plans{client: client} + client.PlanExports = &planExports{client: client} client.Policies = &policies{client: client} client.PolicyChecks = &policyChecks{client: client} client.PolicySets = &policySets{client: client} @@ -291,7 +295,11 @@ func rateLimitBackoff(min, max time.Duration, attemptNum int, resp *http.Respons // configureLimiter configures the rate limiter. func (c *Client) configureLimiter() error { // Create a new request. - req, err := http.NewRequest("GET", c.baseURL.String(), nil) + u, err := c.baseURL.Parse(PingEndpoint) + if err != nil { + return err + } + req, err := http.NewRequest("GET", u.String(), nil) if err != nil { return err } diff --git a/vendor/github.com/hashicorp/go-tfe/type_helpers.go b/vendor/github.com/hashicorp/go-tfe/type_helpers.go index 87f497dd2..9296e4e6f 100644 --- a/vendor/github.com/hashicorp/go-tfe/type_helpers.go +++ b/vendor/github.com/hashicorp/go-tfe/type_helpers.go @@ -40,6 +40,11 @@ func NotificationDestination(v NotificationDestinationType) *NotificationDestina return &v } +// PlanExportType returns a pointer to the given plan export data type. +func PlanExportType(v PlanExportDataType) *PlanExportDataType { + return &v +} + // ServiceProvider returns a pointer to the given service provider type. func ServiceProvider(v ServiceProviderType) *ServiceProviderType { return &v diff --git a/vendor/github.com/hashicorp/go-tfe/workspace.go b/vendor/github.com/hashicorp/go-tfe/workspace.go index 7b95ac132..e9c55d13b 100644 --- a/vendor/github.com/hashicorp/go-tfe/workspace.go +++ b/vendor/github.com/hashicorp/go-tfe/workspace.go @@ -25,15 +25,27 @@ type Workspaces interface { // Read a workspace by its name. Read(ctx context.Context, organization string, workspace string) (*Workspace, error) + // ReadByID reads a workspace by its ID. + ReadByID(ctx context.Context, workspaceID string) (*Workspace, error) + // Update settings of an existing workspace. Update(ctx context.Context, organization string, workspace string, options WorkspaceUpdateOptions) (*Workspace, error) + // UpdateByID updates the settings of an existing workspace. + UpdateByID(ctx context.Context, workspaceID string, options WorkspaceUpdateOptions) (*Workspace, error) + // Delete a workspace by its name. Delete(ctx context.Context, organization string, workspace string) error + // DeleteByID deletes a workspace by its ID. + DeleteByID(ctx context.Context, workspaceID string) error + // RemoveVCSConnection from a workspace. RemoveVCSConnection(ctx context.Context, organization, workspace string) (*Workspace, error) + // RemoveVCSConnectionByID removes a VCS connection from a workspace. + RemoveVCSConnectionByID(ctx context.Context, workspaceID string) (*Workspace, error) + // Lock a workspace by its ID. Lock(ctx context.Context, workspaceID string, options WorkspaceLockOptions) (*Workspace, error) @@ -69,6 +81,7 @@ type Workspace struct { CanQueueDestroyPlan bool `jsonapi:"attr,can-queue-destroy-plan"` CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` Environment string `jsonapi:"attr,environment"` + FileTriggersEnabled bool `jsonapi:"attr,file-triggers-enabled"` Locked bool `jsonapi:"attr,locked"` MigrationEnvironment string `jsonapi:"attr,migration-environment"` Name string `jsonapi:"attr,name"` @@ -76,6 +89,7 @@ type Workspace struct { Permissions *WorkspacePermissions `jsonapi:"attr,permissions"` QueueAllRuns bool `jsonapi:"attr,queue-all-runs"` TerraformVersion string `jsonapi:"attr,terraform-version"` + TriggerPrefixes []string `jsonapi:"attr,trigger-prefixes"` VCSRepo *VCSRepo `jsonapi:"attr,vcs-repo"` WorkingDirectory string `jsonapi:"attr,working-directory"` @@ -149,6 +163,12 @@ type WorkspaceCreateOptions struct { // Whether to automatically apply changes when a Terraform plan is successful. AutoApply *bool `jsonapi:"attr,auto-apply,omitempty"` + // Whether to filter runs based on the changed files in a VCS push. If + // enabled, the working directory and trigger prefixes describe a set of + // paths which must contain changes for a VCS push to trigger a run. If + // disabled, any push will trigger a run. + FileTriggersEnabled *bool `jsonapi:"attr,file-triggers-enabled,omitempty"` + // The legacy TFE environment to use as the source of the migration, in the // form organization/environment. Omit this unless you are migrating a legacy // environment. @@ -167,6 +187,10 @@ type WorkspaceCreateOptions struct { // workspace, the latest version is selected unless otherwise specified. TerraformVersion *string `jsonapi:"attr,terraform-version,omitempty"` + // List of repository-root-relative paths which list all locations to be + // tracked for changes. See FileTriggersEnabled above for more details. + TriggerPrefixes []string `jsonapi:"attr,trigger-prefixes,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. @@ -251,6 +275,27 @@ func (s *workspaces) Read(ctx context.Context, organization, workspace string) ( return w, nil } +// ReadByID reads a workspace by its ID. +func (s *workspaces) ReadByID(ctx context.Context, workspaceID string) (*Workspace, error) { + if !validStringID(&workspaceID) { + return nil, errors.New("invalid value for workspace ID") + } + + u := fmt.Sprintf("workspaces/%s", url.QueryEscape(workspaceID)) + req, err := s.client.newRequest("GET", u, nil) + if err != nil { + return nil, err + } + + w := &Workspace{} + err = s.client.do(ctx, req, w) + if err != nil { + return nil, err + } + + return w, nil +} + // WorkspaceUpdateOptions represents the options for updating a workspace. type WorkspaceUpdateOptions struct { // For internal use only! @@ -265,6 +310,12 @@ type WorkspaceUpdateOptions struct { // API and UI. Name *string `jsonapi:"attr,name,omitempty"` + // Whether to filter runs based on the changed files in a VCS push. If + // enabled, the working directory and trigger prefixes describe a set of + // paths which must contain changes for a VCS push to trigger a run. If + // disabled, any push will trigger a run. + FileTriggersEnabled *bool `jsonapi:"attr,file-triggers-enabled,omitempty"` + // Whether to queue all runs. Unless this is set to true, runs triggered by // a webhook will not be queued until at least one run is manually queued. QueueAllRuns *bool `jsonapi:"attr,queue-all-runs,omitempty"` @@ -272,6 +323,10 @@ type WorkspaceUpdateOptions struct { // The version of Terraform to use for this workspace. TerraformVersion *string `jsonapi:"attr,terraform-version,omitempty"` + // List of repository-root-relative paths which list all locations to be + // tracked for changes. See FileTriggersEnabled above for more details. + TriggerPrefixes []string `jsonapi:"attr,trigger-prefixes,omitempty"` + // 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 @@ -317,6 +372,30 @@ func (s *workspaces) Update(ctx context.Context, organization, workspace string, return w, nil } +// UpdateByID updates the settings of an existing workspace. +func (s *workspaces) UpdateByID(ctx context.Context, workspaceID string, options WorkspaceUpdateOptions) (*Workspace, error) { + if !validStringID(&workspaceID) { + return nil, errors.New("invalid value for workspace ID") + } + + // Make sure we don't send a user provided ID. + options.ID = "" + + u := fmt.Sprintf("workspaces/%s", url.QueryEscape(workspaceID)) + req, err := s.client.newRequest("PATCH", u, &options) + if err != nil { + return nil, err + } + + w := &Workspace{} + err = s.client.do(ctx, req, w) + if err != nil { + return nil, err + } + + return w, nil +} + // Delete a workspace by its name. func (s *workspaces) Delete(ctx context.Context, organization, workspace string) error { if !validStringID(&organization) { @@ -339,6 +418,21 @@ func (s *workspaces) Delete(ctx context.Context, organization, workspace string) return s.client.do(ctx, req, nil) } +// DeleteByID deletes a workspace by its ID. +func (s *workspaces) DeleteByID(ctx context.Context, workspaceID string) error { + if !validStringID(&workspaceID) { + return errors.New("invalid value for workspace ID") + } + + u := fmt.Sprintf("workspaces/%s", url.QueryEscape(workspaceID)) + req, err := s.client.newRequest("DELETE", u, nil) + if err != nil { + return err + } + + return s.client.do(ctx, req, nil) +} + // workspaceRemoveVCSConnectionOptions type workspaceRemoveVCSConnectionOptions struct { ID string `jsonapi:"primary,workspaces"` @@ -374,6 +468,28 @@ func (s *workspaces) RemoveVCSConnection(ctx context.Context, organization, work return w, nil } +// RemoveVCSConnectionByID removes a VCS connection from a workspace. +func (s *workspaces) RemoveVCSConnectionByID(ctx context.Context, workspaceID string) (*Workspace, error) { + if !validStringID(&workspaceID) { + return nil, errors.New("invalid value for workspace ID") + } + + u := fmt.Sprintf("workspaces/%s", url.QueryEscape(workspaceID)) + + req, err := s.client.newRequest("PATCH", u, &workspaceRemoveVCSConnectionOptions{}) + if err != nil { + return nil, err + } + + w := &Workspace{} + err = s.client.do(ctx, req, w) + if err != nil { + return nil, err + } + + return w, nil +} + // WorkspaceLockOptions represents the options for locking a workspace. type WorkspaceLockOptions struct { // Specifies the reason for locking the workspace.