Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

Pulumi と Wrangler で異なるリソースを作成する

最終更新 Markdown で表示Agent セットアップ

この例では、2 つの異なる方針でゾーンとその他のリソースを作成します。

  • Cloudflare Pulumi プロバイダーが対応する一部のリソースは Pulumi で作成します。
  • その他の種類のリソースは Wrangler で作成します。

サンプルコードでは、Workers、Zero Trust Applications、Zero Trust Policies、D1 データベースなどのリソース作成を扱います。

Wrangler の使い方

この例のコードでは、Cloudflare Pulumi プロバイダーが直接対応するリソースではなく、Wrangler を直接呼び出して Workers を作成します。この方法の利点は、D1 マイグレーションの実行など、デプロイ関連の任意の作業に使えることです。インフラストラクチャ as Code(IaC)スクリプトを実行すると、Wrangler が Workers と D1 を作成または更新し、データベースマイグレーションも実行します。

この例では、migrations ディレクトリのハッシュが変わったときだけ、Pulumi 上の D1 マイグレーション状態が変わります。その場合に 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

役に立ちましたか?