Skip to content

Commit

Permalink
Commands added:
Browse files Browse the repository at this point in the history
- `circleci project list-secrets`

Other changes:
- Replace a request header `Accept-Type` with `Accept`
  • Loading branch information
threepipes authored and JulesFaucherre committed Feb 6, 2023
1 parent c849191 commit 5cbf90e
Show file tree
Hide file tree
Showing 8 changed files with 498 additions and 1 deletion.
13 changes: 13 additions & 0 deletions api/project/project.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package project

// ProjectEnvironmentVariable is a Environment Variable of a Project
type ProjectEnvironmentVariable struct {
Name string
Value string
}

// ProjectClient is the interface to interact with project and it's
// components.
type ProjectClient interface {
ListAllEnvironmentVariables(vcs, org, project string) ([]*ProjectEnvironmentVariable, error)
}
104 changes: 104 additions & 0 deletions api/project/project_rest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package project

import (
"fmt"
"net/url"

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

type projectRestClient struct {
token string
server string
client *rest.Client
}

var _ ProjectClient = &projectRestClient{}

type listProjectEnvVarsParams struct {
vcs string
org string
project string
pageToken string
}

type projectEnvVarResponse struct {
Name string
Value string
}

type listAllProjectEnvVarsResponse struct {
Items []projectEnvVarResponse
NextPageToken string `json:"next_page_token"`
}

// NewProjectRestClient returns a new projectRestClient satisfying the api.ProjectInterface
// interface via the REST API.
func NewProjectRestClient(config settings.Config) (*projectRestClient, error) {
serverURL, err := config.ServerURL()
if err != nil {
return nil, err
}

client := &projectRestClient{
token: config.Token,
server: serverURL.String(),
client: rest.New(config.Host, &config),
}

return client, nil
}

// ListAllEnvironmentVariables returns all of the environment variables owned by the
// given project. Note that pagination is not supported - we get all
// pages of env vars and return them all.
func (p *projectRestClient) ListAllEnvironmentVariables(vcs, org, project string) ([]*ProjectEnvironmentVariable, error) {
res := make([]*ProjectEnvironmentVariable, 0)
var nextPageToken string
for {
resp, err := p.listEnvironmentVariables(&listProjectEnvVarsParams{
vcs: vcs,
org: org,
project: project,
pageToken: nextPageToken,
})
if err != nil {
return nil, err
}

for _, ev := range resp.Items {
res = append(res, &ProjectEnvironmentVariable{
Name: ev.Name,
Value: ev.Value,
})
}

if resp.NextPageToken == "" {
break
}

nextPageToken = resp.NextPageToken
}
return res, nil
}

func (c *projectRestClient) listEnvironmentVariables(params *listProjectEnvVarsParams) (*listAllProjectEnvVarsResponse, error) {
path := fmt.Sprintf("project/%s/%s/%s/envvar", params.vcs, params.org, params.project)
urlParams := url.Values{}
if params.pageToken != "" {
urlParams.Add("page-token", params.pageToken)
}

req, err := c.client.NewRequest("GET", &url.URL{Path: path, RawQuery: urlParams.Encode()}, nil)
if err != nil {
return nil, err
}

var resp listAllProjectEnvVarsResponse
_, err = c.client.DoRequest(req, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
160 changes: 160 additions & 0 deletions api/project/project_rest_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package project_test

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

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

func getProjectRestClient(server *httptest.Server) (project.ProjectClient, error) {
client := &http.Client{}

return project.NewProjectRestClient(settings.Config{
RestEndpoint: "api/v2",
Host: server.URL,
HTTPClient: client,
Token: "token",
})
}

func Test_projectRestClient_ListAllEnvironmentVariables(t *testing.T) {
const (
vcsType = "github"
orgName = "test-org"
projName = "test-proj"
)
tests := []struct {
name string
handler http.HandlerFunc
want []*project.ProjectEnvironmentVariable
wantErr bool
}{
{
name: "Should handle a successful request with ListAllEnvironmentVariables",
handler: func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.Header.Get("circle-token"), "token")
assert.Equal(t, r.Header.Get("accept"), "application/json")
assert.Equal(t, r.Header.Get("user-agent"), version.UserAgent())

assert.Equal(t, r.Method, "GET")
assert.Equal(t, r.URL.Path, fmt.Sprintf("/api/v2/project/%s/%s/%s/envvar", vcsType, orgName, projName))

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte(`
{
"items": [{
"name": "foo",
"value": "xxxx1234"
}],
"next_page_token": ""
}`))
assert.NilError(t, err)
},
want: []*project.ProjectEnvironmentVariable{
{
Name: "foo",
Value: "xxxx1234",
},
},
},
{
name: "Should handle a request containing next_page_token with ListAllEnvironmentVariables",
handler: func(w http.ResponseWriter, r *http.Request) {
u, err := url.ParseQuery(r.URL.RawQuery)
assert.NilError(t, err)

w.Header().Set("content-type", "application/json")
w.WriteHeader(http.StatusOK)
if tk := u.Get("page-token"); tk == "" {
_, err := w.Write([]byte(`
{
"items": [
{
"name": "foo1",
"value": "xxxx1234"
},
{
"name": "foo2",
"value": "xxxx2345"
}
],
"next_page_token": "pagetoken"
}`))
assert.NilError(t, err)
} else {
assert.Equal(t, tk, "pagetoken")
_, err := w.Write([]byte(`
{
"items": [
{
"name": "bar1",
"value": "xxxxabcd"
},
{
"name": "bar2",
"value": "xxxxbcde"
}
],
"next_page_token": ""
}`))
assert.NilError(t, err)
}
},
want: []*project.ProjectEnvironmentVariable{
{
Name: "foo1",
Value: "xxxx1234",
},
{
Name: "foo2",
Value: "xxxx2345",
},
{
Name: "bar1",
Value: "xxxxabcd",
},
{
Name: "bar2",
Value: "xxxxbcde",
},
},
},
{
name: "Should handle an error request with ListAllEnvironmentVariables",
handler: func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("content-type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
_, err := w.Write([]byte(`{"message": "error"}`))
assert.NilError(t, err)
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server := httptest.NewServer(tt.handler)
defer server.Close()

p, err := getProjectRestClient(server)
assert.NilError(t, err)

got, err := p.ListAllEnvironmentVariables(vcsType, orgName, projName)
if (err != nil) != tt.wantErr {
t.Errorf("projectRestClient.ListAllEnvironmentVariables() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("projectRestClient.ListAllEnvironmentVariables() = %v, want %v", got, tt.want)
}
})
}
}
46 changes: 46 additions & 0 deletions cmd/project/environment_variable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package project

import (
projectapi "github.com/CircleCI-Public/circleci-cli/api/project"
"github.com/CircleCI-Public/circleci-cli/cmd/validator"
"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
)

func newProjectEnvironmentVariableCommand(ops *projectOpts, preRunE validator.Validator) *cobra.Command {
cmd := &cobra.Command{
Use: "secret",
Short: "Operate on environment variables of projects",
}

listVarsCommand := &cobra.Command{
Short: "List all environment variables of a project",
Use: "list <vcs-type> <org-name> <project-name>",
PreRunE: preRunE,
RunE: func(cmd *cobra.Command, args []string) error {
return listProjectEnvironmentVariables(cmd, ops.client, args[0], args[1], args[2])
},
Args: cobra.ExactArgs(3),
}

cmd.AddCommand(listVarsCommand)
return cmd
}

func listProjectEnvironmentVariables(cmd *cobra.Command, client projectapi.ProjectClient, vcsType, orgName, projName string) error {
envVars, err := client.ListAllEnvironmentVariables(vcsType, orgName, projName)
if err != nil {
return err
}

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

table.SetHeader([]string{"Environment Variable", "Value"})

for _, envVar := range envVars {
table.Append([]string{envVar.Name, envVar.Value})
}
table.Render()

return nil
}
34 changes: 34 additions & 0 deletions cmd/project/project.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package project

import (
projectapi "github.com/CircleCI-Public/circleci-cli/api/project"
"github.com/CircleCI-Public/circleci-cli/cmd/validator"

"github.com/CircleCI-Public/circleci-cli/settings"
"github.com/spf13/cobra"
)

type projectOpts struct {
client projectapi.ProjectClient
}

// NewProjectCommand generates a cobra command for managing projects
func NewProjectCommand(config *settings.Config, preRunE validator.Validator) *cobra.Command {
var opts projectOpts
command := &cobra.Command{
Use: "project",
Short: "Operate on projects",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
client, err := projectapi.NewProjectRestClient(*config)
if err != nil {
return err
}
opts.client = client
return nil
},
}

command.AddCommand(newProjectEnvironmentVariableCommand(&opts, preRunE))

return command
}
Loading

0 comments on commit 5cbf90e

Please sign in to comment.