github.com/keshavdv/terraform@v0.7.0-rc2.0.20160711232630-d69256dcb425/command/push.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "path/filepath" 8 "sort" 9 "strings" 10 11 "github.com/hashicorp/atlas-go/archive" 12 "github.com/hashicorp/atlas-go/v1" 13 ) 14 15 type PushCommand struct { 16 Meta 17 18 // client is the client to use for the actual push operations. 19 // If this isn't set, then the Atlas client is used. This should 20 // really only be set for testing reasons (and is hence not exported). 21 client pushClient 22 } 23 24 func (c *PushCommand) Run(args []string) int { 25 var atlasAddress, atlasToken string 26 var archiveVCS, moduleUpload bool 27 var name string 28 var overwrite []string 29 args = c.Meta.process(args, true) 30 cmdFlags := c.Meta.flagSet("push") 31 cmdFlags.StringVar(&atlasAddress, "atlas-address", "", "") 32 cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") 33 cmdFlags.StringVar(&atlasToken, "token", "", "") 34 cmdFlags.BoolVar(&moduleUpload, "upload-modules", true, "") 35 cmdFlags.StringVar(&name, "name", "", "") 36 cmdFlags.BoolVar(&archiveVCS, "vcs", true, "") 37 cmdFlags.Var((*FlagStringSlice)(&overwrite), "overwrite", "") 38 cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } 39 if err := cmdFlags.Parse(args); err != nil { 40 return 1 41 } 42 43 // Make a map of the set values 44 overwriteMap := make(map[string]struct{}, len(overwrite)) 45 for _, v := range overwrite { 46 overwriteMap[v] = struct{}{} 47 } 48 49 // The pwd is used for the configuration path if one is not given 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 to the configuration depending on the args. 57 var configPath string 58 args = cmdFlags.Args() 59 if len(args) > 1 { 60 c.Ui.Error("The apply command expects at most one argument.") 61 cmdFlags.Usage() 62 return 1 63 } else if len(args) == 1 { 64 configPath = args[0] 65 } else { 66 configPath = pwd 67 } 68 69 // Verify the state is remote, we can't push without a remote state 70 s, err := c.State() 71 if err != nil { 72 c.Ui.Error(fmt.Sprintf("Failed to read state: %s", err)) 73 return 1 74 } 75 if !s.State().IsRemote() { 76 c.Ui.Error( 77 "Remote state is not enabled. For Atlas to run Terraform\n" + 78 "for you, remote state must be used and configured. Remote\n" + 79 "state via any backend is accepted, not just Atlas. To\n" + 80 "configure remote state, use the `terraform remote config`\n" + 81 "command.") 82 return 1 83 } 84 85 // Build the context based on the arguments given 86 ctx, planned, err := c.Context(contextOpts{ 87 Path: configPath, 88 StatePath: c.Meta.statePath, 89 }) 90 if err != nil { 91 c.Ui.Error(err.Error()) 92 return 1 93 } 94 if planned { 95 c.Ui.Error( 96 "A plan file cannot be given as the path to the configuration.\n" + 97 "A path to a module (directory with configuration) must be given.") 98 return 1 99 } 100 101 // Get the configuration 102 config := ctx.Module().Config() 103 if name == "" { 104 if config.Atlas == nil || config.Atlas.Name == "" { 105 c.Ui.Error( 106 "The name of this Terraform configuration in Atlas must be\n" + 107 "specified within your configuration or the command-line. To\n" + 108 "set it on the command-line, use the `-name` parameter.") 109 return 1 110 } 111 name = config.Atlas.Name 112 } 113 114 // Initialize the client if it isn't given. 115 if c.client == nil { 116 // Make sure to nil out our client so our token isn't sitting around 117 defer func() { c.client = nil }() 118 119 // Initialize it to the default client, we set custom settings later 120 client := atlas.DefaultClient() 121 if atlasAddress != "" { 122 client, err = atlas.NewClient(atlasAddress) 123 if err != nil { 124 c.Ui.Error(fmt.Sprintf("Error initializing Atlas client: %s", err)) 125 return 1 126 } 127 } 128 129 if atlasToken != "" { 130 client.Token = atlasToken 131 } 132 133 c.client = &atlasPushClient{Client: client} 134 } 135 136 // Get the variables we might already have 137 atlasVars, err := c.client.Get(name) 138 if err != nil { 139 c.Ui.Error(fmt.Sprintf( 140 "Error looking up previously pushed configuration: %s", err)) 141 return 1 142 } 143 for k, v := range atlasVars { 144 if _, ok := overwriteMap[k]; ok { 145 continue 146 } 147 148 ctx.SetVariable(k, v) 149 } 150 151 // Ask for input 152 if err := ctx.Input(c.InputMode()); err != nil { 153 c.Ui.Error(fmt.Sprintf( 154 "Error while asking for variable input:\n\n%s", err)) 155 return 1 156 } 157 158 // Build the archiving options, which includes everything it can 159 // by default according to VCS rules but forcing the data directory. 160 archiveOpts := &archive.ArchiveOpts{ 161 VCS: archiveVCS, 162 Extra: map[string]string{ 163 DefaultDataDir: c.DataDir(), 164 }, 165 } 166 if !moduleUpload { 167 // If we're not uploading modules, then exclude the modules dir. 168 archiveOpts.Exclude = append( 169 archiveOpts.Exclude, 170 filepath.Join(c.DataDir(), "modules")) 171 } 172 173 archiveR, err := archive.CreateArchive(configPath, archiveOpts) 174 if err != nil { 175 c.Ui.Error(fmt.Sprintf( 176 "An error has occurred while archiving the module for uploading:\n"+ 177 "%s", err)) 178 return 1 179 } 180 181 // Output to the user the variables that will be uploaded 182 var setVars []string 183 for k, _ := range ctx.Variables() { 184 if _, ok := overwriteMap[k]; !ok { 185 if _, ok := atlasVars[k]; ok { 186 // Atlas variable not within override, so it came from Atlas 187 continue 188 } 189 } 190 191 // This variable was set from the local value 192 setVars = append(setVars, k) 193 } 194 sort.Strings(setVars) 195 if len(setVars) > 0 { 196 c.Ui.Output( 197 "The following variables will be set or overwritten within Atlas from\n" + 198 "their local values. All other variables are already set within Atlas.\n" + 199 "If you want to modify the value of a variable, use the Atlas web\n" + 200 "interface or set it locally and use the -overwrite flag.\n\n") 201 for _, v := range setVars { 202 c.Ui.Output(fmt.Sprintf(" * %s", v)) 203 } 204 205 // Newline 206 c.Ui.Output("") 207 } 208 209 // Upsert! 210 opts := &pushUpsertOptions{ 211 Name: name, 212 Archive: archiveR, 213 Variables: ctx.Variables(), 214 } 215 c.Ui.Output("Uploading Terraform configuration...") 216 vsn, err := c.client.Upsert(opts) 217 if err != nil { 218 c.Ui.Error(fmt.Sprintf( 219 "An error occurred while uploading the module:\n\n%s", err)) 220 return 1 221 } 222 223 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 224 "[reset][bold][green]Configuration %q uploaded! (v%d)", 225 name, vsn))) 226 return 0 227 } 228 229 func (c *PushCommand) Help() string { 230 helpText := ` 231 Usage: terraform push [options] [DIR] 232 233 Upload this Terraform module to an Atlas server for remote 234 infrastructure management. 235 236 Options: 237 238 -atlas-address=<url> An alternate address to an Atlas instance. Defaults 239 to https://atlas.hashicorp.com 240 241 -upload-modules=true If true (default), then the modules are locked at 242 their current checkout and uploaded completely. This 243 prevents Atlas from running "terraform get". 244 245 -name=<name> Name of the configuration in Atlas. This can also 246 be set in the configuration itself. Format is 247 typically: "username/name". 248 249 -token=<token> Access token to use to upload. If blank or unspecified, 250 the ATLAS_TOKEN environmental variable will be used. 251 252 -overwrite=foo Variable keys that should overwrite values in Atlas. 253 Otherwise, variables already set in Atlas will overwrite 254 local values. This flag can be repeated. 255 256 -var 'foo=bar' Set a variable in the Terraform configuration. This 257 flag can be set multiple times. 258 259 -var-file=foo Set variables in the Terraform configuration from 260 a file. If "terraform.tfvars" is present, it will be 261 automatically loaded if this flag is not specified. 262 263 -vcs=true If true (default), push will upload only files 264 committed to your VCS, if detected. 265 266 -no-color If specified, output won't contain any color. 267 268 ` 269 return strings.TrimSpace(helpText) 270 } 271 272 func (c *PushCommand) Synopsis() string { 273 return "Upload this Terraform module to Atlas to run" 274 } 275 276 // pushClient is implementd internally to control where pushes go. This is 277 // either to Atlas or a mock for testing. 278 type pushClient interface { 279 Get(string) (map[string]string, error) 280 Upsert(*pushUpsertOptions) (int, error) 281 } 282 283 type pushUpsertOptions struct { 284 Name string 285 Archive *archive.Archive 286 Variables map[string]string 287 } 288 289 type atlasPushClient struct { 290 Client *atlas.Client 291 } 292 293 func (c *atlasPushClient) Get(name string) (map[string]string, error) { 294 user, name, err := atlas.ParseSlug(name) 295 if err != nil { 296 return nil, err 297 } 298 299 version, err := c.Client.TerraformConfigLatest(user, name) 300 if err != nil { 301 return nil, err 302 } 303 304 var variables map[string]string 305 if version != nil { 306 variables = version.Variables 307 } 308 309 return variables, nil 310 } 311 312 func (c *atlasPushClient) Upsert(opts *pushUpsertOptions) (int, error) { 313 user, name, err := atlas.ParseSlug(opts.Name) 314 if err != nil { 315 return 0, err 316 } 317 318 data := &atlas.TerraformConfigVersion{ 319 Variables: opts.Variables, 320 } 321 322 version, err := c.Client.CreateTerraformConfigVersion( 323 user, name, data, opts.Archive, opts.Archive.Size) 324 if err != nil { 325 return 0, err 326 } 327 328 return version, nil 329 } 330 331 type mockPushClient struct { 332 File string 333 334 GetCalled bool 335 GetName string 336 GetResult map[string]string 337 GetError error 338 339 UpsertCalled bool 340 UpsertOptions *pushUpsertOptions 341 UpsertVersion int 342 UpsertError error 343 } 344 345 func (c *mockPushClient) Get(name string) (map[string]string, error) { 346 c.GetCalled = true 347 c.GetName = name 348 return c.GetResult, c.GetError 349 } 350 351 func (c *mockPushClient) Upsert(opts *pushUpsertOptions) (int, error) { 352 f, err := os.Create(c.File) 353 if err != nil { 354 return 0, err 355 } 356 defer f.Close() 357 358 data := opts.Archive 359 size := opts.Archive.Size 360 if _, err := io.CopyN(f, data, size); err != nil { 361 return 0, err 362 } 363 364 c.UpsertCalled = true 365 c.UpsertOptions = opts 366 return c.UpsertVersion, c.UpsertError 367 }