hecks 2.6.0 → 2.8.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/exe/hecks +13 -0
- 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/memory.rb +1 -1
- data/lib/hecks/adapters/driven/postgres.rb +1 -1
- data/lib/hecks/adapters/driven/prism.rb +2 -2
- data/lib/hecks/adapters/driven/sqlite/projection.rb +1 -1
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +31 -10
- data/lib/hecks/behaviors/expectations.rb +2 -2
- data/lib/hecks/bluebook/assembly/contract.rb +1 -1
- data/lib/hecks/bluebook/assembly/contracts.rb +5 -5
- data/lib/hecks/bluebook/assembly/specializer.rb +2 -2
- data/lib/hecks/bluebook/assembly.rb +1 -1
- data/lib/hecks/bluebook/behaviour/chapter.rb +5 -2
- data/lib/hecks/bluebook/behaviour/read_model.rb +18 -0
- data/lib/hecks/bluebook/capabilities.rb +2 -2
- data/lib/hecks/bluebook/chapter.rb +1 -1
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +2 -2
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +6 -1
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +2 -2
- 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/entity_builder.rb +1 -1
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +1 -1
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +14 -9
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +1 -1
- data/lib/hecks/bluebook/dsl/rule_reference.rb +3 -3
- data/lib/hecks/bluebook/dsl/translation_builder.rb +1 -1
- data/lib/hecks/bluebook/dsl/world_builder.rb +47 -3
- data/lib/hecks/bluebook/hexagon.rb +15 -7
- data/lib/hecks/bluebook/meta_validator/judge.rb +3 -3
- data/lib/hecks/bluebook/meta_validator/plan.rb +4 -4
- data/lib/hecks/bluebook/meta_validator/readings.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +2 -2
- data/lib/hecks/bluebook/meta_validator/shapes.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +11 -10
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +3 -1
- data/lib/hecks/bluebook/meta_validator.rb +1 -1
- data/lib/hecks/bluebook/model_check/client_profile.rb +4 -70
- data/lib/hecks/bluebook/model_check.rb +13 -14
- data/lib/hecks/cache_dir.rb +103 -0
- data/lib/hecks/cli/document.rb +104 -0
- data/lib/hecks/cli/ir.rb +47 -0
- data/lib/hecks/cli/mcp.rb +32 -0
- data/lib/hecks/cli/mcp_door.rb +461 -0
- data/lib/hecks/cli/model_check.rb +270 -0
- data/lib/hecks/cli/project_cli.rb +123 -0
- data/lib/hecks/cli/project_diagrams.rb +43 -0
- data/lib/hecks/cli/run.rb +219 -0
- data/lib/hecks/cli/smoke_test.rb +59 -0
- data/lib/hecks/cli/stores.rb +54 -0
- data/lib/hecks/cli.rb +166 -0
- 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/facade/handle.rb +2 -2
- data/lib/hecks/facade/surface/aggregate_door.rb +1 -1
- data/lib/hecks/forms/value_object_shape.rb +1 -1
- data/lib/hecks/framework/bluebook/console_settings.bluebook +3 -3
- data/lib/hecks/language/bluebook/vocabulary.bluebook +16 -0
- data/lib/hecks/language/world/world.bluebook +32 -2
- data/lib/hecks/mcp_door_scope.rb +176 -0
- data/lib/hecks/ports/key_vault.rb +1 -1
- data/lib/hecks/ports/persistence/append_only.rb +1 -1
- data/lib/hecks/ports/persistence/binding_policy.rb +16 -5
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -2
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +2 -2
- 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/postgres_era/lineage/era_store.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +13 -7
- 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/state_codec.rb +1 -1
- 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 +188 -139
- data/lib/hecks/projections/deploy/lambda.rb +8 -8
- 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/reference.rb +7 -1
- data/lib/hecks/projections.rb +2 -0
- data/lib/hecks/projector/exporter.rb +4 -4
- data/lib/hecks/projector/target.rb +1 -1
- data/lib/hecks/release/gem_pin/published_versions.rb +58 -0
- data/lib/hecks/release/gem_pin.rb +112 -0
- data/lib/hecks/release/runner/ci_publisher.rb +96 -0
- data/lib/hecks/release/runner/commands.rb +68 -0
- data/lib/hecks/release/runner/console.rb +52 -0
- data/lib/hecks/release/runner/gem_publisher.rb +44 -0
- data/lib/hecks/release/runner/git.rb +48 -0
- data/lib/hecks/release/runner/npm_publisher.rb +89 -0
- data/lib/hecks/release/runner/preflight.rb +119 -0
- data/lib/hecks/release/runner/published.rb +55 -0
- data/lib/hecks/release/runner/tagger.rb +87 -0
- data/lib/hecks/release/runner.rb +244 -0
- data/lib/hecks/router/namespace_installer.rb +1 -1
- data/lib/hecks/runtime/command_interpreter.rb +3 -3
- data/lib/hecks/runtime/command_rules/admissibility.rb +2 -2
- data/lib/hecks/runtime/command_rules/arithmetic.rb +2 -2
- data/lib/hecks/runtime/entity_element.rb +1 -1
- data/lib/hecks/runtime/entity_interpreter.rb +1 -1
- data/lib/hecks/runtime/invocation.rb +1 -1
- data/lib/hecks/runtime/loader.rb +27 -3
- data/lib/hecks/runtime/outbox.rb +1 -1
- data/lib/hecks/runtime/reaction_invocation.rb +3 -3
- data/lib/hecks/runtime/read_model_interpreter.rb +37 -15
- data/lib/hecks/runtime/reference_hop.rb +4 -4
- data/lib/hecks/runtime/registry/saga_persistence.rb +8 -2
- data/lib/hecks/runtime/registry/verification.rb +15 -13
- data/lib/hecks/runtime/registry/world_defaults.rb +106 -0
- data/lib/hecks/runtime/registry.rb +22 -13
- data/lib/hecks/runtime/saga_interpreter.rb +1 -1
- data/lib/hecks/runtime/tenant_check.rb +1 -1
- data/lib/hecks/runtime/value/coercion.rb +3 -3
- data/lib/hecks/runtime/value/entity_list_coercion.rb +1 -1
- data/lib/hecks/storehouse.rb +18 -12
- 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/vocabulary.rb +5 -0
- data/lib/hecks.rb +8 -2
- metadata +72 -58
- data/lib/hecks/bench/cli.rb +0 -74
- data/lib/hecks/bench/environment.rb +0 -100
- data/lib/hecks/bench/postgres_probe.rb +0 -41
- data/lib/hecks/bench/report.rb +0 -116
- data/lib/hecks/bench/ruby_runner.rb +0 -151
- data/lib/hecks/bench/run.rb +0 -55
- data/lib/hecks/bench/rust_runner.rb +0 -165
- data/lib/hecks/bench/stats.rb +0 -75
- data/lib/hecks/bench/suite.rb +0 -172
- data/lib/hecks/bench/workload.rb +0 -137
- data/lib/hecks/bench.rb +0 -28
- data/lib/hecks/codemod.rb +0 -443
- data/lib/hecks/corpus.rb +0 -479
- data/lib/hecks/doc/reference.rb +0 -585
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
- data/lib/hecks/fuzzing/combination_miner.rb +0 -178
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
- data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
- data/lib/hecks/fuzzing/differential.rb +0 -192
- data/lib/hecks/fuzzing/domain_generator.rb +0 -871
- data/lib/hecks/fuzzing/era_boundary.rb +0 -154
- data/lib/hecks/fuzzing/form_census.rb +0 -285
- data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
- data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
- data/lib/hecks/fuzzing/isolated_boot.rb +0 -604
- data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
- data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
- data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
- data/lib/hecks/fuzzing/properties/guards.rb +0 -398
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
- data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
- data/lib/hecks/fuzzing/properties/querying.rb +0 -260
- data/lib/hecks/fuzzing/properties.rb +0 -267
- data/lib/hecks/fuzzing/qa_settings.rb +0 -164
- data/lib/hecks/fuzzing/replay.rb +0 -930
- data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
- data/lib/hecks/fuzzing/self_consistency.rb +0 -825
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
- data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
- data/lib/hecks/fuzzing/shrinker.rb +0 -309
- data/lib/hecks/fuzzing/structural_skips.rb +0 -53
- data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
- data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
- data/lib/hecks/fuzzing/value_generator.rb +0 -340
- data/lib/hecks/fuzzing.rb +0 -23
- data/lib/hecks/grammar/evolve.rb +0 -524
- data/lib/hecks/query_ir.rb +0 -482
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Projections
|
|
3
|
+
module Deploy
|
|
4
|
+
module Fargate
|
|
5
|
+
# Validation for the optional settings a `deployed_to("AwsFargate")`
|
|
6
|
+
# block accepts beyond the required ones.
|
|
7
|
+
#
|
|
8
|
+
# `FargateTarget.Declare` in deploy.bluebook owns the baseline
|
|
9
|
+
# settings (`cpu`, `memory`, `port`, `database`, `web`). The keys added
|
|
10
|
+
# for multi-container stacks are nested shapes (lists of hashes, maps),
|
|
11
|
+
# which that aggregate's flat attributes cannot describe, so they are
|
|
12
|
+
# checked here. Every check raises `ArgumentError` naming the setting
|
|
13
|
+
# it was reading, so a mistake in a `.world` file points at its line.
|
|
14
|
+
module Check
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
LOGICAL_ID = /\A[A-Za-z][A-Za-z0-9]*\z/
|
|
18
|
+
RESOURCE_NAME = %r{\A[A-Za-z0-9][A-Za-z0-9_./-]*\z}
|
|
19
|
+
|
|
20
|
+
# Checks that a value is a hash with only known keys and returns it symbol-keyed.
|
|
21
|
+
#
|
|
22
|
+
# @param value [Object] the setting as written in the world file
|
|
23
|
+
# @param where [String] the setting's name, used in the error message
|
|
24
|
+
# @param allowed [Array<Symbol>] every key the hash may carry
|
|
25
|
+
# @param required [Array<Symbol>] the keys the hash must carry
|
|
26
|
+
# @return [Hash{Symbol => Object}] the hash with its keys as symbols
|
|
27
|
+
# @raise [ArgumentError] if the value is not a hash, names an unknown key, or lacks a
|
|
28
|
+
# required one
|
|
29
|
+
def hash!(value, where, allowed:, required: [])
|
|
30
|
+
raise ArgumentError, "#{where} must be a hash, got #{value.inspect}" unless value.is_a?(Hash)
|
|
31
|
+
|
|
32
|
+
found = value.transform_keys(&:to_sym)
|
|
33
|
+
unknown = found.keys - allowed
|
|
34
|
+
unless unknown.empty?
|
|
35
|
+
raise ArgumentError,
|
|
36
|
+
"#{where} has unknown key(s) #{unknown.join(', ')}; allowed: #{allowed.join(', ')}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
missing = required.reject { |key| found.key?(key) }
|
|
40
|
+
raise ArgumentError, "#{where} needs #{missing.join(', ')}" unless missing.empty?
|
|
41
|
+
|
|
42
|
+
found
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Checks that a value is a list of hashes, each valid for `hash!`.
|
|
46
|
+
#
|
|
47
|
+
# @param value [Object] the setting as written in the world file
|
|
48
|
+
# @param where [String] the setting's name, used in the error message
|
|
49
|
+
# @param allowed [Array<Symbol>] every key an entry may carry
|
|
50
|
+
# @param required [Array<Symbol>] the keys each entry must carry
|
|
51
|
+
# @return [Array<Hash{Symbol => Object}>] the entries with symbol keys
|
|
52
|
+
# @raise [ArgumentError] if the value is not an array or an entry is invalid
|
|
53
|
+
def hashes!(value, where, allowed:, required: [])
|
|
54
|
+
raise ArgumentError, "#{where} must be a list of hashes, got #{value.inspect}" unless value.is_a?(Array)
|
|
55
|
+
|
|
56
|
+
value.each_with_index.map { |entry, index| hash!(entry, "#{where}[#{index}]", allowed: allowed, required: required) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Checks that a value is a CloudFormation logical id.
|
|
60
|
+
#
|
|
61
|
+
# @param value [Object] the id as written in the world file
|
|
62
|
+
# @param where [String] the setting's name, used in the error message
|
|
63
|
+
# @return [String] the id
|
|
64
|
+
# @raise [ArgumentError] if the id has anything but letters and digits, or starts with a
|
|
65
|
+
# digit
|
|
66
|
+
def logical_id!(value, where)
|
|
67
|
+
text = value.to_s
|
|
68
|
+
return text if LOGICAL_ID.match?(text)
|
|
69
|
+
|
|
70
|
+
raise ArgumentError, "#{where} must be a CloudFormation logical id " \
|
|
71
|
+
"(letters and digits, starting with a letter), got #{value.inspect}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Checks that a value is a name AWS accepts for a container, repository or stack resource.
|
|
75
|
+
#
|
|
76
|
+
# @param value [Object] the name as written in the world file
|
|
77
|
+
# @param where [String] the setting's name, used in the error message
|
|
78
|
+
# @return [String] the name
|
|
79
|
+
# @raise [ArgumentError] if the name has characters outside letters, digits and `_ . / -`
|
|
80
|
+
def resource_name!(value, where)
|
|
81
|
+
text = value.to_s
|
|
82
|
+
return text if RESOURCE_NAME.match?(text)
|
|
83
|
+
|
|
84
|
+
raise ArgumentError, "#{where} must be a name of letters, digits, and _ . / - characters, got #{value.inspect}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Checks that a value is a whole number in a range.
|
|
88
|
+
#
|
|
89
|
+
# @param value [Object] the number as written in the world file
|
|
90
|
+
# @param where [String] the setting's name, used in the error message
|
|
91
|
+
# @param range [Range<Integer>] the accepted values
|
|
92
|
+
# @return [Integer] the number
|
|
93
|
+
# @raise [ArgumentError] if the value is not an integer inside `range`
|
|
94
|
+
def integer!(value, where, range:)
|
|
95
|
+
return value if value.is_a?(Integer) && range.cover?(value)
|
|
96
|
+
|
|
97
|
+
raise ArgumentError, "#{where} must be a whole number from #{range.min} to #{range.max}, got #{value.inspect}"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Checks that a value is true or false.
|
|
101
|
+
#
|
|
102
|
+
# @param value [Object] the flag as written in the world file
|
|
103
|
+
# @param where [String] the setting's name, used in the error message
|
|
104
|
+
# @return [Boolean] the flag
|
|
105
|
+
# @raise [ArgumentError] if the value is neither `true` nor `false`
|
|
106
|
+
def boolean!(value, where)
|
|
107
|
+
return value if [true, false].include?(value)
|
|
108
|
+
|
|
109
|
+
raise ArgumentError, "#{where} must be true or false, got #{value.inspect}"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Checks that a value is a list of non-empty strings.
|
|
113
|
+
#
|
|
114
|
+
# @param value [Object] the list as written in the world file
|
|
115
|
+
# @param where [String] the setting's name, used in the error message
|
|
116
|
+
# @param min [Integer] the fewest entries allowed
|
|
117
|
+
# @param max [Integer, nil] the most entries allowed, or nil for no limit
|
|
118
|
+
# @return [Array<String>] the entries as strings
|
|
119
|
+
# @raise [ArgumentError] if the value is not a list of strings or its size is out of range
|
|
120
|
+
def strings!(value, where, min: 0, max: nil)
|
|
121
|
+
list = value.is_a?(Array) ? value : nil
|
|
122
|
+
ok = list&.all? { |item| (item.is_a?(String) || item.is_a?(Symbol)) && !item.to_s.empty? }
|
|
123
|
+
raise ArgumentError, "#{where} must be a list of strings, got #{value.inspect}" unless ok
|
|
124
|
+
raise ArgumentError, "#{where} needs at least #{min} entr#{min == 1 ? 'y' : 'ies'}" if list.size < min
|
|
125
|
+
raise ArgumentError, "#{where} takes at most #{max} entries, got #{list.size}" if max && list.size > max
|
|
126
|
+
|
|
127
|
+
list.map(&:to_s)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Checks that a value is a map of names to strings and returns it string-keyed.
|
|
131
|
+
#
|
|
132
|
+
# A `nil` value is kept: for the domain container's environment it
|
|
133
|
+
# removes a default variable.
|
|
134
|
+
#
|
|
135
|
+
# @param value [Object] the map as written in the world file
|
|
136
|
+
# @param where [String] the setting's name, used in the error message
|
|
137
|
+
# @param nil_ok [Boolean] whether a `nil` value is accepted
|
|
138
|
+
# @return [Hash{String => Object}] the map with string keys
|
|
139
|
+
# @raise [ArgumentError] if the value is not a hash or a key is not an
|
|
140
|
+
# environment-variable style name
|
|
141
|
+
def map!(value, where, nil_ok: false)
|
|
142
|
+
raise ArgumentError, "#{where} must be a hash, got #{value.inspect}" unless value.is_a?(Hash)
|
|
143
|
+
|
|
144
|
+
value.to_h do |key, item|
|
|
145
|
+
name = key.to_s
|
|
146
|
+
unless name.match?(/\A[A-Za-z_][A-Za-z0-9_]*\z/)
|
|
147
|
+
raise ArgumentError,
|
|
148
|
+
"#{where} key #{key.inspect} must be a name of letters, digits and underscores"
|
|
149
|
+
end
|
|
150
|
+
raise ArgumentError, "#{where}.#{name} needs a value" if item.nil? && !nil_ok
|
|
151
|
+
|
|
152
|
+
[name, item]
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Checks that a value is one of a fixed set.
|
|
157
|
+
#
|
|
158
|
+
# @param value [Object] the value as written in the world file
|
|
159
|
+
# @param where [String] the setting's name, used in the error message
|
|
160
|
+
# @param choices [Array<String>] the accepted values
|
|
161
|
+
# @return [String] the value as a string
|
|
162
|
+
# @raise [ArgumentError] if the value is not among `choices`
|
|
163
|
+
def one_of!(value, where, choices)
|
|
164
|
+
return value.to_s if choices.include?(value.to_s)
|
|
165
|
+
|
|
166
|
+
raise ArgumentError, "#{where} must be one of #{choices.join(', ')}, got #{value.inspect}"
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
require_relative "check"
|
|
2
|
+
require_relative "yaml"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Projections
|
|
6
|
+
module Deploy
|
|
7
|
+
module Fargate
|
|
8
|
+
# The containers of one Fargate task and how the load balancer reaches them.
|
|
9
|
+
#
|
|
10
|
+
# A stack always has the domain container: `rust/host` built from the
|
|
11
|
+
# domain itself. The world's `containers` setting adds more containers
|
|
12
|
+
# to the same task (a website, a content admin), each with its own ECR
|
|
13
|
+
# repository, image-tag parameter, target group and health check.
|
|
14
|
+
# `routes` sends URL path patterns to a container through listener
|
|
15
|
+
# rules on the one load balancer, and `default_container` names the
|
|
16
|
+
# container that answers everything else.
|
|
17
|
+
#
|
|
18
|
+
# ## Settings
|
|
19
|
+
#
|
|
20
|
+
# `domain_container` renames the domain container's own pieces:
|
|
21
|
+
# `name`, `repository`, `image_tag_parameter` and `health_path`. Its `essential`
|
|
22
|
+
# writes an explicit `Essential` flag; the container is essential either way,
|
|
23
|
+
# since that is `ECS`'s default.
|
|
24
|
+
#
|
|
25
|
+
# Each `containers` entry takes `name` and `repository` (required),
|
|
26
|
+
# and `port`, `health_path`, `env`, `secrets`, `image_tag_parameter`,
|
|
27
|
+
# `essential`, `cpu`, `memory`, `repository_id` and `target_group_id`.
|
|
28
|
+
# A container without a `port` receives no load-balancer traffic.
|
|
29
|
+
# `env` values and `secrets` values may be CloudFormation intrinsics
|
|
30
|
+
# written as strings (`"!Ref Name"`, `"!Sub \"...\""`).
|
|
31
|
+
#
|
|
32
|
+
# Each `routes` entry takes `container`, `paths` (one to five path
|
|
33
|
+
# patterns, since a listener rule condition holds at most five),
|
|
34
|
+
# `priority` and an optional `id`.
|
|
35
|
+
module Containers
|
|
36
|
+
module_function
|
|
37
|
+
|
|
38
|
+
Container = Struct.new(
|
|
39
|
+
:name, :repository_id, :repository_name, :tag_parameter, :port, :health_path,
|
|
40
|
+
:env, :secrets, :cpu, :memory, :essential, :target_group_id,
|
|
41
|
+
keyword_init: true
|
|
42
|
+
)
|
|
43
|
+
Route = Struct.new(:id, :container, :paths, :priority, keyword_init: true)
|
|
44
|
+
Layout = Struct.new(:domain, :extras, :routes, :default_container, keyword_init: true) do
|
|
45
|
+
# Lists the domain container followed by every added container.
|
|
46
|
+
#
|
|
47
|
+
# @return [Array<Container>] every container of the task
|
|
48
|
+
def all = [domain, *extras]
|
|
49
|
+
|
|
50
|
+
# Lists the containers that receive load-balancer traffic.
|
|
51
|
+
#
|
|
52
|
+
# @return [Array<Container>] the containers with a port
|
|
53
|
+
def balanced = all.select(&:port)
|
|
54
|
+
|
|
55
|
+
# Tells whether the stack has any container besides the domain's own.
|
|
56
|
+
#
|
|
57
|
+
# @return [Boolean] true when `containers` added at least one
|
|
58
|
+
def multi? = !extras.empty?
|
|
59
|
+
|
|
60
|
+
# Finds the container the listener forwards to by default.
|
|
61
|
+
#
|
|
62
|
+
# @return [Container] the default container
|
|
63
|
+
def default = all.find { |container| container.name == default_container }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
DOMAIN_KEYS = [:name, :repository, :image_tag_parameter, :health_path, :essential].freeze
|
|
67
|
+
CONTAINER_KEYS = [
|
|
68
|
+
:name, :repository, :port, :health_path, :env, :secrets, :image_tag_parameter, :essential,
|
|
69
|
+
:cpu, :memory, :repository_id, :target_group_id
|
|
70
|
+
].freeze
|
|
71
|
+
ROUTE_KEYS = [:container, :paths, :priority, :id].freeze
|
|
72
|
+
|
|
73
|
+
# Reads the container-related settings into a `Layout`.
|
|
74
|
+
#
|
|
75
|
+
# @param settings [Hash{Symbol => Object}] the world's `deployed_to("AwsFargate")`
|
|
76
|
+
# settings
|
|
77
|
+
# @param infra_name [String] the domain's own AWS-facing name, the default container name
|
|
78
|
+
# @param port [Integer] the validated port of the domain container
|
|
79
|
+
# @param ids [Hash{Symbol => String}] the resolved logical ids, for the domain container's
|
|
80
|
+
# own
|
|
81
|
+
# @return [Layout] the containers, routes and default container
|
|
82
|
+
# @raise [ArgumentError] if a container, route or the default container is invalid
|
|
83
|
+
def normalize(settings, infra_name:, port:, ids:)
|
|
84
|
+
domain = domain_container(settings[:domain_container], infra_name: infra_name, port: port, ids: ids)
|
|
85
|
+
extras = Check.hashes!(settings.fetch(:containers, []), "containers", allowed: CONTAINER_KEYS,
|
|
86
|
+
required: [:name, :repository])
|
|
87
|
+
.map do |entry|
|
|
88
|
+
extra_container(entry)
|
|
89
|
+
end
|
|
90
|
+
layout = Layout.new(domain: domain, extras: extras, routes: [], default_container: domain.name)
|
|
91
|
+
layout.default_container = default_name(settings, layout)
|
|
92
|
+
layout.routes = routes(settings.fetch(:routes, []), layout)
|
|
93
|
+
check_unique!(layout)
|
|
94
|
+
check_reachable!(layout)
|
|
95
|
+
layout
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Renders the ECR repository of every added container.
|
|
99
|
+
#
|
|
100
|
+
# @param layout [Layout] the stack's containers
|
|
101
|
+
# @return [String] flush-left resources separated by blank lines, or empty
|
|
102
|
+
def repositories_yaml(layout)
|
|
103
|
+
layout.extras.map do |container|
|
|
104
|
+
<<~REPOSITORY.rstrip
|
|
105
|
+
#{container.repository_id}:
|
|
106
|
+
Type: AWS::ECR::Repository
|
|
107
|
+
Properties:
|
|
108
|
+
RepositoryName: #{container.repository_name}
|
|
109
|
+
ImageScanningConfiguration:
|
|
110
|
+
ScanOnPush: true
|
|
111
|
+
REPOSITORY
|
|
112
|
+
end.join("\n\n")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Renders the image-tag parameter of every added container.
|
|
116
|
+
#
|
|
117
|
+
# @param layout [Layout] the stack's containers
|
|
118
|
+
# @return [String] flush-left parameters, each ending in a newline, or empty
|
|
119
|
+
def parameters_yaml(layout)
|
|
120
|
+
layout.extras.map do |container|
|
|
121
|
+
<<~PARAMETER
|
|
122
|
+
#{container.tag_parameter}:
|
|
123
|
+
Type: String
|
|
124
|
+
Default: latest
|
|
125
|
+
Description: ECR image tag the #{container.name} container pulls.
|
|
126
|
+
PARAMETER
|
|
127
|
+
end.join
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Renders the added containers as `ContainerDefinitions` list entries.
|
|
131
|
+
#
|
|
132
|
+
# @param layout [Layout] the stack's containers
|
|
133
|
+
# @param log_group_id [String] the logical id of the shared log group
|
|
134
|
+
# @return [String] flush-left list entries separated by blank lines, or empty
|
|
135
|
+
def definitions_yaml(layout, log_group_id)
|
|
136
|
+
layout.extras.map { |container| definition_yaml(container, log_group_id) }.join("\n\n")
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Renders the target group of every added container.
|
|
140
|
+
#
|
|
141
|
+
# @param layout [Layout] the stack's containers
|
|
142
|
+
# @param vpc_ref [String] the `!Ref` expression for the VPC the targets live in
|
|
143
|
+
# @param deregistration_delay [Integer, nil] seconds the load balancer drains a target, or
|
|
144
|
+
# nil for the default
|
|
145
|
+
# @return [String] flush-left resources separated by blank lines, or empty
|
|
146
|
+
def target_groups_yaml(layout, vpc_ref, deregistration_delay)
|
|
147
|
+
layout.extras.select(&:port).map do |container|
|
|
148
|
+
[
|
|
149
|
+
"#{container.target_group_id}:",
|
|
150
|
+
" Type: AWS::ElasticLoadBalancingV2::TargetGroup",
|
|
151
|
+
" Properties:",
|
|
152
|
+
" TargetType: ip",
|
|
153
|
+
" Port: #{container.port}",
|
|
154
|
+
" Protocol: HTTP",
|
|
155
|
+
" VpcId: #{vpc_ref}",
|
|
156
|
+
" HealthCheckPath: #{container.health_path}",
|
|
157
|
+
" HealthCheckPort: \"#{container.port}\"",
|
|
158
|
+
target_group_attributes_yaml(deregistration_delay).gsub(/^/, " ")
|
|
159
|
+
].join("\n").rstrip
|
|
160
|
+
end.join("\n\n")
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Renders the `TargetGroupAttributes` property for a drain delay.
|
|
164
|
+
#
|
|
165
|
+
# @param deregistration_delay [Integer, nil] seconds the load balancer drains a target
|
|
166
|
+
# @return [String] flush-left property lines ending in a newline, or empty for nil
|
|
167
|
+
def target_group_attributes_yaml(deregistration_delay)
|
|
168
|
+
return "" unless deregistration_delay
|
|
169
|
+
|
|
170
|
+
<<~ATTRIBUTES
|
|
171
|
+
TargetGroupAttributes:
|
|
172
|
+
- Key: deregistration_delay.timeout_seconds
|
|
173
|
+
Value: "#{deregistration_delay}"
|
|
174
|
+
ATTRIBUTES
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Renders one listener rule per route.
|
|
178
|
+
#
|
|
179
|
+
# @param layout [Layout] the stack's containers and routes
|
|
180
|
+
# @param listener_id [String] the logical id of the HTTP listener
|
|
181
|
+
# @return [String] flush-left resources separated by blank lines, or empty
|
|
182
|
+
def listener_rules_yaml(layout, listener_id)
|
|
183
|
+
layout.routes.map do |route|
|
|
184
|
+
target = layout.all.find { |container| container.name == route.container }
|
|
185
|
+
<<~RULE.rstrip
|
|
186
|
+
#{route.id}:
|
|
187
|
+
Type: AWS::ElasticLoadBalancingV2::ListenerRule
|
|
188
|
+
Properties:
|
|
189
|
+
ListenerArn: !Ref #{listener_id}
|
|
190
|
+
Priority: #{route.priority}
|
|
191
|
+
Conditions:
|
|
192
|
+
- Field: path-pattern
|
|
193
|
+
Values: #{Yaml.flow_list(route.paths)}
|
|
194
|
+
Actions:
|
|
195
|
+
- Type: forward
|
|
196
|
+
TargetGroupArn: !Ref #{target.target_group_id}
|
|
197
|
+
RULE
|
|
198
|
+
end.join("\n\n")
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Renders the service's `LoadBalancers` entries for the added containers.
|
|
202
|
+
#
|
|
203
|
+
# @param layout [Layout] the stack's containers
|
|
204
|
+
# @return [String] flush-left list entries, or empty
|
|
205
|
+
def load_balancers_yaml(layout)
|
|
206
|
+
layout.extras.select(&:port).map do |container|
|
|
207
|
+
<<~ENTRY
|
|
208
|
+
- ContainerName: #{container.name}
|
|
209
|
+
ContainerPort: #{container.port}
|
|
210
|
+
TargetGroupArn: !Ref #{container.target_group_id}
|
|
211
|
+
ENTRY
|
|
212
|
+
end.join
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Writes the `DependsOn` value that holds the service until the listener and its rules
|
|
216
|
+
# exist.
|
|
217
|
+
#
|
|
218
|
+
# @param layout [Layout] the stack's containers and routes
|
|
219
|
+
# @param listener_id [String] the logical id of the HTTP listener
|
|
220
|
+
# @return [String] a single id, or a flow list of ids
|
|
221
|
+
def depends_on(layout, listener_id)
|
|
222
|
+
return listener_id if layout.routes.empty?
|
|
223
|
+
|
|
224
|
+
Yaml.flow_list([listener_id, *layout.routes.map(&:id)])
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Finds the first and last container port the load balancer must reach.
|
|
228
|
+
#
|
|
229
|
+
# @param layout [Layout] the stack's containers
|
|
230
|
+
# @return [Range<Integer>] the security-group ingress port range
|
|
231
|
+
def port_range(layout)
|
|
232
|
+
ports = layout.balanced.map(&:port)
|
|
233
|
+
ports.min..ports.max
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def domain_container(setting, infra_name:, port:, ids:)
|
|
237
|
+
given = setting ? Check.hash!(setting, "domain_container", allowed: DOMAIN_KEYS) : {}
|
|
238
|
+
name = Check.resource_name!(given.fetch(:name, infra_name), "domain_container name")
|
|
239
|
+
repository = Check.resource_name!(given.fetch(:repository, infra_name), "domain_container repository")
|
|
240
|
+
Container.new(
|
|
241
|
+
name: name, repository_id: ids.fetch(:ecr_repository), repository_name: repository,
|
|
242
|
+
tag_parameter: Check.logical_id!(given.fetch(:image_tag_parameter, "ImageTag"),
|
|
243
|
+
"domain_container image_tag_parameter"),
|
|
244
|
+
port: port, health_path: health_path(given, "domain_container"), env: {}, secrets: {},
|
|
245
|
+
essential: given.key?(:essential) ? Check.boolean!(given[:essential], "domain_container essential") : nil,
|
|
246
|
+
target_group_id: ids.fetch(:target_group)
|
|
247
|
+
)
|
|
248
|
+
end
|
|
249
|
+
private_class_method :domain_container
|
|
250
|
+
|
|
251
|
+
def extra_container(entry)
|
|
252
|
+
name = Check.resource_name!(entry.fetch(:name), "containers name")
|
|
253
|
+
where = "containers[#{name}]"
|
|
254
|
+
base = Yaml.camel(name)
|
|
255
|
+
Container.new(
|
|
256
|
+
name: name, repository_name: Check.resource_name!(entry.fetch(:repository), "#{where} repository"),
|
|
257
|
+
repository_id: Check.logical_id!(entry.fetch(:repository_id, "#{base}Repository"), "#{where} repository_id"),
|
|
258
|
+
target_group_id: Check.logical_id!(entry.fetch(:target_group_id, "#{base}TargetGroup"), "#{where} target_group_id"),
|
|
259
|
+
tag_parameter: Check.logical_id!(entry.fetch(:image_tag_parameter, "#{base}ImageTag"),
|
|
260
|
+
"#{where} image_tag_parameter"),
|
|
261
|
+
port: optional_port(entry, where), health_path: health_path(entry, where),
|
|
262
|
+
env: Check.map!(entry.fetch(:env, {}), "#{where} env"),
|
|
263
|
+
secrets: Check.map!(entry.fetch(:secrets, {}), "#{where} secrets"),
|
|
264
|
+
cpu: optional_size(entry, :cpu, where), memory: optional_size(entry, :memory, where),
|
|
265
|
+
essential: Check.boolean!(entry.fetch(:essential, true), "#{where} essential")
|
|
266
|
+
)
|
|
267
|
+
end
|
|
268
|
+
private_class_method :extra_container
|
|
269
|
+
|
|
270
|
+
def optional_port(entry, where)
|
|
271
|
+
entry.key?(:port) ? Check.integer!(entry[:port], "#{where} port", range: 1..65_535) : nil
|
|
272
|
+
end
|
|
273
|
+
private_class_method :optional_port
|
|
274
|
+
|
|
275
|
+
def optional_size(entry, key, where)
|
|
276
|
+
entry.key?(key) ? Check.integer!(entry[key], "#{where} #{key}", range: 1..1_000_000) : nil
|
|
277
|
+
end
|
|
278
|
+
private_class_method :optional_size
|
|
279
|
+
|
|
280
|
+
def health_path(entry, where)
|
|
281
|
+
path = entry.fetch(:health_path, "/").to_s
|
|
282
|
+
raise ArgumentError, "#{where} health_path must start with /, got #{path.inspect}" unless path.start_with?("/")
|
|
283
|
+
|
|
284
|
+
path
|
|
285
|
+
end
|
|
286
|
+
private_class_method :health_path
|
|
287
|
+
|
|
288
|
+
def default_name(settings, layout)
|
|
289
|
+
name = settings.fetch(:default_container, layout.domain.name).to_s
|
|
290
|
+
target = layout.all.find { |container| container.name == name }
|
|
291
|
+
unless target
|
|
292
|
+
raise ArgumentError,
|
|
293
|
+
"default_container #{name.inspect} is not a container; have #{layout.all.map(&:name).join(', ')}"
|
|
294
|
+
end
|
|
295
|
+
unless target.port
|
|
296
|
+
raise ArgumentError,
|
|
297
|
+
"default_container #{name.inspect} has no port, so the listener cannot forward to it"
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
name
|
|
301
|
+
end
|
|
302
|
+
private_class_method :default_name
|
|
303
|
+
|
|
304
|
+
def routes(entries, layout)
|
|
305
|
+
counts = Hash.new(0)
|
|
306
|
+
Check.hashes!(entries, "routes", allowed: ROUTE_KEYS, required: [:container, :paths, :priority]).map do |entry|
|
|
307
|
+
container = entry.fetch(:container).to_s
|
|
308
|
+
counts[container] += 1
|
|
309
|
+
route(entry, container, counts[container], layout)
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
private_class_method :routes
|
|
313
|
+
|
|
314
|
+
def route(entry, container, nth, layout)
|
|
315
|
+
target = layout.balanced.find { |candidate| candidate.name == container }
|
|
316
|
+
raise ArgumentError, "routes name container #{container.inspect}, which is not a container with a port" unless target
|
|
317
|
+
|
|
318
|
+
suffix = nth == 1 ? "" : nth.to_s
|
|
319
|
+
Route.new(
|
|
320
|
+
id: Check.logical_id!(entry.fetch(:id, "ListenerRule#{Yaml.camel(container)}#{suffix}"), "routes id"),
|
|
321
|
+
container: container, priority: Check.integer!(entry.fetch(:priority), "routes priority", range: 1..50_000),
|
|
322
|
+
paths: route_paths(entry.fetch(:paths), container)
|
|
323
|
+
)
|
|
324
|
+
end
|
|
325
|
+
private_class_method :route
|
|
326
|
+
|
|
327
|
+
def route_paths(value, container)
|
|
328
|
+
paths = Check.strings!(value, "routes[#{container}] paths", min: 1, max: 5)
|
|
329
|
+
bad = paths.reject { |path| path.start_with?("/", "*") }
|
|
330
|
+
raise ArgumentError, "routes[#{container}] paths must start with / or *, got #{bad.join(', ')}" unless bad.empty?
|
|
331
|
+
|
|
332
|
+
paths
|
|
333
|
+
end
|
|
334
|
+
private_class_method :route_paths
|
|
335
|
+
|
|
336
|
+
def check_unique!(layout)
|
|
337
|
+
{
|
|
338
|
+
"container names" => layout.all.map(&:name),
|
|
339
|
+
"repository names" => layout.all.map(&:repository_name),
|
|
340
|
+
"image tag parameters" => layout.all.map(&:tag_parameter),
|
|
341
|
+
"repository ids" => layout.all.map(&:repository_id),
|
|
342
|
+
"target group ids" => layout.balanced.map(&:target_group_id),
|
|
343
|
+
"container ports" => layout.balanced.map(&:port),
|
|
344
|
+
"route ids" => layout.routes.map(&:id),
|
|
345
|
+
"route priorities" => layout.routes.map(&:priority)
|
|
346
|
+
}.each do |what, values|
|
|
347
|
+
repeated = values.tally.select { |_value, count| count > 1 }.keys
|
|
348
|
+
raise ArgumentError, "#{what} must be unique; repeated: #{repeated.join(', ')}" unless repeated.empty?
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
private_class_method :check_unique!
|
|
352
|
+
|
|
353
|
+
def check_reachable!(layout)
|
|
354
|
+
routed = layout.routes.map(&:container) + [layout.default_container]
|
|
355
|
+
stranded = layout.balanced.map(&:name) - routed
|
|
356
|
+
return if stranded.empty?
|
|
357
|
+
|
|
358
|
+
raise ArgumentError, "container(s) #{stranded.join(', ')} have a port but no route and are not the " \
|
|
359
|
+
"default_container, so the load balancer never reaches them; add a routes entry or drop the port"
|
|
360
|
+
end
|
|
361
|
+
private_class_method :check_reachable!
|
|
362
|
+
|
|
363
|
+
# Renders one added container as a `ContainerDefinitions` entry.
|
|
364
|
+
def definition_yaml(container, log_group_id)
|
|
365
|
+
lines = [
|
|
366
|
+
"- Name: #{container.name}",
|
|
367
|
+
" Image: !Sub \"${#{container.repository_id}.RepositoryUri}:${#{container.tag_parameter}}\"",
|
|
368
|
+
" Essential: #{container.essential}"
|
|
369
|
+
]
|
|
370
|
+
lines << " Cpu: #{container.cpu}" if container.cpu
|
|
371
|
+
lines << " Memory: #{container.memory}" if container.memory
|
|
372
|
+
lines.concat(port_lines(container), log_lines(container, log_group_id), env_lines(container), secret_lines(container))
|
|
373
|
+
lines.join("\n")
|
|
374
|
+
end
|
|
375
|
+
private_class_method :definition_yaml
|
|
376
|
+
|
|
377
|
+
def port_lines(container)
|
|
378
|
+
container.port ? [" PortMappings:", " - ContainerPort: #{container.port}"] : []
|
|
379
|
+
end
|
|
380
|
+
private_class_method :port_lines
|
|
381
|
+
|
|
382
|
+
def log_lines(container, log_group_id)
|
|
383
|
+
[
|
|
384
|
+
" LogConfiguration:", " LogDriver: awslogs", " Options:",
|
|
385
|
+
" awslogs-group: !Ref #{log_group_id}", " awslogs-region: !Ref AWS::Region",
|
|
386
|
+
" awslogs-stream-prefix: #{container.name}"
|
|
387
|
+
]
|
|
388
|
+
end
|
|
389
|
+
private_class_method :log_lines
|
|
390
|
+
|
|
391
|
+
def env_lines(container)
|
|
392
|
+
return [] if container.env.empty?
|
|
393
|
+
|
|
394
|
+
[" Environment:"] + container.env.flat_map do |name, value|
|
|
395
|
+
[" - Name: #{name}", " Value: #{Yaml.string(value)}"]
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
private_class_method :env_lines
|
|
399
|
+
|
|
400
|
+
def secret_lines(container)
|
|
401
|
+
return [] if container.secrets.empty?
|
|
402
|
+
|
|
403
|
+
[" Secrets:"] + container.secrets.flat_map do |name, value|
|
|
404
|
+
[" - Name: #{name}", " ValueFrom: #{Yaml.string(value)}"]
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
private_class_method :secret_lines
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
end
|