src/release.ts (141 lines of code) (raw):
import * as core from '@actions/core';
import * as tc from '@actions/tool-cache';
import * as fs from 'fs';
import path from 'path';
import {
OctokitClientOptions,
isLatestVersion,
releaseArtifactURL,
resolveLatestVersion,
} from './gh';
import { sha256File } from './hash';
const TOOL_NAME = 'kubelogin';
export type Platform =
| 'darwin-amd64'
| 'darwin-arm64'
| 'linux-amd64'
| 'linux-arm64'
| 'win-amd64';
function resolvePlatform(): Platform {
const platform = process.platform;
const arch = process.arch;
if (platform === 'darwin' && arch === 'x64') {
return 'darwin-amd64';
} else if (platform === 'darwin' && arch === 'arm64') {
return 'darwin-arm64';
} else if (platform === 'linux' && arch === 'x64') {
return 'linux-amd64';
} else if (platform === 'linux' && arch === 'arm64') {
return 'linux-arm64';
} else if (platform === 'win32' && arch === 'x64') {
return 'win-amd64';
} else {
throw new Error(`Unsupported platform: ${platform}-${arch}`);
}
}
export interface KubeloginArtifact {
// version is the SemVer of the release.
readonly version: string;
// platform is the platform of the artifact.
readonly platform: Platform;
// artifactName is the name of the artifact.
readonly artifactName: string;
// artifactUrl is the URL of the artifact.
readonly artifactUrl: string;
// checksumUrl is the SHA256 checksum URL of the artifact.
readonly checksumUrl: string;
}
export type GetReleaseArtifactOpts = {
platform?: Platform;
octokitClientOptions?: OctokitClientOptions;
};
// getReleaseArtifact retrieves a release artifact with specified version and platform.
// platform is resolved automatically if not specified.
export async function getReleaseArtifact(
version: string,
opts?: GetReleaseArtifactOpts
): Promise<KubeloginArtifact> {
if (isLatestVersion(version)) {
version = await resolveLatestVersion(opts?.octokitClientOptions);
}
const platform = opts?.platform || resolvePlatform();
const artifactName = `kubelogin-${platform}.zip`;
return {
version,
platform,
artifactName,
// NOTE: we construct the URL by convention. If the release artifacts change in kubelogin side,
// we need to update this function.
artifactUrl: releaseArtifactURL([version, artifactName]),
checksumUrl: releaseArtifactURL([version, `${artifactName}.sha256`]),
};
}
function resolveBinaryPath(artifact: KubeloginArtifact, dir: string): string {
if (artifact.platform.startsWith('win')) {
// windows has a different story :)
// ex: bin/windows_amd64/kubelogin.exe
const normalizedPlatform = artifact.platform.replace('win-', 'windows_');
return path.join(dir, 'bin', normalizedPlatform, 'kubelogin.exe');
} else {
// ex: bin/linux_amd64/kubelogin
const normalizedPlatform = artifact.platform.replace('-', '_');
return path.join(dir, 'bin', normalizedPlatform, 'kubelogin');
}
}
async function verifyZipballChecksum(
zipballPath: string,
artifactName: string,
checksumPath: string
) {
const zipballChecksum = await sha256File(zipballPath);
core.debug(
`calculated sha256 checksum of ${artifactName}: ${zipballChecksum}`
);
// format:
// {checksum} {filename}
const checksumLines = fs
.readFileSync(checksumPath, 'utf8')
.toString()
.split('\n')
.map((l) => l.split(/\s+/));
const expectedChecksum = checksumLines.find(
(l) => l[1].trim() === artifactName
);
if (!expectedChecksum) {
throw new Error(
`No checksum found for ${artifactName} from ${checksumPath}`
);
}
if (expectedChecksum[0] !== zipballChecksum) {
throw new Error(
`Checksum mismatch: expected ${expectedChecksum[0]}, got ${zipballChecksum}`
);
}
}
// downloadAndCache downloads the artifact and caches it.
// Returns the path to the cached artifact.
async function downloadAndCache(artifact: KubeloginArtifact): Promise<string> {
const artifactZipball = await tc.downloadTool(artifact.artifactUrl);
core.debug(`Downloaded ${artifact.artifactUrl} to ${artifactZipball}`);
const artifactChecksum = await tc.downloadTool(artifact.checksumUrl);
core.debug(`Downloaded ${artifact.checksumUrl} to ${artifactChecksum}`);
await verifyZipballChecksum(
artifactZipball,
artifact.artifactName,
artifactChecksum
);
core.debug(`Verified checksum of ${artifactZipball}`);
const artifactFolder = await tc.extractZip(artifactZipball);
core.debug(`Extracted ${artifactZipball} to ${artifactFolder}`);
const cachedDir = await tc.cacheDir(
artifactFolder,
TOOL_NAME,
artifact.version
);
const rv = resolveBinaryPath(artifact, cachedDir);
core.debug(`Cached kubelogin to ${rv}`);
return rv;
}
// setupArtifact prepares the kubelogin binary and add it to PATH.
export async function setupArtifact(
artifact: KubeloginArtifact,
skipCache?: boolean
) {
let cachedDir = '';
if (!skipCache) {
cachedDir = tc.find(TOOL_NAME, artifact.version);
}
let binaryPath: string;
if (cachedDir) {
binaryPath = resolveBinaryPath(artifact, cachedDir);
core.debug(`Found cached kubelogin at ${binaryPath}`);
} else {
binaryPath = await downloadAndCache(artifact);
core.debug(`Downloaded and cached kubelogin to ${binaryPath}`);
}
core.addPath(path.dirname(binaryPath));
core.info(`Added ${binaryPath} to PATH`);
}