Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ValidationException missing message on Lambda function creation #4733

Closed
jar-b opened this issue Feb 20, 2023 · 3 comments · Fixed by #4740
Closed

ValidationException missing message on Lambda function creation #4733

jar-b opened this issue Feb 20, 2023 · 3 comments · Fixed by #4740
Assignees
Labels
bug This issue is a bug. p2 This is a standard priority issue

Comments

@jar-b
Copy link
Contributor

jar-b commented Feb 20, 2023

Describe the bug

While attempting to create a Lambda function with an invalid configuration (ex. timeout above the maximum limit), the ValidationException message body appears to be dropped. The same input with the AWS Go SDK V2 or AWS CLI returns a complete error.

The absence of messaging makes debugging invalid configurations difficult, as documented in hashicorp/terraform-provider-aws#13709

Go SDK V1

Show Source

main.go:

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/lambda"
)

func main() {
	ctx := context.Background()
	sess := session.Must(session.NewSession())
	b, err := os.ReadFile("java11-hello-example.zip")
	if err != nil {
		log.Fatal(err)
	}

	svc := lambda.New(sess)
	out, err := svc.CreateFunctionWithContext(ctx, &lambda.CreateFunctionInput{
		Code: &lambda.FunctionCode{
			ZipFile: b,
		},
		FunctionName: aws.String("jb-test-aws-sdk-v1"),
		Role:         aws.String("arn:aws:iam::<redacted>:role/jb-test-lambda"),
		Runtime:      aws.String("java11"),
		Handler:      aws.String("example.Hello::handleReqest"),
		Timeout:      aws.Int64(2000), // To trigger a validation error (max is 900)

	})
	if err != nil {
		fmt.Printf("err type: %T\n", err)
		fmt.Printf("err.Error(): %s\n", err.Error())
		os.Exit(1)
	}
	fmt.Printf("output: %v\n", out)
}
$ go run main.go
err type: *awserr.requestError
err.Error(): ValidationException:
        status code: 400, request id: bb9c1aa8-5116-454f-a3d8-ed6a0f334f73

Go SDK V2

Show Source

main.go:

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/lambda"
	"github.com/aws/aws-sdk-go-v2/service/lambda/types"
)

func main() {
	ctx := context.Background()
	cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("us-west-2"))
	if err != nil {
		log.Fatalf("unable to load SDK config, %v", err)
	}
	b, err := os.ReadFile("java11-hello-example.zip")
	if err != nil {
		log.Fatalf("unable to load SDK config, %v", err)
	}

	client := lambda.NewFromConfig(cfg)
	out, err := client.CreateFunction(ctx, &lambda.CreateFunctionInput{
		Code: &types.FunctionCode{
			ZipFile: b,
		},
		FunctionName: aws.String("jb-test-aws-sdk-v2"),
		Role:         aws.String("arn:aws:iam::<redacted>:role/jb-test-lambda"),
		Runtime:      types.RuntimeJava11,
		Handler:      aws.String("example.Hello::handleReqest"),
		Timeout:      aws.Int32(2000), // To trigger a validation error (max is 900)
	})
	if err != nil {
		fmt.Printf("err type: %T\n", err)
		fmt.Printf("err.Error(): %s\n", err.Error())
		os.Exit(1)
	}
	fmt.Printf("output: %v\n", out)
}
$ go run main.go
err type: *smithy.OperationError
err.Error(): operation error Lambda: CreateFunction, https response error StatusCode: 400, RequestID: ec664013-b83f-4884-aed0-caf099f2dd9d, api error ValidationException: 1 validation error detected: Value '2000' at 'timeout' failed to satisfy constraint: Member must have value less than or equal to 900

AWS CLI

main.sh:

aws lambda create-function \
    --zip-file "fileb://java11-hello-example.zip" \
    --function-name "jb-test-aws-cli" \
    --role "arn:aws:iam::<redacted>:role/jb-test-lambda" \
    --runtime "java11" \
    --handler "example.Hello::handleRequest" \
    --timeout 2000 # To trigger violation error (max is 900)
$ ./main.sh
An error occurred (ValidationException) when calling the CreateFunction operation: 1 validation error detected: Value '2000' at 'timeout' failed to satisfy constraint: Member must have value less than or equal to 900

Expected Behavior

A complete error message, including details about which attribute caused the violation, to be returned.

Current Behavior

The error message body is empty.

Reproduction Steps

  1. Run main.go from the "Go SDK V1" section above.
  2. Observe the missing error message.

Possible Solution

No response

Additional Information/Context

This is a highly upvoted issue in the Terraform AWS Provider repository: hashicorp/terraform-provider-aws#13709

SDK version used

v1.44.204

Environment details (Version of Go (go version)? OS name and version, etc.)

go version go1.19.6 darwin/arm64

@jar-b jar-b added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Feb 20, 2023
@RanVaknin RanVaknin self-assigned this Feb 21, 2023
@RanVaknin RanVaknin added investigating This issue is being investigated and/or work is in progress to resolve the issue. and removed needs-triage This issue or PR still needs to be triaged. labels Feb 21, 2023
@RanVaknin
Copy link
Contributor

Analysis:
The Lambda service is returning a ValidationException which is not a modeled error.
For RestJSON, we don't account for the body if the error is unmodeled, and instead rely only on the msg field parsed from the response header.

func (u *UnmarshalTypedError) UnmarshalError(
resp *http.Response,
respMeta protocol.ResponseMetadata,
) (error, error) {
code := resp.Header.Get(errorTypeHeader)
msg := resp.Header.Get(errorMessageHeader)
body := resp.Body
if len(code) == 0 {
// If unable to get code from HTTP headers have to parse JSON message
// to determine what kind of exception this will be.
var buf bytes.Buffer
var jsonErr jsonErrorResponse
teeReader := io.TeeReader(resp.Body, &buf)
err := jsonutil.UnmarshalJSONError(&jsonErr, teeReader)
if err != nil {
return nil, err
}
body = ioutil.NopCloser(&buf)
code = jsonErr.Code
msg = jsonErr.Message
}
// If code has colon separators remove them so can compare against modeled
// exception names.
code = strings.SplitN(code, ":", 2)[0]
if fn, ok := u.exceptions[code]; ok {
// If exception code is know, use associated constructor to get a value
// for the exception that the JSON body can be unmarshaled into.
v := fn(respMeta)
if err := jsonutil.UnmarshalJSONCaseInsensitive(v, body); err != nil {
return nil, err
}
if err := rest.UnmarshalResponse(resp, v, true); err != nil {
return nil, err
}
return v, nil
}
// fallback to unmodeled generic exceptions
return awserr.NewRequestFailure(
awserr.New(code, msg, nil),
respMeta.StatusCode,
respMeta.RequestID,
), nil
}

Possible fix:

+ 	// account for error message in body when error is unmodeled
+	if msg == "" {
+		if b, err := io.ReadAll(body); err == nil {
+			msg = string(b)
+		}
+	}
+
	// fallback to unmodeled generic exceptions
	return awserr.NewRequestFailure(
		awserr.New(code, msg, nil),
		respMeta.StatusCode,
		respMeta.RequestID,
	), nil
}

We also need to update test cases to account for it.

@jar-b since we are shifting focus to the v2 SDK, we are going to assign lower priority for this.
If you or one of the participants in the original request want to take a stab on a cutting a PR I'll have someone review it.

Thanks,
Ran

@RanVaknin RanVaknin added needs-review This issue or pull request needs review from a core team member. p2 This is a standard priority issue and removed investigating This issue is being investigated and/or work is in progress to resolve the issue. labels Feb 22, 2023
@jar-b
Copy link
Contributor Author

jar-b commented Feb 27, 2023

Thanks for the investigation @RanVaknin. I've opened #4740 which I believe addresses the issue while preserving backwards compatibility (at least with existing test cases).

We've upgraded to the AWS SDK V2 for the lambda function use case documented in the original issue, but because this impacts any unmodeled error message it may still be worth patching. We have a sizable AWS SDK V1 footprint in the AWS Terraform Provider, and it may benefit other less visible services as well.

@lucix-aws lucix-aws removed the needs-review This issue or pull request needs review from a core team member. label Mar 15, 2023
@github-actions
Copy link

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see.
If you need more assistance, please either tag a team member or open a new issue that references this one.
If you wish to keep having a conversation with other community members under this issue feel free to do so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug This issue is a bug. p2 This is a standard priority issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants