虽然 Wrangler 可以方便地上传和管理 Workers,但有时你需要更程序化的方式。这可能涉及使用基础设施即代码 (Infrastructure as Code, IaC) 工具,或直接与 Workers API 交互。典型场景包括构建和部署脚本、CI/CD 流水线、自定义开发者工具以及自动化测试。
为简化这一过程,Cloudflare 提供了适用于常用语言的 SDK 库,例如 cloudflare-typescript ↗ 和 cloudflare-python ↗。对于 IaC,你可以使用 HashiCorp 的 Terraform 以及 Cloudflare Terraform Provider 来管理 Workers 资源。
以下示例展示了如何使用不同工具和语言部署 Worker,以及使用 IaC 管理 Workers 时需要注意的事项。
所有示例都需要 账户 ID 和 API 令牌(不是 Global API key)才能正常工作。
以下示例均未执行 Workers 打包。这通常由 Wrangler 或 esbuild ↗ 等工具完成。
通常,在应用 Terraform 计划或使用 API 上传脚本之前,你会先运行打包步骤:
wrangler deploy --dry-run --outdir build当使用 Wrangler 构建而采用其他方式上传时,请确保将 wrangler.json 中的所有配置复制到 Terraform 配置或 API 请求中。这对于 compatibility_date 或脚本依赖的兼容性标志尤为重要。
在此示例中,你需要一个名为 my-script.mjs 的本地文件,其脚本内容类似于下方示例。了解更多关于 Cloudflare Terraform Provider 的信息,并参阅 Workers script resource 示例 ↗ 以查看所有可用的资源配置。
variable "account_id" {
default = "replace_me"
}
resource "cloudflare_worker" "my_worker" {
account_id = var.account_id
name = "my-worker"
observability = {
enabled = true
}
}
resource "cloudflare_worker_version" "my_worker_version" {
account_id = var.account_id
worker_id = cloudflare_worker.my_worker.id
compatibility_date = "2025-02-21" # Set this to today's date
main_module = "my-script.mjs"
modules = [
{
name = "my-script.mjs"
content_type = "application/javascript+module"
# Replacement (version creation) is triggered whenever this file changes
content_file = "my-script.mjs"
}
]
}
resource "cloudflare_workers_deployment" "my_worker_deployment" {
account_id = var.account_id
script_name = cloudflare_worker.my_worker.name
strategy = "percentage"
versions = [{
percentage = 100
version_id = cloudflare_worker_version.my_worker_version.id
}]
}请注意,你不必在 Terraform 中管理所有这些资源。例如,你可以只使用 cloudflare_worker 资源,并无缝地使用 Wrangler 或自己的部署工具来管理 Version 或 Deployment。
绑定(binding) 允许 Worker 与 Cloudflare Developer Platform 上的资源交互。在 Terraform 中,绑定的配置方式与 Wrangler 不同。Terraform 不使用每种绑定类型的独立顶层属性(如 kv_namespaces、r2_buckets 等),而是使用单一的 bindings 数组,其中每个绑定都有一个 type 属性以及类型特定的属性。
以下是各绑定类型及其必需属性的示例:
绑定到 KV namespace 以进行键值存储:
bindings = [{
type = "kv_namespace"
name = "MY_KV"
namespace_id = "your-kv-namespace-id"
}]属性:
type:"kv_namespace"name:绑定的变量名,可通过env.MY_KV访问namespace_id:KV namespace 的 ID
绑定到 R2 bucket 以进行对象存储:
bindings = [{
type = "r2_bucket"
name = "MY_BUCKET"
bucket_name = "my-bucket-name"
}]属性:
type:"r2_bucket"name:绑定的变量名,可通过env.MY_BUCKET访问bucket_name:R2 bucket 的名称
绑定到 D1 数据库 以进行 SQL 存储:
bindings = [{
type = "d1"
name = "DB"
id = "your-database-id"
}]属性:
type:"d1"name:绑定的变量名,可通过env.DB访问id:D1 数据库的 ID
绑定到 Durable Object 类:
bindings = [{
type = "durable_object_namespace"
name = "MY_DURABLE_OBJECT"
class_name = "MyDurableObjectClass"
}]属性:
type:"durable_object_namespace"name:绑定的变量名,可通过env.MY_DURABLE_OBJECT访问class_name:Durable Object 的导出类名script_name:(可选)导出此 Durable Object 类的 Worker 脚本。如果类定义在同一 Worker 中,可省略。
绑定到另一个 Worker 以实现 Worker 间通信:
bindings = [{
type = "service"
name = "MY_SERVICE"
service = "other-worker-name"
}]属性:
type:"service"name:绑定的变量名,可通过env.MY_SERVICE访问service:目标 Worker 的名称entrypoint:(可选)要绑定的命名 entrypoint
绑定到 Queue 以进行消息传递:
用于生产消息:
bindings = [{
type = "queue"
name = "MY_QUEUE"
queue_name = "my-queue"
}]属性:
type:"queue"name:绑定的变量名,可通过env.MY_QUEUE访问queue_name:Queue 的名称
对于消费消息,请在 queue 资源本身中配置 Worker 作为消费者,而不是通过绑定。
绑定到 Vectorize 索引 以进行向量搜索:
bindings = [{
type = "vectorize"
name = "VECTORIZE_INDEX"
index_name = "my-index"
}]属性:
type:"vectorize"name:绑定的变量名,可通过env.VECTORIZE_INDEX访问index_name:Vectorize 索引的名称
绑定到 Workers AI 以进行 AI 推理:
bindings = [{
type = "ai"
name = "AI"
}]属性:
type:"ai"name:绑定的变量名,可通过env.AI访问
绑定到 Hyperdrive 配置以进行数据库连接池化:
bindings = [{
type = "hyperdrive"
name = "HYPERDRIVE"
id = "your-hyperdrive-config-id"
}]属性:
type:"hyperdrive"name:绑定的变量名,可通过env.HYPERDRIVE访问id:Hyperdrive 配置的 ID
绑定到 VPC Service 以访问私有网络中的资源:
bindings = [{
type = "vpc_service"
name = "PRIVATE_API"
service_id = "your-vpc-service-id"
}]属性:
type:"vpc_service"name:绑定的变量名,可通过env.PRIVATE_API访问service_id:VPC Service 的 ID(来自cloudflare_connectivity_directory_service或仪表板)
你可以使用 cloudflare_connectivity_directory_service 资源通过 Terraform 创建 VPC Service。完整演练请参阅使用 Terraform 配置 VPC Services。
绑定到 Analytics Engine 数据集:
bindings = [{
type = "analytics_engine"
name = "ANALYTICS"
dataset = "my_dataset"
}]属性:
type:"analytics_engine"name:绑定的变量名,可通过env.ANALYTICS访问dataset:Analytics Engine 数据集的名称
对于纯文本环境变量,使用 plain_text 绑定类型:
bindings = [{
type = "plain_text"
name = "MY_VARIABLE"
text = "my-value"
}]属性:
type:"plain_text"name:绑定的变量名,可通过env.MY_VARIABLE访问text:环境变量的值
对于加密密钥,使用 secret_text 绑定类型:
bindings = [{
type = "secret_text"
name = "API_KEY"
text = var.api_key
}]属性:
type:"secret_text"name:绑定的变量名,可通过env.API_KEY访问text:密钥值(将被加密)
以下示例组合了多种绑定类型:
resource "cloudflare_worker_version" "my_worker_version" {
account_id = var.account_id
worker_id = cloudflare_worker.my_worker.id
compatibility_date = "2025-08-06"
main_module = "worker.js"
modules = [{
name = "worker.js"
content_type = "application/javascript+module"
content_file = "worker.js"
}]
bindings = [
{
type = "kv_namespace"
name = "MY_KV"
namespace_id = var.kv_namespace_id
},
{
type = "r2_bucket"
name = "MY_BUCKET"
bucket_name = "my-bucket"
},
{
type = "d1"
name = "DB"
id = var.d1_database_id
},
{
type = "service"
name = "AUTH_SERVICE"
service = "auth-worker"
},
{
type = "plain_text"
name = "ENVIRONMENT"
text = "production"
},
{
type = "secret_text"
name = "API_KEY"
text = var.api_key
},
{
type = "vpc_service"
name = "PRIVATE_API"
service_id = var.vpc_service_id
}
]
}此示例使用 cloudflare-typescript ↗ SDK,它提供了从服务端 JavaScript 或 TypeScript 便捷访问 Cloudflare REST API 的方式。
#!/usr/bin/env -S npm run tsn -T
/**
* Create and deploy a Worker
*
* Docs:
* - https://developers.cloudflare.com/workers/configuration/versions-and-deployments/
* - https://developers.cloudflare.com/workers/platform/infrastructure-as-code/
*
* Prerequisites:
* 1. Generate an API token: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/
* 2. Find your account ID: https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/
* 3. Find your workers.dev subdomain: https://developers.cloudflare.com/workers/configuration/routing/workers-dev/
*
* Environment variables:
* - CLOUDFLARE_API_TOKEN (required)
* - CLOUDFLARE_ACCOUNT_ID (required)
* - CLOUDFLARE_SUBDOMAIN (optional)
*
* Usage:
* Run this script to deploy a simple "Hello World" Worker.
* Access it at: my-hello-world-worker.$subdomain.workers.dev
*/
import { exit } from "node:process";
import Cloudflare from "cloudflare";
const WORKER_NAME = "my-hello-world-worker";
const SCRIPT_FILENAME = `${WORKER_NAME}.mjs`;
function loadConfig() {
const apiToken = process.env["CLOUDFLARE_API_TOKEN"];
if (!apiToken) {
throw new Error(
"Missing required environment variable: CLOUDFLARE_API_TOKEN",
);
}
const accountId = process.env["CLOUDFLARE_ACCOUNT_ID"];
if (!accountId) {
throw new Error(
"Missing required environment variable: CLOUDFLARE_ACCOUNT_ID",
);
}
const subdomain = process.env["CLOUDFLARE_SUBDOMAIN"];
return {
apiToken,
accountId,
subdomain: subdomain || undefined,
workerName: WORKER_NAME,
};
}
const config = loadConfig();
const client = new Cloudflare({
apiToken: config.apiToken,
});
async function main() {
try {
console.log("🚀 Starting Worker creation and deployment...");
const scriptContent = `
export default {
async fetch(request, env, ctx) {
return new Response(env.MESSAGE, { status: 200 });
},
}`.trim();
let worker;
try {
worker = await client.workers.beta.workers.get(config.workerName, {
account_id: config.accountId,
});
console.log(`♻️ Worker ${config.workerName} already exists. Using it.`);
} catch (error) {
if (!(error instanceof Cloudflare.NotFoundError)) {
throw error;
}
console.log(`✏️ Creating Worker ${config.workerName}...`);
worker = await client.workers.beta.workers.create({
account_id: config.accountId,
name: config.workerName,
subdomain: {
enabled: config.subdomain !== undefined,
},
observability: {
enabled: true,
},
});
}
console.log(`⚙️ Worker id: ${worker.id}`);
console.log("✏️ Creating Worker version...");
// Create the first version of the Worker
const version = await client.workers.beta.workers.versions.create(
worker.id,
{
account_id: config.accountId,
main_module: SCRIPT_FILENAME,
compatibility_date: new Date().toISOString().split("T")[0],
bindings: [
{
type: "plain_text",
name: "MESSAGE",
text: "Hello World!",
},
],
modules: [
{
name: SCRIPT_FILENAME,
content_type: "application/javascript+module",
content_base64: Buffer.from(scriptContent).toString("base64"),
},
],
},
);
console.log(`⚙️ Version id: ${version.id}`);
console.log("🚚 Creating Worker deployment...");
// Create a deployment and point all traffic to the version we created
await client.workers.scripts.deployments.create(config.workerName, {
account_id: config.accountId,
strategy: "percentage",
versions: [
{
percentage: 100,
version_id: version.id,
},
],
});
console.log("✅ Deployment successful!");
if (config.subdomain) {
console.log(`
🌍 Your Worker is live!
📍 URL: https://${config.workerName}.${config.subdomain}.workers.dev/
`);
} else {
console.log(`
⚠️ Set up a route, custom domain, or workers.dev subdomain to access your Worker.
Add CLOUDFLARE_SUBDOMAIN to your environment variables to set one up automatically.
`);
}
} catch (error) {
console.error("❌ Deployment failed:", error);
exit(1);
}
}
main();#!/usr/bin/env -S npm run tsn -T
/**
* Create and deploy a Worker
*
* Docs:
* - https://developers.cloudflare.com/workers/configuration/versions-and-deployments/
* - https://developers.cloudflare.com/workers/platform/infrastructure-as-code/
*
* Prerequisites:
* 1. Generate an API token: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/
* 2. Find your account ID: https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/
* 3. Find your workers.dev subdomain: https://developers.cloudflare.com/workers/configuration/routing/workers-dev/
*
* Environment variables:
* - CLOUDFLARE_API_TOKEN (required)
* - CLOUDFLARE_ACCOUNT_ID (required)
* - CLOUDFLARE_SUBDOMAIN (optional)
*
* Usage:
* Run this script to deploy a simple "Hello World" Worker.
* Access it at: my-hello-world-worker.$subdomain.workers.dev
*/
import { exit } from 'node:process';
import Cloudflare from 'cloudflare';
interface Config {
apiToken: string;
accountId: string;
subdomain: string | undefined;
workerName: string;
}
const WORKER_NAME = 'my-hello-world-worker';
const SCRIPT_FILENAME = `${WORKER_NAME}.mjs`;
function loadConfig(): Config {
const apiToken = process.env['CLOUDFLARE_API_TOKEN'];
if (!apiToken) {
throw new Error('Missing required environment variable: CLOUDFLARE_API_TOKEN');
}
const accountId = process.env['CLOUDFLARE_ACCOUNT_ID'];
if (!accountId) {
throw new Error('Missing required environment variable: CLOUDFLARE_ACCOUNT_ID');
}
const subdomain = process.env['CLOUDFLARE_SUBDOMAIN'];
return {
apiToken,
accountId,
subdomain: subdomain || undefined,
workerName: WORKER_NAME,
};
}
const config = loadConfig();
const client = new Cloudflare({
apiToken: config.apiToken,
});
async function main(): Promise<void> {
try {
console.log('🚀 Starting Worker creation and deployment...');
const scriptContent = `
export default {
async fetch(request, env, ctx) {
return new Response(env.MESSAGE, { status: 200 });
},
}`.trim();
let worker;
try {
worker = await client.workers.beta.workers.get(config.workerName, {
account_id: config.accountId,
});
console.log(`♻️ Worker ${config.workerName} already exists. Using it.`);
} catch (error) {
if (!(error instanceof Cloudflare.NotFoundError)) { throw error; }
console.log(`✏️ Creating Worker ${config.workerName}...`);
worker = await client.workers.beta.workers.create({
account_id: config.accountId,
name: config.workerName,
subdomain: {
enabled: config.subdomain !== undefined,
},
observability: {
enabled: true,
},
});
}
console.log(`⚙️ Worker id: ${worker.id}`);
console.log('✏️ Creating Worker version...');
// Create the first version of the Worker
const version = await client.workers.beta.workers.versions.create(worker.id, {
account_id: config.accountId,
main_module: SCRIPT_FILENAME,
compatibility_date: new Date().toISOString().split('T')[0]!,
bindings: [
{
type: 'plain_text',
name: 'MESSAGE',
text: 'Hello World!',
},
],
modules: [
{
name: SCRIPT_FILENAME,
content_type: 'application/javascript+module',
content_base64: Buffer.from(scriptContent).toString('base64'),
},
],
});
console.log(`⚙️ Version id: ${version.id}`);
console.log('🚚 Creating Worker deployment...');
// Create a deployment and point all traffic to the version we created
await client.workers.scripts.deployments.create(config.workerName, {
account_id: config.accountId,
strategy: 'percentage',
versions: [
{
percentage: 100,
version_id: version.id,
},
],
});
console.log('✅ Deployment successful!');
if (config.subdomain) {
console.log(`
🌍 Your Worker is live!
📍 URL: https://${config.workerName}.${config.subdomain}.workers.dev/
`);
} else {
console.log(`
⚠️ Set up a route, custom domain, or workers.dev subdomain to access your Worker.
Add CLOUDFLARE_SUBDOMAIN to your environment variables to set one up automatically.
`);
}
} catch (error) {
console.error('❌ Deployment failed:', error);
exit(1);
}
}
main();打开终端或创建 shell 脚本,使用 curl 上传 Worker 并管理 version 和 deployment。Workers 脚本是 JavaScript ES Modules ↗,我们也支持 Python Workers(open beta)和 Rust Workers。
account_id="replace_me"
api_token="replace_me"
worker_name="my-hello-world-worker"
worker_script_base64=$(echo '
export default {
async fetch(request, env, ctx) {
return new Response(env.MESSAGE, { status: 200 });
}
};
' | base64)
# Note the below will fail if the worker already exists!
# Here's how to delete the Worker
#
# worker_id="replace-me"
# curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/workers/$worker_id" \
# -X DELETE \
# -H "Authorization: Bearer $api_token"
# Create the Worker
worker_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/workers" \
-X POST \
-H "Authorization: Bearer $api_token" \
-H "Content-Type: application/json" \
-d '{
"name": "'$worker_name'"
}' \
| jq -r '.result.id')
echo "\nWorker ID: $worker_id\n"
# Upload the Worker's first version
version_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/workers/$worker_id/versions" \
-X POST \
-H "Authorization: Bearer $api_token" \
-H "Content-Type: application/json" \
-d '{
"compatibility_date": "2025-08-06",
"main_module": "'$worker_name'.mjs",
"modules": [
{
"name": "'$worker_name'.mjs",
"content_type": "application/javascript+module",
"content_base64": "'$worker_script_base64'"
}
],
"bindings": [
{
"type": "plain_text",
"name": "MESSAGE",
"text": "Hello World!"
}
]
}' \
| jq -r '.result.id')
echo "\nVersion ID: $version_id\n"
# Create a deployment for the Worker
deployment_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/scripts/$worker_name/deployments" \
-X POST \
-H "Authorization: Bearer $api_token" \
-H "Content-Type: application/json" \
-d '{
"strategy": "percentage",
"versions": [
{
"percentage": 100,
"version_id": "'$version_id'"
}
]
}' \
| jq -r '.result.id')
echo "\nDeployment ID: $deployment_id\n"Python Workers 有专用的 text/x-python content type 和 python_workers 兼容性标志。
account_id="replace_me"
api_token="replace_me"
worker_name="my-hello-world-worker"
worker_script_base64=$(echo '
from workers import WorkerEntrypoint, Response
class Default(WorkerEntrypoint):
async def fetch(self, request):
return Response(self.env.MESSAGE)
' | base64)
# Note the below will fail if the worker already exists!
# Here's how to delete the Worker
#
# worker_id="replace-me"
# curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/workers/$worker_id" \
# -X DELETE \
# -H "Authorization: Bearer $api_token"
# Create the Worker
worker_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/workers" \
-X POST \
-H "Authorization: Bearer $api_token" \
-H "Content-Type: application/json" \
-d '{
"name": "'$worker_name'"
}' \
| jq -r '.result.id')
echo "\nWorker ID: $worker_id\n"
# Upload the Worker's first version
version_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/workers/$worker_id/versions" \
-X POST \
-H "Authorization: Bearer $api_token" \
-H "Content-Type: application/json" \
-d '{
"compatibility_date": "2025-08-06",
"compatibility_flags": [
"python_workers"
],
"main_module": "'$worker_name'.py",
"modules": [
{
"name": "'$worker_name'.py",
"content_type": "text/x-python",
"content_base64": "'$worker_script_base64'"
}
],
"bindings": [
{
"type": "plain_text",
"name": "MESSAGE",
"text": "Hello World!"
}
]
}' \
| jq -r '.result.id')
echo "\nVersion ID: $version_id\n"
# Create a deployment for the Worker
deployment_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/scripts/$worker_name/deployments" \
-X POST \
-H "Authorization: Bearer $api_token" \
-H "Content-Type: application/json" \
-d '{
"strategy": "percentage",
"versions": [
{
"percentage": 100,
"version_id": "'$version_id'"
}
]
}' \
| jq -r '.result.id')
echo "\nDeployment ID: $deployment_id\n"此 API 使用 multipart/form-data ↗ 上传 Worker,并会隐式创建 version 和 deployment。建议使用上述 API 直接管理 version 和 deployment。
account_id="replace_me"
api_token="replace_me"
worker_name="my-hello-world-script"
script_content='export default {
async fetch(request, env, ctx) {
return new Response(env.MESSAGE, { status: 200 });
}
};'
# Upload the Worker
curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/scripts/$worker_name" \
-X PUT \
-H "Authorization: Bearer $api_token" \
-F "metadata={
'main_module': '"$worker_name".mjs',
'bindings': [
{
'type': 'plain_text',
'name': 'MESSAGE',
'text': 'Hello World!'
}
],
'compatibility_date': '$today'
};type=application/json" \
-F "$worker_name.mjs=@-;filename=$worker_name.mjs;type=application/javascript+module" <<EOF
$script_content
EOF对于 Workers for Platforms,你可以将 User Worker 上传到 dispatch namespace。请注意 API endpoint 位于 /workers/dispatch/namespaces/$DISPATCH_NAMESPACE/scripts/$SCRIPT_NAME。
account_id="replace_me"
api_token="replace_me"
dispatch_namespace="replace_me"
worker_name="my-hello-world-script"
script_content='export default {
async fetch(request, env, ctx) {
return new Response(env.MESSAGE, { status: 200 });
}
};'
# Create a dispatch namespace
curl https://api.cloudflare.com/client/v4/accounts/$account_id/workers/dispatch/namespaces \
-X POST \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $api_token" \
-d '{
"name": "'$dispatch_namespace'"
}'
# Upload the Worker
curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/dispatch/namespaces/$dispatch_namespace/scripts/$worker_name" \
-X PUT \
-H "Authorization: Bearer $api_token" \
-F "metadata={
'main_module': '"$worker_name".mjs',
'bindings': [
{
'type': 'plain_text',
'name': 'MESSAGE',
'text': 'Hello World!'
}
],
'compatibility_date': '$today'
};type=application/json" \
-F "$worker_name.mjs=@-;filename=$worker_name.mjs;type=application/javascript+module" <<EOF
$script_content
EOFPython Workers(open beta)在使用 multipart/form-data API 上传时,有专用的 text/x-python content type 和 python_workers 兼容性标志。
curl https://api.cloudflare.com/client/v4/accounts/<account_id>/workers/scripts/my-hello-world-script \
-X PUT \
-H 'Authorization: Bearer <api_token>' \
-F 'metadata={
"main_module": "my-hello-world-script.py",
"bindings": [
{
"type": "plain_text",
"name": "MESSAGE",
"text": "Hello World!"
}
],
"compatibility_date": "$today",
"compatibility_flags": [
"python_workers"
]
};type=application/json' \
-F 'my-hello-world-script.py=@-;filename=my-hello-world-script.py;type=text/x-python' <<EOF
from workers import WorkerEntrypoint, Response
class Default(WorkerEntrypoint):
async def fetch(self, request):
return Response(self.env.MESSAGE)
EOFDurable Object 迁移通过 deployment 应用。这意味着如果尚未存在 deployment(即迁移尚未应用),你无法在 Version 中绑定 Durable Object。例如,在 Terraform 中首次应用以下配置将会失败:
resource "cloudflare_worker" "my_worker" {
account_id = var.account_id
name = "my-worker"
}
resource "cloudflare_worker_version" "my_worker_version" {
account_id = var.account_id
worker_id = cloudflare_worker.my_worker.id
bindings = [
{
type = "durable_object_namespace"
name = "my_durable_object"
class_name = "MyDurableObjectClass"
}
]
migrations = {
new_sqlite_classes = [
"MyDurableObjectClass"
]
}
# ...version props omitted for brevity
}
resource "cloudflare_workers_deployment" "my_worker_deployment" {
# ...deployment props omitted for brevity
}要使其成功,你需要先注释掉 durable_object 绑定块并应用计划,然后取消注释,再注释掉 migrations 块并再次应用。这次计划将会成功。这也适用于 API 或 SDK。这是一个只管理 cloudflare_worker 和/或 cloudflare_workers_deployment 资源,同时使用 Wrangler 进行构建和 Version 管理的合理场景。
Worker version 在 API 层面是不可变的,意味着创建后无法更新,只能重新创建以应用所需更改。这意味着对 cloudflare_worker_version Terraform 资源的有意义更改总是会触发替换。当 cloudflare_worker_version 资源被替换时,会创建一个包含所需更改的新 version,但之前的 version 不会被删除。这确保了通过 Terraform 管理的 Worker 拥有完整的 version 历史。换句话说,version 既不可变,又只能追加。当父级 cloudflare_worker 资源被删除时,与该 Worker 关联的所有现有 version 也会被删除。
Worker version 模块支持两种互斥的内容提供方式:
content_file— 指向本地文件content_base64— 内联 base64 编码内容
在这两种情况下,底层内容的更改都通过计算属性 content_sha256 进行跟踪。在几乎所有情况下,优先使用 content_file 属性指定内容,因为它避免将内容本身存储在 state 中。模块内容可能相当大(可达数十 MB),将其存储在 state 中会膨胀 state 文件并对 Terraform 操作的性能产生负面影响。content_base64 属性的主要用例是从 API 导入 cloudflare_worker_version Terraform 资源,如下所述。
在导入期间,Terraform 始终会在 state 中填充 content_base64 属性,无论你的配置中使用的是哪个属性。
terraform import cloudflare_worker_version.my_worker_version <account_id>/<worker_id>/<version_id>如果你的配置使用 content_file,导入后会出现不匹配(state 使用 content_base64,配置使用 content_file)。这是预期行为。
假设 content_file 引用的本地文件内容与导入的内容匹配,且它们的 content_sha256 值相同,这将导致 cloudflare_worker_version Terraform 资源的就地更新。这应该是就地更新而非替换,因为底层内容没有变化(两种情况下 content_sha256 属性相同),且资源无需在 API 层面更新。唯一需要更新的是 Terraform state,更新后将从 content_base64 切换为 content_file。
如果 Terraform 想要替换资源,并指出计算出的 content_sha256 值存在差异,则 content_file 引用的本地文件内容与导入的内容不匹配,在不更新本地文件以匹配预期的 API 值的情况下,无法干净地导入该资源。
使用 content_file:
resource "cloudflare_worker_version" "content_file_example" {
account_id = var.account_id
worker_id = cloudflare_worker.example.id
main_module = "worker.js"
modules = [{
name = "worker.js"
content_type = "application/javascript+module"
content_file = "build/worker.js"
}]
}使用 content_base64:
resource "cloudflare_worker_version" "content_base64_example" {
account_id = var.account_id
worker_id = cloudflare_worker.example.id
main_module = "worker.js"
modules = [{
name = "worker.js"
content_type = "application/javascript+module"
content_base64 = base64encode("export default { async fetch() { return new Response('Hello world!') } }")
}]
}