@suss/contract-terraform 0.33.1 → 0.34.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 +26 -26
- package/dist/index.cjs +3 -3
- package/dist/index.d.cts +136 -141
- package/dist/index.d.ts +136 -141
- package/dist/index.js +3 -3
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
# @suss/contract-terraform
|
|
2
2
|
|
|
3
|
-
Part of [suss](https://github.com/nimbuscloud-ai/suss), which reads both sides of every call in a repository and
|
|
3
|
+
Part of [suss](https://github.com/nimbuscloud-ai/suss), which reads both sides of every call in a repository and reports where the two disagree.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This package reads the boundaries a Terraform configuration declares, so code and other resources can be checked against them.
|
|
6
6
|
|
|
7
7
|
## What this package is
|
|
8
8
|
|
|
9
|
-
A contract reader. It walks `.tf` files and
|
|
9
|
+
A contract reader. It walks `.tf` files and records what it finds as boundaries, the same way `@suss/contract-cloudformation` records what a template declares. It has no provider knowledge of its own. A pack declares that `aws_dynamodb_table` is a store and that `google_logging_metric` is a metric, and this reader matches on those declarations.
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
suss contract --from terraform infra/terraform/dynamodb -o tables.json
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
The path
|
|
15
|
+
The path can be one file or the directory a module is in, since a module declares its resources across several files. A root module that only calls child modules is read too, as described below.
|
|
16
16
|
|
|
17
17
|
## What it reads
|
|
18
18
|
|
|
@@ -34,17 +34,17 @@ resource "aws_dynamodb_table" "orders" {
|
|
|
34
34
|
}
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
The resource label is the container, since
|
|
37
|
+
The resource label is the container, since the rest of the configuration refers to the resource by its label. `name` is what the table is called once deployed, and it goes on the contract as the physical name.
|
|
38
38
|
|
|
39
|
-
That name is usually built at deploy time
|
|
39
|
+
That name is usually built at deploy time. Terraform interpolates the same way CloudFormation does, so `"${local.environment}-orders-v1"` is recorded as the pattern `{local.environment}-orders-v1`. Code that builds the same name from its own variable records `{stage}-orders-v1`, and the two pair on the fixed text. That is how a service written in TypeScript pairs with a table declared in HCL.
|
|
40
40
|
|
|
41
|
-
Each secondary index becomes its own boundary, because a query through an index keys on that index's fields. `hash_key` and `range_key`
|
|
41
|
+
Each secondary index becomes its own boundary, because a query through an index keys on that index's fields. `hash_key` and `range_key` give what identifies an item, partition key first, and the `attribute` blocks give each key its type.
|
|
42
42
|
|
|
43
|
-
A table declares its keys and lets every other attribute vary, so the contract
|
|
43
|
+
A table declares its keys and lets every other attribute vary, so the contract has `fieldSet: "partial"`, and the checker never reports an ordinary attribute as unknown.
|
|
44
44
|
|
|
45
45
|
## A structure written as JSON
|
|
46
46
|
|
|
47
|
-
Several providers take a structure as a string
|
|
47
|
+
Several providers take a structure as a string instead of as blocks. Examples are a BigQuery table's schema and an ECS task's container definitions. Terraform lets an author write one in two ways, and both mean the same thing once deployed:
|
|
48
48
|
|
|
49
49
|
```hcl
|
|
50
50
|
schema = jsonencode([
|
|
@@ -56,23 +56,23 @@ schema = <<EOF
|
|
|
56
56
|
EOF
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
`jsonAttributeValue` reads both
|
|
59
|
+
`jsonAttributeValue` reads both. A pack asks for it through `fieldsFromJson` on a storage entry, giving the attribute the JSON is in, and the keys in each entry that give the field its name, its type, and whether it is always set. A schema written this way lists every field the item has, so the contract comes out `exhaustive`. A schema supplied by a file or a variable is not written anywhere the reader can see, so the reader records nothing and does not guess.
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
For a deployable, the reader goes into such an attribute and reads blocks from it, where a store would have fields read out of it. An ECS task writes its containers as `container_definitions = jsonencode([...])`, and a pack treats that attribute the way it would treat a block. So each container is read the same way as a Cloud Run container written as blocks.
|
|
62
62
|
|
|
63
63
|
## A block written once and deployed many times
|
|
64
64
|
|
|
65
|
-
A module rarely writes a container's variables
|
|
65
|
+
A module rarely writes a container's variables one block at a time. It writes one `dynamic "env"` block over a map, or, in an ECS task's JSON, a `for` expression over the same map. The reader expands both wherever the configuration already gives the contents of that map, so the container declares the variables the deployment will give it, instead of none. [How a repeated block is expanded](./DESIGN.md#a-block-written-once-and-deployed-many-times) covers which forms can be settled and which stay holes.
|
|
66
66
|
|
|
67
67
|
## A root module that calls child modules
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
Many configurations declare no resources at the root. They call one module per service, and every table and function is inside the child. When a `source` points at a directory next to this one, the reader follows it and reads that directory as a module of its own. Everything the child declares gets `module.orders.` in front of its summary name and its deployable unit. [A module call this reader follows](./DESIGN.md#a-module-call-this-reader-follows) covers what passes between a call and the module it calls.
|
|
70
70
|
|
|
71
71
|
## Several entries for one resource type
|
|
72
72
|
|
|
73
|
-
A pack
|
|
73
|
+
A pack declares more than one entry for a resource type when the provider spells it differently across versions. A resource whose engine is chosen by one of its attributes does not need a second entry. A storage entry's `storageSystem` is either a word, or an attribute plus a table of what each of its values means. So `aws_db_instance` reads its `engine`, and `google_sql_database_instance` reads its `database_version`. A table matches whole values by default, or the start of a value with `matches: "prefix"`. Cloud SQL needs the prefix form, because `database_version` gives an engine and a release together, as in `POSTGRES_15` and `MYSQL_8_0_31`, and the releases change every quarter.
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
If the pack does not list a value, or the configuration builds the value at deploy time, the store gets no engine. The resource is still read, since the instance is deployed and has a name, and the summary records a gap saying which attribute was not settled. `appliesWhen` covers a different case: a resource that is not what the entry describes at all. A Firestore database in Datastore mode uses a different API, so that entry checks `type` and skips the resource entirely.
|
|
76
76
|
|
|
77
77
|
## What a deployable declares
|
|
78
78
|
|
|
@@ -92,27 +92,27 @@ resource "aws_lambda_function" "confirm" {
|
|
|
92
92
|
}
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
The contract lists every variable the process starts with, the ones the configuration sets and the ones the platform adds, and
|
|
95
|
+
The contract lists every variable the process starts with, both the ones the configuration sets and the ones the platform adds, and marks which is which. `LOG_LEVEL` is recorded as the text it is set to. `ORDERS_TABLE` is set from another resource, so the contract records both the name that resource declares and which resource it was. Code that reads `process.env.ORDERS_TABLE` to address a table reaches the table through either.
|
|
96
96
|
|
|
97
|
-
The unit is the resource label, `confirm`,
|
|
97
|
+
The unit is the resource label, `confirm`, whatever name the function deploys under. The rest of the configuration refers to the resource by its label, the way CloudFormation uses a logical id, so a variable that points at the resource and the resource's own contract both use the same name.
|
|
98
98
|
|
|
99
|
-
One resource deploys several processes
|
|
99
|
+
One resource deploys several processes when the provider works that way. An ECS task definition produces one contract per container, and each container gets only its own variables.
|
|
100
100
|
|
|
101
|
-
Every
|
|
101
|
+
Every value that cannot be settled is written the same way in a contract. An `image = var.image`, a `runtime`, and a handler supplied by a variable all come out as a pattern such as `{var.image}`, without the `$` of the raw `${var.image}`, so nothing later has to know which field it is reading. A handler with a hole in it does not show which file the code is in, since splitting it at its last dot would pick a module nobody deploys.
|
|
102
102
|
|
|
103
|
-
A configuration
|
|
103
|
+
A configuration gives the handler that runs, but never the directory the deployed artifact was built from, so the handler is all the checker has to go on. A container deployable has no handler at all, since its image was built elsewhere. In that case the caller gives the location of the code with `--code-scope api/web=services/api`, or `codeScopes` in the read options. [Which code a deployable unit runs](./DESIGN.md#which-code-a-deployable-unit-runs) covers what that records, and what happens when two units point at one directory.
|
|
104
104
|
|
|
105
105
|
## What it will not tell you
|
|
106
106
|
|
|
107
|
-
- **
|
|
108
|
-
- **A resource built with `for_each` or `count`**
|
|
109
|
-
- **A name a variable
|
|
110
|
-
- **What a secret contains.**
|
|
111
|
-
- **Which source directory a deployable was built from.** Terraform builds the artifact outside the configuration
|
|
107
|
+
- **Anything no loaded pack describes.** A resource that no entry covers, such as an IAM policy or a subnet, is skipped.
|
|
108
|
+
- **A resource built with `for_each` or `count`** has one `resource` block for many tables, and the reader reads the block as written without working out what it expands to. A `dynamic` block inside a resource is expanded, since it decides what a single deployed process is given.
|
|
109
|
+
- **A name that comes entirely from a variable**, such as `name = var.table_name`, has no fixed text to pair on, so the reader records nothing instead of guessing.
|
|
110
|
+
- **What a secret contains.** For a variable a secret supplies, the reader records which resource supplies it and nothing more, since no configuration writes the contents down.
|
|
111
|
+
- **Which source directory a deployable was built from.** Terraform builds the artifact outside the configuration. A `filename` is a zip nothing in the run can open, and a `source_dir` belongs to a data source the reader does not follow. `--code-scope` is how the caller supplies what the configuration never states.
|
|
112
112
|
|
|
113
113
|
## Where it fits in suss
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
The package depends on `@suss/behavioral-ir` for the summaries it produces, and on `hcl2-parser` for reading HCL. The storage pass in `@suss/checker` pairs what code touches with what this reader declares.
|
|
116
116
|
|
|
117
117
|
## More
|
|
118
118
|
|
package/dist/index.cjs
CHANGED
|
@@ -592,9 +592,9 @@ var TokenReader = class {
|
|
|
592
592
|
return this.readTerm();
|
|
593
593
|
}
|
|
594
594
|
/**
|
|
595
|
-
* A call
|
|
596
|
-
* burn-rate condition that way, and the call
|
|
597
|
-
* is about
|
|
595
|
+
* A call in place of a comparison. Cloud Monitoring writes an SLO
|
|
596
|
+
* burn-rate condition that way, and the call's arguments identify what
|
|
597
|
+
* the condition is about.
|
|
598
598
|
*/
|
|
599
599
|
readCall() {
|
|
600
600
|
const name = this.tokens[this.at];
|
package/dist/index.d.cts
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
import { DeployableUnit, MessageBusTechnology, MetricValueShape, MetricAccumulation, BehavioralSummary } from '@suss/behavioral-ir';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* The types a Terraform pack uses to describe the resources one provider
|
|
5
|
+
* declares.
|
|
5
6
|
*
|
|
6
|
-
* A provider
|
|
7
|
-
* their
|
|
8
|
-
* reader walks HCL and matches
|
|
9
|
-
* is and which provider versions
|
|
7
|
+
* A provider defines its own resource types, and each version defines
|
|
8
|
+
* their attributes, so both belong in a pack and stay out of this
|
|
9
|
+
* reader. The reader walks HCL and matches resources. A pack describes
|
|
10
|
+
* what `aws_dynamodb_table` is and which provider versions that covers.
|
|
10
11
|
*
|
|
11
|
-
* Every entry is data
|
|
12
|
-
*
|
|
13
|
-
* pack to ship code the reader cannot see into.
|
|
12
|
+
* Every entry is data. When these types cannot describe a resource,
|
|
13
|
+
* widen them, so a pack never ships code the reader cannot inspect.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* An attribute whose value the pack translates into
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* An attribute whose value the pack translates into suss's own terms.
|
|
18
|
+
* The reader looks up the value at `attribute` in `means`. A value the
|
|
19
|
+
* pack did not list, or one the configuration builds at deploy time,
|
|
20
|
+
* maps to nothing.
|
|
21
21
|
*/
|
|
22
22
|
interface AttributeMeaning<T extends string> {
|
|
23
|
-
/** The attribute path
|
|
23
|
+
/** The attribute path to read. */
|
|
24
24
|
attribute: string;
|
|
25
|
-
/**
|
|
25
|
+
/** The suss term for each value the provider can write there. */
|
|
26
26
|
means: Record<string, T>;
|
|
27
27
|
/**
|
|
28
28
|
* Whether a value has to equal a key of `means` or start with one.
|
|
@@ -30,45 +30,45 @@ interface AttributeMeaning<T extends string> {
|
|
|
30
30
|
* and the releases change every quarter, so a pack lists the prefix.
|
|
31
31
|
*/
|
|
32
32
|
matches?: "value" | "prefix";
|
|
33
|
-
/**
|
|
33
|
+
/** The meaning to use when the resource does not set the attribute. */
|
|
34
34
|
whenUnset?: T;
|
|
35
35
|
}
|
|
36
36
|
/** A store a caller addresses by container and key. */
|
|
37
37
|
interface StorageResource {
|
|
38
38
|
kind: "storage";
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
40
|
+
* The store, such as `aws.dynamodb` or `s3`. A resource whose engine
|
|
41
|
+
* depends on one of its attributes gives an AttributeMeaning instead.
|
|
42
|
+
* A value the pack does not list leaves the store with no engine, and
|
|
43
|
+
* the resource is still read.
|
|
44
44
|
*/
|
|
45
45
|
storageSystem: string | AttributeMeaning<string>;
|
|
46
|
-
/** How a caller reaches it, when that
|
|
46
|
+
/** How a caller reaches it, when that differs from the store's own name. */
|
|
47
47
|
transport?: string;
|
|
48
48
|
/**
|
|
49
|
-
* What the resource declares. The default, `"container"`, means
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
49
|
+
* What the resource declares. The default, `"container"`, means code
|
|
50
|
+
* addresses the resource directly: code passes a bucket's name to
|
|
51
|
+
* `bucket()`, so the declared name matches the accessed one. `"store"`
|
|
52
|
+
* means the resource only declares that the store exists. Code splits
|
|
53
|
+
* a Redis cluster into key namespaces that no attribute of the cluster
|
|
54
|
+
* lists, so the summary gets no container name and claims no access.
|
|
55
|
+
* A match on the cluster's own name would be a coincidence between a
|
|
56
|
+
* deployment name and a key prefix.
|
|
57
57
|
*/
|
|
58
58
|
declares?: "container" | "store";
|
|
59
|
-
/** The attribute that
|
|
59
|
+
/** The attribute that contains the resource's deployed name. */
|
|
60
60
|
nameAttribute?: string;
|
|
61
61
|
/**
|
|
62
|
-
* The attribute
|
|
62
|
+
* The attribute that contains the namespace a container belongs to. A
|
|
63
63
|
* BigQuery table is always addressed through its dataset, so two
|
|
64
64
|
* tables called `orders` in two datasets are two containers.
|
|
65
65
|
*/
|
|
66
66
|
scopeAttribute?: string;
|
|
67
67
|
/** Whether the fields it declares are every field an item has. */
|
|
68
68
|
fieldSet: "exhaustive" | "partial" | "none";
|
|
69
|
-
/** The attributes that
|
|
69
|
+
/** The attributes that list an item's key fields, in key order. */
|
|
70
70
|
identifies?: string[];
|
|
71
|
-
/** Blocks that declare another
|
|
71
|
+
/** Blocks that declare another access path, each keyed on its own fields. */
|
|
72
72
|
accessPathBlocks?: string[];
|
|
73
73
|
/** The block that gives each field a type, and its two attributes. */
|
|
74
74
|
fieldTypes?: {
|
|
@@ -77,35 +77,35 @@ interface StorageResource {
|
|
|
77
77
|
typeAttribute: string;
|
|
78
78
|
};
|
|
79
79
|
/**
|
|
80
|
-
* An attribute
|
|
81
|
-
* schema written this way
|
|
82
|
-
* makes the contract exhaustive
|
|
80
|
+
* An attribute that lists every field as JSON, one object per field. A
|
|
81
|
+
* schema written this way lists every field the item has, so reading
|
|
82
|
+
* one makes the contract exhaustive regardless of `fieldSet`.
|
|
83
83
|
*/
|
|
84
84
|
fieldsFromJson?: {
|
|
85
85
|
/** The attribute whose value is a JSON list of field objects. */
|
|
86
86
|
attribute: string;
|
|
87
|
-
/** The key
|
|
87
|
+
/** The key in each entry that contains the field's name. */
|
|
88
88
|
nameKey: string;
|
|
89
|
-
/** The key
|
|
89
|
+
/** The key in each entry that contains the field's type. */
|
|
90
90
|
typeKey?: string;
|
|
91
|
-
/** The key that
|
|
91
|
+
/** The key that marks a field as always set, and the values that do. */
|
|
92
92
|
requires?: {
|
|
93
93
|
key: string;
|
|
94
94
|
values: string[];
|
|
95
95
|
};
|
|
96
96
|
};
|
|
97
97
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
98
|
+
* Which fields an access path can return. A DynamoDB index copies only
|
|
99
|
+
* part of an item, and a read of any other attribute returns nothing
|
|
100
|
+
* without an error, so the copied attributes are the index's complete
|
|
101
|
+
* field list.
|
|
102
102
|
*/
|
|
103
103
|
serves?: {
|
|
104
|
-
/** The attribute that
|
|
104
|
+
/** The attribute that contains the projection kind. */
|
|
105
105
|
kindAttribute: string;
|
|
106
|
-
/** The attribute listing
|
|
106
|
+
/** The attribute listing the copied fields, for a kind that lists them. */
|
|
107
107
|
fieldsAttribute: string;
|
|
108
|
-
/** The
|
|
108
|
+
/** The `kindAttribute` value that means the whole item is copied. */
|
|
109
109
|
everything: string;
|
|
110
110
|
};
|
|
111
111
|
}
|
|
@@ -116,46 +116,45 @@ interface MessageBusResource {
|
|
|
116
116
|
nameAttribute?: string;
|
|
117
117
|
}
|
|
118
118
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* its namespace and its name together, so
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* the summary
|
|
119
|
+
* The pattern of the deployed metric type, where each `{...}` is
|
|
120
|
+
* replaced by the value at that attribute path. A CloudWatch metric is
|
|
121
|
+
* identified by its namespace and its name together, so its template
|
|
122
|
+
* gives both: `"{metric_transformation.namespace}/{metric_transformation.name}"`.
|
|
123
|
+
* When the resource leaves one of those attributes unset, the whole
|
|
124
|
+
* identity is unknown, so the summary does not pair on a partial name.
|
|
125
125
|
*/
|
|
126
126
|
type MetricTypeTemplate = string;
|
|
127
127
|
/** A named series of measurements a resource declares. */
|
|
128
128
|
interface MetricResource {
|
|
129
129
|
kind: "metric";
|
|
130
|
-
/**
|
|
130
|
+
/** The metrics system, such as `cloud-monitoring`. */
|
|
131
131
|
metricSystem: string;
|
|
132
|
-
/** How the
|
|
132
|
+
/** How the identity both sides write is built from the resource. */
|
|
133
133
|
metricTypeTemplate: MetricTypeTemplate;
|
|
134
|
-
/**
|
|
134
|
+
/** The attribute that decides whether a measurement is a number or a histogram. */
|
|
135
135
|
values?: AttributeMeaning<MetricValueShape>;
|
|
136
|
-
/**
|
|
136
|
+
/** The attribute that decides what one measurement covers. */
|
|
137
137
|
accumulates?: AttributeMeaning<MetricAccumulation>;
|
|
138
138
|
}
|
|
139
139
|
/**
|
|
140
|
-
*
|
|
141
|
-
* writes the namespace and the name
|
|
142
|
-
*
|
|
143
|
-
* inside it.
|
|
140
|
+
* Where a reading gives the metric it is about. A CloudWatch alarm
|
|
141
|
+
* writes the namespace and the name as attributes. A Cloud Monitoring
|
|
142
|
+
* condition writes a query, and the metric is one value inside it.
|
|
144
143
|
*/
|
|
145
144
|
type MetricIdentity = {
|
|
146
145
|
from: "attributes";
|
|
147
146
|
template: MetricTypeTemplate;
|
|
148
147
|
} | {
|
|
149
148
|
from: "query";
|
|
150
|
-
/** The attribute
|
|
149
|
+
/** The attribute that contains the query. */
|
|
151
150
|
attribute: string;
|
|
152
151
|
/** The key inside that query whose value is the metric's type. */
|
|
153
152
|
key: string;
|
|
154
153
|
};
|
|
155
154
|
/**
|
|
156
155
|
* A resource that reads a metric another resource declares. One
|
|
157
|
-
* resource
|
|
158
|
-
*
|
|
156
|
+
* resource often contains several readings, each in its own block and
|
|
157
|
+
* about its own metric, so each becomes its own boundary.
|
|
159
158
|
*/
|
|
160
159
|
interface MetricReadingResource {
|
|
161
160
|
kind: "metric-reading";
|
|
@@ -165,24 +164,24 @@ interface MetricReadingResource {
|
|
|
165
164
|
* list means the resource is itself one reading.
|
|
166
165
|
*/
|
|
167
166
|
readingBlocks: string[];
|
|
168
|
-
/**
|
|
167
|
+
/** Where the reading gives the metric it is about. */
|
|
169
168
|
identifies: MetricIdentity;
|
|
170
169
|
/**
|
|
171
|
-
*
|
|
172
|
-
* against a value of this shape. A condition
|
|
173
|
-
*
|
|
170
|
+
* An attribute whose presence means the reading compares the series
|
|
171
|
+
* against a value of this shape. A condition that sets a threshold
|
|
172
|
+
* compares against a number, whatever the number is.
|
|
174
173
|
*/
|
|
175
174
|
comparesTo?: {
|
|
176
175
|
attribute: string;
|
|
177
176
|
whenSet: MetricValueShape;
|
|
178
177
|
};
|
|
179
|
-
/**
|
|
178
|
+
/** The attribute that decides what the reading reduces each window to. */
|
|
180
179
|
reducesTo?: AttributeMeaning<MetricValueShape>;
|
|
181
180
|
}
|
|
182
181
|
/**
|
|
183
|
-
* Where a provider writes one process's environment
|
|
184
|
-
*
|
|
185
|
-
* variable
|
|
182
|
+
* Where a provider writes one process's environment: either a map
|
|
183
|
+
* attribute keyed by variable name, or repeated entries that each give
|
|
184
|
+
* one variable's name and its source, a literal or a secret.
|
|
186
185
|
*/
|
|
187
186
|
type EnvDeclaration = {
|
|
188
187
|
style: "map";
|
|
@@ -191,81 +190,80 @@ type EnvDeclaration = {
|
|
|
191
190
|
style: "entries";
|
|
192
191
|
/** The block one entry is written as, as a dotted path. */
|
|
193
192
|
block: string;
|
|
194
|
-
/** The attribute
|
|
193
|
+
/** The attribute that contains the variable's name. */
|
|
195
194
|
nameAttribute: string;
|
|
196
|
-
/** The attribute
|
|
195
|
+
/** The attribute that contains the value, for an entry that writes one. */
|
|
197
196
|
valueAttribute?: string;
|
|
198
|
-
/** The attribute
|
|
197
|
+
/** The attribute that contains the secret supplying the value. */
|
|
199
198
|
secretAttribute?: string;
|
|
200
199
|
};
|
|
201
200
|
/**
|
|
202
|
-
* How a platform
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
* its own, which says nothing about which file it is in.
|
|
201
|
+
* How a platform writes the handler it calls. `"module.export"` is a
|
|
202
|
+
* module path and an exported name joined at the last dot, as Lambda
|
|
203
|
+
* takes it. `"name"` is an exported name alone, with no file.
|
|
206
204
|
*/
|
|
207
205
|
type HandlerSpelling = "module.export" | "name";
|
|
208
|
-
/**
|
|
206
|
+
/** Where the provider records the code one process runs. */
|
|
209
207
|
interface DeployableCode {
|
|
210
|
-
/** The attribute that
|
|
208
|
+
/** The attribute that contains the function the platform calls. */
|
|
211
209
|
handler?: {
|
|
212
210
|
attribute: string;
|
|
213
211
|
spelling: HandlerSpelling;
|
|
214
212
|
};
|
|
215
|
-
/** The attribute that
|
|
213
|
+
/** The attribute that contains the image a container runs. */
|
|
216
214
|
imageAttribute?: string;
|
|
217
215
|
}
|
|
218
216
|
/** A resource that deploys several processes, each with its own environment. */
|
|
219
217
|
interface DeployableContainers {
|
|
220
218
|
/** The blocks one container is written inside, outermost first. */
|
|
221
219
|
blocks: string[];
|
|
222
|
-
/** The attribute inside a container that
|
|
220
|
+
/** The attribute inside a container that contains its name. */
|
|
223
221
|
nameAttribute?: string;
|
|
224
222
|
}
|
|
225
223
|
/**
|
|
226
|
-
* Something that gets deployed and runs
|
|
227
|
-
* job.
|
|
228
|
-
*
|
|
229
|
-
*
|
|
224
|
+
* Something that gets deployed and runs, such as a function, a
|
|
225
|
+
* container or a job. It declares the environment the process starts
|
|
226
|
+
* with, so suss can report code that reads a variable the deployment
|
|
227
|
+
* never sets.
|
|
230
228
|
*
|
|
231
|
-
* The unit is the resource's label
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
229
|
+
* The unit is keyed by the resource's label, and the deployed name is
|
|
230
|
+
* not used. The rest of the configuration refers to the resource by its
|
|
231
|
+
* label, as CloudFormation does with a logical id, and a variable that
|
|
232
|
+
* points at the resource resolves to it.
|
|
235
233
|
*/
|
|
236
234
|
interface DeployableResource {
|
|
237
235
|
kind: "deployable";
|
|
238
|
-
/**
|
|
236
|
+
/** What runs it, as a deployable unit's `deploymentTarget`. */
|
|
239
237
|
deploymentTarget: DeployableUnit["deploymentTarget"];
|
|
240
238
|
/** Where each process is written, for a resource that deploys several. */
|
|
241
239
|
containers?: DeployableContainers;
|
|
242
240
|
/** Where the environment is written, inside the process. */
|
|
243
241
|
env?: EnvDeclaration[];
|
|
244
|
-
/**
|
|
242
|
+
/** Where the provider records the code the process runs. */
|
|
245
243
|
code?: DeployableCode;
|
|
246
|
-
/** The attribute
|
|
244
|
+
/** The attribute that contains the language runtime, copied as written. */
|
|
247
245
|
runtimeAttribute?: string;
|
|
248
246
|
/**
|
|
249
|
-
*
|
|
250
|
-
* that
|
|
251
|
-
*
|
|
252
|
-
*
|
|
247
|
+
* Variables the platform sets regardless of the configuration. An
|
|
248
|
+
* entry that leaves this out gets the list for its deployment target,
|
|
249
|
+
* which every other reader of that target uses. A product with its own
|
|
250
|
+
* list, such as Cloud Run, sets it here.
|
|
253
251
|
*/
|
|
254
252
|
platformEnvVars?: readonly string[];
|
|
255
253
|
}
|
|
256
254
|
type TerraformResource = StorageResource | MessageBusResource | MetricResource | MetricReadingResource | DeployableResource;
|
|
257
255
|
/** One resource type, as one version range of one provider declares it. */
|
|
258
256
|
interface TerraformResourcePattern {
|
|
259
|
-
/** The resource type,
|
|
257
|
+
/** The resource type, as the provider writes it. */
|
|
260
258
|
resource: string;
|
|
261
259
|
/**
|
|
262
260
|
* An attribute that decides whether the entry describes the resource
|
|
263
|
-
* at all. A Firestore database in Datastore mode
|
|
264
|
-
* API, so
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
* `whenUnset`
|
|
268
|
-
*
|
|
261
|
+
* at all. A Firestore database in Datastore mode uses a different
|
|
262
|
+
* API, so the Firestore entry does not describe it. A value outside
|
|
263
|
+
* `equals`, or one built at deploy time, means the resource is
|
|
264
|
+
* skipped, so it is never read as something it may not be.
|
|
265
|
+
* `whenUnset` gives what an absent attribute means; by default the
|
|
266
|
+
* resource is skipped.
|
|
269
267
|
*/
|
|
270
268
|
appliesWhen?: {
|
|
271
269
|
attribute: string;
|
|
@@ -277,7 +275,7 @@ interface TerraformResourcePattern {
|
|
|
277
275
|
* Which provider versions this describes, as a semver range. A
|
|
278
276
|
* configuration states its own constraint under `required_providers`,
|
|
279
277
|
* and an entry outside it is not read. A configuration that states no
|
|
280
|
-
* constraint is read by every entry, since nothing
|
|
278
|
+
* constraint is read by every entry, since nothing rules any out.
|
|
281
279
|
*/
|
|
282
280
|
providerVersions: string;
|
|
283
281
|
/** What the resource is, once read. */
|
|
@@ -297,16 +295,15 @@ interface TerraformPack {
|
|
|
297
295
|
/**
|
|
298
296
|
* A filter written as comparisons joined by AND and OR.
|
|
299
297
|
*
|
|
300
|
-
* Several cloud APIs take a selector as one string
|
|
298
|
+
* Several cloud APIs take a selector as one string such as
|
|
301
299
|
* `metric.type = "x" AND resource.type = "y"`, with quoted or bare
|
|
302
300
|
* values, parentheses, NOT, and terms written next to each other for
|
|
303
301
|
* AND. A resource that reads what another resource declares usually
|
|
304
|
-
*
|
|
305
|
-
* this reader finds out what a resource refers to.
|
|
302
|
+
* identifies it inside such a string, so the reader parses the string.
|
|
306
303
|
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
304
|
+
* The pack gives the key to look for, and this file contains only the
|
|
305
|
+
* grammar. It returns a tree, because OR and NOT change what a term
|
|
306
|
+
* means and only the caller can decide how to treat that.
|
|
310
307
|
*/
|
|
311
308
|
/** One comparison, as the filter writes it. */
|
|
312
309
|
interface FilterTerm {
|
|
@@ -322,7 +319,7 @@ interface FilterTerm {
|
|
|
322
319
|
/** The right side, with the quotes taken off a quoted value. */
|
|
323
320
|
value: string;
|
|
324
321
|
}
|
|
325
|
-
/** A call
|
|
322
|
+
/** A function call in place of a comparison, with its arguments. */
|
|
326
323
|
interface FilterCall {
|
|
327
324
|
type: "call";
|
|
328
325
|
name: string;
|
|
@@ -336,7 +333,7 @@ type FilterQuery = FilterTerm | FilterCall | {
|
|
|
336
333
|
type: "negation";
|
|
337
334
|
operand: FilterQuery;
|
|
338
335
|
};
|
|
339
|
-
/** A
|
|
336
|
+
/** A parse failure includes the reason, so it is never mistaken for an empty filter. */
|
|
340
337
|
type FilterParse = {
|
|
341
338
|
ok: true;
|
|
342
339
|
query: FilterQuery;
|
|
@@ -348,63 +345,61 @@ type FilterParse = {
|
|
|
348
345
|
declare function filterValuesFor(query: FilterQuery, key: string): string[];
|
|
349
346
|
/** Every comparison in the tree, in the order the filter writes them. */
|
|
350
347
|
declare function filterTerms(query: FilterQuery): FilterTerm[];
|
|
351
|
-
/** Every call in the tree,
|
|
348
|
+
/** Every call in the tree, in the order the filter writes them. */
|
|
352
349
|
declare function filterCalls(query: FilterQuery): FilterCall[];
|
|
353
|
-
/**
|
|
350
|
+
/** Parses one filter string, or returns what stopped the parse. */
|
|
354
351
|
declare function parseFilterQuery(source: string): FilterParse;
|
|
355
352
|
|
|
356
353
|
/**
|
|
357
354
|
* An attribute whose value is JSON.
|
|
358
355
|
*
|
|
359
|
-
* Several providers take a structure as a string
|
|
360
|
-
* a BigQuery table's schema
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
356
|
+
* Several providers take a structure as a JSON string instead of as
|
|
357
|
+
* blocks, such as a BigQuery table's schema or an ECS task's container
|
|
358
|
+
* definitions. An author can write one as a literal string, usually a
|
|
359
|
+
* heredoc, or as `jsonencode` over an HCL value, and the parser returns
|
|
360
|
+
* the two very differently. This module turns both into the value the
|
|
361
|
+
* provider receives.
|
|
365
362
|
*
|
|
366
|
-
* A
|
|
367
|
-
*
|
|
368
|
-
* nothing rather than guessing.
|
|
363
|
+
* A value that a file or a variable supplies is not written in the
|
|
364
|
+
* configuration, so it comes back as null and the caller records nothing.
|
|
369
365
|
*/
|
|
370
366
|
/**
|
|
371
|
-
* The value an attribute
|
|
372
|
-
* missing or
|
|
367
|
+
* The value an attribute writes as JSON, or null when the attribute is
|
|
368
|
+
* missing or its value is known only at deploy time.
|
|
373
369
|
*/
|
|
374
370
|
declare function jsonAttributeValue(value: unknown): unknown;
|
|
375
371
|
|
|
376
372
|
/**
|
|
377
373
|
* The boundaries a Terraform configuration declares.
|
|
378
374
|
*
|
|
379
|
-
* This reads HCL and matches
|
|
380
|
-
*
|
|
375
|
+
* This module reads HCL and matches it against the packs a run loads. A
|
|
376
|
+
* pack describes what `aws_dynamodb_table` is, and nothing about any
|
|
381
377
|
* provider is written here.
|
|
382
378
|
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
* block that HCL states once or many times is read.
|
|
379
|
+
* Terraform interpolates the same way CloudFormation does, so
|
|
380
|
+
* `"${local.environment}-orders"` becomes the pattern
|
|
381
|
+
* `{local.environment}-orders` and pairs with code that builds the same
|
|
382
|
+
* name from its own variable. A reference to another resource in the
|
|
383
|
+
* same configuration resolves instead, since that resource writes the
|
|
384
|
+
* value. DESIGN.md covers references, module calls and repeated blocks.
|
|
390
385
|
*/
|
|
391
386
|
|
|
392
387
|
interface TerraformReadOptions {
|
|
393
|
-
/** The packs that
|
|
388
|
+
/** The packs that describe this configuration's resources. */
|
|
394
389
|
packs: TerraformPack[];
|
|
395
390
|
/**
|
|
396
|
-
* The directory each deployable unit's code is in, by the
|
|
397
|
-
* name
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
391
|
+
* The directory each deployable unit's code is in, by the unit's
|
|
392
|
+
* instance name. A configuration gives the handler that runs but never
|
|
393
|
+
* the directory the artifact was built from, so a container whose
|
|
394
|
+
* image is built elsewhere has no code until the caller gives its
|
|
395
|
+
* location.
|
|
401
396
|
*/
|
|
402
397
|
codeScopes?: Record<string, string>;
|
|
403
398
|
}
|
|
404
399
|
/** Every boundary one Terraform configuration declares. */
|
|
405
400
|
declare function terraformToSummaries(source: string, sourceFile: string, options: TerraformReadOptions): BehavioralSummary[];
|
|
406
401
|
/**
|
|
407
|
-
*
|
|
402
|
+
* Reads one `.tf` file, or every one directly inside a directory, and
|
|
408
403
|
* every local module the configuration calls.
|
|
409
404
|
*/
|
|
410
405
|
declare function terraformFileToSummaries(target: string, options: TerraformReadOptions): BehavioralSummary[];
|
package/dist/index.d.ts
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
import { DeployableUnit, MessageBusTechnology, MetricValueShape, MetricAccumulation, BehavioralSummary } from '@suss/behavioral-ir';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* The types a Terraform pack uses to describe the resources one provider
|
|
5
|
+
* declares.
|
|
5
6
|
*
|
|
6
|
-
* A provider
|
|
7
|
-
* their
|
|
8
|
-
* reader walks HCL and matches
|
|
9
|
-
* is and which provider versions
|
|
7
|
+
* A provider defines its own resource types, and each version defines
|
|
8
|
+
* their attributes, so both belong in a pack and stay out of this
|
|
9
|
+
* reader. The reader walks HCL and matches resources. A pack describes
|
|
10
|
+
* what `aws_dynamodb_table` is and which provider versions that covers.
|
|
10
11
|
*
|
|
11
|
-
* Every entry is data
|
|
12
|
-
*
|
|
13
|
-
* pack to ship code the reader cannot see into.
|
|
12
|
+
* Every entry is data. When these types cannot describe a resource,
|
|
13
|
+
* widen them, so a pack never ships code the reader cannot inspect.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* An attribute whose value the pack translates into
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* An attribute whose value the pack translates into suss's own terms.
|
|
18
|
+
* The reader looks up the value at `attribute` in `means`. A value the
|
|
19
|
+
* pack did not list, or one the configuration builds at deploy time,
|
|
20
|
+
* maps to nothing.
|
|
21
21
|
*/
|
|
22
22
|
interface AttributeMeaning<T extends string> {
|
|
23
|
-
/** The attribute path
|
|
23
|
+
/** The attribute path to read. */
|
|
24
24
|
attribute: string;
|
|
25
|
-
/**
|
|
25
|
+
/** The suss term for each value the provider can write there. */
|
|
26
26
|
means: Record<string, T>;
|
|
27
27
|
/**
|
|
28
28
|
* Whether a value has to equal a key of `means` or start with one.
|
|
@@ -30,45 +30,45 @@ interface AttributeMeaning<T extends string> {
|
|
|
30
30
|
* and the releases change every quarter, so a pack lists the prefix.
|
|
31
31
|
*/
|
|
32
32
|
matches?: "value" | "prefix";
|
|
33
|
-
/**
|
|
33
|
+
/** The meaning to use when the resource does not set the attribute. */
|
|
34
34
|
whenUnset?: T;
|
|
35
35
|
}
|
|
36
36
|
/** A store a caller addresses by container and key. */
|
|
37
37
|
interface StorageResource {
|
|
38
38
|
kind: "storage";
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
40
|
+
* The store, such as `aws.dynamodb` or `s3`. A resource whose engine
|
|
41
|
+
* depends on one of its attributes gives an AttributeMeaning instead.
|
|
42
|
+
* A value the pack does not list leaves the store with no engine, and
|
|
43
|
+
* the resource is still read.
|
|
44
44
|
*/
|
|
45
45
|
storageSystem: string | AttributeMeaning<string>;
|
|
46
|
-
/** How a caller reaches it, when that
|
|
46
|
+
/** How a caller reaches it, when that differs from the store's own name. */
|
|
47
47
|
transport?: string;
|
|
48
48
|
/**
|
|
49
|
-
* What the resource declares. The default, `"container"`, means
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
49
|
+
* What the resource declares. The default, `"container"`, means code
|
|
50
|
+
* addresses the resource directly: code passes a bucket's name to
|
|
51
|
+
* `bucket()`, so the declared name matches the accessed one. `"store"`
|
|
52
|
+
* means the resource only declares that the store exists. Code splits
|
|
53
|
+
* a Redis cluster into key namespaces that no attribute of the cluster
|
|
54
|
+
* lists, so the summary gets no container name and claims no access.
|
|
55
|
+
* A match on the cluster's own name would be a coincidence between a
|
|
56
|
+
* deployment name and a key prefix.
|
|
57
57
|
*/
|
|
58
58
|
declares?: "container" | "store";
|
|
59
|
-
/** The attribute that
|
|
59
|
+
/** The attribute that contains the resource's deployed name. */
|
|
60
60
|
nameAttribute?: string;
|
|
61
61
|
/**
|
|
62
|
-
* The attribute
|
|
62
|
+
* The attribute that contains the namespace a container belongs to. A
|
|
63
63
|
* BigQuery table is always addressed through its dataset, so two
|
|
64
64
|
* tables called `orders` in two datasets are two containers.
|
|
65
65
|
*/
|
|
66
66
|
scopeAttribute?: string;
|
|
67
67
|
/** Whether the fields it declares are every field an item has. */
|
|
68
68
|
fieldSet: "exhaustive" | "partial" | "none";
|
|
69
|
-
/** The attributes that
|
|
69
|
+
/** The attributes that list an item's key fields, in key order. */
|
|
70
70
|
identifies?: string[];
|
|
71
|
-
/** Blocks that declare another
|
|
71
|
+
/** Blocks that declare another access path, each keyed on its own fields. */
|
|
72
72
|
accessPathBlocks?: string[];
|
|
73
73
|
/** The block that gives each field a type, and its two attributes. */
|
|
74
74
|
fieldTypes?: {
|
|
@@ -77,35 +77,35 @@ interface StorageResource {
|
|
|
77
77
|
typeAttribute: string;
|
|
78
78
|
};
|
|
79
79
|
/**
|
|
80
|
-
* An attribute
|
|
81
|
-
* schema written this way
|
|
82
|
-
* makes the contract exhaustive
|
|
80
|
+
* An attribute that lists every field as JSON, one object per field. A
|
|
81
|
+
* schema written this way lists every field the item has, so reading
|
|
82
|
+
* one makes the contract exhaustive regardless of `fieldSet`.
|
|
83
83
|
*/
|
|
84
84
|
fieldsFromJson?: {
|
|
85
85
|
/** The attribute whose value is a JSON list of field objects. */
|
|
86
86
|
attribute: string;
|
|
87
|
-
/** The key
|
|
87
|
+
/** The key in each entry that contains the field's name. */
|
|
88
88
|
nameKey: string;
|
|
89
|
-
/** The key
|
|
89
|
+
/** The key in each entry that contains the field's type. */
|
|
90
90
|
typeKey?: string;
|
|
91
|
-
/** The key that
|
|
91
|
+
/** The key that marks a field as always set, and the values that do. */
|
|
92
92
|
requires?: {
|
|
93
93
|
key: string;
|
|
94
94
|
values: string[];
|
|
95
95
|
};
|
|
96
96
|
};
|
|
97
97
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
98
|
+
* Which fields an access path can return. A DynamoDB index copies only
|
|
99
|
+
* part of an item, and a read of any other attribute returns nothing
|
|
100
|
+
* without an error, so the copied attributes are the index's complete
|
|
101
|
+
* field list.
|
|
102
102
|
*/
|
|
103
103
|
serves?: {
|
|
104
|
-
/** The attribute that
|
|
104
|
+
/** The attribute that contains the projection kind. */
|
|
105
105
|
kindAttribute: string;
|
|
106
|
-
/** The attribute listing
|
|
106
|
+
/** The attribute listing the copied fields, for a kind that lists them. */
|
|
107
107
|
fieldsAttribute: string;
|
|
108
|
-
/** The
|
|
108
|
+
/** The `kindAttribute` value that means the whole item is copied. */
|
|
109
109
|
everything: string;
|
|
110
110
|
};
|
|
111
111
|
}
|
|
@@ -116,46 +116,45 @@ interface MessageBusResource {
|
|
|
116
116
|
nameAttribute?: string;
|
|
117
117
|
}
|
|
118
118
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* its namespace and its name together, so
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* the summary
|
|
119
|
+
* The pattern of the deployed metric type, where each `{...}` is
|
|
120
|
+
* replaced by the value at that attribute path. A CloudWatch metric is
|
|
121
|
+
* identified by its namespace and its name together, so its template
|
|
122
|
+
* gives both: `"{metric_transformation.namespace}/{metric_transformation.name}"`.
|
|
123
|
+
* When the resource leaves one of those attributes unset, the whole
|
|
124
|
+
* identity is unknown, so the summary does not pair on a partial name.
|
|
125
125
|
*/
|
|
126
126
|
type MetricTypeTemplate = string;
|
|
127
127
|
/** A named series of measurements a resource declares. */
|
|
128
128
|
interface MetricResource {
|
|
129
129
|
kind: "metric";
|
|
130
|
-
/**
|
|
130
|
+
/** The metrics system, such as `cloud-monitoring`. */
|
|
131
131
|
metricSystem: string;
|
|
132
|
-
/** How the
|
|
132
|
+
/** How the identity both sides write is built from the resource. */
|
|
133
133
|
metricTypeTemplate: MetricTypeTemplate;
|
|
134
|
-
/**
|
|
134
|
+
/** The attribute that decides whether a measurement is a number or a histogram. */
|
|
135
135
|
values?: AttributeMeaning<MetricValueShape>;
|
|
136
|
-
/**
|
|
136
|
+
/** The attribute that decides what one measurement covers. */
|
|
137
137
|
accumulates?: AttributeMeaning<MetricAccumulation>;
|
|
138
138
|
}
|
|
139
139
|
/**
|
|
140
|
-
*
|
|
141
|
-
* writes the namespace and the name
|
|
142
|
-
*
|
|
143
|
-
* inside it.
|
|
140
|
+
* Where a reading gives the metric it is about. A CloudWatch alarm
|
|
141
|
+
* writes the namespace and the name as attributes. A Cloud Monitoring
|
|
142
|
+
* condition writes a query, and the metric is one value inside it.
|
|
144
143
|
*/
|
|
145
144
|
type MetricIdentity = {
|
|
146
145
|
from: "attributes";
|
|
147
146
|
template: MetricTypeTemplate;
|
|
148
147
|
} | {
|
|
149
148
|
from: "query";
|
|
150
|
-
/** The attribute
|
|
149
|
+
/** The attribute that contains the query. */
|
|
151
150
|
attribute: string;
|
|
152
151
|
/** The key inside that query whose value is the metric's type. */
|
|
153
152
|
key: string;
|
|
154
153
|
};
|
|
155
154
|
/**
|
|
156
155
|
* A resource that reads a metric another resource declares. One
|
|
157
|
-
* resource
|
|
158
|
-
*
|
|
156
|
+
* resource often contains several readings, each in its own block and
|
|
157
|
+
* about its own metric, so each becomes its own boundary.
|
|
159
158
|
*/
|
|
160
159
|
interface MetricReadingResource {
|
|
161
160
|
kind: "metric-reading";
|
|
@@ -165,24 +164,24 @@ interface MetricReadingResource {
|
|
|
165
164
|
* list means the resource is itself one reading.
|
|
166
165
|
*/
|
|
167
166
|
readingBlocks: string[];
|
|
168
|
-
/**
|
|
167
|
+
/** Where the reading gives the metric it is about. */
|
|
169
168
|
identifies: MetricIdentity;
|
|
170
169
|
/**
|
|
171
|
-
*
|
|
172
|
-
* against a value of this shape. A condition
|
|
173
|
-
*
|
|
170
|
+
* An attribute whose presence means the reading compares the series
|
|
171
|
+
* against a value of this shape. A condition that sets a threshold
|
|
172
|
+
* compares against a number, whatever the number is.
|
|
174
173
|
*/
|
|
175
174
|
comparesTo?: {
|
|
176
175
|
attribute: string;
|
|
177
176
|
whenSet: MetricValueShape;
|
|
178
177
|
};
|
|
179
|
-
/**
|
|
178
|
+
/** The attribute that decides what the reading reduces each window to. */
|
|
180
179
|
reducesTo?: AttributeMeaning<MetricValueShape>;
|
|
181
180
|
}
|
|
182
181
|
/**
|
|
183
|
-
* Where a provider writes one process's environment
|
|
184
|
-
*
|
|
185
|
-
* variable
|
|
182
|
+
* Where a provider writes one process's environment: either a map
|
|
183
|
+
* attribute keyed by variable name, or repeated entries that each give
|
|
184
|
+
* one variable's name and its source, a literal or a secret.
|
|
186
185
|
*/
|
|
187
186
|
type EnvDeclaration = {
|
|
188
187
|
style: "map";
|
|
@@ -191,81 +190,80 @@ type EnvDeclaration = {
|
|
|
191
190
|
style: "entries";
|
|
192
191
|
/** The block one entry is written as, as a dotted path. */
|
|
193
192
|
block: string;
|
|
194
|
-
/** The attribute
|
|
193
|
+
/** The attribute that contains the variable's name. */
|
|
195
194
|
nameAttribute: string;
|
|
196
|
-
/** The attribute
|
|
195
|
+
/** The attribute that contains the value, for an entry that writes one. */
|
|
197
196
|
valueAttribute?: string;
|
|
198
|
-
/** The attribute
|
|
197
|
+
/** The attribute that contains the secret supplying the value. */
|
|
199
198
|
secretAttribute?: string;
|
|
200
199
|
};
|
|
201
200
|
/**
|
|
202
|
-
* How a platform
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
* its own, which says nothing about which file it is in.
|
|
201
|
+
* How a platform writes the handler it calls. `"module.export"` is a
|
|
202
|
+
* module path and an exported name joined at the last dot, as Lambda
|
|
203
|
+
* takes it. `"name"` is an exported name alone, with no file.
|
|
206
204
|
*/
|
|
207
205
|
type HandlerSpelling = "module.export" | "name";
|
|
208
|
-
/**
|
|
206
|
+
/** Where the provider records the code one process runs. */
|
|
209
207
|
interface DeployableCode {
|
|
210
|
-
/** The attribute that
|
|
208
|
+
/** The attribute that contains the function the platform calls. */
|
|
211
209
|
handler?: {
|
|
212
210
|
attribute: string;
|
|
213
211
|
spelling: HandlerSpelling;
|
|
214
212
|
};
|
|
215
|
-
/** The attribute that
|
|
213
|
+
/** The attribute that contains the image a container runs. */
|
|
216
214
|
imageAttribute?: string;
|
|
217
215
|
}
|
|
218
216
|
/** A resource that deploys several processes, each with its own environment. */
|
|
219
217
|
interface DeployableContainers {
|
|
220
218
|
/** The blocks one container is written inside, outermost first. */
|
|
221
219
|
blocks: string[];
|
|
222
|
-
/** The attribute inside a container that
|
|
220
|
+
/** The attribute inside a container that contains its name. */
|
|
223
221
|
nameAttribute?: string;
|
|
224
222
|
}
|
|
225
223
|
/**
|
|
226
|
-
* Something that gets deployed and runs
|
|
227
|
-
* job.
|
|
228
|
-
*
|
|
229
|
-
*
|
|
224
|
+
* Something that gets deployed and runs, such as a function, a
|
|
225
|
+
* container or a job. It declares the environment the process starts
|
|
226
|
+
* with, so suss can report code that reads a variable the deployment
|
|
227
|
+
* never sets.
|
|
230
228
|
*
|
|
231
|
-
* The unit is the resource's label
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
229
|
+
* The unit is keyed by the resource's label, and the deployed name is
|
|
230
|
+
* not used. The rest of the configuration refers to the resource by its
|
|
231
|
+
* label, as CloudFormation does with a logical id, and a variable that
|
|
232
|
+
* points at the resource resolves to it.
|
|
235
233
|
*/
|
|
236
234
|
interface DeployableResource {
|
|
237
235
|
kind: "deployable";
|
|
238
|
-
/**
|
|
236
|
+
/** What runs it, as a deployable unit's `deploymentTarget`. */
|
|
239
237
|
deploymentTarget: DeployableUnit["deploymentTarget"];
|
|
240
238
|
/** Where each process is written, for a resource that deploys several. */
|
|
241
239
|
containers?: DeployableContainers;
|
|
242
240
|
/** Where the environment is written, inside the process. */
|
|
243
241
|
env?: EnvDeclaration[];
|
|
244
|
-
/**
|
|
242
|
+
/** Where the provider records the code the process runs. */
|
|
245
243
|
code?: DeployableCode;
|
|
246
|
-
/** The attribute
|
|
244
|
+
/** The attribute that contains the language runtime, copied as written. */
|
|
247
245
|
runtimeAttribute?: string;
|
|
248
246
|
/**
|
|
249
|
-
*
|
|
250
|
-
* that
|
|
251
|
-
*
|
|
252
|
-
*
|
|
247
|
+
* Variables the platform sets regardless of the configuration. An
|
|
248
|
+
* entry that leaves this out gets the list for its deployment target,
|
|
249
|
+
* which every other reader of that target uses. A product with its own
|
|
250
|
+
* list, such as Cloud Run, sets it here.
|
|
253
251
|
*/
|
|
254
252
|
platformEnvVars?: readonly string[];
|
|
255
253
|
}
|
|
256
254
|
type TerraformResource = StorageResource | MessageBusResource | MetricResource | MetricReadingResource | DeployableResource;
|
|
257
255
|
/** One resource type, as one version range of one provider declares it. */
|
|
258
256
|
interface TerraformResourcePattern {
|
|
259
|
-
/** The resource type,
|
|
257
|
+
/** The resource type, as the provider writes it. */
|
|
260
258
|
resource: string;
|
|
261
259
|
/**
|
|
262
260
|
* An attribute that decides whether the entry describes the resource
|
|
263
|
-
* at all. A Firestore database in Datastore mode
|
|
264
|
-
* API, so
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
* `whenUnset`
|
|
268
|
-
*
|
|
261
|
+
* at all. A Firestore database in Datastore mode uses a different
|
|
262
|
+
* API, so the Firestore entry does not describe it. A value outside
|
|
263
|
+
* `equals`, or one built at deploy time, means the resource is
|
|
264
|
+
* skipped, so it is never read as something it may not be.
|
|
265
|
+
* `whenUnset` gives what an absent attribute means; by default the
|
|
266
|
+
* resource is skipped.
|
|
269
267
|
*/
|
|
270
268
|
appliesWhen?: {
|
|
271
269
|
attribute: string;
|
|
@@ -277,7 +275,7 @@ interface TerraformResourcePattern {
|
|
|
277
275
|
* Which provider versions this describes, as a semver range. A
|
|
278
276
|
* configuration states its own constraint under `required_providers`,
|
|
279
277
|
* and an entry outside it is not read. A configuration that states no
|
|
280
|
-
* constraint is read by every entry, since nothing
|
|
278
|
+
* constraint is read by every entry, since nothing rules any out.
|
|
281
279
|
*/
|
|
282
280
|
providerVersions: string;
|
|
283
281
|
/** What the resource is, once read. */
|
|
@@ -297,16 +295,15 @@ interface TerraformPack {
|
|
|
297
295
|
/**
|
|
298
296
|
* A filter written as comparisons joined by AND and OR.
|
|
299
297
|
*
|
|
300
|
-
* Several cloud APIs take a selector as one string
|
|
298
|
+
* Several cloud APIs take a selector as one string such as
|
|
301
299
|
* `metric.type = "x" AND resource.type = "y"`, with quoted or bare
|
|
302
300
|
* values, parentheses, NOT, and terms written next to each other for
|
|
303
301
|
* AND. A resource that reads what another resource declares usually
|
|
304
|
-
*
|
|
305
|
-
* this reader finds out what a resource refers to.
|
|
302
|
+
* identifies it inside such a string, so the reader parses the string.
|
|
306
303
|
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
304
|
+
* The pack gives the key to look for, and this file contains only the
|
|
305
|
+
* grammar. It returns a tree, because OR and NOT change what a term
|
|
306
|
+
* means and only the caller can decide how to treat that.
|
|
310
307
|
*/
|
|
311
308
|
/** One comparison, as the filter writes it. */
|
|
312
309
|
interface FilterTerm {
|
|
@@ -322,7 +319,7 @@ interface FilterTerm {
|
|
|
322
319
|
/** The right side, with the quotes taken off a quoted value. */
|
|
323
320
|
value: string;
|
|
324
321
|
}
|
|
325
|
-
/** A call
|
|
322
|
+
/** A function call in place of a comparison, with its arguments. */
|
|
326
323
|
interface FilterCall {
|
|
327
324
|
type: "call";
|
|
328
325
|
name: string;
|
|
@@ -336,7 +333,7 @@ type FilterQuery = FilterTerm | FilterCall | {
|
|
|
336
333
|
type: "negation";
|
|
337
334
|
operand: FilterQuery;
|
|
338
335
|
};
|
|
339
|
-
/** A
|
|
336
|
+
/** A parse failure includes the reason, so it is never mistaken for an empty filter. */
|
|
340
337
|
type FilterParse = {
|
|
341
338
|
ok: true;
|
|
342
339
|
query: FilterQuery;
|
|
@@ -348,63 +345,61 @@ type FilterParse = {
|
|
|
348
345
|
declare function filterValuesFor(query: FilterQuery, key: string): string[];
|
|
349
346
|
/** Every comparison in the tree, in the order the filter writes them. */
|
|
350
347
|
declare function filterTerms(query: FilterQuery): FilterTerm[];
|
|
351
|
-
/** Every call in the tree,
|
|
348
|
+
/** Every call in the tree, in the order the filter writes them. */
|
|
352
349
|
declare function filterCalls(query: FilterQuery): FilterCall[];
|
|
353
|
-
/**
|
|
350
|
+
/** Parses one filter string, or returns what stopped the parse. */
|
|
354
351
|
declare function parseFilterQuery(source: string): FilterParse;
|
|
355
352
|
|
|
356
353
|
/**
|
|
357
354
|
* An attribute whose value is JSON.
|
|
358
355
|
*
|
|
359
|
-
* Several providers take a structure as a string
|
|
360
|
-
* a BigQuery table's schema
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
356
|
+
* Several providers take a structure as a JSON string instead of as
|
|
357
|
+
* blocks, such as a BigQuery table's schema or an ECS task's container
|
|
358
|
+
* definitions. An author can write one as a literal string, usually a
|
|
359
|
+
* heredoc, or as `jsonencode` over an HCL value, and the parser returns
|
|
360
|
+
* the two very differently. This module turns both into the value the
|
|
361
|
+
* provider receives.
|
|
365
362
|
*
|
|
366
|
-
* A
|
|
367
|
-
*
|
|
368
|
-
* nothing rather than guessing.
|
|
363
|
+
* A value that a file or a variable supplies is not written in the
|
|
364
|
+
* configuration, so it comes back as null and the caller records nothing.
|
|
369
365
|
*/
|
|
370
366
|
/**
|
|
371
|
-
* The value an attribute
|
|
372
|
-
* missing or
|
|
367
|
+
* The value an attribute writes as JSON, or null when the attribute is
|
|
368
|
+
* missing or its value is known only at deploy time.
|
|
373
369
|
*/
|
|
374
370
|
declare function jsonAttributeValue(value: unknown): unknown;
|
|
375
371
|
|
|
376
372
|
/**
|
|
377
373
|
* The boundaries a Terraform configuration declares.
|
|
378
374
|
*
|
|
379
|
-
* This reads HCL and matches
|
|
380
|
-
*
|
|
375
|
+
* This module reads HCL and matches it against the packs a run loads. A
|
|
376
|
+
* pack describes what `aws_dynamodb_table` is, and nothing about any
|
|
381
377
|
* provider is written here.
|
|
382
378
|
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
* block that HCL states once or many times is read.
|
|
379
|
+
* Terraform interpolates the same way CloudFormation does, so
|
|
380
|
+
* `"${local.environment}-orders"` becomes the pattern
|
|
381
|
+
* `{local.environment}-orders` and pairs with code that builds the same
|
|
382
|
+
* name from its own variable. A reference to another resource in the
|
|
383
|
+
* same configuration resolves instead, since that resource writes the
|
|
384
|
+
* value. DESIGN.md covers references, module calls and repeated blocks.
|
|
390
385
|
*/
|
|
391
386
|
|
|
392
387
|
interface TerraformReadOptions {
|
|
393
|
-
/** The packs that
|
|
388
|
+
/** The packs that describe this configuration's resources. */
|
|
394
389
|
packs: TerraformPack[];
|
|
395
390
|
/**
|
|
396
|
-
* The directory each deployable unit's code is in, by the
|
|
397
|
-
* name
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
391
|
+
* The directory each deployable unit's code is in, by the unit's
|
|
392
|
+
* instance name. A configuration gives the handler that runs but never
|
|
393
|
+
* the directory the artifact was built from, so a container whose
|
|
394
|
+
* image is built elsewhere has no code until the caller gives its
|
|
395
|
+
* location.
|
|
401
396
|
*/
|
|
402
397
|
codeScopes?: Record<string, string>;
|
|
403
398
|
}
|
|
404
399
|
/** Every boundary one Terraform configuration declares. */
|
|
405
400
|
declare function terraformToSummaries(source: string, sourceFile: string, options: TerraformReadOptions): BehavioralSummary[];
|
|
406
401
|
/**
|
|
407
|
-
*
|
|
402
|
+
* Reads one `.tf` file, or every one directly inside a directory, and
|
|
408
403
|
* every local module the configuration calls.
|
|
409
404
|
*/
|
|
410
405
|
declare function terraformFileToSummaries(target: string, options: TerraformReadOptions): BehavioralSummary[];
|
package/dist/index.js
CHANGED
|
@@ -563,9 +563,9 @@ var TokenReader = class {
|
|
|
563
563
|
return this.readTerm();
|
|
564
564
|
}
|
|
565
565
|
/**
|
|
566
|
-
* A call
|
|
567
|
-
* burn-rate condition that way, and the call
|
|
568
|
-
* is about
|
|
566
|
+
* A call in place of a comparison. Cloud Monitoring writes an SLO
|
|
567
|
+
* burn-rate condition that way, and the call's arguments identify what
|
|
568
|
+
* the condition is about.
|
|
569
569
|
*/
|
|
570
570
|
readCall() {
|
|
571
571
|
const name = this.tokens[this.at];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suss/contract-terraform",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Read the storage a Terraform configuration declares and check it against the code that reads and writes those tables.",
|
|
6
6
|
"keywords": [
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
],
|
|
15
15
|
"license": "Apache-2.0",
|
|
16
16
|
"author": "Nimbus Cloud AI LLC",
|
|
17
|
-
"homepage": "https://
|
|
17
|
+
"homepage": "https://suss.sh",
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
20
|
"url": "git+https://github.com/nimbuscloud-ai/suss.git",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"typecheck": "tsc --noEmit"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@suss/behavioral-ir": "0.
|
|
50
|
+
"@suss/behavioral-ir": "0.34.0",
|
|
51
51
|
"hcl2-parser": "^1.0.3",
|
|
52
52
|
"semver": "^7.6.0"
|
|
53
53
|
},
|