Skip to content

Commit

Permalink
Support MI for PS
Browse files Browse the repository at this point in the history
  • Loading branch information
YanaXu committed Sep 26, 2023
1 parent ff02cec commit cd07ba8
Show file tree
Hide file tree
Showing 18 changed files with 396 additions and 360 deletions.
68 changes: 68 additions & 0 deletions __tests__/PowerShell/AzPSLogin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
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;

beforeAll(() => {
var loginConfig = new LoginConfig();
loginConfig.servicePrincipalId = "servicePrincipalID";
loginConfig.servicePrincipalKey = "servicePrinicipalkey";
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['${AzPSConstants.Success}'] = "true"
$output['${AzPSConstants.Result}'] = $PSVersionTable.PSVersion.ToString()
}
catch {
$output['${AzPSConstants.Error}'] = $_.exception.Message
}
return ConvertTo-Json $output`;

let psVersion: string = await AzPSLogin.runPSScript(script);
expect(psVersion === null).toBeFalsy();
});

});
99 changes: 99 additions & 0 deletions __tests__/PowerShell/AzPSScriptBuilder.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
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('PS script to get latest module path should work', () => {
expect(AzPSSCriptBuilder.getLatestModulePathScript("TestModule")).toContain("(Get-Module -Name 'TestModule' -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1).ModuleBase");
});

test('PS script should not set context while passing allowNoSubscriptionsLogin as 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': 'tenanat-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("Connect-AzAccount -ServicePrincipal -Environment 'azurecloud' -Tenant 'tenanat-id' -Credential")).toBeTruthy();
expect(loginScript.includes("Set-AzContext -SubscriptionId")).toBeFalsy;
});
});

test('PS script should set context while passing allowNoSubscriptionsLogin as false', () => {
setEnv('environment', 'azurecloud');
setEnv('enable-AzPSSession', 'true');
setEnv('allow-no-subscriptions', 'false');
setEnv('auth-type', 'SERVICE_PRINCIPAL');
let creds = {
'clientId': 'client-id',
'clientSecret': 'client-secret',
'tenantId': 'tenanat-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("Connect-AzAccount -ServicePrincipal -Environment 'azurecloud' -Tenant 'tenanat-id' -Credential")).toBeTruthy();
expect(loginScript.includes("Set-AzContext -SubscriptionId")).toBeTruthy();
});
});

test('PS script should use system managed identity to login when auth-type is IDENTITY', () => {
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'")).toBeTruthy();
expect(loginScript.includes("Connect-AzAccount -Identity -Environment 'azurecloud' -AccountId 'client-id'")).toBeFalsy();
expect(loginScript.includes("Set-AzContext -SubscriptionId")).toBeTruthy();
});
});

test('PS script should use user managed identity to login when auth-type is IDENTITY', () => {
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'")).toBeTruthy();
expect(loginScript.includes("Set-AzContext -SubscriptionId")).toBeFalsy();
});
});

});
44 changes: 0 additions & 44 deletions __tests__/PowerShell/ServicePrinicipalLogin.test.ts

This file was deleted.

25 changes: 0 additions & 25 deletions __tests__/PowerShell/Utilities/ScriptBuilder.test.ts

This file was deleted.

45 changes: 0 additions & 45 deletions __tests__/PowerShell/Utilities/Utils.test.ts

This file was deleted.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"build": "tsc",
"test": "jest"
},
"author": "Sumiran Aggarwal",
"license": "MIT",
"devDependencies": {
"@types/jest": "^29.2.4",
Expand Down
20 changes: 10 additions & 10 deletions src/Cli/AzureCliLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class AzureCliLogin {
}

async login() {
console.log(`Running Azure CLI 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.");
Expand All @@ -37,7 +37,7 @@ export class AzureCliLogin {
this.setAzurestackEnvIfNecessary();

await this.executeAzCliCommand(["cloud", "set", "-n", this.loginConfig.environment], false);
console.log(`Done setting cloud: "${this.loginConfig.environment}"`);
core.info(`Done setting cloud: "${this.loginConfig.environment}"`);

if (this.loginConfig.authType == "service_principal") {
let args = ["--service-principal",
Expand Down Expand Up @@ -70,16 +70,16 @@ export class AzureCliLogin {
throw new Error("resourceManagerEndpointUrl is a required parameter when environment is defined.");
}

console.log(`Unregistering cloud: "${this.loginConfig.environment}" first if it exists`);
core.info(`Unregistering cloud: "${this.loginConfig.environment}" first if it exists`);
try {
await this.executeAzCliCommand(["cloud", "set", "-n", "AzureCloud"], true);
await this.executeAzCliCommand(["cloud", "unregister", "-n", this.loginConfig.environment], false);
}
catch (error) {
console.log(`Ignore cloud not registered error: "${error}"`);
core.info(`Ignore cloud not registered error: "${error}"`);
}

console.log(`Registering cloud: "${this.loginConfig.environment}" with ARM endpoint: "${this.loginConfig.resourceManagerEndpointUrl}"`);
core.info(`Registering cloud: "${this.loginConfig.environment}" with ARM endpoint: "${this.loginConfig.resourceManagerEndpointUrl}"`);
try {
let baseUri = this.loginConfig.resourceManagerEndpointUrl;
if (baseUri.endsWith('/')) {
Expand All @@ -95,11 +95,11 @@ export class AzureCliLogin {
throw error;
}

console.log(`Done registering cloud: "${this.loginConfig.environment}"`)
core.info(`Done registering cloud: "${this.loginConfig.environment}"`)
}

async loginWithSecret(args: string[]) {
console.log("Note: Azure/login action also supports OIDC login mechanism. Refer https://github.com/azure/login#configure-a-service-principal-with-a-federated-credential-to-use-oidc-based-authentication for more details.")
core.info("Note: Azure/login action also supports OIDC login mechanism. Refer https://github.com/azure/login#configure-a-service-principal-with-a-federated-credential-to-use-oidc-based-authentication for more details.")
args.push(`--password=${this.loginConfig.servicePrincipalKey}`);
await this.callCliLogin(args, 'service principal with secret');
}
Expand All @@ -120,14 +120,14 @@ export class AzureCliLogin {
}

async callCliLogin(args: string[], methodName: string) {
console.log(`Attempting Azure CLI login by using ${methodName}...`);
core.info(`Attempting Azure CLI login by using ${methodName}...`);
args.unshift("login");
if (this.loginConfig.allowNoSubscriptionsLogin) {
args.push("--allow-no-subscriptions");
}
await this.executeAzCliCommand(args, true, this.loginOptions);
await this.setSubscription();
console.log(`Azure CLI login succeed by using ${methodName}.`);
core.info(`Azure CLI login succeeds by using ${methodName}.`);
}

async setSubscription() {
Expand All @@ -140,7 +140,7 @@ export class AzureCliLogin {
}
let args = ["account", "set", "--subscription", this.loginConfig.subscriptionId];
await this.executeAzCliCommand(args, true, this.loginOptions);
console.log("Subscription is set successfully.");
core.info("Subscription is set successfully.");
}

async executeAzCliCommand(
Expand Down
11 changes: 11 additions & 0 deletions src/PowerShell/AzPSConstants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
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";

static readonly Success: string = "Success";
static readonly Error: string = "Error";
static readonly Result: string = "Result";
}
Loading

0 comments on commit cd07ba8

Please sign in to comment.