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

Changed default for azcliversion. #57

Merged
merged 6 commits into from
Nov 10, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Added restrictLatestToAgent
  • Loading branch information
t-dedah authored Nov 6, 2021
commit 1e8e2c2f0273bc558ba0d48a1a7cba957ab57ffd
6 changes: 3 additions & 3 deletions .github/workflows/ci-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
az account show
az storage -h
EXPECTED_TO: pass
run: ts-node test/main.test.ts
run: ts-node src/test/main.test.ts

- name: Azure CLI Version Test - Negative
env:
Expand All @@ -38,14 +38,14 @@ jobs:
az account show
az storage -h
EXPECTED_TO: fail
run: ts-node test/main.test.ts
run: ts-node src/test/main.test.ts

- name: Inline Script Test - Negative
env:
INPUT_AZCLIVERSION: 2.0.72
INPUT_INLINESCRIPT: " "
EXPECTED_TO: fail
run: ts-node test/main.test.ts
run: ts-node src/test/main.test.ts

- name: Post to slack on failure
if: failure()
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Read more about various Azure CLI versions [here](https://github.com/Azure/azure

- `azcliversion` – **Optional** Example: 2.0.72, Default: latest
- `inlineScript` – **Required**
- `restrictLatestToAgent` – **Optional** If set to true, latest will be restricted to az cli version available on the agent. This can be used as a safeguard againt any mismatch between az cli version on the agent and latest. Valid values are true and false. Default value set to false

Azure CLI GitHub Action is supported for the Azure public cloud as well as Azure government clouds ('AzureUSGovernment' or 'AzureChinaCloud') and Azure Stack ('AzureStack') Hub. Before running this action, login to the respective Azure Cloud using [Azure Login](https://github.com/Azure/login) by setting appropriate value for the `environment` parameter.

Expand Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ inputs:
description: 'Azure CLI version to be used to execute the script. If not provided, latest version is used'
required: false
default: 'latest'
restrictLatestToAgent:
description: 'If set to true, latest will be restricted to maximum version available on the agent. Valid values are true and false'
required: false
default: false
branding:
icon: 'login.svg'
color: 'blue'
Expand Down
157 changes: 157 additions & 0 deletions lib/src/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core"));
const exec = __importStar(require("@actions/exec"));
const io = __importStar(require("@actions/io"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const utils_1 = require("./utils");
const START_SCRIPT_EXECUTION_MARKER = `Starting script execution via docker image mcr.microsoft.com/azure-cli:`;
const BASH_ARG = `bash --noprofile --norc -e `;
const CONTAINER_WORKSPACE = '/github/workspace';
const CONTAINER_TEMP_DIRECTORY = '/_temp';
exports.run = () => __awaiter(void 0, void 0, void 0, function* () {
var scriptFileName = '';
const CONTAINER_NAME = `MICROSOFT_AZURE_CLI_${utils_1.getCurrentTime()}_CONTAINER`;
try {
if (process.env.RUNNER_OS != 'Linux') {
core.setFailed('Please use Linux based OS as a runner.');
return;
}
let inlineScript = core.getInput('inlineScript', { required: true });
let azcliversion = core.getInput('azcliversion', { required: true }).trim().toLowerCase();
if (!(yield checkIfValidCLIVersion(azcliversion))) {
core.setFailed('Please enter a valid azure cli version. \nSee available versions: https://github.com/Azure/azure-cli/releases.');
throw new Error('Please enter a valid azure cli version. \nSee available versions: https://github.com/Azure/azure-cli/releases.');
}
if (!inlineScript.trim()) {
core.setFailed('Please enter a valid script.');
throw new Error('Please enter a valid script.');
}
inlineScript = ` set -e >&2; echo '${START_SCRIPT_EXECUTION_MARKER}' >&2; ${inlineScript}`;
scriptFileName = yield utils_1.createScriptFile(inlineScript);
let startCommand = ` ${BASH_ARG}${CONTAINER_TEMP_DIRECTORY}/${scriptFileName} `;
let environmentVariables = '';
for (let key in process.env) {
// if (key.toUpperCase().startsWith("GITHUB_") && key.toUpperCase() !== 'GITHUB_WORKSPACE' && process.env[key]){
if (!utils_1.checkIfEnvironmentVariableIsOmitted(key) && process.env[key]) {
environmentVariables += ` -e "${key}=${process.env[key]}" `;
}
}
/*
For the docker run command, we are doing the following
- Set the working directory for docker continer
- volume mount the GITHUB_WORKSPACE env variable (path where users checkout code is present) to work directory of container
- voulme mount .azure session token file between host and container,
- volume mount temp directory between host and container, inline script file is created in temp directory
*/
let github_env_file_relative_path = path.relative(utils_1.TEMP_DIRECTORY, process.env.GITHUB_ENV);
;
const CONTAINER_GITHUB_ENV = path.resolve(CONTAINER_TEMP_DIRECTORY, github_env_file_relative_path);
let command = `run --workdir ${CONTAINER_WORKSPACE} -v ${process.env.GITHUB_WORKSPACE}:${CONTAINER_WORKSPACE} `;
command += ` -v ${process.env.HOME}/.azure:/root/.azure -v ${utils_1.TEMP_DIRECTORY}:${CONTAINER_TEMP_DIRECTORY} `;
command += ` ${environmentVariables} `;
command += `-e GITHUB_WORKSPACE=${CONTAINER_WORKSPACE} `;
command += `-e GITHUB_ENV=${CONTAINER_GITHUB_ENV} `;
command += `--name ${CONTAINER_NAME} `;
command += ` mcr.microsoft.com/azure-cli:${azcliversion} ${startCommand}`;
console.log(`${START_SCRIPT_EXECUTION_MARKER}${azcliversion}`);
yield executeDockerCommand(command);
console.log("az script ran successfully.");
}
catch (error) {
core.error(error);
core.setFailed(error.stderr);
throw error;
}
finally {
// clean up
const scriptFilePath = path.join(utils_1.TEMP_DIRECTORY, scriptFileName);
yield utils_1.deleteFile(scriptFilePath);
console.log("cleaning up container...");
yield executeDockerCommand(` container rm --force ${CONTAINER_NAME} `, true);
}
});
const checkIfValidCLIVersion = (azcliversion) => __awaiter(void 0, void 0, void 0, function* () {
const allVersions = yield getAllAzCliVersions();
if (!allVersions || allVersions.length == 0) {
return true;
}
return allVersions.some((eachVersion) => eachVersion.toLowerCase() === azcliversion);
});
const getAllAzCliVersions = () => __awaiter(void 0, void 0, void 0, function* () {
var outStream = '';
var execOptions = {
outStream: new utils_1.NullOutstreamStringWritable({ decodeStrings: false }),
listeners: {
stdout: (data) => outStream += data.toString() + os.EOL,
}
};
try {
yield exec.exec(`curl --location -s https://mcr.microsoft.com/v2/azure-cli/tags/list`, [], execOptions);
if (outStream && JSON.parse(outStream).tags) {
return JSON.parse(outStream).tags;
}
}
catch (error) {
// if output is 404 page not found, please verify the url
core.warning(`Unable to fetch all az cli versions, please report it as an issue on https://github.com/Azure/CLI/issues. Output: ${outStream}, Error: ${error}`);
}
return [];
});
const executeDockerCommand = (dockerCommand, continueOnError = false) => __awaiter(void 0, void 0, void 0, function* () {
const dockerTool = yield io.which("docker", true);
var errorStream = '';
var shouldOutputErrorStream = false;
var execOptions = {
outStream: new utils_1.NullOutstreamStringWritable({ decodeStrings: false }),
listeners: {
stdout: (data) => console.log(data.toString()),
errline: (data) => {
if (!shouldOutputErrorStream) {
errorStream += data + os.EOL;
}
else {
console.log(data);
}
if (data.trim() === START_SCRIPT_EXECUTION_MARKER) {
shouldOutputErrorStream = true;
errorStream = ''; // Flush the container logs. After this, script error logs will be tracked.
}
}
}
};
var exitCode;
try {
exitCode = yield exec.exec(`"${dockerTool}" ${dockerCommand}`, [], execOptions);
}
catch (error) {
if (!continueOnError) {
throw error;
}
core.warning(error);
}
finally {
if (exitCode !== 0 && !continueOnError) {
throw new Error(errorStream || 'az cli script failed.');
}
core.warning(errorStream);
}
});
exports.run();
104 changes: 104 additions & 0 deletions lib/src/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const stream = require("stream");
const exec = __importStar(require("@actions/exec"));
const core = __importStar(require("@actions/core"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const fs = __importStar(require("fs"));
exports.TEMP_DIRECTORY = process.env.RUNNER_TEMP || os.tmpdir();
exports.createScriptFile = (inlineScript) => __awaiter(void 0, void 0, void 0, function* () {
const fileName = `AZ_CLI_GITHUB_ACTION_${exports.getCurrentTime().toString()}.sh`;
const filePath = path.join(exports.TEMP_DIRECTORY, fileName);
fs.writeFileSync(filePath, `${inlineScript}`);
yield exports.giveExecutablePermissionsToFile(filePath);
return fileName;
});
exports.deleteFile = (filePath) => __awaiter(void 0, void 0, void 0, function* () {
if (fs.existsSync(filePath)) {
try {
fs.unlinkSync(filePath);
}
catch (err) {
core.warning(err.toString());
}
}
});
exports.giveExecutablePermissionsToFile = (filePath) => __awaiter(void 0, void 0, void 0, function* () { return yield exec.exec(`chmod +x ${filePath}`, [], { silent: true }); });
exports.getCurrentTime = () => {
return new Date().getTime();
};
class NullOutstreamStringWritable extends stream.Writable {
constructor(options) {
super(options);
}
_write(data, encoding, callback) {
if (callback) {
callback();
}
}
}
exports.NullOutstreamStringWritable = NullOutstreamStringWritable;
;
exports.checkIfEnvironmentVariableIsOmitted = (key) => {
const omitEnvironmentVariables = [
'LANG',
'HOSTNAME',
'PWD',
'HOME',
'PYTHON_VERSION',
'PYTHON_PIP_VERSION',
'SHLVL',
'PATH',
'GPG_KEY',
'CONDA',
'AGENT_TOOLSDIRECTORY',
'GITHUB_WORKSPACE',
'GITHUB_ENV',
'RUNNER_PERFLOG',
'RUNNER_WORKSPACE',
'RUNNER_TEMP',
'RUNNER_TRACKING_ID',
'RUNNER_TOOL_CACHE',
'DOTNET_SKIP_FIRST_TIME_EXPERIENCE',
'JOURNAL_STREAM',
'DEPLOYMENT_BASEPATH',
'VCPKG_INSTALLATION_ROOT',
'PERFLOG_LOCATION_SETTING'
];
const omitEnvironmentVariablesWithPrefix = [
'JAVA_',
'LEIN_',
'M2_',
'BOOST_',
'GOROOT',
'ANDROID_',
'GRADLE_',
'ANT_',
'CHROME',
'SELENIUM_',
'INPUT_'
];
for (let i = 0; i < omitEnvironmentVariables.length; i++) {
if (omitEnvironmentVariables[i] === key.toUpperCase()) {
return true;
}
}
return omitEnvironmentVariablesWithPrefix.some((prefix) => key.toUpperCase().startsWith(prefix));
};
28 changes: 28 additions & 0 deletions lib/test/main.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use strict";
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const main_1 = require("../src/main");
const core = __importStar(require("@actions/core"));
main_1.run()
.then(() => {
checkOutcome('pass');
})
.catch((e) => {
core.error(e);
checkOutcome('fail');
});
function checkOutcome(outcome) {
if (outcome != process.env.EXPECTED_TO) {
core.error(`Expected outcome did not meet the real outcome. Expected value: ${process.env.EXPECTED_TO}, actual value: ${outcome}`);
process.exit(1);
}
else {
process.exit(0);
}
}
28 changes: 27 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import * as exec from '@actions/exec';
import * as io from '@actions/io';
import * as os from 'os';
import * as path from 'path';
const util = require('util');
const cpExec = util.promisify(require('child_process').exec);

import { createScriptFile, TEMP_DIRECTORY, NullOutstreamStringWritable, deleteFile, getCurrentTime, checkIfEnvironmentVariableIsOmitted } from './utils';

Expand All @@ -23,7 +25,31 @@ export const run = async () => {

let inlineScript: string = core.getInput('inlineScript', { required: true });
let azcliversion: string = core.getInput('azcliversion', { required: true }).trim().toLowerCase();

let restrictLatestToAgentString: string = core.getInput('restrictLatestToAgent', { required: true });

// Temporary code, will update actions/core in future to get access to getBooleanInput
let restrictLatestToAgent = false
if (restrictLatestToAgentString == "true") {
restrictLatestToAgent = true
}
// Ends here

let agentAzCliVersion = azcliversion
if (restrictLatestToAgent && azcliversion == "latest") {
const { stdout, stderr } = await cpExec('az version');
if (!stderr) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stderr should never be used as an indicator of failure. See Azure/azure-cli#18372.

try {
agentAzCliVersion = JSON.parse(stdout)["azure-cli"]
}
catch (er) {
console.log('Failed to fetch az cli version from agent. Reverting back to azcliversion input.')
}
} else {
console.log('Failed to fetch az cli version from agent. Reverting back to azcliversion input.')
}
azcliversion = agentAzCliVersion
}

if (!(await checkIfValidCLIVersion(azcliversion))) {
core.setFailed('Please enter a valid azure cli version. \nSee available versions: https://github.com/Azure/azure-cli/releases.');
throw new Error('Please enter a valid azure cli version. \nSee available versions: https://github.com/Azure/azure-cli/releases.')
Expand Down
2 changes: 1 addition & 1 deletion test/main.test.ts → src/test/main.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { run } from "../src/main";
import { run } from "../main";
import * as core from '@actions/core';

run()
Expand Down