forked from gruntwork-io/terragrunt
-
Notifications
You must be signed in to change notification settings - Fork 0
/
remote_state.go
120 lines (98 loc) · 4.85 KB
/
remote_state.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package remote
import (
"fmt"
"github.com/gruntwork-io/terragrunt/errors"
"github.com/gruntwork-io/terragrunt/shell"
"github.com/gruntwork-io/terragrunt/options"
"reflect"
)
// Configuration for Terraform remote state
type RemoteState struct {
Backend string `hcl:"backend"`
Config map[string]string `hcl:"config"`
}
func (state *RemoteState) String() string {
return fmt.Sprintf("RemoteState{Backend = %v, Config = %v}", state.Backend, state.Config)
}
type RemoteStateInitializer func(map[string]string, *options.TerragruntOptions) error
// TODO: initialization actions for other remote state backends can be added here
var remoteStateInitializers = map[string]RemoteStateInitializer {
"s3": InitializeRemoteStateS3,
}
// Fill in any default configuration for remote state
func (remoteState *RemoteState) FillDefaults() {
// Nothing to do
}
// Validate that the remote state is configured correctly
func (remoteState *RemoteState) Validate() error {
if remoteState.Backend == "" {
return errors.WithStackTrace(RemoteBackendMissing)
}
return nil
}
// Perform any actions necessary to initialize the remote state before it's used for storage. For example, if you're
// using S3 for remote state storage, this may create the S3 bucket if it doesn't exist already.
func (remoteState *RemoteState) Initialize(terragruntOptions *options.TerragruntOptions) error {
initializer, hasInitializer := remoteStateInitializers[remoteState.Backend]
if hasInitializer {
return initializer(remoteState.Config, terragruntOptions)
}
return nil
}
// Configure Terraform remote state
func (remoteState RemoteState) ConfigureRemoteState(terragruntOptions *options.TerragruntOptions) error {
shouldConfigure, err := shouldConfigureRemoteState(remoteState, terragruntOptions)
if err != nil {
return err
}
if shouldConfigure {
terragruntOptions.Logger.Printf("Initializing remote state for the %s backend", remoteState.Backend)
if err := remoteState.Initialize(terragruntOptions); err != nil {
return err
}
terragruntOptions.Logger.Printf("Configuring remote state for the %s backend", remoteState.Backend)
return shell.RunShellCommand(terragruntOptions, terragruntOptions.TerraformPath, remoteState.toTerraformRemoteConfigArgs()...)
}
return nil
}
// Returns true if remote state needs to be configured. This will be the case when:
//
// 1. Remote state has not already been configured
// 2. Remote state has been configured, but for a different backend type, and the user confirms it's OK to overwrite it.
func shouldConfigureRemoteState(remoteStateFromTerragruntConfig RemoteState, terragruntOptions *options.TerragruntOptions) (bool, error) {
state, err := ParseTerraformStateFileFromLocation(terragruntOptions.WorkingDir)
if err != nil {
return false, err
}
if state != nil && state.IsRemote() {
return shouldOverrideExistingRemoteState(state.Remote, remoteStateFromTerragruntConfig, terragruntOptions)
} else {
return true, nil
}
}
// Check if the remote state that is already configured matches the one specified in the Terragrunt config. If it does,
// return false to indicate remote state does not need to be configured again. If it doesn't, prompt the user whether
// we should override the existing remote state setting.
func shouldOverrideExistingRemoteState(existingRemoteState *TerraformStateRemote, remoteStateFromTerragruntConfig RemoteState, terragruntOptions *options.TerragruntOptions) (bool, error) {
if existingRemoteState.Type != remoteStateFromTerragruntConfig.Backend {
prompt := fmt.Sprintf("WARNING: Terraform remote state is already configured, but for backend %s, whereas your Terragrunt configuration specifies %s. Overwrite?", existingRemoteState.Type, remoteStateFromTerragruntConfig.Backend)
return shell.PromptUserForYesNo(prompt, terragruntOptions)
}
if !reflect.DeepEqual(existingRemoteState.Config, remoteStateFromTerragruntConfig.Config) {
prompt := fmt.Sprintf("WARNING: Terraform remote state is already configured for backend %s with config %v, but your Terragrunt configuration specifies config %v. Overwrite?", existingRemoteState.Type, existingRemoteState.Config, remoteStateFromTerragruntConfig.Config)
return shell.PromptUserForYesNo(prompt, terragruntOptions)
}
terragruntOptions.Logger.Printf("Remote state is already configured for backend %s", existingRemoteState.Type)
return false, nil
}
// Convert the RemoteState config into the format used by Terraform
func (remoteState RemoteState) toTerraformRemoteConfigArgs() []string {
baseArgs := []string{"remote", "config", "-backend", remoteState.Backend}
backendConfigArgs := []string{}
for key, value := range remoteState.Config {
arg := fmt.Sprintf("-backend-config=%s=%s", key, value)
backendConfigArgs = append(backendConfigArgs, arg)
}
return append(baseArgs, backendConfigArgs...)
}
var RemoteBackendMissing = fmt.Errorf("The remoteState.backend field cannot be empty")