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