func TestCore_pushInterpolate(t *testing.T) { cases := []struct { File string Vars map[string]string Result template.Push }{ { "push-vars.json", map[string]string{"foo": "bar"}, template.Push{Name: "bar"}, }, } for _, tc := range cases { tpl, err := template.ParseFile(fixtureDir(tc.File)) if err != nil { t.Fatalf("err: %s\n\n%s", tc.File, err) } core, err := NewCore(&CoreConfig{ Template: tpl, Variables: tc.Vars, }) if err != nil { t.Fatalf("err: %s\n\n%s", tc.File, err) } expected := core.Template.Push if !reflect.DeepEqual(expected, tc.Result) { t.Fatalf("err: %s\n\n%#v", tc.File, expected) } } }
func testCoreTemplate(t *testing.T, c *CoreConfig, p string) { tpl, err := template.ParseFile(p) if err != nil { t.Fatalf("err: %s\n\n%s", p, err) } c.Template = tpl }
func TestCoreBuildNames(t *testing.T) { cases := []struct { File string Vars map[string]string Result []string }{ { "build-names-basic.json", nil, []string{"something"}, }, { "build-names-func.json", nil, []string{"TUBES"}, }, } for _, tc := range cases { tpl, err := template.ParseFile(fixtureDir(tc.File)) if err != nil { t.Fatalf("err: %s\n\n%s", tc.File, err) } core, err := NewCore(&CoreConfig{ Template: tpl, Variables: tc.Vars, }) if err != nil { t.Fatalf("err: %s\n\n%s", tc.File, err) } names := core.BuildNames() if !reflect.DeepEqual(names, tc.Result) { t.Fatalf("err: %s\n\n%#v", tc.File, names) } } }
func (c BuildCommand) Run(args []string) int { var cfgColor, cfgDebug, cfgForce, cfgParallel bool var cfgOnError string flags := c.Meta.FlagSet("build", FlagSetBuildFilter|FlagSetVars) flags.Usage = func() { c.Ui.Say(c.Help()) } flags.BoolVar(&cfgColor, "color", true, "") flags.BoolVar(&cfgDebug, "debug", false, "") flags.BoolVar(&cfgForce, "force", false, "") flagOnError := enumflag.New(&cfgOnError, "cleanup", "abort", "ask") flags.Var(flagOnError, "on-error", "") flags.BoolVar(&cfgParallel, "parallel", true, "") if err := flags.Parse(args); err != nil { return 1 } args = flags.Args() if len(args) != 1 { flags.Usage() return 1 } // Parse the template var tpl *template.Template var err error tpl, err = template.ParseFile(args[0]) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err)) return 1 } // Get the core core, err := c.Meta.Core(tpl) if err != nil { c.Ui.Error(err.Error()) return 1 } // Get the builds we care about buildNames := c.Meta.BuildNames(core) builds := make([]packer.Build, 0, len(buildNames)) for _, n := range buildNames { b, err := core.Build(n) if err != nil { c.Ui.Error(fmt.Sprintf( "Failed to initialize build '%s': %s", n, err)) continue } builds = append(builds, b) } if cfgDebug { c.Ui.Say("Debug mode enabled. Builds will not be parallelized.") } // Compile all the UIs for the builds colors := [5]packer.UiColor{ packer.UiColorGreen, packer.UiColorCyan, packer.UiColorMagenta, packer.UiColorYellow, packer.UiColorBlue, } buildUis := make(map[string]packer.Ui) for i, b := range buildNames { var ui packer.Ui ui = c.Ui if cfgColor { ui = &packer.ColoredUi{ Color: colors[i%len(colors)], Ui: ui, } } buildUis[b] = ui ui.Say(fmt.Sprintf("%s output will be in this color.", b)) } // Add a newline between the color output and the actual output c.Ui.Say("") log.Printf("Build debug mode: %v", cfgDebug) log.Printf("Force build: %v", cfgForce) log.Printf("On error: %v", cfgOnError) // Set the debug and force mode and prepare all the builds for _, b := range builds { log.Printf("Preparing build: %s", b.Name()) b.SetDebug(cfgDebug) b.SetForce(cfgForce) b.SetOnError(cfgOnError) warnings, err := b.Prepare() if err != nil { c.Ui.Error(err.Error()) return 1 } if len(warnings) > 0 { ui := buildUis[b.Name()] ui.Say(fmt.Sprintf("Warnings for build '%s':\n", b.Name())) for _, warning := range warnings { ui.Say(fmt.Sprintf("* %s", warning)) } ui.Say("") } } // Run all the builds in parallel and wait for them to complete var interruptWg, wg sync.WaitGroup interrupted := false var artifacts = struct { sync.RWMutex m map[string][]packer.Artifact }{m: make(map[string][]packer.Artifact)} errors := make(map[string]error) for _, b := range builds { // Increment the waitgroup so we wait for this item to finish properly wg.Add(1) // Handle interrupts for this build sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, os.Interrupt) defer signal.Stop(sigCh) go func(b packer.Build) { <-sigCh interruptWg.Add(1) defer interruptWg.Done() interrupted = true log.Printf("Stopping build: %s", b.Name()) b.Cancel() log.Printf("Build cancelled: %s", b.Name()) }(b) // Run the build in a goroutine go func(b packer.Build) { defer wg.Done() name := b.Name() log.Printf("Starting build run: %s", name) ui := buildUis[name] runArtifacts, err := b.Run(ui, c.Cache) if err != nil { ui.Error(fmt.Sprintf("Build '%s' errored: %s", name, err)) errors[name] = err } else { ui.Say(fmt.Sprintf("Build '%s' finished.", name)) artifacts.Lock() artifacts.m[name] = runArtifacts artifacts.Unlock() } }(b) if cfgDebug { log.Printf("Debug enabled, so waiting for build to finish: %s", b.Name()) wg.Wait() } if !cfgParallel { log.Printf("Parallelization disabled, waiting for build to finish: %s", b.Name()) wg.Wait() } if interrupted { log.Println("Interrupted, not going to start any more builds.") break } } // Wait for both the builds to complete and the interrupt handler, // if it is interrupted. log.Printf("Waiting on builds to complete...") wg.Wait() log.Printf("Builds completed. Waiting on interrupt barrier...") interruptWg.Wait() if interrupted { c.Ui.Say("Cleanly cancelled builds after being interrupted.") return 1 } if len(errors) > 0 { c.Ui.Machine("error-count", strconv.FormatInt(int64(len(errors)), 10)) c.Ui.Error("\n==> Some builds didn't complete successfully and had errors:") for name, err := range errors { // Create a UI for the machine readable stuff to be targetted ui := &packer.TargettedUi{ Target: name, Ui: c.Ui, } ui.Machine("error", err.Error()) c.Ui.Error(fmt.Sprintf("--> %s: %s", name, err)) } } if len(artifacts.m) > 0 { c.Ui.Say("\n==> Builds finished. The artifacts of successful builds are:") for name, buildArtifacts := range artifacts.m { // Create a UI for the machine readable stuff to be targetted ui := &packer.TargettedUi{ Target: name, Ui: c.Ui, } // Machine-readable helpful ui.Machine("artifact-count", strconv.FormatInt(int64(len(buildArtifacts)), 10)) for i, artifact := range buildArtifacts { var message bytes.Buffer fmt.Fprintf(&message, "--> %s: ", name) if artifact != nil { fmt.Fprint(&message, artifact.String()) } else { fmt.Fprint(&message, "<nothing>") } iStr := strconv.FormatInt(int64(i), 10) if artifact != nil { ui.Machine("artifact", iStr, "builder-id", artifact.BuilderId()) ui.Machine("artifact", iStr, "id", artifact.Id()) ui.Machine("artifact", iStr, "string", artifact.String()) files := artifact.Files() ui.Machine("artifact", iStr, "files-count", strconv.FormatInt(int64(len(files)), 10)) for fi, file := range files { fiStr := strconv.FormatInt(int64(fi), 10) ui.Machine("artifact", iStr, "file", fiStr, file) } } else { ui.Machine("artifact", iStr, "nil") } ui.Machine("artifact", iStr, "end") c.Ui.Say(message.String()) } } } else { c.Ui.Say("\n==> Builds finished but no artifacts were created.") } if len(errors) > 0 { // If any errors occurred, exit with a non-zero exit status return 1 } return 0 }
func (c *InspectCommand) Run(args []string) int { flags := c.Meta.FlagSet("inspect", FlagSetNone) flags.Usage = func() { c.Ui.Say(c.Help()) } if err := flags.Parse(args); err != nil { return 1 } args = flags.Args() if len(args) != 1 { flags.Usage() return 1 } // Parse the template tpl, err := template.ParseFile(args[0]) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err)) return 1 } // Convenience... ui := c.Ui // Description if tpl.Description != "" { ui.Say("Description:\n") ui.Say(tpl.Description + "\n") } // Variables if len(tpl.Variables) == 0 { ui.Say("Variables:\n") ui.Say(" <No variables>") } else { requiredHeader := false for k, v := range tpl.Variables { if v.Required { if !requiredHeader { requiredHeader = true ui.Say("Required variables:\n") } ui.Machine("template-variable", k, v.Default, "1") ui.Say(" " + k) } } if requiredHeader { ui.Say("") } ui.Say("Optional variables and their defaults:\n") keys := make([]string, 0, len(tpl.Variables)) max := 0 for k := range tpl.Variables { keys = append(keys, k) if len(k) > max { max = len(k) } } sort.Strings(keys) for _, k := range keys { v := tpl.Variables[k] if v.Required { continue } padding := strings.Repeat(" ", max-len(k)) output := fmt.Sprintf(" %s%s = %s", k, padding, v.Default) ui.Machine("template-variable", k, v.Default, "0") ui.Say(output) } } ui.Say("") // Builders ui.Say("Builders:\n") if len(tpl.Builders) == 0 { ui.Say(" <No builders>") } else { keys := make([]string, 0, len(tpl.Builders)) max := 0 for k := range tpl.Builders { keys = append(keys, k) if len(k) > max { max = len(k) } } sort.Strings(keys) for _, k := range keys { v := tpl.Builders[k] padding := strings.Repeat(" ", max-len(k)) output := fmt.Sprintf(" %s%s", k, padding) if v.Name != v.Type { output = fmt.Sprintf("%s (%s)", output, v.Type) } ui.Machine("template-builder", k, v.Type) ui.Say(output) } } ui.Say("") // Provisioners ui.Say("Provisioners:\n") if len(tpl.Provisioners) == 0 { ui.Say(" <No provisioners>") } else { for _, v := range tpl.Provisioners { ui.Machine("template-provisioner", v.Type) ui.Say(fmt.Sprintf(" %s", v.Type)) } } ui.Say("\nNote: If your build names contain user variables or template\n" + "functions such as 'timestamp', these are processed at build time,\n" + "and therefore only show in their raw form here.") return 0 }
func (c *PushCommand) Run(args []string) int { var token string var message string var name string var create bool f := c.Meta.FlagSet("push", FlagSetVars) f.Usage = func() { c.Ui.Error(c.Help()) } f.StringVar(&token, "token", "", "token") f.StringVar(&message, "m", "", "message") f.StringVar(&message, "message", "", "message") f.StringVar(&name, "name", "", "name") f.BoolVar(&create, "create", false, "create (deprecated)") if err := f.Parse(args); err != nil { return 1 } if message != "" { c.Ui.Say("[DEPRECATED] -m/-message is deprecated and will be removed in a future Packer release") } args = f.Args() if len(args) != 1 { f.Usage() return 1 } // Print deprecations if create { c.Ui.Error(fmt.Sprintf("The '-create' option is now the default and is\n" + "longer used. It will be removed in the next version.")) } // Parse the template tpl, err := template.ParseFile(args[0]) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err)) return 1 } // Get the core core, err := c.Meta.Core(tpl) if err != nil { c.Ui.Error(err.Error()) return 1 } push := core.Template.Push // If we didn't pass name from the CLI, use the template if name == "" { name = push.Name } // Validate some things if name == "" { c.Ui.Error(fmt.Sprintf( "The 'push' section must be specified in the template with\n" + "at least the 'name' option set. Alternatively, you can pass the\n" + "name parameter from the CLI.")) return 1 } if !reName.MatchString(name) { c.Ui.Error(errInvalidName.Error()) return 1 } // Determine our token if token == "" { token = push.Token } // Build our client defer func() { c.client = nil }() c.client = atlas.DefaultClient() if push.Address != "" { c.client, err = atlas.NewClient(push.Address) if err != nil { c.Ui.Error(fmt.Sprintf( "Error setting up API client: %s", err)) return 1 } } if token != "" { c.client.Token = token } // Build the archiving options var opts archive.ArchiveOpts opts.Include = push.Include opts.Exclude = push.Exclude opts.VCS = push.VCS opts.Extra = map[string]string{ archiveTemplateEntry: args[0], } // Determine the path we're archiving. This logic is a bit complicated // as there are three possibilities: // // 1.) BaseDir is an absolute path, just use that. // // 2.) BaseDir is empty, so we use the directory of the template. // // 3.) BaseDir is relative, so we use the path relative to the directory // of the template. // path := push.BaseDir if path == "" || !filepath.IsAbs(path) { tplPath, err := filepath.Abs(args[0]) if err != nil { c.Ui.Error(fmt.Sprintf("Error determining path to archive: %s", err)) return 1 } tplPath = filepath.Dir(tplPath) if path != "" { tplPath = filepath.Join(tplPath, path) } path, err = filepath.Abs(tplPath) if err != nil { c.Ui.Error(fmt.Sprintf("Error determining path to archive: %s", err)) return 1 } } // Find the Atlas post-processors, if possible var atlasPPs []*template.PostProcessor for _, list := range tpl.PostProcessors { for _, pp := range list { if pp.Type == "atlas" { atlasPPs = append(atlasPPs, pp) } } } // Build the upload options var uploadOpts uploadOpts uploadOpts.Slug = name uploadOpts.Builds = make(map[string]*uploadBuildInfo) for _, b := range tpl.Builders { info := &uploadBuildInfo{Type: b.Type} // Determine if we're artifacting this build for _, pp := range atlasPPs { if !pp.Skip(b.Name) { info.Artifact = true break } } uploadOpts.Builds[b.Name] = info } // Add the upload metadata metadata := make(map[string]interface{}) if message != "" { metadata["message"] = message } metadata["template"] = tpl.RawContents metadata["template_name"] = filepath.Base(args[0]) uploadOpts.Metadata = metadata // Warn about builds not having post-processors. var badBuilds []string for name, b := range uploadOpts.Builds { if b.Artifact { continue } badBuilds = append(badBuilds, name) } if len(badBuilds) > 0 { c.Ui.Error(fmt.Sprintf( "Warning! One or more of the builds in this template does not\n"+ "have an Atlas post-processor. Artifacts from this template will\n"+ "not appear in the Atlas artifact registry.\n\n"+ "This is just a warning. Atlas will still build your template\n"+ "and assume other post-processors are sending the artifacts where\n"+ "they need to go.\n\n"+ "Builds: %s\n\n", strings.Join(badBuilds, ", "))) } // Start the archiving process r, err := archive.CreateArchive(path, &opts) if err != nil { c.Ui.Error(fmt.Sprintf("Error archiving: %s", err)) return 1 } defer r.Close() // Start the upload process doneCh, uploadErrCh, err := c.upload(r, &uploadOpts) if err != nil { c.Ui.Error(fmt.Sprintf("Error starting upload: %s", err)) return 1 } // Make a ctrl-C channel sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, os.Interrupt) defer signal.Stop(sigCh) err = nil select { case err = <-uploadErrCh: err = fmt.Errorf("Error uploading: %s", err) case <-sigCh: err = fmt.Errorf("Push cancelled from Ctrl-C") case <-doneCh: } if err != nil { c.Ui.Error(err.Error()) return 1 } c.Ui.Say(fmt.Sprintf("Push successful to '%s'", name)) return 0 }
func (c *ValidateCommand) Run(args []string) int { var cfgSyntaxOnly bool flags := c.Meta.FlagSet("validate", FlagSetBuildFilter|FlagSetVars) flags.Usage = func() { c.Ui.Say(c.Help()) } flags.BoolVar(&cfgSyntaxOnly, "syntax-only", false, "check syntax only") if err := flags.Parse(args); err != nil { return 1 } args = flags.Args() if len(args) != 1 { flags.Usage() return 1 } // Parse the template tpl, err := template.ParseFile(args[0]) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err)) return 1 } // If we're only checking syntax, then we're done already if cfgSyntaxOnly { c.Ui.Say("Syntax-only check passed. Everything looks okay.") return 0 } // Get the core core, err := c.Meta.Core(tpl) if err != nil { c.Ui.Error(err.Error()) return 1 } errs := make([]error, 0) warnings := make(map[string][]string) // Get the builds we care about buildNames := c.Meta.BuildNames(core) builds := make([]packer.Build, 0, len(buildNames)) for _, n := range buildNames { b, err := core.Build(n) if err != nil { c.Ui.Error(fmt.Sprintf( "Failed to initialize build '%s': %s", n, err)) return 1 } builds = append(builds, b) } // Check the configuration of all builds for _, b := range builds { log.Printf("Preparing build: %s", b.Name()) warns, err := b.Prepare() if len(warns) > 0 { warnings[b.Name()] = warns } if err != nil { errs = append(errs, fmt.Errorf("Errors validating build '%s'. %s", b.Name(), err)) } } if len(errs) > 0 { c.Ui.Error("Template validation failed. Errors are shown below.\n") for i, err := range errs { c.Ui.Error(err.Error()) if (i + 1) < len(errs) { c.Ui.Error("") } } return 1 } if len(warnings) > 0 { c.Ui.Say("Template validation succeeded, but there were some warnings.") c.Ui.Say("These are ONLY WARNINGS, and Packer will attempt to build the") c.Ui.Say("template despite them, but they should be paid attention to.\n") for build, warns := range warnings { c.Ui.Say(fmt.Sprintf("Warnings for build '%s':\n", build)) for _, warning := range warns { c.Ui.Say(fmt.Sprintf("* %s", warning)) } } return 0 } c.Ui.Say("Template validated successfully.") return 0 }