diff --git a/docker-compose.yml b/docker-compose.yml index 2619722b..b976defc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,12 +12,14 @@ services: - DIR=create environment: AWS_REGION: eu-west-1 - AWS_ACCESS_KEY_ID: localstack AWS_DYNAMODB_ENDPOINT: http://localstack:4566 AWS_EVENTBRIDGE_ENDPOINT: http://localstack:4566 + AWS_S3_ENDPOINT: http://localstack:4566 + AWS_ACCESS_KEY_ID: localstack AWS_SECRET_ACCESS_KEY: localstack DDB_TABLE_NAME_DEEDS: deeds DDB_TABLE_NAME_CHANGES: changes + S3_BUCKET_NAME_ORIGINAL: opg-lpa-store-static-eu-west-1 EVENT_BUS_NAME: local-main JWT_SECRET_KEY: ${JWT_SECRET_KEY:-secret} volumes: @@ -79,6 +81,8 @@ services: localstack: image: localstack/localstack:3.1 + ports: + - "4566:4566" volumes: - "./localstack/init:/etc/localstack/init/ready.d" - "./localstack/wait:/scripts/wait" diff --git a/go.mod b/go.mod index e0271f1d..392b0da0 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.13.2 github.com/aws/aws-sdk-go-v2/service/dynamodb v1.29.0 github.com/aws/aws-sdk-go-v2/service/eventbridge v1.29.3 + github.com/aws/aws-sdk-go-v2/service/s3 v1.50.3 github.com/aws/aws-xray-sdk-go v1.8.3 github.com/golang-jwt/jwt/v5 v5.2.0 github.com/google/go-cmp v0.6.0 @@ -22,6 +23,7 @@ require ( require ( github.com/andybalholm/brotli v1.0.6 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.0 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.0 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.1 // indirect @@ -29,8 +31,10 @@ require ( github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.1 // indirect github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.19.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.1 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.19.0 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.22.0 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.27.0 // indirect diff --git a/go.sum b/go.sum index 78953a52..ab0d0161 100644 --- a/go.sum +++ b/go.sum @@ -8,26 +8,20 @@ github.com/aws/aws-sdk-go v1.50.20 h1:xfAnSDVf/azIWTVQXQODp89bubvCS85r70O3nuQ4dn github.com/aws/aws-sdk-go v1.50.20/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aws/aws-sdk-go-v2 v1.25.1 h1:P7hU6A5qEdmajGwvae/zDkOq+ULLC9tQBTwqqiwFGpI= github.com/aws/aws-sdk-go-v2 v1.25.1/go.mod h1:Evoc5AsmtveRt1komDwIsjHFyrP5tDuF1D1U+6z6pNo= -github.com/aws/aws-sdk-go-v2/config v1.26.6 h1:Z/7w9bUqlRI0FFQpetVuFYEsjzE3h7fpU6HuGmfPL/o= -github.com/aws/aws-sdk-go-v2/config v1.26.6/go.mod h1:uKU6cnDmYCvJ+pxO9S4cWDb2yWWIH5hra+32hVh1MI4= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 h1:gTK2uhtAPtFcdRRJilZPx8uJLL2J85xK11nKtWL0wfU= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1/go.mod h1:sxpLb+nZk7tIfCWChfd+h4QwHNUR57d8hA1cleTkjJo= github.com/aws/aws-sdk-go-v2/config v1.27.0 h1:J5sdGCAHuWKIXLeXiqr8II/adSvetkx0qdZwdbXXpb0= github.com/aws/aws-sdk-go-v2/config v1.27.0/go.mod h1:cfh8v69nuSUohNFMbIISP2fhmblGmYEOKs5V53HiHnk= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0= github.com/aws/aws-sdk-go-v2/credentials v1.17.0 h1:lMW2x6sKBsiAJrpi1doOXqWFyEPoE886DTb1X0wb7So= github.com/aws/aws-sdk-go-v2/credentials v1.17.0/go.mod h1:uT41FIH8cCIxOdUYIL0PYyHlL1NoneDuDSCwg5VE/5o= github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.13.2 h1:ksCAKvVacJbsCJAUWaCk4ZS254NByOKlB8V4dGVWC9c= github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.13.2/go.mod h1:vtaNpWHO0v6kWfS27bLuU9dklVj1YmdY/uSc4FqhBE0= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.0 h1:xWCwjjvVz2ojYTP4kBKUuUh9ZrXfcAXpflhOUUeXg1k= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.0/go.mod h1:j3fACuqXg4oMTQOR2yY7m0NmJY0yBK4L4sLsRXq1Ins= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.1 h1:evvi7FbTAoFxdP/mixmP7LIYzQWAmzBcwNB/es9XPNc= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.1/go.mod h1:rH61DT6FDdikhPghymripNUCsf+uVF4Cnk4c4DBKH64= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.1 h1:RAnaIrbxPtlXNVI/OIlh1sidTQ3e1qM6LRjs7N0bE0I= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.1/go.mod h1:nbgAGkH5lk0RZRMh6A4K/oG6Xj11eC/1CyDow+DUAFI= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 h1:n3GDfwqF2tzEkXlv5cuy4iy7LpKDtqDMcNLfZDu9rls= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.1 h1:rtYJd3w6IWCTVS8vmMaiXjW198noh2PBm5CiXyJea9o= @@ -38,22 +32,20 @@ github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.19.1 h1:Wd1F42HO5ZJ+auc4 github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.19.1/go.mod h1:0FgUg08+1knEoYHo0pa8ogm7D9sjH79lHnRzCNGk/6Q= github.com/aws/aws-sdk-go-v2/service/eventbridge v1.29.3 h1:m/JoWWQI/4Ka9WqTgv9ZupD2zePqVMW8PLmLwr+fiGg= github.com/aws/aws-sdk-go-v2/service/eventbridge v1.29.3/go.mod h1:efCw7VuDRT7Jzj75Tu4Wfx6Pm5Yh6JR2SPSoL7FI1CM= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.0 h1:a33HuFlO0KsveiP90IUJh8Xr/cx9US2PqkSroaLc+o8= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.0/go.mod h1:SxIkWpByiGbhbHYTo9CMTUnx2G4p4ZQMrDPcRRy//1c= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.0 h1:SHN/umDLTmFTmYfI+gkanz6da3vK8Kvj/5wkqnTHbuA= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.0/go.mod h1:l8gPU5RYGOFHJqWEpPMoRTP0VoaWQSkJdKo+hwWnnDA= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 h1:EyBZibRTVAs6ECHZOw5/wlylS9OcTzwyjeQMudmREjE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1/go.mod h1:JKpmtYhhPs7D97NL/ltqz7yCkERFW5dOlHyVl66ZYF8= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.1 h1:5Wxh862HkXL9CbQ83BIkWKLIgQapGeuh5zG2G9OZtQk= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.1/go.mod h1:V7GLA01pNUxMCYSQsibdVrqUrNIYIT/9lCOyR8ExNvQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.1 h1:cVP8mng1RjDyI3JN/AXFCn5FHNlsBaBH0/MBtG1bg0o= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.1/go.mod h1:C8sQjoyAsdfjC7hpy4+S6B92hnFzx0d0UAyHicaOTIE= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.1 h1:OYmmIcyw19f7x0qLBLQ3XsrCZSSyLhxd9GXng5evsN4= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.1/go.mod h1:s5rqdn74Vdg10k61Pwf4ZHEApOSD6CKRe6qpeHDq32I= +github.com/aws/aws-sdk-go-v2/service/s3 v1.50.3 h1:Cv/HH7sLzEdJMYQi4MCNHxZeyubQNOOIdVc0VU0lo3Q= +github.com/aws/aws-sdk-go-v2/service/s3 v1.50.3/go.mod h1:lTW7O4iMAnO2o7H3XJTvqaWFZCH6zIPs+eP7RdG/yp0= github.com/aws/aws-sdk-go-v2/service/sso v1.19.0 h1:u6OkVDxtBPnxPkZ9/63ynEe+8kHbtS5IfaC4PzVxzWM= github.com/aws/aws-sdk-go-v2/service/sso v1.19.0/go.mod h1:YqbU3RS/pkDVu+v+Nwxvn0i1WB0HkNWEePWbmODEbbs= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.22.0 h1:6DL0qu5+315wbsAEEmzK+P9leRwNbkp+lGjPC+CEvb8= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.22.0/go.mod h1:olUAyg+FaoFaL/zFaeQQONjOZ9HXoxgvI/c7mQTYz7M= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= github.com/aws/aws-sdk-go-v2/service/sts v1.27.0 h1:cjTRjh700H36MQ8M0LnDn33W3JmwC77mdxIIyPWCdpM= github.com/aws/aws-sdk-go-v2/service/sts v1.27.0/go.mod h1:nXfOBMWPokIbOY+Gi7a1psWMSvskUCemZzI+SMB7Akc= github.com/aws/aws-xray-sdk-go v1.8.3 h1:S8GdgVncBRhzbNnNUgTPwhEqhwt2alES/9rLASyhxjU= diff --git a/internal/ddb/client.go b/internal/ddb/client.go index fe130916..a367cc48 100644 --- a/internal/ddb/client.go +++ b/internal/ddb/client.go @@ -18,12 +18,12 @@ type Client struct { } func (c *Client) PutChanges(ctx context.Context, data any, update shared.Update) error { - changesItem, err := dynamodbattribute.MarshalMap(map[string]interface{}{ - "uid": update.Uid, + changesItem, _ := dynamodbattribute.MarshalMap(map[string]interface{}{ + "uid": update.Uid, "applied": update.Applied, - "author": update.Author, - "type": update.Type, - "change": update.Changes, + "author": update.Author, + "type": update.Type, + "change": update.Changes, }) item, err := dynamodbattribute.MarshalMap(data) @@ -94,7 +94,7 @@ func (c *Client) Get(ctx context.Context, uid string) (shared.Lpa, error) { return lpa, err } -func New(endpoint, tableName string, changesTableName string) *Client { +func New(endpoint, tableName, changesTableName string) *Client { sess := session.Must(session.NewSession()) sess.Config.Endpoint = &endpoint diff --git a/internal/event/client.go b/internal/event/client.go index e3793318..ffd6e832 100644 --- a/internal/event/client.go +++ b/internal/event/client.go @@ -23,7 +23,7 @@ type Client struct { func NewClient(cfg aws.Config, eventBusName string) *Client { return &Client{ - svc: eventbridge.NewFromConfig(cfg, func (o *eventbridge.Options) { + svc: eventbridge.NewFromConfig(cfg, func(o *eventbridge.Options) { o.BaseEndpoint = aws.String(os.Getenv("AWS_EVENTBRIDGE_ENDPOINT")) }), eventBusName: eventBusName, @@ -40,13 +40,13 @@ func (c *Client) send(ctx context.Context, eventType string, detail any) error { if err != nil { return err } - + _, err = c.svc.PutEvents(ctx, &eventbridge.PutEventsInput{ Entries: []types.PutEventsRequestEntry{{ EventBusName: aws.String(c.eventBusName), - Source: aws.String(source), - DetailType: aws.String(eventType), - Detail: aws.String(string(v)), + Source: aws.String(source), + DetailType: aws.String(eventType), + Detail: aws.String(string(v)), }}, }) diff --git a/internal/event/client_test.go b/internal/event/client_test.go index ffee2f20..66242cc2 100644 --- a/internal/event/client_test.go +++ b/internal/event/client_test.go @@ -28,18 +28,18 @@ func TestClientSendEvent(t *testing.T) { ctx := context.Background() expectedError := errors.New("err") - event := LpaUpdated{ Uid: "M-1234-1234-1234", ChangeType: "CREATED" } + event := LpaUpdated{Uid: "M-1234-1234-1234", ChangeType: "CREATED"} data, _ := json.Marshal(event) mockClient := &mockEventBridgeClient{} mockClient.On("PutEvents", mock.Anything, &eventbridge.PutEventsInput{ - Entries: []types.PutEventsRequestEntry{{ - EventBusName: aws.String("my-bus"), - Source: aws.String("opg.poas.lpastore"), - DetailType: aws.String("lpa-updated"), - Detail: aws.String(string(data)), - }}, - }). + Entries: []types.PutEventsRequestEntry{{ + EventBusName: aws.String("my-bus"), + Source: aws.String("opg.poas.lpastore"), + DetailType: aws.String("lpa-updated"), + Detail: aws.String(string(data)), + }}, + }). Return(nil, expectedError) svc := &Client{svc: mockClient, eventBusName: "my-bus"} diff --git a/internal/objectstore/client.go b/internal/objectstore/client.go new file mode 100644 index 00000000..799d8bf5 --- /dev/null +++ b/internal/objectstore/client.go @@ -0,0 +1,48 @@ +package objectstore + +import ( + "bytes" + "context" + "encoding/json" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/aws/aws-sdk-go-v2/service/s3/types" +) + +type awsS3Client interface { + PutObject(ctx context.Context, input *s3.PutObjectInput, opts ...func(*s3.Options)) (*s3.PutObjectOutput, error) +} + +type S3Client struct { + bucketName string + awsClient awsS3Client +} + +func (c *S3Client) Put(ctx context.Context, objectKey string, obj any) error { + b, err := json.Marshal(obj) + if err != nil { + return err + } + + _, err = c.awsClient.PutObject( + ctx, + &s3.PutObjectInput{ + Bucket: aws.String(c.bucketName), + Key: aws.String(objectKey), + Body: bytes.NewReader(b), + ServerSideEncryption: types.ServerSideEncryptionAwsKms, + }, + ) + + return err +} + +func NewS3Client(awsConfig aws.Config, bucketName string) *S3Client { + awsClient := s3.NewFromConfig(awsConfig) + + return &S3Client{ + bucketName: bucketName, + awsClient: awsClient, + } +} diff --git a/internal/objectstore/client_test.go b/internal/objectstore/client_test.go new file mode 100644 index 00000000..550cbed5 --- /dev/null +++ b/internal/objectstore/client_test.go @@ -0,0 +1,34 @@ +package objectstore + +import ( + "context" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +type mockAwsClient struct { + mock.Mock +} + +func (m *mockAwsClient) PutObject(ctx context.Context, input *s3.PutObjectInput, opts ...func(*s3.Options)) (*s3.PutObjectOutput, error) { + args := m.Called(ctx, input) + return args.Get(0).(*s3.PutObjectOutput), args.Error(1) +} + +func TestPut(t *testing.T) { + client := mockAwsClient{} + client.On("PutObject", mock.Anything, mock.Anything, mock.Anything).Return(&s3.PutObjectOutput{}, nil) + + c := S3Client{ + bucketName: "bucket1", + awsClient: &client, + } + + err := c.Put(context.Background(), "anobject", struct{ ID int }{ID: 1}) + + assert.Equal(t, nil, err) + client.AssertExpectations(t) +} diff --git a/internal/shared/lpa.go b/internal/shared/lpa.go index e20f3ad9..0b8a5c3a 100644 --- a/internal/shared/lpa.go +++ b/internal/shared/lpa.go @@ -1,6 +1,8 @@ package shared -import "time" +import ( + "time" +) type LpaInit struct { LpaType LpaType `json:"lpaType"` diff --git a/internal/shared/problem.go b/internal/shared/problem.go index 890a1883..bbe4e194 100644 --- a/internal/shared/problem.go +++ b/internal/shared/problem.go @@ -16,9 +16,9 @@ type FieldError struct { } type Problem struct { - StatusCode int `json:"-"` - Code string `json:"code"` - Detail string `json:"detail"` + StatusCode int `json:"-"` + Code string `json:"code"` + Detail string `json:"detail"` Errors []FieldError `json:"errors,omitempty"` } diff --git a/lambda/create/main.go b/lambda/create/main.go index fc9d49a2..1b2f82bc 100644 --- a/lambda/create/main.go +++ b/lambda/create/main.go @@ -3,14 +3,17 @@ package main import ( "context" "encoding/json" + "fmt" "os" "time" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/ministryofjustice/opg-data-lpa-store/internal/ddb" "github.com/ministryofjustice/opg-data-lpa-store/internal/event" + "github.com/ministryofjustice/opg-data-lpa-store/internal/objectstore" "github.com/ministryofjustice/opg-data-lpa-store/internal/shared" "github.com/ministryofjustice/opg-go-common/logging" ) @@ -28,15 +31,20 @@ type Store interface { Get(ctx context.Context, uid string) (shared.Lpa, error) } +type S3Client interface { + Put(ctx context.Context, objectKey string, obj any) error +} + type Verifier interface { VerifyHeader(events.APIGatewayProxyRequest) (*shared.LpaStoreClaims, error) } type Lambda struct { - eventClient EventClient - store Store - verifier Verifier - logger Logger + eventClient EventClient + staticLpaStorage S3Client + store Store + verifier Verifier + logger Logger } func (l *Lambda) HandleEvent(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { @@ -77,10 +85,9 @@ func (l *Lambda) HandleEvent(ctx context.Context, req events.APIGatewayProxyRequ } // validation - errors := Validate(input) - if len(errors) > 0 { + if errs := Validate(input); len(errs) > 0 { problem := shared.ProblemInvalidRequest - problem.Errors = errors + problem.Errors = errs return problem.Respond() } @@ -91,16 +98,22 @@ func (l *Lambda) HandleEvent(ctx context.Context, req events.APIGatewayProxyRequ data.UpdatedAt = time.Now() // save - err = l.store.Put(ctx, data) + if err = l.store.Put(ctx, data); err != nil { + l.logger.Print(err) + return shared.ProblemInternalServerError.Respond() + } - if err != nil { + // save to static storage as JSON + objectKey := fmt.Sprintf("%s/donor-executed-lpa.json", data.Uid) + + if err = l.staticLpaStorage.Put(ctx, objectKey, data); err != nil { l.logger.Print(err) return shared.ProblemInternalServerError.Respond() } // send lpa-updated event err = l.eventClient.SendLpaUpdated(ctx, event.LpaUpdated{ - Uid: uid, + Uid: uid, ChangeType: "CREATED", }) @@ -117,19 +130,48 @@ func (l *Lambda) HandleEvent(ctx context.Context, req events.APIGatewayProxyRequ func main() { logger := logging.New(os.Stdout, "opg-data-lpa-store") + + // set endpoint to "" outside dev to use default AWS resolver + endpointURL := os.Getenv("AWS_S3_ENDPOINT") + + var endpointResolverWithOptions aws.EndpointResolverWithOptions + if endpointURL != "" { + endpointResolverWithOptions = aws.EndpointResolverWithOptionsFunc( + func(service, region string, options ...interface{}) (aws.Endpoint, error) { + return aws.Endpoint{URL: endpointURL, HostnameImmutable: true}, nil + }, + ) + } + ctx := context.Background() - awsConfig, err := config.LoadDefaultConfig(ctx) + + eventClientConfig, err := config.LoadDefaultConfig(ctx) if err != nil { - logger.Print("Failed to load configuration:", err) + logger.Print("Failed to load event client configuration:", err) + } + + s3Config, err := config.LoadDefaultConfig( + ctx, + func(o *config.LoadOptions) error { + o.EndpointResolverWithOptions = endpointResolverWithOptions + return nil + }, + ) + if err != nil { + logger.Print("Failed to load S3 configuration:", err) } l := &Lambda{ - eventClient: event.NewClient(awsConfig, os.Getenv("EVENT_BUS_NAME")), - store: ddb.New( + eventClient: event.NewClient(eventClientConfig, os.Getenv("EVENT_BUS_NAME")), + store: ddb.New( os.Getenv("AWS_DYNAMODB_ENDPOINT"), os.Getenv("DDB_TABLE_NAME_DEEDS"), os.Getenv("DDB_TABLE_NAME_CHANGES"), ), + staticLpaStorage: objectstore.NewS3Client( + s3Config, + os.Getenv("S3_BUCKET_NAME_ORIGINAL"), + ), verifier: shared.NewJWTVerifier(), logger: logger, } diff --git a/lambda/get/main.go b/lambda/get/main.go index f0cf203d..a5fe6781 100644 --- a/lambda/get/main.go +++ b/lambda/get/main.go @@ -73,7 +73,7 @@ func (l *Lambda) HandleEvent(ctx context.Context, event events.APIGatewayProxyRe func main() { l := &Lambda{ - store: ddb.New( + store: ddb.New( os.Getenv("AWS_DYNAMODB_ENDPOINT"), os.Getenv("DDB_TABLE_NAME_DEEDS"), os.Getenv("DDB_TABLE_NAME_CHANGES"), diff --git a/lambda/update/main.go b/lambda/update/main.go index b7d161d4..b5dac94e 100644 --- a/lambda/update/main.go +++ b/lambda/update/main.go @@ -34,10 +34,10 @@ type Verifier interface { } type Lambda struct { - eventClient EventClient - store Store - verifier Verifier - logger Logger + eventClient EventClient + store Store + verifier Verifier + logger Logger } func (l *Lambda) HandleEvent(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { @@ -103,7 +103,7 @@ func (l *Lambda) HandleEvent(ctx context.Context, req events.APIGatewayProxyRequ // send lpa-updated event err = l.eventClient.SendLpaUpdated(ctx, event.LpaUpdated{ - Uid: lpa.Uid, + Uid: lpa.Uid, ChangeType: "UPDATED", }) @@ -122,12 +122,12 @@ func main() { ctx := context.Background() awsConfig, err := config.LoadDefaultConfig(ctx) if err != nil { - logger.Print("Failed to load configuration:", err) + logger.Print("Failed to load configuration:", err) } l := &Lambda{ eventClient: event.NewClient(awsConfig, os.Getenv("EVENT_BUS_NAME")), - store: ddb.New( + store: ddb.New( os.Getenv("AWS_DYNAMODB_ENDPOINT"), os.Getenv("DDB_TABLE_NAME_DEEDS"), os.Getenv("DDB_TABLE_NAME_CHANGES"), diff --git a/lambda/update/main_test.go b/lambda/update/main_test.go index 75bdfc51..0b232b50 100644 --- a/lambda/update/main_test.go +++ b/lambda/update/main_test.go @@ -10,10 +10,10 @@ import ( "time" "github.com/aws/aws-lambda-go/events" + "github.com/golang-jwt/jwt/v5" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/golang-jwt/jwt/v5" "github.com/ministryofjustice/opg-data-lpa-store/internal/event" "github.com/ministryofjustice/opg-data-lpa-store/internal/shared" "github.com/ministryofjustice/opg-go-common/logging" @@ -61,7 +61,7 @@ func (m *mockStore) PutChanges(ctx context.Context, data any, update shared.Upda type mockVerifier struct { claims shared.LpaStoreClaims - err error + err error } func (m *mockVerifier) VerifyHeader(events.APIGatewayProxyRequest) (*shared.LpaStoreClaims, error) { @@ -74,15 +74,15 @@ func TestHandleEvent(t *testing.T) { client.On("SendLpaUpdated", mock.Anything, mock.Anything).Return(nil) l := Lambda{ eventClient: &client, - store: store, - verifier: &mockVerifier{ + store: store, + verifier: &mockVerifier{ claims: shared.LpaStoreClaims{ RegisteredClaims: jwt.RegisteredClaims{ Subject: "1234", }, }, }, - logger: logging.New(io.Discard, ""), + logger: logging.New(io.Discard, ""), } resp, err := l.HandleEvent(context.Background(), events.APIGatewayProxyRequest{ @@ -108,9 +108,9 @@ func TestHandleEvent(t *testing.T) { assert.True(t, cmp.Equal( shared.Update{ - Uid: "1", + Uid: "1", Author: "1234", - Type: "CERTIFICATE_PROVIDER_SIGN", + Type: "CERTIFICATE_PROVIDER_SIGN", Changes: []shared.Change{ shared.Change{ Key: "/certificateProvider/signedAt", @@ -132,9 +132,9 @@ func TestHandleEvent(t *testing.T) { func TestHandleEventWhenUnknownType(t *testing.T) { l := Lambda{ - store: &mockStore{get: shared.Lpa{Uid: "1"}}, - verifier: &mockVerifier{}, - logger: logging.New(io.Discard, ""), + store: &mockStore{get: shared.Lpa{Uid: "1"}}, + verifier: &mockVerifier{}, + logger: logging.New(io.Discard, ""), } resp, err := l.HandleEvent(context.Background(), events.APIGatewayProxyRequest{ @@ -147,9 +147,9 @@ func TestHandleEventWhenUnknownType(t *testing.T) { func TestHandleEventWhenUpdateInvalid(t *testing.T) { l := Lambda{ - store: &mockStore{get: shared.Lpa{Uid: "1"}}, - verifier: &mockVerifier{}, - logger: logging.New(io.Discard, ""), + store: &mockStore{get: shared.Lpa{Uid: "1"}}, + verifier: &mockVerifier{}, + logger: logging.New(io.Discard, ""), } resp, err := l.HandleEvent(context.Background(), events.APIGatewayProxyRequest{ @@ -225,15 +225,15 @@ func TestHandleEventWhenSendLpaUpdatedFailed(t *testing.T) { l := Lambda{ eventClient: &client, - store: store, - verifier: &mockVerifier{ + store: store, + verifier: &mockVerifier{ claims: shared.LpaStoreClaims{ RegisteredClaims: jwt.RegisteredClaims{ Subject: "1234", }, }, }, - logger: &logger, + logger: &logger, } resp, err := l.HandleEvent(context.Background(), events.APIGatewayProxyRequest{ diff --git a/terraform/environment/region/iam.tf b/terraform/environment/region/iam.tf index e1b56ecc..b0a707b3 100644 --- a/terraform/environment/region/iam.tf +++ b/terraform/environment/region/iam.tf @@ -23,7 +23,7 @@ data "aws_iam_policy_document" "lambda_dynamodb_policy" { } } -resource "aws_iam_role_policy" "lambda_s3" { +resource "aws_iam_role_policy" "lambda_s3_policy" { for_each = local.functions name = "LambdaAllowS3" role = module.lambda[each.key].iam_role.id @@ -40,7 +40,7 @@ data "aws_iam_policy_document" "lambda_s3_policy" { "${var.lpa_store_static_bucket.arn}/*", ] actions = [ - "s3:PutObject", + "s3:PutObject" ] } statement { diff --git a/terraform/environment/region/lambda.tf b/terraform/environment/region/lambda.tf index b50b7867..baee764d 100644 --- a/terraform/environment/region/lambda.tf +++ b/terraform/environment/region/lambda.tf @@ -17,10 +17,11 @@ module "lambda" { cloudwatch_kms_key_id = aws_kms_key.cloudwatch.arn environment_variables = { - DDB_TABLE_NAME_DEEDS = var.dynamodb_name - DDB_TABLE_NAME_CHANGES = var.dynamodb_name_changes - EVENT_BUS_NAME = var.event_bus.name - JWT_SECRET_KEY = "secret" + DDB_TABLE_NAME_DEEDS = var.dynamodb_name + DDB_TABLE_NAME_CHANGES = var.dynamodb_name_changes + EVENT_BUS_NAME = var.event_bus.name + S3_BUCKET_NAME_ORIGINAL = var.lpa_store_static_bucket.bucket + JWT_SECRET_KEY = "secret" } providers = {