diff --git a/Makefile b/Makefile index e19043a9..a6592238 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ test: go test -cover -short ./... build: - go build -o debian/drone/usr/local/bin/drone -ldflags "-X main.revision $(SHA)" github.com/drone/drone/client + go build -o debian/drone/usr/local/bin/drone -ldflags "-X main.revision $(SHA)" github.com/drone/drone/cmd go build -o debian/drone/usr/local/bin/droned -ldflags "-X main.revision $(SHA)" github.com/drone/drone/server install: diff --git a/cmd/client.go b/client/client.go similarity index 60% rename from cmd/client.go rename to client/client.go index 690cc8a1..30c2ce7b 100644 --- a/cmd/client.go +++ b/client/client.go @@ -1,4 +1,4 @@ -package main +package client import ( "bytes" @@ -10,6 +10,27 @@ import ( "strconv" ) +type Client struct { + token string + url string + + Commits *CommitService + Repos *RepoService + Users *UserService +} + +func New(token, url string) *Client { + c := Client{ + token: token, + url: url, + } + + c.Commits = &CommitService{&c} + c.Repos = &RepoService{&c} + c.Users = &UserService{&c} + return &c +} + var ( ErrNotFound = errors.New("Not Found") ErrForbidden = errors.New("Forbidden") @@ -18,17 +39,12 @@ var ( ErrInternalServer = errors.New("Internal Server Error") ) -type Client struct { - Token string - URL string -} - -// Do submits an http.Request and parses the JSON-encoded http.Response, +// runs an http.Request and parses the JSON-encoded http.Response, // storing the result in the value pointed to by v. -func (c *Client) Do(method, path string, in, out interface{}) error { +func (c *Client) run(method, path string, in, out interface{}) error { // create the URI - uri, err := url.Parse(c.URL + path) + uri, err := url.Parse(c.url + path) if err != nil { return err } @@ -37,9 +53,9 @@ func (c *Client) Do(method, path string, in, out interface{}) error { uri.Scheme = "http" } - if len(c.Token) > 0 { + if len(c.token) > 0 { params := uri.Query() - params.Add("access_token", c.Token) + params.Add("access_token", c.token) uri.RawQuery = params.Encode() } @@ -102,3 +118,36 @@ func (c *Client) Do(method, path string, in, out interface{}) error { return nil } + +// do makes an http.Request and returns the response +func (c *Client) do(method, path string) (*http.Response, error) { + + // create the URI + uri, err := url.Parse(c.url + path) + if err != nil { + return nil, err + } + + if len(uri.Scheme) == 0 { + uri.Scheme = "http" + } + + if len(c.token) > 0 { + params := uri.Query() + params.Add("access_token", c.token) + uri.RawQuery = params.Encode() + } + + // create the request + req := &http.Request{ + URL: uri, + Method: method, + ProtoMajor: 1, + ProtoMinor: 1, + Close: true, + ContentLength: 0, + } + + // make the request using the default http client + return http.DefaultClient.Do(req) +} diff --git a/client/commits.go b/client/commits.go new file mode 100644 index 00000000..96b9fa9c --- /dev/null +++ b/client/commits.go @@ -0,0 +1,52 @@ +package client + +import ( + "fmt" + "io" + + "github.com/drone/drone/shared/model" +) + +type CommitService struct { + *Client +} + +// GET /v1/repos/{host}/{owner}/{name}/branch/{branch}/commit/{commit} +func (s *CommitService) Get(host, owner, name, branch, sha string) (*model.Commit, error) { + var path = fmt.Sprintf("/v1/repos/%s/%s/%s/branch/%s/commit/%s", host, owner, name, branch, sha) + var commit = model.Commit{} + var err = s.run("GET", path, nil, &commit) + return &commit, err +} + +// GET /v1/repos/{host}/{owner}/{name}/branches/{branch}/commits/{commit}/console +func (s *CommitService) GetOutput(host, owner, name, branch, sha string) (io.ReadCloser, error) { + var path = fmt.Sprintf("/v1/repos/%s/%s/%s/branch/%s/commit/%s/console", host, owner, name, branch, sha) + resp, err := s.do("GET", path) + if err != nil { + return nil, nil + } + return resp.Body, nil +} + +// POST /v1/repos/{host}/{owner}/{name}/branches/{branch}/commits/{commit}?action=rebuild +func (s *CommitService) Rebuild(host, owner, name, branch, sha string) error { + var path = fmt.Sprintf("/v1/repos/%s/%s/%s/branch/%s/commit/%s", host, owner, name, branch, sha) + return s.run("POST", path, nil, nil) +} + +// GET /v1/repos/{host}/{owner}/{name}/feed +func (s *CommitService) List(host, owner, name string) ([]*model.Commit, error) { + var path = fmt.Sprintf("/v1/repos/%s/%s/%s/feed", host, owner, name) + var list []*model.Commit + var err = s.run("GET", path, nil, &list) + return list, err +} + +// GET /v1/repos/{host}/{owner}/{name}/branch/{branch} +func (s *CommitService) ListBranch(host, owner, name, branch string) ([]*model.Commit, error) { + var path = fmt.Sprintf("/v1/repos/%s/%s/%s/branch/%s", host, owner, name, branch) + var list []*model.Commit + var err = s.run("GET", path, nil, &list) + return list, err +} diff --git a/client/main.go b/client/main.go deleted file mode 100644 index 7d3868a5..00000000 --- a/client/main.go +++ /dev/null @@ -1,314 +0,0 @@ -package main - -import ( - "bytes" - "flag" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "sync" - "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" - - "gopkg.in/yaml.v1" -) - -var ( - // identity file (id_rsa) that will be injected - // into the container if specified - identity = flag.String("identity", "", "") - - // runs Drone in parallel mode if True - parallel = flag.Bool("parallel", false, "") - - // build will timeout after N milliseconds. - // this will default to 500 minutes (6 hours) - timeout = flag.Duration("timeout", 300*time.Minute, "") - - // build will run in a privileged container - privileged = flag.Bool("privileged", false, "") - - // runs Drone with verbose output if True - verbose = flag.Bool("v", false, "") - - // displays the help / usage if True - help = flag.Bool("h", false, "") - - // version number, currently deterined by the - // git revision number (sha) - version string -) - -func init() { - // default logging - log.SetPrefix("\033[2m[DRONE] ") - log.SetSuffix("\033[0m\n") - log.SetOutput(os.Stdout) - log.SetPriority(log.LOG_NOTICE) - docker.Logging = false -} - -func main() { - // Parse the input parameters - flag.Usage = usage - flag.Parse() - - if *help { - flag.Usage() - os.Exit(0) - } - - if *verbose { - log.SetPriority(log.LOG_DEBUG) - } - - // Must speicify a command - args := flag.Args() - if len(args) == 0 { - flag.Usage() - os.Exit(0) - } - - switch { - // run drone build assuming the current - // working directory contains the drone.yml - case args[0] == "build" && len(args) == 1: - path, _ := os.Getwd() - path = filepath.Join(path, ".drone.yml") - run(path) - - // run drone build where the path to the - // source directory is provided - case args[0] == "build" && len(args) == 2: - path := args[1] - path = filepath.Clean(path) - path, _ = filepath.Abs(path) - path = filepath.Join(path, ".drone.yml") - run(path) - - // run drone vet where the path to the - // source directory is provided - case args[0] == "vet" && len(args) == 2: - path := args[1] - path = filepath.Clean(path) - path, _ = filepath.Abs(path) - path = filepath.Join(path, ".drone.yml") - vet(path) - - // run drone vet assuming the current - // working directory contains the drone.yml - case args[0] == "vet" && len(args) == 1: - path, _ := os.Getwd() - path = filepath.Join(path, ".drone.yml") - vet(path) - - // print the version / revision number - case args[0] == "version" && len(args) == 1: - println(version) - - // print the help message - case args[0] == "help" && len(args) == 1: - flag.Usage() - } - - os.Exit(0) -} - -func vet(path string) { - // parse the Drone yml file - script, err := script.ParseBuildFile(path) - if err != nil { - log.Err(err.Error()) - os.Exit(1) - return - } - - // print the Drone yml as parsed - out, _ := yaml.Marshal(script) - log.Noticef("parsed yaml:\n%s", string(out)) -} - -func run(path string) { - dockerClient := docker.New() - - // parse the Drone yml file - s, err := script.ParseBuildFile(path) - if err != nil { - log.Err(err.Error()) - os.Exit(1) - return - } - - // 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)) - - // track all build results - var builders []*build.Builder - - // 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) - os.Exit(1) - return - } - } - - builds := []*script.Build{s} - - // loop through and create builders - for _, b := range builds { //script.Builds { - builder := build.New(dockerClient) - builder.Build = b - builder.Repo = &code - builder.Key = key - builder.Stdout = os.Stdout - builder.Timeout = *timeout - builder.Privileged = *privileged - - if *parallel == true { - var buf bytes.Buffer - builder.Stdout = &buf - } - - builders = append(builders, builder) - } - - switch *parallel { - case false: - runSequential(builders) - case true: - runParallel(builders) - } - - // if in parallel mode, print out the buffer - // if we had a failure - for _, builder := range builders { - if builder.BuildState.ExitCode == 0 { - continue - } - - if buf, ok := builder.Stdout.(*bytes.Buffer); ok { - log.Noticef("printing stdout for failed build %s", builder.Build.Name) - println(buf.String()) - } - } - - // this exit code is initially 0 and will - // be set to an error code if any of the - // builds fail. - var exit int - - fmt.Printf("\nDrone Build Results \033[90m(%v)\033[0m\n", len(builders)) - - // loop through and print results - for _, builder := range builders { - 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)) - exit = builder.BuildState.ExitCode - } - } - - os.Exit(exit) -} - -func runSequential(builders []*build.Builder) { - // loop through and execute each build - for _, builder := range builders { - if err := builder.Run(); err != nil { - log.Errf("Error executing build: %s", err.Error()) - os.Exit(1) - } - } -} - -func runParallel(builders []*build.Builder) { - // spawn four worker goroutines - var wg sync.WaitGroup - for _, builder := range builders { - // Increment the WaitGroup counter - wg.Add(1) - // Launch a goroutine to run the build - go func(builder *build.Builder) { - defer wg.Done() - builder.Run() - }(builder) - time.Sleep(500 * time.Millisecond) // get weird iptables failures unless we sleep. - } - - // wait for the workers to finish - wg.Wait() -} - -var usage = func() { - fmt.Println(`Drone is a tool for building and testing code in Docker containers. - -Usage: - - drone command [arguments] - -The commands are: - - build build and test the repository - version print the version number - vet validate the yaml configuration file - - -v runs drone with verbose output - -h display this help and exit - --parallel runs drone build tasks in parallel - --timeout=300ms timeout build after 300 milliseconds - --privileged runs drone build in a privileged container - -Examples: - drone build builds the source in the pwd - drone build /path/to/repo builds the source repository - -Use "drone help [command]" for more information about a command. -`) -} diff --git a/client/repos.go b/client/repos.go new file mode 100644 index 00000000..19569c99 --- /dev/null +++ b/client/repos.go @@ -0,0 +1,46 @@ +package client + +import ( + "fmt" + + "github.com/drone/drone/shared/model" +) + +type RepoService struct { + *Client +} + +// GET /v1/repos/{host}/{owner}/{name} +func (s *RepoService) Get() (*model.Repo, error) { + var path = fmt.Sprintf("/v1/repos/%s/%s/%s") + var repo = model.Repo{} + var err = s.run("PUT", path, nil, &repo) + return &repo, err +} + +// PUT /v1/repos/{host}/{owner}/{name} +func (s *RepoService) Update(repo *model.Repo) (*model.Repo, error) { + var path = fmt.Sprintf("/v1/repos/%s/%s/%s") + var result = model.Repo{} + var err = s.run("PUT", path, &repo, &result) + return &result, err +} + +// POST /v1/repos/{host}/{owner}/{name} +func (s *RepoService) Enable(host, owner, name string) error { + var path = fmt.Sprintf("/v1/repos/%s/%s/%s", host, owner, name) + return s.run("POST", path, nil, nil) +} + +// DELETE /v1/repos/{host}/{owner}/{name} +func (s *RepoService) Disable(host, owner, name string) error { + var path = fmt.Sprintf("/v1/repos/%s/%s/%s", host, owner, name) + return s.run("DELETE", path, nil, nil) +} + +// GET /v1/user/repos +func (s *RepoService) List() ([]*model.Repo, error) { + var repos []*model.Repo + var err = s.run("GET", "/v1/user/repos", nil, &repos) + return repos, err +} diff --git a/client/users.go b/client/users.go new file mode 100644 index 00000000..b1728a62 --- /dev/null +++ b/client/users.go @@ -0,0 +1,47 @@ +package client + +import ( + "fmt" + + "github.com/drone/drone/shared/model" +) + +type UserService struct { + *Client +} + +// GET /v1/users/{host}/{login} +func (s *UserService) Get(remote, login string) (*model.User, error) { + var path = fmt.Sprintf("/v1/users/%s/%s", remote, login) + var user = model.User{} + var err = s.run("GET", path, nil, &user) + return &user, err +} + +// GET /v1/user +func (s *UserService) GetCurrent() (*model.User, error) { + var user = model.User{} + var err = s.run("GET", "/v1/user", nil, &user) + return &user, err +} + +// POST /v1/users/{host}/{login} +func (s *UserService) Create(remote, login string) (*model.User, error) { + var path = fmt.Sprintf("/v1/users/%s/%s", remote, login) + var user = model.User{} + var err = s.run("POST", path, nil, &user) + return &user, err +} + +// DELETE /v1/users/{host}/{login} +func (s *UserService) Delete(remote, login string) error { + var path = fmt.Sprintf("/v1/users/%s/%s", remote, login) + return s.run("DELETE", path, nil, nil) +} + +// GET /v1/users +func (s *UserService) List() ([]*model.User, error) { + var users []*model.User + var err = s.run("GET", "/v1/users", nil, &users) + return users, err +} diff --git a/client/util.go b/client/util.go deleted file mode 100644 index a5f628ca..00000000 --- a/client/util.go +++ /dev/null @@ -1,90 +0,0 @@ -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/cmd/disable.go b/cmd/disable.go index 5cdc4aae..7f4467f1 100644 --- a/cmd/disable.go +++ b/cmd/disable.go @@ -2,6 +2,7 @@ package main import ( "github.com/codegangsta/cli" + "github.com/drone/drone/client" ) // NewDisableCommand returns the CLI command for "disable". @@ -17,18 +18,13 @@ func NewDisableCommand() cli.Command { } // disableCommandFunc executes the "disable" command. -func disableCommandFunc(c *cli.Context, client *Client) error { - var repo string - var arg = c.Args() +func disableCommandFunc(c *cli.Context, client *client.Client) error { + var host, owner, name string + var args = c.Args() - if len(arg) != 0 { - repo = arg[0] + if len(args) != 0 { + host, owner, name = parseRepo(args[0]) } - err := client.Do("DELETE", "/v1/repos/"+repo, nil, nil) - if err != nil { - return err - } - - return nil + return client.Repos.Disable(host, owner, name) } diff --git a/cmd/enable.go b/cmd/enable.go index 5332a39d..c6b8ebad 100644 --- a/cmd/enable.go +++ b/cmd/enable.go @@ -2,6 +2,7 @@ package main import ( "github.com/codegangsta/cli" + "github.com/drone/drone/client" ) // NewEnableCommand returns the CLI command for "enable". @@ -17,18 +18,13 @@ func NewEnableCommand() cli.Command { } // enableCommandFunc executes the "enable" command. -func enableCommandFunc(c *cli.Context, client *Client) error { - var repo string - var arg = c.Args() +func enableCommandFunc(c *cli.Context, client *client.Client) error { + var host, owner, name string + var args = c.Args() - if len(arg) != 0 { - repo = arg[0] + if len(args) != 0 { + host, owner, name = parseRepo(args[0]) } - err := client.Do("POST", "/v1/repos/"+repo, nil, nil) - if err != nil { - return err - } - - return nil + return client.Repos.Enable(host, owner, name) } diff --git a/cmd/handle.go b/cmd/handle.go index 3634fd4f..a7fc48f6 100644 --- a/cmd/handle.go +++ b/cmd/handle.go @@ -4,25 +4,28 @@ import ( "os" "github.com/codegangsta/cli" + "github.com/drone/drone/client" ) -type handlerFunc func(*cli.Context, *Client) error +type handlerFunc func(*cli.Context, *client.Client) error // handle wraps the command function handlers and // sets up the environment. func handle(c *cli.Context, fn handlerFunc) { - client := Client{} - client.Token = os.Getenv("DRONE_TOKEN") - client.URL = os.Getenv("DRONE_HOST") + var token = c.GlobalString("token") + var server = c.GlobalString("server") - // if no url is provided we can default + // if no server url is provided we can default // to the hosted Drone service. - if len(client.URL) == 0 { - client.URL = "http://test.drone.io" + if len(server) == 0 { + server = "http://test.drone.io" } + // create the drone client + client := client.New(token, server) + // handle the function - if err := fn(c, &client); err != nil { + if err := fn(c, client); err != nil { println(err.Error()) os.Exit(1) } diff --git a/cmd/main.go b/cmd/main.go index d103a22b..f8fd1569 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -5,11 +5,31 @@ import ( "os" ) +var ( + // commit sha for the current build. + version string = "0.3-dev" + revision string +) + func main() { app := cli.NewApp() app.Name = "drone" - app.Version = "1.0" + 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(), diff --git a/cmd/restart.go b/cmd/restart.go index 29ec5561..e8657cca 100644 --- a/cmd/restart.go +++ b/cmd/restart.go @@ -1,9 +1,8 @@ package main import ( - "fmt" - "github.com/codegangsta/cli" + "github.com/drone/drone/client" ) // NewRestartCommand returns the CLI command for "restart". @@ -19,22 +18,19 @@ func NewRestartCommand() cli.Command { } // restartCommandFunc executes the "restart" command. -func restartCommandFunc(c *cli.Context, client *Client) error { - var branch string = "master" - var commit string - var repo string - var arg = c.Args() +func restartCommandFunc(c *cli.Context, client *client.Client) error { + var host, owner, repo, branch, sha string + var args = c.Args() - switch len(arg) { - case 2: - repo = arg[0] - commit = arg[1] - case 3: - repo = arg[0] - branch = arg[1] - commit = arg[2] + if len(args) == 5 { + host, owner, repo = parseRepo(args[0]) + } else { + host = "unknown" + owner = "unknown" + repo = "unknown" + branch = "unknown" + sha = "unknown" } - path := fmt.Sprintf("/v1/repos/%s/branches/%s/commits/%s?action=rebuild", repo, branch, commit) - return client.Do("POST", path, nil, nil) + return client.Commits.Rebuild(host, owner, repo, branch, sha) } diff --git a/cmd/util.go b/cmd/util.go index a5f628ca..1208d288 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -9,6 +9,20 @@ import ( "time" ) +func parseRepo(str string) (host, owner, repo string) { + var parts = strings.Split(str, "/") + if len(repo) != 3 { + host = "undefined" + owner = "undefined" + repo = "undefined" + 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 diff --git a/cmd/whoami.go b/cmd/whoami.go index 0c3716fa..3b96301d 100644 --- a/cmd/whoami.go +++ b/cmd/whoami.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/codegangsta/cli" - "github.com/drone/drone/shared/model" + "github.com/drone/drone/client" ) // NewWhoamiCommand returns the CLI command for "whoami". @@ -20,9 +20,8 @@ func NewWhoamiCommand() cli.Command { } // whoamiCommandFunc executes the "logout" command. -func whoamiCommandFunc(c *cli.Context, client *Client) error { - user := model.User{} - err := client.Do("GET", "/v1/user", nil, &user) +func whoamiCommandFunc(c *cli.Context, client *client.Client) error { + user, err := client.Users.GetCurrent() if err != nil { return err }