@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.
@@ -1,309 +0,0 @@
1
- import { select, input, confirm } from '@inquirer/prompts';
2
- import { getPipeline, updatePipeline } from '../aws.mjs';
3
- import { printInfo, printPanel, createSpinner, theme, ARROW } from '../ui.mjs';
4
- import { loadPipelines } from './index.mjs';
5
-
6
- /**
7
- * Extract known configurable values from pipeline YAML by regex.
8
- */
9
- function parseConfigValues(yaml) {
10
- if (!yaml) return {};
11
-
12
- const vals = {};
13
-
14
- // OpenSearch hosts (first occurrence)
15
- const hostsMatch = yaml.match(/hosts:\s*\n\s*-\s*'([^']+)'/);
16
- if (hostsMatch) vals.opensearchEndpoint = hostsMatch[1];
17
-
18
- // IAM role ARN (sts_role_arn)
19
- const roleMatch = yaml.match(/sts_role_arn:\s*"([^"]+)"/);
20
- if (roleMatch) vals.iamRoleArn = roleMatch[1];
21
-
22
- // Prometheus URL
23
- const promMatch = yaml.match(/url:\s*'([^']*prometheus[^']*)'/i)
24
- || yaml.match(/url:\s*'([^']*aps-workspaces[^']*)'/i);
25
- if (promMatch) vals.prometheusUrl = promMatch[1];
26
-
27
- // Service map window duration
28
- const windowMatch = yaml.match(/window_duration:\s*(\S+)/);
29
- if (windowMatch) vals.serviceMapWindow = windowMatch[1];
30
-
31
- // Serverless flag
32
- vals.serverless = /serverless:\s*true/i.test(yaml);
33
-
34
- return vals;
35
- }
36
-
37
- /**
38
- * Replace known config values in pipeline YAML.
39
- */
40
- function patchConfigYaml(yaml, updates) {
41
- let patched = yaml;
42
-
43
- if (updates.opensearchEndpoint) {
44
- patched = patched.replace(
45
- /(hosts:\s*\n\s*-\s*')([^']+)(')/g,
46
- `$1${updates.opensearchEndpoint}$3`,
47
- );
48
- }
49
-
50
- if (updates.iamRoleArn) {
51
- patched = patched.replace(
52
- /(sts_role_arn:\s*")([^"]+)(")/g,
53
- `$1${updates.iamRoleArn}$3`,
54
- );
55
- }
56
-
57
- if (updates.prometheusUrl) {
58
- patched = patched.replace(
59
- /(url:\s*')([^']*(?:prometheus|aps-workspaces)[^']*)(')/gi,
60
- `$1${updates.prometheusUrl}$3`,
61
- );
62
- }
63
-
64
- if (updates.serviceMapWindow) {
65
- patched = patched.replace(
66
- /(window_duration:\s*)\S+/,
67
- `$1${updates.serviceMapWindow}`,
68
- );
69
- }
70
-
71
- return patched;
72
- }
73
-
74
- export async function runUpdate(session) {
75
- console.error();
76
-
77
- const pipelines = await loadPipelines(session.region);
78
-
79
- // Filter to updatable pipelines
80
- const updatable = pipelines.filter((p) => p.status === 'ACTIVE');
81
- if (updatable.length === 0) {
82
- printInfo('No active pipelines available for update.');
83
- console.error();
84
- return;
85
- }
86
-
87
- // Select pipeline
88
- const choices = updatable.map((p) => ({
89
- name: `${p.name} ${theme.muted(`(OCUs: ${p.minUnits}\u2013${p.maxUnits})`)}`,
90
- value: p.name,
91
- }));
92
-
93
- const pipelineName = await select({
94
- message: 'Select pipeline to update',
95
- choices,
96
- });
97
-
98
- // Get current details
99
- const detailSpinner = createSpinner(`Loading ${pipelineName}...`);
100
- detailSpinner.start();
101
-
102
- let pipeline;
103
- try {
104
- pipeline = await getPipeline(session.region, pipelineName);
105
- detailSpinner.succeed(`Loaded ${pipelineName}`);
106
- } catch (err) {
107
- detailSpinner.fail('Failed to get pipeline details');
108
- throw err;
109
- }
110
-
111
- // Parse current config values from YAML
112
- const current = parseConfigValues(pipeline.pipelineConfigurationBody);
113
-
114
- // Show current config summary in a panel
115
- console.error();
116
- const configEntries = [
117
- ['Min OCUs', String(pipeline.minUnits)],
118
- ['Max OCUs', String(pipeline.maxUnits)],
119
- ];
120
- if (current.opensearchEndpoint) {
121
- configEntries.push(['OpenSearch endpoint', current.opensearchEndpoint]);
122
- }
123
- if (current.iamRoleArn) {
124
- configEntries.push(['IAM role ARN', current.iamRoleArn]);
125
- }
126
- if (current.prometheusUrl) {
127
- configEntries.push(['Prometheus URL', current.prometheusUrl]);
128
- }
129
- if (current.serviceMapWindow) {
130
- configEntries.push(['Service map window', current.serviceMapWindow]);
131
- }
132
-
133
- const cwLogGroup = pipeline.logPublishingOptions?.CloudWatchLogDestination?.LogGroup;
134
- if (cwLogGroup) {
135
- configEntries.push(['CloudWatch log group', cwLogGroup]);
136
- }
137
-
138
- const persistentBuffering = pipeline.bufferOptions?.PersistentBufferEnabled;
139
- configEntries.push(['Persistent buffering', persistentBuffering ? 'enabled' : 'disabled']);
140
-
141
- printPanel('Current Configuration', configEntries);
142
- console.error();
143
-
144
- // ── Prompt for new values ──────────────────────────────────────────────
145
-
146
- const newMinOcu = Number(await input({
147
- message: 'Minimum OCUs',
148
- default: String(pipeline.minUnits),
149
- validate: (v) => /^\d+$/.test(v.trim()) && Number(v) >= 1 || 'Must be a positive integer',
150
- }));
151
-
152
- const newMaxOcu = Number(await input({
153
- message: 'Maximum OCUs',
154
- default: String(pipeline.maxUnits),
155
- validate: (v) => /^\d+$/.test(v.trim()) && Number(v) >= newMinOcu || `Must be >= min OCUs (${newMinOcu})`,
156
- }));
157
-
158
- // Pipeline config values
159
- const yamlUpdates = {};
160
- let configChanged = false;
161
-
162
- if (current.opensearchEndpoint) {
163
- const newEndpoint = await input({
164
- message: 'OpenSearch endpoint',
165
- default: current.opensearchEndpoint,
166
- validate: (v) => /^https?:\/\//.test(v.trim()) || 'Must start with http:// or https://',
167
- });
168
- if (newEndpoint !== current.opensearchEndpoint) {
169
- yamlUpdates.opensearchEndpoint = newEndpoint;
170
- configChanged = true;
171
- }
172
- }
173
-
174
- if (current.iamRoleArn) {
175
- const newRole = await input({
176
- message: 'IAM role ARN (sts_role_arn)',
177
- default: current.iamRoleArn,
178
- validate: (v) => v.trim().startsWith('arn:aws:iam:') || 'Must start with arn:aws:iam:',
179
- });
180
- if (newRole !== current.iamRoleArn) {
181
- yamlUpdates.iamRoleArn = newRole;
182
- configChanged = true;
183
- }
184
- }
185
-
186
- if (current.prometheusUrl) {
187
- const newProm = await input({
188
- message: 'Prometheus remote-write URL',
189
- default: current.prometheusUrl,
190
- validate: (v) => /^https?:\/\//.test(v.trim()) || 'Must start with http:// or https://',
191
- });
192
- if (newProm !== current.prometheusUrl) {
193
- yamlUpdates.prometheusUrl = newProm;
194
- configChanged = true;
195
- }
196
- }
197
-
198
- if (current.serviceMapWindow) {
199
- const newWindow = await input({
200
- message: 'Service map window duration',
201
- default: current.serviceMapWindow,
202
- validate: (v) => /^\d+[smh]$/.test(v.trim()) || 'Expected format: 10s, 5m, 1h',
203
- });
204
- if (newWindow !== current.serviceMapWindow) {
205
- yamlUpdates.serviceMapWindow = newWindow;
206
- configChanged = true;
207
- }
208
- }
209
-
210
- // Log publishing options
211
- const newLogGroup = await input({
212
- message: 'CloudWatch log group (leave empty to disable)',
213
- default: cwLogGroup || '',
214
- });
215
-
216
- const logPublishingChanged = (newLogGroup || '') !== (cwLogGroup || '');
217
-
218
- // Persistent buffering
219
- const newPersistentBuffer = await confirm({
220
- message: 'Enable persistent buffering?',
221
- default: !!persistentBuffering,
222
- });
223
-
224
- const bufferChanged = newPersistentBuffer !== !!persistentBuffering;
225
-
226
- // ── Check for changes ──────────────────────────────────────────────────
227
-
228
- const ocuChanged = newMinOcu !== pipeline.minUnits || newMaxOcu !== pipeline.maxUnits;
229
-
230
- if (!ocuChanged && !configChanged && !logPublishingChanged && !bufferChanged) {
231
- printInfo('No changes \u2014 all values are the same as current config.');
232
- console.error();
233
- return;
234
- }
235
-
236
- // ── Summary of changes ─────────────────────────────────────────────────
237
-
238
- console.error();
239
- const changeLines = [];
240
- if (ocuChanged) {
241
- changeLines.push(`OCUs: ${pipeline.minUnits}\u2013${pipeline.maxUnits} ${ARROW} ${newMinOcu}\u2013${newMaxOcu}`);
242
- }
243
- if (yamlUpdates.opensearchEndpoint) {
244
- changeLines.push(`OpenSearch: ${theme.muted(current.opensearchEndpoint)} ${ARROW} ${yamlUpdates.opensearchEndpoint}`);
245
- }
246
- if (yamlUpdates.iamRoleArn) {
247
- changeLines.push(`IAM role: ${theme.muted(current.iamRoleArn)} ${ARROW} ${yamlUpdates.iamRoleArn}`);
248
- }
249
- if (yamlUpdates.prometheusUrl) {
250
- changeLines.push(`Prometheus: ${theme.muted(current.prometheusUrl)} ${ARROW} ${yamlUpdates.prometheusUrl}`);
251
- }
252
- if (yamlUpdates.serviceMapWindow) {
253
- changeLines.push(`Service map window: ${current.serviceMapWindow} ${ARROW} ${yamlUpdates.serviceMapWindow}`);
254
- }
255
- if (logPublishingChanged) {
256
- const from = cwLogGroup || '(disabled)';
257
- const to = newLogGroup || '(disabled)';
258
- changeLines.push(`CloudWatch logging: ${from} ${ARROW} ${to}`);
259
- }
260
- if (bufferChanged) {
261
- changeLines.push(`Persistent buffering: ${persistentBuffering ? 'enabled' : 'disabled'} ${ARROW} ${newPersistentBuffer ? 'enabled' : 'disabled'}`);
262
- }
263
-
264
- printPanel('Pending Changes', changeLines.map((l) => ['', theme.warn('\u2022') + ' ' + l]));
265
- console.error();
266
-
267
- // Confirm
268
- const proceed = await confirm({
269
- message: `Apply these changes to ${pipelineName}?`,
270
- default: true,
271
- });
272
-
273
- if (!proceed) {
274
- printInfo('Update cancelled.');
275
- console.error();
276
- return;
277
- }
278
-
279
- // ── Build update params ────────────────────────────────────────────────
280
-
281
- const params = {};
282
-
283
- if (ocuChanged) {
284
- params.minUnits = newMinOcu;
285
- params.maxUnits = newMaxOcu;
286
- }
287
-
288
- if (configChanged) {
289
- params.pipelineConfigurationBody = patchConfigYaml(
290
- pipeline.pipelineConfigurationBody,
291
- yamlUpdates,
292
- );
293
- }
294
-
295
- if (logPublishingChanged) {
296
- params.logPublishingOptions = newLogGroup
297
- ? { IsLoggingEnabled: true, CloudWatchLogDestination: { LogGroup: newLogGroup } }
298
- : { IsLoggingEnabled: false };
299
- }
300
-
301
- if (bufferChanged) {
302
- params.bufferOptions = { PersistentBufferEnabled: newPersistentBuffer };
303
- }
304
-
305
- // Execute update
306
- console.error();
307
- await updatePipeline(session.region, pipelineName, params);
308
- console.error();
309
- }