hecks 3.4.2 → 3.5.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/deploy/bluebook/deploy.bluebook +1510 -3
  3. data/lib/hecks/deploy/bluebook/deploy.hecksagon +57 -0
  4. data/lib/hecks/deploy/oidc.json +225 -0
  5. data/lib/hecks/hecks/adapters/bluebook_report.rb +91 -0
  6. data/lib/hecks/hecks/adapters/deploy_plans.rb +49 -0
  7. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +295 -14
  8. data/lib/hecks/hecks/adapters/in_process_operations.rb +6 -2
  9. data/lib/hecks/hecks/adapters/site_toolchain.rb +3 -3
  10. data/lib/hecks/hecks/custodian.bluebook +12 -2
  11. data/lib/hecks/hecks/hecks.hecksagon +57 -0
  12. data/lib/hecks/hecks/hecks.world +5 -1
  13. data/lib/hecks/projections/deploy/box/hosting.rb +13 -4
  14. data/lib/hecks/projections/deploy/box/roll_recipe.rb +62 -0
  15. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +10 -3
  16. data/lib/hecks/projections/deploy/box/templates/deploy-service.services-roll.part +2 -2
  17. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +16 -4
  18. data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef-roll.part +6 -6
  19. data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef.part +1 -1
  20. data/lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl +6 -3
  21. data/lib/hecks/projections/deploy/box/templates/restore-to-rds.sh.tmpl +8 -3
  22. data/lib/hecks/projections/deploy/box/templates/verify-copy.sh.tmpl +2 -2
  23. data/lib/hecks/projections/deploy/box.rb +8 -14
  24. data/lib/hecks/projections/site/admin.rb +140 -0
  25. data/lib/hecks/projections/site/admin_cms/templates.rb +388 -0
  26. data/lib/hecks/projections/site/admin_cms.rb +72 -0
  27. data/lib/hecks/projections/site/payload/fields.rb +234 -0
  28. data/lib/hecks/projections/site/payload/lifecycle.rb +191 -0
  29. data/lib/hecks/projections/site/payload/model.rb +128 -0
  30. data/lib/hecks/projections/site/payload/specs.rb +179 -0
  31. data/lib/hecks/projections/site/payload_driver.rb +93 -0
  32. data/lib/hecks/projections/site/root_rows.rb +59 -0
  33. data/lib/hecks/projections/site/site_admin.rb +292 -0
  34. data/lib/hecks/projections/site/site_host/templates.rb +165 -0
  35. data/lib/hecks/projections/site/site_host.rb +58 -0
  36. data/lib/hecks/projections/site/site_root.rb +179 -0
  37. data/lib/hecks/site/bluebook/site.bluebook +7 -1
  38. data/lib/hecks/tools/site_routes.rb +78 -8
  39. data/lib/hecks/version.rb +1 -1
  40. data/rust/host/HECKS_RELEASE +1 -1
  41. metadata +17 -1
@@ -46,11 +46,68 @@ Hecks.hecksagon "Deploy" do
46
46
  Deploy::SmokeRun.port "DeployToolchain" do
47
47
  asks "Smoke", to: SmokeRun do
48
48
  attribute :run, RunKey
49
+ attribute :after, Origin, default: { value: "alone" }
49
50
  answers "SmokeAnswered"
50
51
  refuses "SmokeRefused"
51
52
  end
52
53
  end
53
54
 
55
+ Deploy::ServiceRoll.port "DeployToolchain" do
56
+ asks "RollService", to: ServiceRoll do
57
+ attribute :run, RunKey
58
+ answers "ServiceRollAnswered"
59
+ refuses "ServiceRollRefused"
60
+ end
61
+ end
62
+
63
+ Deploy::BoxRoll.port "DeployToolchain" do
64
+ asks "RollBox", to: BoxRoll do
65
+ attribute :run, RunKey
66
+ answers "BoxRollAnswered"
67
+ refuses "BoxRollRefused"
68
+ end
69
+ end
70
+
71
+ Deploy::DataCopy.port "DeployToolchain" do
72
+ asks "CopyData", to: DataCopy do
73
+ attribute :run, RunKey
74
+ answers "DataCopyAnswered"
75
+ refuses "DataCopyRefused"
76
+ end
77
+ end
78
+
79
+ Deploy::CopyVerification.port "DeployToolchain" do
80
+ asks "CompareCopy", to: CopyVerification do
81
+ attribute :run, RunKey
82
+ answers "CopyVerificationAnswered"
83
+ refuses "CopyVerificationRefused"
84
+ end
85
+ end
86
+
87
+ Deploy::BluebookDiff.port "DeployToolchain" do
88
+ asks "CompareBluebooks", to: BluebookDiff do
89
+ attribute :run, RunKey
90
+ answers "BluebookDiffAnswered"
91
+ refuses "BluebookDiffRefused"
92
+ end
93
+ end
94
+
95
+ Deploy::PreviewRun.port "DeployToolchain" do
96
+ asks "RunPreview", to: PreviewRun do
97
+ attribute :run, RunKey
98
+ answers "PreviewRunAnswered"
99
+ refuses "PreviewRunRefused"
100
+ end
101
+ end
102
+
103
+ Deploy::CompanionRoll.port "DeployToolchain" do
104
+ asks "RollCompanion", to: CompanionRoll do
105
+ attribute :run, RunKey
106
+ answers "CompanionRollAnswered"
107
+ refuses "CompanionRollRefused"
108
+ end
109
+ end
110
+
54
111
  Deploy::TemplateComparison.port "DeployToolchain" do
55
112
  asks "Compare", to: TemplateComparison do
56
113
  attribute :run, RunKey
@@ -1,11 +1,101 @@
1
1
  {
2
2
  "audience": "Deploy",
3
3
  "scopes": [
4
+ {
5
+ "scope": "deploy:bluebook_diff.differ",
6
+ "verb": "Deploy::BluebookDiff.Differ",
7
+ "role": null
8
+ },
9
+ {
10
+ "scope": "deploy:bluebook_diff.flag",
11
+ "verb": "Deploy::BluebookDiff.Flag",
12
+ "role": null
13
+ },
14
+ {
15
+ "scope": "deploy:bluebook_diff.match",
16
+ "verb": "Deploy::BluebookDiff.Match",
17
+ "role": null
18
+ },
19
+ {
20
+ "scope": "deploy:bluebook_diff.run",
21
+ "verb": "Deploy::BluebookDiff.Run",
22
+ "role": null
23
+ },
24
+ {
25
+ "scope": "deploy:bluebook_diff.withhold",
26
+ "verb": "Deploy::BluebookDiff.Withhold",
27
+ "role": null
28
+ },
29
+ {
30
+ "scope": "deploy:box_roll.complete",
31
+ "verb": "Deploy::BoxRoll.Complete",
32
+ "role": null
33
+ },
34
+ {
35
+ "scope": "deploy:box_roll.flag",
36
+ "verb": "Deploy::BoxRoll.Flag",
37
+ "role": null
38
+ },
39
+ {
40
+ "scope": "deploy:box_roll.flag_smoke",
41
+ "verb": "Deploy::BoxRoll.FlagSmoke",
42
+ "role": null
43
+ },
44
+ {
45
+ "scope": "deploy:box_roll.run",
46
+ "verb": "Deploy::BoxRoll.Run",
47
+ "role": null
48
+ },
49
+ {
50
+ "scope": "deploy:box_roll.verify",
51
+ "verb": "Deploy::BoxRoll.Verify",
52
+ "role": null
53
+ },
4
54
  {
5
55
  "scope": "deploy:box_target.declare",
6
56
  "verb": "Deploy::BoxTarget.Declare",
7
57
  "role": null
8
58
  },
59
+ {
60
+ "scope": "deploy:companion_roll.complete",
61
+ "verb": "Deploy::CompanionRoll.Complete",
62
+ "role": null
63
+ },
64
+ {
65
+ "scope": "deploy:companion_roll.flag",
66
+ "verb": "Deploy::CompanionRoll.Flag",
67
+ "role": null
68
+ },
69
+ {
70
+ "scope": "deploy:companion_roll.plan",
71
+ "verb": "Deploy::CompanionRoll.Plan",
72
+ "role": null
73
+ },
74
+ {
75
+ "scope": "deploy:companion_roll.run",
76
+ "verb": "Deploy::CompanionRoll.Run",
77
+ "role": null
78
+ },
79
+ {
80
+ "scope": "deploy:copy_verification.differ",
81
+ "verb": "Deploy::CopyVerification.Differ",
82
+ "role": null
83
+ },
84
+ {
85
+ "scope": "deploy:copy_verification.flag",
86
+ "verb": "Deploy::CopyVerification.Flag",
87
+ "role": null
88
+ },
89
+ {
90
+ "scope": "deploy:copy_verification.match",
91
+ "verb": "Deploy::CopyVerification.Match",
92
+ "role": null
93
+ },
94
+ {
95
+ "scope": "deploy:copy_verification.run",
96
+ "verb": "Deploy::CopyVerification.Run",
97
+ "role": null
98
+ },
9
99
  {
10
100
  "scope": "deploy:cost_check.check",
11
101
  "verb": "Deploy::CostCheck.Check",
@@ -21,6 +111,46 @@
21
111
  "verb": "Deploy::CostCheck.Pass",
22
112
  "role": null
23
113
  },
114
+ {
115
+ "scope": "deploy:data_copy.complete",
116
+ "verb": "Deploy::DataCopy.Complete",
117
+ "role": null
118
+ },
119
+ {
120
+ "scope": "deploy:data_copy.confirm",
121
+ "verb": "Deploy::DataCopy.Confirm",
122
+ "role": null
123
+ },
124
+ {
125
+ "scope": "deploy:data_copy.drift",
126
+ "verb": "Deploy::DataCopy.Drift",
127
+ "role": null
128
+ },
129
+ {
130
+ "scope": "deploy:data_copy.flag",
131
+ "verb": "Deploy::DataCopy.Flag",
132
+ "role": null
133
+ },
134
+ {
135
+ "scope": "deploy:data_copy.flag_verification",
136
+ "verb": "Deploy::DataCopy.FlagVerification",
137
+ "role": null
138
+ },
139
+ {
140
+ "scope": "deploy:data_copy.plan",
141
+ "verb": "Deploy::DataCopy.Plan",
142
+ "role": null
143
+ },
144
+ {
145
+ "scope": "deploy:data_copy.restore",
146
+ "verb": "Deploy::DataCopy.Restore",
147
+ "role": null
148
+ },
149
+ {
150
+ "scope": "deploy:data_copy.verify",
151
+ "verb": "Deploy::DataCopy.Verify",
152
+ "role": null
153
+ },
24
154
  {
25
155
  "scope": "deploy:fargate_target.declare",
26
156
  "verb": "Deploy::FargateTarget.Declare",
@@ -61,6 +191,76 @@
61
191
  "verb": "Deploy::OidcManifest.ProjectOidc",
62
192
  "role": null
63
193
  },
194
+ {
195
+ "scope": "deploy:preview_run.deploy",
196
+ "verb": "Deploy::PreviewRun.Deploy",
197
+ "role": null
198
+ },
199
+ {
200
+ "scope": "deploy:preview_run.destroy",
201
+ "verb": "Deploy::PreviewRun.Destroy",
202
+ "role": null
203
+ },
204
+ {
205
+ "scope": "deploy:preview_run.flag",
206
+ "verb": "Deploy::PreviewRun.Flag",
207
+ "role": null
208
+ },
209
+ {
210
+ "scope": "deploy:preview_run.list",
211
+ "verb": "Deploy::PreviewRun.List",
212
+ "role": null
213
+ },
214
+ {
215
+ "scope": "deploy:preview_run.login",
216
+ "verb": "Deploy::PreviewRun.Login",
217
+ "role": null
218
+ },
219
+ {
220
+ "scope": "deploy:preview_run.name",
221
+ "verb": "Deploy::PreviewRun.Name",
222
+ "role": null
223
+ },
224
+ {
225
+ "scope": "deploy:preview_run.plan",
226
+ "verb": "Deploy::PreviewRun.Plan",
227
+ "role": null
228
+ },
229
+ {
230
+ "scope": "deploy:preview_run.record_deploy",
231
+ "verb": "Deploy::PreviewRun.RecordDeploy",
232
+ "role": null
233
+ },
234
+ {
235
+ "scope": "deploy:preview_run.record_destroy",
236
+ "verb": "Deploy::PreviewRun.RecordDestroy",
237
+ "role": null
238
+ },
239
+ {
240
+ "scope": "deploy:preview_run.record_list",
241
+ "verb": "Deploy::PreviewRun.RecordList",
242
+ "role": null
243
+ },
244
+ {
245
+ "scope": "deploy:preview_run.record_login",
246
+ "verb": "Deploy::PreviewRun.RecordLogin",
247
+ "role": null
248
+ },
249
+ {
250
+ "scope": "deploy:preview_run.record_name",
251
+ "verb": "Deploy::PreviewRun.RecordName",
252
+ "role": null
253
+ },
254
+ {
255
+ "scope": "deploy:preview_run.record_url",
256
+ "verb": "Deploy::PreviewRun.RecordUrl",
257
+ "role": null
258
+ },
259
+ {
260
+ "scope": "deploy:preview_run.url",
261
+ "verb": "Deploy::PreviewRun.Url",
262
+ "role": null
263
+ },
64
264
  {
65
265
  "scope": "deploy:recipe.complete",
66
266
  "verb": "Deploy::Recipe.Complete",
@@ -76,6 +276,31 @@
76
276
  "verb": "Deploy::Recipe.Project",
77
277
  "role": null
78
278
  },
279
+ {
280
+ "scope": "deploy:service_roll.complete",
281
+ "verb": "Deploy::ServiceRoll.Complete",
282
+ "role": null
283
+ },
284
+ {
285
+ "scope": "deploy:service_roll.flag",
286
+ "verb": "Deploy::ServiceRoll.Flag",
287
+ "role": null
288
+ },
289
+ {
290
+ "scope": "deploy:service_roll.flag_smoke",
291
+ "verb": "Deploy::ServiceRoll.FlagSmoke",
292
+ "role": null
293
+ },
294
+ {
295
+ "scope": "deploy:service_roll.run",
296
+ "verb": "Deploy::ServiceRoll.Run",
297
+ "role": null
298
+ },
299
+ {
300
+ "scope": "deploy:service_roll.verify",
301
+ "verb": "Deploy::ServiceRoll.Verify",
302
+ "role": null
303
+ },
79
304
  {
80
305
  "scope": "deploy:smoke_run.flag",
81
306
  "verb": "Deploy::SmokeRun.Flag",
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Hecks
6
+ module Adapters
7
+ # Which bluebook releases one deploy changes: two `hecks package.verify` outputs compared by
8
+ # bluebook name, version, shape and digest.
9
+ #
10
+ # A bluebook is added, removed, re-versioned (a new era when its shape changed too), or has the
11
+ # same version with different content (a vendored copy edited or re-tagged). The comparison is
12
+ # pure; reading the running image's label out of a registry is not done here.
13
+ class BluebookReport
14
+ ROW = " %-24s %s"
15
+
16
+ # @param old_json [String] the running side's `package.verify` output
17
+ # @param new_json [String] the side about to ship
18
+ # @param label [String] what the old side is called in the heading
19
+ # @raise [JSON::ParserError] when either side is not JSON
20
+ def initialize(old_json, new_json, label: "old")
21
+ @old = JSON.parse(old_json)
22
+ @new = JSON.parse(new_json)
23
+ @label = label
24
+ @changed = false
25
+ @era = false
26
+ end
27
+
28
+ # @return [Boolean] whether any bluebook was added, removed, re-versioned or changed
29
+ def changed?
30
+ text
31
+ @changed
32
+ end
33
+
34
+ # @return [String] the heading, one line per bluebook, the commits, and a note on a new era
35
+ def text
36
+ @text ||= build.join("\n")
37
+ end
38
+
39
+ private
40
+
41
+ def build
42
+ old_books = @old.fetch("bluebooks", {})
43
+ new_books = @new.fetch("bluebooks", {})
44
+ lines = ["==> bluebooks in this deploy (#{@label} -> this build)"]
45
+ (old_books.keys | new_books.keys).sort.each { |name| lines << row(name, old_books[name], new_books[name]) }
46
+ lines << commits
47
+ lines << " no bluebook changes." unless @changed
48
+ lines << " NOTE: a shape change mints a new era when the domain boots on it." if @era
49
+ lines
50
+ end
51
+
52
+ def row(name, old, new)
53
+ return mark(name, "ADDED #{new['version']}") if old.nil?
54
+ return mark(name, "REMOVED (was #{old['version']})") if new.nil?
55
+ return reversioned(name, old, new) if old["version"] != new["version"]
56
+ return same_version_changed(name, old, new) if old["digest"] != new["digest"]
57
+
58
+ format(ROW, name, "#{new['version']} unchanged")
59
+ end
60
+
61
+ def same_version_changed(name, old, new)
62
+ mark(name, "#{new['version']} SAME VERSION, DIFFERENT CONTENT (digest #{old['digest'][0, 8]} -> " \
63
+ "#{new['digest'][0, 8]}); a vendored copy was edited or re-tagged")
64
+ end
65
+
66
+ def reversioned(name, old, new)
67
+ line = "#{old['version']} -> #{new['version']}"
68
+ if old["shape"] != new["shape"]
69
+ line += " shape #{shape(old)} -> #{shape(new)} NEW ERA (its translation edge must already be in " \
70
+ "bluebook/translations/)"
71
+ @era = true
72
+ end
73
+ mark(name, line)
74
+ end
75
+
76
+ def mark(name, text)
77
+ @changed = true
78
+ format(ROW, name, text)
79
+ end
80
+
81
+ def shape(book) = book["shape"].map { |entry| entry.split.last }.join(", ")
82
+
83
+ def commits
84
+ old = @old.dig("built_from", "commit") || "?"
85
+ new = @new.dig("built_from", "commit") || "?"
86
+ dirty = @new.dig("built_from", "dirty") ? " (uncommitted changes)" : ""
87
+ " built from #{old[0, 7]} -> #{new[0, 7]}#{dirty}"
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hecks
4
+ module Adapters
5
+ # The plan a write to AWS prints when it is refused unconfirmed or run with `dry_run`: what the
6
+ # project's script would do, read from the script's own text, so nothing is run to say it.
7
+ module DeployPlans
8
+ module_function
9
+
10
+ # What `preview.sh <action>` would do for a branch.
11
+ #
12
+ # @param text [String] the preview script's source
13
+ # @param action [String] `deploy`, `destroy` or `login`
14
+ # @param branch [String, nil] the branch named, or nil for the checked-out one
15
+ # @return [String]
16
+ def preview(text, action, branch)
17
+ stack = "#{variable(text, 'PREFIX') || '<prefix>'}-<name derived from #{branch || 'the checked-out branch'}>"
18
+ region = variable(text, "REGION")
19
+ where = region ? " in #{region}" : ""
20
+ case action
21
+ when "deploy"
22
+ "push the locally built images and create or update the preview stack #{stack}#{where}, " \
23
+ "create its database and start its service"
24
+ when "destroy" then "empty the preview's storage and delete the preview stack #{stack}#{where}"
25
+ else "read the session secret of the preview stack #{stack}#{where} and mint a signed-in admin session"
26
+ end
27
+ end
28
+
29
+ # What `deploy-<companion>.sh` would roll, and onto which box.
30
+ #
31
+ # @param text [String] the companion script's source
32
+ # @param companion [String] the companion's name
33
+ # @param taskdef [String] the task definition its image and settings are read from
34
+ # @return [String]
35
+ def companion(text, companion, taskdef)
36
+ stack = text[/out\s+(\S+)\s+InstanceId/, 1]
37
+ project = text[/compose -p (\w+)/, 1] || companion
38
+ directory = text[%r{mkdir -p (/\S+)}, 1]
39
+ place = directory ? " in #{directory}" : String.new
40
+ "roll #{companion} (Compose project #{project}#{place}) from task definition " \
41
+ "#{taskdef} onto the box of stack #{stack || '<box stack>'} over SSM, beside the app's project, " \
42
+ "then check it on the box"
43
+ end
44
+
45
+ # The value a script assigns to an upper-case variable at the start of a line.
46
+ def variable(text, name) = text[/^#{name}=["']?([^"'\s]+)/, 1]
47
+ end
48
+ end
49
+ end