Skip to content

Commit

Permalink
Merge pull request #752 from CircleCI-Public/EXT-311-new-cli-command
Browse files Browse the repository at this point in the history
Ext 311 new cli command
  • Loading branch information
rykimcircle authored Jul 20, 2022
2 parents e4216bd + 63ff924 commit 9f5bc2a
Show file tree
Hide file tree
Showing 19 changed files with 450 additions and 85 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ out/
vendor/
.vscode
.idea
.DS_Store

# For supporting generated config of 2.1 syntax for local testing
.circleci/processed.yml
Expand Down
12 changes: 1 addition & 11 deletions api/context_rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"io/ioutil"
"net/http"
"net/url"
"strings"

"github.com/CircleCI-Public/circleci-cli/api/header"
"github.com/CircleCI-Public/circleci-cli/settings"
Expand Down Expand Up @@ -605,16 +604,7 @@ func (c *ContextRestClient) EnsureExists() error {
// NewContextRestClient returns a new client satisfying the api.ContextInterface
// interface via the REST API.
func NewContextRestClient(config settings.Config) (*ContextRestClient, error) {
// Ensure server ends with a slash
if !strings.HasSuffix(config.RestEndpoint, "/") {
config.RestEndpoint += "/"
}
serverURL, err := url.Parse(config.Host)
if err != nil {
return nil, err
}

serverURL, err = serverURL.Parse(config.RestEndpoint)
serverURL, err := config.ServerURL()
if err != nil {
return nil, err
}
Expand Down
115 changes: 115 additions & 0 deletions api/info/info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package info

import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"

"github.com/CircleCI-Public/circleci-cli/api/header"
"github.com/CircleCI-Public/circleci-cli/settings"
"github.com/CircleCI-Public/circleci-cli/version"
)

// InfoClient An interface with all the Info Functions.
type InfoClient interface {
GetInfo() (*[]Organization, error)
}

// errorResponse used to handle error messages from the API.
type errorResponse struct {
Message *string `json:"message"`
}

// InfoRESTClient A restful implementation of the InfoClient
type InfoRESTClient struct {
token string
server string
client *http.Client
}

//organization json org info
type Organization struct {
ID string `json:"id"`
Name string `json:"name"`
}

// GetInfo
func (c *InfoRESTClient) GetInfo() (*[]Organization, error) {
var err error
queryURL, err := url.Parse(c.server)
if err != nil {
return nil, err
}
queryURL, err = queryURL.Parse("me/collaborations")
if err != nil {
return nil, err
}
req, err := c.newHTTPRequest("GET", queryURL.String(), nil)
if err != nil {
return nil, fmt.Errorf("failed to construct new request: %v", err)
}

resp, err := c.client.Do(req)
if err != nil {
return nil, err
}

bodyBytes, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
return nil, err
}

if resp.StatusCode != 200 {
var dest errorResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err

}
return nil, errors.New(*dest.Message)
}

orgs := make([]Organization, 0)
if err := json.Unmarshal(bodyBytes, &orgs); err != nil {
return nil, err
}

return &orgs, nil
}

// newHTTPRequest Creates a new standard HTTP request object used to communicate with the API
func (c *InfoRESTClient) newHTTPRequest(method, url string, body io.Reader) (*http.Request, error) {
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
req.Header.Add("circle-token", c.token)
req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/json")
req.Header.Add("User-Agent", version.UserAgent())
commandStr := header.GetCommandStr()
if commandStr != "" {
req.Header.Add("Circleci-Cli-Command", commandStr)
}
return req, nil
}

// Creates a new client to talk with the rest info endpoints.
func NewInfoClient(config settings.Config) (InfoClient, error) {
serverURL, err := config.ServerURL()
if err != nil {
return nil, err
}

client := &InfoRESTClient{
token: config.Token,
server: serverURL.String(),
client: config.HTTPClient,
}

return client, nil
}
78 changes: 78 additions & 0 deletions api/info/info_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package info

import (
"fmt"
"net/http"
"net/http/httptest"
"testing"

"github.com/CircleCI-Public/circleci-cli/settings"
"gotest.tools/v3/assert"
)

func TestOkResponse(t *testing.T) {
token := "pluto-is-a-planet"
id := "id"
name := "name"

server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.Method, "GET")
assert.Equal(t, r.URL.String(), "/me/collaborations")
assert.Equal(t, r.Header.Get("circle-token"), token)
assert.Equal(t, r.Header.Get("Content-Type"), "application/json")
assert.Equal(t, r.Header.Get("Accept"), "application/json")

w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte(fmt.Sprintf(`[{"id": "%s", "name": "%s"}]`, id, name)))
assert.NilError(t, err)
}))

defer server.Close()

config := settings.Config{
Host: server.URL,
HTTPClient: http.DefaultClient,
Token: token,
}

client, _ := NewInfoClient(config)
orgs, err := client.GetInfo()
organizations := *orgs

assert.NilError(t, err)
assert.Equal(t, len(organizations), 1)

org := organizations[0]
assert.Equal(t, org.ID, id)
assert.Equal(t, org.Name, name)
}

func TestServerErrorResponse(t *testing.T) {
token := "pluto-is-a-planet"
message := "i-come-in-peace"

server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.Method, "GET")
assert.Equal(t, r.URL.String(), "/me/collaborations")
assert.Equal(t, r.Header.Get("circle-token"), token)
assert.Equal(t, r.Header.Get("Content-Type"), "application/json")
assert.Equal(t, r.Header.Get("Accept"), "application/json")

w.WriteHeader(http.StatusInternalServerError)
_, err := w.Write([]byte(fmt.Sprintf(`{"message": "%s"}`, message)))
assert.NilError(t, err)
}))

defer server.Close()

config := settings.Config{
Host: server.URL,
HTTPClient: http.DefaultClient,
Token: token,
}

client, _ := NewInfoClient(config)
_, err := client.GetInfo()

assert.Error(t, err, message)
}
1 change: 0 additions & 1 deletion api/rest/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ func (c *Client) NewRequest(method string, u *url.URL, payload interface{}) (req
if payload != nil {
req.Header.Set("Content-Type", "application/json")
}

return req, nil
}

Expand Down
12 changes: 1 addition & 11 deletions api/schedule_rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"io/ioutil"
"net/http"
"net/url"
"strings"

"github.com/CircleCI-Public/circleci-cli/api/header"
"github.com/CircleCI-Public/circleci-cli/settings"
Expand Down Expand Up @@ -463,16 +462,7 @@ func (c *ScheduleRestClient) EnsureExists() error {
// Returns a new client satisfying the api.ScheduleInterface interface
// via the REST API.
func NewScheduleRestClient(config settings.Config) (*ScheduleRestClient, error) {
// Ensure server ends with a slash
if !strings.HasSuffix(config.RestEndpoint, "/") {
config.RestEndpoint += "/"
}
serverURL, err := url.Parse(config.Host)
if err != nil {
return nil, err
}

serverURL, err = serverURL.Parse(config.RestEndpoint)
serverURL, err := config.ServerURL()
if err != nil {
return nil, err
}
Expand Down
69 changes: 69 additions & 0 deletions cmd/info/info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package info

import (
"github.com/CircleCI-Public/circleci-cli/api/info"
"github.com/CircleCI-Public/circleci-cli/cmd/validator"
"github.com/CircleCI-Public/circleci-cli/settings"
"github.com/olekukonko/tablewriter"

"github.com/spf13/cobra"
)

//infoOptions info command options
type infoOptions struct {
cfg *settings.Config
validator validator.Validator
}

// NewInfoCommand information cobra command creation
func NewInfoCommand(config *settings.Config, preRunE validator.Validator) *cobra.Command {
client, _ := info.NewInfoClient(*config)

opts := infoOptions{
cfg: config,
validator: preRunE,
}
infoCommand := &cobra.Command{
Use: "info",
Short: "Check information associated to your user account.",
}
orgInfoCmd := orgInfoCommand(client, opts)
infoCommand.AddCommand(orgInfoCmd)

return infoCommand
}

//orgInfoCommand organization information subcommand cobra command creation
func orgInfoCommand(client info.InfoClient, opts infoOptions) *cobra.Command {
return &cobra.Command{
Use: "org",
Short: "View your Organizations' information",
Long: `View your Organizations' names and ids.`,
PreRunE: opts.validator,
RunE: func(cmd *cobra.Command, _ []string) error {
return getOrgInformation(cmd, client)
},
Annotations: make(map[string]string),
Example: `circleci info org`,
}
}

//getOrgInformation gets all of the users organizations' information
func getOrgInformation(cmd *cobra.Command, client info.InfoClient) error {
resp, err := client.GetInfo()
if err != nil {
return err
}

table := tablewriter.NewWriter(cmd.OutOrStdout())

table.SetHeader([]string{"ID", "Name"})

for _, info := range *resp {
table.Append([]string{
info.ID, info.Name,
})
}
table.Render()
return nil
}
Loading

0 comments on commit 9f5bc2a

Please sign in to comment.