Created
May 14, 2020 12:10
-
-
Save ingmarh/b7c5254bdcc6dc3bb30e1601a0c2122b to your computer and use it in GitHub Desktop.
CircleCI Build Artifacts server
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export { serve } from 'https://deno.land/[email protected]/http/server.ts' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# docker build -t circleci_build_artifacts . | |
# docker run -it -e CIRCLECI_TOKEN=token --init -p 8091:8091 --name circleci_build_artifacts -d circleci_build_artifacts | |
FROM hayd/alpine-deno:1.0.0 | |
ENV PORT 8091 | |
EXPOSE $PORT | |
WORKDIR /app | |
USER deno | |
COPY deps.ts . | |
RUN deno cache deps.ts | |
ADD . . | |
RUN deno cache server.js | |
CMD ["run", "--allow-env", "--allow-net", "server.js"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { serve } from './deps.ts' | |
const port = Number(Deno.env.get('PORT')) | |
const token = Deno.env.get('CIRCLECI_TOKEN') | |
if (!token) { | |
console.error('CIRCLECI_TOKEN environment variable not set.') | |
Deno.exit(1) | |
} | |
const s = serve({ port }) | |
console.log(`Server started on port ${port}`) | |
// CircleCI build artifacts server. Requires CircleCI API token with "view-builds" scope. | |
// - View latest build artifacts for a repo with /:repo/latest/artifacts | |
// - Go to a specific build artifact with /:repo/latest/artifacts/:artifact-path | |
// - Specify the branch name by appending the "branch" query parameter (default branch: master) | |
for await (const req of s) { | |
const [pathname, search] = req.url.split('?') | |
const [, repo, artifactPath] = pathname.match(/^\/(.+?\/.+?)\/latest\/artifacts\/?(.+)?/) || '' | |
const branch = new URLSearchParams(search).get('branch') || 'master' | |
function respondNotFound(body) { | |
req.respond({ | |
headers: new Headers({ 'Content-Type': 'text/html' }), | |
status: 404, | |
body: `<p>${body}</p>`, | |
}) | |
} | |
if (!repo) { | |
respondNotFound('Invalid request. Required format: /:repo/latest/artifacts(/:artifact-path)') | |
} else { | |
const latestBuilds = await apiGet(`${repo}/tree/${branch}?filter=successful&shallow=true`) | |
if (latestBuilds.length) { | |
// Get the artifacts of the latest "test" job build. Fall back to using the latest build. | |
const build = latestBuilds.find(build => build.workflows?.job_name === 'test') || latestBuilds[0] | |
const buildArtifacts = await apiGet(`${repo}/${build.build_num}/artifacts`) | |
if (buildArtifacts) { | |
const artifactUrl = buildArtifacts.find(artifact => artifact.path === artifactPath)?.url | |
if (artifactUrl) { | |
req.respond({ | |
headers: new Headers({ Location: artifactUrl }), | |
status: 302, | |
}) | |
} else if (!artifactPath) { | |
req.respond({ | |
headers: new Headers({ 'Content-Type': 'application/json' }), | |
body: JSON.stringify(buildArtifacts), | |
}) | |
} else { | |
respondNotFound(`Artifact "${artifactPath}" not found for <a href="${build.build_url}">build ${build.build_num}</a>.`) | |
} | |
} else { | |
respondNotFound(`No build artifacts for <a href="${build.build_url}">build ${build.build_num}</a>.`) | |
} | |
} else { | |
respondNotFound(`Couldn't find any successful builds for branch "${branch}" in ${repo} (or no access).`) | |
} | |
} | |
} | |
async function apiGet(path) { | |
const url = new URL(`https://circleci.com/api/v1.1/project/github/${path}`) | |
console.log(`Fetching ${url.href}`) | |
url.searchParams.set('circle-token', token) | |
return fetch(url.href).then(response => response.ok && response.json()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment