Skip to content
This repository was archived by the owner on Dec 11, 2020. It is now read-only.
Merged
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
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"import/prefer-default-export": 0,
"max-nested-callbacks": [1, 4],
"max-classes-per-file": [0],
"max-len": ["error", { "code": 170 }],
"no-alert": 2,
"no-caller": 2,
"no-console": 0,
Expand Down
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The OpenShift Extension for GitHub Actions gives you the ability to create workf

| Name | Requirement | Description |
| --------------------- | ----------- | ----------- |
| `version` | _optional_ | Default: "latest". Must be in form `version: 'latest'`; It accepts 3 different values: version number (such as 3.11.36), url where to download oc bundle (i.e https://mirror.openshift.com/pub/openshift-v3/clients/3.11.36/linux/oc.tar.gz) or latest (which will download the latest version available). N.B: By using the version number you have to make sure it exists in our Oc repo - v.3 (https://mirror.openshift.com/pub/openshift-v3/clients/) or v.4 (https://mirror.openshift.com/pub/openshift-v4/clients/oc/) |
| `version` | _optional_ | Default: "latest". Must be in form `version: 'latest'`; It accepts 3 different values: version number (such as 3.11.36), url where to download oc bundle (i.e https://mirror.openshift.com/pub/openshift-v3/clients/3.11.36/linux/oc.tar.gz) or latest (which will download the latest version available). Also look at <a href="#how-the-cache-works">How the cache works</a> N.B: By using the version number you have to make sure it exists in our Oc repo - v.3 (https://mirror.openshift.com/pub/openshift-v3/clients/) or v.4 (https://mirror.openshift.com/pub/openshift-v4/clients/oc/) |
| `openshift_server_url` | _required_ | The URL of the Openshift cluster. We suggest to use [secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets#creating-encrypted-secrets) to store Openshift URL. Must be in form `openshift_server_url: ${{ secrets.OPENSHIFT_SERVER_URL }}` |
| `parameters` | _required_ | JSON with values to connect to the Openshift cluster. We suggest to use secrets to store sensitive data. Must be in form `parameters: '{"apitoken": "${{ secrets.API_TOKEN }}", "acceptUntrustedCerts": "true"}'` [More Info](#openshift-authentication-methods-supported) |
| `cmd` | _required_ | One or more oc commands to be executed. |
Expand Down Expand Up @@ -98,6 +98,15 @@ jobs:
'new-project my-project'
```

<a id="how-the-cache-works"></a>
## How the cache works in OpenShift action

OpenShift action supports oc executable caching based by its version to avoid downloading the same executable over and over when running different pipelines.

The cache is only enabled when the version is in number format and clearly specified in the task (e.g 4.1, 3.1.28..). If the version will be defined as an URL or using the latest label (when wanting to use the latest oc version available) the extension will try to download the oc version requested without checking the cache.

The oc executable will be cached inside the `_work/_tool/oc` folder.

## Contributing

This is an open source project open to anyone. This project welcomes contributions and suggestions!
Expand Down
129 changes: 86 additions & 43 deletions lib/src/installer.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,87 +18,101 @@ const fs = require("mz/fs");
const io = require("@actions/io/lib/io");
const ioUtil = require("@actions/io/lib/io-util");
const path = require("path");
const semver = require("semver");
const tc = require("@actions/tool-cache");
const validUrl = require("valid-url");
const constants_1 = require("./constants");
const command_1 = require("./command");
class Installer {
static installOc(version, runnerOS, useLocalOc) {
static installOc(versionToUse, runnerOS, useLocalOc) {
return __awaiter(this, void 0, void 0, function* () {
if (useLocalOc) {
const localOcPath = yield Installer.getLocalOcPath(version);
const localOcPath = yield Installer.getLocalOcBinary(versionToUse);
if (localOcPath) {
return localOcPath;
}
}
if (!version) {
return Promise.reject(new Error('Invalid version input. Provide a valid version number or url where to download an oc bundle.'));
if (versionToUse.valid === false) {
return { found: false, reason: versionToUse.reason };
}
let url = '';
if (validUrl.isWebUri(version)) {
url = version;
// check if oc version requested exists in cache
let versionToCache;
if (versionToUse.type === 'number') {
versionToCache = Installer.versionToCache(versionToUse.value);
const toolCached = Installer.versionInCache(versionToCache, runnerOS);
if (toolCached.found) {
return toolCached;
}
}
else {
url = yield Installer.getOcBundleUrl(version, runnerOS);
const url = yield Installer.getOcURLToDownload(versionToUse, runnerOS);
if (!url) {
return { found: false, reason: 'Unable to determine URL where to download oc executable.' };
}
core.debug(`downloading: ${url}`);
const ocBinary = yield Installer.downloadAndExtract(url, runnerOS);
const ocBinary = yield Installer.downloadAndExtract(url, runnerOS, versionToCache);
return ocBinary;
});
}
static downloadAndExtract(url, runnerOS) {
static downloadAndExtract(url, runnerOS, versionToCache) {
return __awaiter(this, void 0, void 0, function* () {
if (!url) {
return Promise.reject(new Error('Unable to determine oc download URL.'));
return { found: false, reason: 'URL where to download oc is not valid.' };
}
let downloadDir = '';
const pathOcArchive = yield tc.downloadTool(url);
let ocBinary;
if (runnerOS === 'Windows') {
downloadDir = yield tc.extractZip(pathOcArchive);
ocBinary = path.join(downloadDir, 'oc.exe');
}
else {
downloadDir = yield tc.extractTar(pathOcArchive);
ocBinary = path.join(downloadDir, 'oc');
}
let ocBinary = Installer.ocBinaryByOS(runnerOS);
ocBinary = path.join(downloadDir, ocBinary);
if (!(yield ioUtil.exists(ocBinary))) {
return Promise.reject(new Error('Unable to download or extract oc binary.'));
return { found: false, reason: `An error occurred while downloading and extracting oc binary from ${url}.` };
}
fs.chmodSync(ocBinary, '0755');
return ocBinary;
if (versionToCache) {
yield tc.cacheFile(ocBinary, path.parse(ocBinary).base, 'oc', versionToCache);
}
return { found: true, path: ocBinary };
});
}
static getOcBundleUrl(version, runnerOS) {
static getOcURLToDownload(version, runnerOS) {
return __awaiter(this, void 0, void 0, function* () {
let url = '';
if (version === 'latest') {
if (!version.valid) {
return undefined;
}
if (version.type === 'url') {
return version.value;
}
if (version.type === 'latest') {
url = yield Installer.latest(runnerOS);
return url;
}
// determine the base_url based on version
const reg = new RegExp('\\d+(?=\\.)');
const vMajorRegEx = reg.exec(version);
const vMajorRegEx = reg.exec(version.value);
if (!vMajorRegEx || vMajorRegEx.length === 0) {
core.debug('Error retrieving version major');
return null;
return undefined;
}
const vMajor = +vMajorRegEx[0];
const ocUtils = yield Installer.getOcUtils();
if (vMajor === 3) {
url = `${ocUtils.openshiftV3BaseUrl}/${version}/`;
url = `${ocUtils.openshiftV3BaseUrl}/${version.value}/`;
}
else if (vMajor === 4) {
url = `${ocUtils.openshiftV4BaseUrl}/${version}/`;
url = `${ocUtils.openshiftV4BaseUrl}/${version.value}/`;
}
else {
core.debug('Invalid version');
return null;
return undefined;
}
const bundle = Installer.getOcBundleByOS(runnerOS);
if (!bundle) {
core.debug('Unable to find bundle url');
return null;
core.debug('Unable to find oc bundle url');
return undefined;
}
url += bundle;
core.debug(`archive URL: ${url}`);
Expand All @@ -109,7 +123,7 @@ class Installer {
return __awaiter(this, void 0, void 0, function* () {
const bundle = Installer.getOcBundleByOS(runnerOS);
if (!bundle) {
core.debug('Unable to find bundle url');
core.debug('Unable to find oc bundle url');
return null;
}
const ocUtils = yield Installer.getOcUtils();
Expand Down Expand Up @@ -141,32 +155,34 @@ class Installer {
return url;
}
/**
* Retrieve the path of the oc CLI installed in the machine.
* Retrieve the oc CLI installed in the machine.
*
* @param version the version of `oc` to be used. If not specified any `oc` version,
* if found, will be used.
* @return the full path to the installed executable or
* undefined if the oc CLI version requested is not found.
* @param version the version of `oc` to be used.
* If no version was specified, it uses the oc CLI found whatever its version.
* @return the installed executable
*/
static getLocalOcPath(version) {
static getLocalOcBinary(version) {
return __awaiter(this, void 0, void 0, function* () {
let ocBinaryStatus;
let ocPath;
try {
ocPath = yield io.which('oc', true);
ocBinaryStatus = { found: true, path: ocPath };
core.debug(`ocPath ${ocPath}`);
}
catch (ex) {
ocBinaryStatus = { found: false };
core.debug(`Oc has not been found on this machine. Err ${ex}`);
}
if (version && ocPath) {
// if user requested to use a specific version, we need to check that version is the one installed
if (version.valid && version.type === 'number' && ocPath) {
const localOcVersion = yield Installer.getOcVersion(ocPath);
core.debug(`localOcVersion ${localOcVersion} vs ${version}`);
if (!localOcVersion
|| localOcVersion.toLowerCase() !== version.toLowerCase()) {
return undefined;
core.debug(`localOcVersion ${localOcVersion} vs ${version.value}`);
if (!localOcVersion.valid || localOcVersion.value.toLowerCase() !== version.value.toLowerCase()) {
ocBinaryStatus = { found: false, reason: 'Oc installed has a different version of the one requested.' };
}
}
return ocPath;
return ocBinaryStatus;
});
}
static getOcVersion(ocPath) {
Expand All @@ -186,15 +202,15 @@ class Installer {
}
if (exitCode === 1) {
core.debug('error executing oc version');
return undefined;
return { valid: false, reason: `An error occured when retrieving version of oc CLI in ${ocPath}` };
}
core.debug(`stdout ${stdOut}`);
const regexVersion = new RegExp('v[0-9]+.[0-9]+.[0-9]+');
const versionObj = regexVersion.exec(stdOut);
if (versionObj && versionObj.length > 0) {
return versionObj[0];
return { valid: true, type: 'number', value: versionObj[0] };
}
return undefined;
return { valid: false, reason: `The version of oc CLI in ${ocPath} is in an unknown format.` };
});
}
static getOcUtils() {
Expand All @@ -204,5 +220,32 @@ class Installer {
return JSON.parse(rawData);
});
}
static versionToCache(version) {
const sanitizedVersion = semver.coerce(version);
if (!sanitizedVersion)
return undefined;
return sanitizedVersion.version;
}
/**
* Retrieve the version of oc CLI in cache
*
* @param version version to search in cache
* @param runnerOS the OS type. One of 'Linux', 'Darwin' or 'Windows_NT'.
*/
static versionInCache(version, runnerOS) {
let cachePath;
if (version) {
cachePath = tc.find('oc', version);
if (cachePath) {
return { found: true, path: path.join(cachePath, Installer.ocBinaryByOS(runnerOS)) };
}
}
return { found: false };
}
static ocBinaryByOS(osType) {
if (osType.includes('Windows'))
return 'oc.exe';
return 'oc';
}
}
exports.Installer = Installer;
12 changes: 7 additions & 5 deletions lib/src/ocExec.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const core = require("@actions/core");
const auth_1 = require("./auth");
const command_1 = require("./command");
const installer_1 = require("./installer");
const execHelper_1 = require("./utils/execHelper");
function run() {
return __awaiter(this, void 0, void 0, function* () {
const openShiftUrl = core.getInput('openshift_server_url');
Expand All @@ -33,15 +34,16 @@ function run() {
return Promise.reject(new Error('Invalid cmd input. Insert at least one command to be executed.'));
}
const cmds = args.split('\n');
const ocPath = yield installer_1.Installer.installOc(version, runnerOS, useLocalOc === 'true');
if (ocPath === null) {
return Promise.reject(new Error('no oc binary found'));
const binaryVersion = execHelper_1.convertStringToBinaryVersion(version);
const ocBinary = yield installer_1.Installer.installOc(binaryVersion, runnerOS, useLocalOc === 'true');
if (ocBinary.found === false) {
return Promise.reject(new Error(execHelper_1.getReason(ocBinary)));
}
const endpoint = auth_1.OcAuth.initOpenShiftEndpoint(openShiftUrl, parameters);
yield auth_1.OcAuth.loginOpenshift(endpoint, ocPath);
yield auth_1.OcAuth.loginOpenshift(endpoint, ocBinary.path);
for (const cmd of cmds) {
// eslint-disable-next-line no-await-in-loop
yield command_1.Command.execute(ocPath, cmd);
yield command_1.Command.execute(ocBinary.path, cmd);
}
});
}
Expand Down
29 changes: 29 additions & 0 deletions lib/src/utils/execHelper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/*-----------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See LICENSE file in the project root for license information.
*-----------------------------------------------------------------------------------------------*/
const validUrl = require("valid-url");
function convertStringToBinaryVersion(version) {
if (!version) {
return { valid: false, reason: 'The action was run without any version as input.' };
}
if (version === 'latest') {
return { valid: true, type: 'latest', value: version };
}
if (validUrl.isWebUri(version)) {
return { valid: true, type: 'url', value: version };
}
const regexVersion = new RegExp('[0-9]+[.]{1}[0-9]+[.]{0,1}[0-9]*');
const versionObj = regexVersion.exec(version);
if (versionObj && versionObj.length > 0) {
return { valid: true, type: 'number', value: version };
}
return { valid: false, reason: 'Version is written in an unknown format' };
}
exports.convertStringToBinaryVersion = convertStringToBinaryVersion;
function getReason(version) {
return version.reason ? version.reason : 'error';
}
exports.getReason = getReason;
41 changes: 41 additions & 0 deletions node_modules/semver/CHANGELOG.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading