github.com/leg100/ots@v0.0.7-0.20210919080622-034055ced4bd/workspace.go (about)

     1  package ots
     2  
     3  import (
     4  	"errors"
     5  	"time"
     6  
     7  	tfe "github.com/leg100/go-tfe"
     8  	"gorm.io/gorm"
     9  )
    10  
    11  const (
    12  	DefaultAllowDestroyPlan    = true
    13  	DefaultFileTriggersEnabled = true
    14  	DefaultTerraformVersion    = "0.15.4"
    15  	DefaultExecutionMode       = "remote"
    16  )
    17  
    18  var (
    19  	ErrWorkspaceAlreadyLocked    = errors.New("workspace already locked")
    20  	ErrWorkspaceAlreadyUnlocked  = errors.New("workspace already unlocked")
    21  	ErrInvalidWorkspaceSpecifier = errors.New("invalid workspace specifier options")
    22  )
    23  
    24  // Workspace represents a Terraform Enterprise workspace.
    25  type Workspace struct {
    26  	ID string
    27  
    28  	gorm.Model
    29  
    30  	AllowDestroyPlan           bool
    31  	AutoApply                  bool
    32  	CanQueueDestroyPlan        bool
    33  	Description                string
    34  	Environment                string
    35  	ExecutionMode              string
    36  	FileTriggersEnabled        bool
    37  	GlobalRemoteState          bool
    38  	Locked                     bool
    39  	MigrationEnvironment       string
    40  	Name                       string
    41  	Permissions                *tfe.WorkspacePermissions
    42  	QueueAllRuns               bool
    43  	SpeculativeEnabled         bool
    44  	SourceName                 string
    45  	SourceURL                  string
    46  	StructuredRunOutputEnabled bool
    47  	TerraformVersion           string
    48  	VCSRepo                    *tfe.VCSRepo
    49  	WorkingDirectory           string
    50  	ResourceCount              int
    51  	ApplyDurationAverage       time.Duration
    52  	PlanDurationAverage        time.Duration
    53  	PolicyCheckFailures        int
    54  	RunFailures                int
    55  	RunsCount                  int
    56  
    57  	TriggerPrefixes []string
    58  
    59  	// Relations AgentPool  *tfe.AgentPool CurrentRun *Run
    60  
    61  	// Workspace belongs to an organization
    62  	Organization *Organization
    63  
    64  	//SSHKey *tfe.SSHKey
    65  }
    66  
    67  // WorkspaceList represents a list of Workspaces.
    68  type WorkspaceList struct {
    69  	*tfe.Pagination
    70  	Items []*Workspace
    71  }
    72  
    73  type WorkspaceService interface {
    74  	Create(org string, opts *tfe.WorkspaceCreateOptions) (*Workspace, error)
    75  	Get(spec WorkspaceSpecifier) (*Workspace, error)
    76  	List(opts WorkspaceListOptions) (*WorkspaceList, error)
    77  	Update(spec WorkspaceSpecifier, opts *tfe.WorkspaceUpdateOptions) (*Workspace, error)
    78  	Lock(id string, opts tfe.WorkspaceLockOptions) (*Workspace, error)
    79  	Unlock(id string) (*Workspace, error)
    80  	Delete(spec WorkspaceSpecifier) error
    81  }
    82  
    83  type WorkspaceStore interface {
    84  	Create(ws *Workspace) (*Workspace, error)
    85  	Get(spec WorkspaceSpecifier) (*Workspace, error)
    86  	List(opts WorkspaceListOptions) (*WorkspaceList, error)
    87  	Update(spec WorkspaceSpecifier, fn func(*Workspace) error) (*Workspace, error)
    88  	Delete(spec WorkspaceSpecifier) error
    89  }
    90  
    91  // WorkspaceSpecifier is used for identifying an individual workspace. Either ID
    92  // *or* both Name and OrganizationName must be specfiied.
    93  type WorkspaceSpecifier struct {
    94  	// Specify workspace using its ID
    95  	ID *string
    96  
    97  	// Specify workspace using its name and organization
    98  	Name             *string
    99  	OrganizationName *string
   100  }
   101  
   102  // WorkspaceListOptions are options for paginating and filtering a list of
   103  // Workspaces
   104  type WorkspaceListOptions struct {
   105  	// Pagination
   106  	tfe.ListOptions
   107  
   108  	// Optionally filter workspaces with name matching prefix
   109  	Prefix *string `schema:"search[name],omitempty"`
   110  
   111  	// OrganizationName filters workspaces by organization name
   112  	OrganizationName *string
   113  
   114  	// A list of relations to include. See available resources https://www.terraform.io/docs/cloud/api/workspaces.html#available-related-resources
   115  	Include *string `schema:"include"`
   116  }
   117  
   118  func NewWorkspace(opts *tfe.WorkspaceCreateOptions, org *Organization) *Workspace {
   119  	ws := Workspace{
   120  		ID:                  GenerateID("ws"),
   121  		Name:                *opts.Name,
   122  		AllowDestroyPlan:    DefaultAllowDestroyPlan,
   123  		ExecutionMode:       "local", // Default until remote ops is officially supported
   124  		FileTriggersEnabled: DefaultFileTriggersEnabled,
   125  		GlobalRemoteState:   true, // Only global remote state is supported
   126  		TerraformVersion:    DefaultTerraformVersion,
   127  		Permissions: &tfe.WorkspacePermissions{
   128  			CanDestroy:        true,
   129  			CanForceUnlock:    true,
   130  			CanLock:           true,
   131  			CanUnlock:         true,
   132  			CanQueueApply:     true,
   133  			CanQueueDestroy:   true,
   134  			CanQueueRun:       true,
   135  			CanReadSettings:   true,
   136  			CanUpdate:         true,
   137  			CanUpdateVariable: true,
   138  		},
   139  		SpeculativeEnabled: true,
   140  		Organization:       org,
   141  	}
   142  
   143  	// TODO: ExecutionMode and Operations are mututally exclusive options, this
   144  	// should be enforced.
   145  	if opts.ExecutionMode != nil {
   146  		ws.ExecutionMode = *opts.ExecutionMode
   147  	}
   148  	// Operations is deprecated in favour of ExecutionMode.
   149  	if opts.Operations != nil {
   150  		if *opts.Operations {
   151  			ws.ExecutionMode = "remote"
   152  		} else {
   153  			ws.ExecutionMode = "local"
   154  		}
   155  	}
   156  
   157  	if opts.AllowDestroyPlan != nil {
   158  		ws.AllowDestroyPlan = *opts.AllowDestroyPlan
   159  	}
   160  	if opts.AutoApply != nil {
   161  		ws.AutoApply = *opts.AutoApply
   162  	}
   163  	if opts.Description != nil {
   164  		ws.Description = *opts.Description
   165  	}
   166  	if opts.FileTriggersEnabled != nil {
   167  		ws.FileTriggersEnabled = *opts.FileTriggersEnabled
   168  	}
   169  	if opts.QueueAllRuns != nil {
   170  		ws.QueueAllRuns = *opts.QueueAllRuns
   171  	}
   172  	if opts.SourceName != nil {
   173  		ws.SourceName = *opts.SourceName
   174  	}
   175  	if opts.SourceURL != nil {
   176  		ws.SourceURL = *opts.SourceURL
   177  	}
   178  	if opts.SpeculativeEnabled != nil {
   179  		ws.SpeculativeEnabled = *opts.SpeculativeEnabled
   180  	}
   181  	if opts.StructuredRunOutputEnabled != nil {
   182  		ws.StructuredRunOutputEnabled = *opts.StructuredRunOutputEnabled
   183  	}
   184  	if opts.TerraformVersion != nil {
   185  		ws.TerraformVersion = *opts.TerraformVersion
   186  	}
   187  	if opts.TriggerPrefixes != nil {
   188  		ws.TriggerPrefixes = opts.TriggerPrefixes
   189  	}
   190  	if opts.WorkingDirectory != nil {
   191  		ws.WorkingDirectory = *opts.WorkingDirectory
   192  	}
   193  
   194  	return &ws
   195  }
   196  
   197  func UpdateWorkspace(ws *Workspace, opts *tfe.WorkspaceUpdateOptions) (*Workspace, error) {
   198  	if opts.Name != nil {
   199  		ws.Name = *opts.Name
   200  	}
   201  
   202  	if opts.AllowDestroyPlan != nil {
   203  		ws.AllowDestroyPlan = *opts.AllowDestroyPlan
   204  	}
   205  	if opts.AutoApply != nil {
   206  		ws.AutoApply = *opts.AutoApply
   207  	}
   208  	if opts.Description != nil {
   209  		ws.Description = *opts.Description
   210  	}
   211  	if opts.ExecutionMode != nil {
   212  		ws.ExecutionMode = *opts.ExecutionMode
   213  	}
   214  	if opts.FileTriggersEnabled != nil {
   215  		ws.FileTriggersEnabled = *opts.FileTriggersEnabled
   216  	}
   217  	if opts.QueueAllRuns != nil {
   218  		ws.QueueAllRuns = *opts.QueueAllRuns
   219  	}
   220  	if opts.SpeculativeEnabled != nil {
   221  		ws.SpeculativeEnabled = *opts.SpeculativeEnabled
   222  	}
   223  	if opts.StructuredRunOutputEnabled != nil {
   224  		ws.StructuredRunOutputEnabled = *opts.StructuredRunOutputEnabled
   225  	}
   226  	if opts.TerraformVersion != nil {
   227  		ws.TerraformVersion = *opts.TerraformVersion
   228  	}
   229  	if opts.TriggerPrefixes != nil {
   230  		ws.TriggerPrefixes = opts.TriggerPrefixes
   231  	}
   232  	if opts.WorkingDirectory != nil {
   233  		ws.WorkingDirectory = *opts.WorkingDirectory
   234  	}
   235  
   236  	return ws, nil
   237  }
   238  
   239  func (ws *Workspace) Actions() *tfe.WorkspaceActions {
   240  	return &tfe.WorkspaceActions{
   241  		IsDestroyable: false,
   242  	}
   243  }
   244  
   245  func (ws *Workspace) ToggleLock(lock bool) error {
   246  	if lock && ws.Locked {
   247  		return ErrWorkspaceAlreadyLocked
   248  	}
   249  	if !lock && !ws.Locked {
   250  		return ErrWorkspaceAlreadyUnlocked
   251  	}
   252  
   253  	ws.Locked = lock
   254  
   255  	return nil
   256  }