Add cloud {} configuration block for Terraform Cloud
This is a replacement declaration for using Terraform Cloud as a remote backend, leaving the literal backend as an implementation detail and not a user-level concept.
This commit is contained in:
parent
a5f063625a
commit
a4c24e3147
|
@ -72,7 +72,7 @@ func (c *ConsoleCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a read-only command
|
// This is a read-only command
|
||||||
c.ignoreRemoteBackendVersionConflict(b)
|
c.ignoreRemoteVersionConflict(b)
|
||||||
|
|
||||||
// Build the operation
|
// Build the operation
|
||||||
opReq := c.Operation(b)
|
opReq := c.Operation(b)
|
||||||
|
|
|
@ -88,7 +88,7 @@ func (c *GraphCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a read-only command
|
// This is a read-only command
|
||||||
c.ignoreRemoteBackendVersionConflict(b)
|
c.ignoreRemoteVersionConflict(b)
|
||||||
|
|
||||||
// Build the operation
|
// Build the operation
|
||||||
opReq := c.Operation(b)
|
opReq := c.Operation(b)
|
||||||
|
|
|
@ -204,7 +204,7 @@ func (c *ImportCommand) Run(args []string) int {
|
||||||
opReq.View = views.NewOperation(arguments.ViewHuman, c.RunningInAutomation, c.View)
|
opReq.View = views.NewOperation(arguments.ViewHuman, c.RunningInAutomation, c.View)
|
||||||
|
|
||||||
// Check remote Terraform version is compatible
|
// Check remote Terraform version is compatible
|
||||||
remoteVersionDiags := c.remoteBackendVersionCheck(b, opReq.Workspace)
|
remoteVersionDiags := c.remoteVersionCheck(b, opReq.Workspace)
|
||||||
diags = diags.Append(remoteVersionDiags)
|
diags = diags.Append(remoteVersionDiags)
|
||||||
c.showDiagnostics(diags)
|
c.showDiagnostics(diags)
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
|
|
|
@ -209,8 +209,20 @@ func (c *InitCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
var back backend.Backend
|
var back backend.Backend
|
||||||
if flagBackend {
|
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case config.Module.CloudConfig != nil:
|
||||||
|
be, backendOutput, backendDiags := c.initCloud(config.Module)
|
||||||
|
diags = diags.Append(backendDiags)
|
||||||
|
if backendDiags.HasErrors() {
|
||||||
|
c.showDiagnostics(diags)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if backendOutput {
|
||||||
|
header = true
|
||||||
|
}
|
||||||
|
back = be
|
||||||
|
case flagBackend:
|
||||||
be, backendOutput, backendDiags := c.initBackend(config.Module, flagConfigExtra)
|
be, backendOutput, backendDiags := c.initBackend(config.Module, flagConfigExtra)
|
||||||
diags = diags.Append(backendDiags)
|
diags = diags.Append(backendDiags)
|
||||||
if backendDiags.HasErrors() {
|
if backendDiags.HasErrors() {
|
||||||
|
@ -221,7 +233,7 @@ func (c *InitCommand) Run(args []string) int {
|
||||||
header = true
|
header = true
|
||||||
}
|
}
|
||||||
back = be
|
back = be
|
||||||
} else {
|
default:
|
||||||
// load the previously-stored backend config
|
// load the previously-stored backend config
|
||||||
be, backendDiags := c.Meta.backendFromState()
|
be, backendDiags := c.Meta.backendFromState()
|
||||||
diags = diags.Append(backendDiags)
|
diags = diags.Append(backendDiags)
|
||||||
|
@ -251,7 +263,7 @@ func (c *InitCommand) Run(args []string) int {
|
||||||
// on a previous run) we'll use the current state as a potential source
|
// on a previous run) we'll use the current state as a potential source
|
||||||
// of provider dependencies.
|
// of provider dependencies.
|
||||||
if back != nil {
|
if back != nil {
|
||||||
c.ignoreRemoteBackendVersionConflict(back)
|
c.ignoreRemoteVersionConflict(back)
|
||||||
workspace, err := c.Workspace()
|
workspace, err := c.Workspace()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Error selecting workspace: %s", err))
|
c.Ui.Error(fmt.Sprintf("Error selecting workspace: %s", err))
|
||||||
|
@ -337,6 +349,21 @@ func (c *InitCommand) getModules(path string, earlyRoot *tfconfig.Module, upgrad
|
||||||
return true, diags
|
return true, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *InitCommand) initCloud(root *configs.Module) (be backend.Backend, output bool, diags tfdiags.Diagnostics) {
|
||||||
|
c.Ui.Output(c.Colorize().Color("\n[reset][bold]Initializing Terraform Cloud..."))
|
||||||
|
|
||||||
|
backendConfig := root.CloudConfig.ToBackendConfig()
|
||||||
|
|
||||||
|
opts := &BackendOpts{
|
||||||
|
Config: &backendConfig,
|
||||||
|
Init: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
back, backDiags := c.Backend(opts)
|
||||||
|
diags = diags.Append(backDiags)
|
||||||
|
return back, true, diags
|
||||||
|
}
|
||||||
|
|
||||||
func (c *InitCommand) initBackend(root *configs.Module, extraConfig rawFlags) (be backend.Backend, output bool, diags tfdiags.Diagnostics) {
|
func (c *InitCommand) initBackend(root *configs.Module, extraConfig rawFlags) (be backend.Backend, output bool, diags tfdiags.Diagnostics) {
|
||||||
c.Ui.Output(c.Colorize().Color("\n[reset][bold]Initializing the backend..."))
|
c.Ui.Output(c.Colorize().Color("\n[reset][bold]Initializing the backend..."))
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ import (
|
||||||
"github.com/hashicorp/hcl/v2"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
"github.com/hashicorp/hcl/v2/hcldec"
|
||||||
"github.com/hashicorp/terraform/internal/backend"
|
"github.com/hashicorp/terraform/internal/backend"
|
||||||
remoteBackend "github.com/hashicorp/terraform/internal/backend/remote"
|
|
||||||
"github.com/hashicorp/terraform/internal/command/arguments"
|
"github.com/hashicorp/terraform/internal/command/arguments"
|
||||||
"github.com/hashicorp/terraform/internal/command/clistate"
|
"github.com/hashicorp/terraform/internal/command/clistate"
|
||||||
"github.com/hashicorp/terraform/internal/command/views"
|
"github.com/hashicorp/terraform/internal/command/views"
|
||||||
|
@ -55,6 +54,13 @@ type BackendOpts struct {
|
||||||
ForceLocal bool
|
ForceLocal bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BackendWithRemoteTerraformVersion is a shared interface between the 'remote' and 'cloud' backends
|
||||||
|
// for simplified type checking when calling functions common to those particular backends.
|
||||||
|
type BackendWithRemoteTerraformVersion interface {
|
||||||
|
IgnoreVersionConflict()
|
||||||
|
VerifyWorkspaceTerraformVersion(workspace string) tfdiags.Diagnostics
|
||||||
|
}
|
||||||
|
|
||||||
// Backend initializes and returns the backend for this CLI session.
|
// Backend initializes and returns the backend for this CLI session.
|
||||||
//
|
//
|
||||||
// The backend is used to perform the actual Terraform operations. This
|
// The backend is used to perform the actual Terraform operations. This
|
||||||
|
@ -1168,32 +1174,32 @@ func (m *Meta) backendInitFromConfig(c *configs.Backend) (backend.Backend, cty.V
|
||||||
return b, configVal, diags
|
return b, configVal, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper method to ignore remote backend version conflicts. Only call this
|
// Helper method to ignore remote/cloud backend version conflicts. Only call this
|
||||||
// for commands which cannot accidentally upgrade remote state files.
|
// for commands which cannot accidentally upgrade remote state files.
|
||||||
func (m *Meta) ignoreRemoteBackendVersionConflict(b backend.Backend) {
|
func (m *Meta) ignoreRemoteVersionConflict(b backend.Backend) {
|
||||||
if rb, ok := b.(*remoteBackend.Remote); ok {
|
if back, ok := b.(BackendWithRemoteTerraformVersion); ok {
|
||||||
rb.IgnoreVersionConflict()
|
back.IgnoreVersionConflict()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper method to check the local Terraform version against the configured
|
// Helper method to check the local Terraform version against the configured
|
||||||
// version in the remote workspace, returning diagnostics if they conflict.
|
// version in the remote workspace, returning diagnostics if they conflict.
|
||||||
func (m *Meta) remoteBackendVersionCheck(b backend.Backend, workspace string) tfdiags.Diagnostics {
|
func (m *Meta) remoteVersionCheck(b backend.Backend, workspace string) tfdiags.Diagnostics {
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
if rb, ok := b.(*remoteBackend.Remote); ok {
|
if back, ok := b.(BackendWithRemoteTerraformVersion); ok {
|
||||||
// Allow user override based on command-line flag
|
// Allow user override based on command-line flag
|
||||||
if m.ignoreRemoteVersion {
|
if m.ignoreRemoteVersion {
|
||||||
rb.IgnoreVersionConflict()
|
back.IgnoreVersionConflict()
|
||||||
}
|
}
|
||||||
// If the override is set, this check will return a warning instead of
|
// If the override is set, this check will return a warning instead of
|
||||||
// an error
|
// an error
|
||||||
versionDiags := rb.VerifyWorkspaceTerraformVersion(workspace)
|
versionDiags := back.VerifyWorkspaceTerraformVersion(workspace)
|
||||||
diags = diags.Append(versionDiags)
|
diags = diags.Append(versionDiags)
|
||||||
// If there are no errors resulting from this check, we do not need to
|
// If there are no errors resulting from this check, we do not need to
|
||||||
// check again
|
// check again
|
||||||
if !diags.HasErrors() {
|
if !diags.HasErrors() {
|
||||||
rb.IgnoreVersionConflict()
|
back.IgnoreVersionConflict()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,17 +75,17 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
|
||||||
// Disregard remote Terraform version for the state source backend. If it's a
|
// Disregard remote Terraform version for the state source backend. If it's a
|
||||||
// Terraform Cloud remote backend, we don't care about the remote version,
|
// Terraform Cloud remote backend, we don't care about the remote version,
|
||||||
// as we are migrating away and will not break a remote workspace.
|
// as we are migrating away and will not break a remote workspace.
|
||||||
m.ignoreRemoteBackendVersionConflict(opts.Source)
|
m.ignoreRemoteVersionConflict(opts.Source)
|
||||||
|
|
||||||
// Disregard remote Terraform version if instructed to do so via CLI flag.
|
// Disregard remote Terraform version if instructed to do so via CLI flag.
|
||||||
if m.ignoreRemoteVersion {
|
if m.ignoreRemoteVersion {
|
||||||
m.ignoreRemoteBackendVersionConflict(opts.Destination)
|
m.ignoreRemoteVersionConflict(opts.Destination)
|
||||||
} else {
|
} else {
|
||||||
// Check the remote Terraform version for the state destination backend. If
|
// Check the remote Terraform version for the state destination backend. If
|
||||||
// it's a Terraform Cloud remote backend, we want to ensure that we don't
|
// it's a Terraform Cloud remote backend, we want to ensure that we don't
|
||||||
// break the workspace by uploading an incompatible state file.
|
// break the workspace by uploading an incompatible state file.
|
||||||
for _, workspace := range destinationWorkspaces {
|
for _, workspace := range destinationWorkspaces {
|
||||||
diags := m.remoteBackendVersionCheck(opts.Destination, workspace)
|
diags := m.remoteVersionCheck(opts.Destination, workspace)
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
return diags.Err()
|
return diags.Err()
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
|
||||||
// If there are no specified destination workspaces, perform a remote
|
// If there are no specified destination workspaces, perform a remote
|
||||||
// backend version check with the default workspace.
|
// backend version check with the default workspace.
|
||||||
if len(destinationWorkspaces) == 0 {
|
if len(destinationWorkspaces) == 0 {
|
||||||
diags := m.remoteBackendVersionCheck(opts.Destination, backend.DefaultStateName)
|
diags := m.remoteVersionCheck(opts.Destination, backend.DefaultStateName)
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
return diags.Err()
|
return diags.Err()
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,6 +136,12 @@ func (m *Meta) loadBackendConfig(rootDir string) (*configs.Backend, tfdiags.Diag
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
return nil, diags
|
return nil, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if mod.CloudConfig != nil {
|
||||||
|
backendConfig := mod.CloudConfig.ToBackendConfig()
|
||||||
|
return &backendConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
return mod.Backend, nil
|
return mod.Backend, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ func (c *OutputCommand) Outputs(statePath string) (map[string]*states.OutputValu
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a read-only command
|
// This is a read-only command
|
||||||
c.ignoreRemoteBackendVersionConflict(b)
|
c.ignoreRemoteVersionConflict(b)
|
||||||
|
|
||||||
env, err := c.Workspace()
|
env, err := c.Workspace()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -83,7 +83,7 @@ func (c *ProvidersCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a read-only command
|
// This is a read-only command
|
||||||
c.ignoreRemoteBackendVersionConflict(b)
|
c.ignoreRemoteVersionConflict(b)
|
||||||
|
|
||||||
// Get the state
|
// Get the state
|
||||||
env, err := c.Workspace()
|
env, err := c.Workspace()
|
||||||
|
|
|
@ -68,7 +68,7 @@ func (c *ProvidersSchemaCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a read-only command
|
// This is a read-only command
|
||||||
c.ignoreRemoteBackendVersionConflict(b)
|
c.ignoreRemoteVersionConflict(b)
|
||||||
|
|
||||||
// we expect that the config dir is the cwd
|
// we expect that the config dir is the cwd
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
|
|
|
@ -70,7 +70,7 @@ func (c *ShowCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a read-only command
|
// This is a read-only command
|
||||||
c.ignoreRemoteBackendVersionConflict(b)
|
c.ignoreRemoteVersionConflict(b)
|
||||||
|
|
||||||
// the show command expects the config dir to always be the cwd
|
// the show command expects the config dir to always be the cwd
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
|
|
|
@ -41,7 +41,7 @@ func (c *StateListCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a read-only command
|
// This is a read-only command
|
||||||
c.ignoreRemoteBackendVersionConflict(b)
|
c.ignoreRemoteVersionConflict(b)
|
||||||
|
|
||||||
// Get the state
|
// Get the state
|
||||||
env, err := c.Workspace()
|
env, err := c.Workspace()
|
||||||
|
|
|
@ -43,7 +43,7 @@ func (c *StateMeta) State() (statemgr.Full, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check remote Terraform version is compatible
|
// Check remote Terraform version is compatible
|
||||||
remoteVersionDiags := c.remoteBackendVersionCheck(b, workspace)
|
remoteVersionDiags := c.remoteVersionCheck(b, workspace)
|
||||||
c.showDiagnostics(remoteVersionDiags)
|
c.showDiagnostics(remoteVersionDiags)
|
||||||
if remoteVersionDiags.HasErrors() {
|
if remoteVersionDiags.HasErrors() {
|
||||||
return nil, fmt.Errorf("Error checking remote Terraform version")
|
return nil, fmt.Errorf("Error checking remote Terraform version")
|
||||||
|
|
|
@ -31,7 +31,7 @@ func (c *StatePullCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a read-only command
|
// This is a read-only command
|
||||||
c.ignoreRemoteBackendVersionConflict(b)
|
c.ignoreRemoteVersionConflict(b)
|
||||||
|
|
||||||
// Get the state manager for the current workspace
|
// Get the state manager for the current workspace
|
||||||
env, err := c.Workspace()
|
env, err := c.Workspace()
|
||||||
|
|
|
@ -80,7 +80,7 @@ func (c *StatePushCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check remote Terraform version is compatible
|
// Check remote Terraform version is compatible
|
||||||
remoteVersionDiags := c.remoteBackendVersionCheck(b, workspace)
|
remoteVersionDiags := c.remoteVersionCheck(b, workspace)
|
||||||
c.showDiagnostics(remoteVersionDiags)
|
c.showDiagnostics(remoteVersionDiags)
|
||||||
if remoteVersionDiags.HasErrors() {
|
if remoteVersionDiags.HasErrors() {
|
||||||
return 1
|
return 1
|
||||||
|
|
|
@ -54,7 +54,7 @@ func (c *StateShowCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a read-only command
|
// This is a read-only command
|
||||||
c.ignoreRemoteBackendVersionConflict(b)
|
c.ignoreRemoteVersionConflict(b)
|
||||||
|
|
||||||
// Check if the address can be parsed
|
// Check if the address can be parsed
|
||||||
addr, addrDiags := addrs.ParseAbsResourceInstanceStr(args[0])
|
addr, addrDiags := addrs.ParseAbsResourceInstanceStr(args[0])
|
||||||
|
|
|
@ -102,7 +102,7 @@ func (c *TaintCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check remote Terraform version is compatible
|
// Check remote Terraform version is compatible
|
||||||
remoteVersionDiags := c.remoteBackendVersionCheck(b, workspace)
|
remoteVersionDiags := c.remoteVersionCheck(b, workspace)
|
||||||
diags = diags.Append(remoteVersionDiags)
|
diags = diags.Append(remoteVersionDiags)
|
||||||
c.showDiagnostics(diags)
|
c.showDiagnostics(diags)
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
|
|
|
@ -67,7 +67,7 @@ func (c *UntaintCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check remote Terraform version is compatible
|
// Check remote Terraform version is compatible
|
||||||
remoteVersionDiags := c.remoteBackendVersionCheck(b, workspace)
|
remoteVersionDiags := c.remoteVersionCheck(b, workspace)
|
||||||
diags = diags.Append(remoteVersionDiags)
|
diags = diags.Append(remoteVersionDiags)
|
||||||
c.showDiagnostics(diags)
|
c.showDiagnostics(diags)
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
|
|
|
@ -68,7 +68,7 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This command will not write state
|
// This command will not write state
|
||||||
c.ignoreRemoteBackendVersionConflict(b)
|
c.ignoreRemoteVersionConflict(b)
|
||||||
|
|
||||||
workspaces, err := b.Workspaces()
|
workspaces, err := b.Workspaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -52,7 +52,7 @@ func (c *WorkspaceListCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This command will not write state
|
// This command will not write state
|
||||||
c.ignoreRemoteBackendVersionConflict(b)
|
c.ignoreRemoteVersionConflict(b)
|
||||||
|
|
||||||
states, err := b.Workspaces()
|
states, err := b.Workspaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -83,7 +83,7 @@ func (c *WorkspaceNewCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This command will not write state
|
// This command will not write state
|
||||||
c.ignoreRemoteBackendVersionConflict(b)
|
c.ignoreRemoteVersionConflict(b)
|
||||||
|
|
||||||
workspaces, err := b.Workspaces()
|
workspaces, err := b.Workspaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -68,7 +68,7 @@ func (c *WorkspaceSelectCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This command will not write state
|
// This command will not write state
|
||||||
c.ignoreRemoteBackendVersionConflict(b)
|
c.ignoreRemoteVersionConflict(b)
|
||||||
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
if !validWorkspaceName(name) {
|
if !validWorkspaceName(name) {
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package configs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cloud represents a "cloud" block inside a "terraform" block in a module
|
||||||
|
// or file.
|
||||||
|
type CloudConfig struct {
|
||||||
|
Config hcl.Body
|
||||||
|
|
||||||
|
DeclRange hcl.Range
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeCloudBlock(block *hcl.Block) (*CloudConfig, hcl.Diagnostics) {
|
||||||
|
return &CloudConfig{
|
||||||
|
Config: block.Body,
|
||||||
|
DeclRange: block.DefRange,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CloudConfig) ToBackendConfig() Backend {
|
||||||
|
return Backend{
|
||||||
|
Type: "cloud",
|
||||||
|
Config: c.Config,
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ type Module struct {
|
||||||
ActiveExperiments experiments.Set
|
ActiveExperiments experiments.Set
|
||||||
|
|
||||||
Backend *Backend
|
Backend *Backend
|
||||||
|
CloudConfig *CloudConfig
|
||||||
ProviderConfigs map[string]*Provider
|
ProviderConfigs map[string]*Provider
|
||||||
ProviderRequirements *RequiredProviders
|
ProviderRequirements *RequiredProviders
|
||||||
ProviderLocalNames map[addrs.Provider]string
|
ProviderLocalNames map[addrs.Provider]string
|
||||||
|
@ -63,6 +64,7 @@ type File struct {
|
||||||
ActiveExperiments experiments.Set
|
ActiveExperiments experiments.Set
|
||||||
|
|
||||||
Backends []*Backend
|
Backends []*Backend
|
||||||
|
CloudConfigs []*CloudConfig
|
||||||
ProviderConfigs []*Provider
|
ProviderConfigs []*Provider
|
||||||
ProviderMetas []*ProviderMeta
|
ProviderMetas []*ProviderMeta
|
||||||
RequiredProviders []*RequiredProviders
|
RequiredProviders []*RequiredProviders
|
||||||
|
@ -190,6 +192,29 @@ func (m *Module) appendFile(file *File) hcl.Diagnostics {
|
||||||
m.Backend = b
|
m.Backend = b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, c := range file.CloudConfigs {
|
||||||
|
if m.CloudConfig != nil {
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Duplicate Terraform Cloud configurations",
|
||||||
|
Detail: fmt.Sprintf("A module may have only one 'cloud' block configuring Terraform Cloud. Terraform Cloud was previously configured at %s.", m.CloudConfig.DeclRange),
|
||||||
|
Subject: &c.DeclRange,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
m.CloudConfig = c
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Backend != nil && m.CloudConfig != nil {
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Both a backend and Terraform Cloud configuration are present",
|
||||||
|
Detail: fmt.Sprintf("A module may declare either one 'cloud' block configuring Terraform Cloud OR one 'backend' block configuring a state backend. Terraform Cloud is configured at %s; a backend is configured at %s. Remove the backend block to configure Terraform Cloud.", m.CloudConfig.DeclRange, m.Backend.DeclRange),
|
||||||
|
Subject: &m.Backend.DeclRange,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
for _, pc := range file.ProviderConfigs {
|
for _, pc := range file.ProviderConfigs {
|
||||||
key := pc.moduleUniqueKey()
|
key := pc.moduleUniqueKey()
|
||||||
if existing, exists := m.ProviderConfigs[key]; exists {
|
if existing, exists := m.ProviderConfigs[key]; exists {
|
||||||
|
|
|
@ -72,6 +72,13 @@ func (p *Parser) loadConfigFile(path string, override bool) (*File, hcl.Diagnost
|
||||||
file.Backends = append(file.Backends, backendCfg)
|
file.Backends = append(file.Backends, backendCfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "cloud":
|
||||||
|
cloudCfg, cfgDiags := decodeCloudBlock(innerBlock)
|
||||||
|
diags = append(diags, cfgDiags...)
|
||||||
|
if cloudCfg != nil {
|
||||||
|
file.CloudConfigs = append(file.CloudConfigs, cloudCfg)
|
||||||
|
}
|
||||||
|
|
||||||
case "required_providers":
|
case "required_providers":
|
||||||
reqs, reqsDiags := decodeRequiredProvidersBlock(innerBlock)
|
reqs, reqsDiags := decodeRequiredProvidersBlock(innerBlock)
|
||||||
diags = append(diags, reqsDiags...)
|
diags = append(diags, reqsDiags...)
|
||||||
|
@ -261,6 +268,9 @@ var terraformBlockSchema = &hcl.BodySchema{
|
||||||
Type: "backend",
|
Type: "backend",
|
||||||
LabelNames: []string{"type"},
|
LabelNames: []string{"type"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Type: "cloud",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Type: "required_providers",
|
Type: "required_providers",
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
terraform {
|
||||||
|
# Only the root module can declare a Cloud configuration. Terraform should emit a warning
|
||||||
|
# about this child module Cloud declaration.
|
||||||
|
cloud {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
module "child" {
|
||||||
|
source = "./child"
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
terraform {
|
||||||
|
cloud {
|
||||||
|
foo = "bar"
|
||||||
|
|
||||||
|
baz {
|
||||||
|
bar = "foo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue