UNPKG

8.96 kBPlain TextView Raw
1/*
2 * Copyright © 2019 Atomist, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import {
18 doWithRetry,
19 GitHubRepoRef,
20 Issue,
21 logger,
22 ProjectOperationCredentials,
23 RemoteRepoRef,
24} from "@atomist/automation-client";
25import { isGitHubRepoRef } from "@atomist/automation-client/lib/operations/common/GitHubRepoRef";
26import { toToken } from "@atomist/sdm";
27/* tslint:disable:import-blacklist */
28import axios, {
29 AxiosPromise,
30 AxiosRequestConfig,
31} from "axios";
32/* tslint:enable:import-blacklist */
33
34export type State = "error" | "failure" | "pending" | "success";
35
36/**
37 * GitHub status
38 */
39export interface Status {
40 state: State;
41 target_url?: string;
42 description?: string;
43 context?: string;
44}
45
46/**
47 * Create a GitHub status
48 * @param {string | ProjectOperationCredentials} creds
49 * @param {GitHubRepoRef} rr
50 * @param {Status} inputStatus
51 * @return {AxiosPromise}
52 */
53export function createStatus(creds: string | ProjectOperationCredentials, rr: GitHubRepoRef, inputStatus: Status): AxiosPromise {
54 const config = authHeaders(toToken(creds));
55 const saferStatus = ensureValidUrl(inputStatus);
56 const url = `${rr.scheme}${rr.apiBase}/repos/${rr.owner}/${rr.repo}/statuses/${rr.sha}`;
57 logger.debug("Updating github status: %s to %j", url, saferStatus);
58 return doWithRetry(() =>
59 axios.post(url, saferStatus, config).catch(err =>
60 Promise.reject(new Error(`Error hitting ${url} to set status ${JSON.stringify(saferStatus)}: ${err.message}`)),
61 ), `Updating github status: ${url} to ${JSON.stringify(saferStatus)}`, {});
62}
63
64/*
65 * If you send a targetUrl that doesn't work, GitHub will not accept the status.
66 * Commonly on findArtifact, we get a Docker image name instead, and people really want
67 * to put that in the URL but it doesn't work.
68 *
69 * This limitation exists only because we are using GitHub Statuses for Goals right now,
70 * and when we move to a custom event it won't be the same problem. So it makes sense
71 * to encode the limitation here.
72 *
73 * Yes the description is going to be ugly. Deal with it.
74 */
75function ensureValidUrl(inputStatus: Status): Status {
76
77 if (!inputStatus.target_url) {
78 return inputStatus;
79 }
80 if (inputStatus.target_url.startsWith("http")) {
81 return inputStatus;
82 }
83 return {
84 target_url: undefined,
85 description: inputStatus.description + " at " + inputStatus.target_url,
86 state: inputStatus.state,
87 context: inputStatus.context,
88 };
89}
90
91export interface Tag {
92 tag: string;
93 message: string;
94
95 /** Commit sha */
96 object: string;
97 type: string;
98 tagger: {
99 name: string;
100 email: string;
101 date: string;
102 };
103}
104
105export function createTag(creds: string | ProjectOperationCredentials, rr: GitHubRepoRef, tag: Tag): AxiosPromise {
106 const config = authHeaders(toToken(creds));
107 const url = `${rr.scheme}${rr.apiBase}/repos/${rr.owner}/${rr.repo}/git/tags`;
108 logger.debug("Updating github tag: %s to %j", url, tag);
109 return doWithRetry(() => axios.post(url, tag, config)
110 .catch(err =>
111 Promise.reject(new Error(`Error hitting ${url} to set tag ${JSON.stringify(tag)}: ${err.message}`)),
112 ), `Updating github tag: ${url} to ${JSON.stringify(tag)}`, {});
113}
114
115export function createTagReference(creds: string | ProjectOperationCredentials, rr: GitHubRepoRef, tag: Tag): AxiosPromise {
116 const config = authHeaders(toToken(creds));
117 const url = `${rr.scheme}${rr.apiBase}/repos/${rr.owner}/${rr.repo}/git/refs`;
118 logger.debug("Creating github reference: %s to %j", url, tag);
119 return doWithRetry(() => axios.post(url, { ref: `refs/tags/${tag.tag}`, sha: tag.object }, config)
120 .catch(err =>
121 Promise.reject(new Error(`Error hitting ${url} to set tag ${JSON.stringify(tag)}: ${err.message}`)),
122 ), `Updating github tag: ${url} to ${JSON.stringify(tag)}`, {});
123}
124
125export function deleteRepository(creds: string | ProjectOperationCredentials, rr: GitHubRepoRef): AxiosPromise {
126 const config = authHeaders(toToken(creds));
127 const url = `${rr.scheme}${rr.apiBase}/repos/${rr.owner}/${rr.repo}`;
128 logger.debug("Deleting repository: %s", url);
129 return axios.delete(url, config)
130 .catch(err => {
131 logger.error(err.message);
132 logger.error(err.response.body);
133 return Promise.reject(new Error(`Error hitting ${url} to delete repo`));
134 },
135 );
136}
137
138export interface Release {
139 tag_name: string;
140 target_commitish?: string;
141 name?: string;
142 body?: string;
143 draft?: boolean;
144 prerelease?: boolean;
145}
146
147export function createRelease(creds: string | ProjectOperationCredentials, rr: GitHubRepoRef, release: Release): AxiosPromise {
148 const config = authHeaders(toToken(creds));
149 const url = `${rr.scheme}${rr.apiBase}/repos/${rr.owner}/${rr.repo}/releases`;
150 logger.debug("Updating github release: %s to %j", url, release);
151 return doWithRetry(() => axios.post(url, release, config)
152 .catch(err =>
153 Promise.reject(new Error(`Error hitting ${url} to set release ${JSON.stringify(release)}: ${err.message}`)),
154 ), `Updating github release: ${url} to ${JSON.stringify(release)}`, {});
155}
156
157export interface GitHubCommitsBetween {
158 commits: Array<{
159 sha: string;
160 author: { login: string };
161 commit: { message: string };
162 }>;
163}
164
165/**
166 * List commits between these shas
167 * @param {string | ProjectOperationCredentials} creds
168 * @param {GitHubRepoRef} rr
169 * @param {string} startSha
170 * @param {string} endSha
171 * @return {Promise<GitHubCommitsBetween>}
172 */
173export function listCommitsBetween(creds: string | ProjectOperationCredentials,
174 rr: GitHubRepoRef,
175 startSha: string,
176 endSha: string): Promise<GitHubCommitsBetween> {
177 const config = authHeaders(toToken(creds));
178 const url = `${rr.scheme}${rr.apiBase}/repos/${rr.owner}/${rr.repo}/compare/${startSha}...${endSha}`;
179 return axios.get(url, config)
180 .then(ap => ap.data);
181}
182
183export function authHeaders(token: string): AxiosRequestConfig {
184 return token ? {
185 headers: {
186 Authorization: `token ${token}`,
187 },
188 }
189 : {};
190}
191
192export function tipOfDefaultBranch(creds: string | ProjectOperationCredentials, rr: GitHubRepoRef): Promise<string> {
193 // TODO: use real default branch
194 const config = authHeaders(toToken(creds));
195 const url = `${rr.scheme}${rr.apiBase}/repos/${rr.owner}/${rr.repo}/branches/master`;
196 return axios.get(url, config)
197 .then(ap => ap.data.commit.sha);
198}
199
200export function isPublicRepo(creds: string | ProjectOperationCredentials, rr: GitHubRepoRef): Promise<boolean> {
201 const config = authHeaders(toToken(creds));
202 const url = `${rr.scheme}${rr.apiBase}/repos/${rr.owner}/${rr.repo}`;
203 return axios.get(url, config)
204 .then(ap => {
205 const privateness = ap.data.private;
206 logger.debug(`Retrieved ${url}. Private is '${privateness}'`);
207 return !privateness;
208 })
209 .catch(err => {
210 logger.warn(`Could not access ${url} to determine repo visibility: ${err.message}`);
211 return false;
212 });
213}
214
215// TODO move to client
216export function updateIssue(creds: string | ProjectOperationCredentials,
217 rr: RemoteRepoRef,
218 issueNumber: number,
219 issue: Issue): AxiosPromise {
220 const grr = isGitHubRepoRef(rr) ? rr : new GitHubRepoRef(rr.owner, rr.repo, rr.sha);
221 const url = `${grr.scheme}${grr.apiBase}/repos/${grr.owner}/${grr.repo}/issues/${issueNumber}`;
222 logger.debug(`Request to '${url}' to update issue`);
223 return axios.patch(url, issue, authHeaders(toToken(creds)));
224}
225
226export async function listTopics(creds: string | ProjectOperationCredentials, rr: RemoteRepoRef): Promise<string[]> {
227 const headers = {
228 headers: {
229 ...authHeaders(toToken(creds)).headers,
230 Accept: "application/vnd.github.mercy-preview+json",
231 },
232 };
233 const grr = isGitHubRepoRef(rr) ? rr : new GitHubRepoRef(rr.owner, rr.repo, rr.sha);
234 const url = `${grr.scheme}${grr.apiBase}/repos/${grr.owner}/${grr.repo}/topics`;
235 const topics = await axios.get(url, headers);
236 return topics.data.names;
237}