Skip to content

Commit cbb7baf

Browse files
committed
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 e2a434d commit cbb7baf

File tree

5 files changed

+237
-19
lines changed

5 files changed

+237
-19
lines changed

.changeset/hungry-turtles-beam.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
Add support for custom instance types

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

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

1588+
test("can apply a simple application (custom instance type)", async () => {
1589+
setIsTTY(false);
1590+
writeWranglerConfig({
1591+
name: "my-container",
1592+
containers: [
1593+
{
1594+
name: "my-container-app",
1595+
instances: 3,
1596+
class_name: "DurableObjectClass",
1597+
instance_type: {
1598+
vcpu: 1,
1599+
memory_mib: 1024,
1600+
disk_mb: 2000,
1601+
},
1602+
image: "./Dockerfile",
1603+
constraints: {
1604+
tier: 2,
1605+
},
1606+
},
1607+
],
1608+
});
1609+
mockGetApplications([]);
1610+
mockCreateApplication({ id: "abc" });
1611+
await runWrangler("cloudchamber apply");
1612+
expect(std.stdout).toMatchInlineSnapshot(`
1613+
"╭ Deploy a container application deploy changes to your application
1614+
1615+
│ Container application changes
1616+
1617+
├ NEW my-container-app
1618+
1619+
│ [[containers]]
1620+
│ name = \\"my-container-app\\"
1621+
│ instances = 3
1622+
│ scheduling_policy = \\"default\\"
1623+
1624+
│ [containers.constraints]
1625+
│ tier = 2
1626+
1627+
│ [containers.configuration]
1628+
│ image = \\"./Dockerfile\\"
1629+
│ vcpu = 1
1630+
│ memory_mib = 1_024
1631+
1632+
│ [containers.configuration.disk]
1633+
│ size_mb = 2_000
1634+
1635+
1636+
│ SUCCESS Created application my-container-app (Application ID: abc)
1637+
1638+
╰ Applied changes
1639+
1640+
"
1641+
`);
1642+
expect(std.stderr).toMatchInlineSnapshot(`""`);
1643+
});
1644+
15881645
test("can apply a simple existing application (instance type)", async () => {
15891646
setIsTTY(false);
15901647
writeWranglerConfig({
@@ -1662,6 +1719,96 @@ describe("cloudchamber apply", () => {
16621719
expect(app.configuration?.instance_type).toEqual("standard");
16631720
});
16641721

1722+
test("can apply a simple existing application (custom instance type)", async () => {
1723+
setIsTTY(false);
1724+
writeWranglerConfig({
1725+
name: "my-container",
1726+
containers: [
1727+
{
1728+
name: "my-container-app",
1729+
instances: 4,
1730+
class_name: "DurableObjectClass",
1731+
instance_type: {
1732+
vcpu: 1,
1733+
memory_mib: 1024,
1734+
disk_mb: 6000,
1735+
},
1736+
image: "./Dockerfile",
1737+
constraints: {
1738+
tier: 2,
1739+
},
1740+
},
1741+
],
1742+
});
1743+
mockGetApplications([
1744+
{
1745+
id: "abc",
1746+
name: "my-container-app",
1747+
instances: 3,
1748+
created_at: new Date().toString(),
1749+
version: 1,
1750+
account_id: "1",
1751+
scheduling_policy: SchedulingPolicy.REGIONAL,
1752+
configuration: {
1753+
image: "./Dockerfile",
1754+
disk: {
1755+
size: "2GB",
1756+
size_mb: 2000,
1757+
},
1758+
vcpu: 0.0625,
1759+
memory: "256MB",
1760+
memory_mib: 256,
1761+
},
1762+
constraints: {
1763+
tier: 3,
1764+
},
1765+
},
1766+
]);
1767+
const applicationReqBodyPromise = mockModifyApplication();
1768+
await runWrangler("cloudchamber apply");
1769+
expect(std.stdout).toMatchInlineSnapshot(`
1770+
"╭ Deploy a container application deploy changes to your application
1771+
1772+
│ Container application changes
1773+
1774+
├ EDIT my-container-app
1775+
1776+
│ [[containers]]
1777+
│ - instances = 3
1778+
│ + instances = 4
1779+
│ name = \\"my-container-app\\"
1780+
1781+
│ [containers.configuration]
1782+
│ ...
1783+
│ memory = \\"256MB\\"
1784+
│ - memory_mib = 256
1785+
│ + memory_mib = 1_024
1786+
│ - vcpu = 0.0625
1787+
│ + vcpu = 1
1788+
1789+
│ [containers.configuration.disk]
1790+
│ ...
1791+
│ size = \\"2GB\\"
1792+
│ - size_mb = 2_000
1793+
│ + size_mb = 6_000
1794+
1795+
│ [containers.constraints]
1796+
│ ...
1797+
│ - tier = 3
1798+
│ + tier = 2
1799+
1800+
1801+
│ SUCCESS Modified application my-container-app
1802+
1803+
╰ Applied changes
1804+
1805+
"
1806+
`);
1807+
expect(std.stderr).toMatchInlineSnapshot(`""`);
1808+
const app = await applicationReqBodyPromise;
1809+
expect(app.configuration?.instance_type).toBeUndefined();
1810+
});
1811+
16651812
test("falls back on dev instance type when instance type is absent", async () => {
16661813
setIsTTY(false);
16671814
writeWranglerConfig({

packages/wrangler/src/cloudchamber/apply.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -189,22 +189,38 @@ function observabilityToConfiguration(
189189

190190
function containerAppToInstanceType(
191191
containerApp: ContainerApp
192-
): InstanceType | undefined {
192+
): Partial<UserDeploymentConfiguration> {
193+
let configuration = (containerApp.configuration ??
194+
{}) as Partial<UserDeploymentConfiguration>;
195+
193196
if (containerApp.instance_type !== undefined) {
194-
return containerApp.instance_type as InstanceType;
197+
if (typeof containerApp.instance_type === "string") {
198+
return { instance_type: containerApp.instance_type as InstanceType };
199+
}
200+
201+
configuration = {
202+
vcpu: containerApp.instance_type.vcpu ?? containerApp.configuration?.vcpu,
203+
memory_mib:
204+
containerApp.instance_type.memory_mib ??
205+
containerApp.configuration?.memory_mib,
206+
disk: {
207+
size_mb:
208+
containerApp.instance_type.disk_mb ??
209+
containerApp.configuration?.disk?.size_mb,
210+
},
211+
};
195212
}
196213

197214
// if no other configuration is set, we fall back to the default "dev" instance type
198-
const configuration =
199-
containerApp.configuration as UserDeploymentConfiguration;
200215
if (
201-
configuration.disk === undefined &&
216+
configuration.disk?.size_mb === undefined &&
202217
configuration.vcpu === undefined &&
203-
configuration.memory === undefined &&
204218
configuration.memory_mib === undefined
205219
) {
206-
return InstanceType.DEV;
220+
return { instance_type: InstanceType.DEV };
207221
}
222+
223+
return configuration;
208224
}
209225

210226
function containerAppToCreateApplication(
@@ -221,8 +237,8 @@ function containerAppToCreateApplication(
221237
const instanceType = containerAppToInstanceType(containerApp);
222238
const configuration: UserDeploymentConfiguration = {
223239
...(containerApp.configuration as UserDeploymentConfiguration),
240+
...instanceType,
224241
observability: observabilityConfiguration,
225-
instance_type: instanceType,
226242
};
227243

228244
// this should have been set to a default value of worker-name-class-name if unspecified by the user

packages/wrangler/src/config/environment.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,15 @@ export type ContainerApp = {
102102
* @optional
103103
* @default "dev"
104104
*/
105-
instance_type?: "dev" | "basic" | "standard";
105+
instance_type?:
106+
| "dev"
107+
| "basic"
108+
| "standard"
109+
| {
110+
vcpu?: number;
111+
memory_mib?: number;
112+
disk_mb?: number;
113+
};
106114

107115
/**
108116
* @deprecated Use top level `containers` fields instead.

packages/wrangler/src/config/validation.ts

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2426,13 +2426,13 @@ function validateContainerApp(
24262426
!containerAppOptional.image
24272427
) {
24282428
diagnostics.errors.push(
2429-
`"containers.image" field must be defined for each container app. This should be the path to your Dockerfile or a image URI pointing to the Cloudflare registry.`
2429+
`"containers.image" field must be defined for each container app. This should be the path to your Dockerfile or an image URI pointing to the Cloudflare registry.`
24302430
);
24312431
}
24322432

24332433
if ("configuration" in containerAppOptional) {
24342434
diagnostics.warnings.push(
2435-
`"containers.configuration" is deprecated. Use top level "containers" fields instead. "configuration.image" should be "image", "configuration.disk" should be set via "instance_type".`
2435+
`"containers.configuration" is deprecated. Use top level "containers" fields instead. "configuration.image" should be "image", limits should be set via "instance_type".`
24362436
);
24372437
if (
24382438
typeof containerAppOptional !== "object" ||
@@ -2485,14 +2485,6 @@ function validateContainerApp(
24852485
"string",
24862486
["full_auto", "full_manual", "none"]
24872487
);
2488-
validateOptionalProperty(
2489-
diagnostics,
2490-
field,
2491-
"instance_type",
2492-
containerAppOptional.instance_type,
2493-
"string",
2494-
["dev", "basic", "standard"]
2495-
);
24962488
validateOptionalProperty(
24972489
diagnostics,
24982490
field,
@@ -2572,6 +2564,56 @@ function validateContainerApp(
25722564
["image", "secrets", "labels", "disk", "vcpu", "memory_mib"]
25732565
);
25742566
}
2567+
2568+
// Instance Type validation: When present, the instance type should be either (1) a string
2569+
// representing a predefined instance type or (2) an object that optionally defines vcpu,
2570+
// memory, and disk.
2571+
//
2572+
// If an instance type is not set, a 'dev' instance type will be used. If a custom instance
2573+
// type doesn't set a value, that value will default to the corresponding value in a 'dev'
2574+
// instance type
2575+
if (typeof containerAppOptional.instance_type === "string") {
2576+
// validate named instance type
2577+
validateOptionalProperty(
2578+
diagnostics,
2579+
field,
2580+
"instance_type",
2581+
containerAppOptional.instance_type,
2582+
"string",
2583+
["dev", "basic", "standard"]
2584+
);
2585+
} else if (
2586+
validateOptionalProperty(
2587+
diagnostics,
2588+
field,
2589+
"instance_type",
2590+
containerAppOptional.instance_type,
2591+
"object"
2592+
) &&
2593+
containerAppOptional.instance_type
2594+
) {
2595+
// validate custom instance type
2596+
const instanceTypeProperties = ["vcpu", "memory_mib", "disk_mb"];
2597+
instanceTypeProperties.forEach((key) => {
2598+
if (
2599+
!isOptionalProperty(
2600+
containerAppOptional.instance_type,
2601+
key,
2602+
"number"
2603+
)
2604+
) {
2605+
diagnostics.errors.push(
2606+
`"containers.instance_type.${key}", when present, should be a number.`
2607+
);
2608+
}
2609+
});
2610+
validateAdditionalProperties(
2611+
diagnostics,
2612+
`${field}.instance_type`,
2613+
Object.keys(containerAppOptional.instance_type),
2614+
instanceTypeProperties
2615+
);
2616+
}
25752617
}
25762618

25772619
if (diagnostics.errors.length > 0) {

0 commit comments

Comments
 (0)