github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/command/workspace_new.go (about)

     1  package command
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/hashicorp/terraform/command/clistate"
    11  	"github.com/hashicorp/terraform/states/statefile"
    12  	"github.com/hashicorp/terraform/tfdiags"
    13  	"github.com/mitchellh/cli"
    14  	"github.com/posener/complete"
    15  )
    16  
    17  type WorkspaceNewCommand struct {
    18  	Meta
    19  	LegacyName bool
    20  }
    21  
    22  func (c *WorkspaceNewCommand) Run(args []string) int {
    23  	args, err := c.Meta.process(args, true)
    24  	if err != nil {
    25  		return 1
    26  	}
    27  
    28  	envCommandShowWarning(c.Ui, c.LegacyName)
    29  
    30  	var stateLock bool
    31  	var stateLockTimeout time.Duration
    32  	var statePath string
    33  	cmdFlags := c.Meta.defaultFlagSet("workspace new")
    34  	cmdFlags.BoolVar(&stateLock, "lock", true, "lock state")
    35  	cmdFlags.DurationVar(&stateLockTimeout, "lock-timeout", 0, "lock timeout")
    36  	cmdFlags.StringVar(&statePath, "state", "", "terraform state file")
    37  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    38  	if err := cmdFlags.Parse(args); err != nil {
    39  		c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
    40  		return 1
    41  	}
    42  
    43  	args = cmdFlags.Args()
    44  	if len(args) == 0 {
    45  		c.Ui.Error("Expected a single argument: NAME.\n")
    46  		return cli.RunResultHelp
    47  	}
    48  
    49  	workspace := args[0]
    50  
    51  	if !validWorkspaceName(workspace) {
    52  		c.Ui.Error(fmt.Sprintf(envInvalidName, workspace))
    53  		return 1
    54  	}
    55  
    56  	// You can't ask to create a workspace when you're overriding the
    57  	// workspace name to be something different.
    58  	if current, isOverridden := c.WorkspaceOverridden(); current != workspace && isOverridden {
    59  		c.Ui.Error(envIsOverriddenNewError)
    60  		return 1
    61  	}
    62  
    63  	configPath, err := ModulePath(args[1:])
    64  	if err != nil {
    65  		c.Ui.Error(err.Error())
    66  		return 1
    67  	}
    68  
    69  	var diags tfdiags.Diagnostics
    70  
    71  	backendConfig, backendDiags := c.loadBackendConfig(configPath)
    72  	diags = diags.Append(backendDiags)
    73  	if diags.HasErrors() {
    74  		c.showDiagnostics(diags)
    75  		return 1
    76  	}
    77  
    78  	// Load the backend
    79  	b, backendDiags := c.Backend(&BackendOpts{
    80  		Config: backendConfig,
    81  	})
    82  	diags = diags.Append(backendDiags)
    83  	if backendDiags.HasErrors() {
    84  		c.showDiagnostics(diags)
    85  		return 1
    86  	}
    87  
    88  	workspaces, err := b.Workspaces()
    89  	if err != nil {
    90  		c.Ui.Error(fmt.Sprintf("Failed to get configured named states: %s", err))
    91  		return 1
    92  	}
    93  	for _, ws := range workspaces {
    94  		if workspace == ws {
    95  			c.Ui.Error(fmt.Sprintf(envExists, workspace))
    96  			return 1
    97  		}
    98  	}
    99  
   100  	_, err = b.StateMgr(workspace)
   101  	if err != nil {
   102  		c.Ui.Error(err.Error())
   103  		return 1
   104  	}
   105  
   106  	// now set the current workspace locally
   107  	if err := c.SetWorkspace(workspace); err != nil {
   108  		c.Ui.Error(fmt.Sprintf("Error selecting new workspace: %s", err))
   109  		return 1
   110  	}
   111  
   112  	c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
   113  		strings.TrimSpace(envCreated), workspace)))
   114  
   115  	if statePath == "" {
   116  		// if we're not loading a state, then we're done
   117  		return 0
   118  	}
   119  
   120  	// load the new Backend state
   121  	stateMgr, err := b.StateMgr(workspace)
   122  	if err != nil {
   123  		c.Ui.Error(err.Error())
   124  		return 1
   125  	}
   126  
   127  	if stateLock {
   128  		stateLocker := clistate.NewLocker(context.Background(), stateLockTimeout, c.Ui, c.Colorize())
   129  		if err := stateLocker.Lock(stateMgr, "workspace_new"); err != nil {
   130  			c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
   131  			return 1
   132  		}
   133  		defer stateLocker.Unlock(nil)
   134  	}
   135  
   136  	// read the existing state file
   137  	f, err := os.Open(statePath)
   138  	if err != nil {
   139  		c.Ui.Error(err.Error())
   140  		return 1
   141  	}
   142  
   143  	stateFile, err := statefile.Read(f)
   144  	if err != nil {
   145  		c.Ui.Error(err.Error())
   146  		return 1
   147  	}
   148  	if err := stateFile.CheckTerraformVersion(); err != nil {
   149  		c.Ui.Error(err.Error())
   150  		return 1
   151  	}
   152  
   153  	// save the existing state in the new Backend.
   154  	err = stateMgr.WriteState(stateFile.State)
   155  	if err != nil {
   156  		c.Ui.Error(err.Error())
   157  		return 1
   158  	}
   159  	err = stateMgr.PersistState()
   160  	if err != nil {
   161  		c.Ui.Error(err.Error())
   162  		return 1
   163  	}
   164  
   165  	return 0
   166  }
   167  
   168  func (c *WorkspaceNewCommand) AutocompleteArgs() complete.Predictor {
   169  	return completePredictSequence{
   170  		complete.PredictNothing, // the "new" subcommand itself (already matched)
   171  		complete.PredictAnything,
   172  		complete.PredictDirs(""),
   173  	}
   174  }
   175  
   176  func (c *WorkspaceNewCommand) AutocompleteFlags() complete.Flags {
   177  	return complete.Flags{
   178  		"-state": complete.PredictFiles("*.tfstate"),
   179  	}
   180  }
   181  
   182  func (c *WorkspaceNewCommand) Help() string {
   183  	helpText := `
   184  Usage: terraform workspace new [OPTIONS] NAME [DIR]
   185  
   186    Create a new Terraform workspace.
   187  
   188  
   189  Options:
   190  
   191      -lock=true          Lock the state file when locking is supported.
   192  
   193      -lock-timeout=0s    Duration to retry a state lock.
   194  
   195      -state=path    Copy an existing state file into the new workspace.
   196  
   197  `
   198  	return strings.TrimSpace(helpText)
   199  }
   200  
   201  func (c *WorkspaceNewCommand) Synopsis() string {
   202  	return "Create a new workspace"
   203  }