vendor latest go-tfe

Signed-off-by: Paul Thrasher <pthrasher@hashicorp.com>
This commit is contained in:
Paul Thrasher 2019-09-27 14:22:34 -07:00
parent d3fc3dee6e
commit d2eaffabea
No known key found for this signature in database
GPG Key ID: D4765F9CA4D82951
12 changed files with 404 additions and 146 deletions

View File

@ -21,6 +21,7 @@ import (
type mockClient struct { type mockClient struct {
Applies *mockApplies Applies *mockApplies
ConfigurationVersions *mockConfigurationVersions ConfigurationVersions *mockConfigurationVersions
CostEstimates *mockCostEstimates
Organizations *mockOrganizations Organizations *mockOrganizations
Plans *mockPlans Plans *mockPlans
PolicyChecks *mockPolicyChecks PolicyChecks *mockPolicyChecks
@ -730,6 +731,11 @@ func (m *mockRuns) Create(ctx context.Context, options tfe.RunCreateOptions) (*t
return nil, err 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) p, err := m.client.Plans.create(options.ConfigurationVersion.ID, options.Workspace.ID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -744,6 +750,7 @@ func (m *mockRuns) Create(ctx context.Context, options tfe.RunCreateOptions) (*t
ID: generateID("run-"), ID: generateID("run-"),
Actions: &tfe.RunActions{IsCancelable: true}, Actions: &tfe.RunActions{IsCancelable: true},
Apply: a, Apply: a,
CostEstimate: ce,
HasChanges: false, HasChanges: false,
Permissions: &tfe.RunPermissions{}, Permissions: &tfe.RunPermissions{},
Plan: p, Plan: p,
@ -1043,6 +1050,14 @@ func (m *mockWorkspaces) Read(ctx context.Context, organization, workspace strin
return w, nil 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) { func (m *mockWorkspaces) Update(ctx context.Context, organization, workspace string, options tfe.WorkspaceUpdateOptions) (*tfe.Workspace, error) {
w, ok := m.workspaceNames[workspace] w, ok := m.workspaceNames[workspace]
if !ok { if !ok {
@ -1065,6 +1080,28 @@ func (m *mockWorkspaces) Update(ctx context.Context, organization, workspace str
return w, nil 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 { func (m *mockWorkspaces) Delete(ctx context.Context, organization, workspace string) error {
if w, ok := m.workspaceNames[workspace]; ok { if w, ok := m.workspaceNames[workspace]; ok {
delete(m.workspaceIDs, w.ID) delete(m.workspaceIDs, w.ID)
@ -1073,6 +1110,14 @@ func (m *mockWorkspaces) Delete(ctx context.Context, organization, workspace str
return nil 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) { func (m *mockWorkspaces) RemoveVCSConnection(ctx context.Context, organization, workspace string) (*tfe.Workspace, error) {
w, ok := m.workspaceNames[workspace] w, ok := m.workspaceNames[workspace]
if !ok { if !ok {
@ -1082,6 +1127,15 @@ func (m *mockWorkspaces) RemoveVCSConnection(ctx context.Context, organization,
return w, nil 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) { func (m *mockWorkspaces) Lock(ctx context.Context, workspaceID string, options tfe.WorkspaceLockOptions) (*tfe.Workspace, error) {
w, ok := m.workspaceIDs[workspaceID] w, ok := m.workspaceIDs[workspaceID]
if !ok { if !ok {

View File

@ -59,7 +59,7 @@ func TestRemote_planBasic(t *testing.T) {
t.Fatalf("expected remote backend header in output: %s", output) t.Fatalf("expected remote backend header in output: %s", output)
} }
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 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) t.Fatalf("expected remote backend header in output: %s", output)
} }
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 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() output := b.CLI.(*cli.MockUi).OutputWriter.String()
if !strings.Contains(output, "No changes. Infrastructure is up-to-date.") { 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") { if !strings.Contains(output, "Sentinel Result: true") {
t.Fatalf("expected policy check result in output: %s", output) 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) t.Fatalf("unexpected remote backend header in output: %s", output)
} }
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 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) t.Fatalf("unexpected remote backend header in output: %s", output)
} }
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 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) t.Fatalf("unexpected remote backend header in output: %s", output)
} }
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 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) t.Fatalf("expected lock timout error in output: %s", output)
} }
if strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 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) t.Fatalf("expected remote backend header in output: %s", output)
} }
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 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) t.Fatalf("expected remote backend header in output: %s", output)
} }
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 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) t.Fatalf("expected policy check result in output: %s", output)
} }
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 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) t.Fatalf("expected policy check result in output: %s", output)
} }
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 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) t.Fatalf("expected policy check result in output: %s", output)
} }
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 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)
} }
} }

View File

@ -115,6 +115,7 @@ func testBackend(t *testing.T, obj cty.Value) (*Remote, func()) {
b.CLI = cli.NewMockUi() b.CLI = cli.NewMockUi()
b.client.Applies = mc.Applies b.client.Applies = mc.Applies
b.client.ConfigurationVersions = mc.ConfigurationVersions b.client.ConfigurationVersions = mc.ConfigurationVersions
b.client.CostEstimates = mc.CostEstimates
b.client.Organizations = mc.Organizations b.client.Organizations = mc.Organizations
b.client.Plans = mc.Plans b.client.Plans = mc.Plans
b.client.PolicyChecks = mc.PolicyChecks b.client.PolicyChecks = mc.PolicyChecks

View File

@ -136,6 +136,7 @@ tests:
$ export TFE_ADDRESS=https://tfe.local $ export TFE_ADDRESS=https://tfe.local
$ export TFE_TOKEN=xxxxxxxxxxxxxxxxxxx $ export TFE_TOKEN=xxxxxxxxxxxxxxxxxxx
$ export GITHUB_TOKEN=xxxxxxxxxxxxxxxx $ export GITHUB_TOKEN=xxxxxxxxxxxxxxxx
$ export GITHUB_IDENTIFIER=xxxxxxxxxxx
``` ```
In order for the tests relating to queuing and capacity to pass, FRQ should be In order for the tests relating to queuing and capacity to pass, FRQ should be

View File

@ -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
}
}

View File

@ -55,6 +55,9 @@ type Plan struct {
ResourceDestructions int `jsonapi:"attr,resource-destructions"` ResourceDestructions int `jsonapi:"attr,resource-destructions"`
Status PlanStatus `jsonapi:"attr,status"` Status PlanStatus `jsonapi:"attr,status"`
StatusTimestamps *PlanStatusTimestamps `jsonapi:"attr,status-timestamps"` StatusTimestamps *PlanStatusTimestamps `jsonapi:"attr,status-timestamps"`
// Relations
Exports []*PlanExport `jsonapi:"relation,exports"`
} }
// PlanStatusTimestamps holds the timestamps for individual plan statuses. // PlanStatusTimestamps holds the timestamps for individual plan statuses.

175
vendor/github.com/hashicorp/go-tfe/plan_export.go generated vendored Normal file
View File

@ -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
}

View File

@ -28,10 +28,12 @@ type PolicySets interface {
// Update an existing policy set. // Update an existing policy set.
Update(ctx context.Context, policySetID string, options PolicySetUpdateOptions) (*PolicySet, error) 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 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 RemovePolicies(ctx context.Context, policySetID string, options PolicySetRemovePoliciesOptions) error
// Add workspaces to a policy set. // Add workspaces to a policy set.
@ -61,7 +63,9 @@ type PolicySet struct {
Name string `jsonapi:"attr,name"` Name string `jsonapi:"attr,name"`
Description string `jsonapi:"attr,description"` Description string `jsonapi:"attr,description"`
Global bool `jsonapi:"attr,global"` Global bool `jsonapi:"attr,global"`
PoliciesPath string `jsonapi:"attr,policies-path"`
PolicyCount int `jsonapi:"attr,policy-count"` PolicyCount int `jsonapi:"attr,policy-count"`
VCSRepo *VCSRepo `jsonapi:"attr,vcs-repo"`
WorkspaceCount int `jsonapi:"attr,workspace-count"` WorkspaceCount int `jsonapi:"attr,workspace-count"`
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
UpdatedAt time.Time `jsonapi:"attr,updated-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. // Whether or not the policy set is global.
Global *bool `jsonapi:"attr,global,omitempty"` 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. // The initial members of the policy set.
Policies []*Policy `jsonapi:"relation,policies,omitempty"` 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. // The initial list of workspaces for which the policy set should be enforced.
Workspaces []*Workspace `jsonapi:"relation,workspaces,omitempty"` Workspaces []*Workspace `jsonapi:"relation,workspaces,omitempty"`
} }

View File

@ -102,7 +102,7 @@ type Run struct {
// Relations // Relations
Apply *Apply `jsonapi:"relation,apply"` Apply *Apply `jsonapi:"relation,apply"`
ConfigurationVersion *ConfigurationVersion `jsonapi:"relation,configuration-version"` ConfigurationVersion *ConfigurationVersion `jsonapi:"relation,configuration-version"`
CostEstimation *CostEstimation `jsonapi:"relation,cost-estimation"` CostEstimate *CostEstimate `jsonapi:"relation,cost-estimate"`
Plan *Plan `jsonapi:"relation,plan"` Plan *Plan `jsonapi:"relation,plan"`
PolicyChecks []*PolicyCheck `jsonapi:"relation,policy-checks"` PolicyChecks []*PolicyCheck `jsonapi:"relation,policy-checks"`
Workspace *Workspace `jsonapi:"relation,workspace"` Workspace *Workspace `jsonapi:"relation,workspace"`

View File

@ -32,6 +32,8 @@ const (
DefaultAddress = "https://app.terraform.io" DefaultAddress = "https://app.terraform.io"
// DefaultBasePath on which the API is served. // DefaultBasePath on which the API is served.
DefaultBasePath = "/api/v2/" DefaultBasePath = "/api/v2/"
// No-op API endpoint used to configure the rate limiter
PingEndpoint = "ping"
) )
var ( var (
@ -106,13 +108,14 @@ type Client struct {
Applies Applies Applies Applies
ConfigurationVersions ConfigurationVersions ConfigurationVersions ConfigurationVersions
CostEstimations CostEstimations CostEstimates CostEstimates
NotificationConfigurations NotificationConfigurations NotificationConfigurations NotificationConfigurations
OAuthClients OAuthClients OAuthClients OAuthClients
OAuthTokens OAuthTokens OAuthTokens OAuthTokens
Organizations Organizations Organizations Organizations
OrganizationTokens OrganizationTokens OrganizationTokens OrganizationTokens
Plans Plans Plans Plans
PlanExports PlanExports
Policies Policies Policies Policies
PolicyChecks PolicyChecks PolicyChecks PolicyChecks
PolicySets PolicySets PolicySets PolicySets
@ -196,13 +199,14 @@ func NewClient(cfg *Config) (*Client, error) {
// Create the services. // Create the services.
client.Applies = &applies{client: client} client.Applies = &applies{client: client}
client.ConfigurationVersions = &configurationVersions{client: client} client.ConfigurationVersions = &configurationVersions{client: client}
client.CostEstimations = &costEstimations{client: client} client.CostEstimates = &costEstimates{client: client}
client.NotificationConfigurations = &notificationConfigurations{client: client} client.NotificationConfigurations = &notificationConfigurations{client: client}
client.OAuthClients = &oAuthClients{client: client} client.OAuthClients = &oAuthClients{client: client}
client.OAuthTokens = &oAuthTokens{client: client} client.OAuthTokens = &oAuthTokens{client: client}
client.Organizations = &organizations{client: client} client.Organizations = &organizations{client: client}
client.OrganizationTokens = &organizationTokens{client: client} client.OrganizationTokens = &organizationTokens{client: client}
client.Plans = &plans{client: client} client.Plans = &plans{client: client}
client.PlanExports = &planExports{client: client}
client.Policies = &policies{client: client} client.Policies = &policies{client: client}
client.PolicyChecks = &policyChecks{client: client} client.PolicyChecks = &policyChecks{client: client}
client.PolicySets = &policySets{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. // configureLimiter configures the rate limiter.
func (c *Client) configureLimiter() error { func (c *Client) configureLimiter() error {
// Create a new request. // 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 { if err != nil {
return err return err
} }

View File

@ -40,6 +40,11 @@ func NotificationDestination(v NotificationDestinationType) *NotificationDestina
return &v 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. // ServiceProvider returns a pointer to the given service provider type.
func ServiceProvider(v ServiceProviderType) *ServiceProviderType { func ServiceProvider(v ServiceProviderType) *ServiceProviderType {
return &v return &v

View File

@ -25,15 +25,27 @@ type Workspaces interface {
// Read a workspace by its name. // Read a workspace by its name.
Read(ctx context.Context, organization string, workspace string) (*Workspace, error) 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 settings of an existing workspace.
Update(ctx context.Context, organization string, workspace string, options WorkspaceUpdateOptions) (*Workspace, error) 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 a workspace by its name.
Delete(ctx context.Context, organization string, workspace string) error 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 from a workspace.
RemoveVCSConnection(ctx context.Context, organization, workspace string) (*Workspace, error) 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 a workspace by its ID.
Lock(ctx context.Context, workspaceID string, options WorkspaceLockOptions) (*Workspace, error) 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"` CanQueueDestroyPlan bool `jsonapi:"attr,can-queue-destroy-plan"`
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
Environment string `jsonapi:"attr,environment"` Environment string `jsonapi:"attr,environment"`
FileTriggersEnabled bool `jsonapi:"attr,file-triggers-enabled"`
Locked bool `jsonapi:"attr,locked"` Locked bool `jsonapi:"attr,locked"`
MigrationEnvironment string `jsonapi:"attr,migration-environment"` MigrationEnvironment string `jsonapi:"attr,migration-environment"`
Name string `jsonapi:"attr,name"` Name string `jsonapi:"attr,name"`
@ -76,6 +89,7 @@ type Workspace struct {
Permissions *WorkspacePermissions `jsonapi:"attr,permissions"` Permissions *WorkspacePermissions `jsonapi:"attr,permissions"`
QueueAllRuns bool `jsonapi:"attr,queue-all-runs"` QueueAllRuns bool `jsonapi:"attr,queue-all-runs"`
TerraformVersion string `jsonapi:"attr,terraform-version"` TerraformVersion string `jsonapi:"attr,terraform-version"`
TriggerPrefixes []string `jsonapi:"attr,trigger-prefixes"`
VCSRepo *VCSRepo `jsonapi:"attr,vcs-repo"` VCSRepo *VCSRepo `jsonapi:"attr,vcs-repo"`
WorkingDirectory string `jsonapi:"attr,working-directory"` WorkingDirectory string `jsonapi:"attr,working-directory"`
@ -149,6 +163,12 @@ type WorkspaceCreateOptions struct {
// Whether to automatically apply changes when a Terraform plan is successful. // Whether to automatically apply changes when a Terraform plan is successful.
AutoApply *bool `jsonapi:"attr,auto-apply,omitempty"` 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 // 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 // form organization/environment. Omit this unless you are migrating a legacy
// environment. // environment.
@ -167,6 +187,10 @@ type WorkspaceCreateOptions struct {
// workspace, the latest version is selected unless otherwise specified. // workspace, the latest version is selected unless otherwise specified.
TerraformVersion *string `jsonapi:"attr,terraform-version,omitempty"` 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 // 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 // created without a VCS repo. If included, you must specify at least the
// oauth-token-id and identifier keys below. // oauth-token-id and identifier keys below.
@ -251,6 +275,27 @@ func (s *workspaces) Read(ctx context.Context, organization, workspace string) (
return w, nil 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. // WorkspaceUpdateOptions represents the options for updating a workspace.
type WorkspaceUpdateOptions struct { type WorkspaceUpdateOptions struct {
// For internal use only! // For internal use only!
@ -265,6 +310,12 @@ type WorkspaceUpdateOptions struct {
// API and UI. // API and UI.
Name *string `jsonapi:"attr,name,omitempty"` 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 // 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. // a webhook will not be queued until at least one run is manually queued.
QueueAllRuns *bool `jsonapi:"attr,queue-all-runs,omitempty"` QueueAllRuns *bool `jsonapi:"attr,queue-all-runs,omitempty"`
@ -272,6 +323,10 @@ type WorkspaceUpdateOptions struct {
// The version of Terraform to use for this workspace. // The version of Terraform to use for this workspace.
TerraformVersion *string `jsonapi:"attr,terraform-version,omitempty"` 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 // 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 // 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 // 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 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. // Delete a workspace by its name.
func (s *workspaces) Delete(ctx context.Context, organization, workspace string) error { func (s *workspaces) Delete(ctx context.Context, organization, workspace string) error {
if !validStringID(&organization) { if !validStringID(&organization) {
@ -339,6 +418,21 @@ func (s *workspaces) Delete(ctx context.Context, organization, workspace string)
return s.client.do(ctx, req, nil) 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 // workspaceRemoveVCSConnectionOptions
type workspaceRemoveVCSConnectionOptions struct { type workspaceRemoveVCSConnectionOptions struct {
ID string `jsonapi:"primary,workspaces"` ID string `jsonapi:"primary,workspaces"`
@ -374,6 +468,28 @@ func (s *workspaces) RemoveVCSConnection(ctx context.Context, organization, work
return w, nil 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. // WorkspaceLockOptions represents the options for locking a workspace.
type WorkspaceLockOptions struct { type WorkspaceLockOptions struct {
// Specifies the reason for locking the workspace. // Specifies the reason for locking the workspace.