cloudfall 0.2.0__py3-none-any.whl

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 (165) hide show
  1. cloudfall/__init__.py +19 -0
  2. cloudfall/__main__.py +5 -0
  3. cloudfall/_bundled/schemas/v1/alert-rule.schema.json +63 -0
  4. cloudfall/_bundled/schemas/v1/application.schema.json +60 -0
  5. cloudfall/_bundled/schemas/v1/artifact.schema.json +71 -0
  6. cloudfall/_bundled/schemas/v1/backup-receipt.schema.json +58 -0
  7. cloudfall/_bundled/schemas/v1/common.schema.json +115 -0
  8. cloudfall/_bundled/schemas/v1/component.schema.json +234 -0
  9. cloudfall/_bundled/schemas/v1/deployment-receipt.schema.json +53 -0
  10. cloudfall/_bundled/schemas/v1/domain.schema.json +208 -0
  11. cloudfall/_bundled/schemas/v1/environment-receipt.schema.json +55 -0
  12. cloudfall/_bundled/schemas/v1/logging-stack.schema.json +473 -0
  13. cloudfall/_bundled/schemas/v1/observed-domain.schema.json +158 -0
  14. cloudfall/_bundled/schemas/v1/observed-server.schema.json +627 -0
  15. cloudfall/_bundled/schemas/v1/operator-policy.schema.json +97 -0
  16. cloudfall/_bundled/schemas/v1/operator-proposal.schema.json +253 -0
  17. cloudfall/_bundled/schemas/v1/release-receipt.schema.json +66 -0
  18. cloudfall/_bundled/schemas/v1/server-type.schema.json +289 -0
  19. cloudfall/_bundled/schemas/v1/server.schema.json +146 -0
  20. cloudfall/_bundled/schemas/v1/service.schema.json +222 -0
  21. cloudfall/_bundled/schemas/v1/ssh-public-key.schema.json +55 -0
  22. cloudfall/agent_tools.py +868 -0
  23. cloudfall/arguments.py +92 -0
  24. cloudfall/audit.py +905 -0
  25. cloudfall/authoring.py +341 -0
  26. cloudfall/cli.py +1748 -0
  27. cloudfall/commands.py +257 -0
  28. cloudfall/cutover.py +370 -0
  29. cloudfall/dashboard.py +407 -0
  30. cloudfall/dashboard_server.py +201 -0
  31. cloudfall/domain.py +1157 -0
  32. cloudfall/importer.py +906 -0
  33. cloudfall/inventory.py +2116 -0
  34. cloudfall/lifecycle.py +874 -0
  35. cloudfall/mcp_server.py +704 -0
  36. cloudfall/migrate.py +730 -0
  37. cloudfall/observation.py +143 -0
  38. cloudfall/operations.py +1228 -0
  39. cloudfall/operator.py +1099 -0
  40. cloudfall/project.py +841 -0
  41. cloudfall/py.typed +0 -0
  42. cloudfall/render_api.py +338 -0
  43. cloudfall/resources.py +67 -0
  44. cloudfall/secrets.py +262 -0
  45. cloudfall/service_evidence.py +659 -0
  46. cloudfall/validation.py +1340 -0
  47. cloudfall-0.2.0.dist-info/METADATA +408 -0
  48. cloudfall-0.2.0.dist-info/RECORD +165 -0
  49. cloudfall-0.2.0.dist-info/WHEEL +4 -0
  50. cloudfall-0.2.0.dist-info/entry_points.txt +4 -0
  51. cloudfall-0.2.0.dist-info/licenses/LICENSE +661 -0
  52. cloudfall_engine/__init__.py +5 -0
  53. cloudfall_engine/__main__.py +5 -0
  54. cloudfall_engine/_bundled/ansible/ansible.cfg +7 -0
  55. cloudfall_engine/_bundled/ansible/playbooks/backup.yml +86 -0
  56. cloudfall_engine/_bundled/ansible/playbooks/baseline.yml +35 -0
  57. cloudfall_engine/_bundled/ansible/playbooks/bootstrap.yml +24 -0
  58. cloudfall_engine/_bundled/ansible/playbooks/data.yml +28 -0
  59. cloudfall_engine/_bundled/ansible/playbooks/deploy.yml +35 -0
  60. cloudfall_engine/_bundled/ansible/playbooks/domains.yml +21 -0
  61. cloudfall_engine/_bundled/ansible/playbooks/health.yml +31 -0
  62. cloudfall_engine/_bundled/ansible/playbooks/inspect.yml +15 -0
  63. cloudfall_engine/_bundled/ansible/playbooks/logging.yml +35 -0
  64. cloudfall_engine/_bundled/ansible/playbooks/restart.yml +31 -0
  65. cloudfall_engine/_bundled/ansible/playbooks/rollback.yml +33 -0
  66. cloudfall_engine/_bundled/ansible/playbooks/services.yml +37 -0
  67. cloudfall_engine/_bundled/ansible/playbooks/time.yml +9 -0
  68. cloudfall_engine/_bundled/ansible/roles/cloudfall_access/defaults/main.yml +2 -0
  69. cloudfall_engine/_bundled/ansible/roles/cloudfall_access/handlers/main.yml +5 -0
  70. cloudfall_engine/_bundled/ansible/roles/cloudfall_access/meta/argument_specs.yml +34 -0
  71. cloudfall_engine/_bundled/ansible/roles/cloudfall_access/meta/main.yml +13 -0
  72. cloudfall_engine/_bundled/ansible/roles/cloudfall_access/tasks/main.yml +59 -0
  73. cloudfall_engine/_bundled/ansible/roles/cloudfall_bootstrap/defaults/main.yml +11 -0
  74. cloudfall_engine/_bundled/ansible/roles/cloudfall_bootstrap/meta/argument_specs.yml +52 -0
  75. cloudfall_engine/_bundled/ansible/roles/cloudfall_bootstrap/meta/main.yml +13 -0
  76. cloudfall_engine/_bundled/ansible/roles/cloudfall_bootstrap/tasks/main.yml +99 -0
  77. cloudfall_engine/_bundled/ansible/roles/cloudfall_data_migration/defaults/main.yml +15 -0
  78. cloudfall_engine/_bundled/ansible/roles/cloudfall_data_migration/meta/argument_specs.yml +36 -0
  79. cloudfall_engine/_bundled/ansible/roles/cloudfall_data_migration/meta/main.yml +13 -0
  80. cloudfall_engine/_bundled/ansible/roles/cloudfall_data_migration/tasks/main.yml +201 -0
  81. cloudfall_engine/_bundled/ansible/roles/cloudfall_deploy/defaults/main.yml +10 -0
  82. cloudfall_engine/_bundled/ansible/roles/cloudfall_deploy/meta/argument_specs.yml +125 -0
  83. cloudfall_engine/_bundled/ansible/roles/cloudfall_deploy/meta/main.yml +13 -0
  84. cloudfall_engine/_bundled/ansible/roles/cloudfall_deploy/tasks/health.yml +3 -0
  85. cloudfall_engine/_bundled/ansible/roles/cloudfall_deploy/tasks/health_gate.yml +37 -0
  86. cloudfall_engine/_bundled/ansible/roles/cloudfall_deploy/tasks/main.yml +285 -0
  87. cloudfall_engine/_bundled/ansible/roles/cloudfall_deploy/tasks/restart.yml +17 -0
  88. cloudfall_engine/_bundled/ansible/roles/cloudfall_deploy/tasks/rollback.yml +52 -0
  89. cloudfall_engine/_bundled/ansible/roles/cloudfall_deploy/templates/component.service.j2 +24 -0
  90. cloudfall_engine/_bundled/ansible/roles/cloudfall_firewall/defaults/main.yml +2 -0
  91. cloudfall_engine/_bundled/ansible/roles/cloudfall_firewall/handlers/main.yml +5 -0
  92. cloudfall_engine/_bundled/ansible/roles/cloudfall_firewall/meta/argument_specs.yml +33 -0
  93. cloudfall_engine/_bundled/ansible/roles/cloudfall_firewall/meta/main.yml +13 -0
  94. cloudfall_engine/_bundled/ansible/roles/cloudfall_firewall/tasks/main.yml +51 -0
  95. cloudfall_engine/_bundled/ansible/roles/cloudfall_firewall/templates/nftables.conf.j2 +31 -0
  96. cloudfall_engine/_bundled/ansible/roles/cloudfall_inspect/defaults/main.yml +6 -0
  97. cloudfall_engine/_bundled/ansible/roles/cloudfall_inspect/meta/argument_specs.yml +24 -0
  98. cloudfall_engine/_bundled/ansible/roles/cloudfall_inspect/meta/main.yml +13 -0
  99. cloudfall_engine/_bundled/ansible/roles/cloudfall_inspect/tasks/main.yml +643 -0
  100. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_backend/defaults/main.yml +6 -0
  101. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_backend/handlers/main.yml +29 -0
  102. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_backend/meta/argument_specs.yml +227 -0
  103. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_backend/meta/main.yml +11 -0
  104. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_backend/tasks/main.yml +487 -0
  105. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_backend/templates/alertmanager-defaults.j2 +2 -0
  106. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_backend/templates/alertmanager.yaml.j2 +33 -0
  107. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_backend/templates/grafana.ini.j2 +20 -0
  108. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_backend/templates/loki-datasource.yml.j2 +11 -0
  109. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_backend/templates/loki-systemd-override.conf.j2 +3 -0
  110. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_backend/templates/loki.yaml.j2 +49 -0
  111. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_backend/templates/nginx-loki-gateway.conf.j2 +45 -0
  112. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_backend/templates/prometheus-datasource.yml.j2 +11 -0
  113. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_backend/templates/prometheus-defaults.j2 +2 -0
  114. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_backend/templates/prometheus-rules.yaml.j2 +20 -0
  115. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_backend/templates/prometheus.yaml.j2 +24 -0
  116. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_collector/defaults/main.yml +7 -0
  117. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_collector/handlers/main.yml +6 -0
  118. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_collector/meta/argument_specs.yml +122 -0
  119. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_collector/meta/main.yml +11 -0
  120. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_collector/tasks/main.yml +224 -0
  121. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_collector/templates/alloy-systemd-override.conf.j2 +3 -0
  122. cloudfall_engine/_bundled/ansible/roles/cloudfall_logging_collector/templates/config.alloy.j2 +127 -0
  123. cloudfall_engine/_bundled/ansible/roles/cloudfall_nginx_site/defaults/main.yml +4 -0
  124. cloudfall_engine/_bundled/ansible/roles/cloudfall_nginx_site/handlers/main.yml +5 -0
  125. cloudfall_engine/_bundled/ansible/roles/cloudfall_nginx_site/meta/argument_specs.yml +69 -0
  126. cloudfall_engine/_bundled/ansible/roles/cloudfall_nginx_site/meta/main.yml +13 -0
  127. cloudfall_engine/_bundled/ansible/roles/cloudfall_nginx_site/tasks/main.yml +212 -0
  128. cloudfall_engine/_bundled/ansible/roles/cloudfall_nginx_site/templates/domain-site.conf.j2 +54 -0
  129. cloudfall_engine/_bundled/ansible/roles/cloudfall_postgresql/defaults/main.yml +6 -0
  130. cloudfall_engine/_bundled/ansible/roles/cloudfall_postgresql/handlers/main.yml +9 -0
  131. cloudfall_engine/_bundled/ansible/roles/cloudfall_postgresql/meta/argument_specs.yml +91 -0
  132. cloudfall_engine/_bundled/ansible/roles/cloudfall_postgresql/meta/main.yml +13 -0
  133. cloudfall_engine/_bundled/ansible/roles/cloudfall_postgresql/tasks/main.yml +295 -0
  134. cloudfall_engine/_bundled/ansible/roles/cloudfall_postgresql/templates/backup.service.j2 +11 -0
  135. cloudfall_engine/_bundled/ansible/roles/cloudfall_postgresql/templates/backup.sh.j2 +17 -0
  136. cloudfall_engine/_bundled/ansible/roles/cloudfall_postgresql/templates/backup.timer.j2 +10 -0
  137. cloudfall_engine/_bundled/ansible/roles/cloudfall_postgresql/templates/cloudfall.conf.j2 +3 -0
  138. cloudfall_engine/_bundled/ansible/roles/cloudfall_postgresql/templates/restore-check.service.j2 +11 -0
  139. cloudfall_engine/_bundled/ansible/roles/cloudfall_postgresql/templates/restore-check.sh.j2 +27 -0
  140. cloudfall_engine/_bundled/ansible/roles/cloudfall_postgresql/templates/restore-check.timer.j2 +10 -0
  141. cloudfall_engine/_bundled/ansible/roles/cloudfall_redis/defaults/main.yml +2 -0
  142. cloudfall_engine/_bundled/ansible/roles/cloudfall_redis/handlers/main.yml +6 -0
  143. cloudfall_engine/_bundled/ansible/roles/cloudfall_redis/meta/argument_specs.yml +68 -0
  144. cloudfall_engine/_bundled/ansible/roles/cloudfall_redis/meta/main.yml +13 -0
  145. cloudfall_engine/_bundled/ansible/roles/cloudfall_redis/tasks/main.yml +148 -0
  146. cloudfall_engine/_bundled/ansible/roles/cloudfall_redis/templates/backup.service.j2 +11 -0
  147. cloudfall_engine/_bundled/ansible/roles/cloudfall_redis/templates/backup.sh.j2 +14 -0
  148. cloudfall_engine/_bundled/ansible/roles/cloudfall_redis/templates/backup.timer.j2 +10 -0
  149. cloudfall_engine/_bundled/ansible/roles/cloudfall_redis/templates/cloudfall.conf.j2 +6 -0
  150. cloudfall_engine/_bundled/ansible/roles/cloudfall_redis/templates/restore-check.service.j2 +11 -0
  151. cloudfall_engine/_bundled/ansible/roles/cloudfall_redis/templates/restore-check.sh.j2 +15 -0
  152. cloudfall_engine/_bundled/ansible/roles/cloudfall_redis/templates/restore-check.timer.j2 +10 -0
  153. cloudfall_engine/_bundled/ansible/roles/cloudfall_time/defaults/main.yml +2 -0
  154. cloudfall_engine/_bundled/ansible/roles/cloudfall_time/meta/argument_specs.yml +10 -0
  155. cloudfall_engine/_bundled/ansible/roles/cloudfall_time/meta/main.yml +13 -0
  156. cloudfall_engine/_bundled/ansible/roles/cloudfall_time/tasks/main.yml +119 -0
  157. cloudfall_engine/_bundled/ansible/roles/cloudfall_unattended_upgrades/defaults/main.yml +3 -0
  158. cloudfall_engine/_bundled/ansible/roles/cloudfall_unattended_upgrades/meta/argument_specs.yml +8 -0
  159. cloudfall_engine/_bundled/ansible/roles/cloudfall_unattended_upgrades/meta/main.yml +13 -0
  160. cloudfall_engine/_bundled/ansible/roles/cloudfall_unattended_upgrades/tasks/main.yml +37 -0
  161. cloudfall_engine/ansible_inventory.py +253 -0
  162. cloudfall_engine/artifact.py +258 -0
  163. cloudfall_engine/cli.py +273 -0
  164. cloudfall_engine/playbook.py +175 -0
  165. cloudfall_engine/py.typed +0 -0
@@ -0,0 +1,285 @@
1
+ ---
2
+ - name: Validate deployment target and declaration
3
+ ansible.builtin.assert:
4
+ that:
5
+ - ansible_facts["distribution"] == "Debian"
6
+ - ansible_facts["service_mgr"] == "systemd"
7
+ - ansible_facts["architecture"] == "x86_64"
8
+ - cloudfall_deploy_component.service.manager == "systemd"
9
+ - cloudfall_deploy_component.runtime.type == "python"
10
+ - cloudfall_deploy_component.runtime.packageManager == "uv"
11
+ - cloudfall_deploy_release | length > 0
12
+ - cloudfall_deploy_artifact_sha256 | length == 64
13
+ fail_msg: >-
14
+ The first deploy slice supports Debian x86_64 hosts and Python
15
+ components managed by uv under systemd
16
+
17
+ - name: Compute release paths
18
+ ansible.builtin.set_fact:
19
+ cloudfall_deploy_release_directory: >-
20
+ {{ cloudfall_deploy_component.install_root }}/releases/{{
21
+ cloudfall_deploy_release
22
+ }}
23
+ cloudfall_deploy_current_link: >-
24
+ {{ cloudfall_deploy_component.install_root }}/current
25
+ cloudfall_deploy_shared_directory: >-
26
+ {{ cloudfall_deploy_component.install_root }}/shared
27
+ cloudfall_deploy_staging_archive: >-
28
+ /tmp/cloudfall-{{ cloudfall_deploy_component.id }}-{{
29
+ cloudfall_deploy_release
30
+ }}.tar.gz
31
+
32
+ - name: Check the installed uv version
33
+ ansible.builtin.command:
34
+ argv:
35
+ - /usr/local/bin/uv
36
+ - --version
37
+ register: cloudfall_deploy_uv_installed
38
+ changed_when: false
39
+ failed_when: false
40
+
41
+ - name: Install the pinned uv release
42
+ when: >-
43
+ cloudfall_deploy_uv_installed.rc != 0
44
+ or cloudfall_deploy_uv_version not in cloudfall_deploy_uv_installed.stdout
45
+ block:
46
+ - name: Download the pinned uv archive
47
+ ansible.builtin.get_url:
48
+ url: "{{ cloudfall_deploy_uv_url }}"
49
+ dest: /tmp/cloudfall-uv.tar.gz
50
+ checksum: "sha256:{{ cloudfall_deploy_uv_sha256 }}"
51
+ mode: "0644"
52
+
53
+ - name: Unpack the uv archive
54
+ ansible.builtin.unarchive:
55
+ src: /tmp/cloudfall-uv.tar.gz
56
+ dest: /tmp
57
+ remote_src: true
58
+
59
+ - name: Install the uv binary
60
+ ansible.builtin.copy:
61
+ src: /tmp/uv-x86_64-unknown-linux-gnu/uv
62
+ dest: /usr/local/bin/uv
63
+ remote_src: true
64
+ owner: root
65
+ group: root
66
+ mode: "0755"
67
+
68
+ - name: Create release and shared directories
69
+ ansible.builtin.file:
70
+ path: "{{ item }}"
71
+ state: directory
72
+ owner: "{{ cloudfall_deploy_component.application_user }}"
73
+ group: "{{ cloudfall_deploy_component.application_user }}"
74
+ mode: "0755"
75
+ loop:
76
+ - "{{ cloudfall_deploy_component.install_root }}/releases"
77
+ - "{{ cloudfall_deploy_release_directory }}"
78
+ - "{{ cloudfall_deploy_shared_directory }}"
79
+ - "{{ cloudfall_deploy_shared_directory }}/env"
80
+ - "{{ cloudfall_deploy_shared_directory }}/uv"
81
+
82
+ - name: Transfer the release artifact
83
+ ansible.builtin.copy:
84
+ src: "{{ cloudfall_deploy_artifact_archive }}"
85
+ dest: "{{ cloudfall_deploy_staging_archive }}"
86
+ owner: root
87
+ group: root
88
+ mode: "0644"
89
+
90
+ - name: Verify the transferred artifact digest
91
+ ansible.builtin.stat:
92
+ path: "{{ cloudfall_deploy_staging_archive }}"
93
+ checksum_algorithm: sha256
94
+ register: cloudfall_deploy_staged
95
+
96
+ - name: Refuse a release whose digest does not match its metadata
97
+ ansible.builtin.assert:
98
+ that:
99
+ - cloudfall_deploy_staged.stat.checksum
100
+ == cloudfall_deploy_artifact_sha256
101
+ fail_msg: >-
102
+ Transferred artifact digest does not match the declared
103
+ artifact metadata
104
+
105
+ - name: Unpack the verified release
106
+ ansible.builtin.unarchive:
107
+ src: "{{ cloudfall_deploy_staging_archive }}"
108
+ dest: "{{ cloudfall_deploy_release_directory }}"
109
+ remote_src: true
110
+ owner: "{{ cloudfall_deploy_component.application_user }}"
111
+ group: "{{ cloudfall_deploy_component.application_user }}"
112
+
113
+ - name: Remove the staged archive
114
+ ansible.builtin.file:
115
+ path: "{{ cloudfall_deploy_staging_archive }}"
116
+ state: absent
117
+
118
+ - name: Install the component environment file
119
+ ansible.builtin.copy:
120
+ src: "{{ cloudfall_deploy_environment_file }}"
121
+ dest: >-
122
+ {{ cloudfall_deploy_shared_directory }}/env/{{
123
+ cloudfall_deploy_component.id
124
+ }}.env
125
+ owner: root
126
+ group: "{{ cloudfall_deploy_component.application_user }}"
127
+ mode: "0640"
128
+ when: cloudfall_deploy_environment_file | length > 0
129
+
130
+ - name: Materialize locked dependencies for the release
131
+ ansible.builtin.command:
132
+ argv:
133
+ - /usr/local/bin/uv
134
+ - sync
135
+ - --frozen
136
+ - --no-dev
137
+ chdir: "{{ cloudfall_deploy_release_directory }}"
138
+ become: true
139
+ become_user: "{{ cloudfall_deploy_component.application_user }}"
140
+ environment:
141
+ HOME: "{{ cloudfall_deploy_shared_directory }}"
142
+ UV_CACHE_DIR: "{{ cloudfall_deploy_shared_directory }}/uv/cache"
143
+ UV_PYTHON_INSTALL_DIR: "{{ cloudfall_deploy_shared_directory }}/uv/python"
144
+ changed_when: true
145
+
146
+ - name: Inspect the currently active release
147
+ ansible.builtin.stat:
148
+ path: "{{ cloudfall_deploy_current_link }}"
149
+ follow: false
150
+ get_checksum: false
151
+ register: cloudfall_deploy_current
152
+
153
+ - name: Record the rollback candidate
154
+ ansible.builtin.set_fact:
155
+ cloudfall_deploy_previous_directory: >-
156
+ {{
157
+ cloudfall_deploy_current.stat.lnk_source
158
+ if cloudfall_deploy_current.stat.exists
159
+ and cloudfall_deploy_current.stat.islnk
160
+ else ""
161
+ }}
162
+
163
+ - name: Install the managed service unit
164
+ ansible.builtin.template:
165
+ src: component.service.j2
166
+ dest: >-
167
+ /etc/systemd/system/{{ cloudfall_deploy_component.service.name }}.service
168
+ owner: root
169
+ group: root
170
+ mode: "0644"
171
+
172
+ - name: Activate the release behind the health gate
173
+ block:
174
+ - name: Switch the current release symlink
175
+ ansible.builtin.file:
176
+ src: "{{ cloudfall_deploy_release_directory }}"
177
+ dest: "{{ cloudfall_deploy_current_link }}"
178
+ state: link
179
+ force: true
180
+ follow: false
181
+
182
+ - name: Restart the component service
183
+ ansible.builtin.systemd_service:
184
+ name: "{{ cloudfall_deploy_component.service.name }}.service"
185
+ enabled: true
186
+ state: restarted
187
+ daemon_reload: true
188
+
189
+ - name: Gate the release on its declared health check
190
+ ansible.builtin.uri:
191
+ url: >-
192
+ {{ cloudfall_deploy_component.healthCheck.scheme }}://127.0.0.1:{{
193
+ cloudfall_deploy_component.healthCheck.port
194
+ }}{{ cloudfall_deploy_component.healthCheck.path }}
195
+ status_code: >-
196
+ {{ cloudfall_deploy_component.healthCheck.expectedStatuses }}
197
+ timeout: "{{ cloudfall_deploy_component.healthCheck.timeoutSeconds }}"
198
+ return_content: false
199
+ register: cloudfall_deploy_health
200
+ retries: >-
201
+ {{ cloudfall_deploy_component.healthCheck.attempts | int - 1 }}
202
+ delay: 3
203
+ until: >-
204
+ cloudfall_deploy_health.status
205
+ in cloudfall_deploy_component.healthCheck.expectedStatuses
206
+ when: not ansible_check_mode
207
+ rescue:
208
+ - name: Restore the previous release symlink
209
+ ansible.builtin.file:
210
+ src: "{{ cloudfall_deploy_previous_directory }}"
211
+ dest: "{{ cloudfall_deploy_current_link }}"
212
+ state: link
213
+ force: true
214
+ follow: false
215
+ when: cloudfall_deploy_previous_directory | length > 0
216
+
217
+ - name: Restart the restored release
218
+ ansible.builtin.systemd_service:
219
+ name: "{{ cloudfall_deploy_component.service.name }}.service"
220
+ state: restarted
221
+ daemon_reload: true
222
+ when: cloudfall_deploy_previous_directory | length > 0
223
+
224
+ - name: Stop the failed first release
225
+ ansible.builtin.systemd_service:
226
+ name: "{{ cloudfall_deploy_component.service.name }}.service"
227
+ state: stopped
228
+ when: cloudfall_deploy_previous_directory | length == 0
229
+
230
+ - name: Fail the deployment after rolling back
231
+ ansible.builtin.fail:
232
+ msg: >-
233
+ Release {{ cloudfall_deploy_release }} failed its health check and
234
+ was {{
235
+ 'rolled back to '
236
+ ~ (cloudfall_deploy_previous_directory | basename)
237
+ if cloudfall_deploy_previous_directory | length > 0
238
+ else 'stopped because no previous release exists'
239
+ }}
240
+
241
+ - name: Ensure the controller receipt directory exists
242
+ ansible.builtin.file:
243
+ path: "{{ cloudfall_deploy_receipt_directory }}"
244
+ state: directory
245
+ mode: "0755"
246
+ delegate_to: localhost
247
+ become: false
248
+ when: cloudfall_deploy_receipt_directory | length > 0
249
+
250
+ - name: Write the release receipt on the controller
251
+ ansible.builtin.copy:
252
+ content: >-
253
+ {{
254
+ {
255
+ 'apiVersion': 'cloudfall/v1',
256
+ 'kind': 'ReleaseReceipt',
257
+ 'metadata': {
258
+ 'id': cloudfall_deploy_component.id,
259
+ 'description': 'Release receipt written by cloudfall_deploy',
260
+ },
261
+ 'spec': {
262
+ 'component': cloudfall_deploy_component.id,
263
+ 'server': cloudfall_server_id,
264
+ 'release': cloudfall_deploy_release,
265
+ 'artifactSha256': cloudfall_deploy_artifact_sha256,
266
+ 'deployedAt': ansible_facts['date_time']['iso8601'],
267
+ 'previousRelease': (
268
+ (cloudfall_deploy_previous_directory | basename)
269
+ if cloudfall_deploy_previous_directory | length > 0
270
+ else none
271
+ ),
272
+ 'healthCheckPassed': true,
273
+ },
274
+ } | to_nice_json
275
+ }}
276
+ dest: >-
277
+ {{ cloudfall_deploy_receipt_directory }}/{{
278
+ cloudfall_deploy_component.id
279
+ }}.json
280
+ mode: "0644"
281
+ delegate_to: localhost
282
+ become: false
283
+ when:
284
+ - not ansible_check_mode
285
+ - cloudfall_deploy_receipt_directory | length > 0
@@ -0,0 +1,17 @@
1
+ ---
2
+ - name: Validate restart target and declaration
3
+ ansible.builtin.assert:
4
+ that:
5
+ - ansible_facts["distribution"] == "Debian"
6
+ - ansible_facts["service_mgr"] == "systemd"
7
+ - cloudfall_deploy_component.service.manager == "systemd"
8
+ fail_msg: Restart requires a Debian systemd host
9
+
10
+ - name: Restart the component service
11
+ ansible.builtin.systemd_service:
12
+ name: "{{ cloudfall_deploy_component.service.name }}.service"
13
+ state: restarted
14
+ daemon_reload: true
15
+
16
+ - name: Require the restarted component to pass its health check
17
+ ansible.builtin.include_tasks: health_gate.yml
@@ -0,0 +1,52 @@
1
+ ---
2
+ - name: Validate rollback target and declaration
3
+ ansible.builtin.assert:
4
+ that:
5
+ - ansible_facts["distribution"] == "Debian"
6
+ - ansible_facts["service_mgr"] == "systemd"
7
+ - cloudfall_deploy_component.service.manager == "systemd"
8
+ - cloudfall_deploy_release | length > 0
9
+ fail_msg: >-
10
+ Rollback requires a Debian systemd host and an explicit release id
11
+
12
+ - name: Compute rollback paths
13
+ ansible.builtin.set_fact:
14
+ cloudfall_deploy_release_directory: >-
15
+ {{ cloudfall_deploy_component.install_root }}/releases/{{
16
+ cloudfall_deploy_release
17
+ }}
18
+ cloudfall_deploy_current_link: >-
19
+ {{ cloudfall_deploy_component.install_root }}/current
20
+
21
+ - name: Inspect the rollback target release
22
+ ansible.builtin.stat:
23
+ path: "{{ cloudfall_deploy_release_directory }}"
24
+ get_checksum: false
25
+ register: cloudfall_deploy_rollback_target
26
+
27
+ - name: Require the rollback target on the server
28
+ ansible.builtin.assert:
29
+ that:
30
+ - cloudfall_deploy_rollback_target.stat.exists
31
+ - cloudfall_deploy_rollback_target.stat.isdir
32
+ fail_msg: >-
33
+ Release {{ cloudfall_deploy_release }} does not exist under
34
+ {{ cloudfall_deploy_component.install_root }}/releases
35
+
36
+ - name: Switch the current release symlink to the rollback target
37
+ ansible.builtin.file:
38
+ src: "{{ cloudfall_deploy_release_directory }}"
39
+ dest: "{{ cloudfall_deploy_current_link }}"
40
+ state: link
41
+ force: true
42
+ follow: false
43
+
44
+ - name: Restart the component service
45
+ ansible.builtin.systemd_service:
46
+ name: "{{ cloudfall_deploy_component.service.name }}.service"
47
+ enabled: true
48
+ state: restarted
49
+ daemon_reload: true
50
+
51
+ - name: Require the rolled-back release to pass its health check
52
+ ansible.builtin.include_tasks: health_gate.yml
@@ -0,0 +1,24 @@
1
+ # Managed by Cloudfall. Local changes will be overwritten.
2
+ {% set command = cloudfall_deploy_component.service.command %}
3
+ {% set executable = command[0]
4
+ if command[0].startswith('/')
5
+ else cloudfall_deploy_component.install_root ~ '/current/' ~ command[0] %}
6
+ [Unit]
7
+ Description=Cloudfall component {{ cloudfall_deploy_component.id }}
8
+ After=network.target
9
+
10
+ [Service]
11
+ Type=exec
12
+ User={{ cloudfall_deploy_component.application_user }}
13
+ Group={{ cloudfall_deploy_component.application_user }}
14
+ WorkingDirectory={{ cloudfall_deploy_component.install_root }}/current
15
+ EnvironmentFile=-{{ cloudfall_deploy_component.install_root }}/shared/env/{{ cloudfall_deploy_component.id }}.env
16
+ ExecStart={{ executable }}{% for argument in command[1:] %} "{{ argument }}"{% endfor %}
17
+
18
+ Restart=on-failure
19
+ RestartSec=2
20
+ NoNewPrivileges=true
21
+ PrivateTmp=true
22
+
23
+ [Install]
24
+ WantedBy=multi-user.target
@@ -0,0 +1,2 @@
1
+ ---
2
+ cloudfall_firewall_configuration_path: /etc/nftables.conf
@@ -0,0 +1,5 @@
1
+ ---
2
+ - name: Reload nftables
3
+ ansible.builtin.systemd_service:
4
+ name: nftables.service
5
+ state: reloaded
@@ -0,0 +1,33 @@
1
+ ---
2
+ argument_specs:
3
+ main:
4
+ short_description: Enforce the declared Cloudfall inbound firewall
5
+ options:
6
+ cloudfall_firewall_ruleset:
7
+ type: dict
8
+ required: true
9
+ options:
10
+ policy:
11
+ type: str
12
+ choices:
13
+ - default-deny
14
+ required: true
15
+ allowedInbound:
16
+ type: list
17
+ elements: dict
18
+ required: true
19
+ options:
20
+ port:
21
+ type: int
22
+ required: true
23
+ protocol:
24
+ type: str
25
+ choices:
26
+ - tcp
27
+ - udp
28
+ required: true
29
+ description:
30
+ type: str
31
+ cloudfall_firewall_configuration_path:
32
+ type: path
33
+ default: /etc/nftables.conf
@@ -0,0 +1,13 @@
1
+ ---
2
+ galaxy_info:
3
+ role_name: cloudfall_firewall
4
+ author: Cloudfall
5
+ description: Enforce a declared default-deny nftables inbound policy
6
+ license: AGPL-3.0-or-later
7
+ min_ansible_version: "2.21"
8
+ platforms:
9
+ - name: Debian
10
+ versions:
11
+ - bookworm
12
+ - trixie
13
+ dependencies: []
@@ -0,0 +1,51 @@
1
+ ---
2
+ - name: Validate firewall target and declared ruleset
3
+ ansible.builtin.assert:
4
+ that:
5
+ - ansible_facts["distribution"] == "Debian"
6
+ - ansible_facts["service_mgr"] == "systemd"
7
+ - cloudfall_firewall_ruleset.policy == "default-deny"
8
+ - cloudfall_firewall_ruleset.allowedInbound | length > 0
9
+ fail_msg: >-
10
+ Cloudfall firewall management requires Debian, systemd, and a declared
11
+ default-deny ruleset with at least one allowed inbound rule
12
+
13
+ - name: Refuse a ruleset that would block the control connection
14
+ ansible.builtin.assert:
15
+ that:
16
+ - >-
17
+ ansible_port | default(22) | int in (
18
+ cloudfall_firewall_ruleset.allowedInbound
19
+ | selectattr('protocol', 'equalto', 'tcp')
20
+ | map(attribute='port')
21
+ | map('int')
22
+ | list
23
+ )
24
+ fail_msg: >-
25
+ Refusing to apply a firewall that does not allow the SSH control port
26
+
27
+ - name: Install nftables
28
+ ansible.builtin.apt:
29
+ name: nftables
30
+ state: present
31
+ update_cache: true
32
+ cache_valid_time: 3600
33
+
34
+ - name: Render the managed nftables configuration
35
+ ansible.builtin.template:
36
+ src: nftables.conf.j2
37
+ dest: "{{ cloudfall_firewall_configuration_path }}"
38
+ owner: root
39
+ group: root
40
+ mode: "0644"
41
+ validate: /usr/sbin/nft --check --file %s
42
+ notify: Reload nftables
43
+
44
+ - name: Enable and start nftables
45
+ ansible.builtin.systemd_service:
46
+ name: nftables.service
47
+ enabled: true
48
+ state: started
49
+
50
+ - name: Apply the validated nftables configuration
51
+ ansible.builtin.meta: flush_handlers
@@ -0,0 +1,31 @@
1
+ #!/usr/sbin/nft -f
2
+ # Managed by Cloudfall. Local changes will be overwritten.
3
+
4
+ flush ruleset
5
+
6
+ table inet cloudfall {
7
+ chain input {
8
+ type filter hook input priority 0; policy drop;
9
+
10
+ iif "lo" accept
11
+ ct state established,related accept
12
+ ct state invalid drop
13
+ ip protocol icmp accept
14
+ meta l4proto ipv6-icmp accept
15
+ {% for rule in cloudfall_firewall_ruleset.allowedInbound %}
16
+ {% if rule.description is defined %}
17
+ {{ rule.protocol }} dport {{ rule.port }} accept comment "{{ rule.description }}"
18
+ {% else %}
19
+ {{ rule.protocol }} dport {{ rule.port }} accept
20
+ {% endif %}
21
+ {% endfor %}
22
+ }
23
+
24
+ chain forward {
25
+ type filter hook forward priority 0; policy drop;
26
+ }
27
+
28
+ chain output {
29
+ type filter hook output priority 0; policy accept;
30
+ }
31
+ }
@@ -0,0 +1,6 @@
1
+ ---
2
+ cloudfall_inspect_collector_version: "0.4.0"
3
+ cloudfall_inspect_logging_backend: {}
4
+ cloudfall_inspect_components: []
5
+ cloudfall_inspect_server_type: {}
6
+ cloudfall_inspect_server_id: ""
@@ -0,0 +1,24 @@
1
+ ---
2
+ argument_specs:
3
+ main:
4
+ short_description: Collect read-only Debian host evidence
5
+ options:
6
+ cloudfall_inspect_collector_version:
7
+ type: str
8
+ default: "0.2.0"
9
+ cloudfall_inspect_output_directory:
10
+ type: path
11
+ required: true
12
+ cloudfall_inspect_server_type:
13
+ type: dict
14
+ required: true
15
+ cloudfall_inspect_server_id:
16
+ type: str
17
+ required: true
18
+ cloudfall_inspect_logging_backend:
19
+ type: dict
20
+ default: {}
21
+ cloudfall_inspect_components:
22
+ type: list
23
+ elements: dict
24
+ default: []
@@ -0,0 +1,13 @@
1
+ ---
2
+ galaxy_info:
3
+ role_name: cloudfall_inspect
4
+ author: Cloudfall
5
+ description: Collect read-only Debian host evidence for Cloudfall audits
6
+ license: AGPL-3.0-or-later
7
+ min_ansible_version: "2.21"
8
+ platforms:
9
+ - name: Debian
10
+ versions:
11
+ - bookworm
12
+ - trixie
13
+ dependencies: []