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");
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");
return hash.digest("hex");
// Function to compute the hash of a directory
async function hashDirectory(dirPath) {
const files = await fs.promises.readdir(dirPath);
for (const file of files) {
const filePath = path.join(dirPath, file);
const fileStat = await fs.promises.stat(filePath);
const fileData = await fs.promises.readFile(filePath);
const hash = crypto.createHash("sha256").update(fileData).digest("hex");
// Combine all file hashes and hash the result to get a unique hash for the directory
const combinedHash = crypto
.update(fileHashes.join(""))
// Instantiate Cloudflare provider
// https://www.pulumi.com/registry/packages/cloudflare/
//-----------------------------------------------------------------------------
const cloudflareProvider = new cloudflare.Provider("cloudflare", {
// Create a Cloudflare Zone
// https://www.pulumi.com/registry/packages/cloudflare/api-docs/zone/
//-----------------------------------------------------------------------------
const myZone = new cloudflare.Zone(
{ 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(
description: "Queue for my messages",
{ 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(
name: "myqueuedeadletter",
description: "Queue for messages that were not processed correctly",
{ provider: cloudflareProvider },
// https://www.pulumi.com/registry/packages/cloudflare/api-docs/d1database/
//-----------------------------------------------------------------------------
const myD1Database = new cloudflare.D1Database(
{ provider: cloudflareProvider },
// Deploy Changes to D1 Schema
// - Cloudflare Wrangler stores a list of migrations in the D1 database.
// - To check which migrations were run, go to the Cloudflare dashboard
// and run "SELECT * FROM d1_migrations" on the console of the D1 database.
//-----------------------------------------------------------------------------
const d1Dir = "../../mydb/";
const d1Migration = new command.local.Command(
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/
//-----------------------------------------------------------------------------
const workerDir = "../../worker-test/";
const workerTest = new command.local.Command(
create: "npx wrangler deploy",
// 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(
{ provider: cloudflareProvider },
// Create an Access "Add" Group
// https://www.pulumi.com/registry/packages/cloudflare/api-docs/zerotrustaccessgroup/
//-----------------------------------------------------------------------------
const myAccessGroup = new cloudflare.ZeroTrustAccessGroup(
// 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(
domain: `myapp.${domainName}`,
{ 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(
applicationId: myAccessApp.id,
groups: [myAccessGroup.id],
{ provider: cloudflareProvider, dependsOn: [myAccessApp] },
// Create a Vectorize Index
//-----------------------------------------------------------------------------
// Define a dynamic provider for Vectorize, since the Cloudflare Pulumi provider does not support
const VectorizeIndexDynamicCloudflareProvider = {
// 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`;
config: { dimensions: 768, metric: "cosine" },
description: inputs.description,
"Content-Type": "application/json",
Authorization: `Bearer ${inputs.apiToken}`,
// Make an API call to create the resource
const response = await axios.post(url, data, options);
// For now we use the Vectorize index name as id, because Vectorize does not
const resourceId = inputs.name;
// Return the ID and output values
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}`;
"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, remove or comment this block of code
const my_vectorize_index = new VectorizeIndex("myvectorizeindex", {
name: "myvectorize_index",
namespaceId: myZone.id, // Set appropriate namespace id
vectorDimensions: 768, // This is an example - adjust dimensions as needed
// Export relevant outputs
// Access these outputs after Pulumi has run using:
// $ 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;