@celilo/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/README.md +1566 -0
  2. package/bin/celilo +16 -0
  3. package/drizzle/0000_complex_puma.sql +179 -0
  4. package/drizzle/0001_dizzy_wolfpack.sql +2 -0
  5. package/drizzle/0002_web_routes.sql +16 -0
  6. package/drizzle/0003_backup_storage.sql +32 -0
  7. package/drizzle/meta/0000_snapshot.json +1151 -0
  8. package/drizzle/meta/0001_snapshot.json +1167 -0
  9. package/drizzle/meta/0002_snapshot.json +1257 -0
  10. package/drizzle/meta/_journal.json +27 -0
  11. package/package.json +64 -0
  12. package/schemas/system_config.json +106 -0
  13. package/src/__integration__/container-services-cli.integration.test.ts +246 -0
  14. package/src/ansible/dependencies.test.ts +309 -0
  15. package/src/ansible/dependencies.ts +896 -0
  16. package/src/ansible/inventory.test.ts +463 -0
  17. package/src/ansible/inventory.ts +445 -0
  18. package/src/ansible/secrets.ts +222 -0
  19. package/src/ansible/validation.test.ts +92 -0
  20. package/src/ansible/validation.ts +272 -0
  21. package/src/api-clients/digitalocean.ts +94 -0
  22. package/src/api-clients/proxmox.ts +655 -0
  23. package/src/capabilities/logging-wrapper.test.ts +217 -0
  24. package/src/capabilities/lookup.test.ts +149 -0
  25. package/src/capabilities/lookup.ts +89 -0
  26. package/src/capabilities/public-web-helpers.test.ts +198 -0
  27. package/src/capabilities/public-web-publish.test.ts +458 -0
  28. package/src/capabilities/registration.test.ts +395 -0
  29. package/src/capabilities/registration.ts +200 -0
  30. package/src/capabilities/route-validation.test.ts +121 -0
  31. package/src/capabilities/route-validation.ts +96 -0
  32. package/src/capabilities/secret-ref.test.ts +313 -0
  33. package/src/capabilities/secret-validation.ts +157 -0
  34. package/src/capabilities/secrets.test.ts +750 -0
  35. package/src/capabilities/secrets.ts +244 -0
  36. package/src/capabilities/validation.test.ts +613 -0
  37. package/src/capabilities/validation.ts +160 -0
  38. package/src/capabilities/well-known.test.ts +238 -0
  39. package/src/capabilities/well-known.ts +222 -0
  40. package/src/cli/cli.test.ts +654 -0
  41. package/src/cli/command-registry.ts +742 -0
  42. package/src/cli/command-tree-parser.test.ts +180 -0
  43. package/src/cli/command-tree-parser.ts +193 -0
  44. package/src/cli/commands/backup-create.ts +137 -0
  45. package/src/cli/commands/backup-delete.ts +74 -0
  46. package/src/cli/commands/backup-import.ts +97 -0
  47. package/src/cli/commands/backup-list.ts +132 -0
  48. package/src/cli/commands/backup-name.ts +73 -0
  49. package/src/cli/commands/backup-prune.ts +98 -0
  50. package/src/cli/commands/backup-restore.ts +122 -0
  51. package/src/cli/commands/capability-info.ts +121 -0
  52. package/src/cli/commands/capability-list.ts +47 -0
  53. package/src/cli/commands/completion.ts +87 -0
  54. package/src/cli/commands/hook-run.ts +176 -0
  55. package/src/cli/commands/ipam.ts +607 -0
  56. package/src/cli/commands/machine-add.ts +235 -0
  57. package/src/cli/commands/machine-earmark.ts +82 -0
  58. package/src/cli/commands/machine-list.ts +77 -0
  59. package/src/cli/commands/machine-remove.ts +90 -0
  60. package/src/cli/commands/machine-status.ts +131 -0
  61. package/src/cli/commands/module-audit.ts +51 -0
  62. package/src/cli/commands/module-build.ts +60 -0
  63. package/src/cli/commands/module-config.ts +170 -0
  64. package/src/cli/commands/module-deploy.ts +71 -0
  65. package/src/cli/commands/module-generate.ts +236 -0
  66. package/src/cli/commands/module-health.ts +108 -0
  67. package/src/cli/commands/module-import.ts +80 -0
  68. package/src/cli/commands/module-list.ts +43 -0
  69. package/src/cli/commands/module-logs.ts +73 -0
  70. package/src/cli/commands/module-remove.ts +162 -0
  71. package/src/cli/commands/module-show.ts +208 -0
  72. package/src/cli/commands/module-status.ts +131 -0
  73. package/src/cli/commands/module-types.ts +189 -0
  74. package/src/cli/commands/module-upgrade.ts +192 -0
  75. package/src/cli/commands/package.ts +68 -0
  76. package/src/cli/commands/secret-list.ts +99 -0
  77. package/src/cli/commands/secret-set.ts +134 -0
  78. package/src/cli/commands/service-add-digitalocean.ts +133 -0
  79. package/src/cli/commands/service-add-proxmox.ts +342 -0
  80. package/src/cli/commands/service-config-get.ts +83 -0
  81. package/src/cli/commands/service-config-set.ts +145 -0
  82. package/src/cli/commands/service-list.ts +74 -0
  83. package/src/cli/commands/service-reconfigure.ts +230 -0
  84. package/src/cli/commands/service-remove.ts +103 -0
  85. package/src/cli/commands/service-verify.ts +240 -0
  86. package/src/cli/commands/status.ts +216 -0
  87. package/src/cli/commands/storage-add-local.ts +106 -0
  88. package/src/cli/commands/storage-add-s3.ts +114 -0
  89. package/src/cli/commands/storage-list.ts +72 -0
  90. package/src/cli/commands/storage-remove.ts +54 -0
  91. package/src/cli/commands/storage-set-default.ts +44 -0
  92. package/src/cli/commands/storage-verify.ts +54 -0
  93. package/src/cli/commands/system-config.ts +168 -0
  94. package/src/cli/commands/system-init.ts +314 -0
  95. package/src/cli/commands/system-secret-get.ts +98 -0
  96. package/src/cli/commands/system-secret-set.ts +76 -0
  97. package/src/cli/commands/system-vault-password.ts +34 -0
  98. package/src/cli/completion.test.ts +37 -0
  99. package/src/cli/completion.ts +482 -0
  100. package/src/cli/fuel-gauge.test.ts +208 -0
  101. package/src/cli/fuel-gauge.ts +405 -0
  102. package/src/cli/generate-zsh-completion.test.ts +95 -0
  103. package/src/cli/generate-zsh-completion.ts +497 -0
  104. package/src/cli/index.ts +1583 -0
  105. package/src/cli/interactive-config.test.ts +201 -0
  106. package/src/cli/interactive-config.ts +62 -0
  107. package/src/cli/parser.test.ts +227 -0
  108. package/src/cli/parser.ts +244 -0
  109. package/src/cli/prompts.test.ts +33 -0
  110. package/src/cli/prompts.ts +121 -0
  111. package/src/cli/types.ts +38 -0
  112. package/src/cli/validators.test.ts +235 -0
  113. package/src/cli/validators.ts +188 -0
  114. package/src/config/env.ts +41 -0
  115. package/src/config/paths.test.ts +172 -0
  116. package/src/config/paths.ts +108 -0
  117. package/src/db/client.ts +190 -0
  118. package/src/db/migrate.ts +30 -0
  119. package/src/db/schema.test.ts +221 -0
  120. package/src/db/schema.ts +434 -0
  121. package/src/hooks/capability-loader-firewall.test.ts +246 -0
  122. package/src/hooks/capability-loader.test.ts +100 -0
  123. package/src/hooks/capability-loader.ts +520 -0
  124. package/src/hooks/define-hook.test.ts +488 -0
  125. package/src/hooks/executor.test.ts +462 -0
  126. package/src/hooks/executor.ts +469 -0
  127. package/src/hooks/logger.test.ts +54 -0
  128. package/src/hooks/logger.ts +95 -0
  129. package/src/hooks/test-fixtures/failing-hook.ts +13 -0
  130. package/src/hooks/test-fixtures/no-default-hook.ts +6 -0
  131. package/src/hooks/test-fixtures/success-hook.ts +20 -0
  132. package/src/hooks/test-fixtures/unbranded-hook.ts +11 -0
  133. package/src/hooks/test-fixtures/void-hook.ts +13 -0
  134. package/src/hooks/types.ts +89 -0
  135. package/src/infrastructure/property-extractor.test.ts +194 -0
  136. package/src/infrastructure/property-extractor.ts +151 -0
  137. package/src/ipam/allocator.test.ts +442 -0
  138. package/src/ipam/allocator.ts +369 -0
  139. package/src/ipam/auto-allocator.test.ts +247 -0
  140. package/src/ipam/auto-allocator.ts +270 -0
  141. package/src/ipam/subnet-parser.test.ts +107 -0
  142. package/src/ipam/subnet-parser.ts +136 -0
  143. package/src/manifest/contracts/index.ts +61 -0
  144. package/src/manifest/contracts/v1.ts +118 -0
  145. package/src/manifest/json-schema-roundtrip.test.ts +99 -0
  146. package/src/manifest/schema.ts +367 -0
  147. package/src/manifest/template-validator.test.ts +231 -0
  148. package/src/manifest/template-validator.ts +322 -0
  149. package/src/manifest/validate.test.ts +1180 -0
  150. package/src/manifest/validate.ts +415 -0
  151. package/src/module/import.test.ts +355 -0
  152. package/src/module/import.ts +676 -0
  153. package/src/module/packaging/audit.ts +169 -0
  154. package/src/module/packaging/build.ts +228 -0
  155. package/src/module/packaging/checksum.ts +41 -0
  156. package/src/module/packaging/extract.ts +234 -0
  157. package/src/module/packaging/signature.ts +47 -0
  158. package/src/secrets/encryption.test.ts +284 -0
  159. package/src/secrets/encryption.ts +162 -0
  160. package/src/secrets/generators.test.ts +112 -0
  161. package/src/secrets/generators.ts +127 -0
  162. package/src/secrets/master-key.test.ts +159 -0
  163. package/src/secrets/master-key.ts +114 -0
  164. package/src/secrets/storage.test.ts +115 -0
  165. package/src/secrets/storage.ts +106 -0
  166. package/src/secrets/vault.test.ts +35 -0
  167. package/src/secrets/vault.ts +42 -0
  168. package/src/services/backup-create.ts +532 -0
  169. package/src/services/backup-metadata.ts +198 -0
  170. package/src/services/backup-restore.ts +229 -0
  171. package/src/services/backup-retention.ts +84 -0
  172. package/src/services/backup-storage.ts +281 -0
  173. package/src/services/build-stream.test.ts +122 -0
  174. package/src/services/build-stream.ts +201 -0
  175. package/src/services/config-interview.ts +694 -0
  176. package/src/services/container-service.test.ts +298 -0
  177. package/src/services/container-service.ts +401 -0
  178. package/src/services/cross-module-data-manager.test.ts +405 -0
  179. package/src/services/cross-module-data-manager.ts +412 -0
  180. package/src/services/deploy-ansible.ts +88 -0
  181. package/src/services/deploy-planner.ts +153 -0
  182. package/src/services/deploy-preflight.ts +274 -0
  183. package/src/services/deploy-ssh.ts +131 -0
  184. package/src/services/deploy-terraform.test.ts +55 -0
  185. package/src/services/deploy-terraform.ts +445 -0
  186. package/src/services/deploy-validation.ts +311 -0
  187. package/src/services/dns-auto-register.ts +211 -0
  188. package/src/services/health-runner.ts +184 -0
  189. package/src/services/infrastructure-selector.test.ts +485 -0
  190. package/src/services/infrastructure-selector.ts +245 -0
  191. package/src/services/infrastructure-variable-resolver.test.ts +751 -0
  192. package/src/services/infrastructure-variable-resolver.ts +234 -0
  193. package/src/services/machine-detector.ts +328 -0
  194. package/src/services/machine-pool.test.ts +405 -0
  195. package/src/services/machine-pool.ts +316 -0
  196. package/src/services/manifest-validation.ts +120 -0
  197. package/src/services/module-build.test.ts +290 -0
  198. package/src/services/module-build.ts +431 -0
  199. package/src/services/module-config.test.ts +237 -0
  200. package/src/services/module-config.ts +298 -0
  201. package/src/services/module-deploy.ts +862 -0
  202. package/src/services/module-types-drift.test.ts +73 -0
  203. package/src/services/module-types-generator.test.ts +288 -0
  204. package/src/services/module-types-generator.ts +189 -0
  205. package/src/services/proxmox-state-recovery.ts +140 -0
  206. package/src/services/schema-validation.ts +155 -0
  207. package/src/services/secret-schema-loader.test.ts +311 -0
  208. package/src/services/secret-schema-loader.ts +239 -0
  209. package/src/services/ssh-key-manager.test.ts +283 -0
  210. package/src/services/ssh-key-manager.ts +193 -0
  211. package/src/services/storage-providers/local.ts +105 -0
  212. package/src/services/storage-providers/s3.ts +182 -0
  213. package/src/services/storage-providers/types.ts +24 -0
  214. package/src/services/system-config-schema-types.ts +25 -0
  215. package/src/services/system-config-validator.test.ts +160 -0
  216. package/src/services/system-config-validator.ts +74 -0
  217. package/src/services/system-init.test.ts +153 -0
  218. package/src/services/system-init.ts +253 -0
  219. package/src/services/terraform-safety.ts +174 -0
  220. package/src/services/zone-detector.test.ts +110 -0
  221. package/src/services/zone-detector.ts +102 -0
  222. package/src/services/zone-policy.test.ts +97 -0
  223. package/src/services/zone-policy.ts +126 -0
  224. package/src/templates/generator.test.ts +645 -0
  225. package/src/templates/generator.ts +1119 -0
  226. package/src/templates/types.ts +62 -0
  227. package/src/test-utils/INTERACTIVE_PROMPTS.md +167 -0
  228. package/src/test-utils/cli-context-interactive.test.ts +152 -0
  229. package/src/test-utils/cli-context-server.test.ts +66 -0
  230. package/src/test-utils/cli-context.test.ts +273 -0
  231. package/src/test-utils/cli-context.ts +677 -0
  232. package/src/test-utils/cli-result.test.ts +282 -0
  233. package/src/test-utils/cli-result.ts +241 -0
  234. package/src/test-utils/cli.ts +55 -0
  235. package/src/test-utils/completion-harness.test.ts +126 -0
  236. package/src/test-utils/completion-harness.ts +82 -0
  237. package/src/test-utils/database.test.ts +182 -0
  238. package/src/test-utils/database.ts +126 -0
  239. package/src/test-utils/filesystem.test.ts +208 -0
  240. package/src/test-utils/filesystem.ts +142 -0
  241. package/src/test-utils/fixtures.test.ts +123 -0
  242. package/src/test-utils/fixtures.ts +160 -0
  243. package/src/test-utils/golden-diff.ts +197 -0
  244. package/src/test-utils/index.ts +77 -0
  245. package/src/test-utils/integration.ts +81 -0
  246. package/src/test-utils/module-fixtures.ts +468 -0
  247. package/src/test-utils/modules.test.ts +144 -0
  248. package/src/test-utils/modules.ts +183 -0
  249. package/src/test-utils/setup-test-db.ts +90 -0
  250. package/src/test-utils/value-extractor.test.ts +231 -0
  251. package/src/test-utils/value-extractor.ts +228 -0
  252. package/src/types/infrastructure.ts +157 -0
  253. package/src/utils/shell.test.ts +365 -0
  254. package/src/utils/shell.ts +159 -0
  255. package/src/validation/schemas.ts +166 -0
  256. package/src/variables/ansible-resolver.test.ts +142 -0
  257. package/src/variables/ansible-resolver.ts +69 -0
  258. package/src/variables/capability-self-ref.test.ts +220 -0
  259. package/src/variables/context.test.ts +1265 -0
  260. package/src/variables/context.ts +624 -0
  261. package/src/variables/declarative-derivation.test.ts +743 -0
  262. package/src/variables/declarative-derivation.ts +200 -0
  263. package/src/variables/parser.test.ts +231 -0
  264. package/src/variables/parser.ts +76 -0
  265. package/src/variables/resolver.test.ts +458 -0
  266. package/src/variables/resolver.ts +282 -0
  267. package/src/variables/types.ts +59 -0
@@ -0,0 +1,92 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import {
3
+ isAnsibleInventoryAvailable,
4
+ isAnsibleLintAvailable,
5
+ isAnsiblePlaybookAvailable,
6
+ validateInventory,
7
+ validatePlaybookSyntax,
8
+ validateWithAnsibleLint,
9
+ } from './validation';
10
+
11
+ describe('Ansible Tool Availability', () => {
12
+ test('checks if ansible-lint is available', () => {
13
+ const available = isAnsibleLintAvailable();
14
+ expect(typeof available).toBe('boolean');
15
+ // Note: May be false in environments without ansible-lint
16
+ });
17
+
18
+ test('checks if ansible-playbook is available', () => {
19
+ const available = isAnsiblePlaybookAvailable();
20
+ expect(typeof available).toBe('boolean');
21
+ });
22
+
23
+ test('checks if ansible-inventory is available', () => {
24
+ const available = isAnsibleInventoryAvailable();
25
+ expect(typeof available).toBe('boolean');
26
+ });
27
+ });
28
+
29
+ describe('validateWithAnsibleLint', () => {
30
+ test('returns error when path does not exist', async () => {
31
+ const result = await validateWithAnsibleLint('/nonexistent/path');
32
+ expect(result.success).toBe(false);
33
+ // Could be either missing path or missing ansible-lint
34
+ expect(result.error).toBeDefined();
35
+ });
36
+
37
+ test('returns error when playbook.yml not found', async () => {
38
+ const result = await validateWithAnsibleLint('/tmp');
39
+ expect(result.success).toBe(false);
40
+ // Could be either missing playbook or missing ansible-lint
41
+ expect(result.error).toBeDefined();
42
+ });
43
+
44
+ test('returns error when ansible-lint not installed', async () => {
45
+ if (!isAnsibleLintAvailable()) {
46
+ const result = await validateWithAnsibleLint('/tmp');
47
+ expect(result.success).toBe(false);
48
+ expect(result.error).toContain('ansible-lint command not found');
49
+ } else {
50
+ // If ansible-lint IS installed, test passes (can't test "not installed" case)
51
+ expect(true).toBe(true);
52
+ }
53
+ });
54
+ });
55
+
56
+ describe('validatePlaybookSyntax', () => {
57
+ test('returns error when playbook does not exist', async () => {
58
+ const result = await validatePlaybookSyntax('/nonexistent/playbook.yml', '/tmp');
59
+ expect(result.success).toBe(false);
60
+ expect(result.error).toContain('Playbook not found');
61
+ });
62
+
63
+ test('returns error when inventory does not exist', async () => {
64
+ const result = await validatePlaybookSyntax('/tmp/playbook.yml', '/nonexistent/inventory');
65
+ expect(result.success).toBe(false);
66
+ expect(result.error).toContain('not found');
67
+ });
68
+
69
+ test('returns error when ansible-playbook not installed', async () => {
70
+ if (!isAnsiblePlaybookAvailable()) {
71
+ const result = await validatePlaybookSyntax('/tmp/playbook.yml', '/tmp');
72
+ expect(result.success).toBe(false);
73
+ expect(result.error).toContain('ansible-playbook command not found');
74
+ }
75
+ });
76
+ });
77
+
78
+ describe('validateInventory', () => {
79
+ test('returns error when inventory does not exist', async () => {
80
+ const result = await validateInventory('/nonexistent/inventory');
81
+ expect(result.success).toBe(false);
82
+ expect(result.error).toContain('Inventory not found');
83
+ });
84
+
85
+ test('returns error when ansible-inventory not installed', async () => {
86
+ if (!isAnsibleInventoryAvailable()) {
87
+ const result = await validateInventory('/tmp');
88
+ expect(result.success).toBe(false);
89
+ expect(result.error).toContain('ansible-inventory command not found');
90
+ }
91
+ });
92
+ });
@@ -0,0 +1,272 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { AnsibleInventorySchema, parseJsonWithValidation } from '../validation/schemas';
4
+
5
+ /**
6
+ * Validation result structure
7
+ */
8
+ export interface ValidationResult {
9
+ success: boolean;
10
+ stdout?: string;
11
+ stderr?: string;
12
+ error?: string;
13
+ details?: unknown;
14
+ }
15
+
16
+ /**
17
+ * Check if ansible-lint is available
18
+ *
19
+ * Policy function (Rule 10.1) - validation only
20
+ *
21
+ * @returns True if ansible-lint is installed
22
+ */
23
+ export function isAnsibleLintAvailable(): boolean {
24
+ const result = Bun.spawnSync(['which', 'ansible-lint'], {
25
+ stdout: 'pipe',
26
+ stderr: 'pipe',
27
+ });
28
+ return result.exitCode === 0;
29
+ }
30
+
31
+ /**
32
+ * Check if ansible-playbook is available
33
+ *
34
+ * Policy function - validation only
35
+ *
36
+ * @returns True if ansible-playbook is installed
37
+ */
38
+ export function isAnsiblePlaybookAvailable(): boolean {
39
+ const result = Bun.spawnSync(['which', 'ansible-playbook'], {
40
+ stdout: 'pipe',
41
+ stderr: 'pipe',
42
+ });
43
+ return result.exitCode === 0;
44
+ }
45
+
46
+ /**
47
+ * Check if ansible-inventory is available
48
+ *
49
+ * Policy function - validation only
50
+ *
51
+ * @returns True if ansible-inventory is installed
52
+ */
53
+ export function isAnsibleInventoryAvailable(): boolean {
54
+ const result = Bun.spawnSync(['which', 'ansible-inventory'], {
55
+ stdout: 'pipe',
56
+ stderr: 'pipe',
57
+ });
58
+ return result.exitCode === 0;
59
+ }
60
+
61
+ /**
62
+ * Validate generated Ansible with ansible-lint
63
+ *
64
+ * Execution function (Rule 10.1) - spawns external process
65
+ *
66
+ * Fails on BOTH warnings and errors (strict mode)
67
+ *
68
+ * @param ansiblePath - Path to ansible directory containing playbook
69
+ * @param vaultPasswordFile - Optional path to vault password file
70
+ * @returns Validation result
71
+ */
72
+ export async function validateWithAnsibleLint(
73
+ ansiblePath: string,
74
+ vaultPasswordFile?: string,
75
+ ): Promise<ValidationResult> {
76
+ // Check if ansible-lint is available
77
+ if (!isAnsibleLintAvailable()) {
78
+ return {
79
+ success: false,
80
+ error:
81
+ 'ansible-lint command not found. Please install ansible-lint: pip install ansible-lint',
82
+ };
83
+ }
84
+
85
+ // Check if path exists
86
+ if (!existsSync(ansiblePath)) {
87
+ return {
88
+ success: false,
89
+ error: `Ansible path does not exist: ${ansiblePath}`,
90
+ };
91
+ }
92
+
93
+ // Check if playbook exists
94
+ const playbookPath = join(ansiblePath, 'playbook.yml');
95
+ if (!existsSync(playbookPath)) {
96
+ return {
97
+ success: false,
98
+ error: `Playbook not found: ${playbookPath}`,
99
+ };
100
+ }
101
+
102
+ // Run ansible-lint
103
+ // Note: ansible-lint exits with non-zero on warnings AND errors
104
+ const args = ['ansible-lint', playbookPath];
105
+ const env = { ...process.env };
106
+
107
+ // If vault password file provided, set environment variable for ansible-vault
108
+ if (vaultPasswordFile) {
109
+ env.ANSIBLE_VAULT_PASSWORD_FILE = vaultPasswordFile;
110
+ }
111
+
112
+ const result = Bun.spawnSync(args, {
113
+ stdout: 'pipe',
114
+ stderr: 'pipe',
115
+ cwd: ansiblePath,
116
+ env,
117
+ });
118
+
119
+ const stdout = result.stdout ? new TextDecoder().decode(result.stdout) : '';
120
+ const stderr = result.stderr ? new TextDecoder().decode(result.stderr) : '';
121
+
122
+ if (result.exitCode !== 0) {
123
+ return {
124
+ success: false,
125
+ error: `ansible-lint found issues (exit code ${result.exitCode})`,
126
+ stdout,
127
+ stderr,
128
+ };
129
+ }
130
+
131
+ return {
132
+ success: true,
133
+ stdout,
134
+ };
135
+ }
136
+
137
+ /**
138
+ * Validate Ansible playbook syntax
139
+ *
140
+ * Execution function - spawns external process
141
+ *
142
+ * @param playbookPath - Path to playbook.yml
143
+ * @param inventoryPath - Path to inventory directory or file
144
+ * @param vaultPasswordFile - Optional path to vault password file
145
+ * @returns Validation result
146
+ */
147
+ export async function validatePlaybookSyntax(
148
+ playbookPath: string,
149
+ inventoryPath: string,
150
+ vaultPasswordFile?: string,
151
+ ): Promise<ValidationResult> {
152
+ // Check if ansible-playbook is available
153
+ if (!isAnsiblePlaybookAvailable()) {
154
+ return {
155
+ success: false,
156
+ error:
157
+ 'ansible-playbook command not found. Please install Ansible: https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html',
158
+ };
159
+ }
160
+
161
+ // Check if files exist
162
+ if (!existsSync(playbookPath)) {
163
+ return {
164
+ success: false,
165
+ error: `Playbook not found: ${playbookPath}`,
166
+ };
167
+ }
168
+
169
+ if (!existsSync(inventoryPath)) {
170
+ return {
171
+ success: false,
172
+ error: `Inventory not found: ${inventoryPath}`,
173
+ };
174
+ }
175
+
176
+ // Run ansible-playbook --syntax-check
177
+ const env = { ...process.env };
178
+
179
+ // If vault password file provided, set environment variable for ansible-vault
180
+ if (vaultPasswordFile) {
181
+ env.ANSIBLE_VAULT_PASSWORD_FILE = vaultPasswordFile;
182
+ }
183
+
184
+ const result = Bun.spawnSync(
185
+ ['ansible-playbook', '--syntax-check', '-i', inventoryPath, playbookPath],
186
+ {
187
+ stdout: 'pipe',
188
+ stderr: 'pipe',
189
+ env,
190
+ },
191
+ );
192
+
193
+ const stdout = result.stdout ? new TextDecoder().decode(result.stdout) : '';
194
+ const stderr = result.stderr ? new TextDecoder().decode(result.stderr) : '';
195
+
196
+ if (result.exitCode !== 0) {
197
+ return {
198
+ success: false,
199
+ error: `Syntax check failed (exit code ${result.exitCode})`,
200
+ stdout,
201
+ stderr,
202
+ };
203
+ }
204
+
205
+ return {
206
+ success: true,
207
+ stdout,
208
+ };
209
+ }
210
+
211
+ /**
212
+ * Validate Ansible inventory structure
213
+ *
214
+ * Execution function - spawns external process
215
+ *
216
+ * @param inventoryPath - Path to inventory directory or file
217
+ * @returns Validation result
218
+ */
219
+ export async function validateInventory(inventoryPath: string): Promise<ValidationResult> {
220
+ // Check if ansible-inventory is available
221
+ if (!isAnsibleInventoryAvailable()) {
222
+ return {
223
+ success: false,
224
+ error: 'ansible-inventory command not found. Please install Ansible.',
225
+ };
226
+ }
227
+
228
+ // Check if inventory exists
229
+ if (!existsSync(inventoryPath)) {
230
+ return {
231
+ success: false,
232
+ error: `Inventory not found: ${inventoryPath}`,
233
+ };
234
+ }
235
+
236
+ // Run ansible-inventory --list
237
+ const result = Bun.spawnSync(['ansible-inventory', '-i', inventoryPath, '--list'], {
238
+ stdout: 'pipe',
239
+ stderr: 'pipe',
240
+ });
241
+
242
+ const stdout = result.stdout ? new TextDecoder().decode(result.stdout) : '';
243
+ const stderr = result.stderr ? new TextDecoder().decode(result.stderr) : '';
244
+
245
+ if (result.exitCode !== 0) {
246
+ return {
247
+ success: false,
248
+ error: `Inventory parsing failed (exit code ${result.exitCode})`,
249
+ stdout,
250
+ stderr,
251
+ };
252
+ }
253
+
254
+ // Try to parse and validate JSON output
255
+ try {
256
+ const inventory = parseJsonWithValidation(stdout, AnsibleInventorySchema, 'Ansible inventory');
257
+
258
+ return {
259
+ success: true,
260
+ stdout,
261
+ details: inventory,
262
+ };
263
+ } catch (error) {
264
+ return {
265
+ success: false,
266
+ error: 'Failed to parse inventory JSON output',
267
+ stdout,
268
+ stderr,
269
+ details: error,
270
+ };
271
+ }
272
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Digital Ocean API Client
3
+ * Validates connection to Digital Ocean API
4
+ */
5
+
6
+ import type { TestResult } from '../types/infrastructure';
7
+
8
+ export interface DigitalOceanCredentials {
9
+ api_token: string;
10
+ }
11
+
12
+ /**
13
+ * Test connection to Digital Ocean API
14
+ * Makes a simple API call to verify credentials
15
+ */
16
+ export async function testDigitalOceanConnection(
17
+ credentials: DigitalOceanCredentials,
18
+ ): Promise<TestResult> {
19
+ try {
20
+ const { api_token } = credentials;
21
+
22
+ // Call /v2/account endpoint (lightweight, returns account info)
23
+ const response = await fetch('https://api.digitalocean.com/v2/account', {
24
+ method: 'GET',
25
+ headers: {
26
+ Authorization: `Bearer ${api_token}`,
27
+ 'Content-Type': 'application/json',
28
+ },
29
+ });
30
+
31
+ if (!response.ok) {
32
+ if (response.status === 401) {
33
+ return {
34
+ success: false,
35
+ message: 'Authentication failed - check API token',
36
+ details: { status: response.status },
37
+ };
38
+ }
39
+
40
+ return {
41
+ success: false,
42
+ message: `API request failed with status ${response.status}`,
43
+ details: { status: response.status },
44
+ };
45
+ }
46
+
47
+ const data = (await response.json()) as unknown;
48
+
49
+ // Validate response structure
50
+ if (
51
+ typeof data !== 'object' ||
52
+ data === null ||
53
+ !('account' in data) ||
54
+ typeof data.account !== 'object' ||
55
+ data.account === null
56
+ ) {
57
+ return {
58
+ success: false,
59
+ message: 'Unexpected API response format',
60
+ details: { response: data },
61
+ };
62
+ }
63
+
64
+ const account = data.account as {
65
+ email: string;
66
+ status: string;
67
+ droplet_limit: number;
68
+ };
69
+
70
+ return {
71
+ success: true,
72
+ message: `Connected to Digital Ocean (${account.email})`,
73
+ details: {
74
+ email: account.email,
75
+ status: account.status,
76
+ droplet_limit: account.droplet_limit,
77
+ },
78
+ };
79
+ } catch (error) {
80
+ if (error instanceof TypeError && error.message.includes('fetch')) {
81
+ return {
82
+ success: false,
83
+ message: 'Network error - check network connectivity',
84
+ details: { error: error.message },
85
+ };
86
+ }
87
+
88
+ return {
89
+ success: false,
90
+ message: `Connection test failed: ${error instanceof Error ? error.message : String(error)}`,
91
+ details: { error: String(error) },
92
+ };
93
+ }
94
+ }