package main import ( "fmt" "github.com/aws/aws-cdk-go/awscdk/v2" "github.com/aws/aws-cdk-go/awscdk/v2/awscloudfront" "github.com/aws/aws-cdk-go/awscdk/v2/awscloudfrontorigins" "github.com/aws/aws-cdk-go/awscdk/v2/awsdynamodb" "github.com/aws/aws-cdk-go/awscdk/v2/awsiam" "github.com/aws/aws-cdk-go/awscdk/v2/awslambda" "github.com/aws/aws-cdk-go/awscdk/v2/awss3" "github.com/aws/aws-cdk-go/awscdk/v2/awss3deployment" awslambdago "github.com/aws/aws-cdk-go/awscdklambdagoalpha/v2" "github.com/aws/constructs-go/constructs/v10" "github.com/aws/jsii-runtime-go" ) type CounterStackProps struct { awscdk.StackProps } func NewCounterStack(scope constructs.Construct, id string, props *CounterStackProps) awscdk.Stack { var sprops awscdk.StackProps if props != nil { sprops = props.StackProps } stack := awscdk.NewStack(scope, &id, &sprops) // Create a global count database. db := awsdynamodb.NewTable(stack, jsii.String("count"), &awsdynamodb.TableProps{ PartitionKey: &awsdynamodb.Attribute{ Name: jsii.String("_pk"), Type: awsdynamodb.AttributeType_STRING, }, BillingMode: awsdynamodb.BillingMode_PAY_PER_REQUEST, // Change this for production systems. RemovalPolicy: awscdk.RemovalPolicy_DESTROY, TimeToLiveAttribute: jsii.String("_ttl"), }) // Strip the binary, and remove the deprecated Lambda SDK RPC code for performance. // These options are not required, but make cold start faster. bundlingOptions := &awslambdago.BundlingOptions{ GoBuildFlags: &[]*string{jsii.String(`-ldflags "-s -w" -tags lambda.norpc`)}, } f := awslambdago.NewGoFunction(stack, jsii.String("handler"), &awslambdago.GoFunctionProps{ Runtime: awslambda.Runtime_PROVIDED_AL2(), MemorySize: jsii.Number(1024), Architecture: awslambda.Architecture_ARM_64(), Entry: jsii.String("../lambda"), Bundling: bundlingOptions, Environment: &map[string]*string{ "TABLE_NAME": db.TableName(), }, }) // Grant DB access. db.GrantReadWriteData(f) // Add a Function URL. lambdaURL := f.AddFunctionUrl(&awslambda.FunctionUrlOptions{ AuthType: awslambda.FunctionUrlAuthType_NONE, }) awscdk.NewCfnOutput(stack, jsii.String("lambdaFunctionUrl"), &awscdk.CfnOutputProps{ ExportName: jsii.String("lambdaFunctionUrl"), Value: lambdaURL.Url(), }) assetsBucket := awss3.NewBucket(stack, jsii.String("assets"), &awss3.BucketProps{ BlockPublicAccess: awss3.BlockPublicAccess_BLOCK_ALL(), Encryption: awss3.BucketEncryption_S3_MANAGED, EnforceSSL: jsii.Bool(true), RemovalPolicy: awscdk.RemovalPolicy_DESTROY, Versioned: jsii.Bool(false), }) // Allow CloudFront to read from the bucket. cfOAI := awscloudfront.NewOriginAccessIdentity(stack, jsii.String("cfnOriginAccessIdentity"), &awscloudfront.OriginAccessIdentityProps{}) cfs := awsiam.NewPolicyStatement(&awsiam.PolicyStatementProps{}) cfs.AddActions(jsii.String("s3:GetBucket*")) cfs.AddActions(jsii.String("s3:GetObject*")) cfs.AddActions(jsii.String("s3:List*")) cfs.AddResources(assetsBucket.BucketArn()) cfs.AddResources(jsii.String(fmt.Sprintf("%v/*", *assetsBucket.BucketArn()))) cfs.AddCanonicalUserPrincipal(cfOAI.CloudFrontOriginAccessIdentityS3CanonicalUserId()) assetsBucket.AddToResourcePolicy(cfs) // Add a CloudFront distribution to route between the public directory and the Lambda function URL. lambdaURLDomain := awscdk.Fn_Select(jsii.Number(2), awscdk.Fn_Split(jsii.String("/"), lambdaURL.Url(), nil)) lambdaOrigin := awscloudfrontorigins.NewHttpOrigin(lambdaURLDomain, &awscloudfrontorigins.HttpOriginProps{ ProtocolPolicy: awscloudfront.OriginProtocolPolicy_HTTPS_ONLY, }) cf := awscloudfront.NewDistribution(stack, jsii.String("customerFacing"), &awscloudfront.DistributionProps{ DefaultBehavior: &awscloudfront.BehaviorOptions{ AllowedMethods: awscloudfront.AllowedMethods_ALLOW_ALL(), Origin: lambdaOrigin, CachedMethods: awscloudfront.CachedMethods_CACHE_GET_HEAD(), OriginRequestPolicy: awscloudfront.OriginRequestPolicy_ALL_VIEWER_EXCEPT_HOST_HEADER(), CachePolicy: awscloudfront.CachePolicy_CACHING_DISABLED(), ViewerProtocolPolicy: awscloudfront.ViewerProtocolPolicy_REDIRECT_TO_HTTPS, }, PriceClass: awscloudfront.PriceClass_PRICE_CLASS_100, }) // Add /assets* to the distribution backed by S3. assetsOrigin := awscloudfrontorigins.NewS3Origin(assetsBucket, &awscloudfrontorigins.S3OriginProps{ // Get content from the / directory in the bucket. OriginPath: jsii.String("/"), OriginAccessIdentity: cfOAI, }) cf.AddBehavior(jsii.String("/assets*"), assetsOrigin, nil) // Export the domain. awscdk.NewCfnOutput(stack, jsii.String("cloudFrontDomain"), &awscdk.CfnOutputProps{ ExportName: jsii.String("cloudfrontDomain"), Value: cf.DomainName(), }) // Deploy the contents of the ./assets directory to the S3 bucket. awss3deployment.NewBucketDeployment(stack, jsii.String("assetsDeployment"), &awss3deployment.BucketDeploymentProps{ DestinationBucket: assetsBucket, Sources: &[]awss3deployment.ISource{ awss3deployment.Source_Asset(jsii.String("../assets"), nil), }, DestinationKeyPrefix: jsii.String("assets"), Distribution: cf, DistributionPaths: jsii.Strings("/assets*"), }) return stack } func main() { defer jsii.Close() app := awscdk.NewApp(nil) NewCounterStack(app, "CounterStack", &CounterStackProps{}) app.Synth(nil) }