Skip to content

Commit

Permalink
feat: hardcoded resource cmds (#203)
Browse files Browse the repository at this point in the history
* add barebones cmds for resources and operations

* wip: add flags

* add more flag types

* remove cmd

* remove comments

* add (bad) tests, fix output

* fix teams short/long description

* change back rootCmd name

* pr feedback
  • Loading branch information
k3llymariee authored Apr 24, 2024
1 parent 96474cd commit b8dc52a
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 0 deletions.
39 changes: 39 additions & 0 deletions cmd/resource_cmds.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// this file WILL be generated (sc-241153)

package cmd

import (
"net/http"

"github.com/spf13/cobra"

"ldcli/cmd/resources"
)

func addAllResourceCmds(rootCmd *cobra.Command, client *http.Client) {
// Resource commands
gen_TeamsResourceCmd := resources.NewResourceCmd(
rootCmd,
"teams",
"A team is a group of members in your LaunchDarkly account.",
"A team can have maintainers who are able to add and remove team members. It also can have custom roles assigned to it that allows shared access to those roles for all team members. To learn more, read [Teams](https://docs.launchdarkly.com/home/teams).\n\nThe Teams API allows you to create, read, update, and delete a team.\n\nSeveral of the endpoints in the Teams API require one or more member IDs. The member ID is returned as part of the [List account members](/tag/Account-members#operation/getMembers) response. It is the `_id` field of each element in the `items` array.",
)

// Operation commands
resources.NewOperationCmd(gen_TeamsResourceCmd, client, resources.OperationData{
Short: "Create team",
Long: "Create a team. To learn more, read [Creating a team](https://docs.launchdarkly.com/home/teams/creating).\n\n### Expanding the teams response\nLaunchDarkly supports four fields for expanding the \"Create team\" response. By default, these fields are **not** included in the response.\n\nTo expand the response, append the `expand` query parameter and add a comma-separated list with any of the following fields:\n\n* `members` includes the total count of members that belong to the team.\n* `roles` includes a paginated list of the custom roles that you have assigned to the team.\n* `projects` includes a paginated list of the projects that the team has any write access to.\n* `maintainers` includes a paginated list of the maintainers that you have assigned to the team.\n\nFor example, `expand=members,roles` includes the `members` and `roles` fields in the response.\n",
Use: "create",
Params: []resources.Param{
{
Name: "expand",
In: "query",
Description: "A comma-separated list of properties that can reveal additional information in the response. Supported fields are explained above.",
Type: "string",
},
},
HTTPMethod: "post",
RequiresBody: true,
Path: "/api/v2/teams",
})
}
42 changes: 42 additions & 0 deletions cmd/resource_cmds_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package cmd_test

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"ldcli/cmd"
"ldcli/internal/analytics"
)

func TestCreateTeam(t *testing.T) {
t.Run("help shows postTeam description", func(t *testing.T) {
args := []string{
"teams",
"create",
"--help",
}

output, err := cmd.CallCmd(t, cmd.APIClients{}, &analytics.NoopClient{}, args)

require.NoError(t, err)
assert.Contains(t, string(output), "Create a team.")
})
t.Run("with valid flags calls makeRequest function", func(t *testing.T) {
args := []string{
"teams",
"create",
"--access-token",
"abcd1234",
"--data",
`{"key": "team-key", "name": "Team Name"}`,
}

output, err := cmd.CallCmd(t, cmd.APIClients{}, &analytics.NoopClient{}, args)

require.NoError(t, err)
s := string(output)
assert.Contains(t, s, "would be making a request to /api/v2/teams here, with args: map[data:map[key:team-key name:Team Name] expand:]\n")
})
}
145 changes: 145 additions & 0 deletions cmd/resources/resources.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package resources

import (
"encoding/json"
"fmt"
"net/http"

"github.com/spf13/cobra"
"github.com/spf13/viper"

"ldcli/cmd/cliflags"
"ldcli/cmd/validators"
)

func NewResourceCmd(parentCmd *cobra.Command, resourceName, shortDescription, longDescription string) *cobra.Command {
cmd := &cobra.Command{
Use: resourceName,
Short: shortDescription,
Long: longDescription,
//TODO: add tracking here
}

parentCmd.AddCommand(cmd)

return cmd
}

type OperationData struct {
Short string
Long string
Use string
Params []Param
HTTPMethod string
RequiresBody bool
Path string
}

type Param struct {
Name string
In string
Description string
Type string
}

type OperationCmd struct {
OperationData
client *http.Client
cmd *cobra.Command
}

func (op *OperationCmd) initFlags() error {
if op.RequiresBody {
op.cmd.Flags().StringP(cliflags.DataFlag, "d", "", "Input data in JSON")
err := op.cmd.MarkFlagRequired(cliflags.DataFlag)
if err != nil {
return err
}
err = viper.BindPFlag(cliflags.DataFlag, op.cmd.Flags().Lookup(cliflags.DataFlag))
if err != nil {
return err
}
}

for _, p := range op.Params {
shorthand := fmt.Sprintf(p.Name[0:1]) // todo: how do we handle potential dupes
switch p.Type {
case "string":
op.cmd.Flags().StringP(p.Name, shorthand, "", p.Description)
case "int":
op.cmd.Flags().IntP(p.Name, shorthand, 0, p.Description)
case "boolean":
op.cmd.Flags().BoolP(p.Name, shorthand, false, p.Description)
}

if p.In == "path" {
err := op.cmd.MarkFlagRequired(p.Name)
if err != nil {
return err
}
}

err := viper.BindPFlag(p.Name, op.cmd.Flags().Lookup(p.Name))
if err != nil {
return err
}
}
return nil
}

func (op *OperationCmd) makeRequest(cmd *cobra.Command, args []string) error {
paramVals := map[string]interface{}{}

if op.RequiresBody {
var data interface{}
// TODO: why does viper.GetString(cliflags.DataFlag) not work?
err := json.Unmarshal([]byte(cmd.Flags().Lookup(cliflags.DataFlag).Value.String()), &data)
if err != nil {
return err
}
paramVals[cliflags.DataFlag] = data
}

for _, p := range op.Params {
var val interface{}
switch p.Type {
case "string":
val = viper.GetString(p.Name)
case "boolean":
val = viper.GetBool(p.Name)
case "int":
val = viper.GetInt(p.Name)
}

if val != nil {
paramVals[p.Name] = val
}
}

fmt.Fprintf(cmd.OutOrStdout(), "would be making a request to %s here, with args: %s\n", op.Path, paramVals)

return nil
}

func NewOperationCmd(parentCmd *cobra.Command, client *http.Client, op OperationData) *cobra.Command {
opCmd := OperationCmd{
OperationData: op,
client: client,
}

cmd := &cobra.Command{
Args: validators.Validate(),
Long: op.Long,
RunE: opCmd.makeRequest,
Short: op.Short,
Use: op.Use,
//TODO: add tracking here
}

opCmd.cmd = cmd
opCmd.initFlags()

parentCmd.AddCommand(cmd)

return cmd
}
6 changes: 6 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package cmd
import (
"fmt"
"log"
"net/http"
"os"
"strings"
"time"

"github.com/spf13/cobra"
"github.com/spf13/viper"
Expand All @@ -28,6 +30,7 @@ type APIClients struct {
FlagsClient flags.Client
MembersClient members.Client
ProjectsClient projects.Client
GenericClient *http.Client
}

func NewRootCommand(
Expand Down Expand Up @@ -131,6 +134,8 @@ func NewRootCommand(
cmd.AddCommand(projectsCmd)
cmd.AddCommand(NewQuickStartCmd(clients.EnvironmentsClient, clients.FlagsClient))

addAllResourceCmds(cmd, clients.GenericClient)

return cmd, nil
}

Expand All @@ -140,6 +145,7 @@ func Execute(analyticsTracker analytics.Tracker, version string) {
FlagsClient: flags.NewClient(version),
MembersClient: members.NewClient(version),
ProjectsClient: projects.NewClient(version),
GenericClient: &http.Client{Timeout: time.Second * 3},
}
rootCmd, err := NewRootCommand(
analyticsTracker,
Expand Down

0 comments on commit b8dc52a

Please sign in to comment.