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

feat: support private mirrors #1240

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,21 @@ See [action.yml](action.yml)
# Set always-auth option in npmrc file.
# Default: ''
always-auth: ''

# Optional mirror to download binaries from.
# Artifacts need to match the official Node.js
# Example:
# V8 Canaray Build: <mirror_url>/download/v8-canary
# RC Build: <mirror_url>/download/rc
# Official: Build <mirror_url>/dist
# Nightly build: <mirror_url>/download/nightly
# Default: ''
mirror: ''

# Optional mirror token.
# The token will be used as a bearer token in the Authorization header
# Default: ''
mirror-token: ''
```
<!-- end usage -->

Expand Down
64 changes: 64 additions & 0 deletions __tests__/canary-installer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,70 @@ describe('setup-node', () => {
);
}
);

it.each([
[
'20.0.0-v8-canary',
'20.0.0-v8-canary20221103f7e2421e91',
'20.0.0-v8-canary20221030fefe1c0879',
'https://my_mirror.org/download/v8-canary/v20.0.0-v8-canary20221103f7e2421e91/node-v20.0.0-v8-canary20221103f7e2421e91-linux-x64.tar.gz'
],
[
'20-v8-canary',
'20.0.0-v8-canary20221103f7e2421e91',
'20.0.0-v8-canary20221030fefe1c0879',
'https://my_mirror.org/download/v8-canary/v20.0.0-v8-canary20221103f7e2421e91/node-v20.0.0-v8-canary20221103f7e2421e91-linux-x64.tar.gz'
],
[
'19.0.0-v8-canary',
'19.0.0-v8-canary202210187d6960f23f',
'19.0.0-v8-canary202210172ec229fc56',
'https://my_mirror.org/download/v8-canary/v19.0.0-v8-canary202210187d6960f23f/node-v19.0.0-v8-canary202210187d6960f23f-linux-x64.tar.gz'
],
[
'19-v8-canary',
'19.0.0-v8-canary202210187d6960f23f',
'19.0.0-v8-canary202210172ec229fc56',
'https://my_mirror.org/download/v8-canary/v19.0.0-v8-canary202210187d6960f23f/node-v19.0.0-v8-canary202210187d6960f23f-linux-x64.tar.gz'
]
])(
'get %s version from dist if check-latest is true',
async (input, expectedVersion, foundVersion, expectedUrl) => {
const foundToolPath = path.normalize(`/cache/node/${foundVersion}/x64`);
const toolPath = path.normalize(`/cache/node/${expectedVersion}/x64`);

inputs['node-version'] = input;
inputs['check-latest'] = 'true';
os['arch'] = 'x64';
os['platform'] = 'linux';
inputs['mirror'] = 'https://my_mirror.org';
inputs['mirror-token'] = 'faketoken';

findSpy.mockReturnValue(foundToolPath);
findAllVersionsSpy.mockReturnValue([
'20.0.0-v8-canary20221030fefe1c0879',
'19.0.0-v8-canary202210172ec229fc56',
'20.0.0-v8-canary2022102310ff1e5a8d'
]);
dlSpy.mockImplementation(async () => '/some/temp/path');
exSpy.mockImplementation(async () => '/some/other/temp/path');
cacheSpy.mockImplementation(async () => toolPath);

// act
await main.run();

// assert
expect(findAllVersionsSpy).toHaveBeenCalled();
expect(logSpy).toHaveBeenCalledWith(
`Acquiring ${expectedVersion} - ${os.arch} from ${expectedUrl}`
);
expect(logSpy).toHaveBeenCalledWith('Extracting ...');
expect(logSpy).toHaveBeenCalledWith('Adding to the cache ...');
expect(cnSpy).toHaveBeenCalledWith(
`::add-path::${path.join(toolPath, 'bin')}${osm.EOL}`
);
}
);
});

describe('setup-node v8 canary tests', () => {
Expand Down
50 changes: 49 additions & 1 deletion __tests__/nightly-installer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ describe('setup-node', () => {
await main.run();

workingUrls.forEach(url => {
expect(dlSpy).toHaveBeenCalledWith(url);
expect(dlSpy).toHaveBeenCalledWith(url, undefined, undefined);
});
expect(cnSpy).toHaveBeenCalledWith(`::add-path::${toolPath}${osm.EOL}`);
});
Expand Down Expand Up @@ -449,6 +449,54 @@ describe('setup-node', () => {
}
}, 100000);

it('acquires specified architecture of node from mirror', async () => {
for (const {arch, version, osSpec} of [
{
arch: 'x86',
version: '18.0.0-nightly202110204cb3e06ed8',
osSpec: 'win32'
},
{
arch: 'x86',
version: '20.0.0-nightly2022101987cdf7d412',
osSpec: 'win32'
}
]) {
os.platform = osSpec;
os.arch = arch;
const fileExtension = os.platform === 'win32' ? '7z' : 'tar.gz';
const platform = {
linux: 'linux',
darwin: 'darwin',
win32: 'win'
}[os.platform];

inputs['node-version'] = version;
inputs['architecture'] = arch;
inputs['always-auth'] = false;
inputs['token'] = 'faketoken';
inputs['mirror'] = 'https://my-mirror.org';
inputs['mirror-token'] = 'my-mirror-token';

const expectedUrl = `https://my-mirror.org/download/nightly/v${version}/node-v${version}-${platform}-${arch}.${fileExtension}`;

// ... but not in the local cache
findSpy.mockImplementation(() => '');
findAllVersionsSpy.mockImplementation(() => []);

dlSpy.mockImplementation(async () => '/some/temp/path');
const toolPath = path.normalize(`/cache/node/${version}/${arch}`);
exSpy.mockImplementation(async () => '/some/other/temp/path');
cacheSpy.mockImplementation(async () => toolPath);

await main.run();
expect(dlSpy).toHaveBeenCalled();
expect(logSpy).toHaveBeenCalledWith(
`Acquiring ${version} - ${arch} from ${expectedUrl}`
);
}
}, 100000);

describe('nightly versions', () => {
it.each([
[
Expand Down
42 changes: 42 additions & 0 deletions __tests__/official-installer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -828,4 +828,46 @@ describe('setup-node', () => {
}
);
});

it('acquires specified architecture of node from mirror', async () => {
for (const {arch, version, osSpec} of [
{arch: 'x86', version: '12.16.2', osSpec: 'win32'},
{arch: 'x86', version: '14.0.0', osSpec: 'win32'}
]) {
os.platform = osSpec;
os.arch = arch;
const fileExtension = os.platform === 'win32' ? '7z' : 'tar.gz';
const platform = {
linux: 'linux',
darwin: 'darwin',
win32: 'win'
}[os.platform];

inputs['node-version'] = version;
inputs['architecture'] = arch;
inputs['always-auth'] = false;
inputs['token'] = 'faketoken';
inputs['mirror'] = 'https://my_mirror_url';
inputs['mirror-token'] = 'faketoken';

const expectedUrl =
arch === 'x64'
? `https://github.com/actions/node-versions/releases/download/${version}/node-${version}-${platform}-${arch}.zip`
: `https://my_mirror_url/dist/v${version}/node-v${version}-${platform}-${arch}.${fileExtension}`;

// ... but not in the local cache
findSpy.mockImplementation(() => '');

dlSpy.mockImplementation(async () => '/some/temp/path');
const toolPath = path.normalize(`/cache/node/${version}/${arch}`);
exSpy.mockImplementation(async () => '/some/other/temp/path');
cacheSpy.mockImplementation(async () => toolPath);

await main.run();
expect(dlSpy).toHaveBeenCalled();
expect(logSpy).toHaveBeenCalledWith(
`Acquiring ${version} - ${arch} from ${expectedUrl}`
);
}
}, 100000);
});
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ inputs:
description: 'Used to specify a package manager for caching in the default directory. Supported values: npm, yarn, pnpm.'
cache-dependency-path:
description: 'Used to specify the path to a dependency file: package-lock.json, yarn.lock, etc. Supports wildcards or a list of file names for caching multiple dependencies.'
mirror:
description: 'Used to specify an alternative mirror to downlooad Node.js binaries from'
mirror-token:
description: 'The token used as Authorization header when fetching from the mirror'
# TODO: add input to control forcing to pull from cloud or dist.
# escape valve for someone having issues or needing the absolute latest which isn't cached yet
outputs:
Expand Down
52 changes: 32 additions & 20 deletions dist/setup/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -100148,9 +100148,13 @@ class BaseDistribution {
}
getNodeJsVersions() {
return __awaiter(this, void 0, void 0, function* () {
const initialUrl = this.getDistributionUrl();
const initialUrl = this.getDistributionUrl(this.nodeInfo.mirror);
const dataUrl = `${initialUrl}/index.json`;
const response = yield this.httpClient.getJson(dataUrl);
const headers = {};
if (this.nodeInfo.mirrorToken) {
headers['Authorization'] = `Bearer ${this.nodeInfo.mirrorToken}`;
}
const response = yield this.httpClient.getJson(dataUrl, headers);
return response.result || [];
});
}
Expand All @@ -100165,7 +100169,7 @@ class BaseDistribution {
? `${fileName}.zip`
: `${fileName}.7z`
: `${fileName}.tar.gz`;
const initialUrl = this.getDistributionUrl();
const initialUrl = this.getDistributionUrl(this.nodeInfo.mirror);
const url = `${initialUrl}/v${version}/${urlFileName}`;
return {
downloadUrl: url,
Expand All @@ -100179,7 +100183,7 @@ class BaseDistribution {
let downloadPath = '';
core.info(`Acquiring ${info.resolvedVersion} - ${info.arch} from ${info.downloadUrl}`);
try {
downloadPath = yield tc.downloadTool(info.downloadUrl);
downloadPath = yield tc.downloadTool(info.downloadUrl, undefined, this.nodeInfo.mirrorToken);
}
catch (err) {
if (err instanceof tc.HTTPError &&
Expand All @@ -100203,7 +100207,7 @@ class BaseDistribution {
}
acquireWindowsNodeFromFallbackLocation(version_1) {
return __awaiter(this, arguments, void 0, function* (version, arch = os_1.default.arch()) {
const initialUrl = this.getDistributionUrl();
const initialUrl = this.getDistributionUrl(this.nodeInfo.mirror);
const osArch = this.translateArchToDistUrl(arch);
// Create temporary folder to download to
const tempDownloadFolder = `temp_${(0, uuid_1.v4)()}`;
Expand All @@ -100217,18 +100221,18 @@ class BaseDistribution {
exeUrl = `${initialUrl}/v${version}/win-${osArch}/node.exe`;
libUrl = `${initialUrl}/v${version}/win-${osArch}/node.lib`;
core.info(`Downloading only node binary from ${exeUrl}`);
const exePath = yield tc.downloadTool(exeUrl);
const exePath = yield tc.downloadTool(exeUrl, undefined, this.nodeInfo.mirrorToken);
yield io.cp(exePath, path.join(tempDir, 'node.exe'));
const libPath = yield tc.downloadTool(libUrl);
const libPath = yield tc.downloadTool(libUrl, undefined, this.nodeInfo.mirrorToken);
yield io.cp(libPath, path.join(tempDir, 'node.lib'));
}
catch (err) {
if (err instanceof tc.HTTPError && err.httpStatusCode == 404) {
exeUrl = `${initialUrl}/v${version}/node.exe`;
libUrl = `${initialUrl}/v${version}/node.lib`;
const exePath = yield tc.downloadTool(exeUrl);
const exePath = yield tc.downloadTool(exeUrl, undefined, this.nodeInfo.mirrorToken);
yield io.cp(exePath, path.join(tempDir, 'node.exe'));
const libPath = yield tc.downloadTool(libUrl);
const libPath = yield tc.downloadTool(libUrl, undefined, this.nodeInfo.mirrorToken);
yield io.cp(libPath, path.join(tempDir, 'node.lib'));
}
else {
Expand Down Expand Up @@ -100391,8 +100395,9 @@ class NightlyNodejs extends base_distribution_prerelease_1.default {
super(nodeInfo);
this.distribution = 'nightly';
}
getDistributionUrl() {
return 'https://nodejs.org/download/nightly';
getDistributionUrl(mirror) {
const url = mirror || 'https://nodejs.org';
return `${url}/download/nightly`;
}
}
exports["default"] = NightlyNodejs;
Expand Down Expand Up @@ -100490,7 +100495,7 @@ class OfficialBuilds extends base_distribution_1.default {
const versionInfo = yield this.getInfoFromManifest(this.nodeInfo.versionSpec, this.nodeInfo.stable, osArch, manifest);
if (versionInfo) {
core.info(`Acquiring ${versionInfo.resolvedVersion} - ${versionInfo.arch} from ${versionInfo.downloadUrl}`);
downloadPath = yield tc.downloadTool(versionInfo.downloadUrl, undefined, this.nodeInfo.auth);
downloadPath = yield tc.downloadTool(versionInfo.downloadUrl, undefined, this.nodeInfo.mirror ? this.nodeInfo.mirrorToken : this.nodeInfo.auth);
if (downloadPath) {
toolPath = yield this.extractArchive(downloadPath, versionInfo, false);
}
Expand Down Expand Up @@ -100558,12 +100563,13 @@ class OfficialBuilds extends base_distribution_1.default {
version = super.evaluateVersions(versions);
return version;
}
getDistributionUrl() {
return `https://nodejs.org/dist`;
getDistributionUrl(mirror) {
const url = mirror || 'https://nodejs.org';
return `${url}/dist`;
}
getManifest() {
core.debug('Getting manifest from actions/node-versions@main');
return tc.getManifestFromRepo('actions', 'node-versions', this.nodeInfo.auth, 'main');
return tc.getManifestFromRepo('actions', 'node-versions', this.nodeInfo.mirror ? this.nodeInfo.mirrorToken : this.nodeInfo.auth, 'main');
}
resolveLtsAliasFromManifest(versionSpec, stable, manifest) {
var _a;
Expand Down Expand Up @@ -100646,8 +100652,9 @@ class RcBuild extends base_distribution_1.default {
constructor(nodeInfo) {
super(nodeInfo);
}
getDistributionUrl() {
return 'https://nodejs.org/download/rc';
getDistributionUrl(mirror) {
const url = mirror || 'https://nodejs.org';
return `${url}/download/rc`;
}
}
exports["default"] = RcBuild;
Expand All @@ -100670,8 +100677,9 @@ class CanaryBuild extends base_distribution_prerelease_1.default {
super(nodeInfo);
this.distribution = 'v8-canary';
}
getDistributionUrl() {
return 'https://nodejs.org/download/v8-canary';
getDistributionUrl(mirror) {
const url = mirror || 'https://nodejs.org';
return `${url}/download/v8-canary`;
}
}
exports["default"] = CanaryBuild;
Expand Down Expand Up @@ -100751,14 +100759,18 @@ function run() {
if (version) {
const token = core.getInput('token');
const auth = !token ? undefined : `token ${token}`;
const mirror = core.getInput('mirror');
const mirrorToken = core.getInput('mirror-token');
const stable = (core.getInput('stable') || 'true').toUpperCase() === 'TRUE';
const checkLatest = (core.getInput('check-latest') || 'false').toUpperCase() === 'TRUE';
const nodejsInfo = {
versionSpec: version,
checkLatest,
auth,
stable,
arch
arch,
mirror,
mirrorToken
};
const nodeDistribution = (0, installer_factory_1.getNodejsDistribution)(nodejsInfo);
yield nodeDistribution.setupNodeJs();
Expand Down
15 changes: 15 additions & 0 deletions docs/advanced-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,3 +418,18 @@ Please refer to the [Ensuring workflow access to your package - Configuring a pa

### always-auth input
The always-auth input sets `always-auth=true` in .npmrc file. With this option set [npm](https://docs.npmjs.com/cli/v6/using-npm/config#always-auth)/yarn sends the authentication credentials when making a request to the registries.

## Use private mirror

It is possible to use a private mirror hosting Node.js binaries. This mirror must be a full mirror of the official Node.js distribution.
The mirror URL can be set using the `mirror` input.
It is possible to specify a token to authenticate with the mirror using the `mirror-token` input.
The token will be passed as a bearer token in the `Authorization` header.

```yaml
- uses: actions/setup-node@v4
with:
node-version: '14.x'
mirror: 'https://nodejs.org/dist'
mirror-token: 'your-mirror-token'
```
Loading
Loading