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