diff --git a/Dockerfile b/Dockerfile index 363afd3b0..536c393f8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,4 +21,4 @@ ADD drone/drone /drone #RUN echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf ENTRYPOINT ["/drone"] -CMD ["daemon"] +CMD ["server"] diff --git a/client/client.go b/client/client.go index 33957fdb8..2def55413 100644 --- a/client/client.go +++ b/client/client.go @@ -9,6 +9,43 @@ import ( // Client is used to communicate with a Drone server. type Client interface { + // Self returns the currently authenticated user. + Self() (*model.User, error) + + // User returns a user by login. + User(string) (*model.User, error) + + // UserList returns a list of all registered users. + UserList() ([]*model.User, error) + + // UserPost creates a new user account. + UserPost(*model.User) (*model.User, error) + + // UserPatch updates a user account. + UserPatch(*model.User) (*model.User, error) + + // UserDel deletes a user account. + UserDel(string) error + + // // UserFeed returns the user's activity feed. + // UserFeed() ([]*Activity, error) + + // Repo returns a repository by name. + Repo(string, string) (*model.Repo, error) + + // RepoList returns a list of all repositories to which + // the user has explicit access in the host system. + RepoList() ([]*model.Repo, error) + + // RepoPost activates a repository. + RepoPost(string, string) (*model.Repo, error) + + // RepoPatch updates a repository. + RepoPatch(*model.Repo) (*model.Repo, error) + + // RepoDel deletes a repository. + RepoDel(string, string) error + // Sign returns a cryptographic signature for the input string. Sign(string, string, []byte) ([]byte, error) @@ -18,6 +55,38 @@ type Client interface { // SecretDel deletes a named repository secret. SecretDel(string, string, string) error + // Build returns a repository build by number. + Build(string, string, int) (*model.Build, error) + + // BuildLast returns the latest repository build by branch. + // An empty branch will result in the default branch. + BuildLast(string, string, string) (*model.Build, error) + + // BuildList returns a list of recent builds for the + // the specified repository. + BuildList(string, string) ([]*model.Build, error) + + // BuildStart re-starts a stopped build. + BuildStart(string, string, int) (*model.Build, error) + + // BuildStop stops the specified running job for given build. + BuildStop(string, string, int, int) error + + // BuildFork re-starts a stopped build with a new build number, + // preserving the prior history. + BuildFork(string, string, int) (*model.Build, error) + + // BuildLogs returns the build logs for the specified job. + BuildLogs(string, string, int, int) (io.ReadCloser, error) + + // Deploy triggers a deployment for an existing build using the + // specified target environment. + Deploy(string, string, int, string) (*model.Build, error) + + // + // queue functions + // + // Pull pulls work from the server queue. Pull(os, arch string) (*queue.Work, error) diff --git a/client/client_impl.go b/client/client_impl.go index 24dfdc6f6..3b40b9e19 100644 --- a/client/client_impl.go +++ b/client/client_impl.go @@ -74,6 +74,164 @@ func NewClientTokenTLS(uri, token string, c *tls.Config) Client { return &client{auther, uri} } +// Self returns the currently authenticated user. +func (c *client) Self() (*model.User, error) { + out := new(model.User) + uri := fmt.Sprintf(pathSelf, c.base) + err := c.get(uri, out) + return out, err +} + +// User returns a user by login. +func (c *client) User(login string) (*model.User, error) { + out := new(model.User) + uri := fmt.Sprintf(pathUser, c.base, login) + err := c.get(uri, out) + return out, err +} + +// UserList returns a list of all registered users. +func (c *client) UserList() ([]*model.User, error) { + var out []*model.User + uri := fmt.Sprintf(pathUsers, c.base) + err := c.get(uri, &out) + return out, err +} + +// UserPost creates a new user account. +func (c *client) UserPost(in *model.User) (*model.User, error) { + out := new(model.User) + uri := fmt.Sprintf(pathUsers, c.base) + err := c.post(uri, in, out) + return out, err +} + +// UserPatch updates a user account. +func (c *client) UserPatch(in *model.User) (*model.User, error) { + out := new(model.User) + uri := fmt.Sprintf(pathUser, c.base, in.Login) + err := c.patch(uri, in, out) + return out, err +} + +// UserDel deletes a user account. +func (c *client) UserDel(login string) error { + uri := fmt.Sprintf(pathUser, c.base, login) + err := c.delete(uri) + return err +} + +// Repo returns a repository by name. +func (c *client) Repo(owner string, name string) (*model.Repo, error) { + out := new(model.Repo) + uri := fmt.Sprintf(pathRepo, c.base, owner, name) + err := c.get(uri, out) + return out, err +} + +// RepoList returns a list of all repositories to which +// the user has explicit access in the host system. +func (c *client) RepoList() ([]*model.Repo, error) { + var out []*model.Repo + uri := fmt.Sprintf(pathRepos, c.base) + err := c.get(uri, &out) + return out, err +} + +// RepoPost activates a repository. +func (c *client) RepoPost(owner string, name string) (*model.Repo, error) { + out := new(model.Repo) + uri := fmt.Sprintf(pathRepo, c.base, owner, name) + err := c.post(uri, nil, out) + return out, err +} + +// RepoPatch updates a repository. +func (c *client) RepoPatch(in *model.Repo) (*model.Repo, error) { + out := new(model.Repo) + uri := fmt.Sprintf(pathRepo, c.base, in.Owner, in.Name) + err := c.patch(uri, in, out) + return out, err +} + +// RepoDel deletes a repository. +func (c *client) RepoDel(owner, name string) error { + uri := fmt.Sprintf(pathRepo, c.base, owner, name) + err := c.delete(uri) + return err +} + +// Build returns a repository build by number. +func (c *client) Build(owner, name string, num int) (*model.Build, error) { + out := new(model.Build) + uri := fmt.Sprintf(pathBuild, c.base, owner, name, num) + err := c.get(uri, out) + return out, err +} + +// Build returns the latest repository build by branch. +func (c *client) BuildLast(owner, name, branch string) (*model.Build, error) { + out := new(model.Build) + uri := fmt.Sprintf(pathBuild, c.base, owner, name, "latest") + if len(branch) != 0 { + uri += "?branch=" + branch + } + err := c.get(uri, out) + return out, err +} + +// BuildList returns a list of recent builds for the +// the specified repository. +func (c *client) BuildList(owner, name string) ([]*model.Build, error) { + var out []*model.Build + uri := fmt.Sprintf(pathBuilds, c.base, owner, name) + err := c.get(uri, &out) + return out, err +} + +// BuildStart re-starts a stopped build. +func (c *client) BuildStart(owner, name string, num int) (*model.Build, error) { + out := new(model.Build) + uri := fmt.Sprintf(pathBuild, c.base, owner, name, num) + err := c.post(uri, nil, out) + return out, err +} + +// BuildStop cancels the running job. +func (c *client) BuildStop(owner, name string, num, job int) error { + uri := fmt.Sprintf(pathJob, c.base, owner, name, num, job) + err := c.delete(uri) + return err +} + +// BuildFork re-starts a stopped build with a new build number, +// preserving the prior history. +func (c *client) BuildFork(owner, name string, num int) (*model.Build, error) { + out := new(model.Build) + uri := fmt.Sprintf(pathBuild+"?fork=true", c.base, owner, name, num) + err := c.post(uri, nil, out) + return out, err +} + +// BuildLogs returns the build logs for the specified job. +func (c *client) BuildLogs(owner, name string, num, job int) (io.ReadCloser, error) { + uri := fmt.Sprintf(pathLog, c.base, owner, name, num, job) + return stream(c.client, uri, "GET", nil, nil) +} + +// Deploy triggers a deployment for an existing build using the +// specified target environment. +func (c *client) Deploy(owner, name string, num int, env string) (*model.Build, error) { + out := new(model.Build) + val := url.Values{} + val.Set("fork", "true") + val.Set("event", "deployment") + val.Set("deploy_to", env) + uri := fmt.Sprintf(pathBuild+"?"+val.Encode(), c.base, owner, name, num) + err := c.post(uri, nil, out) + return out, err +} + // SecretPost create or updates a repository secret. func (c *client) SecretPost(owner, name string, secret *model.Secret) error { uri := fmt.Sprintf(pathSecrets, c.base, owner, name) diff --git a/drone/agent/exec.go b/drone/agent/exec.go index 87983ce25..3e03af759 100644 --- a/drone/agent/exec.go +++ b/drone/agent/exec.go @@ -90,12 +90,7 @@ func (r *pipeline) run() error { } trans := []compiler.Transform{ - builtin.NewCloneOp("git", true), - builtin.NewCacheOp( - "plugins/cache:latest", - "/var/lib/drone/cache/"+w.Repo.FullName, - false, - ), + builtin.NewCloneOp(w.Repo.Kind, true), builtin.NewSecretOp(w.Build.Event, secrets), builtin.NewNormalizeOp(r.config.namespace), builtin.NewWorkspaceOp("/drone", "/drone/src/github.com/"+w.Repo.FullName), diff --git a/drone/build.go b/drone/build.go new file mode 100644 index 000000000..534fef1b7 --- /dev/null +++ b/drone/build.go @@ -0,0 +1,15 @@ +package main + +import "github.com/codegangsta/cli" + +var buildCmd = cli.Command{ + Name: "build", + Usage: "manage builds", + Subcommands: []cli.Command{ + buildListCmd, + buildLastCmd, + buildInfoCmd, + buildStopCmd, + buildStartCmd, + }, +} diff --git a/drone/build_info.go b/drone/build_info.go new file mode 100644 index 000000000..b63a8fd69 --- /dev/null +++ b/drone/build_info.go @@ -0,0 +1,66 @@ +package main + +import ( + "log" + "os" + "strconv" + "text/template" + + "github.com/codegangsta/cli" +) + +var buildInfoCmd = cli.Command{ + Name: "info", + Usage: "show build details", + Action: func(c *cli.Context) { + if err := buildInfo(c); err != nil { + log.Fatalln(err) + } + }, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "format", + Usage: "format output", + Value: tmplBuildInfo, + }, + }, +} + +func buildInfo(c *cli.Context) error { + repo := c.Args().First() + owner, name, err := parseRepo(repo) + if err != nil { + return err + } + number, err := strconv.Atoi(c.Args().Get(1)) + if err != nil { + return err + } + + client, err := newClient(c) + if err != nil { + return err + } + + build, err := client.Build(owner, name, number) + if err != nil { + return err + } + + tmpl, err := template.New("_").Parse(c.String("format")) + if err != nil { + return err + } + return tmpl.Execute(os.Stdout, build) +} + +// template for build information +var tmplBuildInfo = `Number: {{ .Number }} +Status: {{ .Status }} +Event: {{ .Event }} +Commit: {{ .Commit }} +Branch: {{ .Branch }} +Ref: {{ .Ref }} +Message: {{ .Message }} +Author: {{ .Author }} +` diff --git a/drone/build_last.go b/drone/build_last.go new file mode 100644 index 000000000..1bf431fd0 --- /dev/null +++ b/drone/build_last.go @@ -0,0 +1,55 @@ +package main + +import ( + "log" + "os" + "text/template" + + "github.com/codegangsta/cli" +) + +var buildLastCmd = cli.Command{ + Name: "last", + Usage: "show latest build details", + Action: func(c *cli.Context) { + if err := buildLast(c); err != nil { + log.Fatalln(err) + } + }, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "format", + Usage: "format output", + Value: tmplBuildInfo, + }, + cli.StringFlag{ + Name: "branch", + Usage: "branch name", + Value: "master", + }, + }, +} + +func buildLast(c *cli.Context) error { + repo := c.Args().First() + owner, name, err := parseRepo(repo) + if err != nil { + return err + } + + client, err := newClient(c) + if err != nil { + return err + } + + build, err := client.BuildLast(owner, name, c.String("branch")) + if err != nil { + return err + } + + tmpl, err := template.New("_").Parse(c.String("format")) + if err != nil { + return err + } + return tmpl.Execute(os.Stdout, build) +} diff --git a/drone/build_list.go b/drone/build_list.go new file mode 100644 index 000000000..f3c16a188 --- /dev/null +++ b/drone/build_list.go @@ -0,0 +1,101 @@ +package main + +import ( + "log" + "os" + "text/template" + + "github.com/codegangsta/cli" +) + +var buildListCmd = cli.Command{ + Name: "list", + Usage: "show build history", + Action: func(c *cli.Context) { + if err := buildList(c); err != nil { + log.Fatalln(err) + } + }, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "format", + Usage: "format output", + Value: tmplBuildList, + }, + cli.StringFlag{ + Name: "branch", + Usage: "branch filter", + }, + cli.StringFlag{ + Name: "event", + Usage: "event filter", + }, + cli.StringFlag{ + Name: "status", + Usage: "status filter", + }, + cli.IntFlag{ + Name: "limit", + Usage: "limit the list size", + Value: 25, + }, + }, +} + +func buildList(c *cli.Context) error { + repo := c.Args().First() + owner, name, err := parseRepo(repo) + if err != nil { + return err + } + + client, err := newClient(c) + if err != nil { + return err + } + + builds, err := client.BuildList(owner, name) + if err != nil { + return err + } + + tmpl, err := template.New("_").Parse(c.String("format") + "\n") + if err != nil { + return err + } + + branch := c.String("branch") + event := c.String("event") + status := c.String("status") + limit := c.Int("limit") + + var count int + for _, build := range builds { + if count >= limit { + break + } + if branch != "" && build.Branch != branch { + continue + } + if event != "" && build.Event != event { + continue + } + if status != "" && build.Status != status { + continue + } + tmpl.Execute(os.Stdout, build) + count++ + } + return nil +} + +// template for build list information +var tmplBuildList = "\x1b[33mBuild #{{ .Number }} \x1b[0m" + ` +Status: {{ .Status }} +Event: {{ .Event }} +Commit: {{ .Commit }} +Branch: {{ .Branch }} +Ref: {{ .Ref }} +Author: {{ .Author }} {{ if .Email }}<{{.Email}}>{{ end }} +Message: {{ .Message }} +` diff --git a/drone/build_start.go b/drone/build_start.go new file mode 100644 index 000000000..7a871f0cb --- /dev/null +++ b/drone/build_start.go @@ -0,0 +1,56 @@ +package main + +import ( + "fmt" + "log" + "strconv" + + "github.com/codegangsta/cli" + "github.com/drone/drone/model" +) + +var buildStartCmd = cli.Command{ + Name: "start", + Usage: "start a build", + Action: func(c *cli.Context) { + if err := buildStart(c); err != nil { + log.Fatalln(err) + } + }, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "fork", + Usage: "fork the build", + }, + }, +} + +func buildStart(c *cli.Context) (err error) { + repo := c.Args().First() + owner, name, err := parseRepo(repo) + if err != nil { + return err + } + number, err := strconv.Atoi(c.Args().Get(1)) + if err != nil { + return err + } + + client, err := newClient(c) + if err != nil { + return err + } + + var build *model.Build + if c.Bool("fork") { + build, err = client.BuildStart(owner, name, number) + } else { + build, err = client.BuildFork(owner, name, number) + } + if err != nil { + return err + } + + fmt.Printf("Starting build %s/%s#%d\n", owner, name, build.Number) + return nil +} diff --git a/drone/build_stop.go b/drone/build_stop.go new file mode 100644 index 000000000..6d81b4f0f --- /dev/null +++ b/drone/build_stop.go @@ -0,0 +1,48 @@ +package main + +import ( + "fmt" + "log" + "strconv" + + "github.com/codegangsta/cli" +) + +var buildStopCmd = cli.Command{ + Name: "stop", + Usage: "stop a build", + Action: func(c *cli.Context) { + if err := buildStop(c); err != nil { + log.Fatalln(err) + } + }, +} + +func buildStop(c *cli.Context) (err error) { + repo := c.Args().First() + owner, name, err := parseRepo(repo) + if err != nil { + return err + } + number, err := strconv.Atoi(c.Args().Get(1)) + if err != nil { + return err + } + job, _ := strconv.Atoi(c.Args().Get(2)) + if job == 0 { + job = 1 + } + + client, err := newClient(c) + if err != nil { + return err + } + + err = client.BuildStop(owner, name, number, job) + if err != nil { + return err + } + + fmt.Printf("Stopping build %s/%s#%d.%d\n", owner, name, number, job) + return nil +} diff --git a/drone/compile.go b/drone/compile.go new file mode 100644 index 000000000..06ab7d0f9 --- /dev/null +++ b/drone/compile.go @@ -0,0 +1 @@ +package main diff --git a/drone/deploy.go b/drone/deploy.go new file mode 100644 index 000000000..8b389bf85 --- /dev/null +++ b/drone/deploy.go @@ -0,0 +1,80 @@ +package main + +import ( + "fmt" + "html/template" + "log" + "os" + "strconv" + + "github.com/codegangsta/cli" + "github.com/drone/drone-go/drone" +) + +var deployCmd = cli.Command{ + Name: "deploy", + Usage: "deploy code", + Action: func(c *cli.Context) { + if err := deploy(c); err != nil { + log.Fatalln(err) + } + }, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "format", + Usage: "format output", + Value: tmplDeployInfo, + }, + }, +} + +func deploy(c *cli.Context) error { + repo := c.Args().First() + owner, name, err := parseRepo(repo) + if err != nil { + return err + } + number, err := strconv.Atoi(c.Args().Get(1)) + if err != nil { + return err + } + + client, err := newClient(c) + if err != nil { + return err + } + + build, err := client.Build(owner, name, number) + if err != nil { + return err + } + if build.Event == drone.EventPull { + return fmt.Errorf("Cannot deploy a pull request") + } + env := c.Args().Get(2) + if env == "" { + return fmt.Errorf("Please specify the target environment (ie production)") + } + + deploy, err := client.Deploy(owner, name, number, env) + if err != nil { + return err + } + + tmpl, err := template.New("_").Parse(c.String("format")) + if err != nil { + return err + } + return tmpl.Execute(os.Stdout, deploy) +} + +// template for deployment information +var tmplDeployInfo = `Number: {{ .Number }} +Status: {{ .Status }} +Commit: {{ .Commit }} +Branch: {{ .Branch }} +Ref: {{ .Ref }} +Message: {{ .Message }} +Author: {{ .Author }} +Target: {{ .Deploy }} +` diff --git a/drone/exec.go b/drone/exec.go new file mode 100644 index 000000000..06ab7d0f9 --- /dev/null +++ b/drone/exec.go @@ -0,0 +1 @@ +package main diff --git a/drone/info.go b/drone/info.go new file mode 100644 index 000000000..e67ce2095 --- /dev/null +++ b/drone/info.go @@ -0,0 +1,49 @@ +package main + +import ( + "log" + "os" + "text/template" + + "github.com/codegangsta/cli" +) + +var infoCmd = cli.Command{ + Name: "info", + Usage: "should information about the current user", + Action: func(c *cli.Context) { + if err := info(c); err != nil { + log.Fatalln(err) + } + }, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "format", + Usage: "format output", + Value: tmplUserInfo, + }, + }, +} + +func info(c *cli.Context) error { + client, err := newClient(c) + if err != nil { + return err + } + + user, err := client.Self() + if err != nil { + return err + } + + tmpl, err := template.New("_").Parse(c.String("format") + "\n") + if err != nil { + return err + } + + return tmpl.Execute(os.Stdout, user) +} + +// template for user information +var tmplInfo = `User: {{ .Login }} +Email: {{ .Email }}` diff --git a/drone/drone.go b/drone/main.go similarity index 87% rename from drone/drone.go rename to drone/main.go index 310b1fc1b..fb97b6190 100644 --- a/drone/drone.go +++ b/drone/main.go @@ -21,22 +21,25 @@ func main() { 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{ agent.AgentCmd, - DaemonCmd, - SignCmd, - SecretCmd, + buildCmd, + deployCmd, + infoCmd, + secretCmd, + serverCmd, + signCmd, + repoCmd, + userCmd, } app.Run(os.Args) diff --git a/drone/repo.go b/drone/repo.go new file mode 100644 index 000000000..68d8812ab --- /dev/null +++ b/drone/repo.go @@ -0,0 +1,14 @@ +package main + +import "github.com/codegangsta/cli" + +var repoCmd = cli.Command{ + Name: "repo", + Usage: "manage repositories", + Subcommands: []cli.Command{ + repoListCmd, + repoInfoCmd, + repoAddCmd, + repoRemoveCmd, + }, +} diff --git a/drone/repo_add.go b/drone/repo_add.go new file mode 100644 index 000000000..e879f5505 --- /dev/null +++ b/drone/repo_add.go @@ -0,0 +1,37 @@ +package main + +import ( + "fmt" + "log" + + "github.com/codegangsta/cli" +) + +var repoAddCmd = cli.Command{ + Name: "add", + Usage: "add a repository", + Action: func(c *cli.Context) { + if err := repoRemove(c); err != nil { + log.Fatalln(err) + } + }, +} + +func repoAdd(c *cli.Context) error { + repo := c.Args().First() + owner, name, err := parseRepo(repo) + if err != nil { + return err + } + + client, err := newClient(c) + if err != nil { + return err + } + + if _, err := client.RepoPost(owner, name); err != nil { + return err + } + fmt.Printf("Successfully activated repository %s/%s\n", owner, name) + return nil +} diff --git a/drone/repo_info.go b/drone/repo_info.go new file mode 100644 index 000000000..bbaf0137b --- /dev/null +++ b/drone/repo_info.go @@ -0,0 +1,58 @@ +package main + +import ( + "log" + "os" + "text/template" + + "github.com/codegangsta/cli" +) + +var repoInfoCmd = cli.Command{ + Name: "info", + Usage: "show repository details", + Action: func(c *cli.Context) { + if err := repoInfo(c); err != nil { + log.Fatalln(err) + } + }, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "format", + Usage: "format output", + Value: tmplRepoInfo, + }, + }, +} + +func repoInfo(c *cli.Context) error { + arg := c.Args().First() + owner, name, err := parseRepo(arg) + if err != nil { + return err + } + + client, err := newClient(c) + if err != nil { + return err + } + + repo, err := client.Repo(owner, name) + if err != nil { + return err + } + + tmpl, err := template.New("_").Parse(c.String("format")) + if err != nil { + return err + } + return tmpl.Execute(os.Stdout, repo) +} + +// template for repo information +var tmplRepoInfo = `Owner: {{ .Owner }} +Repo: {{ .Name }} +Type: {{ .Kind }} +Private: {{ .IsPrivate }} +Remote: {{ .Clone }} +` diff --git a/drone/repo_list.go b/drone/repo_list.go new file mode 100644 index 000000000..c29fcea10 --- /dev/null +++ b/drone/repo_list.go @@ -0,0 +1,59 @@ +package main + +import ( + "log" + "os" + "text/template" + + "github.com/codegangsta/cli" +) + +var repoListCmd = cli.Command{ + Name: "ls", + Usage: "list all repos", + Action: func(c *cli.Context) { + if err := repoList(c); err != nil { + log.Fatalln(err) + } + }, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "format", + Usage: "format output", + Value: tmplRepoList, + }, + cli.StringFlag{ + Name: "org", + Usage: "filter by organization", + }, + }, +} + +func repoList(c *cli.Context) error { + client, err := newClient(c) + if err != nil { + return err + } + + repos, err := client.RepoList() + if err != nil || len(repos) == 0 { + return err + } + + tmpl, err := template.New("_").Parse(c.String("format") + "\n") + if err != nil { + return err + } + + org := c.String("org") + for _, repo := range repos { + if org != "" && org != repo.Owner { + continue + } + tmpl.Execute(os.Stdout, repo) + } + return nil +} + +// template for repository list items +var tmplRepoList = `{{ .FullName }}` diff --git a/drone/repo_rm.go b/drone/repo_rm.go new file mode 100644 index 000000000..a2aa3c71f --- /dev/null +++ b/drone/repo_rm.go @@ -0,0 +1,37 @@ +package main + +import ( + "fmt" + "log" + + "github.com/codegangsta/cli" +) + +var repoRemoveCmd = cli.Command{ + Name: "rm", + Usage: "remove a repository", + Action: func(c *cli.Context) { + if err := repoRemove(c); err != nil { + log.Fatalln(err) + } + }, +} + +func repoRemove(c *cli.Context) error { + repo := c.Args().First() + owner, name, err := parseRepo(repo) + if err != nil { + return err + } + + client, err := newClient(c) + if err != nil { + return err + } + + if err := client.RepoDel(owner, name); err != nil { + return err + } + fmt.Printf("Successfully removed repository %s/%s\n", owner, name) + return nil +} diff --git a/drone/secert_add.go b/drone/secert_add.go new file mode 100644 index 000000000..2840e6256 --- /dev/null +++ b/drone/secert_add.go @@ -0,0 +1,80 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "strings" + + "github.com/codegangsta/cli" + "github.com/drone/drone/model" +) + +var secretAddCmd = cli.Command{ + Name: "add", + Usage: "adds a secret", + ArgsUsage: "[repo] [key] [value]", + Action: func(c *cli.Context) { + if err := secretAdd(c); err != nil { + log.Fatalln(err) + } + }, + Flags: []cli.Flag{ + cli.StringSliceFlag{ + Name: "event", + Usage: "inject the secret for these event types", + Value: &cli.StringSlice{ + model.EventPush, + model.EventTag, + model.EventDeploy, + }, + }, + cli.StringSliceFlag{ + Name: "image", + Usage: "inject the secret for these image types", + Value: &cli.StringSlice{}, + }, + }, +} + +func secretAdd(c *cli.Context) error { + repo := c.Args().First() + owner, name, err := parseRepo(repo) + if err != nil { + return err + } + + tail := c.Args().Tail() + if len(tail) != 2 { + cli.ShowSubcommandHelp(c) + return nil + } + + secret := &model.Secret{} + secret.Name = tail[0] + secret.Value = tail[1] + secret.Images = c.StringSlice("image") + secret.Events = c.StringSlice("event") + + if len(secret.Images) == 0 { + return fmt.Errorf("Please specify the --image parameter") + } + + // allow secret value to come from a file when prefixed with the @ symbol, + // similar to curl conventions. + if strings.HasPrefix(secret.Value, "@") { + path := secret.Value[1:] + out, ferr := ioutil.ReadFile(path) + if ferr != nil { + return ferr + } + secret.Value = string(out) + } + + client, err := newClient(c) + if err != nil { + return err + } + + return client.SecretPost(owner, name, secret) +} diff --git a/drone/secret.go b/drone/secret.go index dbf5cf603..97c351177 100644 --- a/drone/secret.go +++ b/drone/secret.go @@ -1,103 +1,12 @@ package main -import ( - "fmt" - "log" +import "github.com/codegangsta/cli" - "github.com/drone/drone/model" - - "github.com/codegangsta/cli" -) - -// SecretCmd is the exported command for managing secrets. -var SecretCmd = cli.Command{ +var secretCmd = cli.Command{ Name: "secret", Usage: "manage secrets", Subcommands: []cli.Command{ - // Secret Add - { - Name: "add", - Usage: "add a secret", - ArgsUsage: "[repo] [key] [value]", - Action: func(c *cli.Context) { - if err := secretAdd(c); err != nil { - log.Fatalln(err) - } - }, - Flags: []cli.Flag{ - cli.StringSliceFlag{ - Name: "event", - Usage: "inject the secret for these event types", - Value: &cli.StringSlice{ - model.EventPush, - model.EventTag, - model.EventDeploy, - }, - }, - cli.StringSliceFlag{ - Name: "image", - Usage: "inject the secret for these image types", - Value: &cli.StringSlice{}, - }, - }, - }, - // Secret Delete - { - Name: "rm", - Usage: "remove a secret", - Action: func(c *cli.Context) { - if err := secretDel(c); err != nil { - log.Fatalln(err) - } - }, - }, + secretAddCmd, + secretRemoveCmd, }, } - -func secretAdd(c *cli.Context) error { - - repo := c.Args().First() - owner, name, err := parseRepo(repo) - if err != nil { - return err - } - - tail := c.Args().Tail() - if len(tail) != 2 { - cli.ShowSubcommandHelp(c) - return nil - } - - secret := &model.Secret{} - secret.Name = tail[0] - secret.Value = tail[1] - secret.Images = c.StringSlice("image") - secret.Events = c.StringSlice("event") - - if len(secret.Images) == 0 { - return fmt.Errorf("Please specify the --image parameter") - } - - client, err := newClient(c) - if err != nil { - return err - } - - return client.SecretPost(owner, name, secret) -} - -func secretDel(c *cli.Context) error { - repo := c.Args().First() - owner, name, err := parseRepo(repo) - if err != nil { - return err - } - - secret := c.Args().Get(1) - - client, err := newClient(c) - if err != nil { - return err - } - return client.SecretDel(owner, name, secret) -} diff --git a/drone/secret_info.go b/drone/secret_info.go new file mode 100644 index 000000000..06ab7d0f9 --- /dev/null +++ b/drone/secret_info.go @@ -0,0 +1 @@ +package main diff --git a/drone/secret_list.go b/drone/secret_list.go new file mode 100644 index 000000000..06ab7d0f9 --- /dev/null +++ b/drone/secret_list.go @@ -0,0 +1 @@ +package main diff --git a/drone/secret_rm.go b/drone/secret_rm.go new file mode 100644 index 000000000..a68f17b76 --- /dev/null +++ b/drone/secret_rm.go @@ -0,0 +1,33 @@ +package main + +import ( + "log" + + "github.com/codegangsta/cli" +) + +var secretRemoveCmd = cli.Command{ + Name: "rm", + Usage: "remove a secret", + Action: func(c *cli.Context) { + if err := secretRemove(c); err != nil { + log.Fatalln(err) + } + }, +} + +func secretRemove(c *cli.Context) error { + repo := c.Args().First() + owner, name, err := parseRepo(repo) + if err != nil { + return err + } + + secret := c.Args().Get(1) + + client, err := newClient(c) + if err != nil { + return err + } + return client.SecretDel(owner, name, secret) +} diff --git a/drone/daemon.go b/drone/server.go similarity index 97% rename from drone/daemon.go rename to drone/server.go index 30d9048bb..d7027f421 100644 --- a/drone/daemon.go +++ b/drone/server.go @@ -14,12 +14,11 @@ import ( "github.com/codegangsta/cli" ) -// DaemonCmd is the exported command for starting the drone server daemon. -var DaemonCmd = cli.Command{ - Name: "daemon", +var serverCmd = cli.Command{ + Name: "server", Usage: "starts the drone server daemon", Action: func(c *cli.Context) { - if err := start(c); err != nil { + if err := server(c); err != nil { logrus.Fatal(err) } }, @@ -275,7 +274,7 @@ var DaemonCmd = cli.Command{ }, } -func start(c *cli.Context) error { +func server(c *cli.Context) error { if c.Bool("agreement.ack") == false || c.Bool("agreement.fix") == false { fmt.Println(agreement) diff --git a/drone/sign.go b/drone/sign.go index 03c9a5f08..b3b03efcb 100644 --- a/drone/sign.go +++ b/drone/sign.go @@ -7,8 +7,7 @@ import ( "github.com/codegangsta/cli" ) -// SignCmd is the exported command for signing the yaml. -var SignCmd = cli.Command{ +var signCmd = cli.Command{ Name: "sign", Usage: "creates a secure yaml file", Action: func(c *cli.Context) { diff --git a/drone/user.go b/drone/user.go new file mode 100644 index 000000000..9c5e7cf90 --- /dev/null +++ b/drone/user.go @@ -0,0 +1,14 @@ +package main + +import "github.com/codegangsta/cli" + +var userCmd = cli.Command{ + Name: "user", + Usage: "manage users", + Subcommands: []cli.Command{ + userListCmd, + userInfoCmd, + userAddCmd, + userRemoveCmd, + }, +} diff --git a/drone/user_add.go b/drone/user_add.go new file mode 100644 index 000000000..f68b6919c --- /dev/null +++ b/drone/user_add.go @@ -0,0 +1,35 @@ +package main + +import ( + "fmt" + "log" + + "github.com/codegangsta/cli" + "github.com/drone/drone/model" +) + +var userAddCmd = cli.Command{ + Name: "add", + Usage: "adds a user", + Action: func(c *cli.Context) { + if err := userAdd(c); err != nil { + log.Fatalln(err) + } + }, +} + +func userAdd(c *cli.Context) error { + login := c.Args().First() + + client, err := newClient(c) + if err != nil { + return err + } + + user, err := client.UserPost(&model.User{Login: login}) + if err != nil { + return err + } + fmt.Printf("Successfully added user %s\n", user.Login) + return nil +} diff --git a/drone/user_info.go b/drone/user_info.go new file mode 100644 index 000000000..0a2f0cd54 --- /dev/null +++ b/drone/user_info.go @@ -0,0 +1,54 @@ +package main + +import ( + "fmt" + "log" + "os" + "text/template" + + "github.com/codegangsta/cli" +) + +var userInfoCmd = cli.Command{ + Name: "info", + Usage: "show user details", + Action: func(c *cli.Context) { + if err := userInfo(c); err != nil { + log.Fatalln(err) + } + }, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "format", + Usage: "format output", + Value: tmplUserInfo, + }, + }, +} + +func userInfo(c *cli.Context) error { + client, err := newClient(c) + if err != nil { + return err + } + + login := c.Args().First() + if len(login) == 0 { + return fmt.Errorf("Missing or invalid user login") + } + + user, err := client.User(login) + if err != nil { + return err + } + + tmpl, err := template.New("_").Parse(c.String("format") + "\n") + if err != nil { + return err + } + return tmpl.Execute(os.Stdout, user) +} + +// template for user information +var tmplUserInfo = `User: {{ .Login }} +Email: {{ .Email }}` diff --git a/drone/user_list.go b/drone/user_list.go new file mode 100644 index 000000000..68aa69fb8 --- /dev/null +++ b/drone/user_list.go @@ -0,0 +1,50 @@ +package main + +import ( + "log" + "os" + "text/template" + + "github.com/codegangsta/cli" +) + +var userListCmd = cli.Command{ + Name: "ls", + Usage: "list all users", + Action: func(c *cli.Context) { + if err := userList(c); err != nil { + log.Fatalln(err) + } + }, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "format", + Usage: "format output", + Value: tmplUserList, + }, + }, +} + +func userList(c *cli.Context) error { + client, err := newClient(c) + if err != nil { + return err + } + + users, err := client.UserList() + if err != nil || len(users) == 0 { + return err + } + + tmpl, err := template.New("_").Parse(c.String("format") + "\n") + if err != nil { + return err + } + for _, user := range users { + tmpl.Execute(os.Stdout, user) + } + return nil +} + +// template for user list items +var tmplUserList = `{{ .Login }}` diff --git a/drone/user_rm.go b/drone/user_rm.go new file mode 100644 index 000000000..f3aa74d36 --- /dev/null +++ b/drone/user_rm.go @@ -0,0 +1,33 @@ +package main + +import ( + "fmt" + "log" + + "github.com/codegangsta/cli" +) + +var userRemoveCmd = cli.Command{ + Name: "rm", + Usage: "remove a user", + Action: func(c *cli.Context) { + if err := userRemove(c); err != nil { + log.Fatalln(err) + } + }, +} + +func userRemove(c *cli.Context) error { + login := c.Args().First() + + client, err := newClient(c) + if err != nil { + return err + } + + if err := client.UserDel(login); err != nil { + return err + } + fmt.Printf("Successfully removed user %s\n", login) + return nil +} diff --git a/engine/compiler/builtin/cache.go b/engine/compiler/builtin/cache.go deleted file mode 100644 index acdd2d6d7..000000000 --- a/engine/compiler/builtin/cache.go +++ /dev/null @@ -1,61 +0,0 @@ -package builtin - -import ( - "github.com/drone/drone/engine/runner" - "github.com/drone/drone/engine/compiler/parse" -) - -type cacheOp struct { - visitor - enable bool - plugin string - mount string -} - -// NewCacheOp returns a transformer that configures the default cache plugin. -func NewCacheOp(plugin, mount string, enable bool) Visitor { - return &cacheOp{ - mount: mount, - enable: enable, - plugin: plugin, - } -} - -func (v *cacheOp) VisitContainer(node *parse.ContainerNode) error { - if node.Type() != parse.NodeCache { - return nil - } - if len(node.Vargs) == 0 || v.enable == false { - node.Disabled = true - return nil - } - - if node.Container.Name == "" { - node.Container.Name = "cache" - } - if node.Container.Image == "" { - node.Container.Image = v.plugin - } - - // discard any other cache properties except the image name. - // everything else is discard for security reasons. - node.Container = runner.Container{ - Name: node.Container.Name, - Alias: node.Container.Alias, - Image: node.Container.Image, - Volumes: []string{ - v.mount + ":/cache", - }, - } - - // this is a hack until I can come up with a better solution. - // this copies the clone name, and appends at the end of the - // build. When it is executed a second time the build should - // have a completed status, so it knows to cache instead - // of restore. - cache := node.Root().NewCacheNode() - cache.Vargs = node.Vargs - cache.Container = node.Container - node.Root().Script = append(node.Root().Script, cache) - return nil -} \ No newline at end of file diff --git a/engine/compiler/builtin/cache_test.go b/engine/compiler/builtin/cache_test.go deleted file mode 100644 index 9d5e04a6b..000000000 --- a/engine/compiler/builtin/cache_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package builtin - -// import ( -// "testing" - -// "github.com/libcd/libcd" -// "github.com/libcd/libyaml/parse" - -// "github.com/franela/goblin" -// ) - -// func Test_cache(t *testing.T) { -// root := parse.NewRootNode() - -// g := goblin.Goblin(t) -// g.Describe("cache", func() { - -// g.It("should use default when nil", func() { -// op := NewCacheOp("plugins/cache:latest", "/tmp/cache") - -// op.VisitRoot(root) -// g.Assert(root.Cache.(*parse.ContainerNode).Container.Image).Equal("plugins/cache:latest") -// g.Assert(root.Cache.(*parse.ContainerNode).Container.Volumes[0]).Equal("/tmp/cache:/cache") -// }) - -// g.It("should use user-defined cache plugin", func() { -// op := NewCacheOp("plugins/cache:latest", "/tmp/cache") -// cache := root.NewCacheNode() -// cache.Container = libcd.Container{} -// cache.Container.Image = "custom/cacher:latest" -// root.Cache = cache - -// op.VisitRoot(root) -// g.Assert(cache.Container.Image).Equal("custom/cacher:latest") -// }) -// }) -// }