github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/command/add.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "os" 6 "strings" 7 8 "github.com/hashicorp/hcl/v2" 9 "github.com/hashicorp/terraform/internal/addrs" 10 "github.com/hashicorp/terraform/internal/backend" 11 "github.com/hashicorp/terraform/internal/command/arguments" 12 "github.com/hashicorp/terraform/internal/command/views" 13 "github.com/hashicorp/terraform/internal/configs" 14 "github.com/hashicorp/terraform/internal/states" 15 "github.com/hashicorp/terraform/internal/tfdiags" 16 "github.com/zclconf/go-cty/cty" 17 ) 18 19 // AddCommand is a Command implementation that generates resource configuration templates. 20 type AddCommand struct { 21 Meta 22 } 23 24 func (c *AddCommand) Run(rawArgs []string) int { 25 // Parse and apply global view arguments 26 common, rawArgs := arguments.ParseView(rawArgs) 27 c.View.Configure(common) 28 29 args, diags := arguments.ParseAdd(rawArgs) 30 view := views.NewAdd(args.ViewType, c.View, args) 31 if diags.HasErrors() { 32 view.Diagnostics(diags) 33 return 1 34 } 35 36 // Check for user-supplied plugin path 37 var err error 38 if c.pluginPath, err = c.loadPluginPath(); err != nil { 39 diags = diags.Append(tfdiags.Sourceless( 40 tfdiags.Error, 41 "Error loading plugin path", 42 err.Error(), 43 )) 44 view.Diagnostics(diags) 45 return 1 46 } 47 48 // Apply the state arguments to the meta object here because they are later 49 // used when initializing the backend. 50 c.Meta.applyStateArguments(args.State) 51 52 // Load the backend 53 b, backendDiags := c.Backend(nil) 54 diags = diags.Append(backendDiags) 55 if backendDiags.HasErrors() { 56 view.Diagnostics(diags) 57 return 1 58 } 59 60 // We require a local backend 61 local, ok := b.(backend.Local) 62 if !ok { 63 diags = diags.Append(tfdiags.Sourceless( 64 tfdiags.Error, 65 "Unsupported backend", 66 ErrUnsupportedLocalOp, 67 )) 68 view.Diagnostics(diags) 69 return 1 70 } 71 72 // This is a read-only command (until -import is implemented) 73 c.ignoreRemoteBackendVersionConflict(b) 74 75 cwd, err := os.Getwd() 76 if err != nil { 77 diags = diags.Append(tfdiags.Sourceless( 78 tfdiags.Error, 79 "Error determining current working directory", 80 err.Error(), 81 )) 82 view.Diagnostics(diags) 83 return 1 84 } 85 86 // Build the operation 87 opReq := c.Operation(b) 88 opReq.AllowUnsetVariables = true 89 opReq.ConfigDir = cwd 90 opReq.ConfigLoader, err = c.initConfigLoader() 91 if err != nil { 92 diags = diags.Append(tfdiags.Sourceless( 93 tfdiags.Error, 94 "Error initializing config loader", 95 err.Error(), 96 )) 97 view.Diagnostics(diags) 98 return 1 99 } 100 101 // Get the context 102 lr, _, ctxDiags := local.LocalRun(opReq) 103 diags = diags.Append(ctxDiags) 104 if ctxDiags.HasErrors() { 105 view.Diagnostics(diags) 106 return 1 107 } 108 109 // Successfully creating the context can result in a lock, so ensure we release it 110 defer func() { 111 diags := opReq.StateLocker.Unlock() 112 if diags.HasErrors() { 113 c.showDiagnostics(diags) 114 } 115 }() 116 117 // load the configuration to verify that the resource address doesn't 118 // already exist in the config. 119 var module *configs.Module 120 if args.Addr.Module.IsRoot() { 121 module = lr.Config.Module 122 } else { 123 // This is weird, but users can potentially specify non-existant module names 124 cfg := lr.Config.Root.Descendent(args.Addr.Module.Module()) 125 if cfg != nil { 126 module = cfg.Module 127 } 128 } 129 130 if module == nil { 131 // It's fine if the module doesn't actually exist; we don't need to check if the resource exists. 132 } else { 133 if rs, ok := module.ManagedResources[args.Addr.ContainingResource().Config().String()]; ok { 134 diags = diags.Append(&hcl.Diagnostic{ 135 Severity: hcl.DiagError, 136 Summary: "Resource already in configuration", 137 Detail: fmt.Sprintf("The resource %s is already in this configuration at %s. Resource names must be unique per type in each module.", args.Addr, rs.DeclRange), 138 Subject: &rs.DeclRange, 139 }) 140 c.View.Diagnostics(diags) 141 return 1 142 } 143 } 144 145 // Get the schemas from the context 146 schemas, moreDiags := lr.Core.Schemas(lr.Config, lr.InputState) 147 diags = diags.Append(moreDiags) 148 if moreDiags.HasErrors() { 149 view.Diagnostics(diags) 150 return 1 151 } 152 153 // Determine the correct provider config address. The provider-related 154 // variables may get updated below 155 absProviderConfig := args.Provider 156 var providerLocalName string 157 rs := args.Addr.Resource.Resource 158 159 // If we are getting the values from state, get the AbsProviderConfig 160 // directly from state as well. 161 var resource *states.Resource 162 if args.FromState { 163 resource, moreDiags = c.getResource(b, args.Addr.ContainingResource()) 164 if moreDiags.HasErrors() { 165 diags = diags.Append(moreDiags) 166 c.View.Diagnostics(diags) 167 return 1 168 } 169 absProviderConfig = &resource.ProviderConfig 170 } 171 172 if absProviderConfig == nil { 173 ip := rs.ImpliedProvider() 174 if module != nil { 175 provider := module.ImpliedProviderForUnqualifiedType(ip) 176 providerLocalName = module.LocalNameForProvider(provider) 177 absProviderConfig = &addrs.AbsProviderConfig{ 178 Provider: provider, 179 Module: args.Addr.Module.Module(), 180 } 181 } else { 182 // lacking any configuration to query, we'll go with a default provider. 183 absProviderConfig = &addrs.AbsProviderConfig{ 184 Provider: addrs.NewDefaultProvider(ip), 185 } 186 providerLocalName = ip 187 } 188 } else { 189 if module != nil { 190 providerLocalName = module.LocalNameForProvider(absProviderConfig.Provider) 191 } else { 192 providerLocalName = absProviderConfig.Provider.Type 193 } 194 } 195 196 localProviderConfig := addrs.LocalProviderConfig{ 197 LocalName: providerLocalName, 198 Alias: absProviderConfig.Alias, 199 } 200 201 // Get the schemas from the context 202 if _, exists := schemas.Providers[absProviderConfig.Provider]; !exists { 203 diags = diags.Append(tfdiags.Sourceless( 204 tfdiags.Error, 205 "Missing schema for provider", 206 fmt.Sprintf("No schema found for provider %s. Please verify that this provider exists in the configuration.", absProviderConfig.Provider.String()), 207 )) 208 c.View.Diagnostics(diags) 209 return 1 210 } 211 212 // Get the schema for the resource 213 schema, schemaVersion := schemas.ResourceTypeConfig(absProviderConfig.Provider, rs.Mode, rs.Type) 214 if schema == nil { 215 diags = diags.Append(tfdiags.Sourceless( 216 tfdiags.Error, 217 "Missing resource schema from provider", 218 fmt.Sprintf("No resource schema found for %s.", rs.Type), 219 )) 220 c.View.Diagnostics(diags) 221 return 1 222 } 223 224 stateVal := cty.NilVal 225 // Now that we have the schema, we can decode the previously-acquired resource state 226 if args.FromState { 227 ri := resource.Instance(args.Addr.Resource.Key) 228 if ri.Current == nil { 229 diags = diags.Append(tfdiags.Sourceless( 230 tfdiags.Error, 231 "No state for resource", 232 fmt.Sprintf("There is no state found for the resource %s, so add cannot populate values.", rs.String()), 233 )) 234 c.View.Diagnostics(diags) 235 return 1 236 } 237 238 rio, err := ri.Current.Decode(schema.ImpliedType()) 239 if err != nil { 240 diags = diags.Append(tfdiags.Sourceless( 241 tfdiags.Error, 242 "Error decoding state", 243 fmt.Sprintf("Error decoding state for resource %s: %s", rs.String(), err.Error()), 244 )) 245 c.View.Diagnostics(diags) 246 return 1 247 } 248 249 if ri.Current.SchemaVersion != schemaVersion { 250 diags = diags.Append(tfdiags.Sourceless( 251 tfdiags.Error, 252 "Schema version mismatch", 253 fmt.Sprintf("schema version %d for %s in state does not match version %d from the provider", ri.Current.SchemaVersion, rs.String(), schemaVersion), 254 )) 255 c.View.Diagnostics(diags) 256 return 1 257 } 258 259 stateVal = rio.Value 260 } 261 262 diags = diags.Append(view.Resource(args.Addr, schema, localProviderConfig, stateVal)) 263 c.View.Diagnostics(diags) 264 if diags.HasErrors() { 265 return 1 266 } 267 return 0 268 } 269 270 func (c *AddCommand) Help() string { 271 helpText := ` 272 Usage: terraform [global options] add [options] ADDRESS 273 274 Generates a blank resource template. With no additional options, Terraform 275 will write the result to standard output. 276 277 Options: 278 279 -from-state Fill the template with values from an existing resource 280 instance tracked in the state. By default, Terraform will 281 emit only placeholder values based on the resource type. 282 283 -out=string Write the template to a file, instead of to standard 284 output. 285 286 -optional Include optional arguments. By default, the result will 287 include only required arguments. 288 289 -provider=provider Override the provider configuration for the resource, 290 using the absolute provider configuration address syntax. 291 292 This is incompatible with -from-state, because in that 293 case Terraform will use the provider configuration already 294 selected in the state. 295 ` 296 return strings.TrimSpace(helpText) 297 } 298 299 func (c *AddCommand) Synopsis() string { 300 return "Generate a resource configuration template" 301 } 302 303 func (c *AddCommand) getResource(b backend.Enhanced, addr addrs.AbsResource) (*states.Resource, tfdiags.Diagnostics) { 304 var diags tfdiags.Diagnostics 305 // Get the state 306 env, err := c.Workspace() 307 if err != nil { 308 diags = diags.Append(tfdiags.Sourceless( 309 tfdiags.Error, 310 "Error selecting workspace", 311 err.Error(), 312 )) 313 return nil, diags 314 } 315 316 stateMgr, err := b.StateMgr(env) 317 if err != nil { 318 diags = diags.Append(tfdiags.Sourceless( 319 tfdiags.Error, 320 "Error loading state", 321 fmt.Sprintf(errStateLoadingState, err), 322 )) 323 return nil, diags 324 } 325 326 if err := stateMgr.RefreshState(); err != nil { 327 diags = diags.Append(tfdiags.Sourceless( 328 tfdiags.Error, 329 "Error refreshing state", 330 err.Error(), 331 )) 332 return nil, diags 333 } 334 335 state := stateMgr.State() 336 if state == nil { 337 diags = diags.Append(tfdiags.Sourceless( 338 tfdiags.Error, 339 "No state", 340 "There is no state found for the current workspace, so add cannot populate values.", 341 )) 342 return nil, diags 343 } 344 345 return state.Resource(addr), nil 346 }