support MacOS and self-hosted runner (#91)

This commit is contained in:
Yan Xu 2023-11-17 17:24:21 +08:00 коммит произвёл GitHub
Родитель 1dcd677e09
Коммит 7f8525f92c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 67 добавлений и 72 удалений

Просмотреть файл

@ -56,37 +56,6 @@ describe("Testing AzModuleInstaller", () => {
expect(spyTryInstallingLatest).toBeCalledTimes(1);
expect(mockTryInstalledTrue).toBeCalledTimes(4);
});
test("install with version 1.1.1 available as folder", async () => {
mockPathExists.mockImplementation((path) => path === "C:\\Modules\\az_1.1.1");
const installer = new AzModuleInstaller("1.1.1");
const spyTryInstallingLatest = jest.spyOn(<any>installer, "tryInstallingLatest");
const spyTryInstallFromFolder = jest.spyOn(<any>installer, "tryInstallFromFolder");
const mockTryInstalledTrue = jest.fn(async () => expect(installer["installResult"]["isInstalled"]).toBeTruthy());
installer["tryInstallFromZip"] = mockTryInstalledTrue;
installer["tryInstallFromGHRelease"] = mockTryInstalledTrue;
installer["tryInstallFromPSGallery"] = mockTryInstalledTrue;
const result = await installer.install();
expect(result).toEqual({ isInstalled: true, moduleSource: "hostedAgentFolder" });
expect(spyTryInstallingLatest).toBeCalledTimes(1);
expect(spyTryInstallFromFolder).toBeCalledTimes(1);
expect(mockTryInstalledTrue).toBeCalledTimes(3);
});
test("install with version 1.1.1 available as zip", async () => {
mockPathExists.mockImplementation((path) => path === "C:\\Modules\\az_1.1.1.zip");
const installer = new AzModuleInstaller("1.1.1");
const spyTryInstallingLatest = jest.spyOn(<any>installer, "tryInstallingLatest");
const spyTryInstallFromFolder = jest.spyOn(<any>installer, "tryInstallFromFolder");
const spyTryInstallFromZip = jest.spyOn(<any>installer, "tryInstallFromZip");
const mockTryInstalledTrue = jest.fn(async () => expect(installer["installResult"]["isInstalled"]).toBeTruthy());
installer["tryInstallFromGHRelease"] = mockTryInstalledTrue;
installer["tryInstallFromPSGallery"] = mockTryInstalledTrue;
const result = await installer.install();
expect(result).toEqual({ isInstalled: true, moduleSource: "hostedAgentZip" });
expect(spyTryInstallingLatest).toBeCalledTimes(1);
expect(spyTryInstallFromFolder).toBeCalledTimes(1);
expect(spyTryInstallFromZip).toBeCalledTimes(1);
expect(mockTryInstalledTrue).toBeCalledTimes(2);
});
test("install with version 1.1.1 from GHRelease", async () => {
const installer = new AzModuleInstaller("1.1.1");
installer["getDownloadUrlFromGHRelease"] = jest.fn().mockReturnValue("downloadUrl");

Просмотреть файл

@ -42,22 +42,17 @@ describe('Testing setPSModulePath', () => {
process.env.RUNNER_OS = savedRunnerOS;
});
test('PSModulePath with azPSVersion non-empty', () => {
test('PSModulePath with azPSVersion non-empty', async () => {
process.env.RUNNER_OS = "Windows";
Utils.setPSModulePath(version);
await Utils.setPSModulePath(version);
expect(process.env.PSModulePath).toContain(version);
});
test('PSModulePath with azPSVersion empty', () => {
test('PSModulePath with azPSVersion empty', async () => {
process.env.RUNNER_OS = "Linux";
const prevPSModulePath = process.env.PSModulePath;
Utils.setPSModulePath();
await Utils.setPSModulePath();
expect(process.env.PSModulePath).not.toEqual(prevPSModulePath);
});
test('setPSModulePath should throw for MacOS', () => {
process.env.RUNNER_OS = "Darwin";
expect(() => Utils.setPSModulePath()).toThrow();
expect(() => Utils.setPSModulePath(version)).toThrow();
});
});
describe('Testing getLatestModule', () => {
@ -93,7 +88,6 @@ describe('Testing isHostedAgent', () => {
test('Should return true when file layout check script returns true', async () => {
mockExecutePowerShellCommandOutput = "True";
const isHostedAgentResult = await Utils.isHostedAgent("/usr/share");
expect(mockPowerShellToolRunnerInit).toHaveBeenCalledTimes(1);
expect(mockExecutePowerShellCommand).toHaveBeenCalledTimes(1);
expect(mockExecutePowerShellCommand.mock.calls[0][0]).toBe('Test-Path (Join-Path "/usr/share" "az_*")');
expect(isHostedAgentResult).toBeTruthy();
@ -136,7 +130,6 @@ describe('Testing saveAzModule', () => {
test('Should run without throwing when script succeeds with exit code 0', async () => {
mockExecutePowerShellScriptBlockExitCode = 0;
await Utils.saveAzModule("1.1.1", "/usr/share/az_1.1.1");
expect(mockPowerShellToolRunnerInit).toHaveBeenCalledTimes(1);
expect(mockExecutePowerShellScriptBlock).toHaveBeenCalledTimes(1);
expect(mockExecutePowerShellScriptBlock.mock.calls[0][0]).toContain(
"Save-Module -Path /usr/share/az_1.1.1 -Name Az -RequiredVersion 1.1.1 -Force -ErrorAction Stop");

Просмотреть файл

@ -4,6 +4,8 @@ import * as os from 'os';
import { ArchiveTools } from './Utilities/ArchiveTools';
import FileUtils from './Utilities/FileUtils';
import Utils from './Utilities/Utils';
import path from 'path';
import Constants from './Constants';
export interface InstallResult {
moduleSource: string;
@ -36,20 +38,11 @@ export class AzModuleInstaller {
};
const platform = (process.env.RUNNER_OS || os.type())?.toLowerCase();
core.debug(`Platform: ${platform}`);
switch(platform) {
case "windows":
case "windows_nt":
this.isWin = true;
this.moduleRoot = "C:\\Modules";
this.modulePath = `${this.moduleRoot}\\az_${this.version}`
break;
case "linux":
this.moduleRoot = "/usr/share";
this.modulePath = `${this.moduleRoot}/az_${this.version}`
break;
default:
throw `OS ${platform} not supported`;
this.moduleRoot = Utils.getDefaultAzInstallFolder(platform);
if(platform == "windows" || platform == "windows_nt"){
this.isWin = true;
}
this.modulePath = path.join(this.moduleRoot, `${Constants.prefix}${this.version}`);
this.moduleZipPath = `${this.modulePath}.zip`;
}
@ -137,7 +130,7 @@ export class AzModuleInstaller {
};
} catch (err) {
core.debug(err);
console.log("Download from GHRelease failed, will fallback to PSGallery");
core.info("Download from GHRelease failed, will fallback to PSGallery");
}
}

Просмотреть файл

@ -5,13 +5,13 @@ import Constants from "./Constants";
export default class InitializeAzure {
static async importAzModule(azPSVersion: string) {
Utils.setPSModulePath();
await Utils.setPSModulePath();
if (azPSVersion === "latest") {
azPSVersion = await Utils.getLatestModule(Constants.moduleName);
} else {
await Utils.checkModuleVersion(Constants.moduleName, azPSVersion);
}
core.debug(`Az Module version used: ${azPSVersion}`);
Utils.setPSModulePath(`${Constants.prefix}${azPSVersion}`);
await Utils.setPSModulePath(`${Constants.prefix}${azPSVersion}`);
}
}

Просмотреть файл

@ -38,7 +38,6 @@ export default class ScriptRunner {
this.inlineScript, this.errorActionPreference);
ScriptRunner.filePath = await FileUtils.createScriptFile(scriptToExecute);
core.debug(`script file to run: ${ScriptRunner.filePath}`);
await PowerShellToolRunner.init();
const exitCode: number = await PowerShellToolRunner.executePowerShellScriptBlock(ScriptRunner.filePath, options);
if (exitCode !== 0) {
core.setOutput(`Azure PowerShell exited with code:`, exitCode.toString());

Просмотреть файл

@ -34,7 +34,6 @@ export class ArchiveTools {
$ProgressPreference = 'SilentlyContinue'
Expand-Archive -Path ${zipPath} -DestinationPath ${destination}
$ProgressPreference = $prevProgressPref`;
await PowerShellToolRunner.init();
const exitCode = await PowerShellToolRunner.executePowerShellScriptBlock(script);
if (exitCode != 0) {
throw new Error(`Extraction using Expand-Archive cmdlet failed from ${zipPath} to ${destination}`);

Просмотреть файл

@ -11,10 +11,12 @@ export default class PowerShellToolRunner {
}
static async executePowerShellCommand(command: string, options: any = {}) {
await PowerShellToolRunner.init();
await exec.exec(`"${PowerShellToolRunner.psPath}" -NoLogo -NoProfile -NonInteractive -Command ${command}`, [], options);
}
static async executePowerShellScriptBlock(scriptBlock: string, options: any = {}): Promise<number> {
await PowerShellToolRunner.init();
const exitCode: number = await exec.exec(`"${PowerShellToolRunner.psPath}" -NoLogo -NoProfile -NonInteractive -Command`,
[scriptBlock], options);
return exitCode;

Просмотреть файл

@ -3,6 +3,8 @@ import * as os from 'os';
import Constants from '../Constants';
import PowerShellToolRunner from '../Utilities/PowerShellToolRunner';
import ScriptBuilder from './ScriptBuilder';
import path from 'path';
import fs from 'fs';
export default class Utils {
/**
@ -11,24 +13,66 @@ export default class Utils {
* If azPSVersion is empty, folder path in which all Az modules are present are set
* If azPSVersion is not empty, folder path of exact Az module version is set
*/
static setPSModulePath(azPSVersion: string = "") {
let modulePath: string = "";
static async setPSModulePath(azPSVersion: string = "") {
let output: string = "";
const options: any = {
listeners: {
stdout: (data: Buffer) => {
output += data.toString();
}
}
};
await PowerShellToolRunner.executePowerShellScriptBlock("$env:PSModulePath", options);
const defaultPSModulePath = output.trim();
const runner: string = process.env.RUNNER_OS || os.type();
switch (runner.toLowerCase()) {
let defaultAzInstallFolder:string = Utils.getDefaultAzInstallFolder(runner.toLowerCase());
let modulePath: string = path.join(defaultAzInstallFolder, `${azPSVersion}`);
process.env.PSModulePath = `${modulePath}${path.delimiter}${defaultPSModulePath}`;
}
static getDefaultAzInstallFolder(os:string): string{
let defaultAzInstallFolder = "";
switch (os) {
case "linux":
modulePath = `/usr/share/${azPSVersion}:`;
defaultAzInstallFolder = "/usr/share";
break;
case "windows":
case "windows_nt":
modulePath = `C:\\Modules\\${azPSVersion};`;
defaultAzInstallFolder = "C:\\Modules";
break;
case "macos":
case "darwin":
throw new Error(`OS not supported`);
default:
throw new Error(`Unknown os: ${runner.toLowerCase()}`);
defaultAzInstallFolder = "";
break;
}
process.env.PSModulePath = `${modulePath}${process.env.PSModulePath}`;
if(Utils.isFolderExistAndWritable(defaultAzInstallFolder)){
return defaultAzInstallFolder;
}
if(Utils.isFolderExistAndWritable(process.env.RUNNER_TOOL_CACHE)){
return process.env.RUNNER_TOOL_CACHE;
}
return process.cwd();
}
static isFolderExistAndWritable(folderPath:string) : boolean{
if(!folderPath){
return false;
}
if (!fs.existsSync(folderPath)) {
return false;
}
if (!fs.lstatSync(folderPath).isDirectory() ) {
return false;
}
try {
fs.accessSync(folderPath, fs.constants.W_OK)
} catch (err) {
return false;
}
return true;
}
static async getLatestModule(moduleName: string): Promise<string> {
@ -40,7 +84,6 @@ export default class Utils {
}
}
};
await PowerShellToolRunner.init();
await PowerShellToolRunner.executePowerShellScriptBlock(new ScriptBuilder()
.getLatestModuleScript(moduleName), options);
const outputJson = JSON.parse(output.trim());
@ -66,7 +109,6 @@ export default class Utils {
if (!Utils.isValidVersion(output.trim())) {
return "";
}
await PowerShellToolRunner.init();
await PowerShellToolRunner.executePowerShellCommand(new ScriptBuilder()
.checkModuleVersionScript(moduleName, version), options);
const outputJson = JSON.parse(output.trim());
@ -93,7 +135,6 @@ export default class Utils {
}
}
};
await PowerShellToolRunner.init();
await PowerShellToolRunner.executePowerShellCommand(script, options);
return output.trim().toLowerCase() === "true";
}
@ -111,7 +152,6 @@ export default class Utils {
$ProgressPreference = 'SilentlyContinue'
Save-Module -Path ${modulePath} -Name Az -RequiredVersion ${version} -Force -ErrorAction Stop
$ProgressPreference = $prevProgressPref`;
await PowerShellToolRunner.init();
const exitCode = await PowerShellToolRunner.executePowerShellScriptBlock(script);
if (exitCode != 0) {
throw new Error(`Download from PSGallery failed for Az ${version} to ${modulePath}`);