Skip to content

Commit 200b885

Browse files
authored
added lambda handler and event parser function (#4)
1 parent 76fba28 commit 200b885

File tree

8 files changed

+376
-55
lines changed

8 files changed

+376
-55
lines changed

iac/go-demo/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ var rootCmd = &cobra.Command{
2121
},
2222
}
2323

24-
func Handle(ctx context.Context, event json.RawMessage) (any, error) {
24+
func Handler(ctx context.Context, event json.RawMessage) (any, error) {
2525

2626
args := make([]string, 0, 10)
2727
err := json.Unmarshal(event, &args)
@@ -39,5 +39,5 @@ func Handle(ctx context.Context, event json.RawMessage) (any, error) {
3939
}
4040

4141
func main() {
42-
lambda.Start(Handle)
42+
lambda.Start(Handler)
4343
}

main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"os"
88

9+
"github.com/JayJamieson/cobra-lambda/wrapper"
910
lambda "github.com/JayJamieson/go-lambda-invoke"
1011
)
1112

@@ -41,7 +42,7 @@ func main() {
4142
err = lambda.InvokeSync(ctx, client, &lambda.InvokeInput{
4243
Name: funcName,
4344
Qualifier: "$LATEST",
44-
Payload: os.Args[3:],
45+
Payload: wrapper.CobraLambdaEvent{Args: os.Args[3:]},
4546
}, &output)
4647

4748
if err != nil {

types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package main
22

33
type ExecutionOutput struct {
4-
Stdout string
4+
Stdout string `json:"stdout"`
55
}

wrapper/buffer.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package wrapper
2+
3+
import (
4+
"bytes"
5+
"sync"
6+
)
7+
8+
type threadSafeBuffer struct {
9+
mu sync.Mutex
10+
buf bytes.Buffer
11+
}
12+
13+
func (t *threadSafeBuffer) Write(p []byte) (n int, err error) {
14+
t.mu.Lock()
15+
defer t.mu.Unlock()
16+
return t.buf.Write(p)
17+
}
18+
19+
func (t *threadSafeBuffer) String() string {
20+
t.mu.Lock()
21+
defer t.mu.Unlock()
22+
return t.buf.String()
23+
}

wrapper/lambda.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package wrapper
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"os"
7+
8+
"github.com/spf13/cobra"
9+
)
10+
11+
type CobraLambdaEvent struct {
12+
Args []string `json:"args"`
13+
}
14+
15+
type CobraLambdaFunc func(ctx context.Context, event json.RawMessage) (any, error)
16+
17+
func NewCobrLambdaHandler(cmd *cobra.Command) CobraLambdaFunc {
18+
return func(ctx context.Context, eventJSON json.RawMessage) (any, error) {
19+
lambda := &CobraLambda{
20+
cmd: cmd,
21+
ctx: ctx,
22+
originalStdout: os.Stdout,
23+
originalStderr: os.Stderr,
24+
}
25+
26+
event, err := UnmarshalEvent(eventJSON)
27+
28+
if err != nil {
29+
return nil, err
30+
}
31+
32+
return lambda.ExecuteContext(ctx, event.Args)
33+
}
34+
}
35+
36+
func UnmarshalEvent(eventJSON json.RawMessage) (*CobraLambdaEvent, error) {
37+
event := &CobraLambdaEvent{}
38+
39+
err := json.Unmarshal(eventJSON, event)
40+
41+
if err != nil {
42+
return nil, err
43+
}
44+
45+
return event, nil
46+
}

wrapper/lambda_test.go

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
package wrapper
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"strings"
8+
"testing"
9+
10+
"github.com/spf13/cobra"
11+
)
12+
13+
func TestNewCobrLambdaHandler_BasicExecution(t *testing.T) {
14+
// Create a test command
15+
var name string
16+
cmd := &cobra.Command{
17+
Use: "test",
18+
Short: "Test command",
19+
Run: func(cmd *cobra.Command, args []string) {
20+
cmd.Printf("Hello, %s!\n", name)
21+
fmt.Println("Command executed successfully")
22+
},
23+
}
24+
cmd.Flags().StringVar(&name, "name", "World", "Name to greet")
25+
26+
// Create the handler
27+
handler := NewCobrLambdaHandler(cmd)
28+
29+
// Create a test event
30+
event := CobraLambdaEvent{
31+
Args: []string{"--name", "Lambda"},
32+
}
33+
eventJSON, err := json.Marshal(event)
34+
if err != nil {
35+
t.Fatalf("Failed to marshal event: %v", err)
36+
}
37+
38+
// Execute the handler
39+
ctx := context.Background()
40+
result, err := handler(ctx, eventJSON)
41+
42+
if err != nil {
43+
t.Fatalf("Handler returned error: %v", err)
44+
}
45+
46+
// Check the result is an OutputCapture
47+
output, ok := result.(*OutputCapture)
48+
if !ok {
49+
t.Fatalf("Expected *OutputCapture, got %T", result)
50+
}
51+
52+
// Verify output
53+
if !strings.Contains(output.Stdout, "Hello, Lambda!") {
54+
t.Errorf("Expected output to contain 'Hello, Lambda!', got: %s", output.Stdout)
55+
}
56+
if !strings.Contains(output.Stdout, "Command executed successfully") {
57+
t.Errorf("Expected output to contain 'Command executed successfully', got: %s", output.Stdout)
58+
}
59+
}
60+
61+
func TestNewCobrLambdaHandler_InvalidJSON(t *testing.T) {
62+
cmd := &cobra.Command{
63+
Use: "test",
64+
Run: func(cmd *cobra.Command, args []string) {
65+
cmd.Println("This should not run")
66+
},
67+
}
68+
69+
handler := NewCobrLambdaHandler(cmd)
70+
71+
// Pass invalid JSON
72+
invalidJSON := json.RawMessage(`{"args": [invalid json}`)
73+
74+
ctx := context.Background()
75+
result, err := handler(ctx, invalidJSON)
76+
77+
if err == nil {
78+
t.Fatal("Expected error for invalid JSON, got nil")
79+
}
80+
81+
if result != nil {
82+
t.Errorf("Expected nil result on error, got: %v", result)
83+
}
84+
}
85+
86+
func TestNewCobrLambdaHandler_CommandError(t *testing.T) {
87+
// Create a command that returns an error
88+
cmd := &cobra.Command{
89+
Use: "test",
90+
RunE: func(cmd *cobra.Command, args []string) error {
91+
cmd.Println("About to fail")
92+
return fmt.Errorf("command execution failed")
93+
},
94+
}
95+
96+
handler := NewCobrLambdaHandler(cmd)
97+
98+
event := CobraLambdaEvent{
99+
Args: []string{},
100+
}
101+
eventJSON, err := json.Marshal(event)
102+
if err != nil {
103+
t.Fatalf("Failed to marshal event: %v", err)
104+
}
105+
106+
ctx := context.Background()
107+
result, err := handler(ctx, eventJSON)
108+
109+
if err == nil {
110+
t.Fatal("Expected error from command, got nil")
111+
}
112+
113+
if err.Error() != "command execution failed" {
114+
t.Errorf("Expected 'command execution failed', got: %v", err)
115+
}
116+
117+
// Result should still contain output capture
118+
output, ok := result.(*OutputCapture)
119+
if !ok {
120+
t.Fatalf("Expected *OutputCapture even on error, got %T", result)
121+
}
122+
123+
// Verify output was captured before error
124+
if !strings.Contains(output.Stdout, "About to fail") {
125+
t.Errorf("Expected output to be captured before error, got: %s", output.Stdout)
126+
}
127+
}
128+
129+
func TestNewCobrLambdaHandler_WithSubcommands(t *testing.T) {
130+
// Create a root command with subcommands
131+
rootCmd := &cobra.Command{
132+
Use: "root",
133+
}
134+
135+
var value string
136+
subCmd := &cobra.Command{
137+
Use: "process",
138+
Run: func(cmd *cobra.Command, args []string) {
139+
cmd.Printf("Processing: %s\n", value)
140+
for i, arg := range args {
141+
cmd.Printf("Arg %d: %s\n", i, arg)
142+
}
143+
},
144+
}
145+
subCmd.Flags().StringVar(&value, "value", "", "Value to process")
146+
147+
rootCmd.AddCommand(subCmd)
148+
149+
handler := NewCobrLambdaHandler(rootCmd)
150+
151+
event := CobraLambdaEvent{
152+
Args: []string{"process", "--value", "test-data", "extra", "args"},
153+
}
154+
eventJSON, err := json.Marshal(event)
155+
if err != nil {
156+
t.Fatalf("Failed to marshal event: %v", err)
157+
}
158+
159+
ctx := context.Background()
160+
result, err := handler(ctx, eventJSON)
161+
162+
if err != nil {
163+
t.Fatalf("Handler returned error: %v", err)
164+
}
165+
166+
output, ok := result.(*OutputCapture)
167+
if !ok {
168+
t.Fatalf("Expected *OutputCapture, got %T", result)
169+
}
170+
171+
if !strings.Contains(output.Stdout, "Processing: test-data") {
172+
t.Errorf("Expected subcommand output, got: %s", output.Stdout)
173+
}
174+
if !strings.Contains(output.Stdout, "Arg 0: extra") {
175+
t.Errorf("Expected args to be passed, got: %s", output.Stdout)
176+
}
177+
if !strings.Contains(output.Stdout, "Arg 1: args") {
178+
t.Errorf("Expected args to be passed, got: %s", output.Stdout)
179+
}
180+
}
181+
182+
func TestNewCobrLambdaHandler_EmptyArgs(t *testing.T) {
183+
executed := false
184+
cmd := &cobra.Command{
185+
Use: "test",
186+
Run: func(cmd *cobra.Command, args []string) {
187+
executed = true
188+
cmd.Println("Executed with no args")
189+
},
190+
}
191+
192+
handler := NewCobrLambdaHandler(cmd)
193+
194+
event := CobraLambdaEvent{
195+
Args: []string{},
196+
}
197+
eventJSON, err := json.Marshal(event)
198+
if err != nil {
199+
t.Fatalf("Failed to marshal event: %v", err)
200+
}
201+
202+
ctx := context.Background()
203+
result, err := handler(ctx, eventJSON)
204+
205+
if err != nil {
206+
t.Fatalf("Handler returned error: %v", err)
207+
}
208+
209+
if !executed {
210+
t.Error("Command was not executed")
211+
}
212+
213+
output, ok := result.(*OutputCapture)
214+
if !ok {
215+
t.Fatalf("Expected *OutputCapture, got %T", result)
216+
}
217+
218+
if !strings.Contains(output.Stdout, "Executed with no args") {
219+
t.Errorf("Expected output, got: %s", output.Stdout)
220+
}
221+
}
222+
223+
func TestNewCobrLambdaHandler_ContextPropagation(t *testing.T) {
224+
// Create a command that checks context
225+
receivedCtx := false
226+
cmd := &cobra.Command{
227+
Use: "test",
228+
Run: func(cmd *cobra.Command, args []string) {
229+
ctx := cmd.Context()
230+
if ctx != nil {
231+
if val := ctx.Value("test-key"); val == "test-value" {
232+
receivedCtx = true
233+
cmd.Println("Context received correctly")
234+
}
235+
}
236+
},
237+
}
238+
239+
handler := NewCobrLambdaHandler(cmd)
240+
241+
event := CobraLambdaEvent{
242+
Args: []string{},
243+
}
244+
eventJSON, err := json.Marshal(event)
245+
if err != nil {
246+
t.Fatalf("Failed to marshal event: %v", err)
247+
}
248+
249+
// Create context with value
250+
ctx := context.WithValue(context.Background(), "test-key", "test-value")
251+
result, err := handler(ctx, eventJSON)
252+
253+
if err != nil {
254+
t.Fatalf("Handler returned error: %v", err)
255+
}
256+
257+
if !receivedCtx {
258+
t.Error("Context was not properly propagated to command")
259+
}
260+
261+
output, ok := result.(*OutputCapture)
262+
if !ok {
263+
t.Fatalf("Expected *OutputCapture, got %T", result)
264+
}
265+
266+
if !strings.Contains(output.Stdout, "Context received correctly") {
267+
t.Errorf("Expected context propagation confirmation, got: %s", output.Stdout)
268+
}
269+
}

0 commit comments

Comments
 (0)