forked from hashicorp/terraform
-
Notifications
You must be signed in to change notification settings - Fork 0
/
init.go
273 lines (224 loc) · 8.46 KB
/
init.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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
package command
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/hashicorp/go-getter"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/helper/variables"
)
// InitCommand is a Command implementation that takes a Terraform
// module and clones it to the working directory.
type InitCommand struct {
Meta
}
func (c *InitCommand) Run(args []string) int {
var flagBackend, flagGet bool
var flagConfigExtra map[string]interface{}
args = c.Meta.process(args, false)
cmdFlags := c.flagSet("init")
cmdFlags.BoolVar(&flagBackend, "backend", true, "")
cmdFlags.Var((*variables.FlagAny)(&flagConfigExtra), "backend-config", "")
cmdFlags.BoolVar(&flagGet, "get", true, "")
cmdFlags.BoolVar(&c.forceInitCopy, "force-copy", false, "suppress prompts about copying state data")
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
return 1
}
// Validate the arg count
args = cmdFlags.Args()
if len(args) > 2 {
c.Ui.Error("The init command expects at most two arguments.\n")
cmdFlags.Usage()
return 1
}
// Get our pwd. We don't always need it but always getting it is easier
// than the logic to determine if it is or isn't needed.
pwd, err := os.Getwd()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
return 1
}
// Get the path and source module to copy
var path string
var source string
switch len(args) {
case 0:
path = pwd
case 1:
path = pwd
source = args[0]
case 2:
source = args[0]
path = args[1]
default:
panic("assertion failed on arg count")
}
// Set the state out path to be the path requested for the module
// to be copied. This ensures any remote states gets setup in the
// proper directory.
c.Meta.dataDir = filepath.Join(path, DefaultDataDir)
// This will track whether we outputted anything so that we know whether
// to output a newline before the success message
var header bool
// If we have a source, copy it
if source != "" {
c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
"[reset][bold]"+
"Initializing configuration from: %q...", source)))
if err := c.copySource(path, source, pwd); err != nil {
c.Ui.Error(fmt.Sprintf(
"Error copying source: %s", err))
return 1
}
header = true
}
// If our directory is empty, then we're done. We can't get or setup
// the backend with an empty directory.
if empty, err := config.IsEmptyDir(path); err != nil {
c.Ui.Error(fmt.Sprintf(
"Error checking configuration: %s", err))
return 1
} else if empty {
c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitEmpty)))
return 0
}
// If we're performing a get or loading the backend, then we perform
// some extra tasks.
if flagGet || flagBackend {
// Load the configuration in this directory so that we can know
// if we have anything to get or any backend to configure. We do
// this to improve the UX. Practically, we could call the functions
// below without checking this to the same effect.
conf, err := config.LoadDir(path)
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error loading configuration: %s", err))
return 1
}
// If we requested downloading modules and have modules in the config
if flagGet && len(conf.Modules) > 0 {
header = true
c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
"[reset][bold]" +
"Downloading modules (if any)...")))
if err := getModules(&c.Meta, path, module.GetModeGet); err != nil {
c.Ui.Error(fmt.Sprintf(
"Error downloading modules: %s", err))
return 1
}
}
// If we're requesting backend configuration and configure it
if flagBackend {
header = true
// Only output that we're initializing a backend if we have
// something in the config. We can be UNSETTING a backend as well
// in which case we choose not to show this.
if conf.Terraform != nil && conf.Terraform.Backend != nil {
c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
"[reset][bold]" +
"Initializing the backend...")))
}
opts := &BackendOpts{
ConfigPath: path,
ConfigExtra: flagConfigExtra,
Init: true,
}
if _, err := c.Backend(opts); err != nil {
c.Ui.Error(err.Error())
return 1
}
}
}
// If we outputted information, then we need to output a newline
// so that our success message is nicely spaced out from prior text.
if header {
c.Ui.Output("")
}
c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitSuccess)))
return 0
}
func (c *InitCommand) copySource(dst, src, pwd string) error {
// Verify the directory is empty
if empty, err := config.IsEmptyDir(dst); err != nil {
return fmt.Errorf("Error checking on destination path: %s", err)
} else if !empty {
return fmt.Errorf(strings.TrimSpace(errInitCopyNotEmpty))
}
// Detect
source, err := getter.Detect(src, pwd, getter.Detectors)
if err != nil {
return fmt.Errorf("Error with module source: %s", err)
}
// Get it!
return module.GetCopy(dst, source)
}
func (c *InitCommand) Help() string {
helpText := `
Usage: terraform init [options] [SOURCE] [PATH]
Initialize a new or existing Terraform environment by creating
initial files, loading any remote state, downloading modules, etc.
This is the first command that should be run for any new or existing
Terraform configuration per machine. This sets up all the local data
necessary to run Terraform that is typically not comitted to version
control.
This command is always safe to run multiple times. Though subsequent runs
may give errors, this command will never blow away your environment or state.
Even so, if you have important information, please back it up prior to
running this command just in case.
If no arguments are given, the configuration in this working directory
is initialized.
If one or two arguments are given, the first is a SOURCE of a module to
download to the second argument PATH. After downloading the module to PATH,
the configuration will be initialized as if this command were called pointing
only to that PATH. PATH must be empty of any Terraform files. Any
conflicting non-Terraform files will be overwritten. The module download
is a copy. If you're downloading a module from Git, it will not preserve
Git history.
Options:
-backend=true Configure the backend for this environment.
-backend-config=path This can be either a path to an HCL file with key/value
assignments (same format as terraform.tfvars) or a
'key=value' format. This is merged with what is in the
configuration file. This can be specified multiple
times. The backend type must be in the configuration
itself.
-get=true Download any modules for this configuration.
-input=true Ask for input if necessary. If false, will error if
input was required.
-lock=true Lock the state file when locking is supported.
-lock-timeout=0s Duration to retry a state lock.
-no-color If specified, output won't contain any color.
-force-copy Suppress prompts about copying state data. This is
equivalent to providing a "yes" to all confirmation
prompts.
`
return strings.TrimSpace(helpText)
}
func (c *InitCommand) Synopsis() string {
return "Initialize a new or existing Terraform configuration"
}
const errInitCopyNotEmpty = `
The destination path contains Terraform configuration files. The init command
with a SOURCE parameter can only be used on a directory without existing
Terraform files.
Please resolve this issue and try again.
`
const outputInitEmpty = `
[reset][bold]Terraform initialized in an empty directory![reset]
The directory has no Terraform configuration files. You may begin working
with Terraform immediately by creating Terraform configuration files.
`
const outputInitSuccess = `
[reset][bold][green]Terraform has been successfully initialized![reset][green]
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your environment. If you forget, other
commands will detect it and remind you to do so if necessary.
`