From 949a308f51c1949d87950aaf46541350bcef2050 Mon Sep 17 00:00:00 2001 From: fujiwara Date: Thu, 9 May 2024 14:34:06 +0900 Subject: [PATCH 1/3] use alecthomas/kong --- go.mod | 1 + go.sum | 8 +++++++ main.go | 72 ++++++++++++++++++++++++++++++--------------------------- 3 files changed, 47 insertions(+), 34 deletions(-) diff --git a/go.mod b/go.mod index fbece47..d1dfff0 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/fujiwara/aws-sdk-client-go go 1.21.0 require ( + github.com/alecthomas/kong v0.9.0 github.com/aws/aws-sdk-go-v2 v1.26.1 github.com/aws/aws-sdk-go-v2/config v1.27.11 github.com/goccy/go-yaml v1.11.3 diff --git a/go.sum b/go.sum index 56fc01d..43d3805 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,9 @@ +github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU= +github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA= +github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= github.com/aws/aws-sdk-go-v2/config v1.27.11 h1:f47rANd2LQEYHda2ddSCKYId18/8BhSRM4BULGmfgNA= @@ -36,6 +42,8 @@ github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I= github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= diff --git a/main.go b/main.go index 78d7f3d..12eda54 100644 --- a/main.go +++ b/main.go @@ -5,54 +5,54 @@ import ( "context" "encoding/json" "fmt" + "io" "os" "sort" "strings" + "github.com/alecthomas/kong" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" ) var clientMethods = make(map[string]func(context.Context, aws.Config, json.RawMessage) (any, error)) +type CLI struct { + Service string `arg:"" help:"service name" default:""` + Method string `arg:"" help:"method name" default:""` + Input string `arg:"" help:"input JSON" default:"{}"` + Compact bool `short:"c" help:"compact JSON output"` +} + func Run(ctx context.Context) error { - var err error - awsCfg, err := config.LoadDefaultConfig(ctx) - if err != nil { - return err - } - switch len(os.Args) { - case 1: - return listServices() - case 2: - pkgName := os.Args[1] - return listMethods(pkgName) - case 3: - pkgName := os.Args[1] - methodName := os.Args[2] - input := json.RawMessage(`{}`) - return dispatchMethod(ctx, awsCfg, pkgName, methodName, input) - case 4: - pkgName := os.Args[1] - methodName := os.Args[2] - input := json.RawMessage(os.Args[3]) - return dispatchMethod(ctx, awsCfg, pkgName, methodName, input) - default: - return fmt.Errorf("too many args") + var c CLI + kong.Parse(&c) + if c.Service == "" { + return c.listServices(ctx) + } else if c.Method == "" { + return c.listMethods(ctx) + } else { + return c.dispatchMethod(ctx) } } -func dispatchMethod(ctx context.Context, awsCfg aws.Config, pkgName, methodName string, in json.RawMessage) error { - key := buildKey(pkgName, methodName) - if bytes.Equal(in, []byte(`help`)) { +func (c *CLI) dispatchMethod(ctx context.Context) error { + key := buildKey(c.Service, c.Method) + if c.Input == "help" { fmt.Printf("See https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/%s\n", key) return nil } + fn := clientMethods[key] if fn == nil { return fmt.Errorf("unknown function %s", key) } - out, err := fn(ctx, awsCfg, in) + + awsCfg, err := config.LoadDefaultConfig(ctx) + if err != nil { + return err + } + out, err := fn(ctx, awsCfg, json.RawMessage(c.Input)) if err != nil { return err } @@ -60,18 +60,22 @@ func dispatchMethod(ctx context.Context, awsCfg aws.Config, pkgName, methodName if err != nil { return fmt.Errorf("failed to marshal response: %w", err) } - var buf bytes.Buffer - json.Indent(&buf, b, "", " ") - buf.WriteTo(os.Stdout) - fmt.Fprintln(os.Stdout) + if !c.Compact { + var buf bytes.Buffer + json.Indent(&buf, b, "", " ") + buf.WriteTo(os.Stdout) + fmt.Fprintln(os.Stdout) + } else { + io.WriteString(os.Stdout, string(b)) + } return nil } -func listMethods(pkgName string) error { +func (c *CLI) listMethods(_ context.Context) error { methods := make([]string, 0) for name := range clientMethods { service, method := parseKey(name) - if service == pkgName { + if service == c.Service { methods = append(methods, method) } } @@ -82,7 +86,7 @@ func listMethods(pkgName string) error { return nil } -func listServices() error { +func (c *CLI) listServices(_ context.Context) error { services := make(map[string]struct{}) for name := range clientMethods { service, _ := parseKey(name) From 33856a911c44257d2718e2cd3d1352d9195919f8 Mon Sep 17 00:00:00 2001 From: fujiwara Date: Thu, 9 May 2024 15:14:09 +0900 Subject: [PATCH 2/3] add tests --- Makefile | 2 +- cli_test.go | 126 +++++++++++++++++++++++++++++++++++++++++++++++++ export_test.go | 9 ++++ main.go | 39 ++++++++++----- 4 files changed, 162 insertions(+), 14 deletions(-) create mode 100644 cli_test.go create mode 100644 export_test.go diff --git a/Makefile b/Makefile index 142c178..311f696 100644 --- a/Makefile +++ b/Makefile @@ -16,4 +16,4 @@ gen: go fmt test: - go test -v ./... + go test -v . diff --git a/cli_test.go b/cli_test.go new file mode 100644 index 0000000..8862725 --- /dev/null +++ b/cli_test.go @@ -0,0 +1,126 @@ +package sdkclient_test + +import ( + "bytes" + "context" + "encoding/json" + "io" + "testing" + + "github.com/alecthomas/kong" + "github.com/aws/aws-sdk-go-v2/aws" + sdkclient "github.com/fujiwara/aws-sdk-client-go" +) + +func init() { + sdkclient.SetClientMethod("foo#Client.List", func(_ context.Context, _ aws.Config, _ json.RawMessage) (any, error) { + return []string{"a", "b", "c"}, nil + }) + sdkclient.SetClientMethod("foo#Client.Get", func(_ context.Context, _ aws.Config, _ json.RawMessage) (any, error) { + return struct{ Name string }{Name: "foo"}, nil + }) + sdkclient.SetClientMethod("bar#Client.List", func(_ context.Context, _ aws.Config, _ json.RawMessage) (any, error) { + return []string{"x", "y", "z"}, nil + }) + sdkclient.SetClientMethod("bar#Client.Get", func(_ context.Context, _ aws.Config, _ json.RawMessage) (any, error) { + return struct{ Name string }{Name: "bar"}, nil + }) + sdkclient.SetClientMethod("baz#Client.Echo", func(_ context.Context, _ aws.Config, b json.RawMessage) (any, error) { + var v any + err := json.Unmarshal(b, &v) + return v, err + }) +} + +type TestCase struct { + Name string + Args []string + Expect string + IsError bool +} + +var TestCases = []TestCase{ + { + Name: "no args (list services)", + Args: []string{}, + Expect: "bar\nbaz\nfoo\n", + }, + { + Name: "list methods of foo", + Args: []string{"foo"}, + Expect: "Get\nList\n", + }, + { + Name: "list methods of bar", + Args: []string{"bar"}, + Expect: "Get\nList\n", + }, + { + Name: "list methods of baz", + Args: []string{"baz"}, + Expect: "Echo\n", + }, + { + Name: "call foo#Client.List", + Args: []string{"foo", "List"}, + Expect: "[\n \"a\",\n \"b\",\n \"c\"\n]\n", + }, + { + Name: "call foo#Client.Get", + Args: []string{"foo", "Get"}, + Expect: "{\n \"Name\": \"foo\"\n}\n", + }, + { + Name: "call foo#Client.List", + Args: []string{"foo", "List", "help"}, + Expect: "See https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/foo#Client.List\n", + }, + { + Name: "call foo#Client.List -c", + Args: []string{"foo", "List", "-c"}, + Expect: `["a","b","c"]`, + }, + { + Name: "call bar#Client.Get --compact", + Args: []string{"bar", "Get", "--compact"}, + Expect: `{"Name":"bar"}`, + }, + { + Name: "call baz#Client.Echo", + Args: []string{"baz", "Echo", `{"Example": "value"}`}, + Expect: "{\n \"Example\": \"value\"\n}\n", + }, +} + +func TestRun(t *testing.T) { + for _, tc := range TestCases { + ctx := context.Background() + t.Run(tc.Name, func(t *testing.T) { + buf := &bytes.Buffer{} + c, err := newCLI(tc.Args, buf) + if err != nil { + t.Fatal(err) + } + if err := c.Dispatch(ctx); err != nil { + t.Fatal(err) + } + if got := buf.String(); got != tc.Expect { + t.Errorf("unexpected output: got %q, expect %q", got, tc.Expect) + } + }) + } +} + +func newCLI(args []string, w io.Writer) (*sdkclient.CLI, error) { + c := &sdkclient.CLI{} + c.SetWriter(w) + p, err := kong.New(c) + if err != nil { + return nil, err + } + _, err = p.Parse(args) + if err != nil { + return nil, err + } + return c, nil +} diff --git a/export_test.go b/export_test.go new file mode 100644 index 0000000..1cf4151 --- /dev/null +++ b/export_test.go @@ -0,0 +1,9 @@ +package sdkclient + +func SetClientMethod(key string, fn ClientMethod) { + clientMethods[key] = fn +} + +func ClientMethods() map[string]ClientMethod { + return clientMethods +} diff --git a/main.go b/main.go index 12eda54..6363ee2 100644 --- a/main.go +++ b/main.go @@ -15,31 +15,44 @@ import ( "github.com/aws/aws-sdk-go-v2/config" ) -var clientMethods = make(map[string]func(context.Context, aws.Config, json.RawMessage) (any, error)) +var clientMethods = make(map[string]ClientMethod) + +type ClientMethod func(context.Context, aws.Config, json.RawMessage) (any, error) type CLI struct { Service string `arg:"" help:"service name" default:""` Method string `arg:"" help:"method name" default:""` Input string `arg:"" help:"input JSON" default:"{}"` Compact bool `short:"c" help:"compact JSON output"` + + w io.Writer } func Run(ctx context.Context) error { var c CLI + c.w = os.Stdout kong.Parse(&c) + return c.Dispatch(ctx) +} + +func (c *CLI) Dispatch(ctx context.Context) error { if c.Service == "" { - return c.listServices(ctx) + return c.ListServices(ctx) } else if c.Method == "" { - return c.listMethods(ctx) + return c.ListMethods(ctx) } else { - return c.dispatchMethod(ctx) + return c.CallMethod(ctx) } } -func (c *CLI) dispatchMethod(ctx context.Context) error { +func (c *CLI) SetWriter(w io.Writer) { + c.w = w +} + +func (c *CLI) CallMethod(ctx context.Context) error { key := buildKey(c.Service, c.Method) if c.Input == "help" { - fmt.Printf("See https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/%s\n", key) + fmt.Fprintf(c.w, "See https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/%s\n", key) return nil } @@ -63,15 +76,15 @@ func (c *CLI) dispatchMethod(ctx context.Context) error { if !c.Compact { var buf bytes.Buffer json.Indent(&buf, b, "", " ") - buf.WriteTo(os.Stdout) - fmt.Fprintln(os.Stdout) + buf.WriteString("\n") + buf.WriteTo(c.w) } else { - io.WriteString(os.Stdout, string(b)) + io.WriteString(c.w, string(b)) } return nil } -func (c *CLI) listMethods(_ context.Context) error { +func (c *CLI) ListMethods(_ context.Context) error { methods := make([]string, 0) for name := range clientMethods { service, method := parseKey(name) @@ -81,12 +94,12 @@ func (c *CLI) listMethods(_ context.Context) error { } sort.Strings(methods) for _, name := range methods { - fmt.Println(name) + fmt.Fprintln(c.w, name) } return nil } -func (c *CLI) listServices(_ context.Context) error { +func (c *CLI) ListServices(_ context.Context) error { services := make(map[string]struct{}) for name := range clientMethods { service, _ := parseKey(name) @@ -98,7 +111,7 @@ func (c *CLI) listServices(_ context.Context) error { } sort.Strings(names) for _, name := range names { - fmt.Println(name) + fmt.Fprintln(c.w, name) } return nil } From e018b8c5952b5094fc8faa304f93e8f07dc6107f Mon Sep 17 00:00:00 2001 From: fujiwara Date: Thu, 9 May 2024 15:20:28 +0900 Subject: [PATCH 3/3] fix CI --- .github/workflows/test.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index acdd4d9..d9c003d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,10 +20,11 @@ jobs: - name: Check out code into the Go module directory uses: actions/checkout@v4 - - name: Build & Test + - name: Test + run: | + go test -v . + + - name: Build run: | go generate ./cmd/aws-sdk-client-gen . go build -o aws-sdk-client-go ./cmd/aws-sdk-client-go/main.go - go test ./... - env: - GO111MODULE: on