Pulumi Component Resource - IaC Reusable Block
Like Terraform, Pulumi [1] is also an IaC (infrastructure-as-code)tool. It takes a programming-first approach to infrastructure management and configuration. That way, one can leverage the complete power of the programming language for infra needs. It has providers for all major platforms like AWS, GCP, Azure, K8s It supports multiple popular languages like Go, Python, Typescript, Java. In this article, we will focus on Pulumi's reusable abstraction: Component Resource [2]
Prerequisite
Install Pulumi, Golang, AWS Cli on your local system.
Component Resource
Resource are building blocks in Pulumi. Component Resource is a logical grouping of one or more than one resource.
It can help with enforcing standardizations, guard-rails & proper-tagging of resources
Here we will go with a simple exercise of creating a component-resource in Pulumi using Golang.
This creates a simple hello-world lambda in aws.
Once defined, we can create as many instances of custom-resource (lambda in this case) as we want.
For this example, we shall be using Golang.
We will go with the following steps.
- Lambda Handler Definition
- Component Resource Definition: Lambda + IAM (Role + Policies)
- Component Resource Instantiation: How To Use Component Resource
1. Lambda Handler Definition
package main
import (
"context"
"encoding/json"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
func handler(ctx context.Context, req events.APIGatewayV2HTTPRequest) (events.APIGatewayV2HTTPResponse, error) {
body := map[string]any{
"message": "hello world 👋",
"path": req.RawPath,
}
b, _ := json.Marshal(body)
return events.APIGatewayV2HTTPResponse{
StatusCode: 200,
Headers: map[string]string{
"content-type": "application/json",
},
Body: string(b),
}, nil
}
func main() {
lambda.Start(handler)
}
2. Component Resource Definition: Lambda + IAM (Role + Policies)
package components
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/iam"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/lambda"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
type SimpleLambdaArgs struct {
PathToBinary string
}
type SimpleLambdaComponent struct {
pulumi.ResourceState // important for pulumi's state tracking
Function *lambda.Function `pulumi:"function"`
}
func NewSimpleLambdaComponent(ctx *pulumi.Context, name string, args *SimpleLambdaArgs, opts ...pulumi.ResourceOption) (*SimpleLambdaComponent, error) {
component := &SimpleLambdaComponent{}
// register the component with pulumi engine
err := ctx.RegisterComponentResource("pkg:component:simple:lambda", name, component, opts...)
if err != nil {
return nil, err
}
// 3. Create an IAM Role for the Lambda
role, err := iam.NewRole(ctx, "iam-role-"+name, &iam.RoleArgs{AssumeRolePolicy: pulumi.String(`
{
"Version": "2012-10-17",
"Statement": [{
"Action": "sts:AssumeRole",
"Principal": {"Service": "lambda.amazonaws.com"},
"Effect": "Allow"
}]
}
`)}, pulumi.Parent(component))
if err != nil {
return nil, err
}
// 4. Attach the Basic Execution Policy (for logging to CloudWatch)
_, err = iam.NewRolePolicyAttachment(ctx, name+"-policy", &iam.RolePolicyAttachmentArgs{
Role: role.Name,
PolicyArn: pulumi.String("arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"),
}, pulumi.Parent(component))
if err != nil {
return nil, err
}
component.Function, err = lambda.NewFunction(ctx, name+"-lambda", &lambda.FunctionArgs{
Role: role.Arn,
Runtime: pulumi.String("provided.al2023"), // Modern Go runtime
Handler: pulumi.String("bootstrap"), // AWS standard handler name
Code: pulumi.NewAssetArchive(map[string]interface{}{
"bootstrap": pulumi.NewFileAsset(args.PathToBinary),
}),
}, pulumi.Parent(component))
if err != nil {
return nil, err
}
// This ensures outputs are resolved and visible.
if err := ctx.RegisterResourceOutputs(component, pulumi.Map{
"functionName": component.Function.Name,
"functionArn": component.Function.Arn,
}); err != nil {
return nil, err
}
return component, nil
}
3. Component Resource Instantiation: How To Use Component Resource
package main
import (
pkg "lambda-hello/components"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// Instantiate our custom Component
myLambda, err := pkg.NewSimpleLambdaComponent(ctx, "simple-lambda", &pkg.SimpleLambdaArgs{
PathToBinary: "./lambda/bin/bootstrap",
})
if err != nil {
return err
}
// Export the function name so we can test it later
ctx.Export("lambdaName", myLambda.Function.Name)
return nil
})
}
Running pulumi up on local system, we get
