@build-astron-co/nimbus 0.2.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/LICENSE +21 -0
- package/README.md +628 -0
- package/bin/nimbus +38 -0
- package/package.json +80 -0
- package/src/__tests__/app.test.ts +76 -0
- package/src/__tests__/audit.test.ts +877 -0
- package/src/__tests__/circuit-breaker.test.ts +116 -0
- package/src/__tests__/cli-run.test.ts +115 -0
- package/src/__tests__/context-manager.test.ts +502 -0
- package/src/__tests__/context.test.ts +242 -0
- package/src/__tests__/enterprise.test.ts +401 -0
- package/src/__tests__/generator.test.ts +433 -0
- package/src/__tests__/hooks.test.ts +582 -0
- package/src/__tests__/init.test.ts +436 -0
- package/src/__tests__/intent-parser.test.ts +229 -0
- package/src/__tests__/llm-router.test.ts +209 -0
- package/src/__tests__/lsp.test.ts +293 -0
- package/src/__tests__/modes.test.ts +336 -0
- package/src/__tests__/permissions.test.ts +338 -0
- package/src/__tests__/serve.test.ts +275 -0
- package/src/__tests__/sessions.test.ts +227 -0
- package/src/__tests__/sharing.test.ts +288 -0
- package/src/__tests__/snapshots.test.ts +581 -0
- package/src/__tests__/state-db.test.ts +334 -0
- package/src/__tests__/stream-with-tools.test.ts +732 -0
- package/src/__tests__/subagents.test.ts +176 -0
- package/src/__tests__/system-prompt.test.ts +169 -0
- package/src/__tests__/tool-converter.test.ts +256 -0
- package/src/__tests__/tool-schemas.test.ts +397 -0
- package/src/__tests__/tools.test.ts +143 -0
- package/src/__tests__/version.test.ts +49 -0
- package/src/agent/compaction-agent.ts +227 -0
- package/src/agent/context-manager.ts +435 -0
- package/src/agent/context.ts +427 -0
- package/src/agent/deploy-preview.ts +426 -0
- package/src/agent/index.ts +68 -0
- package/src/agent/loop.ts +717 -0
- package/src/agent/modes.ts +429 -0
- package/src/agent/permissions.ts +466 -0
- package/src/agent/subagents/base.ts +116 -0
- package/src/agent/subagents/cost.ts +51 -0
- package/src/agent/subagents/explore.ts +42 -0
- package/src/agent/subagents/general.ts +54 -0
- package/src/agent/subagents/index.ts +102 -0
- package/src/agent/subagents/infra.ts +59 -0
- package/src/agent/subagents/security.ts +69 -0
- package/src/agent/system-prompt.ts +436 -0
- package/src/app.ts +122 -0
- package/src/audit/activity-log.ts +290 -0
- package/src/audit/compliance-checker.ts +540 -0
- package/src/audit/cost-tracker.ts +318 -0
- package/src/audit/index.ts +23 -0
- package/src/audit/security-scanner.ts +596 -0
- package/src/auth/guard.ts +75 -0
- package/src/auth/index.ts +56 -0
- package/src/auth/oauth.ts +455 -0
- package/src/auth/providers.ts +470 -0
- package/src/auth/sso.ts +113 -0
- package/src/auth/store.ts +505 -0
- package/src/auth/types.ts +187 -0
- package/src/build.ts +141 -0
- package/src/cli/index.ts +16 -0
- package/src/cli/init.ts +854 -0
- package/src/cli/openapi-spec.ts +356 -0
- package/src/cli/run.ts +237 -0
- package/src/cli/serve-auth.ts +80 -0
- package/src/cli/serve.ts +462 -0
- package/src/cli/web.ts +67 -0
- package/src/cli.ts +1417 -0
- package/src/clients/core-engine-client.ts +227 -0
- package/src/clients/enterprise-client.ts +334 -0
- package/src/clients/generator-client.ts +351 -0
- package/src/clients/git-client.ts +627 -0
- package/src/clients/github-client.ts +410 -0
- package/src/clients/helm-client.ts +504 -0
- package/src/clients/index.ts +80 -0
- package/src/clients/k8s-client.ts +497 -0
- package/src/clients/llm-client.ts +161 -0
- package/src/clients/rest-client.ts +130 -0
- package/src/clients/service-discovery.ts +33 -0
- package/src/clients/terraform-client.ts +482 -0
- package/src/clients/tools-client.ts +1843 -0
- package/src/clients/ws-client.ts +115 -0
- package/src/commands/analyze/index.ts +352 -0
- package/src/commands/apply/helm.ts +473 -0
- package/src/commands/apply/index.ts +213 -0
- package/src/commands/apply/k8s.ts +454 -0
- package/src/commands/apply/terraform.ts +582 -0
- package/src/commands/ask.ts +167 -0
- package/src/commands/audit/index.ts +238 -0
- package/src/commands/auth-cloud.ts +294 -0
- package/src/commands/auth-list.ts +134 -0
- package/src/commands/auth-profile.ts +121 -0
- package/src/commands/auth-status.ts +141 -0
- package/src/commands/aws/ec2.ts +501 -0
- package/src/commands/aws/iam.ts +397 -0
- package/src/commands/aws/index.ts +133 -0
- package/src/commands/aws/lambda.ts +396 -0
- package/src/commands/aws/rds.ts +439 -0
- package/src/commands/aws/s3.ts +439 -0
- package/src/commands/aws/vpc.ts +393 -0
- package/src/commands/aws-discover.ts +649 -0
- package/src/commands/aws-terraform.ts +805 -0
- package/src/commands/azure/aks.ts +376 -0
- package/src/commands/azure/functions.ts +253 -0
- package/src/commands/azure/index.ts +116 -0
- package/src/commands/azure/storage.ts +478 -0
- package/src/commands/azure/vm.ts +355 -0
- package/src/commands/billing/index.ts +256 -0
- package/src/commands/chat.ts +314 -0
- package/src/commands/config.ts +346 -0
- package/src/commands/cost/cloud-cost-estimator.ts +266 -0
- package/src/commands/cost/estimator.ts +79 -0
- package/src/commands/cost/index.ts +594 -0
- package/src/commands/cost/parsers/terraform.ts +273 -0
- package/src/commands/cost/parsers/types.ts +25 -0
- package/src/commands/cost/pricing/aws.ts +544 -0
- package/src/commands/cost/pricing/azure.ts +499 -0
- package/src/commands/cost/pricing/gcp.ts +396 -0
- package/src/commands/cost/pricing/index.ts +40 -0
- package/src/commands/demo.ts +250 -0
- package/src/commands/doctor.ts +794 -0
- package/src/commands/drift/index.ts +439 -0
- package/src/commands/explain.ts +277 -0
- package/src/commands/feedback.ts +389 -0
- package/src/commands/fix.ts +324 -0
- package/src/commands/fs/index.ts +402 -0
- package/src/commands/gcp/compute.ts +325 -0
- package/src/commands/gcp/functions.ts +271 -0
- package/src/commands/gcp/gke.ts +438 -0
- package/src/commands/gcp/iam.ts +344 -0
- package/src/commands/gcp/index.ts +129 -0
- package/src/commands/gcp/storage.ts +284 -0
- package/src/commands/generate-helm.ts +1249 -0
- package/src/commands/generate-k8s.ts +1560 -0
- package/src/commands/generate-terraform.ts +1460 -0
- package/src/commands/gh/index.ts +863 -0
- package/src/commands/git/index.ts +1343 -0
- package/src/commands/helm/index.ts +1126 -0
- package/src/commands/help.ts +539 -0
- package/src/commands/history.ts +142 -0
- package/src/commands/import.ts +868 -0
- package/src/commands/index.ts +367 -0
- package/src/commands/init.ts +1046 -0
- package/src/commands/k8s/index.ts +1137 -0
- package/src/commands/login.ts +631 -0
- package/src/commands/logout.ts +83 -0
- package/src/commands/onboarding.ts +228 -0
- package/src/commands/plan/display.ts +279 -0
- package/src/commands/plan/index.ts +599 -0
- package/src/commands/preview.ts +452 -0
- package/src/commands/questionnaire.ts +1270 -0
- package/src/commands/resume.ts +55 -0
- package/src/commands/team/index.ts +346 -0
- package/src/commands/template.ts +232 -0
- package/src/commands/tf/index.ts +1034 -0
- package/src/commands/upgrade.ts +550 -0
- package/src/commands/usage/index.ts +134 -0
- package/src/commands/version.ts +170 -0
- package/src/compat/index.ts +2 -0
- package/src/compat/runtime.ts +12 -0
- package/src/compat/sqlite.ts +107 -0
- package/src/config/index.ts +17 -0
- package/src/config/manager.ts +530 -0
- package/src/config/safety-policy.ts +358 -0
- package/src/config/schema.ts +125 -0
- package/src/config/types.ts +527 -0
- package/src/context/context-db.ts +199 -0
- package/src/demo/index.ts +349 -0
- package/src/demo/scenarios/full-journey.ts +229 -0
- package/src/demo/scenarios/getting-started.ts +127 -0
- package/src/demo/scenarios/helm-release.ts +341 -0
- package/src/demo/scenarios/k8s-deployment.ts +194 -0
- package/src/demo/scenarios/terraform-vpc.ts +170 -0
- package/src/demo/types.ts +92 -0
- package/src/engine/cost-estimator.ts +438 -0
- package/src/engine/diagram-generator.ts +256 -0
- package/src/engine/drift-detector.ts +902 -0
- package/src/engine/executor.ts +1035 -0
- package/src/engine/index.ts +76 -0
- package/src/engine/orchestrator.ts +636 -0
- package/src/engine/planner.ts +720 -0
- package/src/engine/safety.ts +743 -0
- package/src/engine/verifier.ts +770 -0
- package/src/enterprise/audit.ts +348 -0
- package/src/enterprise/auth.ts +270 -0
- package/src/enterprise/billing.ts +822 -0
- package/src/enterprise/index.ts +17 -0
- package/src/enterprise/teams.ts +443 -0
- package/src/generator/best-practices.ts +1608 -0
- package/src/generator/helm.ts +630 -0
- package/src/generator/index.ts +37 -0
- package/src/generator/intent-parser.ts +514 -0
- package/src/generator/kubernetes.ts +976 -0
- package/src/generator/terraform.ts +1867 -0
- package/src/history/index.ts +8 -0
- package/src/history/manager.ts +322 -0
- package/src/history/types.ts +34 -0
- package/src/hooks/config.ts +432 -0
- package/src/hooks/engine.ts +391 -0
- package/src/hooks/index.ts +4 -0
- package/src/llm/auth-bridge.ts +198 -0
- package/src/llm/circuit-breaker.ts +140 -0
- package/src/llm/config-loader.ts +201 -0
- package/src/llm/cost-calculator.ts +171 -0
- package/src/llm/index.ts +8 -0
- package/src/llm/model-aliases.ts +115 -0
- package/src/llm/provider-registry.ts +63 -0
- package/src/llm/providers/anthropic.ts +433 -0
- package/src/llm/providers/bedrock.ts +477 -0
- package/src/llm/providers/google.ts +405 -0
- package/src/llm/providers/ollama.ts +767 -0
- package/src/llm/providers/openai-compatible.ts +340 -0
- package/src/llm/providers/openai.ts +328 -0
- package/src/llm/providers/openrouter.ts +338 -0
- package/src/llm/router.ts +1035 -0
- package/src/llm/types.ts +232 -0
- package/src/lsp/client.ts +298 -0
- package/src/lsp/languages.ts +116 -0
- package/src/lsp/manager.ts +278 -0
- package/src/mcp/client.ts +402 -0
- package/src/mcp/index.ts +5 -0
- package/src/mcp/manager.ts +133 -0
- package/src/nimbus.ts +214 -0
- package/src/plugins/index.ts +27 -0
- package/src/plugins/loader.ts +334 -0
- package/src/plugins/manager.ts +376 -0
- package/src/plugins/types.ts +284 -0
- package/src/scanners/cicd-scanner.ts +258 -0
- package/src/scanners/cloud-scanner.ts +466 -0
- package/src/scanners/framework-scanner.ts +469 -0
- package/src/scanners/iac-scanner.ts +388 -0
- package/src/scanners/index.ts +539 -0
- package/src/scanners/language-scanner.ts +276 -0
- package/src/scanners/package-manager-scanner.ts +277 -0
- package/src/scanners/types.ts +172 -0
- package/src/sessions/manager.ts +365 -0
- package/src/sessions/types.ts +44 -0
- package/src/sharing/sync.ts +296 -0
- package/src/sharing/viewer.ts +97 -0
- package/src/snapshots/index.ts +2 -0
- package/src/snapshots/manager.ts +530 -0
- package/src/state/artifacts.ts +147 -0
- package/src/state/audit.ts +137 -0
- package/src/state/billing.ts +240 -0
- package/src/state/checkpoints.ts +117 -0
- package/src/state/config.ts +67 -0
- package/src/state/conversations.ts +14 -0
- package/src/state/credentials.ts +154 -0
- package/src/state/db.ts +58 -0
- package/src/state/index.ts +26 -0
- package/src/state/messages.ts +115 -0
- package/src/state/projects.ts +123 -0
- package/src/state/schema.ts +236 -0
- package/src/state/sessions.ts +147 -0
- package/src/state/teams.ts +200 -0
- package/src/telemetry.ts +108 -0
- package/src/tools/aws-ops.ts +952 -0
- package/src/tools/azure-ops.ts +579 -0
- package/src/tools/file-ops.ts +593 -0
- package/src/tools/gcp-ops.ts +625 -0
- package/src/tools/git-ops.ts +773 -0
- package/src/tools/github-ops.ts +799 -0
- package/src/tools/helm-ops.ts +943 -0
- package/src/tools/index.ts +17 -0
- package/src/tools/k8s-ops.ts +819 -0
- package/src/tools/schemas/converter.ts +184 -0
- package/src/tools/schemas/devops.ts +612 -0
- package/src/tools/schemas/index.ts +73 -0
- package/src/tools/schemas/standard.ts +1144 -0
- package/src/tools/schemas/types.ts +705 -0
- package/src/tools/terraform-ops.ts +862 -0
- package/src/types/ambient.d.ts +193 -0
- package/src/types/config.ts +83 -0
- package/src/types/drift.ts +116 -0
- package/src/types/enterprise.ts +335 -0
- package/src/types/index.ts +20 -0
- package/src/types/plan.ts +44 -0
- package/src/types/request.ts +65 -0
- package/src/types/response.ts +54 -0
- package/src/types/service.ts +51 -0
- package/src/ui/App.tsx +997 -0
- package/src/ui/DeployPreview.tsx +169 -0
- package/src/ui/Header.tsx +68 -0
- package/src/ui/InputBox.tsx +350 -0
- package/src/ui/MessageList.tsx +585 -0
- package/src/ui/PermissionPrompt.tsx +151 -0
- package/src/ui/StatusBar.tsx +158 -0
- package/src/ui/ToolCallDisplay.tsx +409 -0
- package/src/ui/chat-ui.ts +853 -0
- package/src/ui/index.ts +33 -0
- package/src/ui/ink/index.ts +711 -0
- package/src/ui/streaming.ts +176 -0
- package/src/ui/types.ts +57 -0
- package/src/utils/analytics.ts +72 -0
- package/src/utils/cost-warning.ts +27 -0
- package/src/utils/env.ts +46 -0
- package/src/utils/errors.ts +69 -0
- package/src/utils/event-bus.ts +38 -0
- package/src/utils/index.ts +24 -0
- package/src/utils/logger.ts +171 -0
- package/src/utils/rate-limiter.ts +121 -0
- package/src/utils/service-auth.ts +49 -0
- package/src/utils/validation.ts +53 -0
- package/src/version.ts +4 -0
- package/src/watcher/index.ts +163 -0
- package/src/wizard/approval.ts +383 -0
- package/src/wizard/index.ts +25 -0
- package/src/wizard/prompts.ts +338 -0
- package/src/wizard/types.ts +171 -0
- package/src/wizard/ui.ts +556 -0
- package/src/wizard/wizard.ts +304 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apply Helm Command
|
|
3
|
+
*
|
|
4
|
+
* Install or upgrade Helm releases
|
|
5
|
+
*
|
|
6
|
+
* Usage: nimbus apply helm <release> <chart> [options]
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { logger } from '../../utils';
|
|
10
|
+
import { ui, confirm, input } from '../../wizard';
|
|
11
|
+
import { helmClient } from '../../clients';
|
|
12
|
+
import {
|
|
13
|
+
loadSafetyPolicy,
|
|
14
|
+
evaluateSafety,
|
|
15
|
+
type SafetyContext,
|
|
16
|
+
type SafetyCheckResult,
|
|
17
|
+
} from '../../config/safety-policy';
|
|
18
|
+
import { promptForApproval, displaySafetySummary } from '../../wizard/approval';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Command options
|
|
22
|
+
*/
|
|
23
|
+
export interface ApplyHelmOptions {
|
|
24
|
+
releaseName?: string;
|
|
25
|
+
chart?: string;
|
|
26
|
+
namespace?: string;
|
|
27
|
+
dryRun?: boolean;
|
|
28
|
+
wait?: boolean;
|
|
29
|
+
timeout?: string;
|
|
30
|
+
values?: string;
|
|
31
|
+
valuesFiles?: string[];
|
|
32
|
+
set?: Record<string, string>;
|
|
33
|
+
version?: string;
|
|
34
|
+
createNamespace?: boolean;
|
|
35
|
+
install?: boolean;
|
|
36
|
+
atomic?: boolean;
|
|
37
|
+
force?: boolean;
|
|
38
|
+
/** Skip safety checks */
|
|
39
|
+
skipSafety?: boolean;
|
|
40
|
+
/** Environment name (for safety policy) */
|
|
41
|
+
environment?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Run helm install/upgrade command
|
|
46
|
+
*/
|
|
47
|
+
export async function applyHelmCommand(options: ApplyHelmOptions = {}): Promise<void> {
|
|
48
|
+
logger.info('Running helm apply', { options });
|
|
49
|
+
|
|
50
|
+
// Validate required options
|
|
51
|
+
let releaseName = options.releaseName;
|
|
52
|
+
let chart = options.chart;
|
|
53
|
+
|
|
54
|
+
if (!releaseName) {
|
|
55
|
+
releaseName = await input({
|
|
56
|
+
message: 'Release name:',
|
|
57
|
+
validate: value => {
|
|
58
|
+
if (!value) {
|
|
59
|
+
return 'Release name is required';
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (!releaseName) {
|
|
66
|
+
ui.error('Release name is required');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!chart) {
|
|
72
|
+
chart = await input({
|
|
73
|
+
message: 'Chart (path or name):',
|
|
74
|
+
defaultValue: '.',
|
|
75
|
+
validate: value => {
|
|
76
|
+
if (!value) {
|
|
77
|
+
return 'Chart is required';
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (!chart) {
|
|
84
|
+
ui.error('Chart is required');
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
ui.header('Helm Apply');
|
|
90
|
+
ui.info(`Release: ${releaseName}`);
|
|
91
|
+
ui.info(`Chart: ${chart}`);
|
|
92
|
+
if (options.namespace) {
|
|
93
|
+
ui.info(`Namespace: ${options.namespace}`);
|
|
94
|
+
}
|
|
95
|
+
ui.newLine();
|
|
96
|
+
|
|
97
|
+
// Check if helm client is available
|
|
98
|
+
const clientAvailable = await helmClient.isAvailable();
|
|
99
|
+
|
|
100
|
+
if (clientAvailable) {
|
|
101
|
+
// Use Helm tools service
|
|
102
|
+
await applyWithService(releaseName, chart, options);
|
|
103
|
+
} else {
|
|
104
|
+
// Fall back to local helm CLI
|
|
105
|
+
await applyWithLocalCLI(releaseName, chart, options);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Apply using Helm Tools Service
|
|
111
|
+
*/
|
|
112
|
+
async function applyWithService(
|
|
113
|
+
releaseName: string,
|
|
114
|
+
chart: string,
|
|
115
|
+
options: ApplyHelmOptions
|
|
116
|
+
): Promise<void> {
|
|
117
|
+
// Check if release already exists
|
|
118
|
+
ui.startSpinner({ message: 'Checking existing releases...' });
|
|
119
|
+
|
|
120
|
+
const listResult = await helmClient.list({
|
|
121
|
+
namespace: options.namespace,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const existingRelease = listResult.releases.find(r => r.name === releaseName);
|
|
125
|
+
ui.stopSpinnerSuccess('');
|
|
126
|
+
|
|
127
|
+
const isUpgrade = !!existingRelease;
|
|
128
|
+
|
|
129
|
+
if (isUpgrade) {
|
|
130
|
+
ui.info(`Upgrading existing release (revision ${existingRelease.revision})`);
|
|
131
|
+
} else {
|
|
132
|
+
ui.info('Installing new release');
|
|
133
|
+
}
|
|
134
|
+
ui.newLine();
|
|
135
|
+
|
|
136
|
+
// Dry run mode
|
|
137
|
+
if (options.dryRun) {
|
|
138
|
+
ui.startSpinner({ message: 'Running dry-run...' });
|
|
139
|
+
|
|
140
|
+
if (isUpgrade) {
|
|
141
|
+
const result = await helmClient.upgrade(releaseName, chart, {
|
|
142
|
+
namespace: options.namespace,
|
|
143
|
+
valuesFile: options.values,
|
|
144
|
+
version: options.version,
|
|
145
|
+
dryRun: true,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
ui.stopSpinnerSuccess('Dry-run complete');
|
|
149
|
+
|
|
150
|
+
if (!result.success) {
|
|
151
|
+
ui.error(result.error || 'Unknown error');
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (result.output) {
|
|
156
|
+
ui.newLine();
|
|
157
|
+
ui.print(result.output);
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
const result = await helmClient.install(releaseName, chart, {
|
|
161
|
+
namespace: options.namespace,
|
|
162
|
+
valuesFile: options.values,
|
|
163
|
+
version: options.version,
|
|
164
|
+
createNamespace: options.createNamespace,
|
|
165
|
+
dryRun: true,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
ui.stopSpinnerSuccess('Dry-run complete');
|
|
169
|
+
|
|
170
|
+
if (!result.success) {
|
|
171
|
+
ui.error(result.error || 'Unknown error');
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (result.output) {
|
|
176
|
+
ui.newLine();
|
|
177
|
+
ui.print(result.output);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
ui.newLine();
|
|
182
|
+
ui.info('No changes applied (dry-run mode)');
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Run safety checks if not skipped
|
|
187
|
+
if (!options.skipSafety) {
|
|
188
|
+
const operation = isUpgrade ? 'upgrade' : 'install';
|
|
189
|
+
const safetyResult = await runHelmSafetyChecks(operation, releaseName, chart, options);
|
|
190
|
+
|
|
191
|
+
if (!safetyResult.passed) {
|
|
192
|
+
ui.newLine();
|
|
193
|
+
ui.error('Safety checks failed - operation blocked');
|
|
194
|
+
for (const blocker of safetyResult.blockers) {
|
|
195
|
+
ui.print(` ${ui.color('✗', 'red')} ${blocker.message}`);
|
|
196
|
+
}
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// If safety requires approval, prompt for it
|
|
201
|
+
if (safetyResult.requiresApproval) {
|
|
202
|
+
const approvalResult = await promptForApproval({
|
|
203
|
+
title: `Helm ${isUpgrade ? 'Upgrade' : 'Install'}`,
|
|
204
|
+
operation: `helm ${operation}`,
|
|
205
|
+
risks: safetyResult.risks,
|
|
206
|
+
environment: options.environment,
|
|
207
|
+
affectedResources: [`${releaseName} (${chart})`],
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
if (!approvalResult.approved) {
|
|
211
|
+
ui.newLine();
|
|
212
|
+
ui.info(`Apply cancelled: ${approvalResult.reason || 'User declined'}`);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
// Show safety summary and simple confirm
|
|
217
|
+
displaySafetySummary({
|
|
218
|
+
operation: `helm ${operation}`,
|
|
219
|
+
risks: safetyResult.risks,
|
|
220
|
+
passed: safetyResult.passed,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
ui.newLine();
|
|
224
|
+
const proceed = await confirm({
|
|
225
|
+
message: isUpgrade
|
|
226
|
+
? `Upgrade release '${releaseName}'?`
|
|
227
|
+
: `Install release '${releaseName}'?`,
|
|
228
|
+
defaultValue: true,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (!proceed) {
|
|
232
|
+
ui.info('Apply cancelled');
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
// Simple confirmation when safety is skipped
|
|
238
|
+
const proceed = await confirm({
|
|
239
|
+
message: isUpgrade
|
|
240
|
+
? `Upgrade release '${releaseName}'?`
|
|
241
|
+
: `Install release '${releaseName}'?`,
|
|
242
|
+
defaultValue: true,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (!proceed) {
|
|
246
|
+
ui.info('Apply cancelled');
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Install or upgrade
|
|
252
|
+
ui.newLine();
|
|
253
|
+
ui.startSpinner({
|
|
254
|
+
message: isUpgrade ? 'Upgrading release...' : 'Installing release...',
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
let result;
|
|
258
|
+
|
|
259
|
+
if (isUpgrade) {
|
|
260
|
+
result = await helmClient.upgrade(releaseName, chart, {
|
|
261
|
+
namespace: options.namespace,
|
|
262
|
+
valuesFile: options.values,
|
|
263
|
+
version: options.version,
|
|
264
|
+
wait: options.wait,
|
|
265
|
+
timeout: options.timeout,
|
|
266
|
+
});
|
|
267
|
+
} else {
|
|
268
|
+
result = await helmClient.install(releaseName, chart, {
|
|
269
|
+
namespace: options.namespace,
|
|
270
|
+
valuesFile: options.values,
|
|
271
|
+
version: options.version,
|
|
272
|
+
createNamespace: options.createNamespace ?? true,
|
|
273
|
+
wait: options.wait,
|
|
274
|
+
timeout: options.timeout,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!result.success) {
|
|
279
|
+
ui.stopSpinnerFail(isUpgrade ? 'Upgrade failed' : 'Installation failed');
|
|
280
|
+
ui.error(result.error || 'Unknown error');
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
ui.stopSpinnerSuccess(isUpgrade ? 'Upgrade complete!' : 'Installation complete!');
|
|
285
|
+
|
|
286
|
+
// Track successful helm apply
|
|
287
|
+
try {
|
|
288
|
+
const { trackGeneration } = await import('../../telemetry');
|
|
289
|
+
trackGeneration('helm-apply', ['helm']);
|
|
290
|
+
} catch {
|
|
291
|
+
/* telemetry failure is non-critical */
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Display release info
|
|
295
|
+
ui.newLine();
|
|
296
|
+
ui.print('Release Info:');
|
|
297
|
+
ui.print(` Name: ${result.release.name}`);
|
|
298
|
+
ui.print(` Namespace: ${result.release.namespace}`);
|
|
299
|
+
ui.print(` Revision: ${result.release.revision}`);
|
|
300
|
+
ui.print(` Status: ${result.release.status}`);
|
|
301
|
+
ui.print(` Chart: ${result.release.chart}`);
|
|
302
|
+
|
|
303
|
+
if (result.output) {
|
|
304
|
+
ui.newLine();
|
|
305
|
+
ui.print('Notes:');
|
|
306
|
+
ui.print(result.output);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Apply using local helm CLI
|
|
312
|
+
*/
|
|
313
|
+
async function applyWithLocalCLI(
|
|
314
|
+
releaseName: string,
|
|
315
|
+
chart: string,
|
|
316
|
+
options: ApplyHelmOptions
|
|
317
|
+
): Promise<void> {
|
|
318
|
+
const { spawn, execFileSync } = await import('child_process');
|
|
319
|
+
|
|
320
|
+
// Check if release exists (use execFileSync with args array to prevent shell injection)
|
|
321
|
+
let isUpgrade = false;
|
|
322
|
+
try {
|
|
323
|
+
const statusArgs = ['status', releaseName];
|
|
324
|
+
if (options.namespace) {
|
|
325
|
+
statusArgs.push('-n', options.namespace);
|
|
326
|
+
}
|
|
327
|
+
execFileSync('helm', statusArgs, { stdio: 'pipe' });
|
|
328
|
+
isUpgrade = true;
|
|
329
|
+
ui.info('Upgrading existing release');
|
|
330
|
+
} catch {
|
|
331
|
+
ui.info('Installing new release');
|
|
332
|
+
}
|
|
333
|
+
ui.newLine();
|
|
334
|
+
|
|
335
|
+
// Build helm command
|
|
336
|
+
const command = isUpgrade ? 'upgrade' : 'install';
|
|
337
|
+
const args = [command, releaseName, chart];
|
|
338
|
+
|
|
339
|
+
if (options.namespace) {
|
|
340
|
+
args.push('-n', options.namespace);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (options.values) {
|
|
344
|
+
args.push('-f', options.values);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (options.valuesFiles) {
|
|
348
|
+
for (const vf of options.valuesFiles) {
|
|
349
|
+
args.push('-f', vf);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (options.set) {
|
|
354
|
+
for (const [key, value] of Object.entries(options.set)) {
|
|
355
|
+
args.push('--set', `${key}=${value}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (options.version) {
|
|
360
|
+
args.push('--version', options.version);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (!isUpgrade && (options.createNamespace ?? true)) {
|
|
364
|
+
args.push('--create-namespace');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (options.wait) {
|
|
368
|
+
args.push('--wait');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (options.timeout) {
|
|
372
|
+
args.push('--timeout', options.timeout);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (options.dryRun) {
|
|
376
|
+
args.push('--dry-run');
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (options.atomic) {
|
|
380
|
+
args.push('--atomic');
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (options.force) {
|
|
384
|
+
args.push('--force');
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// For upgrade, always use --install flag
|
|
388
|
+
if (isUpgrade) {
|
|
389
|
+
args.push('--install');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Redact sensitive values from --set flags before logging
|
|
393
|
+
const redactedArgs = [...args];
|
|
394
|
+
for (let i = 0; i < redactedArgs.length; i++) {
|
|
395
|
+
if (
|
|
396
|
+
(redactedArgs[i] === '--set' ||
|
|
397
|
+
redactedArgs[i] === '--set-string' ||
|
|
398
|
+
redactedArgs[i] === '--set-file') &&
|
|
399
|
+
redactedArgs[i + 1]
|
|
400
|
+
) {
|
|
401
|
+
const raw = redactedArgs[i + 1];
|
|
402
|
+
const eq = raw.indexOf('=');
|
|
403
|
+
redactedArgs[i + 1] = eq >= 0 ? `${raw.slice(0, eq + 1)}<REDACTED>` : '<REDACTED>';
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
ui.info(`Running: helm ${redactedArgs.join(' ')}`);
|
|
407
|
+
ui.newLine();
|
|
408
|
+
|
|
409
|
+
// Run helm
|
|
410
|
+
return new Promise((resolve, _reject) => {
|
|
411
|
+
const proc = spawn('helm', args, {
|
|
412
|
+
stdio: 'inherit',
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
proc.on('error', error => {
|
|
416
|
+
ui.error(`Failed to run helm: ${error.message}`);
|
|
417
|
+
ui.info('Make sure helm is installed and in your PATH');
|
|
418
|
+
process.exit(1);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
proc.on('close', code => {
|
|
422
|
+
if (code === 0) {
|
|
423
|
+
ui.newLine();
|
|
424
|
+
ui.success(`Helm ${isUpgrade ? 'upgrade' : 'install'} completed successfully`);
|
|
425
|
+
|
|
426
|
+
// Track successful helm apply
|
|
427
|
+
try {
|
|
428
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
429
|
+
const { trackGeneration } = require('../../telemetry');
|
|
430
|
+
trackGeneration('helm-apply', ['helm']);
|
|
431
|
+
} catch {
|
|
432
|
+
/* telemetry failure is non-critical */
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
resolve();
|
|
436
|
+
} else {
|
|
437
|
+
ui.newLine();
|
|
438
|
+
ui.error(`Helm ${isUpgrade ? 'upgrade' : 'install'} failed with exit code ${code}`);
|
|
439
|
+
process.exit(code || 1);
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Run safety checks for the operation
|
|
447
|
+
*/
|
|
448
|
+
async function runHelmSafetyChecks(
|
|
449
|
+
operation: string,
|
|
450
|
+
releaseName: string,
|
|
451
|
+
chart: string,
|
|
452
|
+
options: ApplyHelmOptions
|
|
453
|
+
): Promise<SafetyCheckResult> {
|
|
454
|
+
const policy = loadSafetyPolicy();
|
|
455
|
+
|
|
456
|
+
const context: SafetyContext = {
|
|
457
|
+
operation,
|
|
458
|
+
type: 'helm',
|
|
459
|
+
environment: options.environment,
|
|
460
|
+
resources: [`${releaseName}:${chart}`],
|
|
461
|
+
metadata: {
|
|
462
|
+
releaseName,
|
|
463
|
+
chart,
|
|
464
|
+
namespace: options.namespace,
|
|
465
|
+
version: options.version,
|
|
466
|
+
},
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
return evaluateSafety(context, policy);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Export as default
|
|
473
|
+
export default applyHelmCommand;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apply Command
|
|
3
|
+
*
|
|
4
|
+
* Apply infrastructure changes for Terraform, Kubernetes, and Helm
|
|
5
|
+
*
|
|
6
|
+
* Usage: nimbus apply <type> [target] [options]
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { logger } from '../../utils';
|
|
10
|
+
import { ui } from '../../wizard';
|
|
11
|
+
import { applyTerraformCommand } from './terraform';
|
|
12
|
+
import { applyK8sCommand } from './k8s';
|
|
13
|
+
import { applyHelmCommand } from './helm';
|
|
14
|
+
|
|
15
|
+
// Re-export subcommand types
|
|
16
|
+
export { type ApplyTerraformOptions } from './terraform';
|
|
17
|
+
export { type ApplyK8sOptions } from './k8s';
|
|
18
|
+
export { type ApplyHelmOptions } from './helm';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Common apply options
|
|
22
|
+
*/
|
|
23
|
+
export interface ApplyOptions {
|
|
24
|
+
dryRun?: boolean;
|
|
25
|
+
autoApprove?: boolean;
|
|
26
|
+
target?: string;
|
|
27
|
+
var?: Record<string, string>;
|
|
28
|
+
varFile?: string;
|
|
29
|
+
namespace?: string;
|
|
30
|
+
wait?: boolean;
|
|
31
|
+
timeout?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Apply type
|
|
36
|
+
*/
|
|
37
|
+
export type ApplyType = 'terraform' | 'k8s' | 'helm';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Parse common apply options from args
|
|
41
|
+
*/
|
|
42
|
+
export function parseApplyOptions(args: string[]): ApplyOptions {
|
|
43
|
+
const options: ApplyOptions = {};
|
|
44
|
+
|
|
45
|
+
for (let i = 0; i < args.length; i++) {
|
|
46
|
+
const arg = args[i];
|
|
47
|
+
|
|
48
|
+
if (arg === '--dry-run') {
|
|
49
|
+
options.dryRun = true;
|
|
50
|
+
} else if (arg === '--auto-approve' || arg === '-y' || arg === '--yes') {
|
|
51
|
+
options.autoApprove = true;
|
|
52
|
+
} else if ((arg === '--target' || arg === '-t') && args[i + 1]) {
|
|
53
|
+
options.target = args[++i];
|
|
54
|
+
} else if (arg === '--var' && args[i + 1]) {
|
|
55
|
+
const varArg = args[++i];
|
|
56
|
+
const [key, ...valueParts] = varArg.split('=');
|
|
57
|
+
options.var = options.var || {};
|
|
58
|
+
options.var[key] = valueParts.join('=');
|
|
59
|
+
} else if (arg === '--var-file' && args[i + 1]) {
|
|
60
|
+
options.varFile = args[++i];
|
|
61
|
+
} else if ((arg === '--namespace' || arg === '-n') && args[i + 1]) {
|
|
62
|
+
options.namespace = args[++i];
|
|
63
|
+
} else if (arg === '--wait') {
|
|
64
|
+
options.wait = true;
|
|
65
|
+
} else if (arg === '--timeout' && args[i + 1]) {
|
|
66
|
+
options.timeout = args[++i];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return options;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Detect infrastructure type from current directory
|
|
75
|
+
*/
|
|
76
|
+
async function detectInfraType(): Promise<ApplyType | null> {
|
|
77
|
+
const fs = await import('fs/promises');
|
|
78
|
+
|
|
79
|
+
// Check for Terraform files
|
|
80
|
+
try {
|
|
81
|
+
const files = await fs.readdir('.');
|
|
82
|
+
if (files.some(f => f.endsWith('.tf'))) {
|
|
83
|
+
return 'terraform';
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
// Ignore
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check for Kubernetes manifests
|
|
90
|
+
try {
|
|
91
|
+
const files = await fs.readdir('.');
|
|
92
|
+
const yamlFiles = files.filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
|
|
93
|
+
for (const file of yamlFiles) {
|
|
94
|
+
const content = await fs.readFile(file, 'utf-8');
|
|
95
|
+
if (content.includes('apiVersion:') && content.includes('kind:')) {
|
|
96
|
+
return 'k8s';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
// Ignore
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Check for Helm chart
|
|
104
|
+
try {
|
|
105
|
+
await fs.access('./Chart.yaml');
|
|
106
|
+
return 'helm';
|
|
107
|
+
} catch {
|
|
108
|
+
// Ignore
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check for values files (Helm)
|
|
112
|
+
try {
|
|
113
|
+
const files = await fs.readdir('.');
|
|
114
|
+
if (files.some(f => f.startsWith('values') && (f.endsWith('.yaml') || f.endsWith('.yml')))) {
|
|
115
|
+
return 'helm';
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
// Ignore
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Run the apply command
|
|
126
|
+
*/
|
|
127
|
+
export async function applyCommand(type: string | undefined, args: string[]): Promise<void> {
|
|
128
|
+
logger.info('Running apply command', { type, args });
|
|
129
|
+
|
|
130
|
+
// Parse common options
|
|
131
|
+
const options = parseApplyOptions(args);
|
|
132
|
+
|
|
133
|
+
// Get positional arguments (after type)
|
|
134
|
+
const positionalArgs = args.filter(arg => !arg.startsWith('-') && !arg.includes('='));
|
|
135
|
+
|
|
136
|
+
// Auto-detect type if not provided
|
|
137
|
+
if (!type || type.startsWith('-')) {
|
|
138
|
+
ui.startSpinner({ message: 'Detecting infrastructure type...' });
|
|
139
|
+
const detectedType = await detectInfraType();
|
|
140
|
+
ui.stopSpinnerSuccess('');
|
|
141
|
+
|
|
142
|
+
if (!detectedType) {
|
|
143
|
+
ui.error('Could not detect infrastructure type');
|
|
144
|
+
ui.newLine();
|
|
145
|
+
ui.info('Usage: nimbus apply <type> [target] [options]');
|
|
146
|
+
ui.info('');
|
|
147
|
+
ui.info('Types:');
|
|
148
|
+
ui.info(' terraform Apply Terraform configuration');
|
|
149
|
+
ui.info(' k8s Apply Kubernetes manifests');
|
|
150
|
+
ui.info(' helm Install/upgrade Helm release');
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
type = detectedType;
|
|
155
|
+
ui.info(`Detected infrastructure type: ${type}`);
|
|
156
|
+
ui.newLine();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
switch (type) {
|
|
160
|
+
case 'terraform':
|
|
161
|
+
case 'tf':
|
|
162
|
+
await applyTerraformCommand({
|
|
163
|
+
directory: positionalArgs[0] || options.target || '.',
|
|
164
|
+
dryRun: options.dryRun,
|
|
165
|
+
autoApprove: options.autoApprove,
|
|
166
|
+
var: options.var,
|
|
167
|
+
varFile: options.varFile,
|
|
168
|
+
target: options.target,
|
|
169
|
+
});
|
|
170
|
+
break;
|
|
171
|
+
|
|
172
|
+
case 'k8s':
|
|
173
|
+
case 'kubernetes':
|
|
174
|
+
await applyK8sCommand({
|
|
175
|
+
manifests: positionalArgs[0] || options.target || '.',
|
|
176
|
+
namespace: options.namespace,
|
|
177
|
+
dryRun: options.dryRun,
|
|
178
|
+
wait: options.wait,
|
|
179
|
+
});
|
|
180
|
+
break;
|
|
181
|
+
|
|
182
|
+
case 'helm': {
|
|
183
|
+
// For Helm, we need release name and chart
|
|
184
|
+
const releaseName = positionalArgs[0];
|
|
185
|
+
const chartPath = positionalArgs[1] || '.';
|
|
186
|
+
|
|
187
|
+
await applyHelmCommand({
|
|
188
|
+
releaseName,
|
|
189
|
+
chart: chartPath,
|
|
190
|
+
namespace: options.namespace,
|
|
191
|
+
dryRun: options.dryRun,
|
|
192
|
+
wait: options.wait,
|
|
193
|
+
timeout: options.timeout,
|
|
194
|
+
values: options.varFile,
|
|
195
|
+
});
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
default:
|
|
200
|
+
ui.error(`Unknown apply type: ${type}`);
|
|
201
|
+
ui.newLine();
|
|
202
|
+
ui.info('Supported types: terraform, k8s, helm');
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Export subcommands
|
|
208
|
+
export { applyTerraformCommand } from './terraform';
|
|
209
|
+
export { applyK8sCommand } from './k8s';
|
|
210
|
+
export { applyHelmCommand } from './helm';
|
|
211
|
+
|
|
212
|
+
// Export as default
|
|
213
|
+
export default applyCommand;
|