Anyone have a AWS lambda development->deploymen...
# aws
a
Anyone have a AWS lambda development->deployment experience they're happy with? I historically use AWS SAM w/ VS Code for local development. This allows me to easily build and test lambda functions locally Then it seems (AI answer) I should upload a CloudFormation template to S3 and point Pulumi at it, but I'd rather have all my infra build files checked into the codebase as opposed to a mix of local files and S3.
f
I mostly deploy software as containerized lambdas. I build the container image in github actions, then use pulumi to deploy the image as a lambda.
I'm using ECR to store the lambdas
a
Thanks Kenji!! I think the downside there is without using AWS SAM, you also have to manually specify roles, API gateways etc.If you have an example template you use I'd be most grateful!
f
Roles yes. I'm not using api gateways for my lambdas since they're mainly getting triggered by schedules or events. here's a sample
Copy code
/**
 * Create a container lambda function.
 *
 * In addition to creating the lambda function, this function also
 * - creates a role for the function with a set of base permissions
 *  as well as any specified in `extraPolicyStatements`
 * - creates an invoke config that defines the retry policy for the function.
 * - associates the function with the event rule for triggering
 *
 * @param name name of function
 * @param args arguments for creating the function
 * @returns function
 */
export function createContainerLambda(
  name: string,
  {
    imageUri,
    imageConfig,
    description = '',
    extraPolicyStatements = [],
    ephemeralStorageMb = 512,
    fileSystemConfig,
    memorySizeMb = 128,
    timeoutSeconds = 300,
    maximumRetryAttempts = 0,
    maximumEventAgeInSeconds = (maximumRetryAttempts + 1) *
      (60 + timeoutSeconds),
    eventRule,
    onSuccessDestination,
    onFailureDestination,
    environment = {},
    vpcConfig,
    tags = {},
  }: ContainerLambdaArgs
) {
  const lambdaRole = createLambdaRole(name, extraPolicyStatements, tags)

  const lambdaFunction = new aws.lambda.Function(`${name}-function`, {
    description: description || `lambda function for ${name}`,
    role: lambdaRole.arn,
    packageType: 'Image',
    architectures: ['x86_64'],
    imageUri,
    imageConfig,
    ephemeralStorage: {
      size: ephemeralStorageMb,
    },
    fileSystemConfig,
    memorySize: memorySizeMb,
    environment: {
      variables: environment,
    },
    timeout: timeoutSeconds,
    vpcConfig: vpcConfig,
    publish: true,
    tags: tags,
  })

  const destinationConfig: inputs.lambda.FunctionEventInvokeConfigDestinationConfig =
    {}

  if (onSuccessDestination) {
    destinationConfig['onSuccess'] = {
      destination: onSuccessDestination,
    }
  }
  if (onFailureDestination) {
    destinationConfig['onFailure'] = {
      destination: onFailureDestination,
    }
  }

  new aws.lambda.FunctionEventInvokeConfig(`${name}-invoke`, {
    functionName: lambdaFunction.name,
    maximumRetryAttempts: maximumRetryAttempts,
    maximumEventAgeInSeconds: maximumEventAgeInSeconds,
    destinationConfig,
  })

  if (eventRule) {
    eventRule.onEvent(`${name}-event`, lambdaFunction)
  }

  return lambdaFunction
}

/**
 * Creates a role for a lambda function with a set of base permissions as well
 * as any specified in `extraPolicyStatements`.
 *
 * @param name  name of the lambda function
 * @param extraPolicyStatements extra policy statements to add to the role
 * @param tags tags to associate with resources that accept them
 * @returns role
 */
export function createLambdaRole(
  name: string,
  extraPolicyStatements: inputs.iam.GetPolicyDocumentStatement[],
  tags: aws.Tags
) {
  const lambdaRole = new aws.iam.Role(`${name}-role`, {
    assumeRolePolicy: {
      Version: '2012-10-17',
      Statement: [
        {
          Action: 'sts:AssumeRole',
          Principal: {
            Service: ['lambda.amazonaws.com', 'edgelambda.amazonaws.com'],
          },
          Effect: 'Allow',
          Sid: '',
        },
      ],
    },
    tags,
  })

  /**
   * Base policy statements for lambda
   */
  const basePolicyStatements: inputs.iam.GetPolicyDocumentStatement[] = [
    {
      actions: [
        // cloudwatch logs
        'logs:CreateLogGroup',
        'logs:CreateLogStream',
        'logs:PutLogEvents',
        // read/write s3
        's3:GetObject',
        's3:GetObjectAttributes',
        's3:PutObject',
        's3:ListBucket',
        // needed for vpc access
        'ec2:DescribeNetworkInterfaces',
        'ec2:CreateNetworkInterface',
        'ec2:DeleteNetworkInterface',
        // cloudwatch metrics
        'cloudwatch:PutMetricData',
        // get secrets
        'secretsmanager:GetSecretValue',
        'secretsmanager:DescribeSecret',
        // needed for ecr
        'ecr:GetRepositoryPolicy',
        'ecr:SetRepositoryPolicy',
        // needed to publish to sns
        'sns:Publish',
        // needed to send messages to sqs
        'sqs:SendMessage',
      ],
      resources: ['*'],
      effect: 'Allow',
    },
  ]
  const policyDoc = aws.iam.getPolicyDocumentOutput({
    statements: basePolicyStatements.concat(extraPolicyStatements),
  })

  const policy = new aws.iam.Policy(`${name}-policy`, {
    path: '/',
    description: 'Allow lambda to write logs and read/write objects in s3',
    policy: policyDoc.json,
    tags,
  })

  new aws.iam.RolePolicyAttachment(`${name}-policy-attachment`, {
    role: lambdaRole,
    policyArn: policy.arn,
  })
  return lambdaRole
}