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

Separate API calls from install logic #29

Merged
merged 19 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 30 additions & 58 deletions src/functions.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,15 @@
import * as core from "@actions/core"
import { GitHub } from "@actions/github/lib/utils"
import * as tc from "@actions/tool-cache"
import retry from "async-retry"
import * as fs from "fs"
import semver from "semver"

const NEXTFLOW_REPO = { owner: "nextflow-io", repo: "nextflow" }
import { NextflowRelease } from "./nextflow-release"

// HACK Private but I want to test this
export async function all_nf_releases(
ok: InstanceType<typeof GitHub>
): Promise<object[]> {
return await ok.paginate(
ok.rest.repos.listReleases,
NEXTFLOW_REPO,
response => response.data
)
}

// HACK Private but I want to test this
export async function latest_stable_release_data(
ok: InstanceType<typeof GitHub>
): Promise<object> {
const { data: stable_release } = await ok.rest.repos.getLatestRelease(
NEXTFLOW_REPO
)

return stable_release
}

export async function release_data(
version: string,
ok: InstanceType<typeof GitHub>
): Promise<object> {
function tag_filter(version: string): (r: NextflowRelease) => Boolean {
// Setup tag-based filtering
let filter = (r: object): boolean => {
return semver.satisfies(r["tag_name"], version, true)
let filter = (r: NextflowRelease): boolean => {
return semver.satisfies(r.versionNumber, version, true)
}

// Check if the user passed a 'latest*' tag, and override filtering
Expand All @@ -44,52 +18,45 @@ export async function release_data(
if (version.includes("-everything")) {
// No filtering
// eslint-disable-next-line @typescript-eslint/no-unused-vars
filter = (r: object) => {
filter = (r: NextflowRelease) => {
return true
}
} else if (version.includes("-edge")) {
filter = r => {
return r["tag_name"].endsWith("-edge")
filter = (r: NextflowRelease) => {
return r.versionNumber.endsWith("-edge")
}
} else {
// This is special: passing 'latest' or 'latest-stable' allows us to use
// the latest stable GitHub release direct from the API
const stable_release = await latest_stable_release_data(ok)
return stable_release
filter = (r: NextflowRelease) => {
return !r.isEdge
}
}
}
return filter
}

// Get all the releases
const all_releases: object[] = await all_nf_releases(ok)

const matching_releases = all_releases.filter(filter)
export async function get_nextflow_release(
version: string,
releases: NextflowRelease[]
): Promise<NextflowRelease> {
// Filter the releases
const filter = tag_filter(version)
const matching_releases = releases.filter(filter)

matching_releases.sort((x, y) => {
// HACK IDK why the value flip is necessary with the return
return semver.compare(x["tag_name"], y["tag_name"], true) * -1
return semver.compare(x.versionNumber, y.versionNumber, true) * -1
})

return matching_releases[0]
}

export function nextflow_bin_url(release: object, get_all: boolean): string {
const release_assets = release["assets"]
const all_asset = release_assets.filter((a: object) => {
return a["browser_download_url"].endsWith("-all")
})[0]
const regular_asset = release_assets.filter((a: object) => {
return a["name"] === "nextflow"
})[0]

const dl_asset = get_all ? all_asset : regular_asset

return dl_asset.browser_download_url
}

export async function install_nextflow(
url: string,
version: string
release: NextflowRelease,
get_all: boolean
): Promise<string> {
const url = get_all ? release.allBinaryURL : release.binaryURL
const version = release.versionNumber

core.debug(`Downloading Nextflow from ${url}`)
const nf_dl_path = await retry(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down Expand Up @@ -119,6 +86,11 @@ export async function install_nextflow(
}

export function check_cache(version: string): boolean {
// A 'latest*' version indicates that a cached version would be invalid until
// the version is resolved: abort
if (version.includes("latest")) {
return false
}
const cleaned_version = semver.clean(version, true)
if (cleaned_version === null) {
return false
Expand Down
45 changes: 13 additions & 32 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import * as core from "@actions/core"
import * as exec from "@actions/exec"
import * as github from "@actions/github"
import { GitHub } from "@actions/github/lib/utils"
import * as tc from "@actions/tool-cache"
import * as fs from "fs"
import semver from "semver"

import {
check_cache,
install_nextflow,
nextflow_bin_url,
release_data
get_nextflow_release,
install_nextflow
} from "./functions"
import { NextflowRelease } from "./nextflow-release"
import { pull_releases, setup_octokit } from "./octokit-wrapper"

async function run(): Promise<void> {
// Set environment variables
// CAPSULE_LOG leads to a bunch of boilerplate being output to the logs: turn
// it off
core.exportVariable("CAPSULE_LOG", "none")

// Read in the arguments
Expand All @@ -28,25 +28,16 @@ async function run(): Promise<void> {
}

// Setup the API
let octokit: InstanceType<typeof GitHub> | undefined
try {
octokit = github.getOctokit(token)
} catch (e: unknown) {
if (e instanceof Error) {
core.setFailed(
`Could not authenticate to GitHub Releases API with provided token\n${e.message}`
)
}
}
const octokit = await setup_octokit(token)

const releases = await pull_releases(octokit)

// Get the release info for the desired release
let release = {}
let release = {} as NextflowRelease
let resolved_version = ""
try {
if (octokit !== undefined) {
release = await release_data(version, octokit)
}
resolved_version = release["tag_name"]
release = await get_nextflow_release(version, releases)
resolved_version = release.versionNumber
core.info(
`Input version '${version}' resolved to Nextflow ${release["name"]}`
)
Expand All @@ -58,20 +49,10 @@ async function run(): Promise<void> {
}
}

// Get the download url for the desired release
let url = ""
try {
url = nextflow_bin_url(release, get_all)
core.info(`Preparing to download from ${url}`)
} catch (e: unknown) {
if (e instanceof Error) {
core.setFailed(`Could not parse the download URL\n${e.message}`)
}
}
try {
// Download Nextflow and add it to path
if (!check_cache(resolved_version)) {
const nf_install_path = await install_nextflow(url, resolved_version)
const nf_install_path = await install_nextflow(release, get_all)
const cleaned_version = String(semver.clean(resolved_version, true))
const nf_path = await tc.cacheDir(
nf_install_path,
Expand Down
49 changes: 49 additions & 0 deletions src/nextflow-release.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Houses the pertinent data that GitHub exposes for each Nextflow release
*/
export type NextflowRelease = {
versionNumber: string
isEdge: boolean
binaryURL: string
allBinaryURL: string
}

/**
* Converts the raw OctoKit data into a structured NextflowRelease
* @param data A "release" data struct from OctoKit
* @returns `data` converted into a `NextflowRelease`
*/
export function nextflow_release(data: object): NextflowRelease {
const nf_release: NextflowRelease = {
versionNumber: data["tag_name"],
isEdge: data["prerelease"],
binaryURL: nextflow_bin_url(data, false),
allBinaryURL: nextflow_bin_url(data, true)
}
return nf_release
}

/**
* Gets the download URL of a Nextflow binary
* @param release A "release" data struct from OctoKit
* @param get_all Whether to return the url for the "all" variant of Nextflow
* @returns The URL of the Nextflow binary
*/
export function nextflow_bin_url(release: object, get_all: boolean): string {
const release_assets = release["assets"]
const all_asset = release_assets.filter((a: object) => {
return a["browser_download_url"].endsWith("-all")
})[0]
const regular_asset = release_assets.filter((a: object) => {
return a["name"] === "nextflow"
})[0]

const dl_asset = get_all ? all_asset : regular_asset
if (dl_asset) {
return dl_asset.browser_download_url
} else {
// Old pre-release versions of Nextflow didn't have an "all" variant. To
// avoid downstream errors, substitute the regular url here.
return regular_asset.browser_download_url
}
}
62 changes: 62 additions & 0 deletions src/octokit-wrapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import * as core from "@actions/core"
import * as github from "@actions/github"
import { GitHub } from "@actions/github/lib/utils"

import { nextflow_release, NextflowRelease } from "./nextflow-release"

const NEXTFLOW_REPO = { owner: "nextflow-io", repo: "nextflow" }

export async function setup_octokit(
github_token: string
): Promise<InstanceType<typeof GitHub>> {
let octokit = {} as InstanceType<typeof GitHub>
try {
octokit = github.getOctokit(github_token)
} catch (e: unknown) {
if (e instanceof Error) {
core.setFailed(
`Could not authenticate to GitHub Releases API with provided token\n${e.message}`
)
}
}
return octokit
}

export async function pull_releases(
octokit: InstanceType<typeof GitHub>
): Promise<NextflowRelease[]> {
const all_release_data: object[] = await all_nf_release_data(octokit)
const all_releases: NextflowRelease[] = []
for (const data of all_release_data) {
all_releases.push(nextflow_release(data))
}

return all_releases
}

export async function all_nf_release_data(
ok: InstanceType<typeof GitHub>
): Promise<object[]> {
return await ok.paginate(
ok.rest.repos.listReleases,
NEXTFLOW_REPO,
response => response.data
)
}

export async function latest_stable_release_data(
ok: InstanceType<typeof GitHub>
): Promise<object> {
const { data: stable_release } = await ok.rest.repos.getLatestRelease(
NEXTFLOW_REPO
)

return stable_release
}

export async function pull_latest_stable_release(
ok: InstanceType<typeof GitHub>
): Promise<NextflowRelease> {
const latest_release = await latest_stable_release_data(ok)
return nextflow_release(latest_release)
}
Loading