1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | import { logger } from "@atomist/automation-client";
|
18 | import {
|
19 | cancelableGoal,
|
20 | GoalCompletionListener,
|
21 | SdmGoalState,
|
22 | SoftwareDeliveryMachine,
|
23 | } from "@atomist/sdm";
|
24 | import * as _ from "lodash";
|
25 | import {
|
26 | deleteJob,
|
27 | deletePods,
|
28 | listJobs,
|
29 | prettyPrintError,
|
30 | sanitizeName,
|
31 | } from "./KubernetesGoalScheduler";
|
32 |
|
33 |
|
34 |
|
35 |
|
36 | export class KubernetesJobDeletingGoalCompletionListenerFactory {
|
37 |
|
38 | private readonly cache: Map<string, { ttl: number, name: string, namespace: string }> = new Map();
|
39 |
|
40 | constructor(private readonly sdm: SoftwareDeliveryMachine) {
|
41 | this.initialize();
|
42 | }
|
43 |
|
44 | public create(): GoalCompletionListener {
|
45 | return async gi => {
|
46 | const goalEvent = gi.completedGoal;
|
47 |
|
48 | if (goalEvent.state === SdmGoalState.in_process) {
|
49 | return;
|
50 | }
|
51 |
|
52 | if (goalEvent.state === SdmGoalState.canceled && !(await cancelableGoal(goalEvent, gi.configuration))) {
|
53 | return;
|
54 | }
|
55 |
|
56 | const selector = `atomist.com/goal-set-id=${goalEvent.goalSetId},atomist.com/creator=${sanitizeName(this.sdm.configuration.name)}`;
|
57 | let jobs;
|
58 |
|
59 | try {
|
60 | jobs = await listJobs(selector);
|
61 | } catch (e) {
|
62 | logger.warn(`Failed to read k8s jobs: ${prettyPrintError(e)}`);
|
63 | return;
|
64 | }
|
65 |
|
66 | logger.debug(
|
67 | `Found k8s jobs for goal set '${goalEvent.goalSetId}': '${
|
68 | jobs.map(j => `${j.metadata.namespace}:${j.metadata.name}`).join(", ")}'`);
|
69 |
|
70 | const goalJobs = jobs.filter(j => {
|
71 | const annotations = j.metadata.annotations;
|
72 | if (!!annotations && !!annotations["atomist.com/sdm"]) {
|
73 | const sdmAnnotation = JSON.parse(annotations["atomist.com/sdm"]);
|
74 | return sdmAnnotation.goal.uniqueName === goalEvent.uniqueName;
|
75 | }
|
76 | return false;
|
77 | });
|
78 |
|
79 | logger.debug(
|
80 | `Matching k8s job for goal '${goalEvent.uniqueName}' found: '${
|
81 | goalJobs.map(j => `${j.metadata.namespace}:${j.metadata.name}`).join(", ")}'`);
|
82 |
|
83 | const ttl: number = _.get(this.sdm.configuration, "sdm.k8s.job.ttl", 1000 * 60 * 2);
|
84 |
|
85 | for (const goalJob of goalJobs) {
|
86 | this.cache.set(
|
87 | goalJob.metadata.uid,
|
88 | {
|
89 | ttl: Date.now() + ttl,
|
90 | name: goalJob.metadata.name,
|
91 | namespace: goalJob.metadata.namespace,
|
92 | });
|
93 | }
|
94 | };
|
95 | }
|
96 |
|
97 | private initialize(): void {
|
98 | setInterval(async () => {
|
99 | const now = Date.now();
|
100 | for (const uid of this.cache.keys()) {
|
101 | const job = this.cache.get(uid);
|
102 | if (job.ttl <= now) {
|
103 | logger.debug(`Deleting k8s job '${job.namespace}:${job.name}'`);
|
104 | await deleteJob(job);
|
105 |
|
106 | logger.debug(`Deleting k8s pods for job '${job.namespace}:${job.name}'`);
|
107 | await deletePods(job);
|
108 | this.cache.delete(uid);
|
109 | }
|
110 | }
|
111 | },
|
112 | _.get(this.sdm.configuration, "sdm.k8s.job.ttlCheckInterval", 15000));
|
113 | }
|
114 | }
|