Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/clean-colts-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": patch
---

Add more thorough validation to containers configuration
22 changes: 11 additions & 11 deletions packages/wrangler/src/__tests__/cloudchamber/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ describe("wrangler deploy with containers", () => {
containers: [
{
name: "my-container",
instances: 10,
max_instances: 10,
class_name: "ExampleDurableObject",
image: "./Dockerfile",
},
Expand Down Expand Up @@ -147,7 +147,7 @@ describe("wrangler deploy with containers", () => {
containers: [
{
name: "my-container",
instances: 10,
max_instances: 10,
class_name: "ExampleDurableObject",
image: "./Dockerfile",
},
Expand All @@ -161,7 +161,7 @@ describe("wrangler deploy with containers", () => {

mockCreateApplication({
name: "my-container",
instances: 10,
max_instances: 10,
durable_objects: { namespace_id: "1" },
configuration: {
image:
Expand Down Expand Up @@ -206,7 +206,7 @@ describe("wrangler deploy with containers", () => {
{
image: "docker.io/hello:world",
name: "my-container",
instances: 10,
max_instances: 10,
class_name: "ExampleDurableObject",
},
],
Expand All @@ -217,7 +217,7 @@ describe("wrangler deploy with containers", () => {

mockCreateApplication({
name: "my-container",
instances: 10,
max_instances: 10,
durable_objects: { namespace_id: "1" },
scheduling_policy: SchedulingPolicy.DEFAULT,
});
Expand Down Expand Up @@ -287,7 +287,7 @@ describe("wrangler deploy with containers", () => {
containers: [
{
name: "my-container",
instances: 10,
max_instances: 10,
class_name: "ExampleDurableObject",
image: "../Dockerfile",
},
Expand All @@ -311,7 +311,7 @@ describe("wrangler deploy with containers", () => {

mockCreateApplication({
name: "my-container",
instances: 10,
max_instances: 10,
durable_objects: { namespace_id: "1" },
configuration: {
image:
Expand Down Expand Up @@ -391,7 +391,7 @@ describe("wrangler deploy with containers", () => {
containers: [
{
name: "my-container",
instances: 10,
max_instances: 10,
class_name: "ExampleDurableObject",
image: "../Dockerfile",
},
Expand All @@ -408,7 +408,7 @@ describe("wrangler deploy with containers", () => {

mockCreateApplication({
name: "my-container",
instances: 10,
max_instances: 10,
durable_objects: { namespace_id: "1" },
configuration: {
image:
Expand Down Expand Up @@ -453,7 +453,7 @@ describe("wrangler deploy with containers", () => {
{
image: "docker.io/hello:world",
name: "my-container",
instances: 10,
max_instances: 10,
class_name: "ExampleDurableObject",
},
],
Expand Down Expand Up @@ -515,7 +515,7 @@ describe("wrangler deploy with containers dry run", () => {
{
image: "./Dockerfile",
name: "my-container",
instances: 10,
max_instances: 10,
class_name: "ExampleDurableObject",
},
],
Expand Down
102 changes: 102 additions & 0 deletions packages/wrangler/src/__tests__/config/configuration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2421,6 +2421,108 @@ describe("normalizeAndValidateConfig()", () => {
);
}
});

it("should error for invalid container app fields", () => {
const { diagnostics } = normalizeAndValidateConfig(
{
name: "test-worker",
containers: [
{
image: "something",
class_name: "test-class",
rollout_kind: "invalid",
instance_type: "invalid",
max_instances: "invalid",
image_build_context: 123,
image_vars: "invalid",
scheduling_policy: "invalid",
unknown_field: "value",
},
],
} as unknown as RawConfig,
undefined,
undefined,
{ env: undefined }
);

expect(diagnostics.renderWarnings()).toMatchInlineSnapshot(`
"Processing wrangler configuration:
- Unexpected fields found in containers field: \\"unknown_field\\""
`);
expect(diagnostics.renderErrors()).toMatchInlineSnapshot(`
"Processing wrangler configuration:
- 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_build_context\\" to be of type string but got 123.
- 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\\"."
`);
});

it("should warn for deprecated container fields", () => {
const { diagnostics } = normalizeAndValidateConfig(
{
name: "test-worker",
containers: [
{
class_name: "test-class",
instances: 10,
configuration: {
image: "config-image",
},
durable_objects: {
namespace_id: "test-namespace",
},
},
],
} as unknown as RawConfig,
undefined,
undefined,
{ env: undefined }
);

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.instances\\" is deprecated. Use \\"containers.max_instances\\" instead.
- \\"containers.durable_objects\\" is deprecated. Use the \\"class_name\\" field instead."
`);
});

it("should error for invalid containers.configuration fields", () => {
const { diagnostics } = normalizeAndValidateConfig(
{
name: "test-worker",
containers: [
{
class_name: "test-class",
configuration: {
image: "config-image",
secrets: [],
labels: [],
disk: { size: "2GB" },
memory: "256MB",
vcpu: 0.5,
memory_mib: 256,
invalid_field: "should not be here",
another_invalid: 123,
},
},
],
} as unknown as RawConfig,
undefined,
undefined,
{ env: undefined }
);

console.dir(diagnostics.warnings);
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\\".
- Unexpected fields found in containers.configuration field: \\"memory\\",\\"invalid_field\\",\\"another_invalid\\""
`);
});
});

describe("[kv_namespaces]", () => {
Expand Down
118 changes: 97 additions & 21 deletions packages/wrangler/src/config/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2421,7 +2421,6 @@ function validateContainerApp(
name += config === undefined ? "" : `-${envName}`;
containerAppOptional.name = name.toLowerCase().replace(/ /g, "-");
}

if (
!containerAppOptional.configuration?.image &&
!containerAppOptional.image
Expand All @@ -2430,6 +2429,11 @@ function validateContainerApp(
`"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.`
);
}
if ("configuration" in containerAppOptional) {
diagnostics.warnings.push(
`"containers.configuration" is deprecated. Use top level "containers" fields instead. "configuration.image" should be "image", "configuration.disk" should be set via "instance_type".`
);
}

// Validate that we have an image configuration for this container app.
// For legacy reasons we have to check both at containerAppOptional.image and
Expand Down Expand Up @@ -2469,19 +2473,6 @@ function validateContainerApp(
`"containers.rollout_step_percentage" field should be a number between 25 and 100, but got ${containerAppOptional.rollout_step_percentage}`
);
}

if (
!isOptionalProperty(containerAppOptional, "rollout_kind", "string") &&
"rollout_kind" in containerAppOptional &&
!["full_auto", "full_manual", "none"].includes(
containerAppOptional.rollout_kind
)
) {
diagnostics.errors.push(
`"containers.rollout_kind" field should be either 'full_auto', 'full_manual' or 'none', but got ${containerAppOptional.rollout_kind}`
);
}

// Leaving for legacy reasons
// TODO: When cleaning up container.configuration usage in other places clean this up
// as well.
Expand All @@ -2490,14 +2481,99 @@ function validateContainerApp(
`"containers.configuration" is defined as an array, it should be an object`
);
}
if ("instance_type" in containerAppOptional) {
validateOptionalProperty(
diagnostics,
field,
validateOptionalProperty(
diagnostics,
field,
"rollout_kind",
containerAppOptional.rollout_kind,
"string",
["full_auto", "full_manual", "none"]
);
validateOptionalProperty(
diagnostics,
field,
"instance_type",
containerAppOptional.instance_type,
"string",
["dev", "basic", "standard"]
);
validateOptionalProperty(
diagnostics,
field,
"max_instances",
containerAppOptional.max_instances,
"number"
);
if (
containerAppOptional.max_instances !== undefined &&
containerAppOptional.max_instances < 0
) {
diagnostics.errors.push(
`"containers.max_instances" field should be a positive number, but got ${containerAppOptional.max_instances}`
);
}
validateOptionalProperty(
diagnostics,
field,
"image_build_context",
containerAppOptional.image_build_context,
"string"
);
validateOptionalProperty(
diagnostics,
field,
"image_vars",
containerAppOptional.image_vars,
"object"
);
validateOptionalProperty(
diagnostics,
field,
"scheduling_policy",
containerAppOptional.scheduling_policy,
"string",
["regional", "moon", "default"]
);

// Add deprecation warnings for legacy fields
if ("instances" in containerAppOptional) {
diagnostics.warnings.push(
`"containers.instances" is deprecated. Use "containers.max_instances" instead.`
);
}
if ("durable_objects" in containerAppOptional) {
diagnostics.warnings.push(
`"containers.durable_objects" is deprecated. Use the "class_name" field instead.`
);
}

validateAdditionalProperties(
diagnostics,
field,
Object.keys(containerAppOptional),
[
"name",
"instances",
"max_instances",
"image",
"image_build_context",
"image_vars",
"class_name",
"scheduling_policy",
"instance_type",
containerAppOptional.instance_type,
"string",
["dev", "basic", "standard"]
"configuration",
"constraints",
"rollout_step_percentage",
"rollout_kind",
"durable_objects",
]
);
if ("configuration" in containerAppOptional) {
validateAdditionalProperties(
diagnostics,
`${field}.configuration`,
Object.keys(containerAppOptional.configuration),
["image", "secrets", "labels", "disk", "vcpu", "memory_mib"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think vcpu and memory_mib are included in configuration?

configuration?: {
image?: string;
labels?: { name: string; value: string }[];
secrets?: { name: string; type: "env"; secret: string }[];
disk?: { size: string };
};

Users shouldn't be able to set these directly in the Wrangler config anyway, they are set by the instance type.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these fields are being used right now for customers that need custom instance types, see #9962 for better handling for custom instance types

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do they need to be added to the type definition then?

Copy link
Contributor Author

@emily-shen emily-shen Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd strongly prefer to have them in the type definition so that we're not trying to read values that typescript doesn't expect to be there. they're hidden from the wrangler config schema, so we don't need to worry about showing this to the general user.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, they should be in the type definition

);
}
}
Expand Down
Loading