hecks 2.5.1 → 2.7.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.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/google_authentication.rb +1 -1
- data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
- data/lib/hecks/adapters/driven/prism.rb +1 -1
- data/lib/hecks/bench/cli.rb +74 -0
- data/lib/hecks/bench/environment.rb +100 -0
- data/lib/hecks/bench/postgres_probe.rb +41 -0
- data/lib/hecks/bench/report.rb +116 -0
- data/lib/hecks/bench/ruby_runner.rb +151 -0
- data/lib/hecks/bench/run.rb +55 -0
- data/lib/hecks/bench/rust_runner.rb +165 -0
- data/lib/hecks/bench/stats.rb +75 -0
- data/lib/hecks/bench/suite.rb +172 -0
- data/lib/hecks/bench/workload.rb +137 -0
- data/lib/hecks/bench.rb +28 -0
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +3 -3
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +1 -1
- data/lib/hecks/bluebook/dsl/world_builder.rb +42 -3
- data/lib/hecks/bluebook/hexagon.rb +15 -7
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +3 -1
- data/lib/hecks/bluebook/model_check/client_profile.rb +153 -0
- data/lib/hecks/bluebook/model_check.rb +21 -2
- data/lib/hecks/embryonaut_bluebook/lock.rb +72 -0
- data/lib/hecks/embryonaut_bluebook/shape.rb +47 -0
- data/lib/hecks/embryonaut_bluebook/vendor.rb +194 -0
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +88 -0
- data/lib/hecks/embryonaut_bluebook.rb +29 -3
- data/lib/hecks/forms/value_object_shape.rb +1 -1
- data/lib/hecks/framework/bluebook/console_settings.bluebook +3 -3
- data/lib/hecks/fuzzing/era_boundary.rb +1 -1
- data/lib/hecks/fuzzing/isolated_boot.rb +136 -69
- data/lib/hecks/language/world/world.bluebook +32 -2
- data/lib/hecks/mcp_stdio_guard.rb +160 -0
- data/lib/hecks/ports/key_vault.rb +1 -1
- data/lib/hecks/ports/persistence/binding_policy.rb +16 -5
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +2 -1
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +132 -0
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era.rb +7 -4
- data/lib/hecks/ports/persistence/postgres_dump/connection.rb +73 -0
- data/lib/hecks/ports/persistence/postgres_dump.rb +125 -0
- data/lib/hecks/ports/persistence/repository_factory.rb +2 -2
- data/lib/hecks/ports/persistence.rb +4 -0
- data/lib/hecks/projections/deploy/fargate/assembly.rb +193 -0
- data/lib/hecks/projections/deploy/fargate/cdn.rb +318 -0
- data/lib/hecks/projections/deploy/fargate/cdn_default.rb +87 -0
- data/lib/hecks/projections/deploy/fargate/check.rb +172 -0
- data/lib/hecks/projections/deploy/fargate/containers.rb +412 -0
- data/lib/hecks/projections/deploy/fargate/extras.rb +268 -0
- data/lib/hecks/projections/deploy/fargate/monitoring.rb +448 -0
- data/lib/hecks/projections/deploy/fargate/settings.rb +235 -0
- data/lib/hecks/projections/deploy/fargate/yaml.rb +116 -0
- data/lib/hecks/projections/deploy/fargate.rb +187 -138
- data/lib/hecks/projections/deploy/lambda.rb +5 -5
- data/lib/hecks/projections/deploy/preview/access_yaml.rb +145 -0
- data/lib/hecks/projections/deploy/preview/containers.rb +218 -0
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +446 -0
- data/lib/hecks/projections/deploy/preview/routing_yaml.rb +216 -0
- data/lib/hecks/projections/deploy/preview/script.rb +40 -0
- data/lib/hecks/projections/deploy/preview/settings.rb +140 -0
- data/lib/hecks/projections/deploy/preview/task_yaml.rb +215 -0
- data/lib/hecks/projections/deploy/preview/template.rb +247 -0
- data/lib/hecks/projections/deploy/preview/yaml_text.rb +48 -0
- data/lib/hecks/projections/deploy/preview.rb +103 -0
- data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +182 -0
- data/lib/hecks/projections/deploy/scripts/expected-era.header +17 -0
- data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +54 -0
- data/lib/hecks/projections/deploy/scripts/settings.rb +114 -0
- data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +232 -0
- data/lib/hecks/projections/deploy/scripts.rb +171 -0
- data/lib/hecks/projections/deploy/smoke/harness.js +366 -0
- data/lib/hecks/projections/deploy/smoke/workflow.yml.tmpl +56 -0
- data/lib/hecks/projections/deploy/smoke.rb +159 -0
- data/lib/hecks/projections/deploy/template_diff/comparer.rb +231 -0
- data/lib/hecks/projections/deploy/template_diff/loader.rb +105 -0
- data/lib/hecks/projections/deploy/template_diff.rb +173 -0
- data/lib/hecks/projections/glossary/sensitivity.rb +1 -1
- data/lib/hecks/projections.rb +2 -0
- data/lib/hecks/release/gem_pin/published_versions.rb +58 -0
- data/lib/hecks/release/gem_pin.rb +112 -0
- data/lib/hecks/runtime/command_rules/arithmetic.rb +2 -2
- data/lib/hecks/runtime/loader.rb +26 -2
- data/lib/hecks/runtime/registry/saga_persistence.rb +8 -2
- data/lib/hecks/runtime/registry/verification.rb +4 -2
- data/lib/hecks/runtime/registry/world_defaults.rb +106 -0
- data/lib/hecks/runtime/registry.rb +14 -9
- data/lib/hecks/runtime/tenant_check.rb +1 -1
- data/lib/hecks/vendoring/git_environment.rb +35 -0
- data/lib/hecks/vendoring/git_source.rb +102 -0
- data/lib/hecks/vendoring.rb +102 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks.rb +3 -1
- metadata +59 -2
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
require_relative "../../projector"
|
|
2
2
|
require_relative "shared"
|
|
3
|
+
require_relative "scripts"
|
|
4
|
+
require_relative "preview"
|
|
5
|
+
require_relative "fargate/settings"
|
|
6
|
+
require_relative "fargate/assembly"
|
|
3
7
|
|
|
4
8
|
module Hecks
|
|
5
9
|
module Projections
|
|
@@ -33,9 +37,8 @@ module Hecks
|
|
|
33
37
|
# never `sam deploy`, so there is no `samconfig.toml` here. A
|
|
34
38
|
# `Dockerfile` packages `rust/host`'s own compiled binary — built by
|
|
35
39
|
# the generated Makefile before `docker build` ever runs, the same
|
|
36
|
-
# "build outside the container, ship the artifact" shape
|
|
37
|
-
#
|
|
38
|
-
# Ruby binary.
|
|
40
|
+
# "build outside the container, ship the artifact" shape a
|
|
41
|
+
# tebako-pressed Ruby binary's Dockerfile already uses.
|
|
39
42
|
#
|
|
40
43
|
# ## What this assumes, and does not build
|
|
41
44
|
#
|
|
@@ -58,9 +61,56 @@ module Hecks
|
|
|
58
61
|
# is ARM64, and the Makefile cross-compiles with
|
|
59
62
|
# aarch64-unknown-linux-gnu (plus the GNU cross-linker on macOS).
|
|
60
63
|
# A Shared-mode ALB sits in OwningPublicSubnetAId/BId — the private
|
|
61
|
-
# pair is unreachable from the internet (found live
|
|
62
|
-
#
|
|
64
|
+
# pair is unreachable from the internet (found live on a
|
|
65
|
+
# shared-database stack). SessionSecret is always minted: HECKS_SERVE_MODE
|
|
63
66
|
# always runs web.rs, which panics on an empty SESSION_SECRET.
|
|
67
|
+
#
|
|
68
|
+
# ## Settings beyond the baseline
|
|
69
|
+
#
|
|
70
|
+
# `region`, `cpu`, `memory`, `port`, `database`, `web`, `stack_name`, `stack_prefix`,
|
|
71
|
+
# `schema`, `desired_count`, `owner` and `owner_stack` are the baseline settings. A world
|
|
72
|
+
# that sets none of the keys below renders the single-container stack described above,
|
|
73
|
+
# unchanged. Each key is optional, checked before anything is rendered, and refused with its
|
|
74
|
+
# own name when malformed; the module named beside it holds the full shape.
|
|
75
|
+
#
|
|
76
|
+
# - `domain_container`: renames the domain container, its repository, image-tag parameter
|
|
77
|
+
# and health path (`Containers`).
|
|
78
|
+
# - `containers`: adds containers to the same task, each with a repository, port, health
|
|
79
|
+
# path, environment and secrets (`Containers`).
|
|
80
|
+
# - `default_container`: the container the listener forwards to when no route matches
|
|
81
|
+
# (`Containers`).
|
|
82
|
+
# - `routes`: sends path patterns to a container through listener rules, with priorities
|
|
83
|
+
# (`Containers`).
|
|
84
|
+
# - `logical_ids`: fixes the logical id of each resource so an existing stack's ids are
|
|
85
|
+
# reproduced (`Settings`).
|
|
86
|
+
# - `names`: fixes the AWS name of the cluster, log group, service, load balancer, task
|
|
87
|
+
# family, the load balancer's security group description, and the task role's database
|
|
88
|
+
# policy name (`Settings`).
|
|
89
|
+
# - `cdn`: aliases, certificate, behaviors and cache policies, an origin secret header,
|
|
90
|
+
# S3 origins and retention (`Cdn`).
|
|
91
|
+
# - `alerts`: a `SNS` topic, an email subscription, alarms and a synthetic check
|
|
92
|
+
# (`Monitoring`).
|
|
93
|
+
# - `buckets`, `generated_secrets` and `session_secret`: extra S3 buckets, generated
|
|
94
|
+
# secrets and the session secret's name (`Extras`).
|
|
95
|
+
# - `task_policies` and `execution_policies`: extra IAM policies on the task role and the
|
|
96
|
+
# execution role (`Extras`).
|
|
97
|
+
# - `parameters` and `outputs`: extra template parameters and outputs (`Extras`).
|
|
98
|
+
# - `execute_command`: enables `aws ecs execute-command` and grants the task role its SSM
|
|
99
|
+
# actions (`Settings`).
|
|
100
|
+
# - `health_check_grace_period`: seconds `ECS` ignores failing health checks after a task
|
|
101
|
+
# starts (`Settings`).
|
|
102
|
+
# - `deregistration_delay`: seconds a target group drains a target (`Settings`).
|
|
103
|
+
# - `desired_count_parameter`: makes the desired count a template parameter (`Settings`).
|
|
104
|
+
# - `domain_env`: extra environment for the domain container; a name the generator sets
|
|
105
|
+
# is replaced, and a nil value removes it (`Settings`).
|
|
106
|
+
# - `execution_role_database_grant`: false leaves the database secret out of the execution
|
|
107
|
+
# role (`Settings`).
|
|
108
|
+
# - `install_dir` and `build_context_dir`: where the Dockerfile installs the host and its
|
|
109
|
+
# sidecars, and where it copies them from (`Settings`).
|
|
110
|
+
# - `db_name_parameter`: for `database "Shared"`, a parameter naming the shared database
|
|
111
|
+
# (`Settings`).
|
|
112
|
+
#
|
|
113
|
+
# Per-branch preview stacks and the deploy and smoke scripts are not generated here.
|
|
64
114
|
module Fargate
|
|
65
115
|
extend Projector::Target
|
|
66
116
|
|
|
@@ -183,7 +233,22 @@ module Hecks
|
|
|
183
233
|
stack_name = "#{stack_prefix}-#{infra_name}"
|
|
184
234
|
desired_count = deploy_settings.fetch(:desired_count, 1)
|
|
185
235
|
|
|
186
|
-
|
|
236
|
+
# Every optional setting, checked; a world that sets none resolves to the
|
|
237
|
+
# derived ids and names the generator has always used.
|
|
238
|
+
begin
|
|
239
|
+
plan = Settings.resolve(
|
|
240
|
+
deploy_settings,
|
|
241
|
+
infra_name: infra_name, logical_id: logical_id, db_id: "#{logical_id.sub(/Service\z/, '')}Db",
|
|
242
|
+
stack_name: stack_name, port: port, shared: shared
|
|
243
|
+
)
|
|
244
|
+
rescue ArgumentError => e
|
|
245
|
+
raise ArgumentError, "#{world_file}'s deployed_to(\"AwsFargate\"): #{e.message}"
|
|
246
|
+
end
|
|
247
|
+
ids = plan.ids
|
|
248
|
+
names = plan.names
|
|
249
|
+
layout = plan.layout
|
|
250
|
+
|
|
251
|
+
db_id = ids[:database_prefix]
|
|
187
252
|
db_ref_id = aurora ? "#{db_id}Cluster" : db_id
|
|
188
253
|
secret_sub = aurora ? "#{db_id}Secret" : "#{db_ref_id}.MasterUserSecret.SecretArn"
|
|
189
254
|
secret_intrinsic = aurora ? "!Ref #{db_id}Secret" : "!GetAtt #{db_ref_id}.MasterUserSecret.SecretArn"
|
|
@@ -208,20 +273,31 @@ module Hecks
|
|
|
208
273
|
# `Lambda.call`'s own Shared-mode `VpcConfig`); the `ECS` task and
|
|
209
274
|
# the `ALB`-ingress rule both reach through the borrowed owner's
|
|
210
275
|
# own security group instead.
|
|
211
|
-
compute_security_group_ref = shared ? "!Ref OwningSecurityGroupId" : "!Ref #{
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
276
|
+
compute_security_group_ref = shared ? "!Ref OwningSecurityGroupId" : "!Ref #{ids[:compute_prefix]}SecurityGroup"
|
|
277
|
+
|
|
278
|
+
# `logical_id` stays the derived name in prose (descriptions, comments);
|
|
279
|
+
# the ids below are what the resources are actually declared with, which
|
|
280
|
+
# the world's `logical_ids` setting may replace.
|
|
281
|
+
service_id = ids[:service]
|
|
282
|
+
ecr_repository_id = ids[:ecr_repository]
|
|
283
|
+
cluster_id = ids[:cluster]
|
|
284
|
+
task_definition_id = ids[:task_definition]
|
|
285
|
+
execution_role_id = ids[:execution_role]
|
|
286
|
+
task_role_id = ids[:task_role]
|
|
287
|
+
log_group_id = ids[:log_group]
|
|
288
|
+
target_group_id = ids[:target_group]
|
|
289
|
+
alb_id = ids[:alb]
|
|
290
|
+
alb_sg_id = ids[:alb_security_group]
|
|
291
|
+
listener_id = ids[:listener]
|
|
292
|
+
distribution_id = ids[:distribution]
|
|
293
|
+
session_secret_id = ids[:session_secret]
|
|
294
|
+
domain_container = layout.domain
|
|
295
|
+
alb_sg_description = names[:alb_security_group_description] ||
|
|
296
|
+
"#{alb_sg_id} - HTTP ingress from CloudFront only, forwarded to #{logical_id} only"
|
|
297
|
+
port_range = Containers.port_range(layout)
|
|
298
|
+
vpc_ref = shared ? "!Ref OwningVpcId" : "!Ref #{db_id}Vpc"
|
|
299
|
+
sidecar_dir = plan.install_dir
|
|
300
|
+
db_name_ref = plan.db_name_parameter ? "!Ref #{plan.db_name_parameter}" : nil
|
|
225
301
|
|
|
226
302
|
stack_outputs = Shared.stack_outputs(
|
|
227
303
|
shared: shared, db_id: db_id, db_ref_id: db_ref_id, secret_intrinsic: secret_intrinsic,
|
|
@@ -231,7 +307,7 @@ module Hecks
|
|
|
231
307
|
Shared.check_bastion_parameters!(bastion_parameters, stack_outputs)
|
|
232
308
|
|
|
233
309
|
always_params_yaml = <<~ALWAYSPARAMS.rstrip
|
|
234
|
-
|
|
310
|
+
#{domain_container.tag_parameter}:
|
|
235
311
|
Type: String
|
|
236
312
|
Default: latest
|
|
237
313
|
Description: ECR image tag this task pulls — never hardcode latest in the TaskDefinition; a first deploy and a later rollout share this one parameter.
|
|
@@ -277,7 +353,7 @@ module Hecks
|
|
|
277
353
|
OwningSubnetBId:
|
|
278
354
|
Type: AWS::EC2::Subnet::Id
|
|
279
355
|
# PUBLIC subnets, NOT OwningSubnetAId/OwningSubnetBId above — a
|
|
280
|
-
# real, live deploy (
|
|
356
|
+
# real, live deploy (a shared-database stack) found the ALB placed in
|
|
281
357
|
# the private pair creates successfully and reports its target
|
|
282
358
|
# health as healthy (health checks run from inside the VPC), yet
|
|
283
359
|
# is completely unreachable from outside it:
|
|
@@ -299,7 +375,8 @@ module Hecks
|
|
|
299
375
|
OwningDatabaseSecretArn:
|
|
300
376
|
Type: String
|
|
301
377
|
SHAREDPARAMS
|
|
302
|
-
|
|
378
|
+
extra_params_yaml = Settings.parameters_yaml(plan, default_count: desired_count, shared_db_name: owner_db_name).rstrip
|
|
379
|
+
parameters_yaml = [always_params_yaml, oauth_params_yaml, owning_params_yaml, extra_params_yaml].reject(&:empty?).join("\n")
|
|
303
380
|
|
|
304
381
|
template_yaml = <<~YAML
|
|
305
382
|
# GENERATED by bin/project_deploy #{domain} — re-run it to refresh
|
|
@@ -320,15 +397,15 @@ module Hecks
|
|
|
320
397
|
Resources:
|
|
321
398
|
#{shared ? "" : Shared.vpc_and_database_yaml(
|
|
322
399
|
db_id: db_id, db_name: db_name, infra_name: infra_name, aurora: aurora,
|
|
323
|
-
google_oauth_present: network_needs_internet, compute_logical_id:
|
|
400
|
+
google_oauth_present: network_needs_internet, compute_logical_id: ids[:compute_prefix],
|
|
324
401
|
compute_description: "#{logical_id} - inbound from #{alb_sg_id} only, egress rules attached separately below"
|
|
325
402
|
).each_line.with_index.map { |l, i| (i.zero? ? "" : " ") + l }.join.rstrip}
|
|
326
403
|
|
|
327
404
|
#{alb_sg_id}:
|
|
328
405
|
Type: AWS::EC2::SecurityGroup
|
|
329
406
|
Properties:
|
|
330
|
-
VpcId: #{
|
|
331
|
-
GroupDescription: #{
|
|
407
|
+
VpcId: #{vpc_ref}
|
|
408
|
+
GroupDescription: #{alb_sg_description}
|
|
332
409
|
SecurityGroupIngress:
|
|
333
410
|
# pl-3b927c52 — com.amazonaws.global.cloudfront.origin-
|
|
334
411
|
# facing, AWS's own global, account-agnostic managed
|
|
@@ -348,31 +425,31 @@ module Hecks
|
|
|
348
425
|
ToPort: 80
|
|
349
426
|
SourcePrefixListId: pl-3b927c52
|
|
350
427
|
|
|
351
|
-
#{
|
|
428
|
+
#{ids[:ingress_from_alb]}:
|
|
352
429
|
Type: AWS::EC2::SecurityGroupIngress
|
|
353
430
|
Properties:
|
|
354
431
|
GroupId: #{compute_security_group_ref}
|
|
355
432
|
IpProtocol: tcp
|
|
356
|
-
FromPort: #{
|
|
357
|
-
ToPort: #{
|
|
433
|
+
FromPort: #{port_range.min}
|
|
434
|
+
ToPort: #{port_range.max}
|
|
358
435
|
SourceSecurityGroupId: !Ref #{alb_sg_id}
|
|
359
436
|
|
|
360
437
|
#{ecr_repository_id}:
|
|
361
438
|
Type: AWS::ECR::Repository
|
|
362
439
|
Properties:
|
|
363
|
-
RepositoryName: #{
|
|
440
|
+
RepositoryName: #{domain_container.repository_name}
|
|
364
441
|
ImageScanningConfiguration:
|
|
365
442
|
ScanOnPush: true
|
|
366
443
|
|
|
367
444
|
#{cluster_id}:
|
|
368
445
|
Type: AWS::ECS::Cluster
|
|
369
446
|
Properties:
|
|
370
|
-
ClusterName: #{
|
|
447
|
+
ClusterName: #{names[:cluster]}
|
|
371
448
|
|
|
372
449
|
#{log_group_id}:
|
|
373
450
|
Type: AWS::Logs::LogGroup
|
|
374
451
|
Properties:
|
|
375
|
-
LogGroupName:
|
|
452
|
+
LogGroupName: #{names[:log_group]}
|
|
376
453
|
RetentionInDays: 30
|
|
377
454
|
|
|
378
455
|
# Always minted — HECKS_SERVE_MODE always runs web.rs, which
|
|
@@ -382,6 +459,7 @@ module Hecks
|
|
|
382
459
|
#{session_secret_id}:
|
|
383
460
|
Type: AWS::SecretsManager::Secret
|
|
384
461
|
Properties:
|
|
462
|
+
# TMPL:session_secret_properties
|
|
385
463
|
GenerateSecretString:
|
|
386
464
|
SecretStringTemplate: '{}'
|
|
387
465
|
GenerateStringKey: session_secret
|
|
@@ -406,13 +484,8 @@ module Hecks
|
|
|
406
484
|
ManagedPolicyArns:
|
|
407
485
|
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
|
|
408
486
|
Policies:
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
Version: '2012-10-17'
|
|
412
|
-
Statement:
|
|
413
|
-
- Effect: Allow
|
|
414
|
-
Action: secretsmanager:GetSecretValue
|
|
415
|
-
Resource: !Sub "${#{db_secret_ref}}"
|
|
487
|
+
# TMPL:execution_database_grant
|
|
488
|
+
# TMPL:extra_execution_policies
|
|
416
489
|
|
|
417
490
|
# The container's OWN runtime permissions — rust/host fetches
|
|
418
491
|
# DB_SECRET_ARN itself, over the AWS SDK, the same "never let
|
|
@@ -430,7 +503,7 @@ module Hecks
|
|
|
430
503
|
Principal: { Service: ecs-tasks.amazonaws.com }
|
|
431
504
|
Action: sts:AssumeRole
|
|
432
505
|
Policies:
|
|
433
|
-
- PolicyName:
|
|
506
|
+
- PolicyName: #{names[:db_secret_policy]}
|
|
434
507
|
PolicyDocument:
|
|
435
508
|
Version: '2012-10-17'
|
|
436
509
|
Statement:
|
|
@@ -444,13 +517,14 @@ module Hecks
|
|
|
444
517
|
- Effect: Allow
|
|
445
518
|
Action: secretsmanager:GetSecretValue
|
|
446
519
|
Resource: !Ref #{session_secret_id}
|
|
447
|
-
#{oauth_task_policy_yaml.
|
|
520
|
+
#{oauth_task_policy_yaml.empty? ? "" : "# TMPL:oauth_task_policy"}
|
|
448
521
|
# TMPL:cross_domain_fargate_policies
|
|
522
|
+
# TMPL:extra_task_policies
|
|
449
523
|
|
|
450
524
|
#{task_definition_id}:
|
|
451
525
|
Type: AWS::ECS::TaskDefinition
|
|
452
526
|
Properties:
|
|
453
|
-
Family: #{
|
|
527
|
+
Family: #{names[:family]}
|
|
454
528
|
RequiresCompatibilities: [FARGATE]
|
|
455
529
|
NetworkMode: awsvpc
|
|
456
530
|
# ARM64, not Fargate's own x86_64 default — matching the
|
|
@@ -467,8 +541,9 @@ module Hecks
|
|
|
467
541
|
ExecutionRoleArn: !GetAtt #{execution_role_id}.Arn
|
|
468
542
|
TaskRoleArn: !GetAtt #{task_role_id}.Arn
|
|
469
543
|
ContainerDefinitions:
|
|
470
|
-
- Name: #{
|
|
471
|
-
Image: !Sub "${#{ecr_repository_id}.RepositoryUri}:${
|
|
544
|
+
- Name: #{domain_container.name}
|
|
545
|
+
Image: !Sub "${#{ecr_repository_id}.RepositoryUri}:${#{domain_container.tag_parameter}}"
|
|
546
|
+
# TMPL:domain_essential
|
|
472
547
|
PortMappings:
|
|
473
548
|
- ContainerPort: #{port}
|
|
474
549
|
LogConfiguration:
|
|
@@ -476,7 +551,7 @@ module Hecks
|
|
|
476
551
|
Options:
|
|
477
552
|
awslogs-group: !Ref #{log_group_id}
|
|
478
553
|
awslogs-region: !Ref AWS::Region
|
|
479
|
-
awslogs-stream-prefix: #{
|
|
554
|
+
awslogs-stream-prefix: #{domain_container.name}
|
|
480
555
|
Environment:
|
|
481
556
|
- Name: HECKS_DOMAIN
|
|
482
557
|
Value: #{declared_domain_name}
|
|
@@ -511,20 +586,21 @@ module Hecks
|
|
|
511
586
|
# dispatch) regardless of `web`/HECKS_SERVE_MODE. A
|
|
512
587
|
# container built with neither set crashes before
|
|
513
588
|
# ever reaching its own serve loop — found live
|
|
514
|
-
# deploying
|
|
515
|
-
# container, fixed here so every Fargate domain ships
|
|
589
|
+
# deploying a real domain container, fixed here so every Fargate domain ships
|
|
516
590
|
# both sidecars by default. Paths match the
|
|
517
591
|
# Dockerfile's own COPY destinations, below.
|
|
518
592
|
- Name: HECKS_WASM_PATH
|
|
519
|
-
Value:
|
|
593
|
+
Value: #{sidecar_dir}/#{domain_name}.wasm
|
|
520
594
|
- Name: HECKS_IR_PATH
|
|
521
|
-
Value:
|
|
595
|
+
Value: #{sidecar_dir}/#{domain_name}.ir.json
|
|
522
596
|
- Name: SESSION_SECRET_ARN
|
|
523
597
|
Value: !Ref #{session_secret_id}
|
|
524
598
|
- Name: HECKS_CHECKOUT_DOMAIN
|
|
525
599
|
Value: #{declared_domain_name}
|
|
526
|
-
#{oauth_task_env_yaml.
|
|
600
|
+
#{oauth_task_env_yaml.empty? ? "" : "# TMPL:oauth_task_env"}
|
|
527
601
|
# TMPL:db_env
|
|
602
|
+
# TMPL:domain_env
|
|
603
|
+
# TMPL:extra_containers
|
|
528
604
|
|
|
529
605
|
#{target_group_id}:
|
|
530
606
|
Type: AWS::ElasticLoadBalancingV2::TargetGroup
|
|
@@ -532,14 +608,15 @@ module Hecks
|
|
|
532
608
|
TargetType: ip
|
|
533
609
|
Port: #{port}
|
|
534
610
|
Protocol: HTTP
|
|
535
|
-
VpcId: #{
|
|
536
|
-
HealthCheckPath:
|
|
611
|
+
VpcId: #{vpc_ref}
|
|
612
|
+
HealthCheckPath: #{domain_container.health_path}
|
|
537
613
|
HealthCheckPort: "#{port}"
|
|
614
|
+
# TMPL:target_group_attributes
|
|
538
615
|
|
|
539
616
|
#{alb_id}:
|
|
540
617
|
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
|
|
541
618
|
Properties:
|
|
542
|
-
Name: #{
|
|
619
|
+
Name: #{names[:alb]}
|
|
543
620
|
Scheme: internet-facing
|
|
544
621
|
Type: application
|
|
545
622
|
SecurityGroups: [!Ref #{alb_sg_id}]
|
|
@@ -553,98 +630,40 @@ module Hecks
|
|
|
553
630
|
Protocol: HTTP
|
|
554
631
|
DefaultActions:
|
|
555
632
|
- Type: forward
|
|
556
|
-
TargetGroupArn: !Ref #{target_group_id}
|
|
633
|
+
TargetGroupArn: !Ref #{layout.default.target_group_id}
|
|
557
634
|
|
|
558
|
-
#
|
|
635
|
+
# TMPL:extra_target_groups
|
|
636
|
+
#{service_id}:
|
|
559
637
|
Type: AWS::ECS::Service
|
|
560
|
-
DependsOn: #{listener_id}
|
|
638
|
+
DependsOn: #{Containers.depends_on(layout, listener_id)}
|
|
561
639
|
Properties:
|
|
562
|
-
ServiceName: #{
|
|
640
|
+
ServiceName: #{names[:service]}
|
|
563
641
|
Cluster: !Ref #{cluster_id}
|
|
564
642
|
TaskDefinition: !Ref #{task_definition_id}
|
|
565
|
-
DesiredCount: #{desired_count}
|
|
643
|
+
DesiredCount: #{plan.desired_count_parameter ? "!Ref #{plan.desired_count_parameter}" : desired_count}
|
|
566
644
|
LaunchType: FARGATE
|
|
645
|
+
# TMPL:service_tuning
|
|
567
646
|
NetworkConfiguration:
|
|
568
647
|
AwsvpcConfiguration:
|
|
569
648
|
AssignPublicIp: DISABLED
|
|
570
649
|
Subnets: #{shared ? "[!Ref OwningSubnetAId, !Ref OwningSubnetBId]" : "[!Ref #{db_id}SubnetA, !Ref #{db_id}SubnetB]"}
|
|
571
650
|
SecurityGroups: [#{compute_security_group_ref}]
|
|
572
651
|
LoadBalancers:
|
|
573
|
-
- ContainerName: #{
|
|
652
|
+
- ContainerName: #{domain_container.name}
|
|
574
653
|
ContainerPort: #{port}
|
|
575
654
|
TargetGroupArn: !Ref #{target_group_id}
|
|
655
|
+
# TMPL:extra_load_balancers
|
|
576
656
|
|
|
577
|
-
#
|
|
578
|
-
# nothing else in this stack terminates TLS) and, just as
|
|
579
|
-
# important, the ONE safe default this generator can offer
|
|
580
|
-
# for caching it has no way to reason about: Managed-
|
|
581
|
-
# CachingDisabled. This domain's own routes — including
|
|
582
|
-
# every hecks-native /login, /logout, /auth/google(/callback),
|
|
583
|
-
# /admin/members request (web.rs's own auth_gate/auth_route,
|
|
584
|
-
# generic across every domain, not just this one's own
|
|
585
|
-
# dispatch commands) — are all session-cookie-driven, and
|
|
586
|
-
# this generator has no way to tell which of a domain's own
|
|
587
|
-
# paths would ever be safe to cache. Found live, the hard
|
|
588
|
-
# way (lifeadelics, 2026-09-21): a hand-authored CloudFront
|
|
589
|
-
# stack applied the OPPOSITE default — a custom, cookie-
|
|
590
|
-
# blind cache policy with a 90-120s TTL — and it served one
|
|
591
|
-
# signed-in session's own response (a short-lived SSO
|
|
592
|
-
# handoff token among them) back to a different, unrelated
|
|
593
|
-
# request within that window. A domain that DOES know one
|
|
594
|
-
# of its own paths is genuinely safe to cache (a public,
|
|
595
|
-
# non-personalized page) adds its own more specific
|
|
596
|
-
# CacheBehavior by hand, the same way lifeadelics's own
|
|
597
|
-
# hand-extended three-container stack already does for
|
|
598
|
-
# /_astro/*, /videos/*, and friends — never by loosening
|
|
599
|
-
# this one.
|
|
600
|
-
#{distribution_id}:
|
|
601
|
-
Type: AWS::CloudFront::Distribution
|
|
602
|
-
Properties:
|
|
603
|
-
DistributionConfig:
|
|
604
|
-
Enabled: true
|
|
605
|
-
HttpVersion: http2
|
|
606
|
-
# No ACM/custom domain here — this generator has no
|
|
607
|
-
# notion of one (deploy.bluebook's own FargateTarget
|
|
608
|
-
# declares no `domain` attribute for it) and CloudFront
|
|
609
|
-
# requires an ACM cert in us-east-1 specifically to
|
|
610
|
-
# attach a custom Aliases entry, a real cross-region
|
|
611
|
-
# dependency this generator can't assume. CloudFront's
|
|
612
|
-
# own default *.cloudfront.net certificate/hostname
|
|
613
|
-
# are what Outputs.CloudFrontDomain below reports;
|
|
614
|
-
# point a real domain's DNS at it by hand, same
|
|
615
|
-
# "generated, extend by hand" posture this whole file
|
|
616
|
-
# already has for anything past its own baseline.
|
|
617
|
-
ViewerCertificate:
|
|
618
|
-
CloudFrontDefaultCertificate: true
|
|
619
|
-
Origins:
|
|
620
|
-
- Id: #{alb_id}Origin
|
|
621
|
-
DomainName: !GetAtt #{alb_id}.DNSName
|
|
622
|
-
CustomOriginConfig:
|
|
623
|
-
OriginProtocolPolicy: http-only
|
|
624
|
-
HTTPPort: 80
|
|
625
|
-
HTTPSPort: 443
|
|
626
|
-
DefaultCacheBehavior:
|
|
627
|
-
TargetOriginId: #{alb_id}Origin
|
|
628
|
-
ViewerProtocolPolicy: redirect-to-https
|
|
629
|
-
Compress: true
|
|
630
|
-
AllowedMethods: [GET, HEAD, OPTIONS, PUT, PATCH, POST, DELETE]
|
|
631
|
-
CachedMethods: [GET, HEAD]
|
|
632
|
-
# Managed-CachingDisabled — see this resource's own
|
|
633
|
-
# header comment for why nothing else is safe here
|
|
634
|
-
# by default.
|
|
635
|
-
CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
|
|
636
|
-
# Managed-AllViewer — forwards every cookie/header/
|
|
637
|
-
# query string through uncached, so rust/host's own
|
|
638
|
-
# session-cookie-based auth sees the real request
|
|
639
|
-
# exactly as the browser sent it.
|
|
640
|
-
OriginRequestPolicyId: 216adef6-5c7f-47e4-b989-5492eafa07d3
|
|
657
|
+
# TMPL:distribution
|
|
641
658
|
|
|
659
|
+
# TMPL:extra_resources
|
|
642
660
|
Outputs:
|
|
643
661
|
ServiceUrl:
|
|
644
662
|
Value: !Sub "http://${#{alb_id}.DNSName}"
|
|
645
663
|
CloudFrontDomain:
|
|
646
664
|
Value: !GetAtt #{distribution_id}.DomainName
|
|
647
665
|
#{stack_outputs.map { |o| "#{o[:key]}:\n Value: #{o[:ref]}" }.join("\n ")}
|
|
666
|
+
# TMPL:extra_outputs
|
|
648
667
|
YAML
|
|
649
668
|
|
|
650
669
|
# Spliced in after the heredoc renders, not interpolated inside
|
|
@@ -661,11 +680,27 @@ module Hecks
|
|
|
661
680
|
# deep as it should have). A plain `String#sub` after the fact,
|
|
662
681
|
# capturing the marker's own real indentation, has no such
|
|
663
682
|
# interaction with the text it replaces into.
|
|
664
|
-
template_yaml = template_yaml.sub(/^([ \t]*)# TMPL:db_env\n/) { db_env_yaml(shared: shared, owner_db_name: owner_db_name, db_ref_id: db_ref_id, db_name: db_name, secret_sub: secret_sub, hecks_schema: hecks_schema, base: $1) }
|
|
683
|
+
template_yaml = template_yaml.sub(/^([ \t]*)# TMPL:db_env\n/) { db_env_yaml(shared: shared, owner_db_name: owner_db_name, db_ref_id: db_ref_id, db_name: db_name, secret_sub: secret_sub, hecks_schema: hecks_schema, db_name_ref: db_name_ref, base: $1) }
|
|
684
|
+
# A multi-line block is indented by the marker's own rendered column, never a
|
|
685
|
+
# hand-computed one: the heredoc above is dedented after its interpolations
|
|
686
|
+
# evaluate, so a column counted in the source ran twelve too deep and made the
|
|
687
|
+
# Google sign-in blocks invalid YAML.
|
|
688
|
+
template_yaml = Yaml.splice(template_yaml, "oauth_task_policy", "#{oauth_task_policy_yaml}\n")
|
|
689
|
+
template_yaml = Yaml.splice(template_yaml, "oauth_task_env", "#{oauth_task_env_yaml}\n")
|
|
665
690
|
template_yaml = template_yaml.sub(/^([ \t]*)# TMPL:cross_domain_fargate_policies\n/) {
|
|
666
691
|
cross_domain_fargate_targets.empty? ? "" : cross_domain_fargate_policy_yaml(cross_domain_fargate_targets, $1)
|
|
667
692
|
}
|
|
668
693
|
|
|
694
|
+
begin
|
|
695
|
+
template_yaml = Assembly.apply(
|
|
696
|
+
template_yaml, plan,
|
|
697
|
+
stack_name: stack_name, vpc_ref: vpc_ref, listener_id: listener_id, alb_id: alb_id, distribution_id: distribution_id,
|
|
698
|
+
db_secret_ref: db_secret_ref
|
|
699
|
+
)
|
|
700
|
+
rescue ArgumentError => e
|
|
701
|
+
raise ArgumentError, "#{world_file}'s deployed_to(\"AwsFargate\"): #{e.message}"
|
|
702
|
+
end
|
|
703
|
+
|
|
669
704
|
bastion_yaml = shared ? nil : Shared.bastion_yaml(
|
|
670
705
|
domain: domain, infra_name: infra_name, stack_name: stack_name, db_id: db_id,
|
|
671
706
|
google_oauth_present: network_needs_internet, bastion_parameters: bastion_parameters
|
|
@@ -673,8 +708,8 @@ module Hecks
|
|
|
673
708
|
|
|
674
709
|
dockerfile = <<~DOCKERFILE
|
|
675
710
|
# GENERATED by bin/project_deploy #{domain} — re-run it to refresh this
|
|
676
|
-
# file rather than hand-editing. Modeled on
|
|
677
|
-
#
|
|
711
|
+
# file rather than hand-editing. Modeled on a Ruby deployment's own
|
|
712
|
+
# Dockerfile shape: build the binary outside the image (this directory's own
|
|
678
713
|
# Makefile, before `docker build` runs), ship only the result — one COPY,
|
|
679
714
|
# not a from-scratch toolchain install on every deploy.
|
|
680
715
|
FROM debian:bookworm-slim
|
|
@@ -688,18 +723,18 @@ module Hecks
|
|
|
688
723
|
RUN apt-get update -qq && apt-get install -y --no-install-recommends -qq ca-certificates libpq5 \\
|
|
689
724
|
&& rm -rf /var/lib/apt/lists/*
|
|
690
725
|
|
|
691
|
-
COPY #{domain_name}-host
|
|
726
|
+
COPY #{plan.build_context_dir}#{domain_name}-host #{sidecar_dir}/#{domain_name}-host
|
|
692
727
|
# The .wasm/.ir.json sidecars main.rs requires at boot —
|
|
693
728
|
# HECKS_WASM_PATH/HECKS_IR_PATH (template.yaml's own
|
|
694
729
|
# ContainerDefinitions Environment) point at these exact paths.
|
|
695
|
-
COPY #{domain_name}.wasm
|
|
696
|
-
COPY #{domain_name}.ir.json
|
|
730
|
+
COPY #{plan.build_context_dir}#{domain_name}.wasm #{sidecar_dir}/#{domain_name}.wasm
|
|
731
|
+
COPY #{plan.build_context_dir}#{domain_name}.ir.json #{sidecar_dir}/#{domain_name}.ir.json
|
|
697
732
|
|
|
698
733
|
ENV PORT=#{port}
|
|
699
734
|
ENV BIND=0.0.0.0
|
|
700
735
|
EXPOSE #{port}
|
|
701
736
|
|
|
702
|
-
CMD ["#{domain_name}-host"]
|
|
737
|
+
CMD ["#{sidecar_dir == Settings::DEFAULT_INSTALL_DIR ? domain_name : "#{sidecar_dir}/#{domain_name}"}-host"]
|
|
703
738
|
DOCKERFILE
|
|
704
739
|
|
|
705
740
|
makefile_content = <<~MAKE
|
|
@@ -728,7 +763,7 @@ module Hecks
|
|
|
728
763
|
\t@rustup target list --installed 2>/dev/null | grep -qx aarch64-unknown-linux-gnu || rustup target add aarch64-unknown-linux-gnu
|
|
729
764
|
# GNU cross-linker, not Apple clang — rustc's aarch64-unknown-linux-gnu
|
|
730
765
|
# target emits `-Wl,--fix-cortex-a53-843419`, which macOS ld rejects
|
|
731
|
-
# (found live building
|
|
766
|
+
# (found live building a domain image). Same
|
|
732
767
|
# toolchain Lambda's generated Makefile already documents.
|
|
733
768
|
\t@command -v aarch64-linux-gnu-gcc >/dev/null 2>&1 || { echo "aarch64-linux-gnu-gcc isn't on PATH. Install once with: brew tap messense/macos-cross-toolchains && brew install aarch64-unknown-linux-gnu"; exit 1; }
|
|
734
769
|
# The .wasm/.ir.json sidecars main.rs requires at boot,
|
|
@@ -752,17 +787,17 @@ module Hecks
|
|
|
752
787
|
# linux/arm64, not the generator's old amd64 default — has to
|
|
753
788
|
# match RuntimePlatform/the binary this Makefile's own build:
|
|
754
789
|
# step just cross-compiled, above.
|
|
755
|
-
\tdocker build --platform linux/arm64 -t #{
|
|
790
|
+
\tdocker build --platform linux/arm64 -t #{domain_container.repository_name}:$(IMAGE_TAG) .
|
|
756
791
|
|
|
757
792
|
.PHONY: docker-push
|
|
758
793
|
docker-push: ecr-login
|
|
759
794
|
\tACCOUNT_ID=$$(aws sts get-caller-identity --query Account --output text); \\
|
|
760
|
-
\t\tdocker tag #{
|
|
761
|
-
\t\tdocker push $$ACCOUNT_ID.dkr.ecr.$(REGION).amazonaws.com/#{
|
|
795
|
+
\t\tdocker tag #{domain_container.repository_name}:$(IMAGE_TAG) $$ACCOUNT_ID.dkr.ecr.$(REGION).amazonaws.com/#{domain_container.repository_name}:$(IMAGE_TAG); \\
|
|
796
|
+
\t\tdocker push $$ACCOUNT_ID.dkr.ecr.$(REGION).amazonaws.com/#{domain_container.repository_name}:$(IMAGE_TAG)
|
|
762
797
|
|
|
763
798
|
.PHONY: deploy
|
|
764
799
|
deploy: docker-build docker-push
|
|
765
|
-
#{shared ? "\t@echo \"Looking up #{owner_stack_name}'s shared VpcId/PrivateSubnetAId/PrivateSubnetBId/PublicSubnetId/BastionSubnetId/FunctionSecurityGroupId/DatabaseEndpoint/DatabaseSecretArn outputs to pass as $(STACK)'s Owning* parameters...\"\n\tOWNER_VPC_ID=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query \"Stacks[0].Outputs[?OutputKey=='VpcId'].OutputValue\" --output text); \\\n\t\tOWNER_SUBNET_A_ID=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query \"Stacks[0].Outputs[?OutputKey=='PrivateSubnetAId'].OutputValue\" --output text); \\\n\t\tOWNER_SUBNET_B_ID=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query \"Stacks[0].Outputs[?OutputKey=='PrivateSubnetBId'].OutputValue\" --output text); \\\n\t\tOWNER_PUBLIC_SUBNET_A_ID=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query \"Stacks[0].Outputs[?OutputKey=='PublicSubnetId'].OutputValue\" --output text); \\\n\t\tOWNER_PUBLIC_SUBNET_B_ID=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query \"Stacks[0].Outputs[?OutputKey=='BastionSubnetId'].OutputValue\" --output text); \\\n\t\tOWNER_SG_ID=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query \"Stacks[0].Outputs[?OutputKey=='FunctionSecurityGroupId'].OutputValue\" --output text); \\\n\t\tOWNER_DB_HOST=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query \"Stacks[0].Outputs[?OutputKey=='DatabaseEndpoint'].OutputValue\" --output text); \\\n\t\tOWNER_DB_SECRET_ARN=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query \"Stacks[0].Outputs[?OutputKey=='DatabaseSecretArn'].OutputValue\" --output text); \\\n\t\taws cloudformation deploy --template-file template.yaml --stack-name $(STACK) --region $(REGION) --capabilities CAPABILITY_IAM \\\n\t\t\t--parameter-overrides
|
|
800
|
+
#{shared ? "\t@echo \"Looking up #{owner_stack_name}'s shared VpcId/PrivateSubnetAId/PrivateSubnetBId/PublicSubnetId/BastionSubnetId/FunctionSecurityGroupId/DatabaseEndpoint/DatabaseSecretArn outputs to pass as $(STACK)'s Owning* parameters...\"\n\tOWNER_VPC_ID=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query \"Stacks[0].Outputs[?OutputKey=='VpcId'].OutputValue\" --output text); \\\n\t\tOWNER_SUBNET_A_ID=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query \"Stacks[0].Outputs[?OutputKey=='PrivateSubnetAId'].OutputValue\" --output text); \\\n\t\tOWNER_SUBNET_B_ID=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query \"Stacks[0].Outputs[?OutputKey=='PrivateSubnetBId'].OutputValue\" --output text); \\\n\t\tOWNER_PUBLIC_SUBNET_A_ID=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query \"Stacks[0].Outputs[?OutputKey=='PublicSubnetId'].OutputValue\" --output text); \\\n\t\tOWNER_PUBLIC_SUBNET_B_ID=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query \"Stacks[0].Outputs[?OutputKey=='BastionSubnetId'].OutputValue\" --output text); \\\n\t\tOWNER_SG_ID=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query \"Stacks[0].Outputs[?OutputKey=='FunctionSecurityGroupId'].OutputValue\" --output text); \\\n\t\tOWNER_DB_HOST=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query \"Stacks[0].Outputs[?OutputKey=='DatabaseEndpoint'].OutputValue\" --output text); \\\n\t\tOWNER_DB_SECRET_ARN=$$(aws cloudformation describe-stacks --stack-name #{owner_stack_name} --query \"Stacks[0].Outputs[?OutputKey=='DatabaseSecretArn'].OutputValue\" --output text); \\\n\t\taws cloudformation deploy --template-file template.yaml --stack-name $(STACK) --region $(REGION) --capabilities CAPABILITY_IAM \\\n\t\t\t--parameter-overrides #{domain_container.tag_parameter}=$(IMAGE_TAG) OwningVpcId=$$OWNER_VPC_ID OwningSubnetAId=$$OWNER_SUBNET_A_ID OwningSubnetBId=$$OWNER_SUBNET_B_ID OwningPublicSubnetAId=$$OWNER_PUBLIC_SUBNET_A_ID OwningPublicSubnetBId=$$OWNER_PUBLIC_SUBNET_B_ID OwningSecurityGroupId=$$OWNER_SG_ID OwningDatabaseEndpoint=$$OWNER_DB_HOST OwningDatabaseSecretArn=$$OWNER_DB_SECRET_ARN" : "\taws cloudformation deploy --template-file template.yaml --stack-name $(STACK) --region $(REGION) --capabilities CAPABILITY_IAM --parameter-overrides #{domain_container.tag_parameter}=$(IMAGE_TAG)"}
|
|
766
801
|
\t$(MAKE) mint-era
|
|
767
802
|
|
|
768
803
|
#{shared ? <<~SHAREDMINT.rstrip : <<~OWNMINT.rstrip
|
|
@@ -827,7 +862,19 @@ module Hecks
|
|
|
827
862
|
files["bastion.yaml"] = bastion_yaml if bastion_yaml
|
|
828
863
|
files["Dockerfile"] = dockerfile
|
|
829
864
|
files["Makefile"] = makefile_content
|
|
830
|
-
|
|
865
|
+
# Opt-in per-branch previews: nothing is added unless the domain declares a `preview`
|
|
866
|
+
# setting under deployed_to("AwsFargate"). See Preview's own header for the keys.
|
|
867
|
+
preview_main = {
|
|
868
|
+
infra_name: infra_name, stack_name: stack_name, stack_prefix: stack_prefix, region: region,
|
|
869
|
+
cpu: cpu, memory: memory, db_name: shared ? owner_db_name : db_name,
|
|
870
|
+
owner_stack: shared ? owner_stack_name : nil, name: infra_name, port: port,
|
|
871
|
+
image: "#{domain_container.repository_name}:latest", domain: declared_domain_name, web: target.state[:web].value,
|
|
872
|
+
wasm_path: "/usr/local/bin/#{domain_name}.wasm", ir_path: "/usr/local/bin/#{domain_name}.ir.json",
|
|
873
|
+
schema: hecks_schema
|
|
874
|
+
}
|
|
875
|
+
files.merge!(Preview.call(deploy_settings: deploy_settings, main: preview_main))
|
|
876
|
+
Scripts.extend_files(files, deploy_settings: deploy_settings, plan: plan,
|
|
877
|
+
stack_name: stack_name, region: region)
|
|
831
878
|
end
|
|
832
879
|
|
|
833
880
|
# Renders the container's own `DB_HOST`/`DB_NAME`/`DB_SECRET_ARN`
|
|
@@ -846,17 +893,19 @@ module Hecks
|
|
|
846
893
|
# @param secret_sub [String] the `${...}`-ready identifier for this domain's
|
|
847
894
|
# own database secret
|
|
848
895
|
# @param hecks_schema [String, nil] the Postgres schema to set, or nil for none
|
|
896
|
+
# @param db_name_ref [String, nil] an intrinsic (such as `!Ref Param`) that names the shared
|
|
897
|
+
# database in place of `owner_db_name`, or nil to write the name itself
|
|
849
898
|
# @param base [String] the marker line's own rendered indentation whitespace
|
|
850
899
|
# @return [String] the rendered `ContainerDefinitions[0].Environment` entries,
|
|
851
900
|
# ending in exactly one trailing newline
|
|
852
|
-
def db_env_yaml(shared:, owner_db_name:, db_ref_id:, db_name:, secret_sub:, hecks_schema:, base:)
|
|
901
|
+
def db_env_yaml(shared:, owner_db_name:, db_ref_id:, db_name:, secret_sub:, hecks_schema:, base:, db_name_ref: nil)
|
|
853
902
|
lines =
|
|
854
903
|
if shared
|
|
855
904
|
[
|
|
856
905
|
"- Name: DB_HOST",
|
|
857
906
|
" Value: !Ref OwningDatabaseEndpoint",
|
|
858
907
|
"- Name: DB_NAME",
|
|
859
|
-
" Value: #{owner_db_name}",
|
|
908
|
+
" Value: #{db_name_ref || owner_db_name}",
|
|
860
909
|
"- Name: DB_SECRET_ARN",
|
|
861
910
|
" Value: !Sub \"${OwningDatabaseSecretArn}\"",
|
|
862
911
|
]
|
|
@@ -1039,10 +1039,10 @@ module Hecks
|
|
|
1039
1039
|
# matching this stack's own name), a question `infra_name`
|
|
1040
1040
|
# answers and a domain's own business identity does not.
|
|
1041
1041
|
# Real, live, and confirmed the hard way: this was still
|
|
1042
|
-
# wired to `declared_domain_name` when
|
|
1042
|
+
# wired to `declared_domain_name` when a client app
|
|
1043
1043
|
# first deployed, and WebFunction immediately threw
|
|
1044
1044
|
# `ResourceNotFoundException: Function not found:
|
|
1045
|
-
# hecks
|
|
1045
|
+
# hecks-<domain>` on every read AND on the
|
|
1046
1046
|
# Google OAuth callback itself — a real signed-in user hit
|
|
1047
1047
|
# this within minutes of the deploy that introduced it.
|
|
1048
1048
|
DOMAIN_NAME: #{infra_name}
|
|
@@ -1317,7 +1317,7 @@ bastion_yaml = shared ? nil : Shared.bastion_yaml(
|
|
|
1317
1317
|
# aggregates with no snapshot table at all -- it only provisions the
|
|
1318
1318
|
# ONE bluebook it's called with (era_resolver.rb's own `bluebook.
|
|
1319
1319
|
# aggregates.each`), never the whole registry. Found live minting
|
|
1320
|
-
#
|
|
1320
|
+
# a client site's own era: `Payments::Payment.Initiate` refused with
|
|
1321
1321
|
# "relation \\"payment_head_snapshot_1\\" does not exist" the first
|
|
1322
1322
|
# time this domain's own checkout route ran for real. The automated
|
|
1323
1323
|
# (non-Shared) recipe below now does this correctly for every OTHER
|
|
@@ -1490,7 +1490,7 @@ bastion_yaml = shared ? nil : Shared.bastion_yaml(
|
|
|
1490
1490
|
# `Hecks::Bluebook::Behaviour::World#for_binding` nor
|
|
1491
1491
|
# `PostgresEra.connect_for` ever reads `ENV["DATABASE_URL"]` or
|
|
1492
1492
|
# `ENV["HECKS_SCHEMA"]` — both scripts call
|
|
1493
|
-
# `registry.
|
|
1493
|
+
# `registry.binding_settings(...)`, a pure hash lookup
|
|
1494
1494
|
# against whatever literal `database "..."` string #{domain}'s own
|
|
1495
1495
|
# `.world` file declares. Without `db_env_blind:` below, this whole
|
|
1496
1496
|
# recipe would stand up a real bastion, punch a live 5432 ingress rule
|
|
@@ -1722,7 +1722,7 @@ bastion_yaml = shared ? nil : Shared.bastion_yaml(
|
|
|
1722
1722
|
# `shared` are collected together here rather than spelled as an
|
|
1723
1723
|
# if/elsif/else, the same shape `Parameters:` above holds to: the
|
|
1724
1724
|
# two are independent facts about a domain, not alternatives, and
|
|
1725
|
-
#
|
|
1725
|
+
# a client site can be both — an elsif shape would silently drop the
|
|
1726
1726
|
# Owning* overrides whenever OAuth is also present. Caught live
|
|
1727
1727
|
# the first time a Shared-mode domain with real Google OAuth
|
|
1728
1728
|
# actually ran `make deploy`: `sam deploy` refused
|