github.com/koding/terraform@v0.6.4-0.20170608090606-5d7e0339779d/command/init.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "strings" 8 9 "github.com/hashicorp/go-getter" 10 "github.com/hashicorp/terraform/config" 11 "github.com/hashicorp/terraform/config/module" 12 "github.com/hashicorp/terraform/helper/variables" 13 ) 14 15 // InitCommand is a Command implementation that takes a Terraform 16 // module and clones it to the working directory. 17 type InitCommand struct { 18 Meta 19 } 20 21 func (c *InitCommand) Run(args []string) int { 22 var flagBackend, flagGet bool 23 var flagConfigExtra map[string]interface{} 24 25 args = c.Meta.process(args, false) 26 cmdFlags := c.flagSet("init") 27 cmdFlags.BoolVar(&flagBackend, "backend", true, "") 28 cmdFlags.Var((*variables.FlagAny)(&flagConfigExtra), "backend-config", "") 29 cmdFlags.BoolVar(&flagGet, "get", true, "") 30 cmdFlags.BoolVar(&c.forceInitCopy, "force-copy", false, "suppress prompts about copying state data") 31 cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") 32 cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") 33 cmdFlags.BoolVar(&c.reconfigure, "reconfigure", false, "reconfigure") 34 35 cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } 36 if err := cmdFlags.Parse(args); err != nil { 37 return 1 38 } 39 40 // Validate the arg count 41 args = cmdFlags.Args() 42 if len(args) > 2 { 43 c.Ui.Error("The init command expects at most two arguments.\n") 44 cmdFlags.Usage() 45 return 1 46 } 47 48 // Get our pwd. We don't always need it but always getting it is easier 49 // than the logic to determine if it is or isn't needed. 50 pwd, err := os.Getwd() 51 if err != nil { 52 c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err)) 53 return 1 54 } 55 56 // Get the path and source module to copy 57 var path string 58 var source string 59 switch len(args) { 60 case 0: 61 path = pwd 62 case 1: 63 path = pwd 64 source = args[0] 65 case 2: 66 source = args[0] 67 path = args[1] 68 default: 69 panic("assertion failed on arg count") 70 } 71 72 // Set the state out path to be the path requested for the module 73 // to be copied. This ensures any remote states gets setup in the 74 // proper directory. 75 c.Meta.dataDir = filepath.Join(path, DefaultDataDir) 76 77 // This will track whether we outputted anything so that we know whether 78 // to output a newline before the success message 79 var header bool 80 81 // If we have a source, copy it 82 if source != "" { 83 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 84 "[reset][bold]"+ 85 "Initializing configuration from: %q...", source))) 86 if err := c.copySource(path, source, pwd); err != nil { 87 c.Ui.Error(fmt.Sprintf( 88 "Error copying source: %s", err)) 89 return 1 90 } 91 92 header = true 93 } 94 95 // If our directory is empty, then we're done. We can't get or setup 96 // the backend with an empty directory. 97 if empty, err := config.IsEmptyDir(path); err != nil { 98 c.Ui.Error(fmt.Sprintf( 99 "Error checking configuration: %s", err)) 100 return 1 101 } else if empty { 102 c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitEmpty))) 103 return 0 104 } 105 106 // If we're performing a get or loading the backend, then we perform 107 // some extra tasks. 108 if flagGet || flagBackend { 109 // Load the configuration in this directory so that we can know 110 // if we have anything to get or any backend to configure. We do 111 // this to improve the UX. Practically, we could call the functions 112 // below without checking this to the same effect. 113 conf, err := config.LoadDir(path) 114 if err != nil { 115 c.Ui.Error(fmt.Sprintf( 116 "Error loading configuration: %s", err)) 117 return 1 118 } 119 120 // If we requested downloading modules and have modules in the config 121 if flagGet && len(conf.Modules) > 0 { 122 header = true 123 124 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 125 "[reset][bold]" + 126 "Downloading modules (if any)..."))) 127 if err := getModules(&c.Meta, path, module.GetModeGet); err != nil { 128 c.Ui.Error(fmt.Sprintf( 129 "Error downloading modules: %s", err)) 130 return 1 131 } 132 } 133 134 // If we're requesting backend configuration and configure it 135 if flagBackend { 136 header = true 137 138 // Only output that we're initializing a backend if we have 139 // something in the config. We can be UNSETTING a backend as well 140 // in which case we choose not to show this. 141 if conf.Terraform != nil && conf.Terraform.Backend != nil { 142 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 143 "[reset][bold]" + 144 "Initializing the backend..."))) 145 } 146 147 opts := &BackendOpts{ 148 ConfigPath: path, 149 ConfigExtra: flagConfigExtra, 150 Init: true, 151 } 152 if _, err := c.Backend(opts); err != nil { 153 c.Ui.Error(err.Error()) 154 return 1 155 } 156 } 157 } 158 159 // If we outputted information, then we need to output a newline 160 // so that our success message is nicely spaced out from prior text. 161 if header { 162 c.Ui.Output("") 163 } 164 165 c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitSuccess))) 166 167 return 0 168 } 169 170 func (c *InitCommand) copySource(dst, src, pwd string) error { 171 // Verify the directory is empty 172 if empty, err := config.IsEmptyDir(dst); err != nil { 173 return fmt.Errorf("Error checking on destination path: %s", err) 174 } else if !empty { 175 return fmt.Errorf(strings.TrimSpace(errInitCopyNotEmpty)) 176 } 177 178 // Detect 179 source, err := getter.Detect(src, pwd, getter.Detectors) 180 if err != nil { 181 return fmt.Errorf("Error with module source: %s", err) 182 } 183 184 // Get it! 185 return module.GetCopy(dst, source) 186 } 187 188 func (c *InitCommand) Help() string { 189 helpText := ` 190 Usage: terraform init [options] [SOURCE] [PATH] 191 192 Initialize a new or existing Terraform environment by creating 193 initial files, loading any remote state, downloading modules, etc. 194 195 This is the first command that should be run for any new or existing 196 Terraform configuration per machine. This sets up all the local data 197 necessary to run Terraform that is typically not committed to version 198 control. 199 200 This command is always safe to run multiple times. Though subsequent runs 201 may give errors, this command will never blow away your environment or state. 202 Even so, if you have important information, please back it up prior to 203 running this command just in case. 204 205 If no arguments are given, the configuration in this working directory 206 is initialized. 207 208 If one or two arguments are given, the first is a SOURCE of a module to 209 download to the second argument PATH. After downloading the module to PATH, 210 the configuration will be initialized as if this command were called pointing 211 only to that PATH. PATH must be empty of any Terraform files. Any 212 conflicting non-Terraform files will be overwritten. The module download 213 is a copy. If you're downloading a module from Git, it will not preserve 214 Git history. 215 216 Options: 217 218 -backend=true Configure the backend for this environment. 219 220 -backend-config=path This can be either a path to an HCL file with key/value 221 assignments (same format as terraform.tfvars) or a 222 'key=value' format. This is merged with what is in the 223 configuration file. This can be specified multiple 224 times. The backend type must be in the configuration 225 itself. 226 227 -force-copy Suppress prompts about copying state data. This is 228 equivalent to providing a "yes" to all confirmation 229 prompts. 230 231 -get=true Download any modules for this configuration. 232 233 -input=true Ask for input if necessary. If false, will error if 234 input was required. 235 236 -lock=true Lock the state file when locking is supported. 237 238 -lock-timeout=0s Duration to retry a state lock. 239 240 -no-color If specified, output won't contain any color. 241 242 -reconfigure Reconfigure the backend, ignoring any saved configuration. 243 ` 244 return strings.TrimSpace(helpText) 245 } 246 247 func (c *InitCommand) Synopsis() string { 248 return "Initialize a new or existing Terraform configuration" 249 } 250 251 const errInitCopyNotEmpty = ` 252 The destination path contains Terraform configuration files. The init command 253 with a SOURCE parameter can only be used on a directory without existing 254 Terraform files. 255 256 Please resolve this issue and try again. 257 ` 258 259 const outputInitEmpty = ` 260 [reset][bold]Terraform initialized in an empty directory![reset] 261 262 The directory has no Terraform configuration files. You may begin working 263 with Terraform immediately by creating Terraform configuration files. 264 ` 265 266 const outputInitSuccess = ` 267 [reset][bold][green]Terraform has been successfully initialized![reset][green] 268 269 You may now begin working with Terraform. Try running "terraform plan" to see 270 any changes that are required for your infrastructure. All Terraform commands 271 should now work. 272 273 If you ever set or change modules or backend configuration for Terraform, 274 rerun this command to reinitialize your environment. If you forget, other 275 commands will detect it and remind you to do so if necessary. 276 `