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,643 @@
1
+ ---
2
+ - name: Validate inspection target and inputs
3
+ ansible.builtin.assert:
4
+ that:
5
+ - ansible_facts["distribution"] == "Debian"
6
+ - ansible_facts["service_mgr"] == "systemd"
7
+ - cloudfall_inspect_server_id | length > 0
8
+ - cloudfall_inspect_server_type.id | length > 0
9
+ - cloudfall_inspect_output_directory.startswith('/')
10
+ fail_msg: Cloudfall inspection requires a Debian systemd host and a server type
11
+
12
+ - name: Collect installed Debian packages
13
+ ansible.builtin.command:
14
+ argv:
15
+ - /usr/bin/dpkg-query
16
+ - --show
17
+ - >-
18
+ --showformat=${db:Status-Abbrev}{"name":"${Package}","version":"${Version}","source":"dpkg-query","arch":"${Architecture}"}\n
19
+ register: cloudfall_inspect_dpkg
20
+ changed_when: false
21
+
22
+ - name: Normalize installed package evidence
23
+ ansible.builtin.set_fact:
24
+ cloudfall_inspect_packages: >-
25
+ {{
26
+ cloudfall_inspect_dpkg.stdout_lines
27
+ | select('match', '^ii ')
28
+ | map('regex_replace', '^ii ', '')
29
+ | map('from_json')
30
+ | list
31
+ }}
32
+
33
+ - name: Normalize Debian version evidence
34
+ ansible.builtin.set_fact:
35
+ cloudfall_inspect_debian_version: >-
36
+ {{
37
+ cloudfall_inspect_packages
38
+ | selectattr('name', 'equalto', 'base-files')
39
+ | map(attribute='version')
40
+ | first
41
+ }}
42
+
43
+ - name: Collect service facts
44
+ ansible.builtin.service_facts:
45
+
46
+ - name: Collect block device topology
47
+ ansible.builtin.command:
48
+ argv:
49
+ - /usr/bin/lsblk
50
+ - --json
51
+ - --bytes
52
+ - --output
53
+ - NAME,PATH,TYPE,SIZE,FSTYPE,MOUNTPOINT,PKNAME,MODEL,SERIAL,ROTA
54
+ register: cloudfall_inspect_lsblk
55
+ changed_when: false
56
+
57
+ - name: Normalize block device evidence
58
+ ansible.builtin.set_fact:
59
+ cloudfall_inspect_block_devices: >-
60
+ {{ (cloudfall_inspect_lsblk.stdout | from_json).blockdevices }}
61
+
62
+ - name: Select NVMe namespaces for SMART inspection
63
+ ansible.builtin.set_fact:
64
+ cloudfall_inspect_nvme_devices: >-
65
+ {{
66
+ cloudfall_inspect_block_devices
67
+ | selectattr('type', 'equalto', 'disk')
68
+ | selectattr('path', 'match', '^/dev/nvme[0-9]+n[0-9]+$')
69
+ | list
70
+ }}
71
+
72
+ - name: Check whether smartctl is installed
73
+ ansible.builtin.stat:
74
+ path: /usr/sbin/smartctl
75
+ get_checksum: false
76
+ register: cloudfall_inspect_smartctl_binary
77
+
78
+ - name: Read smartctl version
79
+ ansible.builtin.command:
80
+ argv:
81
+ - /usr/sbin/smartctl
82
+ - --version
83
+ register: cloudfall_inspect_smartctl_version
84
+ changed_when: false
85
+ when: cloudfall_inspect_smartctl_binary.stat.exists
86
+
87
+ - name: Initialize SMART device evidence
88
+ ansible.builtin.set_fact:
89
+ cloudfall_inspect_smart_devices: []
90
+
91
+ - name: Collect read-only NVMe SMART evidence
92
+ ansible.builtin.command:
93
+ argv:
94
+ - /usr/sbin/smartctl
95
+ - --xall
96
+ - "{{ item.path }}"
97
+ loop: "{{ cloudfall_inspect_nvme_devices }}"
98
+ loop_control:
99
+ label: "{{ item.path }}"
100
+ register: cloudfall_inspect_smartctl_devices
101
+ changed_when: false
102
+ failed_when: >-
103
+ cloudfall_inspect_smartctl_devices.rc < 0
104
+ or cloudfall_inspect_smartctl_devices.rc > 255
105
+ when: cloudfall_inspect_smartctl_binary.stat.exists
106
+
107
+ - name: Validate NVMe SMART report fields
108
+ ansible.builtin.assert:
109
+ that:
110
+ - item.stdout is search('(?m)^Model Number:\s+.+$')
111
+ - item.stdout is search('(?m)^Serial Number:\s+.+$')
112
+ - item.stdout is search('(?m)^Firmware Version:\s+.+$')
113
+ - >-
114
+ item.stdout is search(
115
+ '(?m)^SMART overall-health self-assessment test result:\s+.+$'
116
+ )
117
+ - item.stdout is search('(?m)^Critical Warning:\s+0x[0-9A-Fa-f]+$')
118
+ - item.stdout is search('(?m)^Temperature:\s+-?[0-9]+ Celsius$')
119
+ - item.stdout is search('(?m)^Available Spare:\s+[0-9]+%$')
120
+ - >-
121
+ item.stdout is search(
122
+ '(?m)^Available Spare Threshold:\s+[0-9]+%$'
123
+ )
124
+ - item.stdout is search('(?m)^Percentage Used:\s+[0-9,]+%$')
125
+ - item.stdout is search('(?m)^Data Units Read:\s+[0-9,]+ .+$')
126
+ - item.stdout is search('(?m)^Data Units Written:\s+[0-9,]+ .+$')
127
+ - item.stdout is search('(?m)^Power Cycles:\s+[0-9,]+$')
128
+ - item.stdout is search('(?m)^Power On Hours:\s+[0-9,]+$')
129
+ - item.stdout is search('(?m)^Unsafe Shutdowns:\s+[0-9,]+$')
130
+ - >-
131
+ item.stdout is search(
132
+ '(?m)^Media and Data Integrity Errors:\s+[0-9,]+$'
133
+ )
134
+ - >-
135
+ item.stdout is search(
136
+ '(?m)^Error Information Log Entries:\s+[0-9,]+$'
137
+ )
138
+ fail_msg: >-
139
+ smartctl returned an unsupported NVMe report for {{ item.item.path }}
140
+ loop: "{{ cloudfall_inspect_smartctl_devices.results | default([]) }}"
141
+ loop_control:
142
+ label: "{{ item.item.path }}"
143
+ when:
144
+ - cloudfall_inspect_smartctl_binary.stat.exists
145
+ - not item.skipped | default(false)
146
+
147
+ - name: Normalize NVMe SMART evidence
148
+ ansible.builtin.set_fact:
149
+ cloudfall_inspect_smart_devices: >-
150
+ {{
151
+ cloudfall_inspect_smart_devices
152
+ + [
153
+ {
154
+ 'path': item.item.path,
155
+ 'model': (
156
+ item.stdout_lines
157
+ | select('match', '^Model Number:')
158
+ | first
159
+ | regex_replace('^Model Number:\s+', '')
160
+ | trim
161
+ ),
162
+ 'serial': (
163
+ item.stdout_lines
164
+ | select('match', '^Serial Number:')
165
+ | first
166
+ | regex_replace('^Serial Number:\s+', '')
167
+ | trim
168
+ ),
169
+ 'firmware': (
170
+ item.stdout_lines
171
+ | select('match', '^Firmware Version:')
172
+ | first
173
+ | regex_replace('^Firmware Version:\s+', '')
174
+ | trim
175
+ ),
176
+ 'smartctlExitCode': item.rc | int,
177
+ 'overallHealth': (
178
+ item.stdout_lines
179
+ | select('match', '^SMART overall-health')
180
+ | first
181
+ | regex_replace('^.*:\s+', '')
182
+ | regex_replace('!$', '')
183
+ | lower
184
+ ),
185
+ 'criticalWarning': (
186
+ item.stdout_lines
187
+ | select('match', '^Critical Warning:')
188
+ | first
189
+ | regex_replace('^Critical Warning:\s+', '')
190
+ ),
191
+ 'reliabilityDegraded': (
192
+ 'reliability has been degraded' in item.stdout
193
+ ),
194
+ 'temperatureCelsius': (
195
+ item.stdout_lines
196
+ | select('match', '^Temperature:')
197
+ | first
198
+ | regex_replace('^Temperature:\s+', '')
199
+ | regex_replace(' Celsius$', '')
200
+ | int
201
+ ),
202
+ 'availableSparePercent': (
203
+ item.stdout_lines
204
+ | select('match', '^Available Spare:')
205
+ | first
206
+ | regex_replace('^Available Spare:\s+', '')
207
+ | regex_replace('%$', '')
208
+ | int
209
+ ),
210
+ 'availableSpareThresholdPercent': (
211
+ item.stdout_lines
212
+ | select('match', '^Available Spare Threshold:')
213
+ | first
214
+ | regex_replace('^Available Spare Threshold:\s+', '')
215
+ | regex_replace('%$', '')
216
+ | int
217
+ ),
218
+ 'percentageUsed': (
219
+ item.stdout_lines
220
+ | select('match', '^Percentage Used:')
221
+ | first
222
+ | regex_replace('^Percentage Used:\s+', '')
223
+ | regex_replace('%$', '')
224
+ | replace(',', '')
225
+ | int
226
+ ),
227
+ 'dataUnitsRead': (
228
+ item.stdout_lines
229
+ | select('match', '^Data Units Read:')
230
+ | first
231
+ | regex_replace('^Data Units Read:\s+', '')
232
+ | regex_replace(' .+$', '')
233
+ | replace(',', '')
234
+ | int
235
+ ),
236
+ 'dataUnitsWritten': (
237
+ item.stdout_lines
238
+ | select('match', '^Data Units Written:')
239
+ | first
240
+ | regex_replace('^Data Units Written:\s+', '')
241
+ | regex_replace(' .+$', '')
242
+ | replace(',', '')
243
+ | int
244
+ ),
245
+ 'powerCycles': (
246
+ item.stdout_lines
247
+ | select('match', '^Power Cycles:')
248
+ | first
249
+ | regex_replace('^Power Cycles:\s+', '')
250
+ | replace(',', '')
251
+ | int
252
+ ),
253
+ 'powerOnHours': (
254
+ item.stdout_lines
255
+ | select('match', '^Power On Hours:')
256
+ | first
257
+ | regex_replace('^Power On Hours:\s+', '')
258
+ | replace(',', '')
259
+ | int
260
+ ),
261
+ 'unsafeShutdowns': (
262
+ item.stdout_lines
263
+ | select('match', '^Unsafe Shutdowns:')
264
+ | first
265
+ | regex_replace('^Unsafe Shutdowns:\s+', '')
266
+ | replace(',', '')
267
+ | int
268
+ ),
269
+ 'mediaAndDataIntegrityErrors': (
270
+ item.stdout_lines
271
+ | select('match', '^Media and Data Integrity Errors:')
272
+ | first
273
+ | regex_replace('^Media and Data Integrity Errors:\s+', '')
274
+ | replace(',', '')
275
+ | int
276
+ ),
277
+ 'errorInformationLogEntries': (
278
+ item.stdout_lines
279
+ | select('match', '^Error Information Log Entries:')
280
+ | first
281
+ | regex_replace('^Error Information Log Entries:\s+', '')
282
+ | replace(',', '')
283
+ | int
284
+ )
285
+ }
286
+ ]
287
+ }}
288
+ loop: "{{ cloudfall_inspect_smartctl_devices.results | default([]) }}"
289
+ loop_control:
290
+ label: "{{ item.item.path }}"
291
+ when:
292
+ - cloudfall_inspect_smartctl_binary.stat.exists
293
+ - not item.skipped | default(false)
294
+
295
+ - name: Collect mounted filesystem sizes
296
+ ansible.builtin.command:
297
+ argv:
298
+ - /usr/bin/findmnt
299
+ - --json
300
+ - --bytes
301
+ - --list
302
+ - --output
303
+ - TARGET,SOURCE,FSTYPE,OPTIONS,SIZE,USED,AVAIL
304
+ register: cloudfall_inspect_findmnt
305
+ changed_when: false
306
+
307
+ - name: Check whether Linux software RAID status is available
308
+ ansible.builtin.stat:
309
+ path: /proc/mdstat
310
+ get_checksum: false
311
+ register: cloudfall_inspect_mdstat_file
312
+
313
+ - name: Read Linux software RAID status
314
+ ansible.builtin.slurp:
315
+ src: /proc/mdstat
316
+ register: cloudfall_inspect_mdstat
317
+ when: cloudfall_inspect_mdstat_file.stat.exists
318
+
319
+ - name: Check whether mdadm is installed
320
+ ansible.builtin.stat:
321
+ path: /usr/sbin/mdadm
322
+ get_checksum: false
323
+ register: cloudfall_inspect_mdadm_binary
324
+
325
+ - name: Collect mdadm array definitions
326
+ ansible.builtin.command:
327
+ argv:
328
+ - /usr/sbin/mdadm
329
+ - --detail
330
+ - --scan
331
+ - --verbose
332
+ register: cloudfall_inspect_mdadm
333
+ changed_when: false
334
+ when: cloudfall_inspect_mdadm_binary.stat.exists
335
+
336
+ - name: Collect allowlisted configuration metadata and hashes
337
+ ansible.builtin.stat:
338
+ path: "{{ item.path }}"
339
+ checksum_algorithm: sha256
340
+ get_checksum: "{{ item.capture == 'hash' }}"
341
+ loop: "{{ cloudfall_inspect_server_type.configuration.files }}"
342
+ loop_control:
343
+ label: "{{ item.path }}"
344
+ register: cloudfall_inspect_configuration_stats
345
+
346
+ - name: Initialize configuration evidence
347
+ ansible.builtin.set_fact:
348
+ cloudfall_inspect_configuration: []
349
+
350
+ - name: Normalize configuration evidence
351
+ ansible.builtin.set_fact:
352
+ cloudfall_inspect_configuration: >-
353
+ {{
354
+ cloudfall_inspect_configuration
355
+ + [
356
+ {
357
+ 'path': item.item.path,
358
+ 'capture': item.item.capture,
359
+ 'exists': item.stat.exists,
360
+ 'owner': item.stat.pw_name | default(none),
361
+ 'group': item.stat.gr_name | default(none),
362
+ 'mode': item.stat.mode | default(none),
363
+ 'size': item.stat.size | default(none),
364
+ 'sha256': item.stat.checksum | default(none)
365
+ }
366
+ ]
367
+ }}
368
+ loop: "{{ cloudfall_inspect_configuration_stats.results }}"
369
+ loop_control:
370
+ label: "{{ item.item.path }}"
371
+
372
+ - name: Keep systemd service-unit observations only
373
+ ansible.builtin.set_fact:
374
+ cloudfall_inspect_services: >-
375
+ {{
376
+ ansible_facts["services"]
377
+ | dict2items
378
+ | selectattr('value.source', 'equalto', 'systemd')
379
+ | items2dict
380
+ }}
381
+
382
+ - name: Collect systemd timer unit states
383
+ ansible.builtin.command:
384
+ argv:
385
+ - /usr/bin/systemctl
386
+ - list-units
387
+ - --type=timer
388
+ - --all
389
+ - --output=json
390
+ - --no-pager
391
+ register: cloudfall_inspect_timer_units
392
+ changed_when: false
393
+
394
+ - name: Collect systemd timer unit files
395
+ ansible.builtin.command:
396
+ argv:
397
+ - /usr/bin/systemctl
398
+ - list-unit-files
399
+ - --type=timer
400
+ - --output=json
401
+ - --no-pager
402
+ register: cloudfall_inspect_timer_files
403
+ changed_when: false
404
+
405
+ - name: Normalize systemd timer enablement evidence
406
+ ansible.builtin.set_fact:
407
+ cloudfall_inspect_timer_statuses: >-
408
+ {{
409
+ cloudfall_inspect_timer_files.stdout
410
+ | from_json
411
+ | items2dict(key_name='unit_file', value_name='state')
412
+ }}
413
+
414
+ - name: Initialize systemd timer evidence
415
+ ansible.builtin.set_fact:
416
+ cloudfall_inspect_timers: {}
417
+
418
+ - name: Normalize systemd timer evidence
419
+ ansible.builtin.set_fact:
420
+ cloudfall_inspect_timers: >-
421
+ {{
422
+ cloudfall_inspect_timers
423
+ | combine({
424
+ item.unit: {
425
+ 'name': item.unit,
426
+ 'state': 'running' if item.active == 'active' else 'stopped',
427
+ 'status': cloudfall_inspect_timer_statuses.get(
428
+ item.unit, 'unknown'
429
+ ),
430
+ 'source': 'systemd'
431
+ }
432
+ })
433
+ }}
434
+ loop: "{{ cloudfall_inspect_timer_units.stdout | from_json }}"
435
+ loop_control:
436
+ label: "{{ item.unit }}"
437
+
438
+ - name: Collect listening sockets
439
+ ansible.builtin.command:
440
+ argv:
441
+ - /usr/bin/ss
442
+ - -tulnH
443
+ register: cloudfall_inspect_listening_sockets
444
+ changed_when: false
445
+
446
+ - name: Check whether nft is installed
447
+ ansible.builtin.stat:
448
+ path: /usr/sbin/nft
449
+ get_checksum: false
450
+ register: cloudfall_inspect_nft_binary
451
+
452
+ - name: Read the managed nftables table
453
+ ansible.builtin.command:
454
+ argv:
455
+ - /usr/sbin/nft
456
+ - --json
457
+ - list
458
+ - table
459
+ - inet
460
+ - cloudfall
461
+ register: cloudfall_inspect_nft_table
462
+ changed_when: false
463
+ failed_when: false
464
+ when: cloudfall_inspect_nft_binary.stat.exists
465
+
466
+ - name: Build normalized observation snapshot
467
+ ansible.builtin.set_fact:
468
+ cloudfall_inspect_snapshot:
469
+ apiVersion: cloudfall/v1
470
+ kind: ObservedServer
471
+ metadata:
472
+ id: "{{ cloudfall_inspect_server_id }}"
473
+ description: Read-only server evidence collected by Cloudfall
474
+ spec:
475
+ server: "{{ cloudfall_inspect_server_id }}"
476
+ serverType: "{{ cloudfall_inspect_server_type.id }}"
477
+ observedAt: "{{ ansible_facts['date_time']['iso8601'] }}"
478
+ collectorVersion: "{{ cloudfall_inspect_collector_version }}"
479
+ os:
480
+ distribution: "{{ ansible_facts['distribution'] }}"
481
+ version: "{{ cloudfall_inspect_debian_version }}"
482
+ majorVersion: "{{ cloudfall_inspect_debian_version.split('.')[0] }}"
483
+ release: "{{ ansible_facts['distribution_release'] }}"
484
+ kernel: "{{ ansible_facts['kernel'] }}"
485
+ architecture: "{{ ansible_facts['architecture'] }}"
486
+ serviceManager: "{{ ansible_facts['service_mgr'] }}"
487
+ hardware:
488
+ processorCount: "{{ ansible_facts['processor_count'] | int }}"
489
+ processorCores: "{{ ansible_facts['processor_cores'] | int }}"
490
+ processorVcpus: "{{ ansible_facts['processor_vcpus'] | int }}"
491
+ memoryBytes: >-
492
+ {{ (ansible_facts['memtotal_mb'] | int) * 1048576 }}
493
+ storage:
494
+ blockDevices: "{{ cloudfall_inspect_block_devices }}"
495
+ filesystems: >-
496
+ {{ (cloudfall_inspect_findmnt.stdout | from_json).filesystems }}
497
+ softwareRaid:
498
+ mdstat: >-
499
+ {{
500
+ (cloudfall_inspect_mdstat.content | b64decode)
501
+ if cloudfall_inspect_mdstat_file.stat.exists
502
+ else ''
503
+ }}
504
+ mdadmScan: "{{ cloudfall_inspect_mdadm.stdout | default('') }}"
505
+ smart:
506
+ available: "{{ cloudfall_inspect_smartctl_binary.stat.exists }}"
507
+ smartctlVersion: >-
508
+ {{
509
+ cloudfall_inspect_smartctl_version.stdout_lines[0].split()[1]
510
+ if cloudfall_inspect_smartctl_binary.stat.exists
511
+ else none
512
+ }}
513
+ devices: "{{ cloudfall_inspect_smart_devices }}"
514
+ packages: "{{ cloudfall_inspect_packages }}"
515
+ services: "{{ cloudfall_inspect_services }}"
516
+ timers: "{{ cloudfall_inspect_timers }}"
517
+ network:
518
+ listeningSockets: "{{ cloudfall_inspect_listening_sockets.stdout }}"
519
+ firewall:
520
+ nftAvailable: "{{ cloudfall_inspect_nft_binary.stat.exists }}"
521
+ managedTableJson: >-
522
+ {{
523
+ cloudfall_inspect_nft_table.stdout
524
+ if (
525
+ cloudfall_inspect_nft_binary.stat.exists
526
+ and cloudfall_inspect_nft_table.rc == 0
527
+ )
528
+ else none
529
+ }}
530
+ componentEnvironment: >-
531
+ {{ cloudfall_inspect_component_environment | default([]) }}
532
+ configuration: "{{ cloudfall_inspect_configuration }}"
533
+
534
+ - name: Capture component environment file evidence
535
+ ansible.builtin.stat:
536
+ path: >-
537
+ {{ cloudfall_inspect_component_item.install_root }}/shared/env/{{
538
+ cloudfall_inspect_component_item.id
539
+ }}.env
540
+ checksum_algorithm: sha256
541
+ loop: "{{ cloudfall_inspect_components }}"
542
+ loop_control:
543
+ loop_var: cloudfall_inspect_component_item
544
+ label: "{{ cloudfall_inspect_component_item.id }}"
545
+ register: cloudfall_inspect_component_env
546
+
547
+ - name: Normalize component environment evidence
548
+ ansible.builtin.set_fact:
549
+ cloudfall_inspect_component_environment: >-
550
+ {{
551
+ cloudfall_inspect_component_environment | default([]) + [{
552
+ 'component':
553
+ cloudfall_inspect_env_result.cloudfall_inspect_component_item.id,
554
+ 'path':
555
+ cloudfall_inspect_env_result.cloudfall_inspect_component_item
556
+ .install_root ~ '/shared/env/'
557
+ ~ cloudfall_inspect_env_result.cloudfall_inspect_component_item.id
558
+ ~ '.env',
559
+ 'exists': cloudfall_inspect_env_result.stat.exists,
560
+ 'sha256': cloudfall_inspect_env_result.stat.checksum | default(none),
561
+ }]
562
+ }}
563
+ loop: "{{ cloudfall_inspect_component_env.results | default([]) }}"
564
+ loop_control:
565
+ loop_var: cloudfall_inspect_env_result
566
+ label: >-
567
+ {{ cloudfall_inspect_env_result.cloudfall_inspect_component_item.id }}
568
+
569
+ - name: Collect Prometheus alert rule evidence
570
+ ansible.builtin.uri:
571
+ url: >-
572
+ http://{{
573
+ cloudfall_inspect_logging_backend.metrics.backend.listenAddress
574
+ }}:{{
575
+ cloudfall_inspect_logging_backend.metrics.backend.port
576
+ }}/api/v1/rules?type=alert
577
+ return_content: true
578
+ register: cloudfall_inspect_prometheus_rules
579
+ failed_when: false
580
+ when: cloudfall_inspect_logging_backend | length > 0
581
+
582
+ - name: Collect Prometheus active alert evidence
583
+ ansible.builtin.uri:
584
+ url: >-
585
+ http://{{
586
+ cloudfall_inspect_logging_backend.metrics.backend.listenAddress
587
+ }}:{{
588
+ cloudfall_inspect_logging_backend.metrics.backend.port
589
+ }}/api/v1/alerts
590
+ return_content: true
591
+ register: cloudfall_inspect_prometheus_alerts
592
+ failed_when: false
593
+ when: cloudfall_inspect_logging_backend | length > 0
594
+
595
+ - name: Attach alerting evidence for the logging backend
596
+ ansible.builtin.set_fact:
597
+ cloudfall_inspect_snapshot: >-
598
+ {{
599
+ cloudfall_inspect_snapshot | combine(
600
+ {
601
+ 'spec': {
602
+ 'alerting': {
603
+ 'prometheusAvailable': (
604
+ cloudfall_inspect_prometheus_rules.status | default(0) == 200
605
+ and cloudfall_inspect_prometheus_alerts.status
606
+ | default(0) == 200
607
+ ),
608
+ 'rulesJson': (
609
+ cloudfall_inspect_prometheus_rules.content
610
+ if cloudfall_inspect_prometheus_rules.status
611
+ | default(0) == 200
612
+ else none
613
+ ),
614
+ 'alertsJson': (
615
+ cloudfall_inspect_prometheus_alerts.content
616
+ if cloudfall_inspect_prometheus_alerts.status
617
+ | default(0) == 200
618
+ else none
619
+ ),
620
+ }
621
+ }
622
+ },
623
+ recursive=true
624
+ )
625
+ }}
626
+ when: cloudfall_inspect_logging_backend | length > 0
627
+
628
+ - name: Create controller observation directory
629
+ ansible.builtin.file:
630
+ path: "{{ cloudfall_inspect_output_directory }}"
631
+ state: directory
632
+ mode: "0700"
633
+ delegate_to: localhost
634
+ become: false
635
+
636
+ - name: Write observation snapshot on controller
637
+ ansible.builtin.copy:
638
+ content: "{{ cloudfall_inspect_snapshot | to_nice_json }}\n"
639
+ dest: >-
640
+ {{ cloudfall_inspect_output_directory }}/{{ cloudfall_inspect_server_id }}.json
641
+ mode: "0600"
642
+ delegate_to: localhost
643
+ become: false
@@ -0,0 +1,6 @@
1
+ ---
2
+ cloudfall_logging_backend_stack: {}
3
+ cloudfall_logging_backend_grafana_repository_key_url: >-
4
+ https://apt.grafana.com/gpg-full.key
5
+ cloudfall_logging_backend_grafana_repository_key_path: /etc/apt/keyrings/grafana.asc
6
+ cloudfall_logging_backend_grafana_repository_url: https://apt.grafana.com
@@ -0,0 +1,29 @@
1
+ ---
2
+ - name: Restart Loki
3
+ ansible.builtin.systemd_service:
4
+ name: loki.service
5
+ state: restarted
6
+ daemon_reload: true
7
+
8
+ - name: Restart Prometheus
9
+ ansible.builtin.systemd_service:
10
+ name: prometheus.service
11
+ state: restarted
12
+ daemon_reload: true
13
+
14
+ - name: Restart Alertmanager
15
+ ansible.builtin.systemd_service:
16
+ name: prometheus-alertmanager.service
17
+ state: restarted
18
+ daemon_reload: true
19
+
20
+ - name: Restart Grafana
21
+ ansible.builtin.systemd_service:
22
+ name: grafana-server.service
23
+ state: restarted
24
+ daemon_reload: true
25
+
26
+ - name: Reload Nginx
27
+ ansible.builtin.systemd_service:
28
+ name: nginx.service
29
+ state: reloaded