@joshualiamzn/open-stack 0.0.1 → 0.0.2
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/.kiro/memory-session-id +1 -0
- package/package.json +3 -1
- package/src/aws.mjs +680 -61
- package/src/cli.mjs +57 -7
- package/src/commands/create.mjs +200 -18
- package/src/commands/demo.mjs +193 -0
- package/src/commands/describe.mjs +53 -43
- package/src/commands/help.mjs +5 -6
- package/src/commands/index.mjs +14 -17
- package/src/commands/list.mjs +14 -13
- package/src/config.mjs +11 -1
- package/src/eks.mjs +356 -0
- package/src/interactive.mjs +290 -198
- package/src/main.mjs +111 -16
- package/src/render.mjs +1 -2
- package/src/repl.mjs +19 -43
- package/src/ui.mjs +123 -12
- package/src/commands/update.mjs +0 -309
package/src/commands/help.mjs
CHANGED
|
@@ -4,16 +4,15 @@ export async function runHelp() {
|
|
|
4
4
|
console.error();
|
|
5
5
|
const lines = [
|
|
6
6
|
'',
|
|
7
|
-
`${theme.accentBold('create')} Create a new
|
|
8
|
-
`${theme.accentBold('list')} List existing
|
|
9
|
-
`${theme.accentBold('describe')} Show details of a
|
|
10
|
-
`${theme.accentBold('update')} Update an existing pipeline's settings`,
|
|
7
|
+
`${theme.accentBold('create')} Create a new observability stack`,
|
|
8
|
+
`${theme.accentBold('list')} List existing stacks`,
|
|
9
|
+
`${theme.accentBold('describe')} Show details of a stack`,
|
|
11
10
|
`${theme.accentBold('help')} Show this help message`,
|
|
12
|
-
`${theme.accentBold('quit')} Exit
|
|
11
|
+
`${theme.accentBold('quit')} Exit Open Stack`,
|
|
13
12
|
'',
|
|
14
13
|
];
|
|
15
14
|
printBox(lines, { title: 'Commands', color: 'dim', padding: 2 });
|
|
16
15
|
console.error();
|
|
17
|
-
printKeyHint([['Ctrl+C', '
|
|
16
|
+
printKeyHint([['Ctrl+C', 'exit']]);
|
|
18
17
|
console.error();
|
|
19
18
|
}
|
package/src/commands/index.mjs
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
import { runCreate } from './create.mjs';
|
|
2
2
|
import { runList } from './list.mjs';
|
|
3
3
|
import { runDescribe } from './describe.mjs';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { listPipelines } from '../aws.mjs';
|
|
4
|
+
import { runDemo } from './demo.mjs';
|
|
5
|
+
import { listStacks } from '../aws.mjs';
|
|
7
6
|
import { createSpinner, theme } from '../ui.mjs';
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
|
-
* Load
|
|
9
|
+
* Load stacks with a spinner. Shared by describe and list commands.
|
|
11
10
|
*/
|
|
12
|
-
export async function
|
|
13
|
-
const spinner = createSpinner('Loading
|
|
11
|
+
export async function loadStacks(region) {
|
|
12
|
+
const spinner = createSpinner('Loading stacks...');
|
|
14
13
|
spinner.start();
|
|
15
14
|
try {
|
|
16
|
-
const
|
|
17
|
-
spinner.succeed(`${
|
|
18
|
-
return
|
|
15
|
+
const stacks = await listStacks(region);
|
|
16
|
+
spinner.succeed(`${stacks.length} stack${stacks.length !== 1 ? 's' : ''} found`);
|
|
17
|
+
return stacks;
|
|
19
18
|
} catch (err) {
|
|
20
|
-
spinner.fail('Failed to list
|
|
19
|
+
spinner.fail('Failed to list stacks');
|
|
21
20
|
throw err;
|
|
22
21
|
}
|
|
23
22
|
}
|
|
@@ -26,15 +25,13 @@ export const COMMANDS = {
|
|
|
26
25
|
create: runCreate,
|
|
27
26
|
list: runList,
|
|
28
27
|
describe: runDescribe,
|
|
29
|
-
|
|
30
|
-
help: runHelp,
|
|
28
|
+
demo: runDemo,
|
|
31
29
|
};
|
|
32
30
|
|
|
33
31
|
export const COMMAND_CHOICES = [
|
|
34
|
-
{ name: `\u2728 Create ${theme.muted('Create a new
|
|
35
|
-
{ name: `\u2630
|
|
36
|
-
{ name: `\uD83D\uDD0D Describe ${theme.muted('Show details of a
|
|
37
|
-
{ name: `\
|
|
38
|
-
{ name: `\u2753 Help ${theme.muted('Show available commands')}`, value: 'help' },
|
|
32
|
+
{ name: `\u2728 Create ${theme.muted('Create a new observability stack')}`, value: 'create' },
|
|
33
|
+
{ name: `\u2630 List ${theme.muted('List existing stacks')}`, value: 'list' },
|
|
34
|
+
{ name: `\uD83D\uDD0D Describe ${theme.muted('Show details of a stack')}`, value: 'describe' },
|
|
35
|
+
{ name: `\uD83D\uDE80 Install Demo ${theme.muted('Create demo services on EKS')}`, value: 'demo' },
|
|
39
36
|
{ name: `\uD83D\uDEAA Quit ${theme.muted('Exit')}`, value: 'quit' },
|
|
40
37
|
];
|
package/src/commands/list.mjs
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
|
-
import { printTable, printInfo,
|
|
2
|
-
import {
|
|
1
|
+
import { printTable, printInfo, theme } from '../ui.mjs';
|
|
2
|
+
import { loadStacks } from './index.mjs';
|
|
3
3
|
|
|
4
4
|
export async function runList(session) {
|
|
5
5
|
console.error();
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const stacks = await loadStacks(session.region);
|
|
8
8
|
|
|
9
|
-
if (
|
|
10
|
-
printInfo('No
|
|
9
|
+
if (stacks.length === 0) {
|
|
10
|
+
printInfo('No open-stack stacks found in this region.');
|
|
11
11
|
console.error();
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
console.error();
|
|
16
|
-
const headers = ['
|
|
17
|
-
const rows =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
const headers = ['Stack', 'Resources', 'Types'];
|
|
17
|
+
const rows = stacks.map((s) => {
|
|
18
|
+
const types = [...new Set(s.resources.map((r) => r.type))];
|
|
19
|
+
return [
|
|
20
|
+
s.name,
|
|
21
|
+
String(s.resources.length),
|
|
22
|
+
types.join(theme.muted(', ')),
|
|
23
|
+
];
|
|
24
|
+
});
|
|
24
25
|
|
|
25
26
|
printTable(headers, rows);
|
|
26
27
|
}
|
package/src/config.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Default configuration values.
|
|
3
3
|
*/
|
|
4
4
|
export const DEFAULTS = {
|
|
5
|
-
pipelineName:
|
|
5
|
+
pipelineName: `open-stack-${Math.floor(Date.now() / 1000)}`,
|
|
6
6
|
osInstanceType: 'r6g.large.search',
|
|
7
7
|
osInstanceCount: 1,
|
|
8
8
|
osVolumeSize: 100,
|
|
@@ -38,6 +38,16 @@ export function createDefaultConfig() {
|
|
|
38
38
|
minOcu: DEFAULTS.minOcu,
|
|
39
39
|
maxOcu: DEFAULTS.maxOcu,
|
|
40
40
|
serviceMapWindow: DEFAULTS.serviceMapWindow,
|
|
41
|
+
dashboardsAction: '',
|
|
42
|
+
dashboardsUrl: '',
|
|
43
|
+
dqsRoleName: '',
|
|
44
|
+
dqsRoleArn: '',
|
|
45
|
+
dqsDataSourceName: '',
|
|
46
|
+
dqsDataSourceArn: '',
|
|
47
|
+
appName: '',
|
|
48
|
+
appId: '',
|
|
49
|
+
appEndpoint: '',
|
|
50
|
+
ingestEndpoints: [],
|
|
41
51
|
outputFile: '',
|
|
42
52
|
dryRun: false,
|
|
43
53
|
accountId: '',
|
package/src/eks.mjs
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { execSync, spawn } from 'node:child_process';
|
|
2
|
+
import { mkdtempSync, rmSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import {
|
|
6
|
+
printStep,
|
|
7
|
+
printSuccess,
|
|
8
|
+
printError,
|
|
9
|
+
printWarning,
|
|
10
|
+
printInfo,
|
|
11
|
+
createSpinner,
|
|
12
|
+
} from './ui.mjs';
|
|
13
|
+
import { tagResource } from './aws.mjs';
|
|
14
|
+
import chalk from 'chalk';
|
|
15
|
+
|
|
16
|
+
const HELM_CHART_REPO = 'https://github.com/kylehounslow/observability-stack.git';
|
|
17
|
+
const HELM_CHART_BRANCH = 'feat/helm-charts';
|
|
18
|
+
const HELM_CHART_PATH = 'charts/observability-stack';
|
|
19
|
+
const HELM_RELEASE_NAME = 'obs-stack';
|
|
20
|
+
const HELM_NAMESPACE = 'observability';
|
|
21
|
+
|
|
22
|
+
const OTEL_DEMO_REPO = 'https://open-telemetry.github.io/opentelemetry-helm-charts';
|
|
23
|
+
const OTEL_DEMO_RELEASE_NAME = 'otel-demo';
|
|
24
|
+
const OTEL_DEMO_NAMESPACE = 'otel-demo';
|
|
25
|
+
|
|
26
|
+
// ── Prerequisites ───────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
function commandExists(cmd) {
|
|
29
|
+
try {
|
|
30
|
+
execSync(`command -v ${cmd}`, { stdio: 'ignore' });
|
|
31
|
+
return true;
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function checkDemoPrerequisites() {
|
|
38
|
+
printStep('Checking demo prerequisites...');
|
|
39
|
+
console.error();
|
|
40
|
+
|
|
41
|
+
const required = [
|
|
42
|
+
{ cmd: 'aws', install: 'https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html' },
|
|
43
|
+
{ cmd: 'eksctl', install: 'https://eksctl.io/installation/' },
|
|
44
|
+
{ cmd: 'kubectl', install: 'https://kubernetes.io/docs/tasks/tools/' },
|
|
45
|
+
{ cmd: 'helm', install: 'https://helm.sh/docs/intro/install/' },
|
|
46
|
+
{ cmd: 'git', install: 'https://git-scm.com/downloads' },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const missing = [];
|
|
50
|
+
for (const { cmd, install } of required) {
|
|
51
|
+
if (commandExists(cmd)) {
|
|
52
|
+
printSuccess(`${cmd} found`);
|
|
53
|
+
} else {
|
|
54
|
+
missing.push({ cmd, install });
|
|
55
|
+
printError(`${cmd} not found`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (missing.length > 0) {
|
|
60
|
+
console.error();
|
|
61
|
+
console.error(` ${chalk.bold('Missing required tools:')}`);
|
|
62
|
+
for (const { cmd, install } of missing) {
|
|
63
|
+
console.error(` ${chalk.bold(cmd)}: ${chalk.underline(install)}`);
|
|
64
|
+
}
|
|
65
|
+
console.error();
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.error();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ── EKS Cluster ─────────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
function runCommand(cmd, args, { spinner, prefix = '' } = {}) {
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
const proc = spawn(cmd, args, {
|
|
77
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
78
|
+
env: { ...process.env },
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
let stdout = '';
|
|
82
|
+
let stderr = '';
|
|
83
|
+
|
|
84
|
+
proc.stdout.on('data', (data) => {
|
|
85
|
+
stdout += data.toString();
|
|
86
|
+
const lines = data.toString().trim().split('\n');
|
|
87
|
+
for (const line of lines) {
|
|
88
|
+
if (spinner && line.trim()) {
|
|
89
|
+
spinner.text = `${prefix}${line.trim()}`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
proc.stderr.on('data', (data) => {
|
|
95
|
+
stderr += data.toString();
|
|
96
|
+
const lines = data.toString().trim().split('\n');
|
|
97
|
+
for (const line of lines) {
|
|
98
|
+
if (spinner && line.trim()) {
|
|
99
|
+
spinner.text = `${prefix}${line.trim()}`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
proc.on('close', (code) => {
|
|
105
|
+
if (code === 0) {
|
|
106
|
+
resolve(stdout.trim());
|
|
107
|
+
} else {
|
|
108
|
+
reject(new Error(stderr.trim() || `Command failed with exit code ${code}`));
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
proc.on('error', reject);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function createEksCluster(cfg) {
|
|
117
|
+
const { clusterName, region, nodeCount, instanceType, stackName, accountId } = cfg;
|
|
118
|
+
|
|
119
|
+
printStep(`Creating EKS cluster '${clusterName}'...`);
|
|
120
|
+
console.error();
|
|
121
|
+
|
|
122
|
+
// Check if cluster already exists
|
|
123
|
+
try {
|
|
124
|
+
const existing = execSync(
|
|
125
|
+
`eksctl get cluster --name ${clusterName} --region ${region} 2>/dev/null`,
|
|
126
|
+
{ encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] },
|
|
127
|
+
);
|
|
128
|
+
if (existing.includes(clusterName)) {
|
|
129
|
+
printSuccess(`Cluster '${clusterName}' already exists`);
|
|
130
|
+
printInfo('Updating kubeconfig...');
|
|
131
|
+
execSync(
|
|
132
|
+
`aws eks update-kubeconfig --name ${clusterName} --region ${region}`,
|
|
133
|
+
{ stdio: 'ignore' },
|
|
134
|
+
);
|
|
135
|
+
printSuccess('kubeconfig updated');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
} catch { /* cluster doesn't exist — proceed to create */ }
|
|
139
|
+
|
|
140
|
+
const spinner = createSpinner('Creating EKS cluster (15-25 min)...');
|
|
141
|
+
spinner.start();
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
await runCommand('eksctl', [
|
|
145
|
+
'create', 'cluster',
|
|
146
|
+
'--name', clusterName,
|
|
147
|
+
'--region', region,
|
|
148
|
+
'--nodes', String(nodeCount),
|
|
149
|
+
'--node-type', instanceType,
|
|
150
|
+
'--managed',
|
|
151
|
+
], { spinner, prefix: 'EKS: ' });
|
|
152
|
+
|
|
153
|
+
spinner.succeed(`Cluster '${clusterName}' created`);
|
|
154
|
+
} catch (err) {
|
|
155
|
+
spinner.fail('Failed to create EKS cluster');
|
|
156
|
+
console.error();
|
|
157
|
+
console.error(` ${chalk.dim(err.message)}`);
|
|
158
|
+
console.error();
|
|
159
|
+
throw new Error('Failed to create EKS cluster');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Tag the EKS cluster for stack discovery
|
|
163
|
+
if (stackName && accountId) {
|
|
164
|
+
const clusterArn = `arn:aws:eks:${region}:${accountId}:cluster/${clusterName}`;
|
|
165
|
+
await tagResource(region, clusterArn, stackName);
|
|
166
|
+
printSuccess('Cluster tagged');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Update kubeconfig
|
|
170
|
+
try {
|
|
171
|
+
execSync(
|
|
172
|
+
`aws eks update-kubeconfig --name ${clusterName} --region ${region}`,
|
|
173
|
+
{ stdio: 'ignore' },
|
|
174
|
+
);
|
|
175
|
+
printSuccess('kubeconfig updated');
|
|
176
|
+
} catch (err) {
|
|
177
|
+
printWarning(`Could not update kubeconfig: ${err.message}`);
|
|
178
|
+
printInfo(`Run: aws eks update-kubeconfig --name ${clusterName} --region ${region}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── Helm Chart Installation ─────────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
export async function installHelmChart(cfg) {
|
|
185
|
+
printStep('Installing observability stack Helm chart...');
|
|
186
|
+
console.error();
|
|
187
|
+
|
|
188
|
+
const tmpDir = mkdtempSync(join(tmpdir(), 'obs-stack-'));
|
|
189
|
+
const chartDir = join(tmpDir, 'observability-stack', HELM_CHART_PATH);
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
// Clone the chart repo
|
|
193
|
+
const cloneSpinner = createSpinner('Cloning Helm chart repository...');
|
|
194
|
+
cloneSpinner.start();
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
await runCommand('git', [
|
|
198
|
+
'clone',
|
|
199
|
+
'--branch', HELM_CHART_BRANCH,
|
|
200
|
+
'--depth', '1',
|
|
201
|
+
HELM_CHART_REPO,
|
|
202
|
+
join(tmpDir, 'observability-stack'),
|
|
203
|
+
], { spinner: cloneSpinner });
|
|
204
|
+
cloneSpinner.succeed('Chart repository cloned');
|
|
205
|
+
} catch (err) {
|
|
206
|
+
cloneSpinner.fail('Failed to clone chart repository');
|
|
207
|
+
console.error(` ${chalk.dim(err.message)}`);
|
|
208
|
+
throw new Error('Failed to clone Helm chart repository');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Patch: remove duplicate data-prepper metrics port (4900).
|
|
212
|
+
// The subchart template hardcodes a "server" port on 4900, so having
|
|
213
|
+
// "metrics: 4900" in values.yaml causes a duplicate port warning.
|
|
214
|
+
try {
|
|
215
|
+
const valuesPath = join(chartDir, 'values.yaml');
|
|
216
|
+
const values = readFileSync(valuesPath, 'utf-8');
|
|
217
|
+
writeFileSync(valuesPath, values.replace(/^\s*- name: metrics\n\s*port: 4900\n/m, ''));
|
|
218
|
+
} catch { /* best effort — chart may have been fixed upstream */ }
|
|
219
|
+
|
|
220
|
+
// Build dependencies
|
|
221
|
+
const depSpinner = createSpinner('Building Helm dependencies...');
|
|
222
|
+
depSpinner.start();
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
await runCommand('helm', ['dependency', 'build', chartDir], { spinner: depSpinner });
|
|
226
|
+
depSpinner.succeed('Helm dependencies built');
|
|
227
|
+
} catch (err) {
|
|
228
|
+
depSpinner.fail('Failed to build Helm dependencies');
|
|
229
|
+
console.error(` ${chalk.dim(err.message)}`);
|
|
230
|
+
throw new Error('Failed to build Helm dependencies');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Install chart
|
|
234
|
+
const installSpinner = createSpinner('Installing Helm chart (this may take a few minutes)...');
|
|
235
|
+
installSpinner.start();
|
|
236
|
+
|
|
237
|
+
const helmArgs = [
|
|
238
|
+
'install', HELM_RELEASE_NAME, chartDir,
|
|
239
|
+
'--namespace', HELM_NAMESPACE,
|
|
240
|
+
'--create-namespace',
|
|
241
|
+
'--wait',
|
|
242
|
+
'--timeout', '20m',
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
// If the stack config has an OTLP endpoint, pass it as a value override
|
|
246
|
+
if (cfg.otlpEndpoint) {
|
|
247
|
+
helmArgs.push('--set', `opentelemetry-collector.config.exporters.otlp/osi.endpoint=${cfg.otlpEndpoint}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
await runCommand('helm', helmArgs, { spinner: installSpinner });
|
|
252
|
+
installSpinner.succeed('Helm chart installed');
|
|
253
|
+
} catch (err) {
|
|
254
|
+
// Check if release already exists
|
|
255
|
+
if (/already exists/i.test(err.message) || /cannot re-use/i.test(err.message)) {
|
|
256
|
+
installSpinner.succeed(`Release '${HELM_RELEASE_NAME}' already installed`);
|
|
257
|
+
printInfo(`To upgrade: helm upgrade ${HELM_RELEASE_NAME} ${chartDir} -n ${HELM_NAMESPACE}`);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
installSpinner.fail('Failed to install Helm chart');
|
|
261
|
+
console.error(` ${chalk.dim(err.message)}`);
|
|
262
|
+
throw new Error('Failed to install Helm chart');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Show deployment status
|
|
266
|
+
console.error();
|
|
267
|
+
printSuccess('Demo services deployed to EKS');
|
|
268
|
+
printInfo(`Namespace: ${HELM_NAMESPACE}`);
|
|
269
|
+
printInfo(`Release: ${HELM_RELEASE_NAME}`);
|
|
270
|
+
printInfo(`Check status: kubectl get pods -n ${HELM_NAMESPACE}`);
|
|
271
|
+
} finally {
|
|
272
|
+
// Clean up temp directory
|
|
273
|
+
try {
|
|
274
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
275
|
+
} catch { /* best effort cleanup */ }
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ── OpenTelemetry Demo ────────────────────────────────────────────────────
|
|
280
|
+
|
|
281
|
+
export async function installOtelDemo(cfg) {
|
|
282
|
+
printStep('Installing OpenTelemetry Demo...');
|
|
283
|
+
console.error();
|
|
284
|
+
|
|
285
|
+
// Add the Helm repo
|
|
286
|
+
const repoSpinner = createSpinner('Adding OpenTelemetry Helm repo...');
|
|
287
|
+
repoSpinner.start();
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
await runCommand('helm', [
|
|
291
|
+
'repo', 'add', 'open-telemetry', OTEL_DEMO_REPO,
|
|
292
|
+
], { spinner: repoSpinner });
|
|
293
|
+
await runCommand('helm', ['repo', 'update'], { spinner: repoSpinner });
|
|
294
|
+
repoSpinner.succeed('OpenTelemetry Helm repo added');
|
|
295
|
+
} catch (err) {
|
|
296
|
+
// Repo may already exist — try update anyway
|
|
297
|
+
try {
|
|
298
|
+
await runCommand('helm', ['repo', 'update'], { spinner: repoSpinner });
|
|
299
|
+
repoSpinner.succeed('OpenTelemetry Helm repo updated');
|
|
300
|
+
} catch (updateErr) {
|
|
301
|
+
repoSpinner.fail('Failed to add Helm repo');
|
|
302
|
+
console.error(` ${chalk.dim(updateErr.message)}`);
|
|
303
|
+
throw new Error('Failed to add OpenTelemetry Helm repo');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Install the OpenTelemetry Demo chart
|
|
308
|
+
const installSpinner = createSpinner('Installing OpenTelemetry Demo (this may take a few minutes)...');
|
|
309
|
+
installSpinner.start();
|
|
310
|
+
|
|
311
|
+
const helmArgs = [
|
|
312
|
+
'install', OTEL_DEMO_RELEASE_NAME, 'open-telemetry/opentelemetry-demo',
|
|
313
|
+
'--namespace', OTEL_DEMO_NAMESPACE,
|
|
314
|
+
'--create-namespace',
|
|
315
|
+
'--wait',
|
|
316
|
+
'--timeout', '10m',
|
|
317
|
+
// Disable the demo's built-in observability backends — we use our own stack
|
|
318
|
+
'--set', 'opensearch.enabled=false',
|
|
319
|
+
'--set', 'grafana.enabled=false',
|
|
320
|
+
'--set', 'prometheus.enabled=false',
|
|
321
|
+
'--set', 'jaeger.enabled=false',
|
|
322
|
+
];
|
|
323
|
+
|
|
324
|
+
// Point the demo's collector at the observability stack's collector
|
|
325
|
+
if (cfg.otlpEndpoint) {
|
|
326
|
+
helmArgs.push(
|
|
327
|
+
'--set', `opentelemetry-collector.config.exporters.otlp/osi.endpoint=${cfg.otlpEndpoint}`,
|
|
328
|
+
);
|
|
329
|
+
} else {
|
|
330
|
+
// Default: send to the obs-stack collector in the observability namespace
|
|
331
|
+
helmArgs.push(
|
|
332
|
+
'--set', `default.env[0].name=OTEL_EXPORTER_OTLP_ENDPOINT`,
|
|
333
|
+
'--set', `default.env[0].value=http://${HELM_RELEASE_NAME}-opentelemetry-collector.${HELM_NAMESPACE}:4317`,
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
await runCommand('helm', helmArgs, { spinner: installSpinner });
|
|
339
|
+
installSpinner.succeed('OpenTelemetry Demo installed');
|
|
340
|
+
} catch (err) {
|
|
341
|
+
if (/already exists/i.test(err.message) || /cannot re-use/i.test(err.message)) {
|
|
342
|
+
installSpinner.succeed(`Release '${OTEL_DEMO_RELEASE_NAME}' already installed`);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
installSpinner.fail('Failed to install OpenTelemetry Demo');
|
|
346
|
+
console.error(` ${chalk.dim(err.message)}`);
|
|
347
|
+
throw new Error('Failed to install OpenTelemetry Demo');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
console.error();
|
|
351
|
+
printSuccess('OpenTelemetry Demo deployed');
|
|
352
|
+
printInfo(`Namespace: ${OTEL_DEMO_NAMESPACE}`);
|
|
353
|
+
printInfo(`Release: ${OTEL_DEMO_RELEASE_NAME}`);
|
|
354
|
+
printInfo(`Check status: kubectl get pods -n ${OTEL_DEMO_NAMESPACE}`);
|
|
355
|
+
printInfo(`Frontend: kubectl port-forward svc/${OTEL_DEMO_RELEASE_NAME}-frontend-proxy 8080:8080 -n ${OTEL_DEMO_NAMESPACE}`);
|
|
356
|
+
}
|