backstage

Форк
0
/
snyk-github-issue-sync.ts 
201 строка · 5.4 Кб
1
#!/usr/bin/env yarn ts-node --transpile-only
2
/*
3
 * Copyright 2021 The Backstage Authors
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *     http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
/* eslint-disable @backstage/no-undeclared-imports */
18
import { Octokit } from '@octokit/rest';
19
import minimist from 'minimist';
20
// Generated by GitHub workflow .github/workflows/snyk-github-issue-creator
21
import synkJsonOutput from '../snyk.json';
22

23
type Vulnerability = {
24
  description: string;
25
  packages: {
26
    name: string;
27
    target: string;
28
  }[];
29
  snykId: string;
30
};
31

32
const argv = minimist(process.argv.slice(2));
33

34
const GH_OWNER = 'backstage';
35
const GH_REPO = 'backstage';
36
const SNYK_GH_LABEL = 'snyk-vulnerability';
37
const SNYK_ID_REGEX = /\[([^\]]+)]/i;
38

39
const isDryRun = 'dryrun' in argv;
40

41
if (!process.env.GITHUB_TOKEN) {
42
  console.error('GITHUB_TOKEN is not set. Please provide a Github token');
43
  process.exit(1);
44
}
45

46
const octokit = new Octokit({
47
  auth: process.env.GITHUB_TOKEN,
48
});
49

50
if (isDryRun) {
51
  console.log(
52
    '⚠️  Running in dryrun mode, no issues will be updated on Github ⚠️',
53
  );
54
}
55

56
const fetchSnykGithubIssueMap = async (): Promise<Record<string, number>> => {
57
  const snykGithubIssueMap: Record<string, number> = {};
58

59
  const iterator = octokit.paginate.iterator(octokit.rest.issues.listForRepo, {
60
    owner: GH_OWNER,
61
    repo: GH_REPO,
62
    per_page: 100,
63
    state: 'open',
64
    labels: SNYK_GH_LABEL,
65
  });
66

67
  for await (const { data: issues } of iterator) {
68
    for (const issue of issues) {
69
      // Gets the Vulnerability ID from square braces
70
      const match = SNYK_ID_REGEX.exec(issue.title);
71

72
      if (match && match[1]) {
73
        snykGithubIssueMap[match[1]] = issue.number;
74
      } else {
75
        console.log(`Unmatched Snyk ID for ${issue.title}`);
76
      }
77
    }
78
  }
79

80
  return snykGithubIssueMap;
81
};
82

83
const generateIssueBody = (vulnerability: Vulnerability) => `
84
## Affecting Packages/Plugins
85

86
${Array.from(vulnerability.packages)
87
  .map(({ name, target }) => `* [${name}](${target})`)
88
  .join('\n')}
89

90
${vulnerability.description}
91
`;
92

93
const createGithubIssue = async (vulnerability: Vulnerability) => {
94
  console.log(
95
    `Create Github Issue for Snyk Vulnerability ${vulnerability.snykId}`,
96
  );
97

98
  vulnerability.packages.forEach(({ name, target }) => {
99
    console.log(`- ${name} [${target}]`);
100
  });
101

102
  if (!isDryRun) {
103
    await octokit.issues.create({
104
      owner: GH_OWNER,
105
      repo: GH_REPO,
106
      title: `Snyk vulnerability [${vulnerability.snykId}]`,
107
      labels: [SNYK_GH_LABEL, 'help wanted'],
108
      body: generateIssueBody(vulnerability),
109
    });
110
  }
111
};
112

113
const updateGithubIssue = async (
114
  githubIssueId: number,
115
  vulnerability: Vulnerability,
116
) => {
117
  console.log(
118
    `Update Github Issue #${githubIssueId} for Snky Vulnerability ${vulnerability.snykId}`,
119
  );
120

121
  if (!isDryRun) {
122
    await octokit.issues.update({
123
      owner: GH_OWNER,
124
      repo: GH_REPO,
125
      issue_number: githubIssueId,
126
      body: generateIssueBody(vulnerability),
127
    });
128
  }
129
};
130

131
const closeGithubIssue = async (githubIssueId: number) => {
132
  console.log(`Closing Github Issue #${githubIssueId}`);
133

134
  if (!isDryRun) {
135
    await octokit.issues.update({
136
      owner: GH_OWNER,
137
      repo: GH_REPO,
138
      issue_number: githubIssueId,
139
      state: 'closed',
140
    });
141
  }
142
};
143

144
async function main() {
145
  const snykGithubIssueMap = await fetchSnykGithubIssueMap();
146
  const vulnerabilityStore: Record<string, Vulnerability> = {};
147

148
  // Group the Snyk vulnerabilities, and link back to the affecting packages.
149
  synkJsonOutput.forEach(
150
    ({ projectName, displayTargetFile, vulnerabilities }) => {
151
      vulnerabilities.forEach(
152
        ({ id, description }: { id: string; description: string }) => {
153
          if (id !== undefined && description !== undefined) {
154
            if (vulnerabilityStore[id]) {
155
              if (
156
                !vulnerabilityStore[id].packages.some(
157
                  ({ name }) => name === projectName,
158
                )
159
              ) {
160
                vulnerabilityStore[id].packages.push({
161
                  name: projectName,
162
                  target: displayTargetFile,
163
                });
164
              }
165
            } else {
166
              vulnerabilityStore[id] = {
167
                description,
168
                snykId: id,
169
                packages: [
170
                  {
171
                    name: projectName,
172
                    target: displayTargetFile,
173
                  },
174
                ],
175
              };
176
            }
177
          }
178
        },
179
      );
180
    },
181
  );
182

183
  for (const [id, vulnerability] of Object.entries(vulnerabilityStore)) {
184
    if (snykGithubIssueMap[id]) {
185
      await updateGithubIssue(snykGithubIssueMap[id], vulnerability);
186
    } else {
187
      await createGithubIssue(vulnerability);
188
    }
189
  }
190

191
  for (const [snykId, githubIssueId] of Object.entries(snykGithubIssueMap)) {
192
    if (!vulnerabilityStore[snykId]) {
193
      await closeGithubIssue(githubIssueId);
194
    }
195
  }
196
}
197

198
main().catch(error => {
199
  console.error(error.stack);
200
  process.exit(1);
201
});
202

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

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

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

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