@suss/terraform-aws 0.32.0 → 0.33.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 CHANGED
@@ -23,19 +23,23 @@ terraformFileToSummaries("infra/terraform/dynamodb", { packs: [awsTerraform()] }
23
23
  | --- | --- |
24
24
  | `aws_dynamodb_table` | a store, keyed by `hash_key` and `range_key`, with each `global_secondary_index` and `local_secondary_index` as its own way in |
25
25
  | `aws_s3_bucket` | a store whose objects have no fields to compare against |
26
- | `aws_elasticache_cluster` | a Redis store, when `engine` is `redis` or `valkey`; a Memcached cluster is skipped |
26
+ | `aws_elasticache_cluster` | a Redis store, when `engine` is `redis` or `valkey` or unset; any other engine leaves the store with no engine on it |
27
27
  | `aws_elasticache_replication_group` | a Redis store |
28
+ | `aws_rds_cluster` | a PostgreSQL or MySQL store, whichever its `engine` picks |
29
+ | `aws_db_instance` | the same, and `mariadb` counts as MySQL |
28
30
  | `aws_sqs_queue` | a channel |
29
31
  | `aws_sns_topic` | a channel |
30
32
  | `aws_cloudwatch_event_bus` | a channel |
33
+ | `aws_kinesis_stream` | a channel |
34
+ | `aws_kinesis_firehose_delivery_stream` | a channel |
35
+ | `aws_cloudwatch_log_metric_filter` | a metric, identified by its namespace and its name together |
36
+ | `aws_cloudwatch_metric_alarm` | one consumer of that metric, identified the same way |
37
+ | `aws_lambda_function` | a deployable, with the variables it sets and the handler it calls |
38
+ | `aws_ecs_task_definition` | a deployable per container, with that container's variables, secrets and image |
31
39
 
32
40
  Everything else a configuration declares, a security group, a subnet, an IAM policy, is how the deployment is wired rather than something a caller addresses, so nothing reads it.
33
41
 
34
- ## Why a Redis cluster pairs with nothing
35
-
36
- Code addresses Redis by key namespace: `@suss/framework-redis` reads `session:{id}` and records the container `session`. A cluster declares no namespaces; its `cluster_id` is a deployment name, and code never spells it. The two sides share no name either can state, so the entry says so: the summary declares the store exists, with no container name, and the storage check claims no access for it. A pair between a cluster called `session` and the `session` namespace would be a coincidence between a deployment name and a key prefix, so the entry refuses it rather than reporting it.
37
-
38
- What you get from the entry is visibility: `suss contract --from terraform` shows the cluster, and `suss check` reports it as a declared store nothing paired with. Namespace-level pairing stays between the code's own readers and writers of the same keys.
42
+ A database instance and a Redis cluster are the two that pair with nothing: code addresses tables and key namespaces inside them, and no attribute of either lists one, so each declares the store and claims no access. Kinesis pairs with nothing yet either, since no code pack records a send to a stream. [What each entry decided](./DESIGN.md) says why for each of them, and what an alarm and a metric filter share.
39
43
 
40
44
  ## Why every entry states a version range
41
45
 
@@ -53,6 +57,21 @@ terraform {
53
57
 
54
58
  The reader takes that pin and hands it to the pack. A configuration that pins nothing is read by every entry, since nothing said otherwise.
55
59
 
60
+ Every entry here was written against `>=4 <7`, from these pages of the provider's own documentation:
61
+
62
+ - [`aws_dynamodb_table`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table), [`aws_s3_bucket`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket)
63
+ - [`aws_elasticache_cluster`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_cluster), [`aws_elasticache_replication_group`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_replication_group)
64
+ - [`aws_rds_cluster`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster), [`aws_db_instance`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_instance)
65
+ - [`aws_sqs_queue`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue), [`aws_sns_topic`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic), [`aws_cloudwatch_event_bus`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_bus)
66
+ - [`aws_kinesis_stream`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kinesis_stream), [`aws_kinesis_firehose_delivery_stream`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kinesis_firehose_delivery_stream)
67
+ - [`aws_cloudwatch_log_metric_filter`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_metric_filter), [`aws_cloudwatch_metric_alarm`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_metric_alarm)
68
+
56
69
  ## Where it fits in suss
57
70
 
58
- Depends on `@suss/contract-terraform` for the shape of an entry, and on nothing else. A pack for another provider, `google` or `cloudflare`, is the same file with different entries.
71
+ Depends on `@suss/contract-terraform` for the shape of an entry, and on `@suss/ir-core` for the variables each deployment target injects on its own. A pack for another provider, `google` or `cloudflare`, is the same file with different entries.
72
+
73
+ ## More
74
+
75
+ - [What each entry decided](./DESIGN.md)
76
+ - [Documentation](https://suss.sh/)
77
+ - [Source and issues](https://github.com/nimbuscloud-ai/suss)
package/dist/index.cjs CHANGED
@@ -25,6 +25,41 @@ __export(index_exports, {
25
25
  });
26
26
  module.exports = __toCommonJS(index_exports);
27
27
  var CURRENT = ">=4 <7";
28
+ var METRIC_SYSTEM = "cloudwatch";
29
+ var STATISTICS = {
30
+ SampleCount: "number",
31
+ Average: "number",
32
+ Sum: "number",
33
+ Minimum: "number",
34
+ Maximum: "number"
35
+ };
36
+ var SQL_ENGINES = {
37
+ attribute: "engine",
38
+ means: {
39
+ postgres: "postgresql",
40
+ "aurora-postgresql": "postgresql",
41
+ mysql: "mysql",
42
+ "aurora-mysql": "mysql",
43
+ mariadb: "mysql"
44
+ }
45
+ };
46
+ var CACHE_ENGINES = {
47
+ attribute: "engine",
48
+ means: { redis: "redis", valkey: "redis" },
49
+ whenUnset: "redis"
50
+ };
51
+ function sqlStore(resource) {
52
+ return {
53
+ resource,
54
+ providerVersions: CURRENT,
55
+ boundary: {
56
+ kind: "storage",
57
+ storageSystem: SQL_ENGINES,
58
+ declares: "store",
59
+ fieldSet: "none"
60
+ }
61
+ };
62
+ }
28
63
  function awsTerraform() {
29
64
  return {
30
65
  name: "terraform-aws",
@@ -74,17 +109,9 @@ function awsTerraform() {
74
109
  {
75
110
  resource: "aws_elasticache_cluster",
76
111
  providerVersions: CURRENT,
77
- // A cluster with no engine of its own joins a replication
78
- // group, which always runs one of the two Redis-protocol
79
- // engines, so an unset engine is read.
80
- appliesWhen: {
81
- attribute: "engine",
82
- equals: ["redis", "valkey"],
83
- whenUnset: "read"
84
- },
85
112
  boundary: {
86
113
  kind: "storage",
87
- storageSystem: "redis",
114
+ storageSystem: CACHE_ENGINES,
88
115
  // Code addresses key namespaces, which no attribute of a
89
116
  // cluster lists, so the cluster declares the store and
90
117
  // claims no access. The README says how the sides meet.
@@ -102,6 +129,8 @@ function awsTerraform() {
102
129
  fieldSet: "none"
103
130
  }
104
131
  },
132
+ sqlStore("aws_rds_cluster"),
133
+ sqlStore("aws_db_instance"),
105
134
  {
106
135
  resource: "aws_sqs_queue",
107
136
  providerVersions: CURRENT,
@@ -128,6 +157,98 @@ function awsTerraform() {
128
157
  messageBus: "eventbridge",
129
158
  nameAttribute: "name"
130
159
  }
160
+ },
161
+ {
162
+ resource: "aws_kinesis_stream",
163
+ providerVersions: CURRENT,
164
+ boundary: {
165
+ kind: "message-bus",
166
+ messageBus: "aws_kinesis",
167
+ nameAttribute: "name"
168
+ }
169
+ },
170
+ {
171
+ resource: "aws_kinesis_firehose_delivery_stream",
172
+ providerVersions: CURRENT,
173
+ boundary: {
174
+ kind: "message-bus",
175
+ messageBus: "aws_firehose",
176
+ nameAttribute: "name"
177
+ }
178
+ },
179
+ {
180
+ resource: "aws_cloudwatch_log_metric_filter",
181
+ providerVersions: CURRENT,
182
+ boundary: {
183
+ kind: "metric",
184
+ metricSystem: METRIC_SYSTEM,
185
+ // CloudWatch identifies a metric by its namespace and its
186
+ // name together, and an alarm spells both, so both are the
187
+ // identity the two sides share.
188
+ metricTypeTemplate: "{metric_transformation.namespace}/{metric_transformation.name}"
189
+ }
190
+ },
191
+ {
192
+ resource: "aws_cloudwatch_metric_alarm",
193
+ providerVersions: CURRENT,
194
+ boundary: {
195
+ kind: "metric-reading",
196
+ metricSystem: METRIC_SYSTEM,
197
+ // An alarm on a single metric is one reading, written in the
198
+ // resource itself rather than in a block.
199
+ readingBlocks: [],
200
+ identifies: {
201
+ from: "attributes",
202
+ template: "{namespace}/{metric_name}"
203
+ },
204
+ comparesTo: { attribute: "threshold", whenSet: "number" },
205
+ reducesTo: { attribute: "statistic", means: STATISTICS }
206
+ }
207
+ },
208
+ {
209
+ resource: "aws_lambda_function",
210
+ providerVersions: CURRENT,
211
+ boundary: {
212
+ kind: "deployable",
213
+ deploymentTarget: "lambda",
214
+ runtimeAttribute: "runtime",
215
+ env: [{ style: "map", attribute: "environment.variables" }],
216
+ // Lambda writes the module and the exported name into one
217
+ // string, split at the last dot.
218
+ code: {
219
+ handler: { attribute: "handler", spelling: "module.export" }
220
+ }
221
+ }
222
+ },
223
+ {
224
+ resource: "aws_ecs_task_definition",
225
+ providerVersions: CURRENT,
226
+ boundary: {
227
+ kind: "deployable",
228
+ deploymentTarget: "ecs-task",
229
+ // One task definition runs several containers, each starting
230
+ // with an environment of its own, and the JSON they are
231
+ // written in is read the same way a block would be.
232
+ containers: {
233
+ blocks: ["container_definitions"],
234
+ nameAttribute: "name"
235
+ },
236
+ env: [
237
+ {
238
+ style: "entries",
239
+ block: "environment",
240
+ nameAttribute: "name",
241
+ valueAttribute: "value"
242
+ },
243
+ {
244
+ style: "entries",
245
+ block: "secrets",
246
+ nameAttribute: "name",
247
+ secretAttribute: "valueFrom"
248
+ }
249
+ ],
250
+ code: { imageAttribute: "image" }
251
+ }
131
252
  }
132
253
  ]
133
254
  };
package/dist/index.js CHANGED
@@ -1,5 +1,40 @@
1
1
  // src/index.ts
2
2
  var CURRENT = ">=4 <7";
3
+ var METRIC_SYSTEM = "cloudwatch";
4
+ var STATISTICS = {
5
+ SampleCount: "number",
6
+ Average: "number",
7
+ Sum: "number",
8
+ Minimum: "number",
9
+ Maximum: "number"
10
+ };
11
+ var SQL_ENGINES = {
12
+ attribute: "engine",
13
+ means: {
14
+ postgres: "postgresql",
15
+ "aurora-postgresql": "postgresql",
16
+ mysql: "mysql",
17
+ "aurora-mysql": "mysql",
18
+ mariadb: "mysql"
19
+ }
20
+ };
21
+ var CACHE_ENGINES = {
22
+ attribute: "engine",
23
+ means: { redis: "redis", valkey: "redis" },
24
+ whenUnset: "redis"
25
+ };
26
+ function sqlStore(resource) {
27
+ return {
28
+ resource,
29
+ providerVersions: CURRENT,
30
+ boundary: {
31
+ kind: "storage",
32
+ storageSystem: SQL_ENGINES,
33
+ declares: "store",
34
+ fieldSet: "none"
35
+ }
36
+ };
37
+ }
3
38
  function awsTerraform() {
4
39
  return {
5
40
  name: "terraform-aws",
@@ -49,17 +84,9 @@ function awsTerraform() {
49
84
  {
50
85
  resource: "aws_elasticache_cluster",
51
86
  providerVersions: CURRENT,
52
- // A cluster with no engine of its own joins a replication
53
- // group, which always runs one of the two Redis-protocol
54
- // engines, so an unset engine is read.
55
- appliesWhen: {
56
- attribute: "engine",
57
- equals: ["redis", "valkey"],
58
- whenUnset: "read"
59
- },
60
87
  boundary: {
61
88
  kind: "storage",
62
- storageSystem: "redis",
89
+ storageSystem: CACHE_ENGINES,
63
90
  // Code addresses key namespaces, which no attribute of a
64
91
  // cluster lists, so the cluster declares the store and
65
92
  // claims no access. The README says how the sides meet.
@@ -77,6 +104,8 @@ function awsTerraform() {
77
104
  fieldSet: "none"
78
105
  }
79
106
  },
107
+ sqlStore("aws_rds_cluster"),
108
+ sqlStore("aws_db_instance"),
80
109
  {
81
110
  resource: "aws_sqs_queue",
82
111
  providerVersions: CURRENT,
@@ -103,6 +132,98 @@ function awsTerraform() {
103
132
  messageBus: "eventbridge",
104
133
  nameAttribute: "name"
105
134
  }
135
+ },
136
+ {
137
+ resource: "aws_kinesis_stream",
138
+ providerVersions: CURRENT,
139
+ boundary: {
140
+ kind: "message-bus",
141
+ messageBus: "aws_kinesis",
142
+ nameAttribute: "name"
143
+ }
144
+ },
145
+ {
146
+ resource: "aws_kinesis_firehose_delivery_stream",
147
+ providerVersions: CURRENT,
148
+ boundary: {
149
+ kind: "message-bus",
150
+ messageBus: "aws_firehose",
151
+ nameAttribute: "name"
152
+ }
153
+ },
154
+ {
155
+ resource: "aws_cloudwatch_log_metric_filter",
156
+ providerVersions: CURRENT,
157
+ boundary: {
158
+ kind: "metric",
159
+ metricSystem: METRIC_SYSTEM,
160
+ // CloudWatch identifies a metric by its namespace and its
161
+ // name together, and an alarm spells both, so both are the
162
+ // identity the two sides share.
163
+ metricTypeTemplate: "{metric_transformation.namespace}/{metric_transformation.name}"
164
+ }
165
+ },
166
+ {
167
+ resource: "aws_cloudwatch_metric_alarm",
168
+ providerVersions: CURRENT,
169
+ boundary: {
170
+ kind: "metric-reading",
171
+ metricSystem: METRIC_SYSTEM,
172
+ // An alarm on a single metric is one reading, written in the
173
+ // resource itself rather than in a block.
174
+ readingBlocks: [],
175
+ identifies: {
176
+ from: "attributes",
177
+ template: "{namespace}/{metric_name}"
178
+ },
179
+ comparesTo: { attribute: "threshold", whenSet: "number" },
180
+ reducesTo: { attribute: "statistic", means: STATISTICS }
181
+ }
182
+ },
183
+ {
184
+ resource: "aws_lambda_function",
185
+ providerVersions: CURRENT,
186
+ boundary: {
187
+ kind: "deployable",
188
+ deploymentTarget: "lambda",
189
+ runtimeAttribute: "runtime",
190
+ env: [{ style: "map", attribute: "environment.variables" }],
191
+ // Lambda writes the module and the exported name into one
192
+ // string, split at the last dot.
193
+ code: {
194
+ handler: { attribute: "handler", spelling: "module.export" }
195
+ }
196
+ }
197
+ },
198
+ {
199
+ resource: "aws_ecs_task_definition",
200
+ providerVersions: CURRENT,
201
+ boundary: {
202
+ kind: "deployable",
203
+ deploymentTarget: "ecs-task",
204
+ // One task definition runs several containers, each starting
205
+ // with an environment of its own, and the JSON they are
206
+ // written in is read the same way a block would be.
207
+ containers: {
208
+ blocks: ["container_definitions"],
209
+ nameAttribute: "name"
210
+ },
211
+ env: [
212
+ {
213
+ style: "entries",
214
+ block: "environment",
215
+ nameAttribute: "name",
216
+ valueAttribute: "value"
217
+ },
218
+ {
219
+ style: "entries",
220
+ block: "secrets",
221
+ nameAttribute: "name",
222
+ secretAttribute: "valueFrom"
223
+ }
224
+ ],
225
+ code: { imageAttribute: "image" }
226
+ }
106
227
  }
107
228
  ]
108
229
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/terraform-aws",
3
- "version": "0.32.0",
3
+ "version": "0.33.0",
4
4
  "type": "module",
5
5
  "description": "What the AWS Terraform provider declares, so suss can check your code against the infrastructure you deploy.",
6
6
  "keywords": [
@@ -46,7 +46,7 @@
46
46
  "typecheck": "tsc --noEmit"
47
47
  },
48
48
  "dependencies": {
49
- "@suss/contract-terraform": "0.32.0"
49
+ "@suss/contract-terraform": "0.33.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@suss/behavioral-ir": "*",