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 }