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
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
require_relative "check"
|
|
2
|
+
require_relative "yaml"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Projections
|
|
6
|
+
module Deploy
|
|
7
|
+
module Fargate
|
|
8
|
+
# The extra resources, parameters, outputs and IAM grants a stack can
|
|
9
|
+
# declare beyond the ones the generator always makes.
|
|
10
|
+
#
|
|
11
|
+
# ## Settings
|
|
12
|
+
#
|
|
13
|
+
# - `buckets`: S3 buckets, each `{id:, name_prefix:, public_read:, cors_origins:}`. The
|
|
14
|
+
# bucket is named `<name_prefix>-<account id>`. `public_read` opens the bucket to
|
|
15
|
+
# anonymous `GetObject`. `cors_origins` allows those origins to `GET` from a browser.
|
|
16
|
+
# - `generated_secrets`: Secrets Manager secrets with a generated value, each
|
|
17
|
+
# `{id:, name:, description:, key:, length:}`. `key` is the JSON field the value is
|
|
18
|
+
# stored under (`secret`).
|
|
19
|
+
# - `session_secret`: `{name:, description:}` names the always-created session secret
|
|
20
|
+
# and describes it.
|
|
21
|
+
# - `task_policies` and `execution_policies`: extra IAM policies for the task role (what
|
|
22
|
+
# the containers may do) and the execution role (what `ECS` may do to start them).
|
|
23
|
+
# Each is `{name:, statements: [{effect:, actions:, resources:}]}`; `effect` is `Allow`
|
|
24
|
+
# by default and resources may be intrinsics.
|
|
25
|
+
# - `parameters`: extra template parameters, a map of name to
|
|
26
|
+
# `{type:, default:, description:, no_echo:}`.
|
|
27
|
+
# - `outputs`: extra template outputs, a map of name to a value or intrinsic.
|
|
28
|
+
module Extras
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
BUCKET_KEYS = [:id, :name_prefix, :public_read, :cors_origins].freeze
|
|
32
|
+
SECRET_KEYS = [:id, :name, :description, :key, :length].freeze
|
|
33
|
+
POLICY_KEYS = [:name, :statements].freeze
|
|
34
|
+
STATEMENT_KEYS = [:effect, :actions, :resources].freeze
|
|
35
|
+
PARAMETER_KEYS = [:type, :default, :description, :no_echo].freeze
|
|
36
|
+
PARAMETER_TYPE = /\A(String|Number|CommaDelimitedList|List<Number>|AWS::[A-Za-z0-9:]+|AWS::SSM::Parameter::Value<[^>]+>)\z/
|
|
37
|
+
EXEC_ACTIONS = %w[ssmmessages:CreateControlChannel ssmmessages:CreateDataChannel
|
|
38
|
+
ssmmessages:OpenControlChannel ssmmessages:OpenDataChannel].freeze
|
|
39
|
+
|
|
40
|
+
# Reads and checks the resource-declaring settings.
|
|
41
|
+
#
|
|
42
|
+
# @param settings [Hash{Symbol => Object}] the world's `deployed_to("AwsFargate")`
|
|
43
|
+
# settings
|
|
44
|
+
# @return [Hash{Symbol => Object}] `:buckets`, `:secrets`, `:session_secret`,
|
|
45
|
+
# `:task_policies`,
|
|
46
|
+
# `:execution_policies`, `:parameters` and `:outputs`, each checked and defaulted to
|
|
47
|
+
# empty
|
|
48
|
+
# @raise [ArgumentError] if any of those settings is malformed
|
|
49
|
+
def normalize(settings)
|
|
50
|
+
{
|
|
51
|
+
buckets: buckets(settings.fetch(:buckets, [])),
|
|
52
|
+
secrets: secrets(settings.fetch(:generated_secrets, [])),
|
|
53
|
+
session_secret: session_secret(settings[:session_secret]),
|
|
54
|
+
task_policies: policies(settings.fetch(:task_policies, []), "task_policies"),
|
|
55
|
+
execution_policies: policies(settings.fetch(:execution_policies, []), "execution_policies"),
|
|
56
|
+
parameters: parameters(settings.fetch(:parameters, {})),
|
|
57
|
+
outputs: outputs(settings.fetch(:outputs, {}))
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Renders the buckets and generated secrets.
|
|
62
|
+
#
|
|
63
|
+
# @param extras [Hash] the checked settings from `normalize`
|
|
64
|
+
# @return [String] flush-left resources separated by blank lines, or empty
|
|
65
|
+
def resources_yaml(extras)
|
|
66
|
+
(extras[:buckets].flat_map { |bucket| bucket_blocks(bucket) } + extras[:secrets].map do |secret|
|
|
67
|
+
secret_yaml(secret)
|
|
68
|
+
end).join("\n\n")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Renders extra policies as entries of a role's `Policies` list.
|
|
72
|
+
#
|
|
73
|
+
# @param policies [Array<Hash>] the checked policies
|
|
74
|
+
# @return [String] flush-left list entries, or empty
|
|
75
|
+
def policies_yaml(policies)
|
|
76
|
+
policies.map { |policy| policy_yaml(policy) }.join
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Renders the policy that lets `aws ecs execute-command` open a session in a container.
|
|
80
|
+
#
|
|
81
|
+
# @return [String] one flush-left `Policies` list entry
|
|
82
|
+
def execute_command_policy_yaml
|
|
83
|
+
policy_yaml(
|
|
84
|
+
name: "EcsExecDebug",
|
|
85
|
+
statements: [{ effect: "Allow", actions: EXEC_ACTIONS, resources: ["*"] }]
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Renders the extra template parameters.
|
|
90
|
+
#
|
|
91
|
+
# @param parameters [Hash{String => Hash}] the checked parameters
|
|
92
|
+
# @return [String] flush-left parameters, or empty
|
|
93
|
+
def parameters_yaml(parameters)
|
|
94
|
+
parameters.map do |name, given|
|
|
95
|
+
lines = ["#{name}:", " Type: #{given[:type]}"]
|
|
96
|
+
lines << " Default: #{Yaml.scalar(given[:default])}" if given.key?(:default)
|
|
97
|
+
lines << " Description: #{Yaml.string(given[:description])}" if given[:description]
|
|
98
|
+
lines << " NoEcho: true" if given[:no_echo]
|
|
99
|
+
"#{lines.join("\n")}\n"
|
|
100
|
+
end.join
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Renders the extra template outputs.
|
|
104
|
+
#
|
|
105
|
+
# @param outputs [Hash{String => Object}] the checked outputs
|
|
106
|
+
# @return [String] flush-left outputs, or empty
|
|
107
|
+
def outputs_yaml(outputs)
|
|
108
|
+
outputs.map { |name, value| "#{name}:\n Value: #{Yaml.string(value)}\n" }.join
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def buckets(entries)
|
|
112
|
+
Check.hashes!(entries, "buckets", allowed: BUCKET_KEYS, required: [:id, :name_prefix]).map do |entry|
|
|
113
|
+
{
|
|
114
|
+
id: Check.logical_id!(entry[:id], "buckets id"),
|
|
115
|
+
name_prefix: Check.resource_name!(entry[:name_prefix], "buckets name_prefix").downcase,
|
|
116
|
+
public_read: Check.boolean!(entry.fetch(:public_read, false), "buckets public_read"),
|
|
117
|
+
cors_origins: Check.strings!(entry.fetch(:cors_origins, []), "buckets cors_origins")
|
|
118
|
+
}
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
private_class_method :buckets
|
|
122
|
+
|
|
123
|
+
def secrets(entries)
|
|
124
|
+
Check.hashes!(entries, "generated_secrets", allowed: SECRET_KEYS, required: [:id, :name]).map do |entry|
|
|
125
|
+
{
|
|
126
|
+
id: Check.logical_id!(entry[:id], "generated_secrets id"),
|
|
127
|
+
name: Check.resource_name!(entry[:name], "generated_secrets name"),
|
|
128
|
+
description: entry[:description]&.to_s,
|
|
129
|
+
key: Check.resource_name!(entry.fetch(:key, "secret"), "generated_secrets key"),
|
|
130
|
+
length: Check.integer!(entry.fetch(:length, 64), "generated_secrets length", range: 8..512)
|
|
131
|
+
}
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
private_class_method :secrets
|
|
135
|
+
|
|
136
|
+
def session_secret(value)
|
|
137
|
+
return {} if value.nil?
|
|
138
|
+
|
|
139
|
+
given = Check.hash!(value, "session_secret", allowed: [:name, :description])
|
|
140
|
+
{ name: given[:name] && Check.resource_name!(given[:name], "session_secret name"),
|
|
141
|
+
description: given[:description]&.to_s }.compact
|
|
142
|
+
end
|
|
143
|
+
private_class_method :session_secret
|
|
144
|
+
|
|
145
|
+
def policies(entries, where)
|
|
146
|
+
Check.hashes!(entries, where, allowed: POLICY_KEYS, required: [:name, :statements]).map do |entry|
|
|
147
|
+
statements = Check.hashes!(entry[:statements], "#{where}[#{entry[:name]}] statements",
|
|
148
|
+
allowed: STATEMENT_KEYS, required: [:actions, :resources])
|
|
149
|
+
{
|
|
150
|
+
name: Check.logical_id!(entry[:name], "#{where} name"),
|
|
151
|
+
statements: statements.map do |statement|
|
|
152
|
+
{
|
|
153
|
+
effect: Check.one_of!(statement.fetch(:effect, "Allow"), "#{where} effect", %w[Allow Deny]),
|
|
154
|
+
actions: Check.strings!(statement[:actions], "#{where} actions", min: 1),
|
|
155
|
+
resources: Check.strings!(statement[:resources], "#{where} resources", min: 1)
|
|
156
|
+
}
|
|
157
|
+
end
|
|
158
|
+
}
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
private_class_method :policies
|
|
162
|
+
|
|
163
|
+
def parameters(map)
|
|
164
|
+
raise ArgumentError, "parameters must be a hash of name to settings, got #{map.inspect}" unless map.is_a?(Hash)
|
|
165
|
+
|
|
166
|
+
map.to_h do |name, entry|
|
|
167
|
+
key = Check.logical_id!(name, "parameters name")
|
|
168
|
+
given = Check.hash!(entry, "parameters.#{key}", allowed: PARAMETER_KEYS, required: [:type])
|
|
169
|
+
unless PARAMETER_TYPE.match?(given[:type].to_s)
|
|
170
|
+
raise ArgumentError,
|
|
171
|
+
"parameters.#{key} type #{given[:type].inspect} is not a CloudFormation parameter type"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
[key, given.merge(type: given[:type].to_s, no_echo: given[:no_echo] ? true : false)]
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
private_class_method :parameters
|
|
178
|
+
|
|
179
|
+
def outputs(map)
|
|
180
|
+
raise ArgumentError, "outputs must be a hash of name to value, got #{map.inspect}" unless map.is_a?(Hash)
|
|
181
|
+
|
|
182
|
+
map.to_h do |name, value|
|
|
183
|
+
raise ArgumentError, "outputs.#{name} needs a value" if value.nil?
|
|
184
|
+
|
|
185
|
+
[Check.logical_id!(name, "outputs name"), value]
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
private_class_method :outputs
|
|
189
|
+
|
|
190
|
+
def bucket_blocks(bucket)
|
|
191
|
+
blocks = [bucket_yaml(bucket)]
|
|
192
|
+
blocks << bucket_policy_yaml(bucket) if bucket[:public_read]
|
|
193
|
+
blocks
|
|
194
|
+
end
|
|
195
|
+
private_class_method :bucket_blocks
|
|
196
|
+
|
|
197
|
+
def bucket_yaml(bucket)
|
|
198
|
+
lines = ["#{bucket[:id]}:", " Type: AWS::S3::Bucket", " Properties:",
|
|
199
|
+
" BucketName: !Sub \"#{bucket[:name_prefix]}-${AWS::AccountId}\""]
|
|
200
|
+
lines.concat(public_access_lines) if bucket[:public_read]
|
|
201
|
+
lines.concat(cors_lines(bucket[:cors_origins])) unless bucket[:cors_origins].empty?
|
|
202
|
+
lines.join("\n")
|
|
203
|
+
end
|
|
204
|
+
private_class_method :bucket_yaml
|
|
205
|
+
|
|
206
|
+
def public_access_lines
|
|
207
|
+
[" PublicAccessBlockConfiguration:", " BlockPublicAcls: false", " BlockPublicPolicy: false",
|
|
208
|
+
" IgnorePublicAcls: false", " RestrictPublicBuckets: false"]
|
|
209
|
+
end
|
|
210
|
+
private_class_method :public_access_lines
|
|
211
|
+
|
|
212
|
+
def cors_lines(origins)
|
|
213
|
+
[" CorsConfiguration:", " CorsRules:", " - AllowedOrigins: #{Yaml.flow_list(origins)}",
|
|
214
|
+
" AllowedMethods: [GET]", " AllowedHeaders: [\"*\"]"]
|
|
215
|
+
end
|
|
216
|
+
private_class_method :cors_lines
|
|
217
|
+
|
|
218
|
+
def bucket_policy_yaml(bucket)
|
|
219
|
+
<<~POLICY.rstrip
|
|
220
|
+
#{bucket[:id]}Policy:
|
|
221
|
+
Type: AWS::S3::BucketPolicy
|
|
222
|
+
Properties:
|
|
223
|
+
Bucket: !Ref #{bucket[:id]}
|
|
224
|
+
PolicyDocument:
|
|
225
|
+
Version: "2012-10-17"
|
|
226
|
+
Statement:
|
|
227
|
+
- Effect: Allow
|
|
228
|
+
Principal: "*"
|
|
229
|
+
Action: s3:GetObject
|
|
230
|
+
Resource: !Sub "${#{bucket[:id]}.Arn}/*"
|
|
231
|
+
POLICY
|
|
232
|
+
end
|
|
233
|
+
private_class_method :bucket_policy_yaml
|
|
234
|
+
|
|
235
|
+
def secret_yaml(secret)
|
|
236
|
+
lines = ["#{secret[:id]}:", " Type: AWS::SecretsManager::Secret", " Properties:", " Name: #{secret[:name]}"]
|
|
237
|
+
lines << " Description: #{Yaml.string(secret[:description])}" if secret[:description]
|
|
238
|
+
lines.push(" GenerateSecretString:", " SecretStringTemplate: '{}'",
|
|
239
|
+
" GenerateStringKey: #{secret[:key]}", " PasswordLength: #{secret[:length]}",
|
|
240
|
+
" ExcludePunctuation: true")
|
|
241
|
+
lines.join("\n")
|
|
242
|
+
end
|
|
243
|
+
private_class_method :secret_yaml
|
|
244
|
+
|
|
245
|
+
def policy_yaml(policy)
|
|
246
|
+
lines = ["- PolicyName: #{policy[:name]}", " PolicyDocument:", " Version: '2012-10-17'", " Statement:"]
|
|
247
|
+
policy[:statements].each do |statement|
|
|
248
|
+
lines << " - Effect: #{statement[:effect]}"
|
|
249
|
+
lines.concat(statement_list("Action", statement[:actions]))
|
|
250
|
+
lines.concat(statement_list("Resource", statement[:resources].map { |resource| Yaml.string(resource) }))
|
|
251
|
+
end
|
|
252
|
+
"#{lines.join("\n")}\n"
|
|
253
|
+
end
|
|
254
|
+
private_class_method :policy_yaml
|
|
255
|
+
|
|
256
|
+
# One entry is written as a scalar, the form IAM policies are usually written in; more are
|
|
257
|
+
# a list.
|
|
258
|
+
def statement_list(key, values)
|
|
259
|
+
return [" #{key}: #{values.first}"] if values.one?
|
|
260
|
+
|
|
261
|
+
[" #{key}:"] + values.map { |value| " - #{value}" }
|
|
262
|
+
end
|
|
263
|
+
private_class_method :statement_list
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|