GitHub Actions のアーティファクトを楽に見れるようにしてみた


こちらは、GitHub Actions Advent Calendar 2020 18日目の記事になります。

はじめに

GitHub Actions を使っていて、アーティファクト(成果物)をリポジトリのタブから
パイプラインを選択して探すの面倒だと思ったことはないでしょうか?

自分もAndroidの開発でアーティファクトにアップロードしたアプリを
インストールするのが大変だと感じていました。

なので今回は、Github Actions でアップロードしたアーティファクトを
簡単にダウンロードできる Webアプリ を作ってみました。
(といっても、1ページの簡単なものですが)

作成したもの



Webページ:
https://takenokotech.github.io/apricot

システム構成図

フロント部分は Next.js を使ってみました。

シーケンス

シーケンスと使用したAPIを紹介します。

認可〜トークン取得

https://docs.github.com/ja/free-pro-team@latest/developers/apps/authorizing-oauth-apps
トークン取得のPOSTは、CORSを回避するために CORS Anywhere を用いてプロキシしました。

export async function getToken(clientId: string, clientSecret: string, redirectUri: string, oauthScope: string, code: string = null): Promise<string> {
  if (!code) {
    window.location.href = `https://github.com/login/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${oauthScope}`;
    return null;
  }

  const accessTokenUrl = "https://cors-anywhere.herokuapp.com/https://github.com/login/oauth/access_token";
  const response = await fetch(`${accessTokenUrl}?client_id=${clientId}&client_secret=${clientSecret}&code=${code}&redirect_uri=${redirectUri}`, {
    method: "POST",
    headers: {
      Accept: "application/json",
    },
  });

  const { access_token, scope, token_type } = await response.json();
  return access_token
}

ユーザー情報取得

https://docs.github.com/en/free-pro-team@latest/rest/reference/users#get-the-authenticated-user
ユーザー情報に紐づく様々な公開データが取れます。

export async function getUser(accesstoken: string): Promise<User> {
  const apiUrl = "https://api.github.com/user";
  const resp = await fetch(apiUrl, {
    headers: {
      Authorization: `token ${accesstoken}`,
      Accept: "application/vnd.github.v3+json",
    },
  });

  // repos_urlは、下のリポジトリ取得で使われるURLと同じ
  const { login, repos_url } = await resp.json();
  return login

リポジトリ取得

https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#list-organization-repositories
ユーザーが公開しているリポジトリが見えます。

export async function getRepos(userName: string): Promise<RepoItem[]> {
  const url = `https://api.github.com/users/${userName}/repos?per_page=100`;
  const resp = await fetch(url);
  const json = await resp.json();
  const repos =
    json.map((repo) => ({
      name: repo.name,
      full_name: repo.full_name,
      html_url: repo.html_url,
    })) || [];
  return repos;
}

ワークフロー取得

https://docs.github.com/en/free-pro-team@latest/rest/reference/actions#list-workflow-runs-for-a-repository
コミットログなどが取れます。

export async function getWorkflow(userName: string, repoName: string): Promise<WorkflowItem[]> {
  const url = `https://api.github.com/repos/${userName}/${repoName}/actions/runs`;
  const resp = await fetch(url);
  const json = await resp.json();
  const workflows = json.workflow_runs?.map((workflow) => workflow) || [];
  return workflows;
}

アーティファクト取得

https://docs.github.com/en/free-pro-team@latest/rest/reference/actions#list-artifacts-for-a-repository
アーティファクトURLはワークフローから取得できます。
このgetArtifactUrlではファイルを取得するための期限つきURLが発行されるので、そちらを使ってファイルを取得します。

export async function getArtifactUrl(user: string, accesstoken: string, artifactUrl: string): Promise<string> {
  const response = await fetch(artifactUrl, {
    headers: {
      Authorization: `token $accesstoken`,
      Accept: "application/vnd.github.v3+json",
      "Content-Type": "application/zip",
      "User-Agent": user,
    },
  });
  return response.url;
}

// getArtifactUrlで取得したURLを使ってファイルをダウンロードする。
const url = await getArtifactUrl(this.state.artifactUrl);
const link = document.createElement("a");
link.download = this.state.name;
link.href = url;
link.click();

まとめ

以上、今回作った Apricot の技術解説でした。
ご覧いただきありがとうございました。