I’m not a python expert but I believe you need to ...
# aws
p
I’m not a python expert but I believe you need to first resolve the s3_bucket.id. I’m mostly familiar with Typescript where we use Pulumi output so first we do either pulumi resolve or pulumi interpolate. When you run Pulumi it actually creates a plan it doesn’t create the resources so imagine if resource haven’t been created then how can it’s ID work it’s just like an asynchronous operation
j
Thank you @plain-eye-2667 Is there any workaround to generate a function text with the name of the bucket, and then create a lambda function based on it? In principle, the programming language is not important to me, if there is a ready-made solution in Typescript, I am ready to use it
p
@jolly-manchester-8306 here is an example:
Copy code
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Create an S3 bucket
const bucket = new aws.s3.Bucket("myBucket");

// IAM role for the Lambda function
const iamRole = new aws.iam.Role("lambdaEdgeRole", {
    assumeRolePolicy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Action: "sts:AssumeRole",
            Principal: {"Service": "<http://lambda.amazonaws.com|lambda.amazonaws.com>"},
            Effect: "Allow",
            Sid: "",
        }, {
            Action: "sts:AssumeRole",
            Principal: {"Service": "<http://edgelambda.amazonaws.com|edgelambda.amazonaws.com>"},
            Effect: "Allow",
            Sid: "",
        }],
    }),
});

// Attach the AWSLambdaBasicExecutionRole policy
new aws.iam.RolePolicyAttachment("lambdaExecutionPolicy", {
    role: iamRole,
    policyArn: "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
});

// Lambda@Edge function
const lambdaCode = bucket.id.apply(bucketId => `
    const AWS = require("aws-sdk");
    exports.handler = (event, context, callback) => {
        const s3 = new AWS.S3();
        const request = event.Records[0].cf.request;
        const uri = request.uri;
        const key = uri.substring(1);
        const bucketName = "${bucketId}";

        s3.headObject({
            Bucket: bucketName,
            Key: key,
        }, (err, data) => {
            if (err) {
                request.uri = '/index.html';
            }
            callback(null, request);
        });
    };
`);

const lambdaAtEdge = new aws.lambda.Function("myLambdaAtEdge", {
    code: new pulumi.asset.AssetArchive({
        "index.js": new pulumi.asset.StringAsset(lambdaCode),
    }),
    handler: "index.handler",
    role: iamRole.arn,
    runtime: aws.lambda.Runtime.NodeJS12dX, // Make sure to use a current supported runtime
    functionName: "myLambdaAtEdgeFunction",
    publish: true,
});

// Associate the Lambda function with CloudFront
const cloudFrontDistribution = new aws.cloudfront.Distribution("myDistribution", {
    origins: [{
        domainName: bucket.bucketRegionalDomainName,
        originId: bucket.arn,
    }],
    enabled: true,
    isIpv6Enabled: true,
    defaultRootObject: "index.html",
    defaultCacheBehavior: {
        targetOriginId: bucket.arn,
        viewerProtocolPolicy: "redirect-to-https",
        lambdaFunctionAssociations: [{
            eventType: "origin-request",
            lambdaArn: lambdaAtEdge.qualifiedArn,
        }],
    },
    restrictions: {
        geoRestriction: {
            restrictionType: "none",
        },
    },
    viewerCertificate: {
        cloudfrontDefaultCertificate: true,
    },
});

// Outputs
export const bucketName = bucket.id;
export const distributionId = cloudFrontDistribution.id;
If you need more assistance you can take help from here: https://www.pulumi.com/ai I haven't tested this but let me explain to you: 1. You should run it only in
pulumi
not as simple TS code 2. CloudFront is needed before you can have a lambda@edge that's why u see a CloudFront distribution 3. I am a bit doubtful in the code provided by ai in context of cache behavior but I hope it works
j
Thank you @plain-eye-2667! I understand the idea, but I'm getting an error for now - Type 'Output<string>' is not assignable to type 'string'. for line pulumi.asset.StringAsset(lambdaCode)
p
@jolly-manchester-8306 oh so that means StringAsset is not a
pulumi input
sorry my bad I didn't tested the code. I still can't test, the reason is I have mostly company workspaces/aws accounts so can't give it a try
Copy code
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Create an S3 bucket
const bucket = new aws.s3.Bucket("myBucket");

// IAM role for the Lambda function
const iamRole = new aws.iam.Role("lambdaEdgeRole", {
  assumeRolePolicy: JSON.stringify({
    Version: "2012-10-17",
    Statement: [{
      Action: "sts:AssumeRole",
      Principal: {"Service": "lambda.amazonaws.com"},
      Effect: "Allow",
      Sid: "",
    }, {
      Action: "sts:AssumeRole",
      Principal: {"Service": "edgelambda.amazonaws.com"},
      Effect: "Allow",
      Sid: "",
    }],
  }),
});

// Attach the AWSLambdaBasicExecutionRole policy
new aws.iam.RolePolicyAttachment("lambdaExecutionPolicy", {
  role: iamRole,
  policyArn: "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
});

bucket.id.apply(bucketId => {
  const lambdaCode = `
    const AWS = require("aws-sdk");
    exports.handler = (event, context, callback) => {
        const s3 = new AWS.S3();
        const request = event.Records[0].cf.request;
        const uri = request.uri;
        const key = uri.substring(1);
        const bucketName = "${bucketId}";

        s3.headObject({
            Bucket: bucketName,
            Key: key,
        }, (err, data) => {
            if (err) {
                request.uri = '/index.html';
            }
            callback(null, request);
        });
    };
`;


  const lambdaAtEdge = new aws.lambda.Function("myLambdaAtEdge", {
    code: new pulumi.asset.AssetArchive({
      "index.js": new pulumi.asset.StringAsset(lambdaCode),
    }),
    handler: "index.handler",
    role: iamRole.arn,
    runtime: aws.lambda.Runtime.NodeJS12dX, // Make sure to use a current supported runtime
    publish: true,
  });

// Associate the Lambda function with CloudFront
  const cloudFrontDistribution = new aws.cloudfront.Distribution("myDistribution", {
    origins: [{
      domainName: bucket.bucketRegionalDomainName,
      originId: bucket.arn,
    }],
    enabled: true,
    isIpv6Enabled: true,
    defaultRootObject: "index.html",
    defaultCacheBehavior: {
      targetOriginId: bucket.arn,
      viewerProtocolPolicy: "redirect-to-https",
      lambdaFunctionAssociations: [{
        eventType: "origin-request",
        lambdaArn: lambdaAtEdge.qualifiedArn,
      }],
      allowedMethods: ["GET", "HEAD"],
      cachedMethods: ["GET", "HEAD"],
    },
    restrictions: {
      geoRestriction: {
        restrictionType: "none",
      },
    },
    viewerCertificate: {
      cloudfrontDefaultCertificate: true,
    },
  });
});


// Outputs
export const bucketName = bucket.id;
This is going to behave a bit different the reason is that some resources will appear after the output of bucket.id is resolved.
j
Thank you very much @plain-eye-2667 ! It's working! Only for history 🙂 my code to put value to text:
Copy code
bucket = aws.s3.Bucket("myS3Bucket1")
def create_lambda(bucket_id):
    lambda_code = f'bucketName = "{bucket_id}";'
    lambda_at_edge = aws.lambda_.Function("myLambdaAtEdge2",
        code=pulumi.AssetArchive({
            "index.js": pulumi.StringAsset(lambda_code),
        }),
        handler="index.handler",
        role="arn:.........",
        runtime="python3.12",
        publish=True,
    )
    return lambda_at_edge.arn

my_lambda = bucket.id.apply(create_lambda)

pulumi.export("bucket_name", bucket.id)
pulumi.export("lambda_arn", my_lambda)