hecks 3.4.1 → 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.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/memory/shared_elements.rb +121 -17
- data/lib/hecks/deploy/bluebook/deploy.bluebook +1630 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +65 -0
- data/lib/hecks/deploy/oidc.json +240 -0
- data/lib/hecks/doors/cli_runner.rb +13 -4
- data/lib/hecks/doors/launcher_options.rb +14 -2
- data/lib/hecks/hecks/adapters/bluebook_report.rb +91 -0
- data/lib/hecks/hecks/adapters/deploy_plans.rb +49 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +379 -8
- data/lib/hecks/hecks/adapters/first_admin.rb +8 -1
- data/lib/hecks/hecks/adapters/git.rb +29 -0
- data/lib/hecks/hecks/adapters/in_process_operations.rb +6 -2
- data/lib/hecks/hecks/adapters/site_toolchain.rb +8 -8
- data/lib/hecks/hecks/custodian.bluebook +23 -2
- data/lib/hecks/hecks/hecks.hecksagon +66 -0
- data/lib/hecks/hecks/hecks.world +6 -1
- data/lib/hecks/hecks/site.world +4 -2
- data/lib/hecks/projections/deploy/box/hosting.rb +13 -3
- data/lib/hecks/projections/deploy/box/roll_recipe.rb +62 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +10 -3
- data/lib/hecks/projections/deploy/box/templates/deploy-service.services-roll.part +2 -2
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +16 -4
- data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef-roll.part +6 -6
- data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef.part +1 -1
- data/lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl +11 -5
- data/lib/hecks/projections/deploy/box/templates/restore-to-rds.sh.tmpl +8 -3
- data/lib/hecks/projections/deploy/box/templates/verify-copy.sh.tmpl +2 -2
- data/lib/hecks/projections/deploy/box.rb +26 -13
- data/lib/hecks/projections/site/admin.rb +140 -0
- data/lib/hecks/projections/site/admin_cms/templates.rb +388 -0
- data/lib/hecks/projections/site/admin_cms.rb +72 -0
- data/lib/hecks/projections/site/payload/fields.rb +234 -0
- data/lib/hecks/projections/site/payload/lifecycle.rb +191 -0
- data/lib/hecks/projections/site/payload/model.rb +128 -0
- data/lib/hecks/projections/site/payload/specs.rb +179 -0
- data/lib/hecks/projections/site/payload_driver.rb +93 -0
- data/lib/hecks/projections/site/root_rows.rb +59 -0
- data/lib/hecks/projections/site/site_admin.rb +292 -0
- data/lib/hecks/projections/site/site_host/templates.rb +165 -0
- data/lib/hecks/projections/site/site_host.rb +58 -0
- data/lib/hecks/projections/site/site_root.rb +179 -0
- data/lib/hecks/runtime/value.rb +7 -0
- data/lib/hecks/site/bluebook/site.bluebook +13 -2
- data/lib/hecks/tools/site_routes.rb +78 -8
- data/lib/hecks/version.rb +1 -1
- data/rust/host/HECKS_RELEASE +1 -1
- metadata +18 -2
|
@@ -770,6 +770,1636 @@ Hecks.bluebook "Deploy" do
|
|
|
770
770
|
trigger CostCheck::Flag, with: { refusal: :refusal }
|
|
771
771
|
end
|
|
772
772
|
|
|
773
|
+
aggregate "SmokeRun" do
|
|
774
|
+
description "One post-deploy smoke: the generated smoke-after-deploy script run, and how it ended."
|
|
775
|
+
|
|
776
|
+
attribute :project, Location
|
|
777
|
+
attribute :script, Location, optional: true
|
|
778
|
+
attribute :run, RunKey
|
|
779
|
+
attribute :taskdef, TaskDefinition, optional: true
|
|
780
|
+
attribute :skip, Switch, optional: true
|
|
781
|
+
attribute :async, Switch, optional: true
|
|
782
|
+
attribute :dry_run, Switch, optional: true
|
|
783
|
+
attribute :after, Origin, default: { value: "alone" }
|
|
784
|
+
attribute :report, Report, optional: true
|
|
785
|
+
attribute :refusal, Report, optional: true
|
|
786
|
+
identified_by :run
|
|
787
|
+
|
|
788
|
+
value_object "RunKey" do
|
|
789
|
+
attribute :value, String, pattern: '[^ \t\n\r]'
|
|
790
|
+
|
|
791
|
+
invariant("a run is named") { !value.to_s.empty? }
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
value_object "Location" do
|
|
795
|
+
attribute :value, String, pattern: '[^\t\n\r]'
|
|
796
|
+
|
|
797
|
+
invariant("a project or script is named by its path") { !value.to_s.empty? }
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
# A task definition family, or family:revision, as the generated script takes it in TASKDEF.
|
|
801
|
+
value_object "TaskDefinition" do
|
|
802
|
+
attribute :value, String, pattern: '^[A-Za-z0-9_-]+(:[0-9]+)?$'
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
# What requested the smoke: `alone` for a person, else the roll whose record takes the outcome.
|
|
806
|
+
value_object "Origin" do
|
|
807
|
+
attribute :value, String, pattern: '^(alone|service_roll|box_roll)$'
|
|
808
|
+
end
|
|
809
|
+
|
|
810
|
+
value_object "Switch" do
|
|
811
|
+
attribute :value, Boolean
|
|
812
|
+
end
|
|
813
|
+
|
|
814
|
+
value_object "Report" do
|
|
815
|
+
attribute :value, String
|
|
816
|
+
end
|
|
817
|
+
|
|
818
|
+
lifecycle :status, default: "requested" do
|
|
819
|
+
transition "Pass" => "passed", from: "requested"
|
|
820
|
+
transition "Flag" => "flagged", from: "requested"
|
|
821
|
+
end
|
|
822
|
+
|
|
823
|
+
# `hecks deploy smoke_run.run <project> [script=<path>] [taskdef=<family[:rev]>] [skip=true]
|
|
824
|
+
# [async=true] [dry_run=true]`. Finds the `smoke-after-deploy.sh` the project's AwsBox projection
|
|
825
|
+
# generated (`script=` names it when the project holds more than one) and runs it once a roll
|
|
826
|
+
# has settled: the script waits for the box, dispatches the smoke workflow and follows it.
|
|
827
|
+
# It only reads AWS; the smoke itself runs on GitHub. Exit 1 when it did not pass, with the
|
|
828
|
+
# script's own status (20 unsettled, 21 not run, 22 failed, 23 unknown) in the reason.
|
|
829
|
+
command "Run" do
|
|
830
|
+
goal "Run a project's generated post-deploy smoke and record whether the live site passed"
|
|
831
|
+
|
|
832
|
+
attribute :project, Location
|
|
833
|
+
attribute :script, Location, optional: true
|
|
834
|
+
attribute :run, RunKey
|
|
835
|
+
attribute :taskdef, TaskDefinition, optional: true
|
|
836
|
+
attribute :skip, Switch, optional: true
|
|
837
|
+
attribute :async, Switch, optional: true
|
|
838
|
+
attribute :dry_run, Switch, optional: true
|
|
839
|
+
attribute :after, Origin, default: { value: "alone" }
|
|
840
|
+
|
|
841
|
+
sets :project
|
|
842
|
+
sets :script
|
|
843
|
+
sets :run
|
|
844
|
+
sets :taskdef
|
|
845
|
+
sets :skip
|
|
846
|
+
sets :async
|
|
847
|
+
sets :dry_run
|
|
848
|
+
sets :after
|
|
849
|
+
|
|
850
|
+
emits "SmokeRequested"
|
|
851
|
+
end
|
|
852
|
+
|
|
853
|
+
command "Pass" do
|
|
854
|
+
goal "Record that the smoke passed, was skipped on purpose, or was left running, and what it said"
|
|
855
|
+
|
|
856
|
+
reference_to SmokeRun
|
|
857
|
+
attribute :report, Report
|
|
858
|
+
attribute :after, Origin, default: { value: "alone" }
|
|
859
|
+
|
|
860
|
+
sets :report
|
|
861
|
+
sets :after
|
|
862
|
+
|
|
863
|
+
emits "SmokePassed"
|
|
864
|
+
end
|
|
865
|
+
|
|
866
|
+
command "Flag" do
|
|
867
|
+
goal "Record that the smoke failed, did not run or could not be read, and what was said"
|
|
868
|
+
|
|
869
|
+
reference_to SmokeRun
|
|
870
|
+
attribute :refusal, Report
|
|
871
|
+
attribute :after, Origin, default: { value: "alone" }
|
|
872
|
+
|
|
873
|
+
sets :refusal
|
|
874
|
+
sets :after
|
|
875
|
+
|
|
876
|
+
emits "SmokeFlagged"
|
|
877
|
+
end
|
|
878
|
+
|
|
879
|
+
query "Verdict" do
|
|
880
|
+
description "What one smoke found: its status, and the report or the reason it was flagged."
|
|
881
|
+
attribute :run, RunKey
|
|
882
|
+
where(run: :run)
|
|
883
|
+
order_by :run
|
|
884
|
+
end
|
|
885
|
+
|
|
886
|
+
query "Flagged" do
|
|
887
|
+
description "Every smoke that failed, did not run or could not be read."
|
|
888
|
+
where(status: "flagged")
|
|
889
|
+
order_by :run
|
|
890
|
+
end
|
|
891
|
+
end
|
|
892
|
+
|
|
893
|
+
policy "SmokeWhenRequested" do
|
|
894
|
+
on "SmokeRun.SmokeRequested"
|
|
895
|
+
trigger SmokeRun::DeployToolchain::Smoke, with: { run: :run, after: :after }
|
|
896
|
+
end
|
|
897
|
+
|
|
898
|
+
policy "RecordThePassedSmoke" do
|
|
899
|
+
on "SmokeRun.SmokeAnswered"
|
|
900
|
+
trigger SmokeRun::Pass, with: { report: :report, after: :after }
|
|
901
|
+
end
|
|
902
|
+
|
|
903
|
+
policy "RecordTheFlaggedSmoke" do
|
|
904
|
+
on "SmokeRun.SmokeRefused"
|
|
905
|
+
trigger SmokeRun::Flag, with: { refusal: :refusal, after: :after }
|
|
906
|
+
end
|
|
907
|
+
|
|
908
|
+
aggregate "ServiceRoll" do
|
|
909
|
+
description "One roll of a single service onto the app box: the generated deploy-service script run, and how it ended."
|
|
910
|
+
|
|
911
|
+
attribute :project, Location
|
|
912
|
+
attribute :script, Location, optional: true
|
|
913
|
+
attribute :run, RunKey
|
|
914
|
+
attribute :service, ServiceName
|
|
915
|
+
attribute :existing_tag, ImageTag, optional: true
|
|
916
|
+
attribute :local_image, ImageName, optional: true
|
|
917
|
+
attribute :skip_smoke, Switch, optional: true
|
|
918
|
+
attribute :taskdef, TaskDefinition, optional: true
|
|
919
|
+
attribute :tag, ImageTag, optional: true
|
|
920
|
+
attribute :report, Report, optional: true
|
|
921
|
+
attribute :smoke, Report, optional: true
|
|
922
|
+
attribute :refusal, Report, optional: true
|
|
923
|
+
|
|
924
|
+
identified_by :run
|
|
925
|
+
|
|
926
|
+
value_object "RunKey" do
|
|
927
|
+
attribute :value, String, pattern: '[^ \t\n\r]'
|
|
928
|
+
|
|
929
|
+
invariant("a run is named") { !value.to_s.empty? }
|
|
930
|
+
end
|
|
931
|
+
|
|
932
|
+
value_object "Location" do
|
|
933
|
+
attribute :value, String, pattern: '[^\t\n\r]'
|
|
934
|
+
|
|
935
|
+
invariant("a project or script is named by its path") { !value.to_s.empty? }
|
|
936
|
+
end
|
|
937
|
+
|
|
938
|
+
# A task definition family, or family:revision, as the generated script takes it in TASKDEF.
|
|
939
|
+
value_object "TaskDefinition" do
|
|
940
|
+
attribute :value, String, pattern: '^[A-Za-z0-9_-]+(:[0-9]+)?$'
|
|
941
|
+
end
|
|
942
|
+
|
|
943
|
+
# A container name from the world: lower case words joined by hyphens or underscores.
|
|
944
|
+
value_object "ServiceName" do
|
|
945
|
+
attribute :value, String, pattern: '^[a-z0-9][a-z0-9_-]*$'
|
|
946
|
+
end
|
|
947
|
+
|
|
948
|
+
# An ECR image tag, as the script's EXISTING_TAG takes it.
|
|
949
|
+
value_object "ImageTag" do
|
|
950
|
+
attribute :value, String, pattern: '^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$'
|
|
951
|
+
end
|
|
952
|
+
|
|
953
|
+
# A local image reference to push, as the script's LOCAL_IMAGE takes it.
|
|
954
|
+
value_object "ImageName" do
|
|
955
|
+
attribute :value, String, pattern: '^[^ \t\n\r]+$'
|
|
956
|
+
end
|
|
957
|
+
|
|
958
|
+
value_object "Switch" do
|
|
959
|
+
attribute :value, Boolean
|
|
960
|
+
end
|
|
961
|
+
|
|
962
|
+
value_object "Report" do
|
|
963
|
+
attribute :value, String
|
|
964
|
+
end
|
|
965
|
+
|
|
966
|
+
lifecycle :status, default: "requested" do
|
|
967
|
+
transition "Complete" => "rolled", from: "requested"
|
|
968
|
+
transition "Flag" => "flagged", from: "requested"
|
|
969
|
+
transition "Verify" => "verified", from: "rolled"
|
|
970
|
+
transition "FlagSmoke" => "flagged", from: "rolled"
|
|
971
|
+
end
|
|
972
|
+
|
|
973
|
+
# `hecks deploy service_roll.run <project> service=<name> [existing_tag=<tag>] [local_image=<ref>]
|
|
974
|
+
# [skip_smoke=true] [script=<path>]`. Runs the project's generated `deploy-service.sh`: push the
|
|
975
|
+
# local image under a fresh tag (or reuse `existing_tag`), set the stack's tag parameter, roll
|
|
976
|
+
# the box. A policy then requests a `SmokeRun`, unless `skip_smoke=true`. Exit 1 when flagged,
|
|
977
|
+
# with the script's status (2 unknown service, 30 to 38 a refused step, 40 to 42 the box roll's).
|
|
978
|
+
|
|
979
|
+
command "Run" do
|
|
980
|
+
goal "Roll one service's local image onto the box and record whether it took (exit 1 when flagged: 2 unknown service, 30-38 a refused step, 40-42 the box roll)"
|
|
981
|
+
|
|
982
|
+
attribute :project, Location
|
|
983
|
+
attribute :script, Location, optional: true
|
|
984
|
+
attribute :run, RunKey
|
|
985
|
+
attribute :service, ServiceName
|
|
986
|
+
attribute :existing_tag, ImageTag, optional: true
|
|
987
|
+
attribute :local_image, ImageName, optional: true
|
|
988
|
+
attribute :skip_smoke, Switch, optional: true
|
|
989
|
+
|
|
990
|
+
sets :project
|
|
991
|
+
sets :script
|
|
992
|
+
sets :run
|
|
993
|
+
sets :service
|
|
994
|
+
sets :existing_tag
|
|
995
|
+
sets :local_image
|
|
996
|
+
sets :skip_smoke
|
|
997
|
+
|
|
998
|
+
emits "ServiceRollRequested"
|
|
999
|
+
end
|
|
1000
|
+
|
|
1001
|
+
command "Complete" do
|
|
1002
|
+
goal "Record that the service rolled, with the task definition and tag it rolled"
|
|
1003
|
+
|
|
1004
|
+
reference_to ServiceRoll
|
|
1005
|
+
attribute :report, Report
|
|
1006
|
+
attribute :taskdef, TaskDefinition, optional: true
|
|
1007
|
+
attribute :tag, ImageTag, optional: true
|
|
1008
|
+
attribute :smoke, Report, optional: true
|
|
1009
|
+
|
|
1010
|
+
sets :report
|
|
1011
|
+
sets :taskdef
|
|
1012
|
+
sets :tag
|
|
1013
|
+
sets :smoke
|
|
1014
|
+
|
|
1015
|
+
emits "ServiceRollCompleted"
|
|
1016
|
+
end
|
|
1017
|
+
|
|
1018
|
+
command "Flag" do
|
|
1019
|
+
goal "Record that the roll failed or could not start, with the script's status and what it said"
|
|
1020
|
+
|
|
1021
|
+
reference_to ServiceRoll
|
|
1022
|
+
attribute :refusal, Report
|
|
1023
|
+
|
|
1024
|
+
sets :refusal
|
|
1025
|
+
|
|
1026
|
+
emits "ServiceRollFlagged"
|
|
1027
|
+
end
|
|
1028
|
+
|
|
1029
|
+
command "Verify" do
|
|
1030
|
+
goal "Record that the smoke the roll requested passed"
|
|
1031
|
+
|
|
1032
|
+
reference_to ServiceRoll
|
|
1033
|
+
attribute :smoke, Report
|
|
1034
|
+
|
|
1035
|
+
sets :smoke
|
|
1036
|
+
|
|
1037
|
+
emits "ServiceRollVerified"
|
|
1038
|
+
end
|
|
1039
|
+
|
|
1040
|
+
command "FlagSmoke" do
|
|
1041
|
+
goal "Record that the roll took but the smoke it requested did not pass, and why"
|
|
1042
|
+
|
|
1043
|
+
reference_to ServiceRoll
|
|
1044
|
+
attribute :smoke, Report
|
|
1045
|
+
attribute :refusal, Report
|
|
1046
|
+
|
|
1047
|
+
sets :smoke
|
|
1048
|
+
sets :refusal
|
|
1049
|
+
|
|
1050
|
+
emits "ServiceRollSmokeFlagged"
|
|
1051
|
+
end
|
|
1052
|
+
|
|
1053
|
+
query "Verdict" do
|
|
1054
|
+
description "What one roll found: its status, and the report or the reason it was flagged."
|
|
1055
|
+
attribute :run, RunKey
|
|
1056
|
+
where(run: :run)
|
|
1057
|
+
order_by :run
|
|
1058
|
+
end
|
|
1059
|
+
|
|
1060
|
+
query "Flagged" do
|
|
1061
|
+
description "Every roll that failed or could not start."
|
|
1062
|
+
where(status: "flagged")
|
|
1063
|
+
order_by :run
|
|
1064
|
+
end
|
|
1065
|
+
end
|
|
1066
|
+
|
|
1067
|
+
policy "RollServiceWhenRequested" do
|
|
1068
|
+
on "ServiceRoll.ServiceRollRequested"
|
|
1069
|
+
trigger ServiceRoll::DeployToolchain::RollService, with: { run: :run }
|
|
1070
|
+
end
|
|
1071
|
+
|
|
1072
|
+
policy "RecordTheRolledService" do
|
|
1073
|
+
on "ServiceRoll.ServiceRollAnswered"
|
|
1074
|
+
trigger ServiceRoll::Complete,
|
|
1075
|
+
with: { report: :report, taskdef: :taskdef, tag: :tag, smoke: :smoke }
|
|
1076
|
+
end
|
|
1077
|
+
|
|
1078
|
+
policy "RecordTheFlaggedServiceRoll" do
|
|
1079
|
+
on "ServiceRoll.ServiceRollRefused"
|
|
1080
|
+
trigger ServiceRoll::Flag, with: { refusal: :refusal }
|
|
1081
|
+
end
|
|
1082
|
+
|
|
1083
|
+
# Declared after RecordTheRolledService: the roll is recorded before its smoke is requested, and the
|
|
1084
|
+
# smoke is requested from the answer itself because a reaction chain is bounded.
|
|
1085
|
+
policy "SmokeAfterServiceRoll" do
|
|
1086
|
+
on "ServiceRoll.ServiceRollAnswered"
|
|
1087
|
+
where { run_smoke.value }
|
|
1088
|
+
trigger SmokeRun::Run, with: { project: :project, run: :run, taskdef: :taskdef, after: :kind }
|
|
1089
|
+
end
|
|
1090
|
+
|
|
1091
|
+
policy "VerifyServiceRollOnPassedSmoke" do
|
|
1092
|
+
on "SmokeRun.SmokePassed"
|
|
1093
|
+
where { after.value == "service_roll" }
|
|
1094
|
+
trigger ServiceRoll::Verify, with: { service_roll: :run, smoke: :report }
|
|
1095
|
+
end
|
|
1096
|
+
|
|
1097
|
+
policy "FlagServiceRollOnFlaggedSmoke" do
|
|
1098
|
+
on "SmokeRun.SmokeFlagged"
|
|
1099
|
+
where { after.value == "service_roll" }
|
|
1100
|
+
trigger ServiceRoll::FlagSmoke, with: { service_roll: :run, smoke: :refusal, refusal: :refusal }
|
|
1101
|
+
end
|
|
1102
|
+
|
|
1103
|
+
aggregate "BoxRoll" do
|
|
1104
|
+
description "One roll of the whole app box: the generated deploy-box script run, and how it ended."
|
|
1105
|
+
|
|
1106
|
+
attribute :project, Location
|
|
1107
|
+
attribute :script, Location, optional: true
|
|
1108
|
+
attribute :run, RunKey
|
|
1109
|
+
attribute :taskdef, TaskDefinition, optional: true
|
|
1110
|
+
attribute :tags, Tags, optional: true
|
|
1111
|
+
attribute :skip_smoke, Switch, optional: true
|
|
1112
|
+
attribute :report, Report, optional: true
|
|
1113
|
+
attribute :smoke, Report, optional: true
|
|
1114
|
+
attribute :refusal, Report, optional: true
|
|
1115
|
+
|
|
1116
|
+
identified_by :run
|
|
1117
|
+
|
|
1118
|
+
value_object "RunKey" do
|
|
1119
|
+
attribute :value, String, pattern: '[^ \t\n\r]'
|
|
1120
|
+
|
|
1121
|
+
invariant("a run is named") { !value.to_s.empty? }
|
|
1122
|
+
end
|
|
1123
|
+
|
|
1124
|
+
value_object "Location" do
|
|
1125
|
+
attribute :value, String, pattern: '[^\t\n\r]'
|
|
1126
|
+
|
|
1127
|
+
invariant("a project or script is named by its path") { !value.to_s.empty? }
|
|
1128
|
+
end
|
|
1129
|
+
|
|
1130
|
+
# A task definition family, or family:revision, as the generated script takes it in TASKDEF.
|
|
1131
|
+
value_object "TaskDefinition" do
|
|
1132
|
+
attribute :value, String, pattern: '^[A-Za-z0-9_-]+(:[0-9]+)?$'
|
|
1133
|
+
end
|
|
1134
|
+
|
|
1135
|
+
# `name=tag` pairs separated by spaces, as a project without a task definition names its images.
|
|
1136
|
+
value_object "Tags" do
|
|
1137
|
+
attribute :value, String, pattern: '^[A-Za-z0-9_-]+=[A-Za-z0-9_.-]+( [A-Za-z0-9_-]+=[A-Za-z0-9_.-]+)*$'
|
|
1138
|
+
end
|
|
1139
|
+
|
|
1140
|
+
value_object "Switch" do
|
|
1141
|
+
attribute :value, Boolean
|
|
1142
|
+
end
|
|
1143
|
+
|
|
1144
|
+
value_object "Report" do
|
|
1145
|
+
attribute :value, String
|
|
1146
|
+
end
|
|
1147
|
+
|
|
1148
|
+
lifecycle :status, default: "requested" do
|
|
1149
|
+
transition "Complete" => "rolled", from: "requested"
|
|
1150
|
+
transition "Flag" => "flagged", from: "requested"
|
|
1151
|
+
transition "Verify" => "verified", from: "rolled"
|
|
1152
|
+
transition "FlagSmoke" => "flagged", from: "rolled"
|
|
1153
|
+
end
|
|
1154
|
+
|
|
1155
|
+
# `hecks deploy box_roll.run <project> [taskdef=<family[:rev]> | tags="name=tag ..."]
|
|
1156
|
+
# [skip_smoke=true] [script=<path>]`.
|
|
1157
|
+
# Runs the project's generated `deploy-box.sh`: render the Compose files, push them to the box over
|
|
1158
|
+
# SSM, start the containers and check them. A policy then requests a `SmokeRun`, unless
|
|
1159
|
+
# `skip_smoke=true`. Exit 1 when flagged, with the script's status (40 no instance, 41 the roll
|
|
1160
|
+
# failed, 42 the box unhealthy).
|
|
1161
|
+
|
|
1162
|
+
command "Run" do
|
|
1163
|
+
goal "Roll the app box and record whether it came up healthy (exit 1 when flagged: 40 no instance, 41 roll failed, 42 box unhealthy)"
|
|
1164
|
+
|
|
1165
|
+
attribute :project, Location
|
|
1166
|
+
attribute :script, Location, optional: true
|
|
1167
|
+
attribute :run, RunKey
|
|
1168
|
+
attribute :taskdef, TaskDefinition, optional: true
|
|
1169
|
+
attribute :tags, Tags, optional: true
|
|
1170
|
+
attribute :skip_smoke, Switch, optional: true
|
|
1171
|
+
|
|
1172
|
+
sets :project
|
|
1173
|
+
sets :script
|
|
1174
|
+
sets :run
|
|
1175
|
+
sets :taskdef
|
|
1176
|
+
sets :tags
|
|
1177
|
+
sets :skip_smoke
|
|
1178
|
+
|
|
1179
|
+
emits "BoxRollRequested"
|
|
1180
|
+
end
|
|
1181
|
+
|
|
1182
|
+
command "Complete" do
|
|
1183
|
+
goal "Record that the box rolled and answered healthy, and what the script said"
|
|
1184
|
+
|
|
1185
|
+
reference_to BoxRoll
|
|
1186
|
+
attribute :report, Report
|
|
1187
|
+
attribute :taskdef, TaskDefinition, optional: true
|
|
1188
|
+
attribute :smoke, Report, optional: true
|
|
1189
|
+
|
|
1190
|
+
sets :report
|
|
1191
|
+
sets :taskdef
|
|
1192
|
+
sets :smoke
|
|
1193
|
+
|
|
1194
|
+
emits "BoxRollCompleted"
|
|
1195
|
+
end
|
|
1196
|
+
|
|
1197
|
+
command "Flag" do
|
|
1198
|
+
goal "Record that the roll failed or could not start, with the script's status and what it said"
|
|
1199
|
+
|
|
1200
|
+
reference_to BoxRoll
|
|
1201
|
+
attribute :refusal, Report
|
|
1202
|
+
|
|
1203
|
+
sets :refusal
|
|
1204
|
+
|
|
1205
|
+
emits "BoxRollFlagged"
|
|
1206
|
+
end
|
|
1207
|
+
|
|
1208
|
+
command "Verify" do
|
|
1209
|
+
goal "Record that the smoke the roll requested passed"
|
|
1210
|
+
|
|
1211
|
+
reference_to BoxRoll
|
|
1212
|
+
attribute :smoke, Report
|
|
1213
|
+
|
|
1214
|
+
sets :smoke
|
|
1215
|
+
|
|
1216
|
+
emits "BoxRollVerified"
|
|
1217
|
+
end
|
|
1218
|
+
|
|
1219
|
+
command "FlagSmoke" do
|
|
1220
|
+
goal "Record that the roll took but the smoke it requested did not pass, and why"
|
|
1221
|
+
|
|
1222
|
+
reference_to BoxRoll
|
|
1223
|
+
attribute :smoke, Report
|
|
1224
|
+
attribute :refusal, Report
|
|
1225
|
+
|
|
1226
|
+
sets :smoke
|
|
1227
|
+
sets :refusal
|
|
1228
|
+
|
|
1229
|
+
emits "BoxRollSmokeFlagged"
|
|
1230
|
+
end
|
|
1231
|
+
|
|
1232
|
+
query "Verdict" do
|
|
1233
|
+
description "What one roll found: its status, and the report or the reason it was flagged."
|
|
1234
|
+
attribute :run, RunKey
|
|
1235
|
+
where(run: :run)
|
|
1236
|
+
order_by :run
|
|
1237
|
+
end
|
|
1238
|
+
|
|
1239
|
+
query "Flagged" do
|
|
1240
|
+
description "Every roll that failed or could not start."
|
|
1241
|
+
where(status: "flagged")
|
|
1242
|
+
order_by :run
|
|
1243
|
+
end
|
|
1244
|
+
end
|
|
1245
|
+
|
|
1246
|
+
policy "RollBoxWhenRequested" do
|
|
1247
|
+
on "BoxRoll.BoxRollRequested"
|
|
1248
|
+
trigger BoxRoll::DeployToolchain::RollBox, with: { run: :run }
|
|
1249
|
+
end
|
|
1250
|
+
|
|
1251
|
+
policy "RecordTheRolledBox" do
|
|
1252
|
+
on "BoxRoll.BoxRollAnswered"
|
|
1253
|
+
trigger BoxRoll::Complete, with: { report: :report, taskdef: :taskdef, smoke: :smoke }
|
|
1254
|
+
end
|
|
1255
|
+
|
|
1256
|
+
policy "RecordTheFlaggedBoxRoll" do
|
|
1257
|
+
on "BoxRoll.BoxRollRefused"
|
|
1258
|
+
trigger BoxRoll::Flag, with: { refusal: :refusal }
|
|
1259
|
+
end
|
|
1260
|
+
|
|
1261
|
+
policy "SmokeAfterBoxRoll" do
|
|
1262
|
+
on "BoxRoll.BoxRollAnswered"
|
|
1263
|
+
where { run_smoke.value }
|
|
1264
|
+
trigger SmokeRun::Run, with: { project: :project, run: :run, taskdef: :taskdef, after: :kind }
|
|
1265
|
+
end
|
|
1266
|
+
|
|
1267
|
+
policy "VerifyBoxRollOnPassedSmoke" do
|
|
1268
|
+
on "SmokeRun.SmokePassed"
|
|
1269
|
+
where { after.value == "box_roll" }
|
|
1270
|
+
trigger BoxRoll::Verify, with: { box_roll: :run, smoke: :report }
|
|
1271
|
+
end
|
|
1272
|
+
|
|
1273
|
+
policy "FlagBoxRollOnFlaggedSmoke" do
|
|
1274
|
+
on "SmokeRun.SmokeFlagged"
|
|
1275
|
+
where { after.value == "box_roll" }
|
|
1276
|
+
trigger BoxRoll::FlagSmoke, with: { box_roll: :run, smoke: :refusal, refusal: :refusal }
|
|
1277
|
+
end
|
|
1278
|
+
|
|
1279
|
+
aggregate "DataCopy" do
|
|
1280
|
+
description "One copy of a project's data from the old database into RDS: the generated restore-to-rds script run, and how it ended."
|
|
1281
|
+
|
|
1282
|
+
attribute :project, Location
|
|
1283
|
+
attribute :script, Location, optional: true
|
|
1284
|
+
attribute :run, RunKey
|
|
1285
|
+
attribute :bastion, Bastion
|
|
1286
|
+
attribute :source, Host
|
|
1287
|
+
attribute :source_secret, Secret
|
|
1288
|
+
attribute :target, Host
|
|
1289
|
+
attribute :target_secret, Secret
|
|
1290
|
+
attribute :source_db, DatabaseName, default: { value: "" }
|
|
1291
|
+
attribute :target_db, DatabaseName, default: { value: "" }
|
|
1292
|
+
attribute :force, Switch, optional: true
|
|
1293
|
+
attribute :confirm, Switch, optional: true
|
|
1294
|
+
attribute :dry_run, Switch, optional: true
|
|
1295
|
+
attribute :skip_verify, Switch, optional: true
|
|
1296
|
+
attribute :report, Report, optional: true
|
|
1297
|
+
attribute :verification, Report, optional: true
|
|
1298
|
+
attribute :refusal, Report, optional: true
|
|
1299
|
+
|
|
1300
|
+
identified_by :run
|
|
1301
|
+
|
|
1302
|
+
value_object "RunKey" do
|
|
1303
|
+
attribute :value, String, pattern: '[^ \t\n\r]'
|
|
1304
|
+
|
|
1305
|
+
invariant("a run is named") { !value.to_s.empty? }
|
|
1306
|
+
end
|
|
1307
|
+
|
|
1308
|
+
value_object "Location" do
|
|
1309
|
+
attribute :value, String, pattern: '[^\t\n\r]'
|
|
1310
|
+
|
|
1311
|
+
invariant("a project or script is named by its path") { !value.to_s.empty? }
|
|
1312
|
+
end
|
|
1313
|
+
|
|
1314
|
+
# The bastion's EC2 instance id, which both databases are reached through.
|
|
1315
|
+
value_object "Bastion" do
|
|
1316
|
+
attribute :value, String, pattern: '^i-[0-9a-f]+$'
|
|
1317
|
+
end
|
|
1318
|
+
|
|
1319
|
+
value_object "Host" do
|
|
1320
|
+
attribute :value, String, pattern: '^[A-Za-z0-9][A-Za-z0-9.-]*$'
|
|
1321
|
+
end
|
|
1322
|
+
|
|
1323
|
+
# A Secrets Manager id or ARN; the script reads the password from it, so no value travels here.
|
|
1324
|
+
value_object "Secret" do
|
|
1325
|
+
attribute :value, String, pattern: '^[^ \t\n\r]+$'
|
|
1326
|
+
end
|
|
1327
|
+
|
|
1328
|
+
# A database's name; empty leaves it to the generated script's own default.
|
|
1329
|
+
value_object "DatabaseName" do
|
|
1330
|
+
attribute :value, String, pattern: '^[A-Za-z0-9_]*$'
|
|
1331
|
+
end
|
|
1332
|
+
|
|
1333
|
+
value_object "Switch" do
|
|
1334
|
+
attribute :value, Boolean
|
|
1335
|
+
end
|
|
1336
|
+
|
|
1337
|
+
value_object "Report" do
|
|
1338
|
+
attribute :value, String
|
|
1339
|
+
end
|
|
1340
|
+
|
|
1341
|
+
lifecycle :status, default: "requested" do
|
|
1342
|
+
transition "Complete" => "restored", from: "requested"
|
|
1343
|
+
transition "Plan" => "planned", from: "requested"
|
|
1344
|
+
transition "Flag" => "flagged", from: "requested"
|
|
1345
|
+
transition "Confirm" => "verified", from: ["requested", "restored"]
|
|
1346
|
+
transition "Drift" => "drifted", from: ["requested", "restored"]
|
|
1347
|
+
transition "FlagVerification" => "flagged", from: ["requested", "restored"]
|
|
1348
|
+
end
|
|
1349
|
+
|
|
1350
|
+
# `hecks deploy data_copy.restore <project> bastion=<i-...> source=<host> source_secret=<id>
|
|
1351
|
+
# target=<host> target_secret=<id> [source_db=] [target_db=] [force=true] [confirm=true]
|
|
1352
|
+
# [dry_run=true] [skip_verify=true] [script=<path>]`. Runs the project's generated
|
|
1353
|
+
# `restore-to-rds.sh`, which overwrites the target database's schemas, so it refuses unless
|
|
1354
|
+
# `confirm=true` (the refusal names what would be overwritten); `dry_run=true` prints the plan
|
|
1355
|
+
# and runs nothing. A policy then requests a `CopyVerification` under the same run key unless
|
|
1356
|
+
# `skip_verify=true`. Exit 1 when flagged or drifted, with the script's status in the reason.
|
|
1357
|
+
command "Restore" do
|
|
1358
|
+
goal "Copy a project's schemas into RDS and record whether the copy matches (exit 1 when flagged or drifted: 60 old client, 61 target has the schema, 62 restore errors)"
|
|
1359
|
+
|
|
1360
|
+
attribute :project, Location
|
|
1361
|
+
attribute :script, Location, optional: true
|
|
1362
|
+
attribute :run, RunKey
|
|
1363
|
+
attribute :bastion, Bastion
|
|
1364
|
+
attribute :source, Host
|
|
1365
|
+
attribute :source_secret, Secret
|
|
1366
|
+
attribute :target, Host
|
|
1367
|
+
attribute :target_secret, Secret
|
|
1368
|
+
attribute :source_db, DatabaseName, default: { value: "" }
|
|
1369
|
+
attribute :target_db, DatabaseName, default: { value: "" }
|
|
1370
|
+
attribute :force, Switch, optional: true
|
|
1371
|
+
attribute :confirm, Switch, optional: true
|
|
1372
|
+
attribute :dry_run, Switch, optional: true
|
|
1373
|
+
attribute :skip_verify, Switch, optional: true
|
|
1374
|
+
|
|
1375
|
+
sets :project
|
|
1376
|
+
sets :script
|
|
1377
|
+
sets :run
|
|
1378
|
+
sets :bastion
|
|
1379
|
+
sets :source
|
|
1380
|
+
sets :source_secret
|
|
1381
|
+
sets :target
|
|
1382
|
+
sets :target_secret
|
|
1383
|
+
sets :source_db
|
|
1384
|
+
sets :target_db
|
|
1385
|
+
sets :force
|
|
1386
|
+
sets :confirm
|
|
1387
|
+
sets :dry_run
|
|
1388
|
+
sets :skip_verify
|
|
1389
|
+
|
|
1390
|
+
emits "DataCopyRequested"
|
|
1391
|
+
end
|
|
1392
|
+
|
|
1393
|
+
# `hecks deploy data_copy.verify <project> bastion= source= source_secret= target= target_secret=
|
|
1394
|
+
# [source_db=] [target_db=] [script=<path>]`. Compares the two databases with the project's
|
|
1395
|
+
# generated `verify-copy.sh` (structure and exact row counts of every table, nothing unpopulated),
|
|
1396
|
+
# read-only on both, without a restore. A policy requests the `CopyVerification` and its outcome
|
|
1397
|
+
# lands here, the same way it does after a restore. Exit 1 when `drifted` or `flagged`.
|
|
1398
|
+
command "Verify" do
|
|
1399
|
+
goal "Compare two databases without a restore and record whether they match (exit 1 when drifted: 50, or flagged: could not compare)"
|
|
1400
|
+
|
|
1401
|
+
attribute :project, Location
|
|
1402
|
+
attribute :script, Location, optional: true
|
|
1403
|
+
attribute :run, RunKey
|
|
1404
|
+
attribute :bastion, Bastion
|
|
1405
|
+
attribute :source, Host
|
|
1406
|
+
attribute :source_secret, Secret
|
|
1407
|
+
attribute :target, Host
|
|
1408
|
+
attribute :target_secret, Secret
|
|
1409
|
+
attribute :source_db, DatabaseName, default: { value: "" }
|
|
1410
|
+
attribute :target_db, DatabaseName, default: { value: "" }
|
|
1411
|
+
|
|
1412
|
+
sets :project
|
|
1413
|
+
sets :script
|
|
1414
|
+
sets :run
|
|
1415
|
+
sets :bastion
|
|
1416
|
+
sets :source
|
|
1417
|
+
sets :source_secret
|
|
1418
|
+
sets :target
|
|
1419
|
+
sets :target_secret
|
|
1420
|
+
sets :source_db
|
|
1421
|
+
sets :target_db
|
|
1422
|
+
|
|
1423
|
+
emits "DataCopyVerifyRequested"
|
|
1424
|
+
end
|
|
1425
|
+
|
|
1426
|
+
command "Complete" do
|
|
1427
|
+
goal "Record that the copy ran, and what the script said"
|
|
1428
|
+
|
|
1429
|
+
reference_to DataCopy
|
|
1430
|
+
attribute :report, Report
|
|
1431
|
+
|
|
1432
|
+
sets :report
|
|
1433
|
+
|
|
1434
|
+
emits "DataCopyCompleted"
|
|
1435
|
+
end
|
|
1436
|
+
|
|
1437
|
+
command "Plan" do
|
|
1438
|
+
goal "Record the plan a dry run printed: what would be copied and overwritten, with nothing run"
|
|
1439
|
+
|
|
1440
|
+
reference_to DataCopy
|
|
1441
|
+
attribute :report, Report
|
|
1442
|
+
|
|
1443
|
+
sets :report
|
|
1444
|
+
|
|
1445
|
+
emits "DataCopyPlanned"
|
|
1446
|
+
end
|
|
1447
|
+
|
|
1448
|
+
command "Flag" do
|
|
1449
|
+
goal "Record that the copy was refused or failed, with the script's status and what it said"
|
|
1450
|
+
|
|
1451
|
+
reference_to DataCopy
|
|
1452
|
+
attribute :refusal, Report
|
|
1453
|
+
|
|
1454
|
+
sets :refusal
|
|
1455
|
+
|
|
1456
|
+
emits "DataCopyFlagged"
|
|
1457
|
+
end
|
|
1458
|
+
|
|
1459
|
+
command "Confirm" do
|
|
1460
|
+
goal "Record that the verification the copy requested found both databases identical"
|
|
1461
|
+
|
|
1462
|
+
reference_to DataCopy
|
|
1463
|
+
attribute :verification, Report
|
|
1464
|
+
|
|
1465
|
+
sets :verification
|
|
1466
|
+
|
|
1467
|
+
emits "DataCopyVerified"
|
|
1468
|
+
end
|
|
1469
|
+
|
|
1470
|
+
command "Drift" do
|
|
1471
|
+
goal "Record that the verification the copy requested found the databases differ, and where"
|
|
1472
|
+
|
|
1473
|
+
reference_to DataCopy
|
|
1474
|
+
attribute :verification, Report
|
|
1475
|
+
attribute :refusal, Report
|
|
1476
|
+
|
|
1477
|
+
sets :verification
|
|
1478
|
+
sets :refusal
|
|
1479
|
+
|
|
1480
|
+
emits "DataCopyDrifted"
|
|
1481
|
+
end
|
|
1482
|
+
|
|
1483
|
+
command "FlagVerification" do
|
|
1484
|
+
goal "Record that the copy ran but its verification could not be made, and why"
|
|
1485
|
+
|
|
1486
|
+
reference_to DataCopy
|
|
1487
|
+
attribute :verification, Report
|
|
1488
|
+
attribute :refusal, Report
|
|
1489
|
+
|
|
1490
|
+
sets :verification
|
|
1491
|
+
sets :refusal
|
|
1492
|
+
|
|
1493
|
+
emits "DataCopyVerificationFlagged"
|
|
1494
|
+
end
|
|
1495
|
+
|
|
1496
|
+
query "Verdict" do
|
|
1497
|
+
description "What one copy found: its status, the report, and the reason it was flagged or drifted."
|
|
1498
|
+
attribute :run, RunKey
|
|
1499
|
+
where(run: :run)
|
|
1500
|
+
order_by :run
|
|
1501
|
+
end
|
|
1502
|
+
|
|
1503
|
+
query "Flagged" do
|
|
1504
|
+
description "Every copy that was refused, failed or could not be verified."
|
|
1505
|
+
where(status: "flagged")
|
|
1506
|
+
order_by :run
|
|
1507
|
+
end
|
|
1508
|
+
|
|
1509
|
+
query "Drifted" do
|
|
1510
|
+
description "Every copy whose verification found the two databases differ."
|
|
1511
|
+
where(status: "drifted")
|
|
1512
|
+
order_by :run
|
|
1513
|
+
end
|
|
1514
|
+
end
|
|
1515
|
+
|
|
1516
|
+
policy "CopyDataWhenRequested" do
|
|
1517
|
+
on "DataCopy.DataCopyRequested"
|
|
1518
|
+
trigger DataCopy::DeployToolchain::CopyData, with: { run: :run }
|
|
1519
|
+
end
|
|
1520
|
+
|
|
1521
|
+
policy "RecordTheCopiedData" do
|
|
1522
|
+
on "DataCopy.DataCopyAnswered"
|
|
1523
|
+
where { !planned.value }
|
|
1524
|
+
trigger DataCopy::Complete, with: { report: :report }
|
|
1525
|
+
end
|
|
1526
|
+
|
|
1527
|
+
policy "RecordTheCopyPlan" do
|
|
1528
|
+
on "DataCopy.DataCopyAnswered"
|
|
1529
|
+
where { planned.value }
|
|
1530
|
+
trigger DataCopy::Plan, with: { report: :report }
|
|
1531
|
+
end
|
|
1532
|
+
|
|
1533
|
+
policy "RecordTheFlaggedCopy" do
|
|
1534
|
+
on "DataCopy.DataCopyRefused"
|
|
1535
|
+
trigger DataCopy::Flag, with: { refusal: :refusal }
|
|
1536
|
+
end
|
|
1537
|
+
|
|
1538
|
+
# Declared after RecordTheCopiedData: the copy is recorded before its verification is requested,
|
|
1539
|
+
# and the request comes from the answer itself because a reaction chain is bounded.
|
|
1540
|
+
policy "VerifyAfterCopy" do
|
|
1541
|
+
on "DataCopy.DataCopyAnswered"
|
|
1542
|
+
where { run_verify.value }
|
|
1543
|
+
trigger CopyVerification::Run,
|
|
1544
|
+
with: { project: :project, run: :run, bastion: :bastion, source: :source,
|
|
1545
|
+
source_secret: :source_secret, target: :target, target_secret: :target_secret,
|
|
1546
|
+
source_db: :source_db, target_db: :target_db }
|
|
1547
|
+
end
|
|
1548
|
+
|
|
1549
|
+
policy "VerifyWhenRequested" do
|
|
1550
|
+
on "DataCopy.DataCopyVerifyRequested"
|
|
1551
|
+
trigger CopyVerification::Run,
|
|
1552
|
+
with: { project: :project, run: :run, bastion: :bastion, source: :source,
|
|
1553
|
+
source_secret: :source_secret, target: :target, target_secret: :target_secret,
|
|
1554
|
+
source_db: :source_db, target_db: :target_db }
|
|
1555
|
+
end
|
|
1556
|
+
|
|
1557
|
+
policy "VerifyDataCopyOnMatch" do
|
|
1558
|
+
on "CopyVerification.CopyMatched"
|
|
1559
|
+
trigger DataCopy::Confirm, with: { data_copy: :run, verification: :report }
|
|
1560
|
+
end
|
|
1561
|
+
|
|
1562
|
+
policy "DriftDataCopyOnDifference" do
|
|
1563
|
+
on "CopyVerification.CopyDrifted"
|
|
1564
|
+
trigger DataCopy::Drift, with: { data_copy: :run, verification: :report, refusal: :report }
|
|
1565
|
+
end
|
|
1566
|
+
|
|
1567
|
+
policy "FlagDataCopyOnUnverified" do
|
|
1568
|
+
on "CopyVerification.CopyUnverified"
|
|
1569
|
+
trigger DataCopy::FlagVerification, with: { data_copy: :run, verification: :refusal, refusal: :refusal }
|
|
1570
|
+
end
|
|
1571
|
+
|
|
1572
|
+
aggregate "CopyVerification" do
|
|
1573
|
+
description "One comparison of two databases holding a project's schemas: the generated verify-copy script run, and what it found."
|
|
1574
|
+
|
|
1575
|
+
attribute :project, Location
|
|
1576
|
+
attribute :script, Location, optional: true
|
|
1577
|
+
attribute :run, RunKey
|
|
1578
|
+
attribute :bastion, Bastion
|
|
1579
|
+
attribute :source, Host
|
|
1580
|
+
attribute :source_secret, Secret
|
|
1581
|
+
attribute :target, Host
|
|
1582
|
+
attribute :target_secret, Secret
|
|
1583
|
+
attribute :source_db, DatabaseName, default: { value: "" }
|
|
1584
|
+
attribute :target_db, DatabaseName, default: { value: "" }
|
|
1585
|
+
attribute :report, Report, optional: true
|
|
1586
|
+
attribute :refusal, Report, optional: true
|
|
1587
|
+
identified_by :run
|
|
1588
|
+
|
|
1589
|
+
value_object "RunKey" do
|
|
1590
|
+
attribute :value, String, pattern: '[^ \t\n\r]'
|
|
1591
|
+
|
|
1592
|
+
invariant("a run is named") { !value.to_s.empty? }
|
|
1593
|
+
end
|
|
1594
|
+
|
|
1595
|
+
value_object "Location" do
|
|
1596
|
+
attribute :value, String, pattern: '[^\t\n\r]'
|
|
1597
|
+
|
|
1598
|
+
invariant("a project or script is named by its path") { !value.to_s.empty? }
|
|
1599
|
+
end
|
|
1600
|
+
|
|
1601
|
+
value_object "Bastion" do
|
|
1602
|
+
attribute :value, String, pattern: '^i-[0-9a-f]+$'
|
|
1603
|
+
end
|
|
1604
|
+
|
|
1605
|
+
value_object "Host" do
|
|
1606
|
+
attribute :value, String, pattern: '^[A-Za-z0-9][A-Za-z0-9.-]*$'
|
|
1607
|
+
end
|
|
1608
|
+
|
|
1609
|
+
value_object "Secret" do
|
|
1610
|
+
attribute :value, String, pattern: '^[^ \t\n\r]+$'
|
|
1611
|
+
end
|
|
1612
|
+
|
|
1613
|
+
# A database's name; empty leaves it to the generated script's own default.
|
|
1614
|
+
value_object "DatabaseName" do
|
|
1615
|
+
attribute :value, String, pattern: '^[A-Za-z0-9_]*$'
|
|
1616
|
+
end
|
|
1617
|
+
|
|
1618
|
+
value_object "Report" do
|
|
1619
|
+
attribute :value, String
|
|
1620
|
+
end
|
|
1621
|
+
|
|
1622
|
+
lifecycle :status, default: "requested" do
|
|
1623
|
+
transition "Match" => "verified", from: "requested"
|
|
1624
|
+
transition "Differ" => "drifted", from: "requested"
|
|
1625
|
+
transition "Flag" => "flagged", from: "requested"
|
|
1626
|
+
end
|
|
1627
|
+
|
|
1628
|
+
# Runs the project's generated `verify-copy.sh`: structure and exact row counts of every table,
|
|
1629
|
+
# nothing unpopulated. Read-only on both databases. A `DataCopy` requests it, by `data_copy.verify`
|
|
1630
|
+
# or after a restore, and takes its outcome by the shared run key.
|
|
1631
|
+
command "Run" do
|
|
1632
|
+
goal "Compare two databases and record whether they match (exit 1 when drifted: 50 differ, or flagged: could not compare)"
|
|
1633
|
+
|
|
1634
|
+
attribute :project, Location
|
|
1635
|
+
attribute :script, Location, optional: true
|
|
1636
|
+
attribute :run, RunKey
|
|
1637
|
+
attribute :bastion, Bastion
|
|
1638
|
+
attribute :source, Host
|
|
1639
|
+
attribute :source_secret, Secret
|
|
1640
|
+
attribute :target, Host
|
|
1641
|
+
attribute :target_secret, Secret
|
|
1642
|
+
attribute :source_db, DatabaseName, default: { value: "" }
|
|
1643
|
+
attribute :target_db, DatabaseName, default: { value: "" }
|
|
1644
|
+
|
|
1645
|
+
sets :project
|
|
1646
|
+
sets :script
|
|
1647
|
+
sets :run
|
|
1648
|
+
sets :bastion
|
|
1649
|
+
sets :source
|
|
1650
|
+
sets :source_secret
|
|
1651
|
+
sets :target
|
|
1652
|
+
sets :target_secret
|
|
1653
|
+
sets :source_db
|
|
1654
|
+
sets :target_db
|
|
1655
|
+
|
|
1656
|
+
emits "CopyVerificationRequested"
|
|
1657
|
+
end
|
|
1658
|
+
|
|
1659
|
+
command "Match" do
|
|
1660
|
+
goal "Record that both databases hold the same structure and row counts"
|
|
1661
|
+
|
|
1662
|
+
reference_to CopyVerification
|
|
1663
|
+
attribute :report, Report
|
|
1664
|
+
|
|
1665
|
+
sets :report
|
|
1666
|
+
|
|
1667
|
+
emits "CopyMatched"
|
|
1668
|
+
end
|
|
1669
|
+
|
|
1670
|
+
command "Differ" do
|
|
1671
|
+
goal "Record that the databases differ, with what the script printed"
|
|
1672
|
+
|
|
1673
|
+
reference_to CopyVerification
|
|
1674
|
+
attribute :report, Report
|
|
1675
|
+
|
|
1676
|
+
sets :report
|
|
1677
|
+
|
|
1678
|
+
emits "CopyDrifted"
|
|
1679
|
+
end
|
|
1680
|
+
|
|
1681
|
+
command "Flag" do
|
|
1682
|
+
goal "Record that the comparison could not be made, and why"
|
|
1683
|
+
|
|
1684
|
+
reference_to CopyVerification
|
|
1685
|
+
attribute :refusal, Report
|
|
1686
|
+
|
|
1687
|
+
sets :refusal
|
|
1688
|
+
|
|
1689
|
+
emits "CopyUnverified"
|
|
1690
|
+
end
|
|
1691
|
+
|
|
1692
|
+
query "Verdict" do
|
|
1693
|
+
description "What one comparison found: its status, and the report or the reason it was flagged."
|
|
1694
|
+
attribute :run, RunKey
|
|
1695
|
+
where(run: :run)
|
|
1696
|
+
order_by :run
|
|
1697
|
+
end
|
|
1698
|
+
|
|
1699
|
+
query "Drifted" do
|
|
1700
|
+
description "Every comparison that found the databases differ."
|
|
1701
|
+
where(status: "drifted")
|
|
1702
|
+
order_by :run
|
|
1703
|
+
end
|
|
1704
|
+
|
|
1705
|
+
query "Flagged" do
|
|
1706
|
+
description "Every comparison that could not be made."
|
|
1707
|
+
where(status: "flagged")
|
|
1708
|
+
order_by :run
|
|
1709
|
+
end
|
|
1710
|
+
end
|
|
1711
|
+
|
|
1712
|
+
policy "CompareCopyWhenRequested" do
|
|
1713
|
+
on "CopyVerification.CopyVerificationRequested"
|
|
1714
|
+
trigger CopyVerification::DeployToolchain::CompareCopy, with: { run: :run }
|
|
1715
|
+
end
|
|
1716
|
+
|
|
1717
|
+
policy "RecordTheMatchingCopy" do
|
|
1718
|
+
on "CopyVerification.CopyVerificationAnswered"
|
|
1719
|
+
where { !drifted.value }
|
|
1720
|
+
trigger CopyVerification::Match, with: { report: :report }
|
|
1721
|
+
end
|
|
1722
|
+
|
|
1723
|
+
policy "RecordTheDriftedCopy" do
|
|
1724
|
+
on "CopyVerification.CopyVerificationAnswered"
|
|
1725
|
+
where { drifted.value }
|
|
1726
|
+
trigger CopyVerification::Differ, with: { report: :report }
|
|
1727
|
+
end
|
|
1728
|
+
|
|
1729
|
+
policy "RecordTheUnverifiedCopy" do
|
|
1730
|
+
on "CopyVerification.CopyVerificationRefused"
|
|
1731
|
+
trigger CopyVerification::Flag, with: { refusal: :refusal }
|
|
1732
|
+
end
|
|
1733
|
+
|
|
1734
|
+
aggregate "BluebookDiff" do
|
|
1735
|
+
description "One report of which bluebook releases a deploy changes: the running image's bluebooks against the ones about to ship."
|
|
1736
|
+
|
|
1737
|
+
attribute :project, Location
|
|
1738
|
+
attribute :script, Location, optional: true
|
|
1739
|
+
attribute :run, RunKey
|
|
1740
|
+
attribute :old, Location, optional: true
|
|
1741
|
+
attribute :new, Location, optional: true
|
|
1742
|
+
attribute :report, Report, optional: true
|
|
1743
|
+
attribute :refusal, Report, optional: true
|
|
1744
|
+
identified_by :run
|
|
1745
|
+
|
|
1746
|
+
value_object "RunKey" do
|
|
1747
|
+
attribute :value, String, pattern: '[^ \t\n\r]'
|
|
1748
|
+
|
|
1749
|
+
invariant("a run is named") { !value.to_s.empty? }
|
|
1750
|
+
end
|
|
1751
|
+
|
|
1752
|
+
value_object "Location" do
|
|
1753
|
+
attribute :value, String, pattern: '[^\t\n\r]'
|
|
1754
|
+
|
|
1755
|
+
invariant("a project, script or file is named by its path") { !value.to_s.empty? }
|
|
1756
|
+
end
|
|
1757
|
+
|
|
1758
|
+
value_object "Report" do
|
|
1759
|
+
attribute :value, String
|
|
1760
|
+
end
|
|
1761
|
+
|
|
1762
|
+
# No outcome is a failure: a report that could not be made is `unavailable`, never `flagged`
|
|
1763
|
+
# (only a malformed request is), because the era check gates a deploy, not this report.
|
|
1764
|
+
lifecycle :status, default: "requested" do
|
|
1765
|
+
transition "Match" => "unchanged", from: "requested"
|
|
1766
|
+
transition "Differ" => "changed", from: "requested"
|
|
1767
|
+
transition "Withhold" => "unavailable", from: "requested"
|
|
1768
|
+
transition "Flag" => "flagged", from: "requested"
|
|
1769
|
+
end
|
|
1770
|
+
|
|
1771
|
+
# `hecks deploy bluebook_diff.run <project> [old=<json> new=<json>] [script=<path>]`. With
|
|
1772
|
+
# `old` and `new` (two `hecks package.verify` outputs) the comparison is made here, offline.
|
|
1773
|
+
# With neither, the project's `bluebooks-diff.sh` reads the running image's bluebooks from the
|
|
1774
|
+
# registry (read-only) and the local image's, and its report is classified. It never fails:
|
|
1775
|
+
# `unchanged`, `changed` or `unavailable` (a lookup failed, or the script is missing). Only
|
|
1776
|
+
# giving one of `old` and `new` is refused, as `flagged`.
|
|
1777
|
+
command "Run" do
|
|
1778
|
+
goal "Report which bluebook releases a deploy changes and record it (never a failure: unchanged, changed or unavailable)"
|
|
1779
|
+
|
|
1780
|
+
attribute :project, Location
|
|
1781
|
+
attribute :script, Location, optional: true
|
|
1782
|
+
attribute :run, RunKey
|
|
1783
|
+
attribute :old, Location, optional: true
|
|
1784
|
+
attribute :new, Location, optional: true
|
|
1785
|
+
|
|
1786
|
+
sets :project
|
|
1787
|
+
sets :script
|
|
1788
|
+
sets :run
|
|
1789
|
+
sets :old
|
|
1790
|
+
sets :new
|
|
1791
|
+
|
|
1792
|
+
emits "BluebookDiffRequested"
|
|
1793
|
+
end
|
|
1794
|
+
|
|
1795
|
+
command "Match" do
|
|
1796
|
+
goal "Record that the deploy changes no bluebook"
|
|
1797
|
+
|
|
1798
|
+
reference_to BluebookDiff
|
|
1799
|
+
attribute :report, Report
|
|
1800
|
+
|
|
1801
|
+
sets :report
|
|
1802
|
+
|
|
1803
|
+
emits "BluebooksUnchanged"
|
|
1804
|
+
end
|
|
1805
|
+
|
|
1806
|
+
command "Differ" do
|
|
1807
|
+
goal "Record the bluebooks the deploy adds, removes, re-versions or changes in content"
|
|
1808
|
+
|
|
1809
|
+
reference_to BluebookDiff
|
|
1810
|
+
attribute :report, Report
|
|
1811
|
+
|
|
1812
|
+
sets :report
|
|
1813
|
+
|
|
1814
|
+
emits "BluebooksChanged"
|
|
1815
|
+
end
|
|
1816
|
+
|
|
1817
|
+
command "Withhold" do
|
|
1818
|
+
goal "Record that the report could not be made, and why; the deploy carries on"
|
|
1819
|
+
|
|
1820
|
+
reference_to BluebookDiff
|
|
1821
|
+
attribute :report, Report
|
|
1822
|
+
|
|
1823
|
+
sets :report
|
|
1824
|
+
|
|
1825
|
+
emits "BluebookDiffUnavailable"
|
|
1826
|
+
end
|
|
1827
|
+
|
|
1828
|
+
command "Flag" do
|
|
1829
|
+
goal "Record that the request was malformed (only one of old and new), and why"
|
|
1830
|
+
|
|
1831
|
+
reference_to BluebookDiff
|
|
1832
|
+
attribute :refusal, Report
|
|
1833
|
+
|
|
1834
|
+
sets :refusal
|
|
1835
|
+
|
|
1836
|
+
emits "BluebookDiffFlagged"
|
|
1837
|
+
end
|
|
1838
|
+
|
|
1839
|
+
query "Verdict" do
|
|
1840
|
+
description "What one report found: its status and the report."
|
|
1841
|
+
attribute :run, RunKey
|
|
1842
|
+
where(run: :run)
|
|
1843
|
+
order_by :run
|
|
1844
|
+
end
|
|
1845
|
+
|
|
1846
|
+
query "Changed" do
|
|
1847
|
+
description "Every report that found a bluebook release changed."
|
|
1848
|
+
where(status: "changed")
|
|
1849
|
+
order_by :run
|
|
1850
|
+
end
|
|
1851
|
+
end
|
|
1852
|
+
|
|
1853
|
+
policy "DiffWhenRequested" do
|
|
1854
|
+
on "BluebookDiff.BluebookDiffRequested"
|
|
1855
|
+
trigger BluebookDiff::DeployToolchain::CompareBluebooks, with: { run: :run }
|
|
1856
|
+
end
|
|
1857
|
+
|
|
1858
|
+
policy "RecordTheUnchangedBluebooks" do
|
|
1859
|
+
on "BluebookDiff.BluebookDiffAnswered"
|
|
1860
|
+
where { outcome.value == "unchanged" }
|
|
1861
|
+
trigger BluebookDiff::Match, with: { report: :report }
|
|
1862
|
+
end
|
|
1863
|
+
|
|
1864
|
+
policy "RecordTheChangedBluebooks" do
|
|
1865
|
+
on "BluebookDiff.BluebookDiffAnswered"
|
|
1866
|
+
where { outcome.value == "changed" }
|
|
1867
|
+
trigger BluebookDiff::Differ, with: { report: :report }
|
|
1868
|
+
end
|
|
1869
|
+
|
|
1870
|
+
policy "RecordTheUnavailableDiff" do
|
|
1871
|
+
on "BluebookDiff.BluebookDiffAnswered"
|
|
1872
|
+
where { outcome.value == "unavailable" }
|
|
1873
|
+
trigger BluebookDiff::Withhold, with: { report: :report }
|
|
1874
|
+
end
|
|
1875
|
+
|
|
1876
|
+
policy "RecordTheFlaggedDiff" do
|
|
1877
|
+
on "BluebookDiff.BluebookDiffRefused"
|
|
1878
|
+
trigger BluebookDiff::Flag, with: { refusal: :refusal }
|
|
1879
|
+
end
|
|
1880
|
+
|
|
1881
|
+
aggregate "PreviewRun" do
|
|
1882
|
+
description "One run of a project's preview script: the per-branch preview stack named, found, listed, deployed, destroyed or signed in to, and how it ended."
|
|
1883
|
+
|
|
1884
|
+
attribute :project, Location
|
|
1885
|
+
attribute :script, Location, optional: true
|
|
1886
|
+
attribute :run, RunKey
|
|
1887
|
+
attribute :action, Action
|
|
1888
|
+
attribute :branch, Branch, optional: true
|
|
1889
|
+
attribute :confirm, Switch, optional: true
|
|
1890
|
+
attribute :dry_run, Switch, optional: true
|
|
1891
|
+
attribute :report, Report, optional: true
|
|
1892
|
+
attribute :refusal, Report, optional: true
|
|
1893
|
+
identified_by :run
|
|
1894
|
+
|
|
1895
|
+
value_object "RunKey" do
|
|
1896
|
+
attribute :value, String, pattern: '[^ \t\n\r]'
|
|
1897
|
+
|
|
1898
|
+
invariant("a run is named") { !value.to_s.empty? }
|
|
1899
|
+
end
|
|
1900
|
+
|
|
1901
|
+
value_object "Location" do
|
|
1902
|
+
attribute :value, String, pattern: '[^\t\n\r]'
|
|
1903
|
+
|
|
1904
|
+
invariant("a project or script is named by its path") { !value.to_s.empty? }
|
|
1905
|
+
end
|
|
1906
|
+
|
|
1907
|
+
# What the script is asked to do; each command sets its own.
|
|
1908
|
+
value_object "Action" do
|
|
1909
|
+
attribute :value, String, pattern: '^(name|url|list|deploy|destroy|login)$'
|
|
1910
|
+
end
|
|
1911
|
+
|
|
1912
|
+
# A git branch name. A preview is never of a production branch.
|
|
1913
|
+
value_object "Branch" do
|
|
1914
|
+
attribute :value, String, pattern: '^[^ \t\n\r]+$'
|
|
1915
|
+
|
|
1916
|
+
invariant("a preview is never of main or master") { value != "main" && value != "master" }
|
|
1917
|
+
end
|
|
1918
|
+
|
|
1919
|
+
value_object "Switch" do
|
|
1920
|
+
attribute :value, Boolean
|
|
1921
|
+
end
|
|
1922
|
+
|
|
1923
|
+
value_object "Report" do
|
|
1924
|
+
attribute :value, String
|
|
1925
|
+
end
|
|
1926
|
+
|
|
1927
|
+
lifecycle :status, default: "requested" do
|
|
1928
|
+
transition "RecordName" => "named", from: "requested"
|
|
1929
|
+
transition "RecordUrl" => "located", from: "requested"
|
|
1930
|
+
transition "RecordList" => "listed", from: "requested"
|
|
1931
|
+
transition "RecordDeploy" => "deployed", from: "requested"
|
|
1932
|
+
transition "RecordDestroy" => "destroyed", from: "requested"
|
|
1933
|
+
transition "RecordLogin" => "signed_in", from: "requested"
|
|
1934
|
+
transition "Plan" => "planned", from: "requested"
|
|
1935
|
+
transition "Flag" => "flagged", from: "requested"
|
|
1936
|
+
end
|
|
1937
|
+
|
|
1938
|
+
# `hecks deploy preview_run.<name|url|list|deploy|destroy|login> <project> [branch=<name>]
|
|
1939
|
+
# [confirm=true] [dry_run=true] [script=<path>]`. Runs the project's `preview.sh` with that
|
|
1940
|
+
# verb; the script derives every name from the branch (the checked-out one unless `branch=`),
|
|
1941
|
+
# refuses `main` and `master`, and only touches stacks under its own prefix. `name`, `url` and
|
|
1942
|
+
# `list` read; `deploy`, `destroy` and `login` write to AWS or read its secrets, so they refuse
|
|
1943
|
+
# unless `confirm=true`, and `dry_run=true` prints the plan and runs nothing. Exit 1 when
|
|
1944
|
+
# flagged, with the script's status (1: it refused or failed) in the reason.
|
|
1945
|
+
command "Name" do
|
|
1946
|
+
goal "Print the names a branch's preview would have, from the project's preview script (no AWS call)"
|
|
1947
|
+
|
|
1948
|
+
attribute :project, Location
|
|
1949
|
+
attribute :script, Location, optional: true
|
|
1950
|
+
attribute :run, RunKey
|
|
1951
|
+
attribute :action, Action, default: { value: "name" }
|
|
1952
|
+
attribute :branch, Branch, optional: true
|
|
1953
|
+
attribute :confirm, Switch, optional: true
|
|
1954
|
+
attribute :dry_run, Switch, optional: true
|
|
1955
|
+
|
|
1956
|
+
sets :project
|
|
1957
|
+
sets :script
|
|
1958
|
+
sets :run
|
|
1959
|
+
sets :action
|
|
1960
|
+
sets :branch
|
|
1961
|
+
sets :confirm
|
|
1962
|
+
sets :dry_run
|
|
1963
|
+
|
|
1964
|
+
emits "PreviewNameRequested"
|
|
1965
|
+
end
|
|
1966
|
+
|
|
1967
|
+
command "Url" do
|
|
1968
|
+
goal "Print a branch's preview URL, read from its stack"
|
|
1969
|
+
|
|
1970
|
+
attribute :project, Location
|
|
1971
|
+
attribute :script, Location, optional: true
|
|
1972
|
+
attribute :run, RunKey
|
|
1973
|
+
attribute :action, Action, default: { value: "url" }
|
|
1974
|
+
attribute :branch, Branch, optional: true
|
|
1975
|
+
attribute :confirm, Switch, optional: true
|
|
1976
|
+
attribute :dry_run, Switch, optional: true
|
|
1977
|
+
|
|
1978
|
+
sets :project
|
|
1979
|
+
sets :script
|
|
1980
|
+
sets :run
|
|
1981
|
+
sets :action
|
|
1982
|
+
sets :branch
|
|
1983
|
+
sets :confirm
|
|
1984
|
+
sets :dry_run
|
|
1985
|
+
|
|
1986
|
+
emits "PreviewUrlRequested"
|
|
1987
|
+
end
|
|
1988
|
+
|
|
1989
|
+
command "List" do
|
|
1990
|
+
goal "List every preview stack that exists, read from AWS"
|
|
1991
|
+
|
|
1992
|
+
attribute :project, Location
|
|
1993
|
+
attribute :script, Location, optional: true
|
|
1994
|
+
attribute :run, RunKey
|
|
1995
|
+
attribute :action, Action, default: { value: "list" }
|
|
1996
|
+
attribute :branch, Branch, optional: true
|
|
1997
|
+
attribute :confirm, Switch, optional: true
|
|
1998
|
+
attribute :dry_run, Switch, optional: true
|
|
1999
|
+
|
|
2000
|
+
sets :project
|
|
2001
|
+
sets :script
|
|
2002
|
+
sets :run
|
|
2003
|
+
sets :action
|
|
2004
|
+
sets :branch
|
|
2005
|
+
sets :confirm
|
|
2006
|
+
sets :dry_run
|
|
2007
|
+
|
|
2008
|
+
emits "PreviewListRequested"
|
|
2009
|
+
end
|
|
2010
|
+
|
|
2011
|
+
command "Deploy" do
|
|
2012
|
+
goal "Create or update a branch's preview from the images built locally (writes to AWS; refuses unless confirm=true)"
|
|
2013
|
+
|
|
2014
|
+
attribute :project, Location
|
|
2015
|
+
attribute :script, Location, optional: true
|
|
2016
|
+
attribute :run, RunKey
|
|
2017
|
+
attribute :action, Action, default: { value: "deploy" }
|
|
2018
|
+
attribute :branch, Branch, optional: true
|
|
2019
|
+
attribute :confirm, Switch, optional: true
|
|
2020
|
+
attribute :dry_run, Switch, optional: true
|
|
2021
|
+
|
|
2022
|
+
sets :project
|
|
2023
|
+
sets :script
|
|
2024
|
+
sets :run
|
|
2025
|
+
sets :action
|
|
2026
|
+
sets :branch
|
|
2027
|
+
sets :confirm
|
|
2028
|
+
sets :dry_run
|
|
2029
|
+
|
|
2030
|
+
emits "PreviewDeployRequested"
|
|
2031
|
+
end
|
|
2032
|
+
|
|
2033
|
+
command "Destroy" do
|
|
2034
|
+
goal "Tear a branch's preview stack down (writes to AWS; refuses unless confirm=true)"
|
|
2035
|
+
|
|
2036
|
+
attribute :project, Location
|
|
2037
|
+
attribute :script, Location, optional: true
|
|
2038
|
+
attribute :run, RunKey
|
|
2039
|
+
attribute :action, Action, default: { value: "destroy" }
|
|
2040
|
+
attribute :branch, Branch, optional: true
|
|
2041
|
+
attribute :confirm, Switch, optional: true
|
|
2042
|
+
attribute :dry_run, Switch, optional: true
|
|
2043
|
+
|
|
2044
|
+
sets :project
|
|
2045
|
+
sets :script
|
|
2046
|
+
sets :run
|
|
2047
|
+
sets :action
|
|
2048
|
+
sets :branch
|
|
2049
|
+
sets :confirm
|
|
2050
|
+
sets :dry_run
|
|
2051
|
+
|
|
2052
|
+
emits "PreviewDestroyRequested"
|
|
2053
|
+
end
|
|
2054
|
+
|
|
2055
|
+
command "Login" do
|
|
2056
|
+
goal "Mint a line that signs the deployer in to a preview as its first admin (reads its session secret; refuses unless confirm=true)"
|
|
2057
|
+
|
|
2058
|
+
attribute :project, Location
|
|
2059
|
+
attribute :script, Location, optional: true
|
|
2060
|
+
attribute :run, RunKey
|
|
2061
|
+
attribute :action, Action, default: { value: "login" }
|
|
2062
|
+
attribute :branch, Branch, optional: true
|
|
2063
|
+
attribute :confirm, Switch, optional: true
|
|
2064
|
+
attribute :dry_run, Switch, optional: true
|
|
2065
|
+
|
|
2066
|
+
sets :project
|
|
2067
|
+
sets :script
|
|
2068
|
+
sets :run
|
|
2069
|
+
sets :action
|
|
2070
|
+
sets :branch
|
|
2071
|
+
sets :confirm
|
|
2072
|
+
sets :dry_run
|
|
2073
|
+
|
|
2074
|
+
emits "PreviewLoginRequested"
|
|
2075
|
+
end
|
|
2076
|
+
|
|
2077
|
+
command "RecordName" do
|
|
2078
|
+
goal "Record the names the script printed"
|
|
2079
|
+
|
|
2080
|
+
reference_to PreviewRun
|
|
2081
|
+
attribute :report, Report
|
|
2082
|
+
|
|
2083
|
+
sets :report
|
|
2084
|
+
|
|
2085
|
+
emits "PreviewNamed"
|
|
2086
|
+
end
|
|
2087
|
+
|
|
2088
|
+
command "RecordUrl" do
|
|
2089
|
+
goal "Record the preview URL the script printed"
|
|
2090
|
+
|
|
2091
|
+
reference_to PreviewRun
|
|
2092
|
+
attribute :report, Report
|
|
2093
|
+
|
|
2094
|
+
sets :report
|
|
2095
|
+
|
|
2096
|
+
emits "PreviewLocated"
|
|
2097
|
+
end
|
|
2098
|
+
|
|
2099
|
+
command "RecordList" do
|
|
2100
|
+
goal "Record the preview stacks the script listed"
|
|
2101
|
+
|
|
2102
|
+
reference_to PreviewRun
|
|
2103
|
+
attribute :report, Report
|
|
2104
|
+
|
|
2105
|
+
sets :report
|
|
2106
|
+
|
|
2107
|
+
emits "PreviewListed"
|
|
2108
|
+
end
|
|
2109
|
+
|
|
2110
|
+
command "RecordDeploy" do
|
|
2111
|
+
goal "Record that the preview was created or updated, and what the script said"
|
|
2112
|
+
|
|
2113
|
+
reference_to PreviewRun
|
|
2114
|
+
attribute :report, Report
|
|
2115
|
+
|
|
2116
|
+
sets :report
|
|
2117
|
+
|
|
2118
|
+
emits "PreviewDeployed"
|
|
2119
|
+
end
|
|
2120
|
+
|
|
2121
|
+
command "RecordDestroy" do
|
|
2122
|
+
goal "Record that the preview was torn down, and what the script said"
|
|
2123
|
+
|
|
2124
|
+
reference_to PreviewRun
|
|
2125
|
+
attribute :report, Report
|
|
2126
|
+
|
|
2127
|
+
sets :report
|
|
2128
|
+
|
|
2129
|
+
emits "PreviewDestroyed"
|
|
2130
|
+
end
|
|
2131
|
+
|
|
2132
|
+
command "RecordLogin" do
|
|
2133
|
+
goal "Record that the sign-in line was minted, and where the script put it"
|
|
2134
|
+
|
|
2135
|
+
reference_to PreviewRun
|
|
2136
|
+
attribute :report, Report
|
|
2137
|
+
|
|
2138
|
+
sets :report
|
|
2139
|
+
|
|
2140
|
+
emits "PreviewSignedIn"
|
|
2141
|
+
end
|
|
2142
|
+
|
|
2143
|
+
command "Plan" do
|
|
2144
|
+
goal "Record the plan a dry run printed: what would run and where, with nothing run"
|
|
2145
|
+
|
|
2146
|
+
reference_to PreviewRun
|
|
2147
|
+
attribute :report, Report
|
|
2148
|
+
|
|
2149
|
+
sets :report
|
|
2150
|
+
|
|
2151
|
+
emits "PreviewPlanned"
|
|
2152
|
+
end
|
|
2153
|
+
|
|
2154
|
+
command "Flag" do
|
|
2155
|
+
goal "Record that the run was refused or failed, with the script's status and what it said"
|
|
2156
|
+
|
|
2157
|
+
reference_to PreviewRun
|
|
2158
|
+
attribute :refusal, Report
|
|
2159
|
+
|
|
2160
|
+
sets :refusal
|
|
2161
|
+
|
|
2162
|
+
emits "PreviewFlagged"
|
|
2163
|
+
end
|
|
2164
|
+
|
|
2165
|
+
query "Verdict" do
|
|
2166
|
+
description "What one run found: its status, the report, and the reason it was flagged."
|
|
2167
|
+
attribute :run, RunKey
|
|
2168
|
+
where(run: :run)
|
|
2169
|
+
order_by :run
|
|
2170
|
+
end
|
|
2171
|
+
|
|
2172
|
+
query "Flagged" do
|
|
2173
|
+
description "Every preview run that was refused or failed."
|
|
2174
|
+
where(status: "flagged")
|
|
2175
|
+
order_by :run
|
|
2176
|
+
end
|
|
2177
|
+
end
|
|
2178
|
+
|
|
2179
|
+
policy "RunPreviewWhenNameRequested" do
|
|
2180
|
+
on "PreviewRun.PreviewNameRequested"
|
|
2181
|
+
trigger PreviewRun::DeployToolchain::RunPreview, with: { run: :run }
|
|
2182
|
+
end
|
|
2183
|
+
|
|
2184
|
+
policy "RunPreviewWhenUrlRequested" do
|
|
2185
|
+
on "PreviewRun.PreviewUrlRequested"
|
|
2186
|
+
trigger PreviewRun::DeployToolchain::RunPreview, with: { run: :run }
|
|
2187
|
+
end
|
|
2188
|
+
|
|
2189
|
+
policy "RunPreviewWhenListRequested" do
|
|
2190
|
+
on "PreviewRun.PreviewListRequested"
|
|
2191
|
+
trigger PreviewRun::DeployToolchain::RunPreview, with: { run: :run }
|
|
2192
|
+
end
|
|
2193
|
+
|
|
2194
|
+
policy "RunPreviewWhenDeployRequested" do
|
|
2195
|
+
on "PreviewRun.PreviewDeployRequested"
|
|
2196
|
+
trigger PreviewRun::DeployToolchain::RunPreview, with: { run: :run }
|
|
2197
|
+
end
|
|
2198
|
+
|
|
2199
|
+
policy "RunPreviewWhenDestroyRequested" do
|
|
2200
|
+
on "PreviewRun.PreviewDestroyRequested"
|
|
2201
|
+
trigger PreviewRun::DeployToolchain::RunPreview, with: { run: :run }
|
|
2202
|
+
end
|
|
2203
|
+
|
|
2204
|
+
policy "RunPreviewWhenLoginRequested" do
|
|
2205
|
+
on "PreviewRun.PreviewLoginRequested"
|
|
2206
|
+
trigger PreviewRun::DeployToolchain::RunPreview, with: { run: :run }
|
|
2207
|
+
end
|
|
2208
|
+
|
|
2209
|
+
policy "RecordThePreviewName" do
|
|
2210
|
+
on "PreviewRun.PreviewRunAnswered"
|
|
2211
|
+
where { outcome.value == "named" }
|
|
2212
|
+
trigger PreviewRun::RecordName, with: { report: :report }
|
|
2213
|
+
end
|
|
2214
|
+
|
|
2215
|
+
policy "RecordThePreviewUrl" do
|
|
2216
|
+
on "PreviewRun.PreviewRunAnswered"
|
|
2217
|
+
where { outcome.value == "located" }
|
|
2218
|
+
trigger PreviewRun::RecordUrl, with: { report: :report }
|
|
2219
|
+
end
|
|
2220
|
+
|
|
2221
|
+
policy "RecordThePreviewList" do
|
|
2222
|
+
on "PreviewRun.PreviewRunAnswered"
|
|
2223
|
+
where { outcome.value == "listed" }
|
|
2224
|
+
trigger PreviewRun::RecordList, with: { report: :report }
|
|
2225
|
+
end
|
|
2226
|
+
|
|
2227
|
+
policy "RecordTheDeployedPreview" do
|
|
2228
|
+
on "PreviewRun.PreviewRunAnswered"
|
|
2229
|
+
where { outcome.value == "deployed" }
|
|
2230
|
+
trigger PreviewRun::RecordDeploy, with: { report: :report }
|
|
2231
|
+
end
|
|
2232
|
+
|
|
2233
|
+
policy "RecordTheDestroyedPreview" do
|
|
2234
|
+
on "PreviewRun.PreviewRunAnswered"
|
|
2235
|
+
where { outcome.value == "destroyed" }
|
|
2236
|
+
trigger PreviewRun::RecordDestroy, with: { report: :report }
|
|
2237
|
+
end
|
|
2238
|
+
|
|
2239
|
+
policy "RecordThePreviewLogin" do
|
|
2240
|
+
on "PreviewRun.PreviewRunAnswered"
|
|
2241
|
+
where { outcome.value == "signed_in" }
|
|
2242
|
+
trigger PreviewRun::RecordLogin, with: { report: :report }
|
|
2243
|
+
end
|
|
2244
|
+
|
|
2245
|
+
policy "RecordThePreviewPlan" do
|
|
2246
|
+
on "PreviewRun.PreviewRunAnswered"
|
|
2247
|
+
where { outcome.value == "planned" }
|
|
2248
|
+
trigger PreviewRun::Plan, with: { report: :report }
|
|
2249
|
+
end
|
|
2250
|
+
|
|
2251
|
+
policy "RecordTheFlaggedPreview" do
|
|
2252
|
+
on "PreviewRun.PreviewRunRefused"
|
|
2253
|
+
trigger PreviewRun::Flag, with: { refusal: :refusal }
|
|
2254
|
+
end
|
|
2255
|
+
|
|
2256
|
+
aggregate "CompanionRoll" do
|
|
2257
|
+
description "One roll of a companion Compose project (a second project beside the app's) onto the box: the project's deploy script run, and how it ended."
|
|
2258
|
+
|
|
2259
|
+
attribute :project, Location
|
|
2260
|
+
attribute :script, Location, optional: true
|
|
2261
|
+
attribute :run, RunKey
|
|
2262
|
+
attribute :companion, Companion, default: { value: "umami" }
|
|
2263
|
+
attribute :taskdef, TaskDefinition
|
|
2264
|
+
attribute :confirm, Switch, optional: true
|
|
2265
|
+
attribute :dry_run, Switch, optional: true
|
|
2266
|
+
attribute :report, Report, optional: true
|
|
2267
|
+
attribute :refusal, Report, optional: true
|
|
2268
|
+
identified_by :run
|
|
2269
|
+
|
|
2270
|
+
value_object "RunKey" do
|
|
2271
|
+
attribute :value, String, pattern: '[^ \t\n\r]'
|
|
2272
|
+
|
|
2273
|
+
invariant("a run is named") { !value.to_s.empty? }
|
|
2274
|
+
end
|
|
2275
|
+
|
|
2276
|
+
value_object "Location" do
|
|
2277
|
+
attribute :value, String, pattern: '[^\t\n\r]'
|
|
2278
|
+
|
|
2279
|
+
invariant("a project or script is named by its path") { !value.to_s.empty? }
|
|
2280
|
+
end
|
|
2281
|
+
|
|
2282
|
+
# The companion's name; its script is `deploy-<companion>.sh`.
|
|
2283
|
+
value_object "Companion" do
|
|
2284
|
+
attribute :value, String, pattern: '^[a-z0-9][a-z0-9-]*$'
|
|
2285
|
+
end
|
|
2286
|
+
|
|
2287
|
+
# A task definition revision (or family) the companion's image and settings are read from.
|
|
2288
|
+
value_object "TaskDefinition" do
|
|
2289
|
+
attribute :value, String, pattern: '^[A-Za-z0-9_-]+(:[0-9]+)?$'
|
|
2290
|
+
end
|
|
2291
|
+
|
|
2292
|
+
value_object "Switch" do
|
|
2293
|
+
attribute :value, Boolean
|
|
2294
|
+
end
|
|
2295
|
+
|
|
2296
|
+
value_object "Report" do
|
|
2297
|
+
attribute :value, String
|
|
2298
|
+
end
|
|
2299
|
+
|
|
2300
|
+
lifecycle :status, default: "requested" do
|
|
2301
|
+
transition "Complete" => "rolled", from: "requested"
|
|
2302
|
+
transition "Plan" => "planned", from: "requested"
|
|
2303
|
+
transition "Flag" => "flagged", from: "requested"
|
|
2304
|
+
end
|
|
2305
|
+
|
|
2306
|
+
# `hecks deploy companion_roll.run <project> taskdef=<family:rev> [companion=umami] [confirm=true]
|
|
2307
|
+
# [dry_run=true] [script=<path>]`. Runs the project's `deploy-<companion>.sh`, which reads the
|
|
2308
|
+
# companion's image and settings from the task definition, sends a Compose project and its secret
|
|
2309
|
+
# references to the box over SSM (secret values never travel; the box resolves them), starts it
|
|
2310
|
+
# beside the app's project, and checks it on the box. It writes to the box, so it refuses unless
|
|
2311
|
+
# `confirm=true`; `dry_run=true` prints the plan and runs nothing. Exit 1 when flagged.
|
|
2312
|
+
command "Run" do
|
|
2313
|
+
goal "Roll a companion Compose project onto the box and record the check on the box (refuses unless confirm=true)"
|
|
2314
|
+
|
|
2315
|
+
attribute :project, Location
|
|
2316
|
+
attribute :script, Location, optional: true
|
|
2317
|
+
attribute :run, RunKey
|
|
2318
|
+
attribute :companion, Companion, default: { value: "umami" }
|
|
2319
|
+
attribute :taskdef, TaskDefinition
|
|
2320
|
+
attribute :confirm, Switch, optional: true
|
|
2321
|
+
attribute :dry_run, Switch, optional: true
|
|
2322
|
+
|
|
2323
|
+
sets :project
|
|
2324
|
+
sets :script
|
|
2325
|
+
sets :run
|
|
2326
|
+
sets :companion
|
|
2327
|
+
sets :taskdef
|
|
2328
|
+
sets :confirm
|
|
2329
|
+
sets :dry_run
|
|
2330
|
+
|
|
2331
|
+
emits "CompanionRollRequested"
|
|
2332
|
+
end
|
|
2333
|
+
|
|
2334
|
+
command "Complete" do
|
|
2335
|
+
goal "Record that the companion rolled and passed its check on the box, and what the script printed"
|
|
2336
|
+
|
|
2337
|
+
reference_to CompanionRoll
|
|
2338
|
+
attribute :report, Report
|
|
2339
|
+
|
|
2340
|
+
sets :report
|
|
2341
|
+
|
|
2342
|
+
emits "CompanionRolled"
|
|
2343
|
+
end
|
|
2344
|
+
|
|
2345
|
+
command "Plan" do
|
|
2346
|
+
goal "Record the plan a dry run printed: what would be rolled and where, with nothing run"
|
|
2347
|
+
|
|
2348
|
+
reference_to CompanionRoll
|
|
2349
|
+
attribute :report, Report
|
|
2350
|
+
|
|
2351
|
+
sets :report
|
|
2352
|
+
|
|
2353
|
+
emits "CompanionRollPlanned"
|
|
2354
|
+
end
|
|
2355
|
+
|
|
2356
|
+
command "Flag" do
|
|
2357
|
+
goal "Record that the roll was refused, or the companion did not roll or is not healthy, and why"
|
|
2358
|
+
|
|
2359
|
+
reference_to CompanionRoll
|
|
2360
|
+
attribute :refusal, Report
|
|
2361
|
+
|
|
2362
|
+
sets :refusal
|
|
2363
|
+
|
|
2364
|
+
emits "CompanionRollFlagged"
|
|
2365
|
+
end
|
|
2366
|
+
|
|
2367
|
+
query "Verdict" do
|
|
2368
|
+
description "What one roll found: its status, the report, and the reason it was flagged."
|
|
2369
|
+
attribute :run, RunKey
|
|
2370
|
+
where(run: :run)
|
|
2371
|
+
order_by :run
|
|
2372
|
+
end
|
|
2373
|
+
|
|
2374
|
+
query "Flagged" do
|
|
2375
|
+
description "Every companion roll that was refused, did not roll or was not healthy."
|
|
2376
|
+
where(status: "flagged")
|
|
2377
|
+
order_by :run
|
|
2378
|
+
end
|
|
2379
|
+
end
|
|
2380
|
+
|
|
2381
|
+
policy "RollCompanionWhenRequested" do
|
|
2382
|
+
on "CompanionRoll.CompanionRollRequested"
|
|
2383
|
+
trigger CompanionRoll::DeployToolchain::RollCompanion, with: { run: :run }
|
|
2384
|
+
end
|
|
2385
|
+
|
|
2386
|
+
policy "RecordTheRolledCompanion" do
|
|
2387
|
+
on "CompanionRoll.CompanionRollAnswered"
|
|
2388
|
+
where { !planned.value }
|
|
2389
|
+
trigger CompanionRoll::Complete, with: { report: :report }
|
|
2390
|
+
end
|
|
2391
|
+
|
|
2392
|
+
policy "RecordTheCompanionPlan" do
|
|
2393
|
+
on "CompanionRoll.CompanionRollAnswered"
|
|
2394
|
+
where { planned.value }
|
|
2395
|
+
trigger CompanionRoll::Plan, with: { report: :report }
|
|
2396
|
+
end
|
|
2397
|
+
|
|
2398
|
+
policy "RecordTheFlaggedCompanionRoll" do
|
|
2399
|
+
on "CompanionRoll.CompanionRollRefused"
|
|
2400
|
+
trigger CompanionRoll::Flag, with: { refusal: :refusal }
|
|
2401
|
+
end
|
|
2402
|
+
|
|
773
2403
|
aggregate "TemplateComparison" do
|
|
774
2404
|
description "One offline comparison of two CloudFormation templates, by logical id."
|
|
775
2405
|
|