Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
286 changes: 286 additions & 0 deletions src/content/docs/pulumi/tutorial/dynamic-provider-and-wrangler.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
---
title: Manage not supported resources or tasks with your Pulumi script
pcx_content_type: tutorial
updated: 2024-11-08
sidebar:
order: 5
---

import { TabItem, Tabs } from "~/components";

Below you will find a complete example of using Pulumi to create a zone and other types of resources.

In this example besides a Zone we also create a Worker, a Zero Trust Applications, Zero Trust Policies, a D1 database among others.

Check also how to:

- create Workers by calling Wrangler directly, instead of using the Cloudflare Pulumi provider supported Worker resource. The advantage of this method is that you can use it for any other deployment related task like executing D1 migrations as seen in the example below. When running the IaC script, Wrangler will create or update Workers and D1 (also performing migrations). Observe that D1 migrations state in Pulumi is only changed when the migrations directory hash changes triggering that Pulumi action.
- create a dynamic resource provider for a resource not supported. In this example we demonstrate how to do it for Vectorize.


```

"use strict";

const pulumi = require("@pulumi/pulumi");
const cloudflare = require("@pulumi/cloudflare");
const command = require("@pulumi/command");
const path = require('path');
const axios = require("axios");
const https = require('https');
const crypto = require("crypto");
const fs = require("fs");

// Load configuration
const config = new pulumi.Config();
const domainName = config.require("domainName");
const accountId = config.require("accountId");
const apiToken = config.requireSecret("apiToken");

// Function to compute hash of a file
function computeFileHashSync(filePath) {
const fileBuffer = fs.readFileSync(filePath);
const hash = crypto.createHash('sha256');
hash.update(fileBuffer);
return hash.digest('hex');
}

// Function to compute the hash of a directory
async function hashDirectory(dirPath) {
const files = await fs.promises.readdir(dirPath);
const fileHashes = [];
for (const file of files) {
const filePath = path.join(dirPath, file);
const fileStat = await fs.promises.stat(filePath);
if (fileStat.isFile()) {
const fileData = await fs.promises.readFile(filePath);
const hash = crypto.createHash('sha256').update(fileData).digest('hex');
fileHashes.push(hash);
}
}
// Combine all file hashes and hash the result to get a unique hash for the directory
const combinedHash = crypto.createHash('sha256').update(fileHashes.join('')).digest('hex');
return combinedHash;
}

// Instantiate Cloudflare provider
// https://www.pulumi.com/registry/packages/cloudflare/
//--------------------------------------------------------------------------------------------------------------------
const cloudflareProvider = new cloudflare.Provider("cloudflare", {
apiToken: apiToken
});

// Create a Cloudflare Zone
// https://www.pulumi.com/registry/packages/cloudflare/api-docs/zone/
//--------------------------------------------------------------------------------------------------------------------
const myZone = new cloudflare.Zone("myZone", {
zone: domainName,
plan: "enterprise",
accountId: accountId
}, { provider: cloudflareProvider });

// Create a Cloudflare Queue (used as a binding in Worker)
// https://www.pulumi.com/registry/packages/cloudflare/api-docs/queue/
//--------------------------------------------------------------------------------------------------------------------
const myqueue = new cloudflare.Queue("myqueue", {
zoneId: myZone.id,
name: "myqueue",
description: "Queue for my messages",
accountId: accountId
}, { provider: cloudflareProvider });

// Create a Cloudflare Queue (used as a binding in Worker)
// https://www.pulumi.com/registry/packages/cloudflare/api-docs/queue/
//--------------------------------------------------------------------------------------------------------------------
const myqueuedeadletter = new cloudflare.Queue("myqueuedeadletter", {
zoneId: myZone.id,
name: "myqueuedeadletter",
description: "Queue for messages that were not processed correctly",
accountId: accountId
}, { provider: cloudflareProvider });

// Create a D1 Database
// https://www.pulumi.com/registry/packages/cloudflare/api-docs/d1database/
//--------------------------------------------------------------------------------------------------------------------
const myD1Database = new cloudflare.D1Database("myD1Database", {
accountId: accountId,
name: "mydb",
}, { provider: cloudflareProvider });

// Deploy Changes to D1 Schema
// Cloudflare Wrangler stores list of migrations in the D1 database.
// To check which migrations were ran go to the Cloudflare dashboard and run "select * from d1_migrations" on the console of the D1 database
//--------------------------------------------------------------------------------------------------------------------
let d1Dir="../../mydb/";
const d1Migration = new command.local.Command("d1Migration", {
dir: d1Dir,
create: `npx wrangler d1 migrations apply mydb --remote`,
triggers:[
hashDirectory(`${d1Dir}migrations`)
]
}, { dependsOn: [myD1Database] });

// Run 'wrangler' command
// https://www.pulumi.com/registry/packages/command/api-docs/local/command/
//--------------------------------------------------------------------------------------------------------------------
let workerDir="../../worker-test/";
const workerTest = new command.local.Command("worker-test", {
dir: workerDir,
create: "npx wrangler deploy",
triggers: [ // A unique trigger vector to force recreation
computeFileHashSync(`${workerDir}src/index.js`),
computeFileHashSync(`${workerDir}wrangler.toml`)]
}, { dependsOn: [myZone, myqueue, myqueuedeadletter, myD1Database] });

// Create "Add" group Service Auth Token
// https://www.pulumi.com/registry/packages/cloudflare/api-docs/zerotrustaccessservicetoken/
//--------------------------------------------------------------------------------------------------------------------
const myServiceToken = new cloudflare.ZeroTrustAccessServiceToken("myServiceToken", {
zoneId: myZone.id,
name: "myServiceToken"
}, { provider: cloudflareProvider });

// Create an Access "Add" Group
// https://www.pulumi.com/registry/packages/cloudflare/api-docs/zerotrustaccessgroup/
//--------------------------------------------------------------------------------------------------------------------
const myAccessGroup = new cloudflare.ZeroTrustAccessGroup("myAccessGroup", {
accountId: accountId,
name: "myAccessGroup",
// Define the group criteria (e.g., email domains, identity providers, etc.)
// This example adds users from the specified email domain.
includes: [
{ serviceTokens: [myServiceToken.id] }
]
}, { provider: cloudflareProvider, dependsOn: [myServiceToken] });

// Create an Access App for "Add"
// https://www.pulumi.com/registry/packages/cloudflare/api-docs/zerotrustaccessapplication/
//--------------------------------------------------------------------------------------------------------------------
const myAccessApp = new cloudflare.ZeroTrustAccessApplication("myAccessApp", {
zoneId: myZone.id,
name: "myApp",
domain: `myapp.${domainName}`,
sessionDuration: "24h"
}, { provider: cloudflareProvider, dependsOn: [myAccessGroup, myZone] });

// Create an Access App with Allow Policy for Access "Add" Group
// https://www.pulumi.com/registry/packages/cloudflare/api-docs/zerotrustaccesspolicy/
//--------------------------------------------------------------------------------------------------------------------
const myAddAccessPolicy = new cloudflare.ZeroTrustAccessPolicy("myAccessPolicy", {
zoneId: myZone.id,
applicationId: myAccessApp.id,
name: "myAccessPolicy",
decision: "allow",
precedence: 1,
includes: [
{
groups: [myAccessGroup.id]
}
],
}, { provider: cloudflareProvider, dependsOn: [myAccessApp] });

// Create a Vectorize Index
//--------------------------------------------------------------------------------------------------------------------
// Define a dynamic provider for Vectorize because the Cloudflare Pulumi provider does not support this resource yet
const VectorizeIndexDynamicCloudflareProvider = {
async create(inputs) {
// Create an instance of the HTTPS Agent with SSL verification disabled to avoid WARP issues
const httpsAgent = new https.Agent({
rejectUnauthorized: false
});
const url = `https://api.cloudflare.com/client/v4/accounts/${inputs.accountId}/vectorize/v2/indexes`;
const data = {
config: {dimensions: 768, metric: 'cosine'},
description: inputs.description,
name: inputs.name
};
// Headers
const options = {
httpsAgent,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${inputs.apiToken}`
}
};
// Make an API call to create the resource
const response = await axios.post(url, data, options);
const resourceId = inputs.name // For now we use the Vectorize index name as id, because Vectorize does not provide an id for it
// Return the ID and output values
return { id: resourceId,
outs: {
name: inputs.name,
accountId: inputs.accountId,
apiToken: inputs.apiToken
} };
},

async delete(id, props) {
// Create an instance of the HTTPS Agent with SSL verification disabled to avoid WARP issues
const httpsAgent = new https.Agent({
rejectUnauthorized: false
});
const url=`https://api.cloudflare.com/client/v4/accounts/${props.accountId}/vectorize/v2/indexes/${id}`;
// Headers
const options = {
httpsAgent,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${props.apiToken}`
}
};
// Make an API call to delete the resource
await axios.delete(url, options);
},

async update(id, oldInputs, newInputs) {
// Vectorize once created does not allow updates
}
};

// Define a dynamic resource
class VectorizeIndex extends pulumi.dynamic.Resource {
constructor(name, args, opts) {
super(VectorizeIndexDynamicCloudflareProvider, name, args, opts);
}
}

// Use the dynamic resource in your Pulumi stack
// Don't change properties after creation. Currently, Vectorize does not allow changes.
// To delete this resource just remove or comment this block of code
const my_vectorize_index = new VectorizeIndex("myvectorizeindex", {
name: "myvectorize_index",
accountId: accountId,
namespaceId: myZone.id, // Set appropriate namespace id
vectorDimensions: 768, // Example, adjust dimensions as needed
apiToken: apiToken
});

// Export relevant outputs
// Access these outputs after Pulumi has ran using:
// $ pulumi stack output
// $ pulumi stack output zoneId
//--------------------------------------------------------------------------------------------------------------------
exports.zoneId = myZone.id;
exports.myqueueId = myqueue.id
exports.myqueuedeadletter = myqueuedeadletter.id
exports.myD1DatabaseId = myD1Database.id
exports.workerTestId = workerTest.id
exports.myServiceToken = myServiceToken.id
exports.myServiceTokenClientId = myAddServiceToken.clientId
exports.myServiceTokenClientSecret = myAddServiceToken.clientSecret

```

## Accessing Pulumi exports

After running your Pulumi script with "pulumi up" you will have your resources created or updated.

The script above also exports outputs that you can access from other tools that you want or need to integrate into your deployment pipeline.

For this, you can use:

```
$ pulumi stack output myServiceTokenClientSecret --show-secrets
```


Loading