You can define a Pulumi component in one language and use it in another, allowing you to share infrastructure abstractions across different teams or projects without code duplication.
Let’s say you have a Pulumi project in TypeScript that defines a reusable component for a standard S3 bucket setup.
// s3-bucket-component/index.ts
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
export interface S3BucketComponentArgs {
bucketName: pulumi.Input<string>;
acl: pulumi.Input<string>;
}
export class S3BucketComponent extends pulumi.ComponentResource {
public readonly bucket: aws.s3.Bucket;
constructor(name: string, args: S3BucketComponentArgs, opts?: pulumi.ComponentResourceOptions) {
super("custom:S3BucketComponent", name, args, opts);
const bucket = new aws.s3.Bucket(`${name}-bucket`, {
bucket: args.bucketName,
acl: args.acl,
}, { parent: this });
this.bucket = bucket;
this.registerOutputs({
bucketName: bucket.bucket,
});
}
}
Now, you want to use this S3BucketComponent in a Python project. First, you need to package your TypeScript component into a Pulumi plugin. This involves building the TypeScript code and then creating a Pulumi.yaml file that describes your component as a provider.
Create a directory for your component, e.g., s3-bucket-component. Inside it, place your index.ts file and a package.json to manage dependencies.
// s3-bucket-component/package.json
{
"name": "pulumi-s3-bucket-component",
"version": "0.1.0",
"description": "A reusable S3 bucket component for Pulumi",
"main": "index.js",
"types": "index.d.ts",
"dependencies": {
"@pulumi/aws": "^6.0.0",
"@pulumi/pulumi": "^3.0.0"
},
"devDependencies": {
"@types/node": "^18.0.0",
"typescript": "^5.0.0"
},
"scripts": {
"build": "tsc"
}
}
Run npm install and then npm run build to compile the TypeScript to JavaScript and generate type definitions.
Next, create a Pulumi.yaml file in the root of your component project. This file tells Pulumi how to find and use your component as a provider.
# s3-bucket-component/Pulumi.yaml
name: s3-bucket-component
runtime: nodejs
description: A reusable S3 bucket component for Pulumi
Now, you need to build this component into a Pulumi plugin. This typically involves creating a Go program that registers your component as a provider.
Create a main.go file:
// s3-bucket-component/main.go
package main
import (
"github.com/pulumi/pulumi-go-provider/pkg/engine"
"github.com/pulumi/pulumi-go-provider/pkg/provider"
"github.com/pulumi/pulumi-go-provider/pkg/version"
)
func main() {
// Replace with the actual path to your compiled component code
componentPath := "./index.js"
componentPackage := "pulumi-s3-bucket-component"
componentVersion := "0.1.0"
p := provider.New(provider.Options{
Name: "custom", // This is the "type" prefix for your component
Version: version.Version,
LogFlow: true,
})
// Register the component resource
p.RegisterResource("custom:S3BucketComponent", "S3BucketComponent", &S3BucketComponent{}, false) // false because it's not a data source
// This part is a bit simplified; a real plugin would need to implement the Resource interface
// and handle Create, Read, Update, Delete operations for the component.
// For a true multi-language component, you'd typically wrap existing Pulumi resources.
// A more common approach for sharing complex logic is to use Pulumi Packages.
// For demonstration, we'll simulate registering it.
// In a real scenario, you'd use `pulumi-go-provider` to define the Go types
// that mirror your TypeScript component's inputs and outputs.
// The actual mechanism for this involves generating a provider from your component.
// Pulumi's "packages" feature is the standard way to achieve this.
// You would publish your component as a Pulumi package.
// For the sake of this example, let's assume you've published your component.
// The Python code below will then reference this published package.
}
// Placeholder for the actual component implementation in Go, if you were building a Go provider.
type S3BucketComponent struct{}
A More Practical Approach: Pulumi Packages
The direct Go plugin route described above is complex for sharing arbitrary components. The standard and recommended way to share Pulumi components across languages is by publishing them as Pulumi Packages.
Here’s how you’d typically do it:
- Develop your component in your preferred language (e.g., TypeScript).
- Define its inputs and outputs clearly.
- Publish it as a Pulumi Package: This involves creating a
package.json(for Node.js/TS),pyproject.toml(for Python),go.mod(for Go), orpom.xml(for Java) and then usingpulumi package publishor publishing to a private registry.
Let’s assume your S3BucketComponent has been published as a Pulumi package named pulumi-s3-bucket-component (this is a conceptual name; in reality, it would be something like @pulumi/aws-components or a custom organization name).
Now, in your Python project, you can install and use this component.
First, ensure your Python project has a Pulumi.yaml and requirements.txt:
# python-project/Pulumi.yaml
name: python-s3-usage
runtime: python
description: A Python project using a shared S3 component
# python-project/requirements.txt
pulumi @ file:///path/to/your/published/pulumi-s3-bucket-component # Or a package registry URL
pulumi-aws
In your Python code (__main__.py):
# python-project/__main__.py
import pulumi
from pulumi_aws import aws # Assuming pulumi-aws is installed and correctly aliased or imported
# Assuming your custom component is available under a namespace like 'custom_components'
# In a real scenario, you'd install a specific package that exposes this.
# For this example, we'll simulate importing it.
# from custom_components import S3BucketComponent, S3BucketComponentArgs
# --- Simulation of importing a cross-language component ---
# This part is where Pulumi's package system shines.
# If you published your TS component, Python code would import it like this:
# from pulumi_s3_bucket_component import S3BucketComponent, S3BucketComponentArgs
# For demonstration, let's define a dummy Python class that mirrors the TS one.
# In reality, Pulumi's tooling would generate this Python wrapper from the published component.
class S3BucketComponentArgs:
def __init__(self, bucket_name: pulumi.Input[str], acl: pulumi.Input[str]):
self.bucket_name = bucket_name
self.acl = acl
class S3BucketComponent(pulumi.ComponentResource):
def __init__(self, name: str, args: S3BucketComponentArgs, opts: pulumi.ResourceOptions = None):
super().__init__("custom:S3BucketComponent", name, args, opts)
self.bucket = aws.s3.Bucket(f"{name}-bucket",
bucket=args.bucket_name,
acl=args.acl,
opts=pulumi.ResourceOptions(parent=self))
self.register_outputs({
"bucket_name": self.bucket.bucket,
})
# --- End Simulation ---
# Instantiate the component
s3_args = S3BucketComponentArgs(bucket_name="my-shared-app-bucket-12345", acl="private")
my_bucket_component = S3BucketComponent("my-app-s3", s3_args)
# Export outputs from the component
pulumi.export("bucket_name", my_bucket_component.bucket.bucket)
pulumi.export("bucket_id", my_bucket_component.bucket.id)
When you run pulumi up in your Python project, Pulumi will:
- Look for the provider that implements
custom:S3BucketComponent. - If it’s a Pulumi Package, it will use the appropriate language runtime for that package (in this case, Node.js).
- The Node.js runtime will execute your original TypeScript component code.
- The results (outputs, resource URNs) will be communicated back to the Python engine.
This mechanism allows you to define a complex infrastructure pattern once in TypeScript (or Go, Python, Java) and then leverage it from any other Pulumi-supported language, ensuring consistency and reducing boilerplate. The key is understanding that Pulumi orchestrates the execution of different language runtimes when a component defined in one language is invoked from another.
The next step is to understand how to manage the lifecycle and state of these cross-language components, especially when updates or deletions occur.