跳转到内容
搜索文档

使用 Pulumi 和 Wrangler 创建不同类型的资源

最后更新 查看 MarkdownAgent 设置

本示例使用两种不同的策略创建 Zone 和其他资源:

  • 使用 Pulumi 管理 Cloudflare Pulumi 提供商支持的部分资源。
  • 使用 Wrangler 创建其他类型的资源。

示例代码涉及创建 Workers、Zero Trust 应用程序、Zero Trust 策略和 D1 数据库等资源。

Wrangler 用法

本示例中的代码展示了如何通过直接调用 Wrangler(而非使用 Cloudflare Pulumi 提供商直接支持的资源)来创建 Workers。使用此方法的优点在于,你可以将其用于任何部署相关任务,例如执行 D1 迁移。当你运行基础设施即代码(IaC)脚本时,Wrangler 将创建或更新 Workers 和 D1,其中包括执行数据库迁移。

在当前示例中,Pulumi 中的 D1 迁移状态仅在 migrations 目录的哈希值发生变化时才会改变,在这种情况下,Pulumi 命令才会被执行。

针对 Vectorize 的动态资源提供商

本示例还提供了一个针对 Cloudflare Pulumi 提供商尚不直接支持的资源(此处为 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 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(
	"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/
//-----------------------------------------------------------------------------
const 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, since 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);
		// For now we use the Vectorize index name as id, because Vectorize does not
		// provide an id for it
		const resourceId = inputs.name;

		// 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, 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, // This is an example - adjust dimensions as needed
	apiToken: apiToken,
});

// Export relevant outputs
// Access these outputs after Pulumi has run 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;

访问 Pulumi 导出值

使用 pulumi up 运行 Pulumi 脚本后,你的资源将被创建或更新。

上面的示例脚本还导出了可以从其他工具访问的输出内容。例如,将 Pulumi 脚本集成到部署流水线中时非常有用。

你可以使用类似以下的命令:

pulumi stack output myServiceTokenClientSecret --show-secrets

这篇文档对您有帮助吗?