@intentius/chant-lexicon-azure 0.28.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/describe-resources.d.ts +21 -6
- package/dist/describe-resources.d.ts.map +1 -1
- package/dist/integrity.json +2 -2
- package/dist/manifest.json +1 -1
- package/package.json +2 -2
- package/src/describe-resources.test.ts +34 -10
- package/src/describe-resources.ts +84 -12
- package/src/lifecycle-integration.test.ts +55 -8
|
@@ -8,12 +8,27 @@
|
|
|
8
8
|
* (using props.name from #39's entity-prop pass-through). The environment
|
|
9
9
|
* argument is treated as the Azure resource group name.
|
|
10
10
|
*
|
|
11
|
-
* Resource-not-found is
|
|
12
|
-
* Nested resource types (e.g.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* Resource-not-found is an absence — `state diff --live` then reports it as
|
|
12
|
+
* missing. Nested resource types (e.g.
|
|
13
|
+
* `Microsoft.Storage/storageAccounts/blobServices`) are NOT-OBSERVED
|
|
14
|
+
* (`unsupported-kind`, #1089): `az resource show` doesn't accept a compound
|
|
15
|
+
* type, so chant never asks, and a blob service that already exists must not
|
|
16
|
+
* come back as a proposed `create`. Auth failures and unreachable subscriptions
|
|
17
|
+
* are holes for the same reason.
|
|
15
18
|
*/
|
|
16
|
-
import type {
|
|
19
|
+
import type { ObservationResult, UnobservedReason } from "@intentius/chant/lexicon";
|
|
20
|
+
/**
|
|
21
|
+
* Classify an `az resource show` failure (#1089). Only the CLI's explicit
|
|
22
|
+
* not-found establishes absence; everything else — an expired login, a
|
|
23
|
+
* subscription that can't be resolved, a network failure — proves nothing.
|
|
24
|
+
*/
|
|
25
|
+
export declare function classifyAzFailure(err: unknown): {
|
|
26
|
+
absent: true;
|
|
27
|
+
} | {
|
|
28
|
+
absent: false;
|
|
29
|
+
reason: UnobservedReason;
|
|
30
|
+
detail: string;
|
|
31
|
+
};
|
|
17
32
|
export declare function describeResources(options: {
|
|
18
33
|
environment: string;
|
|
19
34
|
buildOutput: string;
|
|
@@ -22,5 +37,5 @@ export declare function describeResources(options: {
|
|
|
22
37
|
entityType: string;
|
|
23
38
|
props: Record<string, unknown>;
|
|
24
39
|
}>;
|
|
25
|
-
}): Promise<
|
|
40
|
+
}): Promise<ObservationResult>;
|
|
26
41
|
//# sourceMappingURL=describe-resources.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"describe-resources.d.ts","sourceRoot":"","sources":["../src/describe-resources.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"describe-resources.d.ts","sourceRoot":"","sources":["../src/describe-resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAsC,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAmCxH;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG;IAAE,MAAM,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAkC9H;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;CAC/E,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA2E7B"}
|
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "50025ac710a4bd52992e4a6b196a56d2ff14735a095dd7f72cf8bcb389b761ea",
|
|
5
5
|
"meta.json": "051fb4ee43f7de48a36f1abc59c8ff5207c3b75aa28f13db6440b9f5b0337605",
|
|
6
6
|
"types/index.d.ts": "617a6de433bd92a126170a7f5ae903d6d7aa83fbb0b57169d3450b5a13e72e0d",
|
|
7
7
|
"rules/hardcoded-location.ts": "a9b1d1cec93f2ca9c5206641f53fc9621cefcfd2c00b3ab89c194b30a75fa949",
|
|
@@ -33,5 +33,5 @@
|
|
|
33
33
|
"skills/chant-azure-patterns.md": "1a3bacfac612826b77332d2692a59195638db9f1b5e8ea2eda7579621d25f603",
|
|
34
34
|
"skills/chant-azure-aks.md": "2d4e0098c1a22b54ffadd410564d9df0f3a04cb4eb7c6261f20ae33106e7aca8"
|
|
35
35
|
},
|
|
36
|
-
"composite": "
|
|
36
|
+
"composite": "923cf13c8ea5dfb598f2aecae0e957d42e977bc511747871532644ab9230d8ed"
|
|
37
37
|
}
|
package/dist/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-azure",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"description": "Azure lexicon for chant — declarative IaC in TypeScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"typescript": "^5.9.3"
|
|
75
75
|
},
|
|
76
76
|
"peerDependencies": {
|
|
77
|
-
"@intentius/chant": "^0.
|
|
77
|
+
"@intentius/chant": "^0.30.0",
|
|
78
78
|
"typescript": "^5.9.3"
|
|
79
79
|
}
|
|
80
80
|
}
|
|
@@ -50,7 +50,7 @@ describe("azure describeResources", () => {
|
|
|
50
50
|
expect(receivedCmd).toContain("--name mydata");
|
|
51
51
|
expect(receivedCmd).toContain("--resource-type Microsoft.Storage/storageAccounts");
|
|
52
52
|
|
|
53
|
-
expect(result["dataAccount"]).toMatchObject({
|
|
53
|
+
expect(result.resources["dataAccount"]).toMatchObject({
|
|
54
54
|
type: "Microsoft.Storage/storageAccounts",
|
|
55
55
|
physicalId: "/subscriptions/sub/resourceGroups/prod-rg/providers/Microsoft.Storage/storageAccounts/mydata",
|
|
56
56
|
status: "Succeeded",
|
|
@@ -70,10 +70,10 @@ describe("azure describeResources", () => {
|
|
|
70
70
|
|
|
71
71
|
const result = await describeResources({ environment: "prod-rg", buildOutput: "", entityNames: ["vnet"], entities });
|
|
72
72
|
|
|
73
|
-
expect(result["vnet"].status).toBe("PRESENT");
|
|
73
|
+
expect(result.resources["vnet"].status).toBe("PRESENT");
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
test("az failure (resource not found) leaves entity out", async () => {
|
|
76
|
+
test("az failure (resource not found) leaves entity out — a confirmed absence", async () => {
|
|
77
77
|
execMock.mockImplementation(() => { throw new Error("ResourceNotFound: ..."); });
|
|
78
78
|
|
|
79
79
|
const entities = makeEntities([
|
|
@@ -82,10 +82,28 @@ describe("azure describeResources", () => {
|
|
|
82
82
|
|
|
83
83
|
const result = await describeResources({ environment: "prod-rg", buildOutput: "", entityNames: ["missing"], entities });
|
|
84
84
|
|
|
85
|
-
expect(result).toEqual({});
|
|
85
|
+
expect(result.resources).toEqual({});
|
|
86
|
+
expect(result.unobserved ?? {}).toEqual({});
|
|
86
87
|
});
|
|
87
88
|
|
|
88
|
-
test("
|
|
89
|
+
test("an expired az login is unobserved, not absent (#1089)", async () => {
|
|
90
|
+
execMock.mockImplementation(() => {
|
|
91
|
+
throw Object.assign(new Error("az failed"), {
|
|
92
|
+
stderr: "Please run 'az login' to setup account.",
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const entities = makeEntities([
|
|
97
|
+
{ name: "acct", entityType: "Microsoft.Storage/storageAccounts", props: { name: "acct" } },
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
const result = await describeResources({ environment: "prod-rg", buildOutput: "", entityNames: ["acct"], entities });
|
|
101
|
+
|
|
102
|
+
expect(result.resources).toEqual({});
|
|
103
|
+
expect(result.unobserved?.acct?.reason).toBe("no-credentials");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("nested-type entities are unobserved, not absent (#1089)", async () => {
|
|
89
107
|
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
90
108
|
const entities = makeEntities([
|
|
91
109
|
{ name: "nested", entityType: "Microsoft.Storage/storageAccounts/blobServices", props: { name: "x" } },
|
|
@@ -93,31 +111,37 @@ describe("azure describeResources", () => {
|
|
|
93
111
|
|
|
94
112
|
const result = await describeResources({ environment: "prod-rg", buildOutput: "", entityNames: ["nested"], entities });
|
|
95
113
|
|
|
96
|
-
expect(result).toEqual({});
|
|
114
|
+
expect(result.resources).toEqual({});
|
|
115
|
+
expect(result.unobserved?.nested).toMatchObject({
|
|
116
|
+
type: "Microsoft.Storage/storageAccounts/blobServices",
|
|
117
|
+
reason: "unsupported-kind",
|
|
118
|
+
});
|
|
97
119
|
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("nested-type"));
|
|
98
120
|
expect(execMock).not.toHaveBeenCalled();
|
|
99
121
|
warnSpy.mockRestore();
|
|
100
122
|
});
|
|
101
123
|
|
|
102
|
-
test("non-Azure entity types are
|
|
124
|
+
test("non-Azure entity types are unobserved — no ARM type to query", async () => {
|
|
103
125
|
const entities = makeEntities([
|
|
104
126
|
{ name: "x", entityType: "AWS::S3::Bucket", props: { name: "x" } },
|
|
105
127
|
]);
|
|
106
128
|
|
|
107
129
|
const result = await describeResources({ environment: "prod-rg", buildOutput: "", entityNames: ["x"], entities });
|
|
108
130
|
|
|
109
|
-
expect(result).toEqual({});
|
|
131
|
+
expect(result.resources).toEqual({});
|
|
132
|
+
expect(result.unobserved?.x?.reason).toBe("unsupported-kind");
|
|
110
133
|
expect(execMock).not.toHaveBeenCalled();
|
|
111
134
|
});
|
|
112
135
|
|
|
113
|
-
test("entity without name is
|
|
136
|
+
test("entity without name is unobserved — nothing was queried", async () => {
|
|
114
137
|
const entities = makeEntities([
|
|
115
138
|
{ name: "broken", entityType: "Microsoft.Storage/storageAccounts", props: {} },
|
|
116
139
|
]);
|
|
117
140
|
|
|
118
141
|
const result = await describeResources({ environment: "prod-rg", buildOutput: "", entityNames: ["broken"], entities });
|
|
119
142
|
|
|
120
|
-
expect(result).toEqual({});
|
|
143
|
+
expect(result.resources).toEqual({});
|
|
144
|
+
expect(result.unobserved?.broken?.reason).toBe("read-failed");
|
|
121
145
|
expect(execMock).not.toHaveBeenCalled();
|
|
122
146
|
});
|
|
123
147
|
});
|
|
@@ -8,15 +8,19 @@
|
|
|
8
8
|
* (using props.name from #39's entity-prop pass-through). The environment
|
|
9
9
|
* argument is treated as the Azure resource group name.
|
|
10
10
|
*
|
|
11
|
-
* Resource-not-found is
|
|
12
|
-
* Nested resource types (e.g.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* Resource-not-found is an absence — `state diff --live` then reports it as
|
|
12
|
+
* missing. Nested resource types (e.g.
|
|
13
|
+
* `Microsoft.Storage/storageAccounts/blobServices`) are NOT-OBSERVED
|
|
14
|
+
* (`unsupported-kind`, #1089): `az resource show` doesn't accept a compound
|
|
15
|
+
* type, so chant never asks, and a blob service that already exists must not
|
|
16
|
+
* come back as a proposed `create`. Auth failures and unreachable subscriptions
|
|
17
|
+
* are holes for the same reason.
|
|
15
18
|
*/
|
|
16
19
|
|
|
17
20
|
import { exec } from "node:child_process";
|
|
18
21
|
import { promisify } from "node:util";
|
|
19
|
-
import type { ResourceMetadata } from "@intentius/chant/lexicon";
|
|
22
|
+
import type { ObservationResult, ResourceMetadata, UnobservedEntity, UnobservedReason } from "@intentius/chant/lexicon";
|
|
23
|
+
import { observation } from "@intentius/chant/observation";
|
|
20
24
|
|
|
21
25
|
const execAsync = promisify(exec);
|
|
22
26
|
|
|
@@ -50,25 +54,88 @@ function isTopLevelType(entityType: string): boolean {
|
|
|
50
54
|
return slashCount === 1;
|
|
51
55
|
}
|
|
52
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Classify an `az resource show` failure (#1089). Only the CLI's explicit
|
|
59
|
+
* not-found establishes absence; everything else — an expired login, a
|
|
60
|
+
* subscription that can't be resolved, a network failure — proves nothing.
|
|
61
|
+
*/
|
|
62
|
+
export function classifyAzFailure(err: unknown): { absent: true } | { absent: false; reason: UnobservedReason; detail: string } {
|
|
63
|
+
const raw =
|
|
64
|
+
typeof err === "object" && err !== null && typeof (err as { stderr?: unknown }).stderr === "string"
|
|
65
|
+
? ((err as { stderr: string }).stderr.trim() || String((err as { message?: unknown }).message ?? err))
|
|
66
|
+
: String((err as { message?: unknown } | undefined)?.message ?? err);
|
|
67
|
+
const lower = raw.toLowerCase();
|
|
68
|
+
const detail = (raw.split("\n").find((l) => l.trim().length > 0) ?? raw).trim().slice(0, 200);
|
|
69
|
+
|
|
70
|
+
if (
|
|
71
|
+
lower.includes("resourcenotfound") ||
|
|
72
|
+
lower.includes("was not found") ||
|
|
73
|
+
lower.includes("could not be found") ||
|
|
74
|
+
lower.includes("does not exist")
|
|
75
|
+
) {
|
|
76
|
+
return { absent: true };
|
|
77
|
+
}
|
|
78
|
+
if (
|
|
79
|
+
lower.includes("please run 'az login'") ||
|
|
80
|
+
lower.includes("az login") ||
|
|
81
|
+
lower.includes("authenticationfailed") ||
|
|
82
|
+
lower.includes("expired") ||
|
|
83
|
+
lower.includes("authorizationfailed") ||
|
|
84
|
+
lower.includes("forbidden")
|
|
85
|
+
) {
|
|
86
|
+
return { absent: false, reason: "no-credentials", detail };
|
|
87
|
+
}
|
|
88
|
+
if (
|
|
89
|
+
lower.includes("subscriptionnotfound") ||
|
|
90
|
+
lower.includes("no subscription") ||
|
|
91
|
+
lower.includes("please run 'az account set'")
|
|
92
|
+
) {
|
|
93
|
+
return { absent: false, reason: "no-binding", detail };
|
|
94
|
+
}
|
|
95
|
+
return { absent: false, reason: "read-failed", detail };
|
|
96
|
+
}
|
|
97
|
+
|
|
53
98
|
export async function describeResources(options: {
|
|
54
99
|
environment: string;
|
|
55
100
|
buildOutput: string;
|
|
56
101
|
entityNames: string[];
|
|
57
102
|
entities: Map<string, { entityType: string; props: Record<string, unknown> }>;
|
|
58
|
-
}): Promise<
|
|
103
|
+
}): Promise<ObservationResult> {
|
|
59
104
|
const result: Record<string, ResourceMetadata> = {};
|
|
105
|
+
const unobserved: Record<string, UnobservedEntity> = {};
|
|
60
106
|
const skippedNested: string[] = [];
|
|
61
107
|
|
|
62
108
|
for (const [entityName, { entityType, props }] of options.entities) {
|
|
63
|
-
if (!entityType.startsWith("Microsoft."))
|
|
109
|
+
if (!entityType.startsWith("Microsoft.")) {
|
|
110
|
+
// Not an ARM resource type, so `az resource show` has nothing to ask for.
|
|
111
|
+
// Unobserved rather than skipped: a silent skip reads as absence (#1089).
|
|
112
|
+
unobserved[entityName] = {
|
|
113
|
+
type: entityType,
|
|
114
|
+
reason: "unsupported-kind",
|
|
115
|
+
detail: "not an ARM resource type (expected Microsoft.<provider>/<kind>)",
|
|
116
|
+
};
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
64
119
|
|
|
65
120
|
if (!isTopLevelType(entityType)) {
|
|
66
121
|
skippedNested.push(entityName);
|
|
122
|
+
unobserved[entityName] = {
|
|
123
|
+
type: entityType,
|
|
124
|
+
reason: "unsupported-kind",
|
|
125
|
+
detail: "az resource show does not accept a nested ARM type; chant never queried this resource",
|
|
126
|
+
};
|
|
67
127
|
continue;
|
|
68
128
|
}
|
|
69
129
|
|
|
70
130
|
const name = props.name as string | undefined;
|
|
71
|
-
if (!name)
|
|
131
|
+
if (!name) {
|
|
132
|
+
unobserved[entityName] = {
|
|
133
|
+
type: entityType,
|
|
134
|
+
reason: "read-failed",
|
|
135
|
+
detail: "declared entity has no name to query by",
|
|
136
|
+
};
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
72
139
|
|
|
73
140
|
const cmd = [
|
|
74
141
|
"az", "resource", "show",
|
|
@@ -90,17 +157,22 @@ export async function describeResources(options: {
|
|
|
90
157
|
tags: obj.tags,
|
|
91
158
|
}),
|
|
92
159
|
};
|
|
93
|
-
} catch {
|
|
94
|
-
//
|
|
160
|
+
} catch (err) {
|
|
161
|
+
// Not-found leaves the entity out (absence). Auth/binding/other failures
|
|
162
|
+
// are recorded as holes so they can't become creates (#1089).
|
|
163
|
+
const outcome = classifyAzFailure(err);
|
|
164
|
+
if (!outcome.absent) {
|
|
165
|
+
unobserved[entityName] = { type: entityType, reason: outcome.reason, detail: outcome.detail };
|
|
166
|
+
}
|
|
95
167
|
}
|
|
96
168
|
}
|
|
97
169
|
|
|
98
170
|
if (skippedNested.length > 0) {
|
|
99
171
|
// eslint-disable-next-line no-console
|
|
100
172
|
console.warn(
|
|
101
|
-
`[azure]
|
|
173
|
+
`[azure] ${skippedNested.length} nested-type entity(ies) reported as unobserved (not absent) — az resource show doesn't accept compound types: ${skippedNested.slice(0, 5).join(", ")}${skippedNested.length > 5 ? ", ..." : ""}`,
|
|
102
174
|
);
|
|
103
175
|
}
|
|
104
176
|
|
|
105
|
-
return result;
|
|
177
|
+
return observation(result, unobserved);
|
|
106
178
|
}
|
|
@@ -31,6 +31,8 @@ vi.mock("node:child_process", async () => {
|
|
|
31
31
|
const { azurePlugin } = await import("./plugin");
|
|
32
32
|
const { liveImportFromPlugins } = await import("@intentius/chant/cli/commands/import");
|
|
33
33
|
const { buildChangeSet } = await import("@intentius/chant/lifecycle/change-set");
|
|
34
|
+
const { normalizeObservation } = await import("@intentius/chant/observation");
|
|
35
|
+
const { describeObservationConformance } = await import("@intentius/chant-test-utils");
|
|
34
36
|
|
|
35
37
|
const liveTemplate = {
|
|
36
38
|
$schema:
|
|
@@ -85,14 +87,16 @@ describe("azure lifecycle integration (#163)", () => {
|
|
|
85
87
|
: new Error("unexpected"),
|
|
86
88
|
);
|
|
87
89
|
|
|
88
|
-
const observedNow =
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
const { resources: observedNow } = normalizeObservation(
|
|
91
|
+
await azurePlugin.describeResources!({
|
|
92
|
+
environment: "prod",
|
|
93
|
+
buildOutput: "",
|
|
94
|
+
entityNames: ["myStore"],
|
|
95
|
+
entities: new Map([
|
|
96
|
+
["myStore", { entityType: "Microsoft.Storage/storageAccounts", props: { name: "mystore" } }],
|
|
97
|
+
]),
|
|
98
|
+
}),
|
|
99
|
+
);
|
|
96
100
|
expect(observedNow.myStore?.type).toBe("Microsoft.Storage/storageAccounts");
|
|
97
101
|
|
|
98
102
|
const cs = buildChangeSet("prod", {
|
|
@@ -112,3 +116,46 @@ describe("azure lifecycle integration (#163)", () => {
|
|
|
112
116
|
expect(cs2.entries.find((e) => e.name === "myStore")!.action).toBe("noop");
|
|
113
117
|
});
|
|
114
118
|
});
|
|
119
|
+
|
|
120
|
+
// The shared conformance suite (#1089).
|
|
121
|
+
describeObservationConformance({
|
|
122
|
+
lexicon: "azure",
|
|
123
|
+
scenarios: [
|
|
124
|
+
{
|
|
125
|
+
name: "a nested ARM type az resource show cannot query",
|
|
126
|
+
declared: ["blobSvc", "gone"],
|
|
127
|
+
expectUnobserved: ["blobSvc"],
|
|
128
|
+
expectAbsent: ["gone"],
|
|
129
|
+
run: () => {
|
|
130
|
+
execMock.mockImplementation(() => new Error("ResourceNotFound: gone"));
|
|
131
|
+
return azurePlugin.describeResources!({
|
|
132
|
+
environment: "prod",
|
|
133
|
+
buildOutput: "",
|
|
134
|
+
entityNames: ["blobSvc", "gone"],
|
|
135
|
+
entities: new Map([
|
|
136
|
+
["blobSvc", { entityType: "Microsoft.Storage/storageAccounts/blobServices", props: { name: "blob" } }],
|
|
137
|
+
["gone", { entityType: "Microsoft.Storage/storageAccounts", props: { name: "gone" } }],
|
|
138
|
+
]),
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: "an expired login",
|
|
144
|
+
declared: ["myStore"],
|
|
145
|
+
expectUnobserved: ["myStore"],
|
|
146
|
+
run: () => {
|
|
147
|
+
execMock.mockImplementation(() =>
|
|
148
|
+
Object.assign(new Error("az failed"), { stderr: "Please run 'az login' to setup account." }),
|
|
149
|
+
);
|
|
150
|
+
return azurePlugin.describeResources!({
|
|
151
|
+
environment: "prod",
|
|
152
|
+
buildOutput: "",
|
|
153
|
+
entityNames: ["myStore"],
|
|
154
|
+
entities: new Map([
|
|
155
|
+
["myStore", { entityType: "Microsoft.Storage/storageAccounts", props: { name: "mystore" } }],
|
|
156
|
+
]),
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
});
|