Skip to content

Commit a1ad6c6

Browse files
dbolsonSunny Guduru
authored andcommitted
sc-235696/list projects (#20)
`ldcli projects list` returns list of projects in JSON
1 parent a3240dc commit a1ad6c6

687 files changed

Lines changed: 191922 additions & 135 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cmd/hello_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ func TestHelloCmd(t *testing.T) {
1212
t.Run("with no options", func(t *testing.T) {
1313
expected := `{"hello": "world"}`
1414
actual := new(bytes.Buffer)
15-
1615
rootCmd.SetOut(actual)
1716
rootCmd.SetErr(actual)
1817
rootCmd.SetArgs([]string{"hello"})

cmd/projects/list.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package projects
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"ld-cli/internal/projects"
8+
9+
"github.com/spf13/cobra"
10+
"github.com/spf13/viper"
11+
)
12+
13+
func NewListCmd() *cobra.Command {
14+
cmd := &cobra.Command{
15+
Use: "list",
16+
Short: "Return a list of projects",
17+
Long: "Return a list of projects",
18+
RunE: runList,
19+
}
20+
21+
cmd.AddCommand()
22+
23+
return cmd
24+
}
25+
26+
func runList(cmd *cobra.Command, args []string) error {
27+
// TODO: handle missing flags
28+
if viper.GetString("accessToken") == "" {
29+
return errors.New("accessToken required")
30+
}
31+
if viper.GetString("baseUri") == "" {
32+
return errors.New("baseUri required")
33+
}
34+
35+
client := projects.NewClient(
36+
viper.GetString("accessToken"),
37+
viper.GetString("baseUri"),
38+
)
39+
response, err := projects.ListProjects(
40+
context.Background(),
41+
client,
42+
)
43+
if err != nil {
44+
return err
45+
}
46+
47+
// TODO: should this return response and let caller output or pass in stdout-ish interface?
48+
fmt.Fprintf(cmd.OutOrStdout(), string(response)+"\n")
49+
50+
return nil
51+
}

cmd/projects/projects.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package projects
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
)
6+
7+
func NewProjectsCmd() *cobra.Command {
8+
cmd := &cobra.Command{
9+
Use: "projects",
10+
Short: "Make requests (list, create, etc.) on projects",
11+
Long: "Make requests (list, create, etc.) on projects",
12+
}
13+
14+
cmd.AddCommand(NewListCmd())
15+
16+
return cmd
17+
}

cmd/root.go

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55

66
"github.com/spf13/cobra"
77
"github.com/spf13/viper"
8+
9+
"ld-cli/cmd/projects"
810
)
911

1012
var rootCmd = &cobra.Command{
@@ -21,31 +23,35 @@ func Execute() {
2123
}
2224

2325
func init() {
24-
var accessToken string
26+
var (
27+
accessToken string
28+
baseURI string
29+
)
2530

2631
rootCmd.PersistentFlags().StringVarP(
2732
&accessToken,
28-
"baseUri",
29-
"u",
30-
"http://localhost:3000",
31-
"LaunchDarkly base URI.",
33+
"accessToken",
34+
"t",
35+
"",
36+
"LaunchDarkly personal access token",
3237
)
33-
err := viper.BindPFlag("baseUri", rootCmd.PersistentFlags().Lookup("baseUri"))
38+
err := viper.BindPFlag("accessToken", rootCmd.PersistentFlags().Lookup("accessToken"))
3439
if err != nil {
3540
os.Exit(1)
3641
}
3742
rootCmd.PersistentFlags().StringVarP(
38-
&accessToken,
39-
"accessToken",
40-
"t",
41-
"",
42-
"LaunchDarkly personal access token with write-level access.",
43+
&baseURI,
44+
"baseUri",
45+
"u",
46+
"http://localhost:3000",
47+
"LaunchDarkly base URI",
4348
)
44-
err = viper.BindPFlag("accessToken", rootCmd.PersistentFlags().Lookup("accessToken"))
49+
err = viper.BindPFlag("baseUri", rootCmd.PersistentFlags().Lookup("baseUri"))
4550
if err != nil {
4651
os.Exit(1)
4752
}
4853

4954
rootCmd.AddCommand(newHelloCmd())
55+
rootCmd.AddCommand(projects.NewProjectsCmd())
5056
rootCmd.AddCommand(setupCmd)
5157
}

go.mod

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/charmbracelet/bubbles v0.18.0
77
github.com/charmbracelet/bubbletea v0.25.0
88
github.com/charmbracelet/lipgloss v0.10.0
9+
github.com/launchdarkly/api-client-go/v14 v14.0.0
910
github.com/spf13/cobra v1.8.0
1011
github.com/spf13/viper v1.18.2
1112
github.com/stretchr/testify v1.9.0
@@ -17,6 +18,7 @@ require (
1718
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
1819
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
1920
github.com/fsnotify/fsnotify v1.7.0 // indirect
21+
github.com/golang/protobuf v1.5.3 // indirect
2022
github.com/hashicorp/hcl v1.0.0 // indirect
2123
github.com/inconshreveable/mousetrap v1.1.0 // indirect
2224
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
@@ -43,10 +45,14 @@ require (
4345
go.uber.org/atomic v1.9.0 // indirect
4446
go.uber.org/multierr v1.9.0 // indirect
4547
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
48+
golang.org/x/net v0.19.0 // indirect
49+
golang.org/x/oauth2 v0.15.0 // indirect
4650
golang.org/x/sync v0.5.0 // indirect
47-
golang.org/x/sys v0.15.0 // indirect
48-
golang.org/x/term v0.6.0 // indirect
51+
golang.org/x/sys v0.18.0 // indirect
52+
golang.org/x/term v0.18.0 // indirect
4953
golang.org/x/text v0.14.0 // indirect
54+
google.golang.org/appengine v1.6.7 // indirect
55+
google.golang.org/protobuf v1.31.0 // indirect
5056
gopkg.in/ini.v1 v1.67.0 // indirect
5157
gopkg.in/yaml.v3 v3.0.1 // indirect
5258
)

go.sum

Lines changed: 371 additions & 4 deletions
Large diffs are not rendered by default.

internal/projects/projects.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package projects
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
7+
ldapi "github.com/launchdarkly/api-client-go/v14"
8+
)
9+
10+
type Client interface {
11+
List(context.Context) (*ldapi.Projects, error)
12+
}
13+
14+
type ProjectsClient struct {
15+
client *ldapi.APIClient
16+
}
17+
18+
func NewClient(accessToken string, baseURI string) ProjectsClient {
19+
config := ldapi.NewConfiguration()
20+
config.AddDefaultHeader("Authorization", accessToken)
21+
config.Servers[0].URL = baseURI
22+
client := ldapi.NewAPIClient(config)
23+
24+
return ProjectsClient{
25+
client: client,
26+
}
27+
}
28+
29+
func (c ProjectsClient) List(ctx context.Context) (*ldapi.Projects, error) {
30+
projects, _, err := c.client.ProjectsApi.
31+
GetProjects(ctx).
32+
Limit(2).
33+
Execute()
34+
if err != nil {
35+
// TODO: make this nicer
36+
return nil, err
37+
}
38+
39+
return projects, nil
40+
}
41+
42+
func ListProjects(ctx context.Context, client2 Client) ([]byte, error) {
43+
projects, err := client2.List(ctx)
44+
if err != nil {
45+
// 401 - should return unauthorized type error with body(?)
46+
// 404 - should return not found type error with body
47+
e, ok := err.(ldapi.GenericOpenAPIError)
48+
if ok {
49+
return e.Body(), err
50+
}
51+
return nil, err
52+
}
53+
54+
projectsJSON, err := json.Marshal(projects)
55+
if err != nil {
56+
return nil, err
57+
}
58+
59+
return projectsJSON, nil
60+
}

internal/projects/projects_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package projects_test
2+
3+
import (
4+
"context"
5+
"ld-cli/internal/projects"
6+
"testing"
7+
8+
ldapi "github.com/launchdarkly/api-client-go/v14"
9+
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func strPtr(s string) *string {
15+
return &s
16+
}
17+
18+
type GetProjectsResponse struct{}
19+
20+
type MockClient struct{}
21+
22+
func (c MockClient) List(ctx context.Context) (*ldapi.Projects, error) {
23+
totalCount := int32(1)
24+
25+
return &ldapi.Projects{
26+
Links: map[string]ldapi.Link{
27+
"last": {
28+
Href: strPtr("/api/v2/projects?expand=environments&limit=1&offset=1"),
29+
Type: strPtr("application/json"),
30+
},
31+
"next": {
32+
Href: strPtr("/api/v2/projects?expand=environments&limit=1&offset=0"),
33+
Type: strPtr("application/json"),
34+
},
35+
"self": {
36+
Href: strPtr("/api/v2/projects?expand=environments&limit=1"),
37+
Type: strPtr("application/json"),
38+
},
39+
},
40+
Items: []ldapi.Project{
41+
{
42+
Id: "000000000000000000000001",
43+
Key: "test-project",
44+
},
45+
},
46+
TotalCount: &totalCount,
47+
}, nil
48+
}
49+
50+
func TestListProjects(t *testing.T) {
51+
t.Run("returns a paginated list of projects", func(t *testing.T) {
52+
expected := `{
53+
"_links": {
54+
"last": {
55+
"href": "/api/v2/projects?expand=environments&limit=1&offset=1",
56+
"type": "application/json"
57+
},
58+
"next": {
59+
"href": "/api/v2/projects?expand=environments&limit=1&offset=0",
60+
"type": "application/json"
61+
},
62+
"self": {
63+
"href": "/api/v2/projects?expand=environments&limit=1",
64+
"type": "application/json"
65+
}
66+
},
67+
"items": [
68+
{
69+
"_id": "000000000000000000000001",
70+
"_links": null,
71+
"includeInSnippetByDefault": false,
72+
"key": "test-project",
73+
"name": "",
74+
"tags": null
75+
}
76+
],
77+
"totalCount": 1
78+
}`
79+
mockClient := MockClient{}
80+
81+
response, err := projects.ListProjects(context.Background(), mockClient)
82+
83+
require.NoError(t, err)
84+
assert.JSONEq(t, expected, string(response))
85+
})
86+
87+
t.Run("with invalid accessToken is forbidden", func(t *testing.T) {
88+
})
89+
}

vendor/github.com/golang/protobuf/AUTHORS

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/golang/protobuf/CONTRIBUTORS

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)