Compare commits

...

7 Commits

Author SHA1 Message Date
MoChilia
2431008d0a test update node 16 to 20 2024-02-19 11:31:25 +08:00
Shiying Chen
aeb0c3630a Fix #403: Catch the error thrown in pre and post steps (#407)
* fix #403

* modify error handling
2024-01-17 17:37:07 +08:00
andy Augustin
2d38cb8921 docs: 📝 add warning about using self hosted runners on a single host (#394) (#397)
Co-authored-by: Yan Xu <yanxu1@microsoft.com>
2024-01-11 10:03:13 +08:00
Shiying Chen
3f2bf91b4d Fix #384: Remove az account clear in az cli (#398) 2024-01-08 18:26:20 +08:00
Shiying Chen
b5038826b1 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
2023-12-28 17:00:03 +08:00
Yan Xu
45c3280904 update workflow files for macos (#390) 2023-12-27 14:30:51 +08:00
Josh Soref
165d6877fd Update Workflows (#385)
* Consistently use azure/powershell@v1

* Consistently use actions/checkout@v4

* Consistently use actions/setup-node@v4

* Consistently use actions/github-script@v7

* Consistently use actions/stale@v8

* Specify codql language as javascript

See https://api.github.com/repos/github/codeql-action/issues/comments/762138640

* Name ci.yml

* CodeQL does not need a special code
2023-12-27 10:26:03 +08:00
20 changed files with 610 additions and 657 deletions

View File

@@ -30,7 +30,7 @@ jobs:
az --version
- name: Check out repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: 'Az CLI login with subscription'
uses: azure/login@v1

View File

@@ -9,86 +9,6 @@ permissions:
jobs:
OSTest:
runs-on: macos-latest
environment: Automation test
steps:
- name: 'Checking out repo code'
uses: actions/checkout@v3.5.2
- name: Set Node.js 16.x for GitHub Action
uses: actions/setup-node@v1
with:
node-version: 16.x
- name: 'Validate build'
run: |
npm install
npm run build
npm run test
- name: Login with creds
continue-on-error: true
uses: ./
with:
creds: ${{secrets.SP1}}
enable-AzPSSession: true
- name: Run Azure Cli
run: |
az account show --output none
az group show --name GitHubAction_CI_RG --output none
az vm list --output none
- name: Run Azure PowerShell
id: ps_1
continue-on-error: true
uses: azure/powershell@v1.2.0
with:
azPSVersion: "latest"
inlineScript: |
(Get-AzContext).Environment.Name -eq 'AzureCloud'
(Get-AzResourceGroup -Name GitHubAction_CI_RG).ResourceGroupName -eq 'GitHubAction_CI_RG'
(Get-AzVM).Count -gt 0
- name: Check Last step failed
if: steps.ps_1.outcome == 'success'
uses: actions/github-script@v3
with:
script: |
core.setFailed('Last action should fail but not. Please check it.')
- name: Login with individual parameters
id: login_2
uses: ./
with:
client-id: ${{ secrets.OIDC_SP2_CLIENT_ID }}
tenant-id: ${{ secrets.OIDC_SP2_TENANT_ID }}
# subscription-id: ${{ secrets.OIDC_SP2_SUBSCRIPTION_ID }}
allow-no-subscriptions: true
enable-AzPSSession: true
- name: Run Azure Cli again
run: |
az account show --output none
- name: Run Azure PowerShell again
id: ps_2
continue-on-error: true
uses: azure/powershell@v1.2.0
with:
azPSVersion: "latest"
inlineScript: |
(Get-AzContext).Environment.Name -eq 'AzureCloud'
- name: Check Last step failed
if: steps.ps_2.outcome == 'success'
uses: actions/github-script@v3
with:
script: |
core.setFailed('Last action should fail but not. Please check it.')
PermissionTest:
strategy:
matrix:
@@ -99,12 +19,12 @@ jobs:
steps:
- name: 'Checking out repo code'
uses: actions/checkout@v3.5.2
uses: actions/checkout@v4
- name: Set Node.js 16.x for GitHub Action
uses: actions/setup-node@v1
- name: Set Node.js 20.x for GitHub Action
uses: actions/setup-node@v4
with:
node-version: 16.x
node-version: 20.x
- name: 'Validate build'
run: |
@@ -130,7 +50,7 @@ jobs:
- name: Check Last step failed
if: steps.cli_3.outcome == 'success'
uses: actions/github-script@v3
uses: actions/github-script@v7
with:
script: |
core.setFailed('Last action should fail but not. Please check it.')
@@ -138,7 +58,7 @@ jobs:
- name: Run Azure PowerShell
id: ps_3
continue-on-error: true
uses: azure/powershell@v1.2.0
uses: azure/powershell@v1
with:
azPSVersion: "latest"
inlineScript: |
@@ -148,7 +68,7 @@ jobs:
- name: Check Last step failed
if: steps.ps_3.outcome == 'success'
uses: actions/github-script@v3
uses: actions/github-script@v7
with:
script: |
core.setFailed('Last action should fail but not. Please check it.')
@@ -162,12 +82,12 @@ jobs:
steps:
- name: 'Checking out repo code'
uses: actions/checkout@v3.5.2
uses: actions/checkout@v4
- name: Set Node.js 16.x for GitHub Action
uses: actions/setup-node@v1
- name: Set Node.js 20.x for GitHub Action
uses: actions/setup-node@v4
with:
node-version: 16.x
node-version: 20.x
- name: 'Validate build'
run: |
@@ -184,7 +104,7 @@ jobs:
- name: Check Last step failed
if: steps.login_4.outcome == 'success'
uses: actions/github-script@v3
uses: actions/github-script@v7
with:
script: |
core.setFailed('Last action should fail but not. Please check it.')
@@ -199,7 +119,7 @@ jobs:
- name: Check Last step failed
if: steps.login_5.outcome == 'success'
uses: actions/github-script@v3
uses: actions/github-script@v7
with:
script: |
core.setFailed('Last action should fail but not. Please check it.')
@@ -213,7 +133,7 @@ jobs:
- name: Check Last step failed
if: steps.login_6.outcome == 'success'
uses: actions/github-script@v3
uses: actions/github-script@v7
with:
script: |
core.setFailed('Last action should fail but not. Please check it.')
@@ -229,7 +149,7 @@ jobs:
- name: Check Last step failed
if: steps.login_7.outcome == 'success'
uses: actions/github-script@v3
uses: actions/github-script@v7
with:
script: |
core.setFailed('Last action should fail but not. Please check it.')
@@ -246,7 +166,7 @@ jobs:
- name: Check Last step failed
if: steps.login_8.outcome == 'success'
uses: actions/github-script@v3
uses: actions/github-script@v7
with:
script: |
core.setFailed('Last action should fail but not. Please check it.')
@@ -266,7 +186,7 @@ jobs:
- name: Run Azure PowerShell
id: ps_8
continue-on-error: true
uses: azure/powershell@v1.2.0
uses: azure/powershell@v1
with:
azPSVersion: "latest"
inlineScript: |
@@ -276,7 +196,7 @@ jobs:
- name: Check Last step failed
if: steps.ps_8.outcome == 'success'
uses: actions/github-script@v3
uses: actions/github-script@v7
with:
script: |
core.setFailed('Last action should fail but not. Please check it.')
@@ -296,7 +216,7 @@ jobs:
- name: Run Azure PowerShell
id: ps_9
continue-on-error: true
uses: azure/powershell@v1.2.0
uses: azure/powershell@v1
with:
azPSVersion: "latest"
inlineScript: |
@@ -306,7 +226,7 @@ jobs:
- name: Check Last step failed
if: steps.ps_9.outcome == 'success'
uses: actions/github-script@v3
uses: actions/github-script@v7
with:
script: |
core.setFailed('Last action should fail but not. Please check it.')
@@ -325,7 +245,7 @@ jobs:
- name: Check Last step failed
if: steps.login_10.outcome == 'success'
uses: actions/github-script@v3
uses: actions/github-script@v7
with:
script: |
core.setFailed('Last action should fail but not. Please check it.')
@@ -342,7 +262,7 @@ jobs:
- name: Check Last step failed
if: steps.login_11.outcome == 'success'
uses: actions/github-script@v3
uses: actions/github-script@v7
with:
script: |
core.setFailed('Last action should fail but not. Please check it.')
@@ -362,7 +282,7 @@ jobs:
- name: Check Last step failed
if: steps.login_12.outcome == 'success'
uses: actions/github-script@v3
uses: actions/github-script@v7
with:
script: |
core.setFailed('Last action should fail but not. Please check it.')
@@ -378,7 +298,7 @@ jobs:
- name: Check Last step failed
if: steps.login_13.outcome == 'success'
uses: actions/github-script@v3
uses: actions/github-script@v7
with:
script: |
core.setFailed('Last action should fail but not. Please check it.')
@@ -394,7 +314,7 @@ jobs:
- name: Check Last step failed
if: steps.login_14.outcome == 'success'
uses: actions/github-script@v3
uses: actions/github-script@v7
with:
script: |
core.setFailed('Last action should fail but not. Please check it.')
@@ -409,7 +329,7 @@ jobs:
- name: Check Last step failed
if: steps.login_15.outcome == 'success'
uses: actions/github-script@v3
uses: actions/github-script@v7
with:
script: |
core.setFailed('Last action should fail but not. Please check it.')
@@ -423,12 +343,12 @@ jobs:
steps:
- name: 'Checking out repo code'
uses: actions/checkout@v3.5.2
uses: actions/checkout@v4
- name: Set Node.js 16.x for GitHub Action
uses: actions/setup-node@v1
- name: Set Node.js 20.x for GitHub Action
uses: actions/setup-node@v4
with:
node-version: 16.x
node-version: 20.x
- name: 'Validate build'
run: |
@@ -442,7 +362,7 @@ jobs:
- name: Check Last step failed
if: steps.login_14.outcome == 'success'
uses: actions/github-script@v3
uses: actions/github-script@v7
with:
script: |
core.setFailed('Last action should fail but not. Please check it.')

View File

@@ -12,18 +12,18 @@ jobs:
BasicTest:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, self_linux, self_windows]
os: [ubuntu-latest, windows-latest, macos-latest, self_linux, self_windows]
runs-on: ${{ matrix.os }}
environment: Automation test
steps:
- name: 'Checking out repo code'
uses: actions/checkout@v3.5.2
uses: actions/checkout@v4
- name: Set Node.js 16.x for GitHub Action
uses: actions/setup-node@v1
- name: Set Node.js 20.x for GitHub Action
uses: actions/setup-node@v4
with:
node-version: 16.x
node-version: 20.x
- name: 'Validate build'
run: |
@@ -47,7 +47,7 @@ jobs:
az vm list --output none
- name: Run Azure PowerShell
uses: azure/powershell@v1.2.0
uses: azure/powershell@v1
with:
azPSVersion: "latest"
inlineScript: |
@@ -71,7 +71,7 @@ jobs:
az account show --output none
- name: Run Azure PowerShell again
uses: azure/powershell@v1.2.0
uses: azure/powershell@v1
with:
azPSVersion: "latest"
inlineScript: |
@@ -94,7 +94,7 @@ jobs:
az vm list --output none
- name: Run Azure PowerShell
uses: azure/powershell@v1.2.0
uses: azure/powershell@v1
with:
azPSVersion: "latest"
inlineScript: |
@@ -108,18 +108,18 @@ jobs:
ParameterTest:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
environment: Automation test
steps:
- name: 'Checking out repo code'
uses: actions/checkout@v3.5.2
uses: actions/checkout@v4
- name: Set Node.js 16.x for GitHub Action
uses: actions/setup-node@v1
- name: Set Node.js 20.x for GitHub Action
uses: actions/setup-node@v4
with:
node-version: 16.x
node-version: 20.x
- name: 'Validate build'
run: |
@@ -164,7 +164,7 @@ jobs:
az vm list --output none
- name: Run Azure PowerShell
uses: azure/powershell@v1.2.0
uses: azure/powershell@v1
with:
azPSVersion: "latest"
inlineScript: |
@@ -189,7 +189,7 @@ jobs:
az account show --output none
- name: Run Azure PowerShell again
uses: azure/powershell@v1.2.0
uses: azure/powershell@v1
with:
azPSVersion: "latest"
inlineScript: |
@@ -207,17 +207,21 @@ jobs:
enable-AzPSSession: true
- name: Run Azure Cli
shell: pwsh
run: |
az account show --output none
$checkResult = (az account list --output json | ConvertFrom-Json).Count -eq 2
if(-not $checkResult){
throw "Not all checks passed!"
}
- name: Run Azure PowerShell
uses: azure/powershell@v1.2.0
uses: azure/powershell@v1
with:
azPSVersion: "latest"
inlineScript: |
$checkResult = (Get-AzContext).Environment.Name -eq 'AzureCloud'
$checkResult = (Get-AzContext -ListAvailable).Count -eq 2
if(-not $checkResult){
throw "Not all checks passed!"
throw "Not all checks passed!"
}
- name: Login with creds, no subscription, allow no subscription
@@ -232,7 +236,7 @@ jobs:
az account show --output none
- name: Run Azure PowerShell
uses: azure/powershell@v1.2.0
uses: azure/powershell@v1
with:
azPSVersion: "latest"
inlineScript: |
@@ -250,12 +254,12 @@ jobs:
steps:
- name: 'Checking out repo code'
uses: actions/checkout@v3.5.2
uses: actions/checkout@v4
- name: Set Node.js 16.x for GitHub Action
uses: actions/setup-node@v1
- name: Set Node.js 20.x for GitHub Action
uses: actions/setup-node@v4
with:
node-version: 16.x
node-version: 20.x
- name: 'Validate build'
run: |
@@ -274,7 +278,7 @@ jobs:
az account show --output none
- name: Run Azure PowerShell
uses: azure/powershell@v1.2.0
uses: azure/powershell@v1
with:
azPSVersion: "latest"
inlineScript: |
@@ -297,7 +301,7 @@ jobs:
az vm list --output none
- name: Run Azure PowerShell
uses: azure/powershell@v1.2.0
uses: azure/powershell@v1
with:
azPSVersion: "latest"
inlineScript: |
@@ -321,7 +325,7 @@ jobs:
az account show --output none
- name: Run Azure PowerShell
uses: azure/powershell@v1.2.0
uses: azure/powershell@v1
with:
azPSVersion: "latest"
inlineScript: |
@@ -345,7 +349,7 @@ jobs:
az vm list --output none
- name: Run Azure PowerShell
uses: azure/powershell@v1.2.0
uses: azure/powershell@v1
with:
azPSVersion: "latest"
inlineScript: |

View File

@@ -11,16 +11,16 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout from PR branch
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.ref }}
# Using 16.x version as an example
- name: Set Node.js 16.x for GitHub Action
uses: actions/setup-node@v1
# Using 20.x version as an example
- name: Set Node.js 20.x for GitHub Action
uses: actions/setup-node@v4
with:
node-version: 16.x
node-version: 20.x
- name: installing node_modules
run: npm install

View File

@@ -1,3 +1,5 @@
name: Build and Test
on:
pull_request:
branches:
@@ -16,12 +18,12 @@ jobs:
steps:
- name: 'Checking out repo code'
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Set Node.js 16.x for GitHub Action
uses: actions/setup-node@v1
- name: Set Node.js 20.x for GitHub Action
uses: actions/setup-node@v4
with:
node-version: 16.x
node-version: 20.x
- name: 'Validate build'
run: |

View File

@@ -14,23 +14,13 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
uses: actions/checkout@v4
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
with:
languages: javascript
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)

View File

@@ -14,7 +14,7 @@ jobs:
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
- uses: actions/stale@v3
- uses: actions/stale@v8
name: Setting issue as idle
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
@@ -25,7 +25,7 @@ jobs:
operations-per-run: 100
exempt-issue-labels: 'backlog'
- uses: actions/stale@v3
- uses: actions/stale@v8
name: Setting PR as idle
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -7,11 +7,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 16.x
node-version: 20.x
- name: Run Markdownlint
run: |
npm i -g markdownlint-cli2

View File

@@ -235,7 +235,7 @@ jobs:
az account show
- name: Azure PowerShell script
uses: azure/powershell@v1.2.0
uses: azure/powershell@v1
with:
azPSVersion: "latest"
inlineScript: |
@@ -323,7 +323,7 @@ jobs:
az account show
- name: Azure PowerShell script
uses: azure/powershell@v1.2.0
uses: azure/powershell@v1
with:
azPSVersion: "latest"
inlineScript: |
@@ -394,7 +394,7 @@ jobs:
az account show
- name: Azure PowerShell script
uses: azure/powershell@v1.2.0
uses: azure/powershell@v1
with:
azPSVersion: "latest"
inlineScript: |
@@ -461,7 +461,7 @@ jobs:
az account show
- name: Azure PowerShell script
uses: azure/powershell@v1.2.0
uses: azure/powershell@v1
with:
azPSVersion: "latest"
inlineScript: |
@@ -549,7 +549,7 @@ jobs:
az account show
- name: Run Azure PowerShell
uses: azure/powershell@v1.2.0
uses: azure/powershell@v1
with:
azPSVersion: "latest"
inlineScript: |
@@ -560,6 +560,9 @@ jobs:
This action doesn't implement ```az logout``` by default at the end of execution. However, there is no way to tamper with the credentials or account information because the GitHub-hosted runner is on a VM that will get re-imaged for every customer run, which deletes everything. But if the runner is self-hosted (not provided by GitHub), it is recommended to manually log out at the end of the workflow, as shown below. More details on security of the runners can be found [here](https://docs.github.com/actions/learn-github-actions/security-hardening-for-github-actions#hardening-for-self-hosted-runners).
> [!WARNING]
> When using self hosted runners it is possible to have multiple runners on a single VM. Currently if your runners share a single user on the VM each runner will share the same credentials. That means in detail that each runner is able to change the permissions of another run. As a workaround we propose to use one single VM user per runner. If you start the runner as a service, do not forget to add the [optional user argument](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/configuring-the-self-hosted-runner-application-as-a-service#installing-the-service)
```yaml
- name: Azure CLI script
uses: azure/CLI@v1
@@ -570,7 +573,7 @@ This action doesn't implement ```az logout``` by default at the end of execution
az account clear
- name: Azure PowerShell script
uses: azure/powershell@v1.2.0
uses: azure/powershell@v1
with:
azPSVersion: "latest"
inlineScript: |

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

@@ -38,5 +38,7 @@ branding:
icon: 'login.svg'
color: 'blue'
runs:
using: 'node16'
using: 'node20'
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 = "";
@@ -34,8 +31,6 @@ export class AzureCliLogin {
await this.executeAzCliCommand(["--version"], true, execOptions);
core.debug(`Azure CLI version used:\n${output}`);
await this.executeAzCliCommand(["account", "clear"], true, execOptions);
this.setAzurestackEnvIfNecessary();
await this.executeAzCliCommand(["cloud", "set", "-n", this.loginConfig.environment], false);

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.warning(`Login cleanup failed with ${error}. Cleanup will be skipped.`);
core.debug(error.stack);
}
}
cleanup();

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

@@ -0,0 +1,30 @@
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);
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);
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();