Skip to content

Commit 0f7820e

Browse files
Add support for custom instance types (#10051)
* Add support for custom instance types Custom instance types could previously only be set through setting vcpu/memory/disk through the `configuration` block. As we deprecate `configuration`, move setting vcpu, memory, and disk to the instance type field of wrangler. By nature, this enforces that custom instance types are mutually exclusive with the named instance types and will allow us to remove a lot of the logic that verifies this once `configuration` is removed.
1 parent a24c9d8 commit 0f7820e

File tree

9 files changed

+468
-35
lines changed

9 files changed

+468
-35
lines changed

.changeset/hungry-turtles-beam.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
Add support for custom instance limits for containers. For example, instead of
6+
having to use the preconfigured dev/standard/basic instance types, you can now
7+
set:
8+
9+
```
10+
instance_type: {
11+
vcpu: 1,
12+
memory_mib: 1024,
13+
disk_mb: 4000
14+
}
15+
```
16+
17+
This feature is currently only available to customers on an enterprise plan.

packages/wrangler/src/__tests__/cloudchamber/apply.test.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1614,6 +1614,63 @@ describe("cloudchamber apply", () => {
16141614
expect(std.stderr).toMatchInlineSnapshot(`""`);
16151615
});
16161616

1617+
test("can apply a simple application (custom instance type)", async () => {
1618+
setIsTTY(false);
1619+
writeWranglerConfig({
1620+
name: "my-container",
1621+
containers: [
1622+
{
1623+
name: "my-container-app",
1624+
instances: 3,
1625+
class_name: "DurableObjectClass",
1626+
instance_type: {
1627+
vcpu: 1,
1628+
memory_mib: 1024,
1629+
disk_mb: 2000,
1630+
},
1631+
image: "docker.io/beep:boop",
1632+
constraints: {
1633+
tier: 2,
1634+
},
1635+
},
1636+
],
1637+
});
1638+
mockGetApplications([]);
1639+
mockCreateApplication({ id: "abc" });
1640+
await runWrangler("cloudchamber apply");
1641+
expect(std.stdout).toMatchInlineSnapshot(`
1642+
"╭ Deploy a container application deploy changes to your application
1643+
1644+
│ Container application changes
1645+
1646+
├ NEW my-container-app
1647+
1648+
│ [[containers]]
1649+
│ name = \\"my-container-app\\"
1650+
│ instances = 3
1651+
│ scheduling_policy = \\"default\\"
1652+
1653+
│ [containers.constraints]
1654+
│ tier = 2
1655+
1656+
│ [containers.configuration]
1657+
│ image = \\"docker.io/beep:boop\\"
1658+
│ vcpu = 1
1659+
│ memory_mib = 1_024
1660+
1661+
│ [containers.configuration.disk]
1662+
│ size_mb = 2_000
1663+
1664+
1665+
│ SUCCESS Created application my-container-app (Application ID: abc)
1666+
1667+
╰ Applied changes
1668+
1669+
"
1670+
`);
1671+
expect(std.stderr).toMatchInlineSnapshot(`""`);
1672+
});
1673+
16171674
test("can apply a simple existing application (instance type)", async () => {
16181675
setIsTTY(false);
16191676
writeWranglerConfig({
@@ -1691,6 +1748,96 @@ describe("cloudchamber apply", () => {
16911748
expect(app.configuration?.instance_type).toEqual("standard");
16921749
});
16931750

1751+
test("can apply a simple existing application (custom instance type)", async () => {
1752+
setIsTTY(false);
1753+
writeWranglerConfig({
1754+
name: "my-container",
1755+
containers: [
1756+
{
1757+
name: "my-container-app",
1758+
instances: 4,
1759+
class_name: "DurableObjectClass",
1760+
instance_type: {
1761+
vcpu: 1,
1762+
memory_mib: 1024,
1763+
disk_mb: 6000,
1764+
},
1765+
image: "docker.io/beep:boop",
1766+
constraints: {
1767+
tier: 2,
1768+
},
1769+
},
1770+
],
1771+
});
1772+
mockGetApplications([
1773+
{
1774+
id: "abc",
1775+
name: "my-container-app",
1776+
instances: 3,
1777+
created_at: new Date().toString(),
1778+
version: 1,
1779+
account_id: "1",
1780+
scheduling_policy: SchedulingPolicy.REGIONAL,
1781+
configuration: {
1782+
image: "docker.io/beep:boop",
1783+
disk: {
1784+
size: "2GB",
1785+
size_mb: 2000,
1786+
},
1787+
vcpu: 0.0625,
1788+
memory: "256MB",
1789+
memory_mib: 256,
1790+
},
1791+
constraints: {
1792+
tier: 3,
1793+
},
1794+
},
1795+
]);
1796+
const applicationReqBodyPromise = mockModifyApplication();
1797+
await runWrangler("cloudchamber apply");
1798+
expect(std.stdout).toMatchInlineSnapshot(`
1799+
"╭ Deploy a container application deploy changes to your application
1800+
1801+
│ Container application changes
1802+
1803+
├ EDIT my-container-app
1804+
1805+
│ [[containers]]
1806+
│ - instances = 3
1807+
│ + instances = 4
1808+
│ name = \\"my-container-app\\"
1809+
│ scheduling_policy = \\"regional\\"
1810+
1811+
│ [containers.configuration]
1812+
│ image = \\"docker.io/beep:boop\\"
1813+
│ memory = \\"256MB\\"
1814+
│ - memory_mib = 256
1815+
│ + memory_mib = 1_024
1816+
1817+
│ - vcpu = 0.0625
1818+
│ + vcpu = 1
1819+
1820+
│ [containers.configuration.disk]
1821+
│ size = \\"2GB\\"
1822+
│ - size_mb = 2_000
1823+
│ + size_mb = 6_000
1824+
1825+
│ [containers.constraints]
1826+
│ - tier = 3
1827+
│ + tier = 2
1828+
1829+
1830+
│ SUCCESS Modified application my-container-app
1831+
1832+
╰ Applied changes
1833+
1834+
"
1835+
`);
1836+
expect(std.stderr).toMatchInlineSnapshot(`""`);
1837+
const app = await applicationReqBodyPromise;
1838+
expect(app.configuration?.instance_type).toBeUndefined();
1839+
});
1840+
16941841
test("falls back on dev instance type when instance type is absent", async () => {
16951842
setIsTTY(false);
16961843
writeWranglerConfig({

packages/wrangler/src/__tests__/config/configuration.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2454,10 +2454,10 @@ describe("normalizeAndValidateConfig()", () => {
24542454
- The image \\"something\\" does not appear to be a valid path to a Dockerfile, or a valid image registry path:
24552455
If this is an image registry path, it needs to include at least a tag ':' (e.g: docker.io/httpd:1)
24562456
- Expected \\"containers.rollout_kind\\" field to be one of [\\"full_auto\\",\\"full_manual\\",\\"none\\"] but got \\"invalid\\".
2457-
- Expected \\"containers.instance_type\\" field to be one of [\\"dev\\",\\"basic\\",\\"standard\\"] but got \\"invalid\\".
24582457
- Expected \\"containers.max_instances\\" to be of type number but got \\"invalid\\".
24592458
- Expected \\"containers.image_vars\\" to be of type object but got \\"invalid\\".
2460-
- Expected \\"containers.scheduling_policy\\" field to be one of [\\"regional\\",\\"moon\\",\\"default\\"] but got \\"invalid\\"."
2459+
- Expected \\"containers.scheduling_policy\\" field to be one of [\\"regional\\",\\"moon\\",\\"default\\"] but got \\"invalid\\".
2460+
- Expected \\"containers.instance_type\\" field to be one of [\\"dev\\",\\"basic\\",\\"standard\\"] but got \\"invalid\\"."
24612461
`);
24622462
});
24632463

@@ -2485,7 +2485,7 @@ describe("normalizeAndValidateConfig()", () => {
24852485

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

25202520
expect(diagnostics.renderWarnings()).toMatchInlineSnapshot(`
25212521
"Processing wrangler configuration:
2522-
- \\"containers.configuration\\" is deprecated. Use top level \\"containers\\" fields instead. \\"configuration.image\\" should be \\"image\\", \\"configuration.disk\\" should be set via \\"instance_type\\".
2522+
- \\"containers.configuration\\" is deprecated. Use top level \\"containers\\" fields instead. \\"configuration.image\\" should be \\"image\\", limits should be set via \\"instance_type\\".
25232523
- Unexpected fields found in containers.configuration field: \\"memory\\",\\"invalid_field\\",\\"another_invalid\\""
25242524
`);
25252525
});

packages/wrangler/src/__tests__/containers/config.test.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ describe("getNormalizedContainerOptions", () => {
196196
});
197197

198198
it("should handle custom limit configuration", async () => {
199+
// deprecated path for setting custom limits
199200
const config: Config = {
200201
name: "test-worker",
201202
configPath: "/test/wrangler.toml",
@@ -240,6 +241,95 @@ describe("getNormalizedContainerOptions", () => {
240241
});
241242
});
242243

244+
it("should handle custom limit configuration through instance_type", async () => {
245+
// updated path for setting custom limits
246+
const config: Config = {
247+
name: "test-worker",
248+
configPath: "/test/wrangler.toml",
249+
userConfigPath: "/test/wrangler.toml",
250+
topLevelName: "test-worker",
251+
containers: [
252+
{
253+
name: "test-container",
254+
class_name: "TestContainer",
255+
image: "registry.example.com/test:latest",
256+
instance_type: {
257+
disk_mb: 5000,
258+
memory_mib: 1024,
259+
vcpu: 2,
260+
},
261+
},
262+
],
263+
durable_objects: {
264+
bindings: [
265+
{
266+
name: "TEST_DO",
267+
class_name: "TestContainer",
268+
},
269+
],
270+
},
271+
} as Partial<Config> as Config;
272+
273+
const result = await getNormalizedContainerOptions(config);
274+
expect(result).toHaveLength(1);
275+
expect(result[0]).toMatchObject({
276+
name: "test-container",
277+
class_name: "TestContainer",
278+
max_instances: 0,
279+
scheduling_policy: "default",
280+
rollout_step_percentage: 25,
281+
rollout_kind: "full_auto",
282+
disk_bytes: 5_000_000_000, // 5000 MB in bytes
283+
memory_mib: 1024,
284+
vcpu: 2,
285+
image_uri: "registry.example.com/test:latest",
286+
constraints: { tier: 1 },
287+
});
288+
});
289+
290+
it("should normalize and set defaults for custom limits to dev instance type", async () => {
291+
const config: Config = {
292+
name: "test-worker",
293+
configPath: "/test/wrangler.toml",
294+
userConfigPath: "/test/wrangler.toml",
295+
topLevelName: "test-worker",
296+
containers: [
297+
{
298+
name: "test-container",
299+
class_name: "TestContainer",
300+
image: "registry.example.com/test:latest",
301+
instance_type: {
302+
vcpu: 2,
303+
},
304+
},
305+
],
306+
durable_objects: {
307+
bindings: [
308+
{
309+
name: "TEST_DO",
310+
class_name: "TestContainer",
311+
},
312+
],
313+
},
314+
} as Partial<Config> as Config;
315+
316+
const result = await getNormalizedContainerOptions(config);
317+
expect(result).toHaveLength(1);
318+
expect(result[0]).toMatchObject({
319+
name: "test-container",
320+
class_name: "TestContainer",
321+
max_instances: 0,
322+
scheduling_policy: "default",
323+
rollout_step_percentage: 25,
324+
rollout_kind: "full_auto",
325+
disk_bytes: 2_000_000_000, // 2000 MB in bytes
326+
memory_mib: 256,
327+
vcpu: 2,
328+
image_uri: "registry.example.com/test:latest",
329+
constraints: { tier: 1 },
330+
});
331+
});
332+
243333
it("should handle instance type configuration", async () => {
244334
const config: Config = {
245335
name: "test-worker",

0 commit comments

Comments
 (0)