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  }