Skip to content
5 changes: 5 additions & 0 deletions .changeset/hungry-turtles-beam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": patch
---

Add support for custom instance types for users without the `REQUIRE_INSTANCE_TYPE` capability
147 changes: 147 additions & 0 deletions packages/wrangler/src/__tests__/cloudchamber/apply.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1614,6 +1614,63 @@ describe("cloudchamber apply", () => {
expect(std.stderr).toMatchInlineSnapshot(`""`);
});

test("can apply a simple application (custom instance type)", async () => {
setIsTTY(false);
writeWranglerConfig({
name: "my-container",
containers: [
{
name: "my-container-app",
instances: 3,
class_name: "DurableObjectClass",
instance_type: {
vcpu: 1,
memory_mib: 1024,
disk_mb: 2000,
},
image: "docker.io/beep:boop",
constraints: {
tier: 2,
},
},
],
});
mockGetApplications([]);
mockCreateApplication({ id: "abc" });
await runWrangler("cloudchamber apply");
expect(std.stdout).toMatchInlineSnapshot(`
"╭ Deploy a container application deploy changes to your application
│ Container application changes
├ NEW my-container-app
│ [[containers]]
│ name = \\"my-container-app\\"
│ instances = 3
│ scheduling_policy = \\"default\\"
│ [containers.constraints]
│ tier = 2
│ [containers.configuration]
│ image = \\"docker.io/beep:boop\\"
│ vcpu = 1
│ memory_mib = 1_024
│ [containers.configuration.disk]
│ size_mb = 2_000
│ SUCCESS Created application my-container-app (Application ID: abc)
╰ Applied changes

"
`);
expect(std.stderr).toMatchInlineSnapshot(`""`);
});

test("can apply a simple existing application (instance type)", async () => {
setIsTTY(false);
writeWranglerConfig({
Expand Down Expand Up @@ -1691,6 +1748,96 @@ describe("cloudchamber apply", () => {
expect(app.configuration?.instance_type).toEqual("standard");
});

test("can apply a simple existing application (custom instance type)", async () => {
setIsTTY(false);
writeWranglerConfig({
name: "my-container",
containers: [
{
name: "my-container-app",
instances: 4,
class_name: "DurableObjectClass",
instance_type: {
vcpu: 1,
memory_mib: 1024,
disk_mb: 6000,
},
image: "docker.io/beep:boop",
constraints: {
tier: 2,
},
},
],
});
mockGetApplications([
{
id: "abc",
name: "my-container-app",
instances: 3,
created_at: new Date().toString(),
version: 1,
account_id: "1",
scheduling_policy: SchedulingPolicy.REGIONAL,
configuration: {
image: "docker.io/beep:boop",
disk: {
size: "2GB",
size_mb: 2000,
},
vcpu: 0.0625,
memory: "256MB",
memory_mib: 256,
},
constraints: {
tier: 3,
},
},
]);
const applicationReqBodyPromise = mockModifyApplication();
await runWrangler("cloudchamber apply");
expect(std.stdout).toMatchInlineSnapshot(`
"╭ Deploy a container application deploy changes to your application
│ Container application changes
├ EDIT my-container-app
│ [[containers]]
│ - instances = 3
│ + instances = 4
│ name = \\"my-container-app\\"
│ scheduling_policy = \\"regional\\"
│ [containers.configuration]
│ image = \\"docker.io/beep:boop\\"
│ memory = \\"256MB\\"
│ - memory_mib = 256
│ + memory_mib = 1_024
│ - vcpu = 0.0625
│ + vcpu = 1
│ [containers.configuration.disk]
│ size = \\"2GB\\"
│ - size_mb = 2_000
│ + size_mb = 6_000
│ [containers.constraints]
│ - tier = 3
│ + tier = 2
│ SUCCESS Modified application my-container-app
╰ Applied changes

"
`);
expect(std.stderr).toMatchInlineSnapshot(`""`);
const app = await applicationReqBodyPromise;
expect(app.configuration?.instance_type).toBeUndefined();
});

test("falls back on dev instance type when instance type is absent", async () => {
setIsTTY(false);
writeWranglerConfig({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2454,10 +2454,10 @@ describe("normalizeAndValidateConfig()", () => {
- The image \\"something\\" does not appear to be a valid path to a Dockerfile, or a valid image registry path:
If this is an image registry path, it needs to include at least a tag ':' (e.g: docker.io/httpd:1)
- Expected \\"containers.rollout_kind\\" field to be one of [\\"full_auto\\",\\"full_manual\\",\\"none\\"] but got \\"invalid\\".
- Expected \\"containers.instance_type\\" field to be one of [\\"dev\\",\\"basic\\",\\"standard\\"] but got \\"invalid\\".
- Expected \\"containers.max_instances\\" to be of type number but got \\"invalid\\".
- Expected \\"containers.image_vars\\" to be of type object but got \\"invalid\\".
- Expected \\"containers.scheduling_policy\\" field to be one of [\\"regional\\",\\"moon\\",\\"default\\"] but got \\"invalid\\"."
- Expected \\"containers.scheduling_policy\\" field to be one of [\\"regional\\",\\"moon\\",\\"default\\"] but got \\"invalid\\".
- Expected \\"containers.instance_type\\" field to be one of [\\"dev\\",\\"basic\\",\\"standard\\"] but got \\"invalid\\"."
`);
});

Expand Down Expand Up @@ -2485,7 +2485,7 @@ describe("normalizeAndValidateConfig()", () => {

expect(diagnostics.renderWarnings()).toMatchInlineSnapshot(`
"Processing wrangler configuration:
- \\"containers.configuration\\" is deprecated. Use top level \\"containers\\" fields instead. \\"configuration.image\\" should be \\"image\\", \\"configuration.disk\\" should be set via \\"instance_type\\".
- \\"containers.configuration\\" is deprecated. Use top level \\"containers\\" fields instead. \\"configuration.image\\" should be \\"image\\", limits should be set via \\"instance_type\\".
- \\"containers.instances\\" is deprecated. Use \\"containers.max_instances\\" instead.
- \\"containers.durable_objects\\" is deprecated. Use the \\"class_name\\" field instead."
`);
Expand Down Expand Up @@ -2519,7 +2519,7 @@ describe("normalizeAndValidateConfig()", () => {

expect(diagnostics.renderWarnings()).toMatchInlineSnapshot(`
"Processing wrangler configuration:
- \\"containers.configuration\\" is deprecated. Use top level \\"containers\\" fields instead. \\"configuration.image\\" should be \\"image\\", \\"configuration.disk\\" should be set via \\"instance_type\\".
- \\"containers.configuration\\" is deprecated. Use top level \\"containers\\" fields instead. \\"configuration.image\\" should be \\"image\\", limits should be set via \\"instance_type\\".
- Unexpected fields found in containers.configuration field: \\"memory\\",\\"invalid_field\\",\\"another_invalid\\""
`);
});
Expand Down
85 changes: 85 additions & 0 deletions packages/wrangler/src/__tests__/containers/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,91 @@ describe("wrangler deploy with containers", () => {
`);
});

it("should be able to deploy a new container with custom instance limits (instance_type)", async () => {
// note no docker commands have been mocked here!
mockGetVersion("Galaxy-Class");
writeWranglerConfig({
...DEFAULT_DURABLE_OBJECTS,
containers: [
{
...DEFAULT_CONTAINER_FROM_REGISTRY,
instance_type: {
vcpu: 1,
memory_mib: 1000,
disk_mb: 2000,
},
},
],
});

mockGetApplications([]);

mockCreateApplication({
name: "my-container",
max_instances: 10,
scheduling_policy: SchedulingPolicy.DEFAULT,
configuration: {
image: "docker.io/hello:world",
disk: {
size_mb: 2000,
},
vcpu: 1,
memory_mib: 1000,
},
});

await runWrangler("deploy index.js");

expect(std.out).toMatchInlineSnapshot(`
"Total Upload: xx KiB / gzip: xx KiB
Worker Startup Time: 100 ms
Your Worker has access to the following bindings:
Binding Resource
env.EXAMPLE_DO_BINDING (ExampleDurableObject) Durable Object

Uploaded test-name (TIMINGS)
Deployed test-name triggers (TIMINGS)
https://test-name.test-sub-domain.workers.dev
Current Version ID: Galaxy-Class"
`);
expect(std.err).toMatchInlineSnapshot(`""`);

expect(cliStd.stdout).toMatchInlineSnapshot(`
"╭ Deploy a container application deploy changes to your application
│ Container application changes
├ NEW my-container
│ [[containers]]
│ name = \\"my-container\\"
│ scheduling_policy = \\"default\\"
│ instances = 0
│ max_instances = 10
│ [containers.configuration]
│ image = \\"docker.io/hello:world\\"
│ memory_mib = 1_000
│ vcpu = 1
│ [containers.configuration.disk]
│ size_mb = 2_000
│ [containers.constraints]
│ tier = 1
│ [containers.durable_objects]
│ namespace_id = \\"1\\"
│ SUCCESS Created application my-container (Application ID: undefined)
╰ Applied changes

"
`);
});

it("should resolve the docker build context path based on the dockerfile location, if image_build_context is not provided", async () => {
vi.stubEnv("WRANGLER_DOCKER_BIN", "/usr/bin/docker");
mockGetVersion("Galaxy-Class");
Expand Down
28 changes: 20 additions & 8 deletions packages/wrangler/src/cloudchamber/apply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,22 +188,34 @@ function observabilityToConfiguration(

function containerAppToInstanceType(
containerApp: ContainerApp
): InstanceType | undefined {
): Partial<UserDeploymentConfiguration> {
let configuration = (containerApp.configuration ??
{}) as Partial<UserDeploymentConfiguration>;

if (containerApp.instance_type !== undefined) {
return containerApp.instance_type as InstanceType;
if (typeof containerApp.instance_type === "string") {
return { instance_type: containerApp.instance_type as InstanceType };
}

configuration = {
vcpu: containerApp.instance_type.vcpu,
memory_mib: containerApp.instance_type.memory_mib,
disk: {
size_mb: containerApp.instance_type.disk_mb,
},
};
}

// if no other configuration is set, we fall back to the default "dev" instance type
const configuration =
containerApp.configuration as UserDeploymentConfiguration;
if (
configuration.disk === undefined &&
configuration.disk?.size_mb === undefined &&
configuration.vcpu === undefined &&
configuration.memory === undefined &&
configuration.memory_mib === undefined
) {
return InstanceType.DEV;
return { instance_type: InstanceType.DEV };
}

return configuration;
}

function containerAppToCreateApplication(
Expand All @@ -220,8 +232,8 @@ function containerAppToCreateApplication(
const instanceType = containerAppToInstanceType(containerApp);
const configuration: UserDeploymentConfiguration = {
...(containerApp.configuration as UserDeploymentConfiguration),
...instanceType,
observability: observabilityConfiguration,
instance_type: instanceType,
};

// this should have been set to a default value of worker-name-class-name if unspecified by the user
Expand Down
10 changes: 9 additions & 1 deletion packages/wrangler/src/config/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,15 @@ export type ContainerApp = {
* @optional
* @default "dev"
*/
instance_type?: "dev" | "basic" | "standard";
instance_type?:
| "dev"
| "basic"
| "standard"
| {
vcpu?: number;
memory_mib?: number;
disk_mb?: number;
};

/**
* @deprecated Use top level `containers` fields instead.
Expand Down
Loading
Loading