Skip to content

Commit 163304c

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 c70513e commit 163304c

File tree

5 files changed

+245
-21
lines changed

5 files changed

+245
-21
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
@@ -1705,6 +1705,63 @@ describe("cloudchamber apply", () => {
17051705
expect(std.stderr).toMatchInlineSnapshot(`""`);
17061706
});
17071707

1708+
test("can apply a simple application (custom instance type)", async () => {
1709+
setIsTTY(false);
1710+
writeWranglerConfig({
1711+
name: "my-container",
1712+
containers: [
1713+
{
1714+
name: "my-container-app",
1715+
instances: 3,
1716+
class_name: "DurableObjectClass",
1717+
instance_type: {
1718+
vcpu: 1,
1719+
memory_mib: 1024,
1720+
disk_mb: 2000,
1721+
},
1722+
image: "./Dockerfile",
1723+
constraints: {
1724+
tier: 2,
1725+
},
1726+
},
1727+
],
1728+
});
1729+
mockGetApplications([]);
1730+
mockCreateApplication({ id: "abc" });
1731+
await runWrangler("cloudchamber apply");
1732+
expect(std.stdout).toMatchInlineSnapshot(`
1733+
"╭ Deploy a container application deploy changes to your application
1734+
1735+
│ Container application changes
1736+
1737+
├ NEW my-container-app
1738+
1739+
│ [[containers]]
1740+
│ name = \\"my-container-app\\"
1741+
│ instances = 3
1742+
│ scheduling_policy = \\"default\\"
1743+
1744+
│ [containers.constraints]
1745+
│ tier = 2
1746+
1747+
│ [containers.configuration]
1748+
│ image = \\"./Dockerfile\\"
1749+
│ vcpu = 1
1750+
│ memory_mib = 1_024
1751+
1752+
│ [containers.configuration.disk]
1753+
│ size_mb = 2_000
1754+
1755+
1756+
│  SUCCESS  Created application my-container-app (Application ID: abc)
1757+
1758+
╰ Applied changes
1759+
1760+
"
1761+
`);
1762+
expect(std.stderr).toMatchInlineSnapshot(`""`);
1763+
});
1764+
17081765
test("can apply a simple existing application (instance type)", async () => {
17091766
setIsTTY(false);
17101767
writeWranglerConfig({
@@ -1782,6 +1839,96 @@ describe("cloudchamber apply", () => {
17821839
expect(app.configuration?.instance_type).toEqual("standard");
17831840
});
17841841

1842+
test("can apply a simple existing application (custom instance type)", async () => {
1843+
setIsTTY(false);
1844+
writeWranglerConfig({
1845+
name: "my-container",
1846+
containers: [
1847+
{
1848+
name: "my-container-app",
1849+
instances: 4,
1850+
class_name: "DurableObjectClass",
1851+
instance_type: {
1852+
vcpu: 1,
1853+
memory_mib: 1024,
1854+
disk_mb: 6000,
1855+
},
1856+
image: "./Dockerfile",
1857+
constraints: {
1858+
tier: 2,
1859+
},
1860+
},
1861+
],
1862+
});
1863+
mockGetApplications([
1864+
{
1865+
id: "abc",
1866+
name: "my-container-app",
1867+
instances: 3,
1868+
created_at: new Date().toString(),
1869+
version: 1,
1870+
account_id: "1",
1871+
scheduling_policy: SchedulingPolicy.REGIONAL,
1872+
configuration: {
1873+
image: "./Dockerfile",
1874+
disk: {
1875+
size: "2GB",
1876+
size_mb: 2000,
1877+
},
1878+
vcpu: 0.0625,
1879+
memory: "256MB",
1880+
memory_mib: 256,
1881+
},
1882+
constraints: {
1883+
tier: 3,
1884+
},
1885+
},
1886+
]);
1887+
const applicationReqBodyPromise = mockModifyApplication();
1888+
await runWrangler("cloudchamber apply");
1889+
expect(std.stdout).toMatchInlineSnapshot(`
1890+
"╭ Deploy a container application deploy changes to your application
1891+
1892+
│ Container application changes
1893+
1894+
├ EDIT my-container-app
1895+
1896+
│ [[containers]]
1897+
│ - instances = 3
1898+
│ + instances = 4
1899+
│ name = \\"my-container-app\\"
1900+
1901+
│ [containers.configuration]
1902+
│ ...
1903+
│ memory = \\"256MB\\"
1904+
│ - memory_mib = 256
1905+
│ + memory_mib = 1_024
1906+
│ - vcpu = 0.0625
1907+
│ + vcpu = 1
1908+
1909+
│ [containers.configuration.disk]
1910+
│ ...
1911+
│ size = \\"2GB\\"
1912+
│ - size_mb = 2_000
1913+
│ + size_mb = 6_000
1914+
1915+
│ [containers.constraints]
1916+
│ ...
1917+
│ - tier = 3
1918+
│ + tier = 2
1919+
1920+
1921+
│  SUCCESS  Modified application my-container-app
1922+
1923+
╰ Applied changes
1924+
1925+
"
1926+
`);
1927+
expect(std.stderr).toMatchInlineSnapshot(`""`);
1928+
const app = await applicationReqBodyPromise;
1929+
expect(app.configuration?.instance_type).toBeUndefined();
1930+
});
1931+
17851932
test("falls back on dev instance type when instance type is absent", async () => {
17861933
setIsTTY(false);
17871934
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: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,22 +95,36 @@ export type ContainerApp = {
9595
scheduling_policy?: "regional" | "moon" | "default";
9696

9797
/**
98-
* The instance type to be used for the container. This sets preconfigured options for vcpu and memory
98+
* The instance type to be used for the container. This sets preconfigured options for vcpu and memory.
99+
* If the user has the capability to set custom instance types, they can individually configure vcpu,
100+
* memory, and disk.
99101
* @optional
100102
*/
101-
instance_type?: "dev" | "basic" | "standard";
103+
instance_type?:
104+
| "dev"
105+
| "basic"
106+
| "standard"
107+
| {
108+
vcpu?: number;
109+
memory_mib?: number;
110+
disk_mb?: number;
111+
};
102112

103113
/**
104114
* @deprecated Use top level `containers` fields instead.
105115
* `configuration.image` should be `image`
116+
* `configuration.vcpu` should be set via `instance_type`
117+
* `configuration.memory_mib` should be set via `instance_type`
106118
* `configuration.disk` should be set via `instance_type`
107119
* @hidden
108120
*/
109121
configuration?: {
110122
image?: string;
111123
labels?: { name: string; value: string }[];
112124
secrets?: { name: string; type: "env"; secret: string }[];
113-
disk?: { size: string };
125+
vcpu?: number;
126+
memory_mib?: number;
127+
disk?: { size_mb: number };
114128
};
115129

116130
/**

packages/wrangler/src/config/validation.ts

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2436,12 +2436,12 @@ function validateContainerApp(
24362436
!containerAppOptional.image
24372437
) {
24382438
diagnostics.errors.push(
2439-
`"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.`
2439+
`"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.`
24402440
);
24412441
}
24422442
if ("configuration" in containerAppOptional) {
24432443
diagnostics.warnings.push(
2444-
`"containers.configuration" is deprecated. Use top level "containers" fields instead. "configuration.image" should be "image", "configuration.disk" should be set via "instance_type".`
2444+
`"containers.configuration" is deprecated. Use top level "containers" fields instead. "configuration.image" should be "image"; "vcpu", "memory_mib", and "disk" should be set via "instance_type".`
24452445
);
24462446
}
24472447

@@ -2499,14 +2499,6 @@ function validateContainerApp(
24992499
"string",
25002500
["full_auto", "full_manual", "none"]
25012501
);
2502-
validateOptionalProperty(
2503-
diagnostics,
2504-
field,
2505-
"instance_type",
2506-
containerAppOptional.instance_type,
2507-
"string",
2508-
["dev", "basic", "standard"]
2509-
);
25102502
validateOptionalProperty(
25112503
diagnostics,
25122504
field,
@@ -2578,6 +2570,56 @@ function validateContainerApp(
25782570
["image", "secrets", "labels", "disk", "memory", "vcpu", "memory_mib"]
25792571
);
25802572
}
2573+
2574+
// Instance Type validation: When present, the instance type should be either (1) a string
2575+
// representing a predefined instance type or (2) an object that optionally defines vcpu,
2576+
// memory, and disk.
2577+
//
2578+
// If an instance type is not set, a 'dev' instance type will be used. If a custom instance
2579+
// type doesn't set a value, that value will default to the corresponding value in a 'dev'
2580+
// instance type
2581+
if (typeof containerAppOptional.instance_type === "string") {
2582+
// validate named instance type
2583+
validateOptionalProperty(
2584+
diagnostics,
2585+
field,
2586+
"instance_type",
2587+
containerAppOptional.instance_type,
2588+
"string",
2589+
["dev", "basic", "standard"]
2590+
);
2591+
} else if (
2592+
validateOptionalProperty(
2593+
diagnostics,
2594+
field,
2595+
"instance_type",
2596+
containerAppOptional.instance_type,
2597+
"object"
2598+
) &&
2599+
containerAppOptional.instance_type
2600+
) {
2601+
// validate custom instance type
2602+
const instanceTypeProperties = ["vcpu", "memory_mib", "disk_mb"];
2603+
instanceTypeProperties.forEach((key) => {
2604+
if (
2605+
!isOptionalProperty(
2606+
containerAppOptional.instance_type,
2607+
key,
2608+
"number"
2609+
)
2610+
) {
2611+
diagnostics.errors.push(
2612+
`"containers.instance_type.${key}", when present, should be a number.`
2613+
);
2614+
}
2615+
});
2616+
validateAdditionalProperties(
2617+
diagnostics,
2618+
`${field}.instance_type`,
2619+
Object.keys(containerAppOptional.instance_type),
2620+
instanceTypeProperties
2621+
)
2622+
}
25812623
}
25822624

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

0 commit comments

Comments
 (0)