1
0
mirror of https://github.com/drone/drone-cli.git synced 2026-01-15 23:41:33 +01:00

initial commit

This commit is contained in:
Brad Rydzewski 2015-02-13 00:23:01 -08:00
parent a886dc3514
commit 47d0010fb2
14 changed files with 730 additions and 0 deletions

5
.drone.yml Normal file

@ -0,0 +1,5 @@
image: go
script:
- cd drone
- go build
- go test

3
.gitignore vendored

@ -22,3 +22,6 @@ _testmain.go
*.exe
*.test
*.prof
# Binary file(s)
drone/drone

226
drone/build.go Normal file

@ -0,0 +1,226 @@
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.BoolFlag{
Name: "p",
Usage: "runs drone build in a privileged container",
},
cli.BoolFlag{
Name: "deploy",
Usage: "runs drone build with deployments enabled",
},
cli.BoolFlag{
Name: "publish",
Usage: "runs drone build with publishing enabled",
},
cli.StringFlag{
Name: "docker-host",
Value: getHost(),
Usage: "docker daemon address",
},
cli.StringFlag{
Name: "docker-cert",
Value: getCert(),
Usage: "docker daemon tls certificate",
},
cli.StringFlag{
Name: "docker-key",
Value: getKey(),
Usage: "docker daemon tls key",
},
},
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 deploy = c.Bool("deploy")
var publish = c.Bool("publish")
var path string
var dockerhost = c.String("docker-host")
var dockercert = c.String("docker-cert")
var dockerkey = c.String("docker-key")
// 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, dockerhost, dockercert, dockerkey, publish, deploy, privileged)
os.Exit(exit)
}
// TODO this has gotten a bit out of hand. refactor input params
func run(path, identity, dockerhost, dockercert, dockerkey string, publish, deploy, privileged bool) (int, error) {
dockerClient, err := docker.NewHostCertFile(dockerhost, dockercert, dockerkey)
if err != nil {
log.Err(err.Error())
return EXIT_STATUS, err
}
// parse the private environment variables
envs := getParamMap("DRONE_ENV_")
// parse the Drone yml file
s, err := script.ParseBuildFile(path, envs)
if err != nil {
log.Err(err.Error())
return EXIT_STATUS, err
}
// inject private environment variables into build script
for key, val := range envs {
s.Env = append(s.Env, key+"="+val)
}
if deploy == false {
s.Deploy = nil
}
if publish == false {
s.Publish = 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
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
}
func getHost() string {
return os.Getenv("DOCKER_HOST")
}
func getCert() string {
if os.Getenv("DOCKER_CERT_PATH") != "" && os.Getenv("DOCKER_TLS_VERIFY") == "1" {
return filepath.Join(os.Getenv("DOCKER_CERT_PATH"), "cert.pem")
} else {
return ""
}
}
func getKey() string {
if os.Getenv("DOCKER_CERT_PATH") != "" && os.Getenv("DOCKER_TLS_VERIFY") == "1" {
return filepath.Join(os.Getenv("DOCKER_CERT_PATH"), "key.pem")
} else {
return ""
}
}

30
drone/delete.go Normal file

@ -0,0 +1,30 @@
package main
import (
"github.com/codegangsta/cli"
"github.com/drone/drone-go/drone"
)
// NewDeleteCommand returns the CLI command for "delete".
func NewDeleteCommand() cli.Command {
return cli.Command{
Name: "delete",
Usage: "delete a repository",
Flags: []cli.Flag{},
Action: func(c *cli.Context) {
handle(c, deleteCommandFunc)
},
}
}
// deleteCommandFunc executes the "delete" command.
func deleteCommandFunc(c *cli.Context, client *drone.Client) error {
var host, owner, name string
var args = c.Args()
if len(args) != 0 {
host, owner, name = parseRepo(args[0])
}
return client.Repos.Delete(host, owner, name)
}

30
drone/disable.go Normal file

@ -0,0 +1,30 @@
package main
import (
"github.com/codegangsta/cli"
"github.com/drone/drone-go/drone"
)
// NewDisableCommand returns the CLI command for "disable".
func NewDisableCommand() cli.Command {
return cli.Command{
Name: "disable",
Usage: "disable a repository",
Flags: []cli.Flag{},
Action: func(c *cli.Context) {
handle(c, disableCommandFunc)
},
}
}
// disableCommandFunc executes the "disable" command.
func disableCommandFunc(c *cli.Context, client *drone.Client) error {
var host, owner, name string
var args = c.Args()
if len(args) != 0 {
host, owner, name = parseRepo(args[0])
}
return client.Repos.Disable(host, owner, name)
}

30
drone/enable.go Normal file

@ -0,0 +1,30 @@
package main
import (
"github.com/codegangsta/cli"
"github.com/drone/drone-go/drone"
)
// NewEnableCommand returns the CLI command for "enable".
func NewEnableCommand() cli.Command {
return cli.Command{
Name: "enable",
Usage: "enable a repository",
Flags: []cli.Flag{},
Action: func(c *cli.Context) {
handle(c, enableCommandFunc)
},
}
}
// enableCommandFunc executes the "enable" command.
func enableCommandFunc(c *cli.Context, client *drone.Client) error {
var host, owner, name string
var args = c.Args()
if len(args) != 0 {
host, owner, name = parseRepo(args[0])
}
return client.Repos.Enable(host, owner, name)
}

32
drone/handle.go Normal file

@ -0,0 +1,32 @@
package main
import (
"os"
"github.com/codegangsta/cli"
"github.com/drone/drone-go/drone"
)
type handlerFunc func(*cli.Context, *drone.Client) error
// handle wraps the command function handlers and
// sets up the environment.
func handle(c *cli.Context, fn handlerFunc) {
var token = c.GlobalString("token")
var server = c.GlobalString("server")
// if no server url is provided we can default
// to the hosted Drone service.
if len(server) == 0 {
server = "http://test.drone.io"
}
// create the drone client
client := drone.NewClient(token, server)
// handle the function
if err := fn(c, client); err != nil {
println(err.Error())
os.Exit(1)
}
}

48
drone/keys.go Normal file

@ -0,0 +1,48 @@
package main
import (
"fmt"
"io/ioutil"
"github.com/codegangsta/cli"
"github.com/drone/drone-go/drone"
)
// NewSetKeyCommand returns the CLI command for "set-key".
func NewSetKeyCommand() cli.Command {
return cli.Command{
Name: "set-key",
Usage: "sets the SSH private key used to clone",
Flags: []cli.Flag{},
Action: func(c *cli.Context) {
handle(c, setKeyCommandFunc)
},
}
}
// setKeyCommandFunc executes the "set-key" command.
func setKeyCommandFunc(c *cli.Context, client *drone.Client) error {
var host, owner, name, path string
var args = c.Args()
if len(args) != 0 {
host, owner, name = parseRepo(args[0])
}
if len(args) == 2 {
path = args[1]
}
pub, err := ioutil.ReadFile(path)
if err != nil {
return fmt.Errorf("Could not find private RSA key %s. %s", path, err)
}
path_pub := path + ".pub"
priv, err := ioutil.ReadFile(path_pub)
if err != nil {
return fmt.Errorf("Could not find public RSA key %s. %s", path_pub, err)
}
return client.Repos.SetKey(host, owner, name, string(pub), string(priv))
}

48
drone/main.go Normal file

@ -0,0 +1,48 @@
package main
import (
"os"
"github.com/codegangsta/cli"
)
var (
// commit sha for the current build.
version string
revision string
)
func main() {
app := cli.NewApp()
app.Name = "drone"
app.Version = version
app.Usage = "command line utility"
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "t, token",
Value: "",
Usage: "server auth token",
EnvVar: "DRONE_TOKEN",
},
cli.StringFlag{
Name: "s, server",
Value: "",
Usage: "server location",
EnvVar: "DRONE_SERVER",
},
}
app.Commands = []cli.Command{
NewBuildCommand(),
NewReposCommand(),
NewStatusCommand(),
NewEnableCommand(),
NewDisableCommand(),
NewRestartCommand(),
NewWhoamiCommand(),
NewSetKeyCommand(),
NewDeleteCommand(),
}
app.Run(os.Args)
}

43
drone/repos.go Normal file

@ -0,0 +1,43 @@
package main
import (
"fmt"
"github.com/codegangsta/cli"
"github.com/drone/drone-go/drone"
)
// NewReposCommand returns the CLI command for "repos".
func NewReposCommand() cli.Command {
return cli.Command{
Name: "repos",
Usage: "lists active remote repositories",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "a, all",
Usage: "list all repositories",
},
},
Action: func(c *cli.Context) {
handle(c, reposCommandFunc)
},
}
}
// reposCommandFunc executes the "repos" command.
func reposCommandFunc(c *cli.Context, client *drone.Client) error {
repos, err := client.Repos.List()
if err != nil {
return err
}
var all = c.Bool("a")
for _, repo := range repos {
if !all && !repo.Active {
continue
}
fmt.Printf("%s/%s/%s\n", repo.Host, repo.Owner, repo.Name)
}
return nil
}

39
drone/restart.go Normal file

@ -0,0 +1,39 @@
package main
import (
"github.com/codegangsta/cli"
"github.com/drone/drone-go/drone"
)
// NewRestartCommand returns the CLI command for "restart".
func NewRestartCommand() cli.Command {
return cli.Command{
Name: "restart",
Usage: "restarts a build on the server",
Flags: []cli.Flag{},
Action: func(c *cli.Context) {
handle(c, restartCommandFunc)
},
}
}
// restartCommandFunc executes the "restart" command.
func restartCommandFunc(c *cli.Context, client *drone.Client) error {
var host, owner, repo, branch, sha string
var args = c.Args()
if len(args) != 0 {
host, owner, repo = parseRepo(args[0])
}
switch len(args) {
case 2:
branch = "master"
sha = args[1]
case 3, 4, 5:
branch = args[1]
sha = args[2]
}
return client.Commits.Rebuild(host, owner, repo, branch, sha)
}

52
drone/status.go Normal file

@ -0,0 +1,52 @@
package main
import (
"fmt"
"github.com/codegangsta/cli"
"github.com/drone/drone-go/drone"
)
// NewStatusCommand returns the CLI command for "status".
func NewStatusCommand() cli.Command {
return cli.Command{
Name: "status",
Usage: "display a repository build status",
Flags: []cli.Flag{
cli.StringFlag{
Name: "b, branch",
Usage: "branch to display",
},
},
Action: func(c *cli.Context) {
handle(c, statusCommandFunc)
},
}
}
// statusCommandFunc executes the "status" command.
func statusCommandFunc(c *cli.Context, client *drone.Client) error {
var host, owner, repo, branch string
var args = c.Args()
if len(args) != 0 {
host, owner, repo = parseRepo(args[0])
}
if c.IsSet("branch") {
branch = c.String("branch")
} else {
branch = "master"
}
builds, err := client.Commits.ListBranch(host, owner, repo, branch)
if err != nil {
return err
} else if len(builds) == 0 {
return nil
}
var build = builds[len(builds)-1]
fmt.Printf("%s\t%s\t%s\t%s\t%v", build.Status, build.ShaShort(), build.Timestamp, build.Author, build.Message)
return nil
}

112
drone/util.go Normal file

@ -0,0 +1,112 @@
package main
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"
)
func parseRepo(str string) (host, owner, repo string) {
var parts = strings.Split(str, "/")
if len(parts) != 3 {
return
}
host = parts[0]
owner = parts[1]
repo = parts[2]
return
}
// 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
}
// GetRepoMap returns a map of enivronment variables that
// should be injected into the .drone.yml
func getParamMap(prefix string) map[string]string {
envs := map[string]string{}
for _, item := range os.Environ() {
env := strings.SplitN(item, "=", 2)
if len(env) != 2 {
continue
}
key := env[0]
val := env[1]
if strings.HasPrefix(key, prefix) {
envs[strings.TrimPrefix(key, prefix)] = val
}
}
return envs
}
// 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)
}

32
drone/whoami.go Normal file

@ -0,0 +1,32 @@
package main
import (
"fmt"
"github.com/codegangsta/cli"
"github.com/drone/drone-go/drone"
)
// NewWhoamiCommand returns the CLI command for "whoami".
func NewWhoamiCommand() cli.Command {
return cli.Command{
Name: "whoami",
Usage: "outputs the current user",
Flags: []cli.Flag{},
Action: func(c *cli.Context) {
handle(c, whoamiCommandFunc)
},
}
}
// whoamiCommandFunc communicates with the server and echoes
// the currently authenticated user.
func whoamiCommandFunc(c *cli.Context, client *drone.Client) error {
user, err := client.Users.GetCurrent()
if err != nil {
return err
}
fmt.Println(user.Login)
return nil
}