github.com/unclejack/drone@v0.2.1-0.20140918182345-831b034aa33b/cmd/drone/drone.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "flag" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "sync" 11 "time" 12 13 "github.com/drone/drone/pkg/build" 14 "github.com/drone/drone/pkg/build/docker" 15 "github.com/drone/drone/pkg/build/log" 16 "github.com/drone/drone/pkg/build/repo" 17 "github.com/drone/drone/pkg/build/script" 18 19 "launchpad.net/goyaml" 20 ) 21 22 var ( 23 // identity file (id_rsa) that will be injected 24 // into the container if specified 25 identity = flag.String("identity", "", "") 26 27 // runs Drone in parallel mode if True 28 parallel = flag.Bool("parallel", false, "") 29 30 // build will timeout after N milliseconds. 31 // this will default to 500 minutes (6 hours) 32 timeout = flag.Duration("timeout", 300*time.Minute, "") 33 34 // build will run in a privileged container 35 privileged = flag.Bool("privileged", false, "") 36 37 // runs Drone with verbose output if True 38 verbose = flag.Bool("v", false, "") 39 40 // displays the help / usage if True 41 help = flag.Bool("h", false, "") 42 43 // version number, currently deterined by the 44 // git revision number (sha) 45 version string 46 ) 47 48 func init() { 49 // default logging 50 log.SetPrefix("\033[2m[DRONE] ") 51 log.SetSuffix("\033[0m\n") 52 log.SetOutput(os.Stdout) 53 log.SetPriority(log.LOG_NOTICE) 54 } 55 56 func main() { 57 // Parse the input parameters 58 flag.Usage = usage 59 flag.Parse() 60 61 if *help { 62 flag.Usage() 63 os.Exit(0) 64 } 65 66 if *verbose { 67 log.SetPriority(log.LOG_DEBUG) 68 } 69 70 // Must speicify a command 71 args := flag.Args() 72 if len(args) == 0 { 73 flag.Usage() 74 os.Exit(0) 75 } 76 77 switch { 78 // run drone build assuming the current 79 // working directory contains the drone.yml 80 case args[0] == "build" && len(args) == 1: 81 path, _ := os.Getwd() 82 path = filepath.Join(path, ".drone.yml") 83 run(path) 84 85 // run drone build where the path to the 86 // source directory is provided 87 case args[0] == "build" && len(args) == 2: 88 path := args[1] 89 path = filepath.Clean(path) 90 path, _ = filepath.Abs(path) 91 path = filepath.Join(path, ".drone.yml") 92 run(path) 93 94 // run drone vet where the path to the 95 // source directory is provided 96 case args[0] == "vet" && len(args) == 2: 97 path := args[1] 98 path = filepath.Clean(path) 99 path, _ = filepath.Abs(path) 100 path = filepath.Join(path, ".drone.yml") 101 vet(path) 102 103 // run drone vet assuming the current 104 // working directory contains the drone.yml 105 case args[0] == "vet" && len(args) == 1: 106 path, _ := os.Getwd() 107 path = filepath.Join(path, ".drone.yml") 108 vet(path) 109 110 // print the version / revision number 111 case args[0] == "version" && len(args) == 1: 112 println(version) 113 114 // print the help message 115 case args[0] == "help" && len(args) == 1: 116 flag.Usage() 117 } 118 119 os.Exit(0) 120 } 121 122 func vet(path string) { 123 // parse the Drone yml file 124 script, err := script.ParseBuildFile(path) 125 if err != nil { 126 log.Err(err.Error()) 127 os.Exit(1) 128 return 129 } 130 131 // print the Drone yml as parsed 132 out, _ := goyaml.Marshal(script) 133 log.Noticef("parsed yaml:\n%s", string(out)) 134 } 135 136 func run(path string) { 137 dockerClient := docker.New() 138 139 // parse the Drone yml file 140 s, err := script.ParseBuildFile(path) 141 if err != nil { 142 log.Err(err.Error()) 143 os.Exit(1) 144 return 145 } 146 147 // get the repository root directory 148 dir := filepath.Dir(path) 149 code := repo.Repo{ 150 Name: filepath.Base(dir), 151 Branch: "HEAD", // should we do this? 152 Path: dir, 153 } 154 155 // does the local repository match the 156 // $GOPATH/src/{package} pattern? This is 157 // important so we know the target location 158 // where the code should be copied inside 159 // the container. 160 if gopath, ok := getRepoPath(dir); ok { 161 code.Dir = gopath 162 163 } else if gopath, ok := getGoPath(dir); ok { 164 // in this case we found a GOPATH and 165 // reverse engineered the package path 166 code.Dir = gopath 167 168 } else { 169 // otherwise just use directory name 170 code.Dir = filepath.Base(dir) 171 } 172 173 // this is where the code gets uploaded to the container 174 // TODO move this code to the build package 175 code.Dir = filepath.Join("/var/cache/drone/src", filepath.Clean(code.Dir)) 176 177 // track all build results 178 var builders []*build.Builder 179 180 // ssh key to import into container 181 var key []byte 182 if len(*identity) != 0 { 183 key, err = ioutil.ReadFile(*identity) 184 if err != nil { 185 fmt.Printf("[Error] Could not find or read identity file %s\n", *identity) 186 os.Exit(1) 187 return 188 } 189 } 190 191 builds := []*script.Build{s} 192 193 // loop through and create builders 194 for _, b := range builds { //script.Builds { 195 builder := build.New(dockerClient) 196 builder.Build = b 197 builder.Repo = &code 198 builder.Key = key 199 builder.Stdout = os.Stdout 200 builder.Timeout = *timeout 201 builder.Privileged = *privileged 202 203 if *parallel == true { 204 var buf bytes.Buffer 205 builder.Stdout = &buf 206 } 207 208 builders = append(builders, builder) 209 } 210 211 switch *parallel { 212 case false: 213 runSequential(builders) 214 case true: 215 runParallel(builders) 216 } 217 218 // if in parallel mode, print out the buffer 219 // if we had a failure 220 for _, builder := range builders { 221 if builder.BuildState.ExitCode == 0 { 222 continue 223 } 224 225 if buf, ok := builder.Stdout.(*bytes.Buffer); ok { 226 log.Noticef("printing stdout for failed build %s", builder.Build.Name) 227 println(buf.String()) 228 } 229 } 230 231 // this exit code is initially 0 and will 232 // be set to an error code if any of the 233 // builds fail. 234 var exit int 235 236 fmt.Printf("\nDrone Build Results \033[90m(%v)\033[0m\n", len(builders)) 237 238 // loop through and print results 239 for _, builder := range builders { 240 build := builder.Build 241 res := builder.BuildState 242 duration := time.Duration(res.Finished - res.Started) 243 switch { 244 case builder.BuildState.ExitCode == 0: 245 fmt.Printf(" \033[32m\u2713\033[0m %v \033[90m(%v)\033[0m\n", build.Name, humanizeDuration(duration*time.Second)) 246 case builder.BuildState.ExitCode != 0: 247 fmt.Printf(" \033[31m\u2717\033[0m %v \033[90m(%v)\033[0m\n", build.Name, humanizeDuration(duration*time.Second)) 248 exit = builder.BuildState.ExitCode 249 } 250 } 251 252 os.Exit(exit) 253 } 254 255 func runSequential(builders []*build.Builder) { 256 // loop through and execute each build 257 for _, builder := range builders { 258 if err := builder.Run(); err != nil { 259 log.Errf("Error executing build: %s", err.Error()) 260 os.Exit(1) 261 } 262 } 263 } 264 265 func runParallel(builders []*build.Builder) { 266 // spawn four worker goroutines 267 var wg sync.WaitGroup 268 for _, builder := range builders { 269 // Increment the WaitGroup counter 270 wg.Add(1) 271 // Launch a goroutine to run the build 272 go func(builder *build.Builder) { 273 defer wg.Done() 274 builder.Run() 275 }(builder) 276 time.Sleep(500 * time.Millisecond) // get weird iptables failures unless we sleep. 277 } 278 279 // wait for the workers to finish 280 wg.Wait() 281 } 282 283 var usage = func() { 284 fmt.Println(`Drone is a tool for building and testing code in Docker containers. 285 286 Usage: 287 288 drone command [arguments] 289 290 The commands are: 291 292 build build and test the repository 293 version print the version number 294 vet validate the yaml configuration file 295 296 -v runs drone with verbose output 297 -h display this help and exit 298 --parallel runs drone build tasks in parallel 299 --timeout=300ms timeout build after 300 milliseconds 300 --privileged runs drone build in a privileged container 301 302 Examples: 303 drone build builds the source in the pwd 304 drone build /path/to/repo builds the source repository 305 306 Use "drone help [command]" for more information about a command. 307 `) 308 }