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