@suss/contract-terraform 0.9.0 → 0.10.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/README.md +1 -1
- package/dist/index.cjs +16 -3
- package/dist/index.d.cts +26 -1
- package/dist/index.d.ts +26 -1
- package/dist/index.js +16 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -83,7 +83,7 @@ An entry with `kind: "metric"` says which attribute is the name and how the depl
|
|
|
83
83
|
|
|
84
84
|
The selector is a small query language rather than a name, so `parseFilterQuery` reads it: comparisons joined by `AND` and `OR`, with parentheses, `NOT`, quoted or bare values, and terms written next to each other for AND. A key may quote one of its own segments, `metric.label."response_code_class"`, and that comes back as one key with the quotes off. A call may stand where a comparison would, which is how Cloud Monitoring writes an SLO burn-rate condition, and `filterCalls` gives those back. The parser is exported, so a pack that has to read the same string for something else does not write a second one. A selector it cannot read leaves the reading with no metric on it, which pairs with nothing.
|
|
85
85
|
|
|
86
|
-
Both kinds also say what the resource's own words mean in suss's terms, so the checker never learns a provider's vocabulary. A pack writes `values: { attribute: "shape.value_type", means: { SPREAD: "
|
|
86
|
+
Both kinds also say what the resource's own words mean in suss's terms, so the checker never learns a provider's vocabulary. A pack writes `values: { attribute: "shape.value_type", means: { SPREAD: "histogram", SCALAR: "number" } }`, the reader takes the value at that attribute, dotted paths stepping into nested blocks, looks it up in the table, and writes the answer to `metricContract` on the summary. `accumulates` works the same way. A value the table does not list says nothing, the same as an attribute the configuration never set.
|
|
87
87
|
|
|
88
88
|
A reading translates two things the same way. `comparesTo: { attribute: "limit", whenSet: "number" }` says the reading compares the series against a single number when it states a limit at all, since a threshold is a number by being written down. `reducesTo` maps an aligner or reducer to what it leaves behind. Both land on the summary under `metricReading`, along with the setting and the values that would reduce the series, so a finding can name the fix without the checker knowing which provider it came from.
|
|
89
89
|
|
package/dist/index.cjs
CHANGED
|
@@ -394,7 +394,7 @@ function summariesForFiles(files, options) {
|
|
|
394
394
|
for (const [resourceType, label, body] of file.resources) {
|
|
395
395
|
for (const pack of options.packs) {
|
|
396
396
|
const pattern = patternFor(pack, resourceType, file.constraints);
|
|
397
|
-
if (pattern === void 0) {
|
|
397
|
+
if (pattern === void 0 || !entryApplies(pattern, body)) {
|
|
398
398
|
continue;
|
|
399
399
|
}
|
|
400
400
|
summaries.push(
|
|
@@ -469,6 +469,18 @@ function patternFor(pack, resourceType, constraints) {
|
|
|
469
469
|
}
|
|
470
470
|
});
|
|
471
471
|
}
|
|
472
|
+
function entryApplies(pattern, body) {
|
|
473
|
+
const gate = pattern.appliesWhen;
|
|
474
|
+
if (gate === void 0) {
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
const value = body[gate.attribute];
|
|
478
|
+
if (value === void 0) {
|
|
479
|
+
return gate.whenUnset === "read";
|
|
480
|
+
}
|
|
481
|
+
const text = stringOf(value);
|
|
482
|
+
return text !== null && gate.equals.includes(text);
|
|
483
|
+
}
|
|
472
484
|
var READERS = {
|
|
473
485
|
storage: (site, boundary) => {
|
|
474
486
|
const types = fieldTypes(site.body, boundary);
|
|
@@ -488,7 +500,8 @@ function summariesFor(opts) {
|
|
|
488
500
|
function storageSummary(opts) {
|
|
489
501
|
const { boundary, label, shape, types } = opts;
|
|
490
502
|
const address = `${opts.resourceType}.${label}`;
|
|
491
|
-
const
|
|
503
|
+
const declaresContainer = boundary.declares !== "store";
|
|
504
|
+
const physicalTable = !declaresContainer || boundary.nameAttribute === void 0 ? null : namePattern(opts.body[boundary.nameAttribute], opts.scope);
|
|
492
505
|
return {
|
|
493
506
|
kind: "library",
|
|
494
507
|
location: {
|
|
@@ -504,7 +517,7 @@ function storageSummary(opts) {
|
|
|
504
517
|
storageSystem: boundary.storageSystem,
|
|
505
518
|
...boundary.transport !== void 0 ? { transport: boundary.transport } : {},
|
|
506
519
|
scope: "default",
|
|
507
|
-
container: label,
|
|
520
|
+
container: declaresContainer ? label : null,
|
|
508
521
|
accessPath: shape.accessPath
|
|
509
522
|
})
|
|
510
523
|
},
|
package/dist/index.d.cts
CHANGED
|
@@ -20,6 +20,17 @@ interface StorageResource {
|
|
|
20
20
|
storageSystem: string;
|
|
21
21
|
/** How a caller reaches it, when that is not the store's own name. */
|
|
22
22
|
transport?: string;
|
|
23
|
+
/**
|
|
24
|
+
* What the resource declares. The default, `"container"`, means the
|
|
25
|
+
* resource is the thing code addresses: code passes a bucket's name
|
|
26
|
+
* to `bucket()`, so the declared name and the accessed name meet.
|
|
27
|
+
* `"store"` means the resource only says the store exists. Code
|
|
28
|
+
* splits a Redis cluster into key namespaces, no attribute of the
|
|
29
|
+
* cluster lists them, so the summary gets no container name and
|
|
30
|
+
* claims no access. Any match on the cluster's own name would be a
|
|
31
|
+
* coincidence between a deployment name and a key prefix.
|
|
32
|
+
*/
|
|
33
|
+
declares?: "container" | "store";
|
|
23
34
|
/** The attribute that says what the resource is called once deployed. */
|
|
24
35
|
nameAttribute?: string;
|
|
25
36
|
/** Whether the fields it declares are every field an item has. */
|
|
@@ -80,7 +91,7 @@ interface MetricResource {
|
|
|
80
91
|
* metric spells, so it is the identity the two sides share.
|
|
81
92
|
*/
|
|
82
93
|
metricTypeTemplate: string;
|
|
83
|
-
/** Which attribute says whether one measurement is a number or a
|
|
94
|
+
/** Which attribute says whether one measurement is a number or a histogram. */
|
|
84
95
|
values?: AttributeMeaning<MetricValueShape>;
|
|
85
96
|
/** Which attribute says what a measurement covers. */
|
|
86
97
|
accumulates?: AttributeMeaning<MetricAccumulation>;
|
|
@@ -116,6 +127,20 @@ type TerraformResource = StorageResource | MessageBusResource | MetricResource |
|
|
|
116
127
|
interface TerraformResourcePattern {
|
|
117
128
|
/** The resource type, spelled the way the provider spells it. */
|
|
118
129
|
resource: string;
|
|
130
|
+
/**
|
|
131
|
+
* An attribute that decides whether the entry describes the resource
|
|
132
|
+
* at all. `aws_elasticache_cluster` deploys whichever engine its
|
|
133
|
+
* `engine` attribute picks, and only some engines are the store the
|
|
134
|
+
* entry describes. A value outside `equals`, or one built at deploy
|
|
135
|
+
* time, means the resource is not read, rather than read as
|
|
136
|
+
* something it may not be. `whenUnset` says what an absent attribute
|
|
137
|
+
* means; the default is not to read the resource.
|
|
138
|
+
*/
|
|
139
|
+
appliesWhen?: {
|
|
140
|
+
attribute: string;
|
|
141
|
+
equals: string[];
|
|
142
|
+
whenUnset?: "read" | "skip";
|
|
143
|
+
};
|
|
119
144
|
/**
|
|
120
145
|
* Which provider versions this describes, as a semver range. A
|
|
121
146
|
* configuration states its own constraint under `required_providers`,
|
package/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,17 @@ interface StorageResource {
|
|
|
20
20
|
storageSystem: string;
|
|
21
21
|
/** How a caller reaches it, when that is not the store's own name. */
|
|
22
22
|
transport?: string;
|
|
23
|
+
/**
|
|
24
|
+
* What the resource declares. The default, `"container"`, means the
|
|
25
|
+
* resource is the thing code addresses: code passes a bucket's name
|
|
26
|
+
* to `bucket()`, so the declared name and the accessed name meet.
|
|
27
|
+
* `"store"` means the resource only says the store exists. Code
|
|
28
|
+
* splits a Redis cluster into key namespaces, no attribute of the
|
|
29
|
+
* cluster lists them, so the summary gets no container name and
|
|
30
|
+
* claims no access. Any match on the cluster's own name would be a
|
|
31
|
+
* coincidence between a deployment name and a key prefix.
|
|
32
|
+
*/
|
|
33
|
+
declares?: "container" | "store";
|
|
23
34
|
/** The attribute that says what the resource is called once deployed. */
|
|
24
35
|
nameAttribute?: string;
|
|
25
36
|
/** Whether the fields it declares are every field an item has. */
|
|
@@ -80,7 +91,7 @@ interface MetricResource {
|
|
|
80
91
|
* metric spells, so it is the identity the two sides share.
|
|
81
92
|
*/
|
|
82
93
|
metricTypeTemplate: string;
|
|
83
|
-
/** Which attribute says whether one measurement is a number or a
|
|
94
|
+
/** Which attribute says whether one measurement is a number or a histogram. */
|
|
84
95
|
values?: AttributeMeaning<MetricValueShape>;
|
|
85
96
|
/** Which attribute says what a measurement covers. */
|
|
86
97
|
accumulates?: AttributeMeaning<MetricAccumulation>;
|
|
@@ -116,6 +127,20 @@ type TerraformResource = StorageResource | MessageBusResource | MetricResource |
|
|
|
116
127
|
interface TerraformResourcePattern {
|
|
117
128
|
/** The resource type, spelled the way the provider spells it. */
|
|
118
129
|
resource: string;
|
|
130
|
+
/**
|
|
131
|
+
* An attribute that decides whether the entry describes the resource
|
|
132
|
+
* at all. `aws_elasticache_cluster` deploys whichever engine its
|
|
133
|
+
* `engine` attribute picks, and only some engines are the store the
|
|
134
|
+
* entry describes. A value outside `equals`, or one built at deploy
|
|
135
|
+
* time, means the resource is not read, rather than read as
|
|
136
|
+
* something it may not be. `whenUnset` says what an absent attribute
|
|
137
|
+
* means; the default is not to read the resource.
|
|
138
|
+
*/
|
|
139
|
+
appliesWhen?: {
|
|
140
|
+
attribute: string;
|
|
141
|
+
equals: string[];
|
|
142
|
+
whenUnset?: "read" | "skip";
|
|
143
|
+
};
|
|
119
144
|
/**
|
|
120
145
|
* Which provider versions this describes, as a semver range. A
|
|
121
146
|
* configuration states its own constraint under `required_providers`,
|
package/dist/index.js
CHANGED
|
@@ -360,7 +360,7 @@ function summariesForFiles(files, options) {
|
|
|
360
360
|
for (const [resourceType, label, body] of file.resources) {
|
|
361
361
|
for (const pack of options.packs) {
|
|
362
362
|
const pattern = patternFor(pack, resourceType, file.constraints);
|
|
363
|
-
if (pattern === void 0) {
|
|
363
|
+
if (pattern === void 0 || !entryApplies(pattern, body)) {
|
|
364
364
|
continue;
|
|
365
365
|
}
|
|
366
366
|
summaries.push(
|
|
@@ -435,6 +435,18 @@ function patternFor(pack, resourceType, constraints) {
|
|
|
435
435
|
}
|
|
436
436
|
});
|
|
437
437
|
}
|
|
438
|
+
function entryApplies(pattern, body) {
|
|
439
|
+
const gate = pattern.appliesWhen;
|
|
440
|
+
if (gate === void 0) {
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
const value = body[gate.attribute];
|
|
444
|
+
if (value === void 0) {
|
|
445
|
+
return gate.whenUnset === "read";
|
|
446
|
+
}
|
|
447
|
+
const text = stringOf(value);
|
|
448
|
+
return text !== null && gate.equals.includes(text);
|
|
449
|
+
}
|
|
438
450
|
var READERS = {
|
|
439
451
|
storage: (site, boundary) => {
|
|
440
452
|
const types = fieldTypes(site.body, boundary);
|
|
@@ -454,7 +466,8 @@ function summariesFor(opts) {
|
|
|
454
466
|
function storageSummary(opts) {
|
|
455
467
|
const { boundary, label, shape, types } = opts;
|
|
456
468
|
const address = `${opts.resourceType}.${label}`;
|
|
457
|
-
const
|
|
469
|
+
const declaresContainer = boundary.declares !== "store";
|
|
470
|
+
const physicalTable = !declaresContainer || boundary.nameAttribute === void 0 ? null : namePattern(opts.body[boundary.nameAttribute], opts.scope);
|
|
458
471
|
return {
|
|
459
472
|
kind: "library",
|
|
460
473
|
location: {
|
|
@@ -470,7 +483,7 @@ function storageSummary(opts) {
|
|
|
470
483
|
storageSystem: boundary.storageSystem,
|
|
471
484
|
...boundary.transport !== void 0 ? { transport: boundary.transport } : {},
|
|
472
485
|
scope: "default",
|
|
473
|
-
container: label,
|
|
486
|
+
container: declaresContainer ? label : null,
|
|
474
487
|
accessPath: shape.accessPath
|
|
475
488
|
})
|
|
476
489
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suss/contract-terraform",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Read the storage a Terraform configuration declares, for checking code against it.",
|
|
6
6
|
"keywords": [
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"typecheck": "tsc --noEmit"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@suss/behavioral-ir": "0.
|
|
48
|
+
"@suss/behavioral-ir": "0.10.0",
|
|
49
49
|
"hcl2-parser": "^1.0.3",
|
|
50
50
|
"semver": "^7.6.0"
|
|
51
51
|
},
|