Skip to content

Commit cc63aae

Browse files
authored
[wrangler] fix(containers): add --json flag to wrangler containers info (#14057)
1 parent f92d1fc commit cc63aae

3 files changed

Lines changed: 111 additions & 4 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": minor
3+
---
4+
5+
Add `--json` flag to `wrangler containers info` for consistent JSON output with sibling commands `list` and `instances`

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

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ describe("containers info", () => {
4141
-h, --help Show help [boolean]
4242
--install-skills Install Cloudflare skills for detected AI coding agents before running the command [boolean] [default: false]
4343
--profile Use a specific auth profile [string]
44-
-v, --version Show version number [boolean]"
44+
-v, --version Show version number [boolean]
45+
46+
OPTIONS
47+
--json Return output as JSON [boolean] [default: false]"
4548
`);
4649
});
4750

@@ -58,6 +61,85 @@ describe("containers info", () => {
5861
);
5962
});
6063

64+
it("should output JSON via --json flag in TTY mode", async ({ expect }) => {
65+
setIsTTY(true);
66+
setWranglerConfig({});
67+
msw.use(
68+
http.get(
69+
"*/applications/asdf",
70+
async ({ request }) => {
71+
expect(await request.text()).toEqual("");
72+
return HttpResponse.json(
73+
`{"success": true, "result": ${MOCK_APPLICATION_SINGLE}}`
74+
);
75+
},
76+
{ once: true }
77+
)
78+
);
79+
await runWrangler("containers info asdf --json");
80+
expect(std.err).toMatchInlineSnapshot(`""`);
81+
expect(std.out).toMatchInlineSnapshot(`
82+
"{
83+
"id": "asdf",
84+
"created_at": "2025-02-14T18:03:13.268999936Z",
85+
"account_id": "test-account",
86+
"name": "app-test",
87+
"version": 1,
88+
"configuration": {
89+
"image": "registry.test.cfdata.org/test-app:v1",
90+
"network": {
91+
"mode": "private"
92+
}
93+
},
94+
"scheduling_policy": "regional",
95+
"instances": 2,
96+
"jobs": false,
97+
"constraints": {
98+
"region": "WNAM"
99+
},
100+
"durable_objects": {
101+
"namespace_id": "test-id"
102+
},
103+
"health": {
104+
"instances": {
105+
"healthy": 2,
106+
"failed": 0,
107+
"scheduling": 0,
108+
"starting": 0
109+
}
110+
}
111+
}"
112+
`);
113+
});
114+
115+
it("should throw JsonFriendlyFatalError on unexpected API error with --json", async ({
116+
expect,
117+
}) => {
118+
setIsTTY(true);
119+
setWranglerConfig({});
120+
msw.use(
121+
http.get(
122+
"*/applications/asdf",
123+
async () => {
124+
return HttpResponse.json(
125+
{
126+
success: false,
127+
result: null,
128+
errors: [{ code: 2000, message: "boom" }],
129+
},
130+
{ status: 500 }
131+
);
132+
},
133+
{ once: true }
134+
)
135+
);
136+
await expect(runWrangler("containers info asdf --json")).rejects.toThrow(
137+
/There has been an internal error/
138+
);
139+
expect(() => JSON.parse(std.out)).not.toThrow();
140+
expect(JSON.parse(std.out)).toHaveProperty("error");
141+
});
142+
61143
it("should show a single container when given an ID (json)", async ({
62144
expect,
63145
}) => {
@@ -75,8 +157,8 @@ describe("containers info", () => {
75157
{ once: true }
76158
)
77159
);
78-
expect(std.err).toMatchInlineSnapshot(`""`);
79160
await runWrangler("containers info asdf");
161+
expect(std.err).toMatchInlineSnapshot(`""`);
80162
expect(std.out).toMatchInlineSnapshot(`
81163
"{
82164
"id": "asdf",

packages/wrangler/src/containers/containers.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
} from "@cloudflare/cli-shared-helpers";
66
import { inputPrompt } from "@cloudflare/cli-shared-helpers/interactive";
77
import { ApiError, ApplicationsService } from "@cloudflare/containers-shared";
8-
import { UserError } from "@cloudflare/workers-utils";
8+
import { JsonFriendlyFatalError, UserError } from "@cloudflare/workers-utils";
99
import { isNonInteractiveOrCI } from "@cloudflare/workers-utils";
1010
import YAML from "yaml";
1111
import { fillOpenAPIConfiguration } from "../cloudchamber/common";
@@ -135,18 +135,38 @@ export const containersInfoCommand = createCommand({
135135
owner: "Product: Cloudchamber",
136136
},
137137
behaviour: {
138-
printBanner: () => !isNonInteractiveOrCI(),
138+
printBanner: (args) => !args.json && !isNonInteractiveOrCI(),
139139
},
140140
args: {
141141
ID: {
142142
describe: "ID of the container to view",
143143
type: "string",
144144
demandOption: true,
145145
},
146+
json: {
147+
describe: "Return output as JSON",
148+
type: "boolean",
149+
default: false,
150+
},
146151
},
147152
positionalArgs: ["ID"],
148153
async handler(args, { config }) {
149154
await fillOpenAPIConfiguration(config, containersScope);
155+
if (args.json) {
156+
try {
157+
const application = await ApplicationsService.getApplication(args.ID);
158+
logger.json(application);
159+
return;
160+
} catch (err) {
161+
if (err instanceof UserError) {
162+
throw err;
163+
}
164+
const message = err instanceof Error ? err.message : "Unknown error";
165+
throw new JsonFriendlyFatalError(JSON.stringify({ error: message }), {
166+
telemetryMessage: "containers info json output failed",
167+
});
168+
}
169+
}
150170
await infoCommand(args, config);
151171
},
152172
});

0 commit comments

Comments
 (0)