Skip to content

Commit

Permalink
feat: implement service environments + durable objects
Browse files Browse the repository at this point in the history
Now that the APIs for getting migrations tags of services works as expected, this lands support for publishing durable objects to service environments, including migrations. It also removes the error we used to throw when attempting to use service envs + durable objects.

Fixes #739
  • Loading branch information
threepointone committed Apr 30, 2022
1 parent 0a79d75 commit a65e778
Show file tree
Hide file tree
Showing 4 changed files with 300 additions and 29 deletions.
9 changes: 9 additions & 0 deletions .changeset/nasty-teachers-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"wrangler": patch
---

feat: implement service environments + durable objects

Now that the APIs for getting migrations tags of services works as expected, this lands support for publishing durable objects to service environments, including migrations. It also removes the error we used to throw when attempting to use service envs + durable objects.

Fixes https://github.com/cloudflare/wrangler2/issues/739
268 changes: 252 additions & 16 deletions packages/wrangler/src/__tests__/publish.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2389,7 +2389,7 @@ export default{
`export class SomeClass{}; export class SomeOtherClass{}; export default {};`
);
mockSubDomainRequest();
mockScriptData({ scripts: [] }); // no previously uploaded scripts at all
mockLegacyScriptData({ scripts: [] }); // no previously uploaded scripts at all
mockUploadWorkerRequest({
expectedMigrations: {
new_tag: "v2",
Expand Down Expand Up @@ -2428,7 +2428,9 @@ export default{
`export class SomeClass{}; export class SomeOtherClass{}; export default {};`
);
mockSubDomainRequest();
mockScriptData({ scripts: [{ id: "test-name", migration_tag: "v1" }] });
mockLegacyScriptData({
scripts: [{ id: "test-name", migration_tag: "v1" }],
});
mockUploadWorkerRequest({
expectedMigrations: {
old_tag: "v1",
Expand Down Expand Up @@ -2473,7 +2475,9 @@ export default{
`export class SomeClass{}; export class SomeOtherClass{}; export class YetAnotherClass{}; export default {};`
);
mockSubDomainRequest();
mockScriptData({ scripts: [{ id: "test-name", migration_tag: "v3" }] });
mockLegacyScriptData({
scripts: [{ id: "test-name", migration_tag: "v3" }],
});
mockUploadWorkerRequest({
expectedMigrations: undefined,
});
Expand All @@ -2492,36 +2496,208 @@ export default{
});

describe("service environments", () => {
it("should error when using service environments + durable objects", async () => {
it("should publish all migrations on first publish", async () => {
writeWranglerToml({
durable_objects: {
bindings: [
{ name: "SOMENAME", class_name: "SomeClass" },
{ name: "SOMEOTHERNAME", class_name: "SomeOtherClass" },
],
},
migrations: [{ tag: "v1", new_classes: ["SomeClass"] }],
migrations: [
{ tag: "v1", new_classes: ["SomeClass"] },
{ tag: "v2", new_classes: ["SomeOtherClass"] },
],
});
fs.writeFileSync(
"index.js",
`export class SomeClass{}; export class SomeOtherClass{}; export default {};`
);
mockSubDomainRequest();
mockServiceScriptData({}); // no scripts at all
mockUploadWorkerRequest({
legacyEnv: false,
expectedMigrations: {
new_tag: "v2",
steps: [
{ new_classes: ["SomeClass"] },
{ new_classes: ["SomeOtherClass"] },
],
},
});

await runWrangler("publish index.js --legacy-env false");
expect(std.out).toMatchInlineSnapshot(`
"Uploaded test-name (TIMINGS)
Published test-name (TIMINGS)
test-name.test-sub-domain.workers.dev"
`);
expect(std.err).toMatchInlineSnapshot(`""`);
expect(std.warn).toMatchInlineSnapshot(`
"▲ [WARNING] Service environments are in beta, and their behaviour is guaranteed to change in the future. DO NOT USE IN PRODUCTION.
"
`);
});

it("should publish all migrations on first publish (--env)", async () => {
writeWranglerToml({
durable_objects: {
bindings: [
{ name: "SOMENAME", class_name: "SomeClass" },
{ name: "SOMEOTHERNAME", class_name: "SomeOtherClass" },
],
},
env: {
xyz: {
durable_objects: {
bindings: [
{ name: "SOMENAME", class_name: "SomeClass" },
{ name: "SOMEOTHERNAME", class_name: "SomeOtherClass" },
],
},
},
},
migrations: [
{ tag: "v1", new_classes: ["SomeClass"] },
{ tag: "v2", new_classes: ["SomeOtherClass"] },
],
});
fs.writeFileSync(
"index.js",
`export class SomeClass{}; export class SomeOtherClass{}; export default {};`
);
mockSubDomainRequest();
mockServiceScriptData({ env: "xyz" }); // no scripts at all
mockUploadWorkerRequest({
legacyEnv: false,
env: "xyz",
expectedMigrations: {
new_tag: "v2",
steps: [
{ new_classes: ["SomeClass"] },
{ new_classes: ["SomeOtherClass"] },
],
},
});

await expect(
runWrangler("publish index.js --legacy-env false")
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Publishing Durable Objects to a service environment is not currently supported. This is being tracked at https://github.com/cloudflare/wrangler2/issues/739"`
await runWrangler("publish index.js --legacy-env false --env xyz");
expect(std.out).toMatchInlineSnapshot(`
"Uploaded test-name (xyz) (TIMINGS)
Published test-name (xyz) (TIMINGS)
xyz.test-name.test-sub-domain.workers.dev"
`);
expect(std.err).toMatchInlineSnapshot(`""`);
expect(std.warn).toMatchInlineSnapshot(`
"▲ [WARNING] Service environments are in beta, and their behaviour is guaranteed to change in the future. DO NOT USE IN PRODUCTION.
"
`);
});

it("should use a script's current migration tag when publishing migrations", async () => {
writeWranglerToml({
durable_objects: {
bindings: [
{ name: "SOMENAME", class_name: "SomeClass" },
{ name: "SOMEOTHERNAME", class_name: "SomeOtherClass" },
],
},
migrations: [
{ tag: "v1", new_classes: ["SomeClass"] },
{ tag: "v2", new_classes: ["SomeOtherClass"] },
],
});
fs.writeFileSync(
"index.js",
`export class SomeClass{}; export class SomeOtherClass{}; export default {};`
);
mockSubDomainRequest();
mockServiceScriptData({
script: { id: "test-name", migration_tag: "v1" },
});
mockUploadWorkerRequest({
legacyEnv: false,
expectedMigrations: {
old_tag: "v1",
new_tag: "v2",
steps: [
{
new_classes: ["SomeOtherClass"],
},
],
},
});

await runWrangler("publish index.js --legacy-env false");
expect(std).toMatchInlineSnapshot(`
Object {
"debug": "",
"err": "X [ERROR] Publishing Durable Objects to a service environment is not currently supported. This is being tracked at https://github.com/cloudflare/wrangler2/issues/739
"err": "",
"out": "Uploaded test-name (TIMINGS)
Published test-name (TIMINGS)
test-name.test-sub-domain.workers.dev",
"warn": "▲ [WARNING] Service environments are in beta, and their behaviour is guaranteed to change in the future. DO NOT USE IN PRODUCTION.
",
"out": "
If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new.",
}
`);
});

it("should use an environment's current migration tag when publishing migrations", async () => {
writeWranglerToml({
durable_objects: {
bindings: [
{ name: "SOMENAME", class_name: "SomeClass" },
{ name: "SOMEOTHERNAME", class_name: "SomeOtherClass" },
],
},
env: {
xyz: {
durable_objects: {
bindings: [
{ name: "SOMENAME", class_name: "SomeClass" },
{ name: "SOMEOTHERNAME", class_name: "SomeOtherClass" },
],
},
},
},
migrations: [
{ tag: "v1", new_classes: ["SomeClass"] },
{ tag: "v2", new_classes: ["SomeOtherClass"] },
],
});
fs.writeFileSync(
"index.js",
`export class SomeClass{}; export class SomeOtherClass{}; export default {};`
);
mockSubDomainRequest();
mockServiceScriptData({
script: { id: "test-name", migration_tag: "v1" },
env: "xyz",
});
mockUploadWorkerRequest({
legacyEnv: false,
env: "xyz",
expectedMigrations: {
old_tag: "v1",
new_tag: "v2",
steps: [
{
new_classes: ["SomeOtherClass"],
},
],
},
});

await runWrangler("publish index.js --legacy-env false --env xyz");
expect(std).toMatchInlineSnapshot(`
Object {
"debug": "",
"err": "",
"out": "Uploaded test-name (xyz) (TIMINGS)
Published test-name (xyz) (TIMINGS)
xyz.test-name.test-sub-domain.workers.dev",
"warn": "▲ [WARNING] Service environments are in beta, and their behaviour is guaranteed to change in the future. DO NOT USE IN PRODUCTION.
",
Expand Down Expand Up @@ -2674,7 +2850,7 @@ export default{
],
});
mockSubDomainRequest();
mockScriptData({ scripts: [] });
mockLegacyScriptData({ scripts: [] });

await expect(runWrangler("publish index.js")).resolves.toBeUndefined();
expect(std.out).toMatchInlineSnapshot(`
Expand Down Expand Up @@ -3434,7 +3610,9 @@ export default{
`export class ExampleDurableObject {}; export default{};`
);
mockSubDomainRequest();
mockScriptData({ scripts: [{ id: "test-name", migration_tag: "v1" }] });
mockLegacyScriptData({
scripts: [{ id: "test-name", migration_tag: "v1" }],
});
mockUploadWorkerRequest({
expectedBindings: [
{
Expand Down Expand Up @@ -3508,7 +3686,9 @@ export default{
`export class ExampleDurableObject {}; export default{};`
);
mockSubDomainRequest();
mockScriptData({ scripts: [{ id: "test-name", migration_tag: "v1" }] });
mockLegacyScriptData({
scripts: [{ id: "test-name", migration_tag: "v1" }],
});
mockUploadWorkerRequest({
expectedType: "esm",
expectedBindings: [
Expand Down Expand Up @@ -4312,7 +4492,7 @@ function mockDeleteUnusedAssetsRequest(

type LegacyScriptInfo = { id: string; migration_tag?: string };

function mockScriptData(options: { scripts: LegacyScriptInfo[] }) {
function mockLegacyScriptData(options: { scripts: LegacyScriptInfo[] }) {
const { scripts } = options;
setMockResponse(
"/accounts/:accountId/workers/scripts",
Expand All @@ -4323,3 +4503,59 @@ function mockScriptData(options: { scripts: LegacyScriptInfo[] }) {
}
);
}

type DurableScriptInfo = { id: string; migration_tag?: string };

function mockServiceScriptData(options: {
script?: DurableScriptInfo;
scriptName?: string;
env?: string;
}) {
const { script } = options;
if (options.env) {
if (!script) {
setMockRawResponse(
"/accounts/:accountId/workers/services/:scriptName/environments/:envName",
"GET",
() => {
return createFetchResult(null, false, [
{ code: 10092, message: "workers.api.error.environment_not_found" },
]);
}
);
return;
}
setMockResponse(
"/accounts/:accountId/workers/services/:scriptName/environments/:envName",
"GET",
([_url, accountId, scriptName, envName]) => {
expect(accountId).toEqual("some-account-id");
expect(scriptName).toEqual(options.scriptName || "test-name");
expect(envName).toEqual(options.env);
return { script };
}
);
} else {
if (!script) {
setMockRawResponse(
"/accounts/:accountId/workers/services/:scriptName",
"GET",
() => {
return createFetchResult(null, false, [
{ code: 10090, message: "workers.api.error.service_not_found" },
]);
}
);
return;
}
setMockResponse(
"/accounts/:accountId/workers/services/:scriptName",
"GET",
([_url, accountId, scriptName]) => {
expect(accountId).toEqual("some-account-id");
expect(scriptName).toEqual(options.scriptName || "test-name");
return { default_environment: { script } };
}
);
}
}
2 changes: 1 addition & 1 deletion packages/wrangler/src/config/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1041,7 +1041,7 @@ const validateBindingsProperty =
diagnostics.warnings.push(
`The following bindings are at the top level, but not on "env.${envName}".\n` +
`This is not what you probably want, since "${field}" configuration is not inherited by environments.\n` +
`Please add a binding for each to "${fieldPath}.bindings".` +
`Please add a binding for each to "${fieldPath}.bindings":\n` +
missingBindings.map((name) => `- ${name}`).join("\n")
);
}
Expand Down
Loading

0 comments on commit a65e778

Please sign in to comment.