diff --git a/.gitignore b/.gitignore index 8c04d8bc1..544efd320 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ drone.sublime-workspace *.out *.rice-box.go +cmd/cmd client/client server/server debian/drone/usr diff --git a/client/command/build.go b/client/command/build.go deleted file mode 100644 index 8f70454d9..000000000 --- a/client/command/build.go +++ /dev/null @@ -1,23 +0,0 @@ -package main - -import ( - "github.com/codegangsta/cli" -) - -// NewBuildCommand returns the CLI command for "build". -func NewBuildCommand() cli.Command { - return cli.Command{ - Name: "build", - Usage: "run a local build", - Flags: []cli.Flag{}, - Action: func(c *cli.Context) { - buildCommandFunc(c) - }, - } -} - -// buildCommandFunc executes the "build" command. -func buildCommandFunc(c *cli.Context) error { - - return nil -} diff --git a/client/command/command b/client/command/command deleted file mode 100755 index 676797512..000000000 Binary files a/client/command/command and /dev/null differ diff --git a/cmd/build.go b/cmd/build.go new file mode 100644 index 000000000..1e69359ce --- /dev/null +++ b/cmd/build.go @@ -0,0 +1,164 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "time" + + "github.com/drone/drone/shared/build" + "github.com/drone/drone/shared/build/docker" + "github.com/drone/drone/shared/build/log" + "github.com/drone/drone/shared/build/repo" + "github.com/drone/drone/shared/build/script" + + "github.com/codegangsta/cli" +) + +const EXIT_STATUS = 1 + +// NewBuildCommand returns the CLI command for "build". +func NewBuildCommand() cli.Command { + return cli.Command{ + Name: "build", + Usage: "run a local build", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "i", + Value: "", + Usage: "identify file injected in the container", + }, + cli.StringFlag{ + Name: "p", + Value: "false", + Usage: "runs drone build in a privileged container", + }, + }, + Action: func(c *cli.Context) { + buildCommandFunc(c) + }, + } +} + +// buildCommandFunc executes the "build" command. +func buildCommandFunc(c *cli.Context) { + var privileged = c.Bool("p") + var identity = c.String("i") + var path string + + // the path is provided as an optional argument that + // will otherwise default to $PWD/.drone.yml + if len(c.Args()) > 0 { + path = c.Args()[0] + } + + switch len(path) { + case 0: + path, _ = os.Getwd() + path = filepath.Join(path, ".drone.yml") + default: + path = filepath.Clean(path) + path, _ = filepath.Abs(path) + path = filepath.Join(path, ".drone.yml") + } + + // this configures the default Docker logging levels, + // and suffix and prefix values. + log.SetPrefix("\033[2m[DRONE] ") + log.SetSuffix("\033[0m\n") + log.SetOutput(os.Stdout) + log.SetPriority(log.LOG_DEBUG) //LOG_NOTICE + docker.Logging = false + + var exit, _ = run(path, identity, privileged) + os.Exit(exit) +} + +func run(path, identity string, privileged bool) (int, error) { + dockerClient := docker.New() + + // parse the Drone yml file + s, err := script.ParseBuildFile(path) + if err != nil { + log.Err(err.Error()) + return EXIT_STATUS, err + } + + // remove deploy & publish sections + // for now, until I fix bug + s.Publish = nil + s.Deploy = nil + + // get the repository root directory + dir := filepath.Dir(path) + code := repo.Repo{ + Name: filepath.Base(dir), + Branch: "HEAD", // should we do this? + Path: dir, + } + + // does the local repository match the + // $GOPATH/src/{package} pattern? This is + // important so we know the target location + // where the code should be copied inside + // the container. + if gopath, ok := getRepoPath(dir); ok { + code.Dir = gopath + + } else if gopath, ok := getGoPath(dir); ok { + // in this case we found a GOPATH and + // reverse engineered the package path + code.Dir = gopath + + } else { + // otherwise just use directory name + code.Dir = filepath.Base(dir) + } + + // this is where the code gets uploaded to the container + // TODO move this code to the build package + code.Dir = filepath.Join("/var/cache/drone/src", filepath.Clean(code.Dir)) + + // ssh key to import into container + var key []byte + if len(identity) != 0 { + key, err = ioutil.ReadFile(identity) + if err != nil { + fmt.Printf("[Error] Could not find or read identity file %s\n", identity) + return EXIT_STATUS, err + } + } + + // loop through and create builders + builder := build.New(dockerClient) + builder.Build = s + builder.Repo = &code + builder.Key = key + builder.Stdout = os.Stdout + // TODO ADD THIS BACK + builder.Timeout = 300 * time.Minute + builder.Privileged = privileged + + // execute the build + if err := builder.Run(); err != nil { + log.Errf("Error executing build: %s", err.Error()) + return EXIT_STATUS, err + } + + fmt.Printf("\nDrone Build Results \033[90m(%s)\033[0m\n", dir) + + // loop through and print results + + build := builder.Build + res := builder.BuildState + duration := time.Duration(res.Finished - res.Started) + switch { + case builder.BuildState.ExitCode == 0: + fmt.Printf(" \033[32m\u2713\033[0m %v \033[90m(%v)\033[0m\n", build.Name, humanizeDuration(duration*time.Second)) + case builder.BuildState.ExitCode != 0: + fmt.Printf(" \033[31m\u2717\033[0m %v \033[90m(%v)\033[0m\n", build.Name, humanizeDuration(duration*time.Second)) + } + + return builder.BuildState.ExitCode, nil +} diff --git a/client/command/client.go b/cmd/client.go similarity index 100% rename from client/command/client.go rename to cmd/client.go diff --git a/client/command/disable.go b/cmd/disable.go similarity index 100% rename from client/command/disable.go rename to cmd/disable.go diff --git a/client/command/enable.go b/cmd/enable.go similarity index 100% rename from client/command/enable.go rename to cmd/enable.go diff --git a/client/command/handle.go b/cmd/handle.go similarity index 100% rename from client/command/handle.go rename to cmd/handle.go diff --git a/client/command/main.go b/cmd/main.go similarity index 94% rename from client/command/main.go rename to cmd/main.go index 31889ccee..af56396b3 100644 --- a/client/command/main.go +++ b/cmd/main.go @@ -13,6 +13,7 @@ func main() { app.EnableBashCompletion = true app.Commands = []cli.Command{ + NewBuildCommand(), NewEnableCommand(), NewDisableCommand(), NewRestartCommand(), diff --git a/client/command/restart.go b/cmd/restart.go similarity index 94% rename from client/command/restart.go rename to cmd/restart.go index df607dfff..29ec5561d 100644 --- a/client/command/restart.go +++ b/cmd/restart.go @@ -10,7 +10,7 @@ import ( func NewRestartCommand() cli.Command { return cli.Command{ Name: "restart", - Usage: "restarts a build", + Usage: "restarts a build on the server", Flags: []cli.Flag{}, Action: func(c *cli.Context) { handle(c, restartCommandFunc) diff --git a/cmd/util.go b/cmd/util.go new file mode 100644 index 000000000..a5f628ca1 --- /dev/null +++ b/cmd/util.go @@ -0,0 +1,90 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + "time" +) + +// getGoPath checks the source codes absolute path +// in reference to the host operating system's GOPATH +// to correctly determine the code's package path. This +// is Go-specific, since Go code must exist in +// $GOPATH/src/github.com/{owner}/{name} +func getGoPath(dir string) (string, bool) { + path := os.Getenv("GOPATH") + if len(path) == 0 { + return "", false + } + // append src to the GOPATH, since + // the code will be stored in the src dir + path = filepath.Join(path, "src") + if !filepath.HasPrefix(dir, path) { + return "", false + } + + // remove the prefix from the directory + // this should leave us with the go package name + return dir[len(path):], true +} + +var gopathExp = regexp.MustCompile("./src/(github.com/[^/]+/[^/]+|bitbucket.org/[^/]+/[^/]+|code.google.com/[^/]+/[^/]+)") + +// getRepoPath checks the source codes absolute path +// on the host operating system in an attempt +// to correctly determine the code's package path. This +// is Go-specific, since Go code must exist in +// $GOPATH/src/github.com/{owner}/{name} +func getRepoPath(dir string) (path string, ok bool) { + // let's get the package directory based + // on the path in the host OS + indexes := gopathExp.FindStringIndex(dir) + if len(indexes) == 0 { + return + } + + index := indexes[len(indexes)-1] + + // if the dir is /home/ubuntu/go/src/github.com/foo/bar + // the index will start at /src/github.com/foo/bar. + // We'll need to strip "/src/" which is where the + // magic number 5 comes from. + index = strings.LastIndex(dir, "/src/") + return dir[index+5:], true +} + +// getGitOrigin checks the .git origin in an attempt +// to correctly determine the code's package path. This +// is Go-specific, since Go code must exist in +// $GOPATH/src/github.com/{owner}/{name} +func getGitOrigin(dir string) (path string, ok bool) { + // TODO + return +} + +// prints the time as a human readable string +func humanizeDuration(d time.Duration) string { + if seconds := int(d.Seconds()); seconds < 1 { + return "Less than a second" + } else if seconds < 60 { + return fmt.Sprintf("%d seconds", seconds) + } else if minutes := int(d.Minutes()); minutes == 1 { + return "About a minute" + } else if minutes < 60 { + return fmt.Sprintf("%d minutes", minutes) + } else if hours := int(d.Hours()); hours == 1 { + return "About an hour" + } else if hours < 48 { + return fmt.Sprintf("%d hours", hours) + } else if hours < 24*7*2 { + return fmt.Sprintf("%d days", hours/24) + } else if hours < 24*30*3 { + return fmt.Sprintf("%d weeks", hours/24/7) + } else if hours < 24*365*2 { + return fmt.Sprintf("%d months", hours/24/30) + } + return fmt.Sprintf("%f years", d.Hours()/24/365) +} diff --git a/client/command/whoami.go b/cmd/whoami.go similarity index 100% rename from client/command/whoami.go rename to cmd/whoami.go diff --git a/shared/build/build_test.go b/shared/build/build_test.go index ebd45934c..df35938bd 100644 --- a/shared/build/build_test.go +++ b/shared/build/build_test.go @@ -558,6 +558,7 @@ func TestWriteBuildScript(t *testing.T) { f := buildfile.New() f.WriteEnv("CI", "true") f.WriteEnv("DRONE", "true") + f.WriteEnv("DRONE_REMOTE", "git://github.com/drone/drone.git") f.WriteEnv("DRONE_BRANCH", "master") f.WriteEnv("DRONE_COMMIT", "e7e046b35") f.WriteEnv("DRONE_PR", "123") @@ -565,6 +566,7 @@ func TestWriteBuildScript(t *testing.T) { f.WriteEnv("CI_NAME", "DRONE") f.WriteEnv("CI_BUILD_NUMBER", "e7e046b35") f.WriteEnv("CI_BUILD_URL", "") + f.WriteEnv("CI_REMOTE", "git://github.com/drone/drone.git") f.WriteEnv("CI_BRANCH", "master") f.WriteEnv("CI_PULL_REQUEST", "123") f.WriteHost("127.0.0.1")