@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.
- package/README.md +1566 -0
- package/bin/celilo +16 -0
- package/drizzle/0000_complex_puma.sql +179 -0
- package/drizzle/0001_dizzy_wolfpack.sql +2 -0
- package/drizzle/0002_web_routes.sql +16 -0
- package/drizzle/0003_backup_storage.sql +32 -0
- package/drizzle/meta/0000_snapshot.json +1151 -0
- package/drizzle/meta/0001_snapshot.json +1167 -0
- package/drizzle/meta/0002_snapshot.json +1257 -0
- package/drizzle/meta/_journal.json +27 -0
- package/package.json +64 -0
- package/schemas/system_config.json +106 -0
- package/src/__integration__/container-services-cli.integration.test.ts +246 -0
- package/src/ansible/dependencies.test.ts +309 -0
- package/src/ansible/dependencies.ts +896 -0
- package/src/ansible/inventory.test.ts +463 -0
- package/src/ansible/inventory.ts +445 -0
- package/src/ansible/secrets.ts +222 -0
- package/src/ansible/validation.test.ts +92 -0
- package/src/ansible/validation.ts +272 -0
- package/src/api-clients/digitalocean.ts +94 -0
- package/src/api-clients/proxmox.ts +655 -0
- package/src/capabilities/logging-wrapper.test.ts +217 -0
- package/src/capabilities/lookup.test.ts +149 -0
- package/src/capabilities/lookup.ts +89 -0
- package/src/capabilities/public-web-helpers.test.ts +198 -0
- package/src/capabilities/public-web-publish.test.ts +458 -0
- package/src/capabilities/registration.test.ts +395 -0
- package/src/capabilities/registration.ts +200 -0
- package/src/capabilities/route-validation.test.ts +121 -0
- package/src/capabilities/route-validation.ts +96 -0
- package/src/capabilities/secret-ref.test.ts +313 -0
- package/src/capabilities/secret-validation.ts +157 -0
- package/src/capabilities/secrets.test.ts +750 -0
- package/src/capabilities/secrets.ts +244 -0
- package/src/capabilities/validation.test.ts +613 -0
- package/src/capabilities/validation.ts +160 -0
- package/src/capabilities/well-known.test.ts +238 -0
- package/src/capabilities/well-known.ts +222 -0
- package/src/cli/cli.test.ts +654 -0
- package/src/cli/command-registry.ts +742 -0
- package/src/cli/command-tree-parser.test.ts +180 -0
- package/src/cli/command-tree-parser.ts +193 -0
- package/src/cli/commands/backup-create.ts +137 -0
- package/src/cli/commands/backup-delete.ts +74 -0
- package/src/cli/commands/backup-import.ts +97 -0
- package/src/cli/commands/backup-list.ts +132 -0
- package/src/cli/commands/backup-name.ts +73 -0
- package/src/cli/commands/backup-prune.ts +98 -0
- package/src/cli/commands/backup-restore.ts +122 -0
- package/src/cli/commands/capability-info.ts +121 -0
- package/src/cli/commands/capability-list.ts +47 -0
- package/src/cli/commands/completion.ts +87 -0
- package/src/cli/commands/hook-run.ts +176 -0
- package/src/cli/commands/ipam.ts +607 -0
- package/src/cli/commands/machine-add.ts +235 -0
- package/src/cli/commands/machine-earmark.ts +82 -0
- package/src/cli/commands/machine-list.ts +77 -0
- package/src/cli/commands/machine-remove.ts +90 -0
- package/src/cli/commands/machine-status.ts +131 -0
- package/src/cli/commands/module-audit.ts +51 -0
- package/src/cli/commands/module-build.ts +60 -0
- package/src/cli/commands/module-config.ts +170 -0
- package/src/cli/commands/module-deploy.ts +71 -0
- package/src/cli/commands/module-generate.ts +236 -0
- package/src/cli/commands/module-health.ts +108 -0
- package/src/cli/commands/module-import.ts +80 -0
- package/src/cli/commands/module-list.ts +43 -0
- package/src/cli/commands/module-logs.ts +73 -0
- package/src/cli/commands/module-remove.ts +162 -0
- package/src/cli/commands/module-show.ts +208 -0
- package/src/cli/commands/module-status.ts +131 -0
- package/src/cli/commands/module-types.ts +189 -0
- package/src/cli/commands/module-upgrade.ts +192 -0
- package/src/cli/commands/package.ts +68 -0
- package/src/cli/commands/secret-list.ts +99 -0
- package/src/cli/commands/secret-set.ts +134 -0
- package/src/cli/commands/service-add-digitalocean.ts +133 -0
- package/src/cli/commands/service-add-proxmox.ts +342 -0
- package/src/cli/commands/service-config-get.ts +83 -0
- package/src/cli/commands/service-config-set.ts +145 -0
- package/src/cli/commands/service-list.ts +74 -0
- package/src/cli/commands/service-reconfigure.ts +230 -0
- package/src/cli/commands/service-remove.ts +103 -0
- package/src/cli/commands/service-verify.ts +240 -0
- package/src/cli/commands/status.ts +216 -0
- package/src/cli/commands/storage-add-local.ts +106 -0
- package/src/cli/commands/storage-add-s3.ts +114 -0
- package/src/cli/commands/storage-list.ts +72 -0
- package/src/cli/commands/storage-remove.ts +54 -0
- package/src/cli/commands/storage-set-default.ts +44 -0
- package/src/cli/commands/storage-verify.ts +54 -0
- package/src/cli/commands/system-config.ts +168 -0
- package/src/cli/commands/system-init.ts +314 -0
- package/src/cli/commands/system-secret-get.ts +98 -0
- package/src/cli/commands/system-secret-set.ts +76 -0
- package/src/cli/commands/system-vault-password.ts +34 -0
- package/src/cli/completion.test.ts +37 -0
- package/src/cli/completion.ts +482 -0
- package/src/cli/fuel-gauge.test.ts +208 -0
- package/src/cli/fuel-gauge.ts +405 -0
- package/src/cli/generate-zsh-completion.test.ts +95 -0
- package/src/cli/generate-zsh-completion.ts +497 -0
- package/src/cli/index.ts +1583 -0
- package/src/cli/interactive-config.test.ts +201 -0
- package/src/cli/interactive-config.ts +62 -0
- package/src/cli/parser.test.ts +227 -0
- package/src/cli/parser.ts +244 -0
- package/src/cli/prompts.test.ts +33 -0
- package/src/cli/prompts.ts +121 -0
- package/src/cli/types.ts +38 -0
- package/src/cli/validators.test.ts +235 -0
- package/src/cli/validators.ts +188 -0
- package/src/config/env.ts +41 -0
- package/src/config/paths.test.ts +172 -0
- package/src/config/paths.ts +108 -0
- package/src/db/client.ts +190 -0
- package/src/db/migrate.ts +30 -0
- package/src/db/schema.test.ts +221 -0
- package/src/db/schema.ts +434 -0
- package/src/hooks/capability-loader-firewall.test.ts +246 -0
- package/src/hooks/capability-loader.test.ts +100 -0
- package/src/hooks/capability-loader.ts +520 -0
- package/src/hooks/define-hook.test.ts +488 -0
- package/src/hooks/executor.test.ts +462 -0
- package/src/hooks/executor.ts +469 -0
- package/src/hooks/logger.test.ts +54 -0
- package/src/hooks/logger.ts +95 -0
- package/src/hooks/test-fixtures/failing-hook.ts +13 -0
- package/src/hooks/test-fixtures/no-default-hook.ts +6 -0
- package/src/hooks/test-fixtures/success-hook.ts +20 -0
- package/src/hooks/test-fixtures/unbranded-hook.ts +11 -0
- package/src/hooks/test-fixtures/void-hook.ts +13 -0
- package/src/hooks/types.ts +89 -0
- package/src/infrastructure/property-extractor.test.ts +194 -0
- package/src/infrastructure/property-extractor.ts +151 -0
- package/src/ipam/allocator.test.ts +442 -0
- package/src/ipam/allocator.ts +369 -0
- package/src/ipam/auto-allocator.test.ts +247 -0
- package/src/ipam/auto-allocator.ts +270 -0
- package/src/ipam/subnet-parser.test.ts +107 -0
- package/src/ipam/subnet-parser.ts +136 -0
- package/src/manifest/contracts/index.ts +61 -0
- package/src/manifest/contracts/v1.ts +118 -0
- package/src/manifest/json-schema-roundtrip.test.ts +99 -0
- package/src/manifest/schema.ts +367 -0
- package/src/manifest/template-validator.test.ts +231 -0
- package/src/manifest/template-validator.ts +322 -0
- package/src/manifest/validate.test.ts +1180 -0
- package/src/manifest/validate.ts +415 -0
- package/src/module/import.test.ts +355 -0
- package/src/module/import.ts +676 -0
- package/src/module/packaging/audit.ts +169 -0
- package/src/module/packaging/build.ts +228 -0
- package/src/module/packaging/checksum.ts +41 -0
- package/src/module/packaging/extract.ts +234 -0
- package/src/module/packaging/signature.ts +47 -0
- package/src/secrets/encryption.test.ts +284 -0
- package/src/secrets/encryption.ts +162 -0
- package/src/secrets/generators.test.ts +112 -0
- package/src/secrets/generators.ts +127 -0
- package/src/secrets/master-key.test.ts +159 -0
- package/src/secrets/master-key.ts +114 -0
- package/src/secrets/storage.test.ts +115 -0
- package/src/secrets/storage.ts +106 -0
- package/src/secrets/vault.test.ts +35 -0
- package/src/secrets/vault.ts +42 -0
- package/src/services/backup-create.ts +532 -0
- package/src/services/backup-metadata.ts +198 -0
- package/src/services/backup-restore.ts +229 -0
- package/src/services/backup-retention.ts +84 -0
- package/src/services/backup-storage.ts +281 -0
- package/src/services/build-stream.test.ts +122 -0
- package/src/services/build-stream.ts +201 -0
- package/src/services/config-interview.ts +694 -0
- package/src/services/container-service.test.ts +298 -0
- package/src/services/container-service.ts +401 -0
- package/src/services/cross-module-data-manager.test.ts +405 -0
- package/src/services/cross-module-data-manager.ts +412 -0
- package/src/services/deploy-ansible.ts +88 -0
- package/src/services/deploy-planner.ts +153 -0
- package/src/services/deploy-preflight.ts +274 -0
- package/src/services/deploy-ssh.ts +131 -0
- package/src/services/deploy-terraform.test.ts +55 -0
- package/src/services/deploy-terraform.ts +445 -0
- package/src/services/deploy-validation.ts +311 -0
- package/src/services/dns-auto-register.ts +211 -0
- package/src/services/health-runner.ts +184 -0
- package/src/services/infrastructure-selector.test.ts +485 -0
- package/src/services/infrastructure-selector.ts +245 -0
- package/src/services/infrastructure-variable-resolver.test.ts +751 -0
- package/src/services/infrastructure-variable-resolver.ts +234 -0
- package/src/services/machine-detector.ts +328 -0
- package/src/services/machine-pool.test.ts +405 -0
- package/src/services/machine-pool.ts +316 -0
- package/src/services/manifest-validation.ts +120 -0
- package/src/services/module-build.test.ts +290 -0
- package/src/services/module-build.ts +431 -0
- package/src/services/module-config.test.ts +237 -0
- package/src/services/module-config.ts +298 -0
- package/src/services/module-deploy.ts +862 -0
- package/src/services/module-types-drift.test.ts +73 -0
- package/src/services/module-types-generator.test.ts +288 -0
- package/src/services/module-types-generator.ts +189 -0
- package/src/services/proxmox-state-recovery.ts +140 -0
- package/src/services/schema-validation.ts +155 -0
- package/src/services/secret-schema-loader.test.ts +311 -0
- package/src/services/secret-schema-loader.ts +239 -0
- package/src/services/ssh-key-manager.test.ts +283 -0
- package/src/services/ssh-key-manager.ts +193 -0
- package/src/services/storage-providers/local.ts +105 -0
- package/src/services/storage-providers/s3.ts +182 -0
- package/src/services/storage-providers/types.ts +24 -0
- package/src/services/system-config-schema-types.ts +25 -0
- package/src/services/system-config-validator.test.ts +160 -0
- package/src/services/system-config-validator.ts +74 -0
- package/src/services/system-init.test.ts +153 -0
- package/src/services/system-init.ts +253 -0
- package/src/services/terraform-safety.ts +174 -0
- package/src/services/zone-detector.test.ts +110 -0
- package/src/services/zone-detector.ts +102 -0
- package/src/services/zone-policy.test.ts +97 -0
- package/src/services/zone-policy.ts +126 -0
- package/src/templates/generator.test.ts +645 -0
- package/src/templates/generator.ts +1119 -0
- package/src/templates/types.ts +62 -0
- package/src/test-utils/INTERACTIVE_PROMPTS.md +167 -0
- package/src/test-utils/cli-context-interactive.test.ts +152 -0
- package/src/test-utils/cli-context-server.test.ts +66 -0
- package/src/test-utils/cli-context.test.ts +273 -0
- package/src/test-utils/cli-context.ts +677 -0
- package/src/test-utils/cli-result.test.ts +282 -0
- package/src/test-utils/cli-result.ts +241 -0
- package/src/test-utils/cli.ts +55 -0
- package/src/test-utils/completion-harness.test.ts +126 -0
- package/src/test-utils/completion-harness.ts +82 -0
- package/src/test-utils/database.test.ts +182 -0
- package/src/test-utils/database.ts +126 -0
- package/src/test-utils/filesystem.test.ts +208 -0
- package/src/test-utils/filesystem.ts +142 -0
- package/src/test-utils/fixtures.test.ts +123 -0
- package/src/test-utils/fixtures.ts +160 -0
- package/src/test-utils/golden-diff.ts +197 -0
- package/src/test-utils/index.ts +77 -0
- package/src/test-utils/integration.ts +81 -0
- package/src/test-utils/module-fixtures.ts +468 -0
- package/src/test-utils/modules.test.ts +144 -0
- package/src/test-utils/modules.ts +183 -0
- package/src/test-utils/setup-test-db.ts +90 -0
- package/src/test-utils/value-extractor.test.ts +231 -0
- package/src/test-utils/value-extractor.ts +228 -0
- package/src/types/infrastructure.ts +157 -0
- package/src/utils/shell.test.ts +365 -0
- package/src/utils/shell.ts +159 -0
- package/src/validation/schemas.ts +166 -0
- package/src/variables/ansible-resolver.test.ts +142 -0
- package/src/variables/ansible-resolver.ts +69 -0
- package/src/variables/capability-self-ref.test.ts +220 -0
- package/src/variables/context.test.ts +1265 -0
- package/src/variables/context.ts +624 -0
- package/src/variables/declarative-derivation.test.ts +743 -0
- package/src/variables/declarative-derivation.ts +200 -0
- package/src/variables/parser.test.ts +231 -0
- package/src/variables/parser.ts +76 -0
- package/src/variables/resolver.test.ts +458 -0
- package/src/variables/resolver.ts +282 -0
- 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
|
+
}
|