Add pre: and post: action for cleaning up (#384)

* pre and post cleanup-cli

* exec azpath

* enable azure powershell part

* set user agent

* extract utils

* divide cleanup

* extract azpsconfig class

* fix test

* move runpsscript

* change to AzPSUtils

* fix typo
This commit is contained in:
Shiying Chen
2023-12-28 17:00:03 +08:00
committed by GitHub
parent 45c3280904
commit b5038826b1
11 changed files with 523 additions and 481 deletions

View File

@@ -1,92 +1,92 @@
import * as os from 'os';
import { AzPSLogin } from '../../src/PowerShell/AzPSLogin';
import { LoginConfig } from '../../src/common/LoginConfig';
import AzPSConstants from '../../src/PowerShell/AzPSConstants';
let azpsLogin: AzPSLogin;
jest.setTimeout(30000);
beforeAll(() => {
var loginConfig = new LoginConfig();
loginConfig.servicePrincipalId = "servicePrincipalID";
loginConfig.servicePrincipalSecret = "servicePrincipalSecret";
loginConfig.tenantId = "tenantId";
loginConfig.subscriptionId = "subscriptionId";
azpsLogin = new AzPSLogin(loginConfig);
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('Testing login', () => {
let loginSpy;
beforeEach(() => {
loginSpy = jest.spyOn(azpsLogin, 'login');
});
test('ServicePrincipal login should pass', async () => {
loginSpy.mockImplementationOnce(() => Promise.resolve());
await azpsLogin.login();
expect(loginSpy).toHaveBeenCalled();
});
});
describe('Testing set module path', () => {
test('setDefaultPSModulePath should work', () => {
azpsLogin.setPSModulePathForGitHubRunner();
const runner: string = process.env.RUNNER_OS || os.type();
if(runner.toLowerCase() === "linux"){
expect(process.env.PSModulePath).toContain(AzPSConstants.DEFAULT_AZ_PATH_ON_LINUX);
}
if(runner.toLowerCase().startsWith("windows")){
expect(process.env.PSModulePath).toContain(AzPSConstants.DEFAULT_AZ_PATH_ON_WINDOWS);
}
});
});
describe('Testing runPSScript', () => {
test('Get PowerShell Version', async () => {
let script = `try {
$ErrorActionPreference = "Stop"
$WarningPreference = "SilentlyContinue"
$output = @{}
$output['Success'] = $true
$output['Result'] = $PSVersionTable.PSVersion.ToString()
}
catch {
$output['Success'] = $false
$output['Error'] = $_.exception.Message
}
return ConvertTo-Json $output`;
let psVersion: string = await AzPSLogin.runPSScript(script);
expect(psVersion === null).toBeFalsy();
});
test('Get PowerShell Version with Wrong Name', async () => {
let script = `try {
$ErrorActionPreference = "Stop"
$WarningPreference = "SilentlyContinue"
$output = @{}
$output['Success'] = $true
$output['Result'] = $PSVersionTableWrongName.PSVersion.ToString()
}
catch {
$output['Success'] = $false
$output['Error'] = $_.exception.Message
}
return ConvertTo-Json $output`;
try{
await AzPSLogin.runPSScript(script);
throw new Error("The last step should fail.");
}catch(error){
expect(error.message.includes("Azure PowerShell login failed with error: You cannot call a method on a null-valued expression.")).toBeTruthy();
}
});
import * as os from 'os';
import { AzPSLogin } from '../../src/PowerShell/AzPSLogin';
import { LoginConfig } from '../../src/common/LoginConfig';
import { AzPSConstants, AzPSUtils } from '../../src/PowerShell/AzPSUtils';
let azpsLogin: AzPSLogin;
jest.setTimeout(30000);
beforeAll(() => {
var loginConfig = new LoginConfig();
loginConfig.servicePrincipalId = "servicePrincipalID";
loginConfig.servicePrincipalSecret = "servicePrincipalSecret";
loginConfig.tenantId = "tenantId";
loginConfig.subscriptionId = "subscriptionId";
azpsLogin = new AzPSLogin(loginConfig);
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('Testing login', () => {
let loginSpy;
beforeEach(() => {
loginSpy = jest.spyOn(azpsLogin, 'login');
});
test('ServicePrincipal login should pass', async () => {
loginSpy.mockImplementationOnce(() => Promise.resolve());
await azpsLogin.login();
expect(loginSpy).toHaveBeenCalled();
});
});
describe('Testing set module path', () => {
test('setDefaultPSModulePath should work', () => {
AzPSUtils.setPSModulePathForGitHubRunner();
const runner: string = process.env.RUNNER_OS || os.type();
if(runner.toLowerCase() === "linux"){
expect(process.env.PSModulePath).toContain(AzPSConstants.DEFAULT_AZ_PATH_ON_LINUX);
}
if(runner.toLowerCase().startsWith("windows")){
expect(process.env.PSModulePath).toContain(AzPSConstants.DEFAULT_AZ_PATH_ON_WINDOWS);
}
});
});
describe('Testing runPSScript', () => {
test('Get PowerShell Version', async () => {
let script = `try {
$ErrorActionPreference = "Stop"
$WarningPreference = "SilentlyContinue"
$output = @{}
$output['Success'] = $true
$output['Result'] = $PSVersionTable.PSVersion.ToString()
}
catch {
$output['Success'] = $false
$output['Error'] = $_.exception.Message
}
return ConvertTo-Json $output`;
let psVersion: string = await AzPSUtils.runPSScript(script);
expect(psVersion === null).toBeFalsy();
});
test('Get PowerShell Version with Wrong Name', async () => {
let script = `try {
$ErrorActionPreference = "Stop"
$WarningPreference = "SilentlyContinue"
$output = @{}
$output['Success'] = $true
$output['Result'] = $PSVersionTableWrongName.PSVersion.ToString()
}
catch {
$output['Success'] = $false
$output['Error'] = $_.exception.Message
}
return ConvertTo-Json $output`;
try{
await AzPSUtils.runPSScript(script);
throw new Error("The last step should fail.");
}catch(error){
expect(error.message.includes("Azure PowerShell login failed with error: You cannot call a method on a null-valued expression.")).toBeTruthy();
}
});
});

View File

@@ -1,153 +1,153 @@
import AzPSSCriptBuilder from "../../src/PowerShell/AzPSScriptBuilder";
import { LoginConfig } from "../../src/common/LoginConfig";
describe("Getting AzLogin PS script", () => {
function setEnv(name: string, value: string) {
process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] = value;
}
function cleanEnv() {
for (const envKey in process.env) {
if (envKey.startsWith('INPUT_')) {
delete process.env[envKey]
}
}
}
beforeEach(() => {
cleanEnv();
});
test('getImportLatestModuleScript', () => {
expect(AzPSSCriptBuilder.getImportLatestModuleScript("TestModule")).toContain("(Get-Module -Name 'TestModule' -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1).Path");
expect(AzPSSCriptBuilder.getImportLatestModuleScript("TestModule")).toContain("Import-Module -Name $latestModulePath");
});
test('getAzPSLoginScript for SP+secret with allowNoSubscriptionsLogin=true', () => {
setEnv('environment', 'azurecloud');
setEnv('enable-AzPSSession', 'true');
setEnv('allow-no-subscriptions', 'true');
setEnv('auth-type', 'SERVICE_PRINCIPAL');
let creds = {
'clientId': 'client-id',
'clientSecret': "client-secret",
'tenantId': 'tenant-id',
'subscriptionId': 'subscription-id'
}
setEnv('creds', JSON.stringify(creds));
let loginConfig = new LoginConfig();
loginConfig.initialize();
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => {
expect(loginScript.includes("Clear-AzContext -Scope Process; Clear-AzContext -Scope CurrentUser -Force -ErrorAction SilentlyContinue; $psLoginSecrets = ConvertTo-SecureString 'client-secret' -AsPlainText -Force; $psLoginCredential = New-Object System.Management.Automation.PSCredential('client-id', $psLoginSecrets); Connect-AzAccount -ServicePrincipal -Environment 'azurecloud' -Tenant 'tenant-id' -Subscription 'subscription-id' -Credential $psLoginCredential | out-null;")).toBeTruthy();
expect(loginMethod).toBe('service principal with secret');
});
});
test('getAzPSLoginScript for SP+secret with allowNoSubscriptionsLogin=true, secret with single-quote', () => {
setEnv('environment', 'azurecloud');
setEnv('enable-AzPSSession', 'true');
setEnv('allow-no-subscriptions', 'true');
setEnv('auth-type', 'SERVICE_PRINCIPAL');
let creds = {
'clientId': 'client-id',
'clientSecret': "client-se'cret",
'tenantId': 'tenant-id',
'subscriptionId': 'subscription-id'
}
setEnv('creds', JSON.stringify(creds));
let loginConfig = new LoginConfig();
loginConfig.initialize();
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => {
expect(loginScript.includes("Clear-AzContext -Scope Process; Clear-AzContext -Scope CurrentUser -Force -ErrorAction SilentlyContinue; $psLoginSecrets = ConvertTo-SecureString 'client-se''cret' -AsPlainText -Force; $psLoginCredential = New-Object System.Management.Automation.PSCredential('client-id', $psLoginSecrets); Connect-AzAccount -ServicePrincipal -Environment 'azurecloud' -Tenant 'tenant-id' -Subscription 'subscription-id' -Credential $psLoginCredential | out-null;")).toBeTruthy();
expect(loginMethod).toBe('service principal with secret');
});
});
test('getAzPSLoginScript for SP+secret with allowNoSubscriptionsLogin=false', () => {
setEnv('environment', 'azurecloud');
setEnv('enable-AzPSSession', 'true');
setEnv('allow-no-subscriptions', 'false'); // same as true
setEnv('auth-type', 'SERVICE_PRINCIPAL');
let creds = {
'clientId': 'client-id',
'clientSecret': 'client-secret',
'tenantId': 'tenant-id',
'subscriptionId': 'subscription-id'
}
setEnv('creds', JSON.stringify(creds));
let loginConfig = new LoginConfig();
loginConfig.initialize();
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => {
expect(loginScript.includes("Clear-AzContext -Scope Process; Clear-AzContext -Scope CurrentUser -Force -ErrorAction SilentlyContinue; $psLoginSecrets = ConvertTo-SecureString 'client-secret' -AsPlainText -Force; $psLoginCredential = New-Object System.Management.Automation.PSCredential('client-id', $psLoginSecrets); Connect-AzAccount -ServicePrincipal -Environment 'azurecloud' -Tenant 'tenant-id' -Subscription 'subscription-id' -Credential $psLoginCredential | out-null;")).toBeTruthy();
expect(loginMethod).toBe('service principal with secret');
});
});
test('getAzPSLoginScript for OIDC', () => {
setEnv('environment', 'azurecloud');
setEnv('enable-AzPSSession', 'true');
setEnv('allow-no-subscriptions', 'false');
setEnv('tenant-id', 'tenant-id');
setEnv('subscription-id', 'subscription-id');
setEnv('client-id', 'client-id');
setEnv('auth-type', 'SERVICE_PRINCIPAL');
let loginConfig = new LoginConfig();
loginConfig.initialize();
jest.spyOn(loginConfig, 'getFederatedToken').mockImplementation(async () => {loginConfig.federatedToken = "fake-token";});
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => {
expect(loginScript.includes("Clear-AzContext -Scope Process; Clear-AzContext -Scope CurrentUser -Force -ErrorAction SilentlyContinue; Connect-AzAccount -ServicePrincipal -Environment 'azurecloud' -Tenant 'tenant-id' -Subscription 'subscription-id' -ApplicationId 'client-id' -FederatedToken 'fake-token' | out-null;")).toBeTruthy();
expect(loginMethod).toBe('OIDC');
});
});
test('getAzPSLoginScript for System MI', () => {
setEnv('environment', 'azurecloud');
setEnv('enable-AzPSSession', 'true');
setEnv('allow-no-subscriptions', 'false');
setEnv('subscription-id', 'subscription-id');
setEnv('auth-type', 'IDENTITY');
let loginConfig = new LoginConfig();
loginConfig.initialize();
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => {
expect(loginScript.includes("Clear-AzContext -Scope Process; Clear-AzContext -Scope CurrentUser -Force -ErrorAction SilentlyContinue; Connect-AzAccount -Identity -Environment 'azurecloud' -Subscription 'subscription-id' | out-null;")).toBeTruthy();
expect(loginMethod).toBe('system-assigned managed identity');
});
});
test('getAzPSLoginScript for System MI without subscription id', () => {
setEnv('environment', 'azurecloud');
setEnv('enable-AzPSSession', 'true');
setEnv('allow-no-subscriptions', 'false');
// setEnv('subscription-id', 'subscription-id');
setEnv('auth-type', 'IDENTITY');
let loginConfig = new LoginConfig();
loginConfig.initialize();
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => {
expect(loginScript.includes("Clear-AzContext -Scope Process; Clear-AzContext -Scope CurrentUser -Force -ErrorAction SilentlyContinue; Connect-AzAccount -Identity -Environment 'azurecloud' | out-null;")).toBeTruthy();
expect(loginMethod).toBe('system-assigned managed identity');
});
});
test('getAzPSLoginScript for user-assigned MI', () => {
setEnv('environment', 'azurecloud');
setEnv('enable-AzPSSession', 'true');
setEnv('allow-no-subscriptions', 'true');
setEnv('auth-type', 'IDENTITY');
setEnv('client-id', 'client-id');
let loginConfig = new LoginConfig();
loginConfig.initialize();
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => {
expect(loginScript.includes("Clear-AzContext -Scope Process; Clear-AzContext -Scope CurrentUser -Force -ErrorAction SilentlyContinue; Connect-AzAccount -Identity -Environment 'azurecloud' -AccountId 'client-id' | out-null;")).toBeTruthy();
expect(loginMethod).toBe('user-assigned managed identity');
});
});
import AzPSSCriptBuilder from "../../src/PowerShell/AzPSScriptBuilder";
import { LoginConfig } from "../../src/common/LoginConfig";
describe("Getting AzLogin PS script", () => {
function setEnv(name: string, value: string) {
process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] = value;
}
function cleanEnv() {
for (const envKey in process.env) {
if (envKey.startsWith('INPUT_')) {
delete process.env[envKey]
}
}
}
beforeEach(() => {
cleanEnv();
});
test('getImportLatestModuleScript', () => {
expect(AzPSSCriptBuilder.getImportLatestModuleScript("TestModule")).toContain("(Get-Module -Name 'TestModule' -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1).Path");
expect(AzPSSCriptBuilder.getImportLatestModuleScript("TestModule")).toContain("Import-Module -Name $latestModulePath");
});
test('getAzPSLoginScript for SP+secret with allowNoSubscriptionsLogin=true', () => {
setEnv('environment', 'azurecloud');
setEnv('enable-AzPSSession', 'true');
setEnv('allow-no-subscriptions', 'true');
setEnv('auth-type', 'SERVICE_PRINCIPAL');
let creds = {
'clientId': 'client-id',
'clientSecret': "client-secret",
'tenantId': 'tenant-id',
'subscriptionId': 'subscription-id'
}
setEnv('creds', JSON.stringify(creds));
let loginConfig = new LoginConfig();
loginConfig.initialize();
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => {
expect(loginScript.includes("$psLoginSecrets = ConvertTo-SecureString 'client-secret' -AsPlainText -Force; $psLoginCredential = New-Object System.Management.Automation.PSCredential('client-id', $psLoginSecrets); Connect-AzAccount -ServicePrincipal -Environment 'azurecloud' -Tenant 'tenant-id' -Subscription 'subscription-id' -Credential $psLoginCredential | out-null;")).toBeTruthy();
expect(loginMethod).toBe('service principal with secret');
});
});
test('getAzPSLoginScript for SP+secret with allowNoSubscriptionsLogin=true, secret with single-quote', () => {
setEnv('environment', 'azurecloud');
setEnv('enable-AzPSSession', 'true');
setEnv('allow-no-subscriptions', 'true');
setEnv('auth-type', 'SERVICE_PRINCIPAL');
let creds = {
'clientId': 'client-id',
'clientSecret': "client-se'cret",
'tenantId': 'tenant-id',
'subscriptionId': 'subscription-id'
}
setEnv('creds', JSON.stringify(creds));
let loginConfig = new LoginConfig();
loginConfig.initialize();
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => {
expect(loginScript.includes("$psLoginSecrets = ConvertTo-SecureString 'client-se''cret' -AsPlainText -Force; $psLoginCredential = New-Object System.Management.Automation.PSCredential('client-id', $psLoginSecrets); Connect-AzAccount -ServicePrincipal -Environment 'azurecloud' -Tenant 'tenant-id' -Subscription 'subscription-id' -Credential $psLoginCredential | out-null;")).toBeTruthy();
expect(loginMethod).toBe('service principal with secret');
});
});
test('getAzPSLoginScript for SP+secret with allowNoSubscriptionsLogin=false', () => {
setEnv('environment', 'azurecloud');
setEnv('enable-AzPSSession', 'true');
setEnv('allow-no-subscriptions', 'false'); // same as true
setEnv('auth-type', 'SERVICE_PRINCIPAL');
let creds = {
'clientId': 'client-id',
'clientSecret': 'client-secret',
'tenantId': 'tenant-id',
'subscriptionId': 'subscription-id'
}
setEnv('creds', JSON.stringify(creds));
let loginConfig = new LoginConfig();
loginConfig.initialize();
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => {
expect(loginScript.includes("$psLoginSecrets = ConvertTo-SecureString 'client-secret' -AsPlainText -Force; $psLoginCredential = New-Object System.Management.Automation.PSCredential('client-id', $psLoginSecrets); Connect-AzAccount -ServicePrincipal -Environment 'azurecloud' -Tenant 'tenant-id' -Subscription 'subscription-id' -Credential $psLoginCredential | out-null;")).toBeTruthy();
expect(loginMethod).toBe('service principal with secret');
});
});
test('getAzPSLoginScript for OIDC', () => {
setEnv('environment', 'azurecloud');
setEnv('enable-AzPSSession', 'true');
setEnv('allow-no-subscriptions', 'false');
setEnv('tenant-id', 'tenant-id');
setEnv('subscription-id', 'subscription-id');
setEnv('client-id', 'client-id');
setEnv('auth-type', 'SERVICE_PRINCIPAL');
let loginConfig = new LoginConfig();
loginConfig.initialize();
jest.spyOn(loginConfig, 'getFederatedToken').mockImplementation(async () => {loginConfig.federatedToken = "fake-token";});
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => {
expect(loginScript.includes("Connect-AzAccount -ServicePrincipal -Environment 'azurecloud' -Tenant 'tenant-id' -Subscription 'subscription-id' -ApplicationId 'client-id' -FederatedToken 'fake-token' | out-null;")).toBeTruthy();
expect(loginMethod).toBe('OIDC');
});
});
test('getAzPSLoginScript for System MI', () => {
setEnv('environment', 'azurecloud');
setEnv('enable-AzPSSession', 'true');
setEnv('allow-no-subscriptions', 'false');
setEnv('subscription-id', 'subscription-id');
setEnv('auth-type', 'IDENTITY');
let loginConfig = new LoginConfig();
loginConfig.initialize();
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => {
expect(loginScript.includes("Connect-AzAccount -Identity -Environment 'azurecloud' -Subscription 'subscription-id' | out-null;")).toBeTruthy();
expect(loginMethod).toBe('system-assigned managed identity');
});
});
test('getAzPSLoginScript for System MI without subscription id', () => {
setEnv('environment', 'azurecloud');
setEnv('enable-AzPSSession', 'true');
setEnv('allow-no-subscriptions', 'false');
// setEnv('subscription-id', 'subscription-id');
setEnv('auth-type', 'IDENTITY');
let loginConfig = new LoginConfig();
loginConfig.initialize();
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => {
expect(loginScript.includes("Connect-AzAccount -Identity -Environment 'azurecloud' | out-null;")).toBeTruthy();
expect(loginMethod).toBe('system-assigned managed identity');
});
});
test('getAzPSLoginScript for user-assigned MI', () => {
setEnv('environment', 'azurecloud');
setEnv('enable-AzPSSession', 'true');
setEnv('allow-no-subscriptions', 'true');
setEnv('auth-type', 'IDENTITY');
setEnv('client-id', 'client-id');
let loginConfig = new LoginConfig();
loginConfig.initialize();
return AzPSSCriptBuilder.getAzPSLoginScript(loginConfig).then(([loginMethod, loginScript]) => {
expect(loginScript.includes("Connect-AzAccount -Identity -Environment 'azurecloud' -AccountId 'client-id' | out-null;")).toBeTruthy();
expect(loginMethod).toBe('user-assigned managed identity');
});
});
});

View File

@@ -39,4 +39,6 @@ branding:
color: 'blue'
runs:
using: 'node16'
pre: 'lib/cleanup.js'
main: 'lib/main.js'
post: 'lib/cleanup.js'

View File

@@ -17,9 +17,6 @@ export class AzureCliLogin {
async login() {
core.info(`Running Azure CLI Login.`);
this.azPath = await io.which("az", true);
if (!this.azPath) {
throw new Error("Azure CLI is not found in the runner.");
}
core.debug(`Azure CLI path: ${this.azPath}`);
let output: string = "";

View File

@@ -1,7 +0,0 @@
export default class AzPSConstants {
static readonly DEFAULT_AZ_PATH_ON_LINUX: string = '/usr/share';
static readonly DEFAULT_AZ_PATH_ON_WINDOWS: string = 'C:\\Modules';
static readonly AzAccounts: string = "Az.Accounts";
static readonly PowerShell_CmdName = "pwsh";
}

View File

@@ -1,100 +1,24 @@
import * as core from '@actions/core';
import * as exec from '@actions/exec';
import * as io from '@actions/io';
import * as os from 'os';
import * as path from 'path';
import AzPSScriptBuilder from './AzPSScriptBuilder';
import AzPSConstants from './AzPSConstants';
import { LoginConfig } from '../common/LoginConfig';
interface PSResultType {
Result: string;
Success: boolean;
Error: string;
}
export class AzPSLogin {
loginConfig: LoginConfig;
constructor(loginConfig: LoginConfig) {
this.loginConfig = loginConfig;
}
async login() {
core.info(`Running Azure PowerShell Login.`);
this.setPSModulePathForGitHubRunner();
await this.importLatestAzAccounts();
const [loginMethod, loginScript] = await AzPSScriptBuilder.getAzPSLoginScript(this.loginConfig);
core.info(`Attempting Azure PowerShell login by using ${loginMethod}...`);
core.debug(`Azure PowerShell Login Script: ${loginScript}`);
await AzPSLogin.runPSScript(loginScript);
console.log(`Running Azure PowerShell Login successfully.`);
}
setPSModulePathForGitHubRunner() {
const runner: string = process.env.RUNNER_OS || os.type();
switch (runner.toLowerCase()) {
case "linux":
this.pushPSModulePath(AzPSConstants.DEFAULT_AZ_PATH_ON_LINUX);
break;
case "windows":
case "windows_nt":
this.pushPSModulePath(AzPSConstants.DEFAULT_AZ_PATH_ON_WINDOWS);
break;
case "macos":
case "darwin":
core.warning(`Skip setting the default PowerShell module path for OS ${runner.toLowerCase()}.`);
break;
default:
core.warning(`Skip setting the default PowerShell module path for unknown OS ${runner.toLowerCase()}.`);
break;
}
}
private pushPSModulePath(psModulePath: string) {
process.env.PSModulePath = `${psModulePath}${path.delimiter}${process.env.PSModulePath}`;
core.debug(`Set PSModulePath as ${process.env.PSModulePath}`);
}
private async importLatestAzAccounts() {
let importLatestAccountsScript: string = AzPSScriptBuilder.getImportLatestModuleScript(AzPSConstants.AzAccounts);
core.debug(`The script to import the latest Az.Accounts: ${importLatestAccountsScript}`);
let azAccountsPath: string = await AzPSLogin.runPSScript(importLatestAccountsScript);
core.debug(`The latest Az.Accounts used: ${azAccountsPath}`);
}
static async runPSScript(psScript: string): Promise<string> {
let outputString: string = "";
let commandStdErr = false;
const options: any = {
silent: true,
listeners: {
stdout: (data: Buffer) => {
outputString += data.toString();
},
stderr: (data: Buffer) => {
let error = data.toString();
if (error && error.trim().length !== 0) {
commandStdErr = true;
core.error(error);
}
}
}
};
let psPath: string = await io.which(AzPSConstants.PowerShell_CmdName, true);
await exec.exec(`"${psPath}"`, ["-Command", psScript], options)
if (commandStdErr) {
throw new Error('Azure PowerShell login failed with errors.');
}
const result: PSResultType = JSON.parse(outputString.trim());
console.log(result);
if (!(result.Success)) {
throw new Error(`Azure PowerShell login failed with error: ${result.Error}`);
}
return result.Result;
}
}
import * as core from '@actions/core';
import AzPSScriptBuilder from './AzPSScriptBuilder';
import { AzPSUtils } from './AzPSUtils';
import { LoginConfig } from '../common/LoginConfig';
export class AzPSLogin {
loginConfig: LoginConfig;
constructor(loginConfig: LoginConfig) {
this.loginConfig = loginConfig;
}
async login() {
core.info(`Running Azure PowerShell Login.`);
AzPSUtils.setPSModulePathForGitHubRunner();
await AzPSUtils.importLatestAzAccounts();
const [loginMethod, loginScript] = await AzPSScriptBuilder.getAzPSLoginScript(this.loginConfig);
core.info(`Attempting Azure PowerShell login by using ${loginMethod}...`);
core.debug(`Azure PowerShell Login Script: ${loginScript}`);
await AzPSUtils.runPSScript(loginScript);
console.log(`Running Azure PowerShell Login successfully.`);
}
}

View File

@@ -1,113 +1,111 @@
import AzPSConstants from "./AzPSConstants";
import { LoginConfig } from '../common/LoginConfig';
export default class AzPSScriptBuilder {
static getImportLatestModuleScript(moduleName: string): string {
let script = `try {
$ErrorActionPreference = "Stop"
$WarningPreference = "SilentlyContinue"
$output = @{}
$latestModulePath = (Get-Module -Name '${moduleName}' -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1).Path
Import-Module -Name $latestModulePath
$output['Success'] = $true
$output['Result'] = $latestModulePath
}
catch {
$output['Success'] = $false
$output['Error'] = $_.exception.Message
}
return ConvertTo-Json $output`;
return script;
}
static async getAzPSLoginScript(loginConfig: LoginConfig) {
let loginMethodName = "";
let commands = 'Clear-AzContext -Scope Process; ';
commands += 'Clear-AzContext -Scope CurrentUser -Force -ErrorAction SilentlyContinue; ';
if (loginConfig.environment.toLowerCase() == "azurestack") {
commands += `Add-AzEnvironment -Name '${loginConfig.environment}' -ARMEndpoint '${loginConfig.resourceManagerEndpointUrl}' | out-null;`;
}
if (loginConfig.authType === LoginConfig.AUTH_TYPE_SERVICE_PRINCIPAL) {
if (loginConfig.servicePrincipalSecret) {
commands += AzPSScriptBuilder.loginWithSecret(loginConfig);
loginMethodName = 'service principal with secret';
} else {
commands += await AzPSScriptBuilder.loginWithOIDC(loginConfig);
loginMethodName = "OIDC";
}
} else {
if (loginConfig.servicePrincipalId) {
commands += AzPSScriptBuilder.loginWithUserAssignedIdentity(loginConfig);
loginMethodName = 'user-assigned managed identity';
} else {
commands += AzPSScriptBuilder.loginWithSystemAssignedIdentity(loginConfig);
loginMethodName = 'system-assigned managed identity';
}
}
let script = `try {
$ErrorActionPreference = "Stop"
$WarningPreference = "SilentlyContinue"
$output = @{}
${commands}
$output['Success'] = $true
$output['Result'] = ""
}
catch {
$output['Success'] = $false
$output['Error'] = $_.exception.Message
}
return ConvertTo-Json $output`;
return [loginMethodName, script];
}
private static loginWithSecret(loginConfig: LoginConfig): string {
let servicePrincipalSecret: string = loginConfig.servicePrincipalSecret.split("'").join("''");
let loginCmdlet = `$psLoginSecrets = ConvertTo-SecureString '${servicePrincipalSecret}' -AsPlainText -Force; `;
loginCmdlet += `$psLoginCredential = New-Object System.Management.Automation.PSCredential('${loginConfig.servicePrincipalId}', $psLoginSecrets); `;
let cmdletSuffix = "-Credential $psLoginCredential";
loginCmdlet += AzPSScriptBuilder.psLoginCmdlet(loginConfig.authType, loginConfig.environment, loginConfig.tenantId, loginConfig.subscriptionId, cmdletSuffix);
return loginCmdlet;
}
private static async loginWithOIDC(loginConfig: LoginConfig) {
await loginConfig.getFederatedToken();
let cmdletSuffix = `-ApplicationId '${loginConfig.servicePrincipalId}' -FederatedToken '${loginConfig.federatedToken}'`;
return AzPSScriptBuilder.psLoginCmdlet(loginConfig.authType, loginConfig.environment, loginConfig.tenantId, loginConfig.subscriptionId, cmdletSuffix);
}
private static loginWithSystemAssignedIdentity(loginConfig: LoginConfig): string {
let cmdletSuffix = "";
return AzPSScriptBuilder.psLoginCmdlet(loginConfig.authType, loginConfig.environment, loginConfig.tenantId, loginConfig.subscriptionId, cmdletSuffix);
}
static loginWithUserAssignedIdentity(loginConfig: LoginConfig): string {
let cmdletSuffix = `-AccountId '${loginConfig.servicePrincipalId}'`;
return AzPSScriptBuilder.psLoginCmdlet(loginConfig.authType, loginConfig.environment, loginConfig.tenantId, loginConfig.subscriptionId, cmdletSuffix);
}
private static psLoginCmdlet(authType:string, environment:string, tenantId:string, subscriptionId:string, cmdletSuffix:string){
let loginCmdlet = `Connect-AzAccount `;
if(authType === LoginConfig.AUTH_TYPE_SERVICE_PRINCIPAL){
loginCmdlet += "-ServicePrincipal ";
}else{
loginCmdlet += "-Identity ";
}
loginCmdlet += `-Environment '${environment}' `;
if(tenantId){
loginCmdlet += `-Tenant '${tenantId}' `;
}
if(subscriptionId){
loginCmdlet += `-Subscription '${subscriptionId}' `;
}
loginCmdlet += `${cmdletSuffix} | out-null;`;
return loginCmdlet;
}
}
import { LoginConfig } from '../common/LoginConfig';
export default class AzPSScriptBuilder {
static getImportLatestModuleScript(moduleName: string): string {
let script = `try {
$ErrorActionPreference = "Stop"
$WarningPreference = "SilentlyContinue"
$output = @{}
$latestModulePath = (Get-Module -Name '${moduleName}' -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1).Path
Import-Module -Name $latestModulePath
$output['Success'] = $true
$output['Result'] = $latestModulePath
}
catch {
$output['Success'] = $false
$output['Error'] = $_.exception.Message
}
return ConvertTo-Json $output`;
return script;
}
static async getAzPSLoginScript(loginConfig: LoginConfig) {
let loginMethodName = "";
let commands = "";
if (loginConfig.environment.toLowerCase() == "azurestack") {
commands += `Add-AzEnvironment -Name '${loginConfig.environment}' -ARMEndpoint '${loginConfig.resourceManagerEndpointUrl}' | out-null;`;
}
if (loginConfig.authType === LoginConfig.AUTH_TYPE_SERVICE_PRINCIPAL) {
if (loginConfig.servicePrincipalSecret) {
commands += AzPSScriptBuilder.loginWithSecret(loginConfig);
loginMethodName = 'service principal with secret';
} else {
commands += await AzPSScriptBuilder.loginWithOIDC(loginConfig);
loginMethodName = "OIDC";
}
} else {
if (loginConfig.servicePrincipalId) {
commands += AzPSScriptBuilder.loginWithUserAssignedIdentity(loginConfig);
loginMethodName = 'user-assigned managed identity';
} else {
commands += AzPSScriptBuilder.loginWithSystemAssignedIdentity(loginConfig);
loginMethodName = 'system-assigned managed identity';
}
}
let script = `try {
$ErrorActionPreference = "Stop"
$WarningPreference = "SilentlyContinue"
$output = @{}
${commands}
$output['Success'] = $true
$output['Result'] = ""
}
catch {
$output['Success'] = $false
$output['Error'] = $_.exception.Message
}
return ConvertTo-Json $output`;
return [loginMethodName, script];
}
private static loginWithSecret(loginConfig: LoginConfig): string {
let servicePrincipalSecret: string = loginConfig.servicePrincipalSecret.split("'").join("''");
let loginCmdlet = `$psLoginSecrets = ConvertTo-SecureString '${servicePrincipalSecret}' -AsPlainText -Force; `;
loginCmdlet += `$psLoginCredential = New-Object System.Management.Automation.PSCredential('${loginConfig.servicePrincipalId}', $psLoginSecrets); `;
let cmdletSuffix = "-Credential $psLoginCredential";
loginCmdlet += AzPSScriptBuilder.psLoginCmdlet(loginConfig.authType, loginConfig.environment, loginConfig.tenantId, loginConfig.subscriptionId, cmdletSuffix);
return loginCmdlet;
}
private static async loginWithOIDC(loginConfig: LoginConfig) {
await loginConfig.getFederatedToken();
let cmdletSuffix = `-ApplicationId '${loginConfig.servicePrincipalId}' -FederatedToken '${loginConfig.federatedToken}'`;
return AzPSScriptBuilder.psLoginCmdlet(loginConfig.authType, loginConfig.environment, loginConfig.tenantId, loginConfig.subscriptionId, cmdletSuffix);
}
private static loginWithSystemAssignedIdentity(loginConfig: LoginConfig): string {
let cmdletSuffix = "";
return AzPSScriptBuilder.psLoginCmdlet(loginConfig.authType, loginConfig.environment, loginConfig.tenantId, loginConfig.subscriptionId, cmdletSuffix);
}
static loginWithUserAssignedIdentity(loginConfig: LoginConfig): string {
let cmdletSuffix = `-AccountId '${loginConfig.servicePrincipalId}'`;
return AzPSScriptBuilder.psLoginCmdlet(loginConfig.authType, loginConfig.environment, loginConfig.tenantId, loginConfig.subscriptionId, cmdletSuffix);
}
private static psLoginCmdlet(authType:string, environment:string, tenantId:string, subscriptionId:string, cmdletSuffix:string){
let loginCmdlet = `Connect-AzAccount `;
if(authType === LoginConfig.AUTH_TYPE_SERVICE_PRINCIPAL){
loginCmdlet += "-ServicePrincipal ";
}else{
loginCmdlet += "-Identity ";
}
loginCmdlet += `-Environment '${environment}' `;
if(tenantId){
loginCmdlet += `-Tenant '${tenantId}' `;
}
if(subscriptionId){
loginCmdlet += `-Subscription '${subscriptionId}' `;
}
loginCmdlet += `${cmdletSuffix} | out-null;`;
return loginCmdlet;
}
}

View File

@@ -0,0 +1,85 @@
import * as core from '@actions/core';
import * as os from 'os';
import * as path from 'path';
import * as exec from '@actions/exec';
import * as io from '@actions/io';
import AzPSScriptBuilder from './AzPSScriptBuilder';
interface PSResultType {
Result: string;
Success: boolean;
Error: string;
}
export class AzPSConstants {
static readonly DEFAULT_AZ_PATH_ON_LINUX: string = '/usr/share';
static readonly DEFAULT_AZ_PATH_ON_WINDOWS: string = 'C:\\Modules';
static readonly AzAccounts: string = "Az.Accounts";
static readonly PowerShell_CmdName = "pwsh";
}
export class AzPSUtils {
static async setPSModulePathForGitHubRunner() {
const runner: string = process.env.RUNNER_OS || os.type();
switch (runner.toLowerCase()) {
case "linux":
AzPSUtils.pushPSModulePath(AzPSConstants.DEFAULT_AZ_PATH_ON_LINUX);
break;
case "windows":
case "windows_nt":
AzPSUtils.pushPSModulePath(AzPSConstants.DEFAULT_AZ_PATH_ON_WINDOWS);
break;
case "macos":
case "darwin":
core.warning(`Skip setting the default PowerShell module path for OS ${runner.toLowerCase()}.`);
break;
default:
core.warning(`Skip setting the default PowerShell module path for unknown OS ${runner.toLowerCase()}.`);
break;
}
}
private static pushPSModulePath(psModulePath: string) {
process.env.PSModulePath = `${psModulePath}${path.delimiter}${process.env.PSModulePath}`;
core.debug(`Set PSModulePath as ${process.env.PSModulePath}`);
}
static async importLatestAzAccounts() {
let importLatestAccountsScript: string = AzPSScriptBuilder.getImportLatestModuleScript(AzPSConstants.AzAccounts);
core.debug(`The script to import the latest Az.Accounts: ${importLatestAccountsScript}`);
let azAccountsPath: string = await AzPSUtils.runPSScript(importLatestAccountsScript);
core.debug(`The latest Az.Accounts used: ${azAccountsPath}`);
}
static async runPSScript(psScript: string): Promise<string> {
let outputString: string = "";
let commandStdErr = false;
const options: any = {
silent: true,
listeners: {
stdout: (data: Buffer) => {
outputString += data.toString();
},
stderr: (data: Buffer) => {
let error = data.toString();
if (error && error.trim().length !== 0) {
commandStdErr = true;
core.error(error);
}
}
}
};
let psPath: string = await io.which(AzPSConstants.PowerShell_CmdName, true);
await exec.exec(`"${psPath}"`, ["-Command", psScript], options)
if (commandStdErr) {
throw new Error('Azure PowerShell login failed with errors.');
}
const result: PSResultType = JSON.parse(outputString.trim());
console.log(result);
if (!(result.Success)) {
throw new Error(`Azure PowerShell login failed with error: ${result.Error}`);
}
return result.Result;
}
}

19
src/cleanup.ts Normal file
View File

@@ -0,0 +1,19 @@
import * as core from '@actions/core';
import { setUserAgent, cleanupAzCLIAccounts, cleanupAzPSAccounts } from './common/Utils';
async function cleanup() {
try {
setUserAgent();
await cleanupAzCLIAccounts();
if(core.getInput('enable-AzPSSession').toLowerCase() === "true"){
await cleanupAzPSAccounts();
}
}
catch (error) {
core.setFailed(`Login cleanup failed with ${error}. Make sure 'az' is installed on the runner. If 'enable-AzPSSession' is true, make sure 'pwsh' is installed on the runner together with Azure PowerShell module.`);
core.debug(error.stack);
}
}
cleanup();

36
src/common/Utils.ts Normal file
View File

@@ -0,0 +1,36 @@
import * as core from '@actions/core';
import * as exec from '@actions/exec';
import * as io from '@actions/io';
import * as crypto from 'crypto';
import { AzPSConstants, AzPSUtils } from '../PowerShell/AzPSUtils';
export function setUserAgent(): void {
let usrAgentRepo = crypto.createHash('sha256').update(`${process.env.GITHUB_REPOSITORY}`).digest('hex');
let actionName = 'AzureLogin';
process.env.AZURE_HTTP_USER_AGENT = (!!process.env.AZURE_HTTP_USER_AGENT ? `${process.env.AZURE_HTTP_USER_AGENT} ` : '') + `GITHUBACTIONS/${actionName}@v1_${usrAgentRepo}`;
process.env.AZUREPS_HOST_ENVIRONMENT = (!!process.env.AZUREPS_HOST_ENVIRONMENT ? `${process.env.AZUREPS_HOST_ENVIRONMENT} ` : '') + `GITHUBACTIONS/${actionName}@v1_${usrAgentRepo}`;
}
export async function cleanupAzCLIAccounts(): Promise<void> {
let azPath = await io.which("az", true);
if (!azPath) {
throw new Error("Azure CLI is not found in the runner.");
}
core.debug(`Azure CLI path: ${azPath}`);
core.info("Clearing azure cli accounts from the local cache.");
await exec.exec(`"${azPath}"`, ["account", "clear"]);
}
export async function cleanupAzPSAccounts(): Promise<void> {
let psPath: string = await io.which(AzPSConstants.PowerShell_CmdName, true);
if (!psPath) {
throw new Error("PowerShell is not found in the runner.");
}
core.debug(`PowerShell path: ${psPath}`);
core.debug("Importing Azure PowerShell module.");
AzPSUtils.setPSModulePathForGitHubRunner();
await AzPSUtils.importLatestAzAccounts();
core.info("Clearing azure powershell accounts from the local cache.");
await exec.exec(`"${psPath}"`, ["-Command", "Clear-AzContext", "-Scope", "Process"]);
await exec.exec(`"${psPath}"`, ["-Command", "Clear-AzContext", "-Scope", "CurrentUser", "-Force", "-ErrorAction", "SilentlyContinue"]);
}

View File

@@ -1,19 +1,12 @@
import * as core from '@actions/core';
import { setUserAgent } from './common/Utils';
import { AzPSLogin } from './PowerShell/AzPSLogin';
import { LoginConfig } from './common/LoginConfig';
import { AzureCliLogin } from './Cli/AzureCliLogin';
var prefix = !!process.env.AZURE_HTTP_USER_AGENT ? `${process.env.AZURE_HTTP_USER_AGENT}` : "";
var azPSHostEnv = !!process.env.AZUREPS_HOST_ENVIRONMENT ? `${process.env.AZUREPS_HOST_ENVIRONMENT}` : "";
async function main() {
try {
let usrAgentRepo = `${process.env.GITHUB_REPOSITORY}`;
let actionName = 'AzureLogin';
let userAgentString = (!!prefix ? `${prefix}+` : '') + `GITHUBACTIONS/${actionName}@v1_${usrAgentRepo}`;
let azurePSHostEnv = (!!azPSHostEnv ? `${azPSHostEnv}+` : '') + `GITHUBACTIONS/${actionName}@v1_${usrAgentRepo}`;
core.exportVariable('AZURE_HTTP_USER_AGENT', userAgentString);
core.exportVariable('AZUREPS_HOST_ENVIRONMENT', azurePSHostEnv);
setUserAgent();
// prepare the login configuration
var loginConfig = new LoginConfig();
@@ -31,14 +24,9 @@ async function main() {
}
}
catch (error) {
core.setFailed(`Login failed with ${error}. Make sure 'az' is installed on the runner. If 'enable-AzPSSession' is true, make sure 'pwsh' is installed on the runner together with Azure PowerShell module. Double check if the 'auth-type' is correct. Refer to https://github.com/Azure/login#readme for more information.`);
core.setFailed(`Login failed with ${error}. Double check if the 'auth-type' is correct. Refer to https://github.com/Azure/login#readme for more information.`);
core.debug(error.stack);
}
finally {
// Reset AZURE_HTTP_USER_AGENT
core.exportVariable('AZURE_HTTP_USER_AGENT', prefix);
core.exportVariable('AZUREPS_HOST_ENVIRONMENT', azPSHostEnv);
}
}
main();