Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get the download URL for a workflow artifact instead of following the redirect and downloading it #240

Closed
bantic opened this issue Aug 19, 2020 · 5 comments
Labels
Type: Support Any questions, information, or general needs around the SDK or GitHub APIs

Comments

@bantic
Copy link

bantic commented Aug 19, 2020

Is it possible to get the download URL for an artifact?
The docs here suggest that the 302 redirect URL will be returned from a call like this:

let response = await octokit.request('GET /repos/{owner}/{repo}/actions/artifacts/{artifact_id}/{archive_format}', {
  owner: 'octocat',
  repo: 'hello-world',
  artifact_id: 42,
  archive_format: 'archive_format'
})

When I am trying that (as well as octokit.actions.downloadArtifact, as per these docs), it seems that the 302 redirect is followed, making my response look like so:

{
  status: 200,
  url: 'https://pipelines.actions...', // <-- this is the correct, signed URL, that I want
  headers: { ... },
  data: ArrayBuffer(23750288) // <-- Downloads the artifact into `data`. I don't want it to do this
}

I want to just get that url, without downloading the response into that data ArrayBuffer.
Is that possible? I've tried adding request: { redirect: 'manual'} as an option, but that didn't seem to do it.

@wolfy1339 wolfy1339 added the Type: Support Any questions, information, or general needs around the SDK or GitHub APIs label Aug 19, 2020
@gr2m
Copy link
Contributor

gr2m commented Aug 19, 2020

In Node.js, yes:

let { url } = await octokit.request('HEAD /repos/{owner}/{repo}/actions/artifacts/{artifact_id}/{archive_format}', {
  owner: 'octocat',
  repo: 'hello-world',
  artifact_id: 42,
  archive_format: 'archive_format'
})

In browsers it is not currently possible, due to lacking CORS settings

See also

@bantic
Copy link
Author

bantic commented Aug 20, 2020

Thanks for the speedy reply. Fwiw I get a 404 from Github for the HEAD request. I might have messed something up with the other headers for the request.

I ended up using Axios (from Node.js), like this:

let request = octokit.actions.downloadArtifact.endpoint({
  owner,
  repo,
  artifact_id,
  archive_format,
});
let response = await axios.get(request.url, {
  headers: {
    ...request.headers,
    Authorization: `token ${token}`,
  },
  validateStatus(status) {
    return status === 302;
  },
  maxRedirects: 0,
});
let url = response.headers.location;

@gr2m
Copy link
Contributor

gr2m commented Aug 20, 2020

Fwiw I get a 404 from Github for the HEAD request

I was able to reproduce the problem, thank you!

That is likely an API bug. The problem is the final URL at https://pipelines.actions.githubusercontent.com/.... It does not accept a HEAD request.

I'll check in with support and keep you posted. Your workaround looks good for the time being

gr2m added a commit to gr2m/github-api-wishlist that referenced this issue Aug 20, 2020
@cadesalaberry
Copy link

Thanks @bantic, it is exactly what I was looking for.

dorny added a commit to dorny/test-reporter that referenced this issue Feb 15, 2021
@gr2m
Copy link
Contributor

gr2m commented Apr 22, 2021

For reference, here is a GitHub Action workflow file to create an artifact for testing:

name: Create artifact
on:
  workflow_dispatch: {}

jobs:
  create-artifact:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: mkdir -p my-artifact

      - run: echo hello > my-artifact/world.txt

      - uses: actions/upload-artifact@v2
        with:
          name: my-artifact
          path: my-artifact/world.txt

See it in action at https://github.com/gr2m/sandbox/actions/workflows/create-artifact.yml

Then I use this script to get the URL, which works

const { Octokit } = require("@octokit/core");

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

run();

async function run() {
  const owner = "gr2m";
  const repo = "sandbox";

  const {
    data: { artifacts },
  } = await octokit.request("GET /repos/{owner}/{repo}/actions/artifacts", {
    owner,
    repo,
  });

  try {
    let { headers } = await octokit.request(
      "HEAD /repos/{owner}/{repo}/actions/artifacts/{artifact_id}/{archive_format}",
      {
        owner,
        repo,
        artifact_id: artifacts[0].id,
        archive_format: "zip",
      }
    );
    console.log(headers.location);
  } catch (error) {
    console.log(error);
  }
}

But when I change it to HEAD /repos/{owner}/{repo}/actions/artifacts it get a 404.

But it turns out not to be a problem with the API, the problem is that octokit follows the redirect and then retries the HEAD request on https://pipelines.actions.githubusercontent.com, which fails.

For node usage, the request can be set to not follow redirects. So this works

let { headers } = await octokit.request(
  "HEAD /repos/{owner}/{repo}/actions/artifacts/{artifact_id}/{archive_format}",
  {
    owner,
    repo,
    artifact_id: artifacts[0].id,
    archive_format: "zip",
    request: {
      redirect: "manual",
    },
  }
);
console.log(headers.location);

Note the request.redirect option.

Sorry this took me a while to figure out.

All node-fetch extensions documented here can be set using request.[node-fetch extension name], but they only work for Node, of course

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Support Any questions, information, or general needs around the SDK or GitHub APIs
Projects
None yet
Development

No branches or pull requests

4 participants