github.com/askholme/packer@v0.7.2-0.20140924152349-70d9566a6852/command/build/command.go (about) 1 package build 2 3 import ( 4 "bytes" 5 "flag" 6 "fmt" 7 cmdcommon "github.com/mitchellh/packer/common/command" 8 "github.com/mitchellh/packer/packer" 9 "log" 10 "os" 11 "os/signal" 12 "strconv" 13 "strings" 14 "sync" 15 ) 16 17 type Command byte 18 19 func (Command) Help() string { 20 return strings.TrimSpace(helpText) 21 } 22 23 func (c Command) Run(env packer.Environment, args []string) int { 24 var cfgColor, cfgDebug, cfgForce, cfgParallel bool 25 buildOptions := new(cmdcommon.BuildOptions) 26 27 cmdFlags := flag.NewFlagSet("build", flag.ContinueOnError) 28 cmdFlags.Usage = func() { env.Ui().Say(c.Help()) } 29 cmdFlags.BoolVar(&cfgColor, "color", true, "enable or disable color") 30 cmdFlags.BoolVar(&cfgDebug, "debug", false, "debug mode for builds") 31 cmdFlags.BoolVar(&cfgForce, "force", false, "force a build if artifacts exist") 32 cmdFlags.BoolVar(&cfgParallel, "parallel", true, "enable/disable parallelization") 33 cmdcommon.BuildOptionFlags(cmdFlags, buildOptions) 34 if err := cmdFlags.Parse(args); err != nil { 35 return 1 36 } 37 38 args = cmdFlags.Args() 39 if len(args) != 1 { 40 cmdFlags.Usage() 41 return 1 42 } 43 44 if err := buildOptions.Validate(); err != nil { 45 env.Ui().Error(err.Error()) 46 env.Ui().Error("") 47 env.Ui().Error(c.Help()) 48 return 1 49 } 50 51 userVars, err := buildOptions.AllUserVars() 52 if err != nil { 53 env.Ui().Error(fmt.Sprintf("Error compiling user variables: %s", err)) 54 env.Ui().Error("") 55 env.Ui().Error(c.Help()) 56 return 1 57 } 58 59 // Read the file into a byte array so that we can parse the template 60 log.Printf("Reading template: %s", args[0]) 61 tpl, err := packer.ParseTemplateFile(args[0], userVars) 62 if err != nil { 63 env.Ui().Error(fmt.Sprintf("Failed to parse template: %s", err)) 64 return 1 65 } 66 67 // The component finder for our builds 68 components := &packer.ComponentFinder{ 69 Builder: env.Builder, 70 Hook: env.Hook, 71 PostProcessor: env.PostProcessor, 72 Provisioner: env.Provisioner, 73 } 74 75 // Go through each builder and compile the builds that we care about 76 builds, err := buildOptions.Builds(tpl, components) 77 if err != nil { 78 env.Ui().Error(err.Error()) 79 return 1 80 } 81 82 if cfgDebug { 83 env.Ui().Say("Debug mode enabled. Builds will not be parallelized.") 84 } 85 86 // Compile all the UIs for the builds 87 colors := [5]packer.UiColor{ 88 packer.UiColorGreen, 89 packer.UiColorCyan, 90 packer.UiColorMagenta, 91 packer.UiColorYellow, 92 packer.UiColorBlue, 93 } 94 95 buildUis := make(map[string]packer.Ui) 96 for i, b := range builds { 97 var ui packer.Ui 98 ui = env.Ui() 99 if cfgColor { 100 ui = &packer.ColoredUi{ 101 Color: colors[i%len(colors)], 102 Ui: env.Ui(), 103 } 104 } 105 106 buildUis[b.Name()] = ui 107 ui.Say(fmt.Sprintf("%s output will be in this color.", b.Name())) 108 } 109 110 // Add a newline between the color output and the actual output 111 env.Ui().Say("") 112 113 log.Printf("Build debug mode: %v", cfgDebug) 114 log.Printf("Force build: %v", cfgForce) 115 116 // Set the debug and force mode and prepare all the builds 117 for _, b := range builds { 118 log.Printf("Preparing build: %s", b.Name()) 119 b.SetDebug(cfgDebug) 120 b.SetForce(cfgForce) 121 122 warnings, err := b.Prepare() 123 if err != nil { 124 env.Ui().Error(err.Error()) 125 return 1 126 } 127 if len(warnings) > 0 { 128 ui := buildUis[b.Name()] 129 ui.Say(fmt.Sprintf("Warnings for build '%s':\n", b.Name())) 130 for _, warning := range warnings { 131 ui.Say(fmt.Sprintf("* %s", warning)) 132 } 133 ui.Say("") 134 } 135 } 136 137 // Run all the builds in parallel and wait for them to complete 138 var interruptWg, wg sync.WaitGroup 139 interrupted := false 140 artifacts := make(map[string][]packer.Artifact) 141 errors := make(map[string]error) 142 for _, b := range builds { 143 // Increment the waitgroup so we wait for this item to finish properly 144 wg.Add(1) 145 146 // Handle interrupts for this build 147 sigCh := make(chan os.Signal, 1) 148 signal.Notify(sigCh, os.Interrupt) 149 defer signal.Stop(sigCh) 150 go func(b packer.Build) { 151 <-sigCh 152 interruptWg.Add(1) 153 defer interruptWg.Done() 154 interrupted = true 155 156 log.Printf("Stopping build: %s", b.Name()) 157 b.Cancel() 158 log.Printf("Build cancelled: %s", b.Name()) 159 }(b) 160 161 // Run the build in a goroutine 162 go func(b packer.Build) { 163 defer wg.Done() 164 165 name := b.Name() 166 log.Printf("Starting build run: %s", name) 167 ui := buildUis[name] 168 runArtifacts, err := b.Run(ui, env.Cache()) 169 170 if err != nil { 171 ui.Error(fmt.Sprintf("Build '%s' errored: %s", name, err)) 172 errors[name] = err 173 } else { 174 ui.Say(fmt.Sprintf("Build '%s' finished.", name)) 175 artifacts[name] = runArtifacts 176 } 177 }(b) 178 179 if cfgDebug { 180 log.Printf("Debug enabled, so waiting for build to finish: %s", b.Name()) 181 wg.Wait() 182 } 183 184 if !cfgParallel { 185 log.Printf("Parallelization disabled, waiting for build to finish: %s", b.Name()) 186 wg.Wait() 187 } 188 189 if interrupted { 190 log.Println("Interrupted, not going to start any more builds.") 191 break 192 } 193 } 194 195 // Wait for both the builds to complete and the interrupt handler, 196 // if it is interrupted. 197 log.Printf("Waiting on builds to complete...") 198 wg.Wait() 199 200 log.Printf("Builds completed. Waiting on interrupt barrier...") 201 interruptWg.Wait() 202 203 if interrupted { 204 env.Ui().Say("Cleanly cancelled builds after being interrupted.") 205 return 1 206 } 207 208 if len(errors) > 0 { 209 env.Ui().Machine("error-count", strconv.FormatInt(int64(len(errors)), 10)) 210 211 env.Ui().Error("\n==> Some builds didn't complete successfully and had errors:") 212 for name, err := range errors { 213 // Create a UI for the machine readable stuff to be targetted 214 ui := &packer.TargettedUi{ 215 Target: name, 216 Ui: env.Ui(), 217 } 218 219 ui.Machine("error", err.Error()) 220 221 env.Ui().Error(fmt.Sprintf("--> %s: %s", name, err)) 222 } 223 } 224 225 if len(artifacts) > 0 { 226 env.Ui().Say("\n==> Builds finished. The artifacts of successful builds are:") 227 for name, buildArtifacts := range artifacts { 228 // Create a UI for the machine readable stuff to be targetted 229 ui := &packer.TargettedUi{ 230 Target: name, 231 Ui: env.Ui(), 232 } 233 234 // Machine-readable helpful 235 ui.Machine("artifact-count", strconv.FormatInt(int64(len(buildArtifacts)), 10)) 236 237 for i, artifact := range buildArtifacts { 238 var message bytes.Buffer 239 fmt.Fprintf(&message, "--> %s: ", name) 240 241 if artifact != nil { 242 fmt.Fprintf(&message, artifact.String()) 243 } else { 244 fmt.Fprint(&message, "<nothing>") 245 } 246 247 iStr := strconv.FormatInt(int64(i), 10) 248 if artifact != nil { 249 ui.Machine("artifact", iStr, "builder-id", artifact.BuilderId()) 250 ui.Machine("artifact", iStr, "id", artifact.Id()) 251 ui.Machine("artifact", iStr, "string", artifact.String()) 252 253 files := artifact.Files() 254 ui.Machine("artifact", 255 iStr, 256 "files-count", strconv.FormatInt(int64(len(files)), 10)) 257 for fi, file := range files { 258 fiStr := strconv.FormatInt(int64(fi), 10) 259 ui.Machine("artifact", iStr, "file", fiStr, file) 260 } 261 } else { 262 ui.Machine("artifact", iStr, "nil") 263 } 264 265 ui.Machine("artifact", iStr, "end") 266 env.Ui().Say(message.String()) 267 } 268 } 269 } else { 270 env.Ui().Say("\n==> Builds finished but no artifacts were created.") 271 } 272 273 if len(errors) > 0 { 274 // If any errors occurred, exit with a non-zero exit status 275 return 1 276 } 277 278 return 0 279 } 280 281 func (Command) Synopsis() string { 282 return "build image(s) from template" 283 }