terraform/vendor/github.com/hashicorp/go-tfe/workspace.go

645 lines
20 KiB
Go

package tfe
import (
"context"
"errors"
"fmt"
"net/url"
"time"
)
// Compile-time proof of interface implementation.
var _ Workspaces = (*workspaces)(nil)
// Workspaces describes all the workspace related methods that the Terraform
// Enterprise API supports.
//
// TFE API docs: https://www.terraform.io/docs/enterprise/api/workspaces.html
type Workspaces interface {
// List all the workspaces within an organization.
List(ctx context.Context, organization string, options WorkspaceListOptions) (*WorkspaceList, error)
// Create is used to create a new workspace.
Create(ctx context.Context, organization string, options WorkspaceCreateOptions) (*Workspace, error)
// 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)
// Unlock a workspace by its ID.
Unlock(ctx context.Context, workspaceID string) (*Workspace, error)
// ForceUnlock a workspace by its ID.
ForceUnlock(ctx context.Context, workspaceID string) (*Workspace, error)
// AssignSSHKey to a workspace.
AssignSSHKey(ctx context.Context, workspaceID string, options WorkspaceAssignSSHKeyOptions) (*Workspace, error)
// UnassignSSHKey from a workspace.
UnassignSSHKey(ctx context.Context, workspaceID string) (*Workspace, error)
}
// workspaces implements Workspaces.
type workspaces struct {
client *Client
}
// WorkspaceList represents a list of workspaces.
type WorkspaceList struct {
*Pagination
Items []*Workspace
}
// Workspace represents a Terraform Enterprise workspace.
type Workspace struct {
ID string `jsonapi:"primary,workspaces"`
Actions *WorkspaceActions `jsonapi:"attr,actions"`
AutoApply bool `jsonapi:"attr,auto-apply"`
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"`
Operations bool `jsonapi:"attr,operations"`
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"`
// Relations
CurrentRun *Run `jsonapi:"relation,current-run"`
Organization *Organization `jsonapi:"relation,organization"`
SSHKey *SSHKey `jsonapi:"relation,ssh-key"`
}
// VCSRepo contains the configuration of a VCS integration.
type VCSRepo struct {
Branch string `json:"branch"`
Identifier string `json:"identifier"`
IngressSubmodules bool `json:"ingress-submodules"`
OAuthTokenID string `json:"oauth-token-id"`
}
// WorkspaceActions represents the workspace actions.
type WorkspaceActions struct {
IsDestroyable bool `json:"is-destroyable"`
}
// WorkspacePermissions represents the workspace permissions.
type WorkspacePermissions struct {
CanDestroy bool `json:"can-destroy"`
CanForceUnlock bool `json:"can-force-unlock"`
CanLock bool `json:"can-lock"`
CanQueueApply bool `json:"can-queue-apply"`
CanQueueDestroy bool `json:"can-queue-destroy"`
CanQueueRun bool `json:"can-queue-run"`
CanReadSettings bool `json:"can-read-settings"`
CanUnlock bool `json:"can-unlock"`
CanUpdate bool `json:"can-update"`
CanUpdateVariable bool `json:"can-update-variable"`
}
// WorkspaceListOptions represents the options for listing workspaces.
type WorkspaceListOptions struct {
ListOptions
// A search string (partial workspace name) used to filter the results.
Search *string `url:"search[name],omitempty"`
}
// List all the workspaces within an organization.
func (s *workspaces) List(ctx context.Context, organization string, options WorkspaceListOptions) (*WorkspaceList, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
u := fmt.Sprintf("organizations/%s/workspaces", url.QueryEscape(organization))
req, err := s.client.newRequest("GET", u, &options)
if err != nil {
return nil, err
}
wl := &WorkspaceList{}
err = s.client.do(ctx, req, wl)
if err != nil {
return nil, err
}
return wl, nil
}
// WorkspaceCreateOptions represents the options for creating a new workspace.
type WorkspaceCreateOptions struct {
// For internal use only!
ID string `jsonapi:"primary,workspaces"`
// 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.
MigrationEnvironment *string `jsonapi:"attr,migration-environment,omitempty"`
// The name of the workspace, which can only include letters, numbers, -,
// and _. This will be used as an identifier and must be unique in the
// organization.
Name *string `jsonapi:"attr,name"`
// Whether the workspace will use remote or local execution mode.
Operations *bool `jsonapi:"attr,operations,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"`
// The version of Terraform to use for this workspace. Upon creating a
// 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.
VCSRepo *VCSRepoOptions `jsonapi:"attr,vcs-repo,omitempty"`
// A relative path that Terraform will execute within. This defaults to the
// root of your repository and is typically set to a subdirectory matching the
// environment when multiple environments exist within the same repository.
WorkingDirectory *string `jsonapi:"attr,working-directory,omitempty"`
}
// VCSRepoOptions represents the configuration options of a VCS integration.
type VCSRepoOptions struct {
Branch *string `json:"branch,omitempty"`
Identifier *string `json:"identifier,omitempty"`
IngressSubmodules *bool `json:"ingress-submodules,omitempty"`
OAuthTokenID *string `json:"oauth-token-id,omitempty"`
}
func (o WorkspaceCreateOptions) valid() error {
if !validString(o.Name) {
return errors.New("name is required")
}
if !validStringID(o.Name) {
return errors.New("invalid value for name")
}
return nil
}
// Create is used to create a new workspace.
func (s *workspaces) Create(ctx context.Context, organization string, options WorkspaceCreateOptions) (*Workspace, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("organizations/%s/workspaces", url.QueryEscape(organization))
req, err := s.client.newRequest("POST", 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
}
// Read a workspace by its name.
func (s *workspaces) Read(ctx context.Context, organization, workspace string) (*Workspace, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
if !validStringID(&workspace) {
return nil, errors.New("invalid value for workspace")
}
u := fmt.Sprintf(
"organizations/%s/workspaces/%s",
url.QueryEscape(organization),
url.QueryEscape(workspace),
)
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
}
// 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!
ID string `jsonapi:"primary,workspaces"`
// Whether to automatically apply changes when a Terraform plan is successful.
AutoApply *bool `jsonapi:"attr,auto-apply,omitempty"`
// A new name for the workspace, which can only include letters, numbers, -,
// and _. This will be used as an identifier and must be unique in the
// organization. Warning: Changing a workspace's name changes its URL in the
// 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 the workspace will use remote or local execution mode.
Operations *bool `jsonapi:"attr,operations,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"`
// 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
// that didn't previously have one, include at least the oauth-token-id and
// identifier keys.
VCSRepo *VCSRepoOptions `jsonapi:"attr,vcs-repo,omitempty"`
// A relative path that Terraform will execute within. This defaults to the
// root of your repository and is typically set to a subdirectory matching
// the environment when multiple environments exist within the same
// repository.
WorkingDirectory *string `jsonapi:"attr,working-directory,omitempty"`
}
// Update settings of an existing workspace.
func (s *workspaces) Update(ctx context.Context, organization, workspace string, options WorkspaceUpdateOptions) (*Workspace, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
if !validStringID(&workspace) {
return nil, errors.New("invalid value for workspace")
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf(
"organizations/%s/workspaces/%s",
url.QueryEscape(organization),
url.QueryEscape(workspace),
)
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
}
// 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) {
return errors.New("invalid value for organization")
}
if !validStringID(&workspace) {
return errors.New("invalid value for workspace")
}
u := fmt.Sprintf(
"organizations/%s/workspaces/%s",
url.QueryEscape(organization),
url.QueryEscape(workspace),
)
req, err := s.client.newRequest("DELETE", u, nil)
if err != nil {
return err
}
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"`
VCSRepo *VCSRepoOptions `jsonapi:"attr,vcs-repo"`
}
// RemoveVCSConnection from a workspace.
func (s *workspaces) RemoveVCSConnection(ctx context.Context, organization, workspace string) (*Workspace, error) {
if !validStringID(&organization) {
return nil, errors.New("invalid value for organization")
}
if !validStringID(&workspace) {
return nil, errors.New("invalid value for workspace")
}
u := fmt.Sprintf(
"organizations/%s/workspaces/%s",
url.QueryEscape(organization),
url.QueryEscape(workspace),
)
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
}
// 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.
Reason *string `json:"reason,omitempty"`
}
// Lock a workspace by its ID.
func (s *workspaces) Lock(ctx context.Context, workspaceID string, options WorkspaceLockOptions) (*Workspace, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
u := fmt.Sprintf("workspaces/%s/actions/lock", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("POST", 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
}
// Unlock a workspace by its ID.
func (s *workspaces) Unlock(ctx context.Context, workspaceID string) (*Workspace, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
u := fmt.Sprintf("workspaces/%s/actions/unlock", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("POST", 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
}
// ForceUnlock a workspace by its ID.
func (s *workspaces) ForceUnlock(ctx context.Context, workspaceID string) (*Workspace, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
u := fmt.Sprintf("workspaces/%s/actions/force-unlock", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("POST", 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
}
// WorkspaceAssignSSHKeyOptions represents the options to assign an SSH key to
// a workspace.
type WorkspaceAssignSSHKeyOptions struct {
// For internal use only!
ID string `jsonapi:"primary,workspaces"`
// The SSH key ID to assign.
SSHKeyID *string `jsonapi:"attr,id"`
}
func (o WorkspaceAssignSSHKeyOptions) valid() error {
if !validString(o.SSHKeyID) {
return errors.New("SSH key ID is required")
}
if !validStringID(o.SSHKeyID) {
return errors.New("invalid value for SSH key ID")
}
return nil
}
// AssignSSHKey to a workspace.
func (s *workspaces) AssignSSHKey(ctx context.Context, workspaceID string, options WorkspaceAssignSSHKeyOptions) (*Workspace, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
if err := options.valid(); err != nil {
return nil, err
}
// Make sure we don't send a user provided ID.
options.ID = ""
u := fmt.Sprintf("workspaces/%s/relationships/ssh-key", 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
}
// workspaceUnassignSSHKeyOptions represents the options to unassign an SSH key
// to a workspace.
type workspaceUnassignSSHKeyOptions struct {
// For internal use only!
ID string `jsonapi:"primary,workspaces"`
// Must be nil to unset the currently assigned SSH key.
SSHKeyID *string `jsonapi:"attr,id"`
}
// UnassignSSHKey from a workspace.
func (s *workspaces) UnassignSSHKey(ctx context.Context, workspaceID string) (*Workspace, error) {
if !validStringID(&workspaceID) {
return nil, errors.New("invalid value for workspace ID")
}
u := fmt.Sprintf("workspaces/%s/relationships/ssh-key", url.QueryEscape(workspaceID))
req, err := s.client.newRequest("PATCH", u, &workspaceUnassignSSHKeyOptions{})
if err != nil {
return nil, err
}
w := &Workspace{}
err = s.client.do(ctx, req, w)
if err != nil {
return nil, err
}
return w, nil
}