UNPKG

5.66 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 logger,
21 ProjectOperationCredentials,
22 RemoteRepoRef,
23} from "@atomist/automation-client";
24import {
25 ArtifactStore,
26 DeployableArtifact,
27 toToken,
28} from "@atomist/sdm";
29import { AppInfo } from "@atomist/sdm/lib/spi/deploy/Deployment";
30import * as GitHubApi from "@octokit/rest";
31// tslint:disable-next-line:import-blacklist
32import axios from "axios";
33import * as fs from "fs-extra";
34import * as p from "path";
35import * as tmp from "tmp-promise";
36import {
37 createRelease,
38 createTag,
39 Release,
40 Tag,
41} from "../../../util/github/ghub";
42
43/* tslint:disable:deprecation */
44
45/**
46 * Implement ArtifactStore interface to store artifacts as GitHub releases
47 * @deprecated Artifact storage should be done using project listeners
48 */
49export class GitHubReleaseArtifactStore implements ArtifactStore {
50
51 public async storeFile(appInfo: AppInfo, localFile: string, creds: ProjectOperationCredentials): Promise<string> {
52 const token = toToken(creds);
53 const tagName = appInfo.version + new Date().getMilliseconds();
54 const tag: Tag = {
55 tag: tagName,
56 message: appInfo.version + " for release",
57 object: appInfo.id.sha,
58 type: "commit",
59 tagger: {
60 name: "Atomist",
61 email: "info@atomist.com",
62 date: new Date().toISOString(),
63 },
64 };
65 const grr = appInfo.id as GitHubRepoRef;
66 await createTag(token, grr, tag);
67 const release: Release = {
68 name: appInfo.version,
69 tag_name: tag.tag,
70 };
71 await createRelease(token, grr, release);
72 const asset = await uploadAsset(token, grr.owner, grr.repo, tag.tag, localFile);
73 logger.info("Uploaded artifact with url [%s] for %j", asset.browser_download_url, appInfo);
74 return asset.browser_download_url;
75 }
76
77 // TODO this is Maven specific
78 // Name is of format fintan-0.1.0-SNAPSHOT.jar
79 public async checkout(url: string, id: RemoteRepoRef, creds: ProjectOperationCredentials): Promise<DeployableArtifact> {
80 logger.info("Attempting to download artifact [%s] for %j", url, id);
81 const tmpDir = await tmp.dir({ prefix: "GitHubReleaseArtifactStore-", unsafeCleanup: true });
82 const cwd = tmpDir.path;
83 const lastSlash = url.lastIndexOf("/");
84 const filename = url.substring(lastSlash + 1);
85 const re = /([a-zA-Z0-9_]+)-(.*)/;
86 const match = re.exec(filename);
87 const name = match[1];
88 const version = match[2].replace(/.jar$/, "");
89
90 const outputPath = cwd + "/" + filename;
91 logger.info("Attempting to download url %s to %s", url, outputPath);
92 await downloadFileAs(creds, url, outputPath);
93 logger.info("Successfully downloaded url %s to %s", url, outputPath);
94 return {
95 cwd,
96 filename,
97 name,
98 version,
99 id,
100 };
101 }
102}
103
104/**
105 * Download the file to local disk. This only works on public repos, see
106 * https://stackoverflow.com/questions/20396329/how-to-download-github-release-from-private-repo-using-command-line
107 * @param creds ignored
108 * @param {string} url release asset URL from public repo
109 * @param {string} outputFilename
110 * @return {Promise<any>}
111 */
112function downloadFileAs(creds: ProjectOperationCredentials, url: string, outputFilename: string): Promise<any> {
113 return doWithRetry(() => axios.get(url, {
114 headers: { Accept: "application/octet-stream" },
115 responseType: "arraybuffer",
116 }), `Download ${url} to ${outputFilename}`, {
117 minTimeout: 1000,
118 maxTimeout: 10000,
119 retries: 10,
120 })
121 .then(result => {
122 return fs.writeFile(outputFilename, result.data);
123 });
124}
125
126export interface Asset {
127 url: string;
128 browser_download_url: string;
129 name: string;
130}
131
132export async function uploadAsset(token: string,
133 owner: string,
134 repo: string,
135 tag: string,
136 path: string,
137 contentType: string = "application/zip"): Promise<Asset> {
138 const github = githubApi(token);
139 const result = await github.repos.getReleaseByTag({ owner, repo, tag });
140 const file = (await fs.readFile(path)).buffer;
141 const contentLength = (await fs.stat(path)).size;
142 const r = await github.repos.uploadReleaseAsset({
143 url: result.data.upload_url,
144 file,
145 headers: {
146 "content-length": contentLength,
147 "content-type": contentType,
148 },
149 name: p.basename(path),
150 });
151 return r.data as any;
152}
153
154export function githubApi(token: string, apiUrl: string = "https://api.github.com/"): GitHubApi {
155 return new GitHubApi({
156 auth: !!token ? `token ${token}` : undefined,
157 baseUrl: apiUrl.endsWith("/") ? apiUrl.slice(0, -1) : apiUrl,
158 });
159}
160
\No newline at end of file