argo-cd

Форк
0
/
applications-service.ts 
510 строк · 18.8 Кб
1
import * as deepMerge from 'deepmerge';
2
import {Observable} from 'rxjs';
3
import {map, repeat, retry} from 'rxjs/operators';
4

5
import * as models from '../models';
6
import {isValidURL} from '../utils';
7
import requests from './requests';
8

9
interface QueryOptions {
10
    fields: string[];
11
    exclude?: boolean;
12
    selector?: string;
13
    appNamespace?: string;
14
}
15

16
function optionsToSearch(options?: QueryOptions) {
17
    if (options) {
18
        return {fields: (options.exclude ? '-' : '') + options.fields.join(','), selector: options.selector || '', appNamespace: options.appNamespace || ''};
19
    }
20
    return {};
21
}
22

23
export class ApplicationsService {
24
    public list(projects: string[], options?: QueryOptions): Promise<models.ApplicationList> {
25
        return requests
26
            .get('/applications')
27
            .query({projects, ...optionsToSearch(options)})
28
            .then(res => res.body as models.ApplicationList)
29
            .then(list => {
30
                list.items = (list.items || []).map(app => this.parseAppFields(app));
31
                return list;
32
            });
33
    }
34

35
    public get(name: string, appNamespace: string, refresh?: 'normal' | 'hard'): Promise<models.Application> {
36
        const query: {[key: string]: string} = {};
37
        if (refresh) {
38
            query.refresh = refresh;
39
        }
40
        if (appNamespace) {
41
            query.appNamespace = appNamespace;
42
        }
43
        return requests
44
            .get(`/applications/${name}`)
45
            .query(query)
46
            .then(res => this.parseAppFields(res.body));
47
    }
48

49
    public getApplicationSyncWindowState(name: string, appNamespace: string): Promise<models.ApplicationSyncWindowState> {
50
        return requests
51
            .get(`/applications/${name}/syncwindows`)
52
            .query({name, appNamespace})
53
            .then(res => res.body as models.ApplicationSyncWindowState);
54
    }
55

56
    public revisionMetadata(name: string, appNamespace: string, revision: string): Promise<models.RevisionMetadata> {
57
        return requests
58
            .get(`/applications/${name}/revisions/${revision || 'HEAD'}/metadata`)
59
            .query({appNamespace})
60
            .then(res => res.body as models.RevisionMetadata);
61
    }
62

63
    public revisionChartDetails(name: string, appNamespace: string, revision: string): Promise<models.ChartDetails> {
64
        return requests
65
            .get(`/applications/${name}/revisions/${revision || 'HEAD'}/chartdetails`)
66
            .query({appNamespace})
67
            .then(res => res.body as models.ChartDetails);
68
    }
69

70
    public resourceTree(name: string, appNamespace: string): Promise<models.ApplicationTree> {
71
        return requests
72
            .get(`/applications/${name}/resource-tree`)
73
            .query({appNamespace})
74
            .then(res => res.body as models.ApplicationTree);
75
    }
76

77
    public watchResourceTree(name: string, appNamespace: string): Observable<models.ApplicationTree> {
78
        return requests
79
            .loadEventSource(`/stream/applications/${name}/resource-tree?appNamespace=${appNamespace}`)
80
            .pipe(map(data => JSON.parse(data).result as models.ApplicationTree));
81
    }
82

83
    public managedResources(name: string, appNamespace: string, options: {id?: models.ResourceID; fields?: string[]} = {}): Promise<models.ResourceDiff[]> {
84
        return requests
85
            .get(`/applications/${name}/managed-resources`)
86
            .query(`appNamespace=${appNamespace.toString()}`)
87
            .query({...options.id, fields: (options.fields || []).join(',')})
88
            .then(res => (res.body.items as any[]) || [])
89
            .then(items => {
90
                items.forEach(item => {
91
                    if (item.liveState) {
92
                        item.liveState = JSON.parse(item.liveState);
93
                    }
94
                    if (item.targetState) {
95
                        item.targetState = JSON.parse(item.targetState);
96
                    }
97
                    if (item.predictedLiveState) {
98
                        item.predictedLiveState = JSON.parse(item.predictedLiveState);
99
                    }
100
                    if (item.normalizedLiveState) {
101
                        item.normalizedLiveState = JSON.parse(item.normalizedLiveState);
102
                    }
103
                });
104
                return items as models.ResourceDiff[];
105
            });
106
    }
107

108
    public getManifest(name: string, appNamespace: string, revision: string): Promise<models.ManifestResponse> {
109
        return requests
110
            .get(`/applications/${name}/manifests`)
111
            .query({name, revision, appNamespace})
112
            .then(res => res.body as models.ManifestResponse);
113
    }
114

115
    public updateSpec(appName: string, appNamespace: string, spec: models.ApplicationSpec): Promise<models.ApplicationSpec> {
116
        return requests
117
            .put(`/applications/${appName}/spec`)
118
            .query({appNamespace})
119
            .send(spec)
120
            .then(res => res.body as models.ApplicationSpec);
121
    }
122

123
    public update(app: models.Application, query: {validate?: boolean} = {}): Promise<models.Application> {
124
        return requests
125
            .put(`/applications/${app.metadata.name}`)
126
            .query(query)
127
            .send(app)
128
            .then(res => this.parseAppFields(res.body));
129
    }
130

131
    public create(app: models.Application): Promise<models.Application> {
132
        // Namespace may be specified in the app name. We need to parse and
133
        // handle it accordingly.
134
        if (app.metadata.name.includes('/')) {
135
            const nns = app.metadata.name.split('/', 2);
136
            app.metadata.name = nns[1];
137
            app.metadata.namespace = nns[0];
138
        }
139
        return requests
140
            .post(`/applications`)
141
            .send(app)
142
            .then(res => this.parseAppFields(res.body));
143
    }
144

145
    public delete(name: string, appNamespace: string, propagationPolicy: string): Promise<boolean> {
146
        let cascade = true;
147
        if (propagationPolicy === 'non-cascading') {
148
            propagationPolicy = '';
149
            cascade = false;
150
        }
151
        return requests
152
            .delete(`/applications/${name}`)
153
            .query({
154
                cascade,
155
                propagationPolicy,
156
                appNamespace
157
            })
158
            .send({})
159
            .then(() => true);
160
    }
161

162
    public watch(query?: {name?: string; resourceVersion?: string; projects?: string[]; appNamespace?: string}, options?: QueryOptions): Observable<models.ApplicationWatchEvent> {
163
        const search = new URLSearchParams();
164
        if (query) {
165
            if (query.name) {
166
                search.set('name', query.name);
167
            }
168
            if (query.resourceVersion) {
169
                search.set('resourceVersion', query.resourceVersion);
170
            }
171
            if (query.appNamespace) {
172
                search.set('appNamespace', query.appNamespace);
173
            }
174
        }
175
        if (options) {
176
            const searchOptions = optionsToSearch(options);
177
            search.set('fields', searchOptions.fields);
178
            search.set('selector', searchOptions.selector);
179
            search.set('appNamespace', searchOptions.appNamespace);
180
            query?.projects?.forEach(project => search.append('projects', project));
181
        }
182
        const searchStr = search.toString();
183
        const url = `/stream/applications${(searchStr && '?' + searchStr) || ''}`;
184
        return requests
185
            .loadEventSource(url)
186
            .pipe(repeat())
187
            .pipe(retry())
188
            .pipe(map(data => JSON.parse(data).result as models.ApplicationWatchEvent))
189
            .pipe(
190
                map(watchEvent => {
191
                    watchEvent.application = this.parseAppFields(watchEvent.application);
192
                    return watchEvent;
193
                })
194
            );
195
    }
196

197
    public sync(
198
        name: string,
199
        appNamespace: string,
200
        revision: string,
201
        prune: boolean,
202
        dryRun: boolean,
203
        strategy: models.SyncStrategy,
204
        resources: models.SyncOperationResource[],
205
        syncOptions?: string[],
206
        retryStrategy?: models.RetryStrategy
207
    ): Promise<boolean> {
208
        return requests
209
            .post(`/applications/${name}/sync`)
210
            .send({
211
                appNamespace,
212
                revision,
213
                prune: !!prune,
214
                dryRun: !!dryRun,
215
                strategy,
216
                resources,
217
                syncOptions: syncOptions ? {items: syncOptions} : null,
218
                retryStrategy
219
            })
220
            .then(() => true);
221
    }
222

223
    public rollback(name: string, appNamespace: string, id: number): Promise<boolean> {
224
        return requests
225
            .post(`/applications/${name}/rollback`)
226
            .send({id, appNamespace})
227
            .then(() => true);
228
    }
229

230
    public getDownloadLogsURL(
231
        applicationName: string,
232
        appNamespace: string,
233
        namespace: string,
234
        podName: string,
235
        resource: {group: string; kind: string; name: string},
236
        containerName: string
237
    ): string {
238
        const search = this.getLogsQuery({namespace, appNamespace, podName, resource, containerName, follow: false});
239
        search.set('download', 'true');
240
        return `api/v1/applications/${applicationName}/logs?${search.toString()}`;
241
    }
242

243
    public getContainerLogs(query: {
244
        applicationName: string;
245
        appNamespace: string;
246
        namespace: string;
247
        podName: string;
248
        resource: {group: string; kind: string; name: string};
249
        containerName: string;
250
        tail?: number;
251
        follow?: boolean;
252
        sinceSeconds?: number;
253
        untilTime?: string;
254
        filter?: string;
255
        previous?: boolean;
256
    }): Observable<models.LogEntry> {
257
        const {applicationName} = query;
258
        const search = this.getLogsQuery(query);
259
        const entries = requests.loadEventSource(`/applications/${applicationName}/logs?${search.toString()}`).pipe(map(data => JSON.parse(data).result as models.LogEntry));
260
        let first = true;
261
        return new Observable(observer => {
262
            const subscription = entries.subscribe(
263
                entry => {
264
                    if (entry.last) {
265
                        first = true;
266
                        observer.complete();
267
                        subscription.unsubscribe();
268
                    } else {
269
                        observer.next({...entry, first});
270
                        first = false;
271
                    }
272
                },
273
                err => {
274
                    first = true;
275
                    observer.error(err);
276
                },
277
                () => {
278
                    first = true;
279
                    observer.complete();
280
                }
281
            );
282
            return () => subscription.unsubscribe();
283
        });
284
    }
285

286
    public getResource(name: string, appNamespace: string, resource: models.ResourceNode): Promise<models.State> {
287
        return requests
288
            .get(`/applications/${name}/resource`)
289
            .query({
290
                name: resource.name,
291
                appNamespace,
292
                namespace: resource.namespace,
293
                resourceName: resource.name,
294
                version: resource.version,
295
                kind: resource.kind,
296
                group: resource.group || '' // The group query param must be present even if empty.
297
            })
298
            .then(res => res.body as {manifest: string})
299
            .then(res => JSON.parse(res.manifest) as models.State);
300
    }
301

302
    public getResourceActions(name: string, appNamespace: string, resource: models.ResourceNode): Promise<models.ResourceAction[]> {
303
        return requests
304
            .get(`/applications/${name}/resource/actions`)
305
            .query({
306
                appNamespace,
307
                namespace: resource.namespace,
308
                resourceName: resource.name,
309
                version: resource.version,
310
                kind: resource.kind,
311
                group: resource.group
312
            })
313
            .then(res => {
314
                const actions = (res.body.actions as models.ResourceAction[]) || [];
315
                actions.sort((actionA, actionB) => actionA.name.localeCompare(actionB.name));
316
                return actions;
317
            });
318
    }
319

320
    public runResourceAction(name: string, appNamespace: string, resource: models.ResourceNode, action: string): Promise<models.ResourceAction[]> {
321
        return requests
322
            .post(`/applications/${name}/resource/actions`)
323
            .query({
324
                appNamespace,
325
                namespace: resource.namespace,
326
                resourceName: resource.name,
327
                version: resource.version,
328
                kind: resource.kind,
329
                group: resource.group
330
            })
331
            .send(JSON.stringify(action))
332
            .then(res => (res.body.actions as models.ResourceAction[]) || []);
333
    }
334

335
    public patchResource(name: string, appNamespace: string, resource: models.ResourceNode, patch: string, patchType: string): Promise<models.State> {
336
        return requests
337
            .post(`/applications/${name}/resource`)
338
            .query({
339
                name: resource.name,
340
                appNamespace,
341
                namespace: resource.namespace,
342
                resourceName: resource.name,
343
                version: resource.version,
344
                kind: resource.kind,
345
                group: resource.group || '', // The group query param must be present even if empty.
346
                patchType
347
            })
348
            .send(JSON.stringify(patch))
349
            .then(res => res.body as {manifest: string})
350
            .then(res => JSON.parse(res.manifest) as models.State);
351
    }
352

353
    public deleteResource(applicationName: string, appNamespace: string, resource: models.ResourceNode, force: boolean, orphan: boolean): Promise<any> {
354
        return requests
355
            .delete(`/applications/${applicationName}/resource`)
356
            .query({
357
                name: resource.name,
358
                appNamespace,
359
                namespace: resource.namespace,
360
                resourceName: resource.name,
361
                version: resource.version,
362
                kind: resource.kind,
363
                group: resource.group || '', // The group query param must be present even if empty.
364
                force,
365
                orphan
366
            })
367
            .send()
368
            .then(() => true);
369
    }
370

371
    public events(applicationName: string, appNamespace: string): Promise<models.Event[]> {
372
        return requests
373
            .get(`/applications/${applicationName}/events`)
374
            .query({appNamespace})
375
            .send()
376
            .then(res => (res.body as models.EventList).items || []);
377
    }
378

379
    public resourceEvents(
380
        applicationName: string,
381
        appNamespace: string,
382
        resource: {
383
            namespace: string;
384
            name: string;
385
            uid: string;
386
        }
387
    ): Promise<models.Event[]> {
388
        return requests
389
            .get(`/applications/${applicationName}/events`)
390
            .query({
391
                appNamespace,
392
                resourceUID: resource.uid,
393
                resourceNamespace: resource.namespace,
394
                resourceName: resource.name
395
            })
396
            .send()
397
            .then(res => (res.body as models.EventList).items || []);
398
    }
399

400
    public terminateOperation(applicationName: string, appNamespace: string): Promise<boolean> {
401
        return requests
402
            .delete(`/applications/${applicationName}/operation`)
403
            .query({appNamespace})
404
            .send()
405
            .then(() => true);
406
    }
407

408
    public getLinks(applicationName: string, namespace: string): Promise<models.LinksResponse> {
409
        return requests
410
            .get(`/applications/${applicationName}/links`)
411
            .query({namespace})
412
            .send()
413
            .then(res => res.body as models.LinksResponse);
414
    }
415

416
    public getResourceLinks(applicationName: string, appNamespace: string, resource: models.ResourceNode): Promise<models.LinksResponse> {
417
        return requests
418
            .get(`/applications/${applicationName}/resource/links`)
419
            .query({
420
                name: resource.name,
421
                appNamespace,
422
                namespace: resource.namespace,
423
                resourceName: resource.name,
424
                version: resource.version,
425
                kind: resource.kind,
426
                group: resource.group || '' // The group query param must be present even if empty.
427
            })
428
            .send()
429
            .then(res => {
430
                const links = res.body as models.LinksResponse;
431
                const items: models.LinkInfo[] = [];
432
                (links?.items || []).forEach(link => {
433
                    if (isValidURL(link.url)) {
434
                        items.push(link);
435
                    }
436
                });
437
                links.items = items;
438
                return links;
439
            });
440
    }
441

442
    private getLogsQuery(query: {
443
        namespace: string;
444
        appNamespace: string;
445
        podName: string;
446
        resource: {group: string; kind: string; name: string};
447
        containerName: string;
448
        tail?: number;
449
        follow?: boolean;
450
        sinceSeconds?: number;
451
        untilTime?: string;
452
        filter?: string;
453
        previous?: boolean;
454
    }): URLSearchParams {
455
        const {appNamespace, containerName, namespace, podName, resource, tail, sinceSeconds, untilTime, filter, previous} = query;
456
        let {follow} = query;
457
        if (follow === undefined || follow === null) {
458
            follow = true;
459
        }
460
        const search = new URLSearchParams();
461
        search.set('appNamespace', appNamespace);
462
        search.set('container', containerName);
463
        search.set('namespace', namespace);
464
        search.set('follow', follow.toString());
465
        if (podName) {
466
            search.set('podName', podName);
467
        } else {
468
            search.set('group', resource.group);
469
            search.set('kind', resource.kind);
470
            search.set('resourceName', resource.name);
471
        }
472
        if (tail) {
473
            search.set('tailLines', tail.toString());
474
        }
475
        if (sinceSeconds) {
476
            search.set('sinceSeconds', sinceSeconds.toString());
477
        }
478
        if (untilTime) {
479
            search.set('untilTime', untilTime);
480
        }
481
        if (filter) {
482
            search.set('filter', filter);
483
        }
484
        if (previous) {
485
            search.set('previous', previous.toString());
486
        }
487
        // The API requires that this field be set to a non-empty string.
488
        search.set('sinceSeconds', '0');
489
        return search;
490
    }
491

492
    private parseAppFields(data: any): models.Application {
493
        data = deepMerge(
494
            {
495
                apiVersion: 'argoproj.io/v1alpha1',
496
                kind: 'Application',
497
                spec: {
498
                    project: 'default'
499
                },
500
                status: {
501
                    resources: [],
502
                    summary: {}
503
                }
504
            },
505
            data
506
        );
507

508
        return data as models.Application;
509
    }
510
}
511

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.