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

Add support for custom instance limits for containers. For example, instead of
having to use the preconfigured dev/standard/basic instance types, you can now
set:

```
instance_type: {
vcpu: 1,
memory_mib: 1024,
disk_mb: 4000
}
```

This feature is currently only available to customers on an enterprise plan.
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
90 changes: 90 additions & 0 deletions packages/wrangler/src/__tests__/containers/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ describe("getNormalizedContainerOptions", () => {
});

it("should handle custom limit configuration", async () => {
// deprecated path for setting custom limits
const config: Config = {
name: "test-worker",
configPath: "/test/wrangler.toml",
Expand Down Expand Up @@ -240,6 +241,95 @@ describe("getNormalizedContainerOptions", () => {
});
});

it("should handle custom limit configuration through instance_type", async () => {
// updated path for setting custom limits
const config: Config = {
name: "test-worker",
configPath: "/test/wrangler.toml",
userConfigPath: "/test/wrangler.toml",
topLevelName: "test-worker",
containers: [
{
name: "test-container",
class_name: "TestContainer",
image: "registry.example.com/test:latest",
instance_type: {
disk_mb: 5000,
memory_mib: 1024,
vcpu: 2,
},
},
],
durable_objects: {
bindings: [
{
name: "TEST_DO",
class_name: "TestContainer",
},
],
},
} as Partial<Config> as Config;

const result = await getNormalizedContainerOptions(config);
expect(result).toHaveLength(1);
expect(result[0]).toMatchObject({
name: "test-container",
class_name: "TestContainer",
max_instances: 0,
scheduling_policy: "default",
rollout_step_percentage: 25,
rollout_kind: "full_auto",
disk_bytes: 5_000_000_000, // 5000 MB in bytes
memory_mib: 1024,
vcpu: 2,
image_uri: "registry.example.com/test:latest",
constraints: { tier: 1 },
});
});

it("should normalize and set defaults for custom limits to dev instance type", async () => {
const config: Config = {
name: "test-worker",
configPath: "/test/wrangler.toml",
userConfigPath: "/test/wrangler.toml",
topLevelName: "test-worker",
containers: [
{
name: "test-container",
class_name: "TestContainer",
image: "registry.example.com/test:latest",
instance_type: {
vcpu: 2,
},
},
],
durable_objects: {
bindings: [
{
name: "TEST_DO",
class_name: "TestContainer",
},
],
},
} as Partial<Config> as Config;

const result = await getNormalizedContainerOptions(config);
expect(result).toHaveLength(1);
expect(result[0]).toMatchObject({
name: "test-container",
class_name: "TestContainer",
max_instances: 0,
scheduling_policy: "default",
rollout_step_percentage: 25,
rollout_kind: "full_auto",
disk_bytes: 2_000_000_000, // 2000 MB in bytes
memory_mib: 256,
vcpu: 2,
image_uri: "registry.example.com/test:latest",
constraints: { tier: 1 },
});
});

it("should handle instance type configuration", async () => {
const config: Config = {
name: "test-worker",
Expand Down
Loading
Loading