diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dbe4984fbaca..459eb54b7a98 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -34,6 +34,8 @@ Tasks/AzureCLIV1/ @microsoft/release-management-task-team @manolerazvan Tasks/AzureCLIV2/ @microsoft/release-management-task-team @manolerazvan +Tasks/AzureCLIV3/ @microsoft/release-management-task-team @manolerazvan + Tasks/AzureCloudPowerShellDeploymentV1/ @microsoft/release-management-task-team @manolerazvan Tasks/AzureCloudPowerShellDeploymentV2/ @microsoft/release-management-task-team @manolerazvan diff --git a/Tasks/AzureCLIV3/.npmrc b/Tasks/AzureCLIV3/.npmrc new file mode 100644 index 000000000000..d5c7fef620a3 --- /dev/null +++ b/Tasks/AzureCLIV3/.npmrc @@ -0,0 +1,5 @@ +scripts-prepend-node-path=true + +registry=https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/ + +always-auth=true \ No newline at end of file diff --git a/Tasks/AzureCLIV3/Readme.md b/Tasks/AzureCLIV3/Readme.md new file mode 100644 index 000000000000..fcabdb025a30 --- /dev/null +++ b/Tasks/AzureCLIV3/Readme.md @@ -0,0 +1,70 @@ +# Azure CLI + +## Overview +This task supports running [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/overview) commands on Cross platform agents running Windows, Linux or Mac. + +### What's new in Version 3.0 +- Azure DevOps service connections support with automatic CLI extension installation +- Workload Identity Federation support for Azure DevOps connections +- Automatic organization and project configuration from pipeline context + +## Contact Information +Please report a problem at [Developer Community Forum](https://developercommunity.visualstudio.com/spaces/21/index.html) if you are facing problems in making this task work. You can also share feedback about the task like, what more functionality should be added to the task, what other tasks you would like to have, at the same place. + +## Pre-requisites for the task +The following pre-requisites need to be setup in the target machine(s) for the task to work properly. + +#### **Azure Subscription** +To deploy to Azure, an Azure subscription has to be linked to Team Foundation Server or to Azure Pipelines using the Services tab in the settings section. Add the Azure subscription to use in the Build or Release Management definition by opening the Account Administration screen (gear icon on the top-right of the screen) and then click on the Services Tab. +- For Azure Classic resources use 'Azure' endpoint type with Certificate or Credentials based authentication. If you are using credentials based auth, ensure that the credentials are for a [**work account**](https://azure.microsoft.com/en-in/pricing/member-offers/msdn-benefits-details/work-accounts-faq/) because Microsoft accounts like [**joe@live.com**](https://github.com/Microsoft/azure-pipelines-tasks/blob/master/Tasks/DeployAzureResourceGroup) or [**joe@hotmail.com**](https://github.com/Microsoft/azure-pipelines-tasks/blob/master/Tasks/DeployAzureResourceGroup) are not supported. +- For [ARM](https://azure.microsoft.com/en-in/documentation/articles/resource-group-overview/), use 'Azure Resource Manager' endpoint type, for more details follow the steps listed in the link [here](https://go.microsoft.com/fwlink/?LinkID=623000&clcid=0x409). + +#### **Azure DevOps Service Connection** +For Azure DevOps CLI operations, you can use Azure DevOps service connections with Workload Identity Federation authentication. This allows secure access to Azure DevOps resources. The task will automatically install and configure the Azure DevOps CLI extension when using this connection type. + +#### **Azure CLI** +The task needs the Azure CLI version to be installed on the automation agent, and the details are available [here](https://azure.microsoft.com/en-us/documentation/articles/xplat-cli-install/). +If an agent is already running on the machine on which the Azure CLI is installed, ensure to restart the agent to ensure all the relevant environment variables are updated. + +## Parameters of the task +The task is used to run Azure CLI commands on Cross platform agents running Windows, Linux or Mac . The mandatory fields are highlighted with a *. + +* **Connection Type**\*: Select the type of service connection to use. Choose 'Azure Resource Manager' for Azure Resource Manager service connections or 'Azure DevOps' for Azure DevOps service connections. + +* **Azure Resource Manager Connection**\*: Select the Azure Resource Manager service connection. This field is visible when Connection Type is set to 'Azure Resource Manager'. + +* **Azure DevOps Service Connection**\*: Select an Azure DevOps service connection. This field is visible when Connection Type is set to 'Azure DevOps'. + +* **Script Type**\*: Select the type of script to be executed on the agent. Task supports four types: Batch / Shell / PowerShell / PowerShell Core scripts, default selection being empty. Select Shell/PowerShell Core script when running on Linux agent or Batch/PowerShell/PowerShell Core script when running on Windows agent. PowerShell Core script can run on cross-platform agents (Linux, macOS, or Windows) + +* **Script Location**\*: Select the mode of providing the script. Task supports two modes: one as a Script Path to a linked artifact and another as an inline script, default selection being the "Script Path" + +* **Script Path**\*: When using Windows based agent, specify the path to the .bat , .cmd , .ps1 script whereas when using Linux based agent, specify the path to the .sh , .ps1 script you want to run. The path must be a fully qualified path or a valid path relative to the default working directory. Note: You must also specify the respective script type in above field. + +* **Inline Script**\*: Specify the script inline here. When using Windows based agent use batch or PowerShell or PowerShell Core scripting whereas use shell or PowerShell Core scripting when using Linux based agents. Note: You must also specify the respective script type in above field. + +* **Script Arguments**: Specify arguments to pass to the script. + +* **Working folder**: Specify the working directory in which you want to run the script. If you leave it empty, the working directory is the folder where the script is located. + +* **Fail on standard error**: Select this check box if you want the build to fail if errors are written to the StandardError stream. + +* **Access service principal details in script**: Select this check box if you want to add service principal id , service principal key and tenantId of the Azure endpoint to the script's execution environment. You can use variables: `servicePrincipalId`, `servicePrincipalKey` and `tenantId` in your script. This is honored only when the Azure endpoint has Service Principal authentication scheme. \ +\ +Syntax to access environment variables based on script type.\ +*Powershell script:* `$env:servicePrincipalId`\ +*Batch script:* `%servicePrincipalId%` \ +*Shell script:* `$servicePrincipalId` + +* **ErrorActionPreference**: Select this checkbox if you want the task to fail when any errors are written to the StandardError stream. If you leave it unchecked, standard errors will be ignored and only exit codes will be used to determine the status. + +* **Use global Azure CLI configuration**: If this is unchecked, the task will use its own separate Azure CLI configuration directory. This allows Azure CLI tasks to run in parallel during releases. + +* **Working Directory**: Current working directory where the script is run. If left blank, this input is the root of the repo (build) or artifacts (release), which is $(System.DefaultWorkingDirectory). + +* **LASTEXITCODE**: If this input is false, the line if ((Test-Path -LiteralPath variable:\LASTEXITCODE)) { exit $LASTEXITCODE } is appended to the end of your script. This will propagate the last exit code from an external command as the exit code of PowerShell. Otherwise, the line is not appended to the end of your script. + +* **az login output visibility**: If this is set to true, az login command will output to the task. Setting it to false will suppress the az login output. + +* **Keep Azure CLI session active**: When enabled, this task will continuously sign into Azure to avoid AADSTS700024 errors when requesting access tokens beyond the IdToken expiry date. Note that this feature is EXPERIMENTAL, may not work in all scenarios and you are using it without any guarantees. Valid only for service connections using the Workload Identity Federation authentication scheme. + \ No newline at end of file diff --git a/Tasks/AzureCLIV3/Strings/resources.resjson/en-US/resources.resjson b/Tasks/AzureCLIV3/Strings/resources.resjson/en-US/resources.resjson new file mode 100644 index 000000000000..56f32d67906f --- /dev/null +++ b/Tasks/AzureCLIV3/Strings/resources.resjson/en-US/resources.resjson @@ -0,0 +1,67 @@ +{ + "loc.friendlyName": "Azure CLI", + "loc.helpMarkDown": "[Learn more about this task](http://github.com/microsoft/azure-pipelines-tasks/blob/master/Tasks/AzureCLIV3/Readme.md) or [see the Azure CLI documentation](https://docs.microsoft.com/cli/azure/)", + "loc.description": "Run Azure CLI commands against an Azure subscription in a PowerShell Core/Shell script when running on Linux agent or PowerShell/PowerShell Core/Batch script when running on Windows agent.", + "loc.instanceNameFormat": "Azure CLI $(scriptPath)", + "loc.releaseNotes": "What's new in Version 3.0:\n- Support for dual connection types: Azure Resource Manager and Azure DevOps service connections. \n- Azure DevOps CLI integration with automatic extension installation and configuration. \n- Workload Identity Federation support for Azure DevOps connections.", + "loc.group.displayName.advanced": "Advanced", + "loc.input.label.connectionType": "Connection Type", + "loc.input.help.connectionType": "Type of service connection to use", + "loc.input.label.connectedServiceNameARM": "Azure Resource Manager connection", + "loc.input.help.connectedServiceNameARM": "Select an Azure Resource Manager service connection for the deployment", + "loc.input.label.azureDevOpsServiceConnection": "Azure DevOps Service Connection", + "loc.input.help.azureDevOpsServiceConnection": "Select an Azure DevOps service connection", + "loc.input.label.scriptType": "Script Type", + "loc.input.help.scriptType": "Type of script: PowerShell/PowerShell Core/Bat/Shell script. Select Shell/PowerShell Core script when running on Linux agent or Batch/PowerShell/PowerShell Core script when running on Windows agent. PowerShell Core script can run on cross-platform agents (Linux, macOS, or Windows).", + "loc.input.label.scriptLocation": "Script Location", + "loc.input.help.scriptLocation": "Path to script: File path or Inline script", + "loc.input.label.scriptPath": "Script Path", + "loc.input.help.scriptPath": "Fully qualified path of the script(.ps1 or .bat or .cmd when using Windows based agent else .ps1 or .sh when using linux based agent) or a path relative to the the default working directory", + "loc.input.label.inlineScript": "Inline Script", + "loc.input.help.inlineScript": "You can write your scripts inline here. When using Windows agent, use PowerShell or PowerShell Core or batch scripting whereas use PowerShell Core or shell scripting when using Linux based agents. For batch files use the prefix \"call\" before every azure command. You can also pass predefined and custom variables to this script using arguments \n\n example for PowerShell/PowerShellCore/shell: \naz --version \naz account show \n\n example for batch:\ncall az --version \ncall az account show", + "loc.input.label.scriptArguments": "Script Arguments", + "loc.input.help.scriptArguments": "Arguments passed to the script", + "loc.input.label.powerShellErrorActionPreference": "ErrorActionPreference", + "loc.input.help.powerShellErrorActionPreference": "Prepends the line `$ErrorActionPreference = 'VALUE'` at the top of your powershell/powershell core script.", + "loc.input.label.addSpnToEnvironment": "Access service principal details in script", + "loc.input.help.addSpnToEnvironment": "Adds service principal id, service principal key and tenant id of the Azure endpoint you chose to the script's execution environment. You can use variables: `servicePrincipalId`, `servicePrincipalKey` and `tenantId` in your script.\n\nThis is honored only when the Azure endpoint has Service Principal authentication scheme.\n\nSyntax to access environment variables based on script type.\n\nPowershell script: $env:servicePrincipalId\n\nBatch script: %servicePrincipalId% \n\nShell script: $servicePrincipalId", + "loc.input.label.useGlobalConfig": "Use global Azure CLI configuration", + "loc.input.help.useGlobalConfig": "If this is false, this task will use its own separate [Azure CLI configuration directory](https://docs.microsoft.com/en-us/cli/azure/azure-cli-configuration?view=azure-cli-latest#cli-configuration-file). This can be used to run Azure CLI tasks in *parallel* releases", + "loc.input.label.cwd": "Working Directory", + "loc.input.help.cwd": "Current working directory where the script is run. Empty is the root of the repo (build) or artifacts (release), which is $(System.DefaultWorkingDirectory)", + "loc.input.label.failOnStandardError": "Fail on Standard Error", + "loc.input.help.failOnStandardError": "If this is true, this task will fail when any errors are written to the StandardError stream. Unselect the checkbox to ignore standard errors and rely on exit codes to determine the status", + "loc.input.label.powerShellIgnoreLASTEXITCODE": "Ignore $LASTEXITCODE", + "loc.input.help.powerShellIgnoreLASTEXITCODE": "If this is false, the line `if ((Test-Path -LiteralPath variable:\\LASTEXITCODE)) { exit $LASTEXITCODE }` is appended to the end of your script. This will cause the last exit code from an external command to be propagated as the exit code of powershell. Otherwise the line is not appended to the end of your script.", + "loc.input.label.visibleAzLogin": "az login output visibility", + "loc.input.help.visibleAzLogin": "If this is set to true, az login command will output to the task. Setting it to false will suppress the az login output", + "loc.input.label.keepAzSessionActive": "[Experimental] Keep Azure CLI session active", + "loc.input.help.keepAzSessionActive": "When enabled, this task will continuously sign into Azure to avoid AADSTS700024 errors when requesting access tokens beyond the IdToken expiry date. Note that this feature is EXPERIMENTAL, may not work in all scenarios and you are using it without any guarantees. Valid only for service connections using the Workload Identity Federation authentication scheme.", + "loc.messages.ScriptReturnCode": "Script exited with return code: %d", + "loc.messages.ScriptFailed": "Script failed with error: %s", + "loc.messages.ScriptFailedStdErr": "Script has output to stderr. Failing as failOnStdErr is set to true.", + "loc.messages.ScriptFailedWithExitCode": "Script failed with exit code: %d", + "loc.messages.UnsupportedEndpointScheme": "Unsupported service connection authorization scheme: Service Principal for AzureRM", + "loc.messages.AzureSDKNotFound": "Azure CLI 2.x is not installed on this machine.", + "loc.messages.FailedToLogout": "The following error occurred while logging out: %s", + "loc.messages.LoginFailed": "Azure login failed", + "loc.messages.MSILoginFailed": "Azure login failed using Managed Service Identity", + "loc.messages.AuthSchemeNotSupportedForAzureRM": "The authentication scheme '%s' is not supported for Azure Resource Manager connections. Please update your Azure Resource Manager service connection to use one of the supported schemes: Service Principal, Workload Identity Federation (WIF), or Managed Identity.", + "loc.messages.AuthSchemeNotSupportedForAzureDevOps": "The authentication scheme '%s' is not supported for Azure DevOps service connections. Please update your Azure DevOps service connection to use one of the supported schemes: Workload Identity Federation. ", + "loc.messages.ErrorInSettingUpSubscription": "Error in setting up subscription", + "loc.messages.SettingAzureConfigDir": "Setting AZURE_CONFIG_DIR env variable to: %s", + "loc.messages.SettingAzureCloud": "Setting active cloud to: %s", + "loc.messages.JS_InvalidFilePath": "Script file could not be found at specified script location: '%s'. Please verify the script exists at the specified path. If you want to use inline script, specify input `Script Location` as `inlineScript`.", + "loc.messages.JS_InvalidErrorActionPreference": "Invalid ErrorActionPreference '%s'. The value must be one of: 'Stop', 'Continue', or 'SilentlyContinue'", + "loc.messages.GlobalCliConfigAgentVersionWarning": "For agent version < 2.115.0, only global Azure CLI configuration can be used", + "loc.messages.UnacceptedScriptLocationValue": "%s is not a valid value for task input 'Script Location' (scriptLocation in YAML). Value can either be'inlineScript' or 'scriptPath'", + "loc.messages.ExpiredServicePrincipalMessageWithLink": "Secret expired, update service connection at %s See https://aka.ms/azdo-rm-workload-identity-conversion to learn more about conversion to secret-less service connections.", + "loc.messages.ProxyConfig": "az tool is configured to use %s as proxy server", + "loc.messages.FailedToRefreshAzSession": "The following error occurred while trying to refresh az-cli session: %s", + "loc.messages.RefreshingAzSession": "Attempting to refresh az-cli session...", + "loc.messages.KeepingAzSessionActiveUnsupportedScheme": "The 'keepAzSessionActive' input might be used only for workload identity federation ARM service connection. The referenced service endpoint auth scheme was unexpected: %s. Change the scheme or remove 'keepAzSessionActive' input.", + "loc.messages.FailedToInstallAzureDevOpsCLI": "Failed to install Azure DevOps CLI extension", + "loc.messages.FailedToLoginAzureDevOpsCLI": "Failed to login to Azure DevOps CLI", + "loc.messages.FailedToSetAzureDevOpsOrganization": "Failed to set Azure DevOps organization", + "loc.messages.FailedToSetAzureDevOpsProject": "Failed to set Azure DevOps project" +} \ No newline at end of file diff --git a/Tasks/AzureCLIV3/Tests/.npmrc b/Tasks/AzureCLIV3/Tests/.npmrc new file mode 100644 index 000000000000..969ccea07661 --- /dev/null +++ b/Tasks/AzureCLIV3/Tests/.npmrc @@ -0,0 +1,3 @@ +registry=https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/ + +always-auth=true \ No newline at end of file diff --git a/Tasks/AzureCLIV3/Tests/L0.ts b/Tasks/AzureCLIV3/Tests/L0.ts new file mode 100644 index 000000000000..04fdd2e09844 --- /dev/null +++ b/Tasks/AzureCLIV3/Tests/L0.ts @@ -0,0 +1,264 @@ +import assert = require('assert'); +import path = require('path'); +import * as ttm from 'azure-pipelines-task-lib/mock-test'; + +describe('AzureCLIV3 Suite', function () { + const timeout = 30000; + + before(() => { + }); + + after(() => { + }); + + it('Should handle Azure DevOps connection with Workload Identity Federation', function (done) { + this.timeout(timeout); + + let tp = path.join(__dirname, 'L0AzureDevOpsWifConnection.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + tr.runAsync().then(() => { + assert(tr.stdout.includes('az version') || tr.stdout.includes('az --version'), 'Should execute az version command'); + assert(tr.stdout.includes('az extension add -n azure-devops'), 'Should install Azure DevOps extension'); + assert(tr.stdout.includes('az login --service-principal'), 'Should login with service principal'); + assert(tr.stdout.includes('az devops configure --defaults organization'), 'Should configure Azure DevOps organization'); + assert(tr.stdout.includes('az devops configure --defaults project'), 'Should configure Azure DevOps project'); + assert(tr.stdout.indexOf('Azure DevOps CLI extension installed') >= 0, 'should install Azure DevOps extension'); + assert(tr.stdout.indexOf('organization configured') >= 0, 'should configure organization'); + assert(tr.stdout.indexOf('project configured') >= 0, 'should configure project'); + done(); + }).catch((err) => { + done(err); + }); + }); + + it('Should fail with unsupported authentication scheme for Azure DevOps', function (done) { + this.timeout(timeout); + + let tp = path.join(__dirname, 'L0AzureDevOpsUnsupportedAuthScheme.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + tr.runAsync().then(() => { + assert(tr.failed, 'should have failed'); + assert(tr.stdout.indexOf('loc_mock_AuthSchemeNotSupportedForAzureDevOps ServicePrincipal') >= 0, 'Should have failed with unsupported auth scheme error'); + done(); + }).catch((err) => { + done(err); + }); + }); + + it('Should skip organization configuration when SYSTEM_COLLECTIONURI is missing', function (done) { + this.timeout(timeout); + + let tp = path.join(__dirname, 'L0AzureDevOpsMissingOrganization.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + tr.runAsync().then(() => { + assert(tr.stdout.includes('az login --service-principal'), 'Should login with service principal'); + assert(tr.stdout.includes('az devops configure --defaults project'), 'Should configure Azure DevOps project'); + assert(tr.stdout.indexOf('project configured') >= 0, 'should configure project'); + assert(!tr.stdout.includes('az devops configure --defaults organization="https://dev.azure.com/testorg/"'), 'Should NOT configure Azure DevOps organization'); + assert(!tr.stdout.includes('az devops configure --defaults organization="undefined"'), 'Should NOT attempt organization config with undefined'); + assert(!tr.stdout.includes('az devops configure --defaults organization="null"'), 'Should NOT attempt organization config with null'); + assert(!tr.stdout.includes('az devops configure --defaults organization=""'), 'Should NOT attempt organization config with empty string'); + done(); + }).catch((err) => { + done(err); + }); + }); + + it('Should skip project configuration when SYSTEM_TEAMPROJECT is missing', function (done) { + this.timeout(timeout); + + let tp = path.join(__dirname, 'L0AzureDevOpsMissingProject.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + tr.runAsync().then(() => { + assert(tr.stdout.includes('az login --service-principal'), 'Should login with service principal'); + assert(tr.stdout.includes('az devops configure --defaults organization'), 'Should configure Azure DevOps organization'); + assert(tr.stdout.indexOf('organization configured') >= 0, 'should configure organization'); + assert(!tr.stdout.includes('az devops configure --defaults project="TestProject"'), 'Should NOT configure Azure DevOps project'); + assert(!tr.stdout.includes('az devops configure --defaults project="undefined"'), 'Should NOT attempt project config with undefined'); + assert(!tr.stdout.includes('az devops configure --defaults project="null"'), 'Should NOT attempt project config with null'); + assert(!tr.stdout.includes('az devops configure --defaults project=""'), 'Should NOT attempt project config with empty string'); + done(); + }).catch((err) => { + done(err); + }); + }); + + it('Should skip extension installation when Azure DevOps extension is already installed', function (done) { + this.timeout(timeout); + + let tp = path.join(__dirname, 'L0AzureDevOpsExtensionAlreadyInstalled.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + tr.runAsync().then(() => { + assert(tr.stdout.includes('Azure DevOps extension is already installed, skipping installation'), 'Should check if extension is installed and skip installation'); + assert(!tr.stdout.includes('az extension add -n azure-devops'), 'Should NOT install Azure DevOps extension'); + assert(tr.stdout.includes('az login --service-principal'), 'Should login with service principal'); + assert(tr.stdout.includes('az devops configure --defaults organization'), 'Should configure Azure DevOps organization'); + assert(tr.stdout.includes('az devops configure --defaults project'), 'Should configure Azure DevOps project'); + done(); + }).catch((err) => { + done(err); + }); + }); + + it('Should handle OIDC token retrieval for Azure DevOps authentication', function (done) { + this.timeout(timeout); + + let tp = path.join(__dirname, 'L0AzureDevOpsOidcTokenRetrieval.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + tr.runAsync().then(() => { + assert(tr.succeeded, 'should have succeeded'); + assert(tr.stdout.includes('az login --service-principal'), 'Should use federated token for login'); + assert(tr.stdout.includes('--federated-token'), 'Should include federated token parameter'); + assert(tr.stdout.includes('--allow-no-subscriptions'), 'Should allow login without subscriptions'); + done(); + }).catch((err) => { + done(err); + }); + }); + + it('Should fail when OIDC token retrieval fails', function (done) { + this.timeout(timeout); + + let tp = path.join(__dirname, 'L0AzureDevOpsOidcTokenFailure.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + tr.runAsync().then(() => { + assert(tr.failed, 'should have failed'); + assert(tr.stdout.indexOf('Failed to setup Azure DevOps CLI') >= 0, 'Should fail with OIDC token error'); + done(); + }).catch((err) => { + done(err); + }); + }); + + it('Should handle Azure DevOps extension installation failure gracefully', function (done) { + this.timeout(timeout); + + let tp = path.join(__dirname, 'L0AzureDevOpsExtensionInstallFailure.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + tr.runAsync().then(() => { + assert(tr.failed, 'should have failed'); + assert(tr.stdout.includes('Azure DevOps extension not found in working environment'), 'Should check if extension is installed'); + assert(tr.stdout.includes('az extension add -n azure-devops'), 'Should attempt to install Azure DevOps extension'); + assert(tr.stdout.indexOf('loc_mock_FailedToInstallAzureDevOpsCLI') >= 0, 'Should fail with extension installation error'); + done(); + }).catch((err) => { + done(err); + }); + }); + + it('Should validate environment variables are set for Azure DevOps authentication', function (done) { + this.timeout(timeout); + + let tp = path.join(__dirname, 'L0AzureDevOpsEnvironmentVariables.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + tr.runAsync().then(() => { + assert(tr.succeeded, 'should have succeeded'); + assert(tr.stdout.includes('az login --service-principal'), 'Should login with service principal'); + done(); + }).catch((err) => { + done(err); + }); + }); + + it('Should properly cleanup Azure DevOps configuration on task completion', function (done) { + this.timeout(timeout); + + let tp = path.join(__dirname, 'L0AzureDevOpsCleanup.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + tr.runAsync().then(() => { + assert(tr.succeeded, 'should have succeeded'); + assert(tr.stdout.includes('az devops configure --defaults project=\'\' organization='), 'Should clear Azure DevOps configuration'); + done(); + }).catch((err) => { + done(err); + }); + }); + + it('Should handle authentication with visible Azure login enabled', function (done) { + this.timeout(timeout); + + let tp = path.join(__dirname, 'L0AzureDevOpsVisibleLogin.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + tr.runAsync().then(() => { + assert(tr.succeeded, 'should have succeeded'); + assert(tr.stdout.includes('az login --service-principal'), 'Should login with service principal'); + assert(!tr.stdout.includes('--output none'), 'Should not suppress login output when visible login is enabled'); + done(); + }).catch((err) => { + done(err); + }); + }); + + it('Should handle authentication with organization URL containing special characters', function (done) { + this.timeout(timeout); + + let tp = path.join(__dirname, 'L0AzureDevOpsSpecialCharacters.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + tr.runAsync().then(() => { + assert(tr.succeeded, 'should have succeeded'); + assert(tr.stdout.includes('az devops configure --defaults organization'), 'Should configure organization'); + assert(tr.stdout.includes('az devops configure --defaults project'), 'Should configure project'); + done(); + }).catch((err) => { + done(err); + }); + }); + + it('Should fail with invalid connectionType input', function (done) { + this.timeout(timeout); + + let tp = path.join(__dirname, 'L0ConnectionTypeValidation.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + tr.runAsync().then(() => { + assert(tr.failed, 'should have failed with invalid connectionType'); + assert(tr.stderr.includes('Unsupported connection type: invalidConnectionType') || tr.errorIssues.some(issue => issue.includes('Unsupported connection type: invalidConnectionType')), 'Should fail with unsupported connection type error'); + done(); + }).catch((err) => { + done(err); + }); + }); + + it('Should handle Azure DevOps organization configuration error gracefully', function (done) { + this.timeout(timeout); + + let tp = path.join(__dirname, 'L0AzureDevOpsOrganizationConfigError.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + tr.runAsync().then(() => { + assert(tr.failed, 'should have failed'); + assert(tr.stdout.indexOf('loc_mock_FailedToSetAzureDevOpsOrganization') >= 0, 'Should fail with organization configuration error message'); + done(); + }).catch((err) => { + done(err); + }); + }); + + it('Should handle Azure DevOps project configuration error gracefully', function (done) { + this.timeout(timeout); + + let tp = path.join(__dirname, 'L0AzureDevOpsProjectConfigError.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + tr.runAsync().then(() => { + assert(tr.failed, 'should have failed'); + assert(tr.stdout.indexOf('loc_mock_FailedToSetAzureDevOpsProject') >= 0, 'Should fail with project configuration error message'); + done(); + }).catch((err) => { + done(err); + }); + }); +}); + \ No newline at end of file diff --git a/Tasks/AzureCLIV3/Tests/L0AzureDevOpsCleanup.ts b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsCleanup.ts new file mode 100644 index 000000000000..d8cee5289c93 --- /dev/null +++ b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsCleanup.ts @@ -0,0 +1,176 @@ +import ma = require('azure-pipelines-task-lib/mock-answer'); +import tmrm = require('azure-pipelines-task-lib/mock-run'); +import path = require('path'); + +let taskPath = path.join(__dirname, '..', 'azureclitask.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +tmr.setInput('connectionType', 'azureDevOps'); +tmr.setInput('azureDevOpsServiceConnection', 'TestAzureDevOpsConnection'); +tmr.setInput('scriptType', 'bash'); +tmr.setInput('scriptLocation', 'inlineScript'); +tmr.setInput('inlineScript', 'echo "test"'); +tmr.setInput('failOnStandardError', 'false'); +tmr.setInput('visibleAzLogin', 'false'); +tmr.setInput('useGlobalConfig', 'false'); +tmr.setInput('cwd', __dirname); + +process.env['ENDPOINT_AUTH_TestAzureDevOpsConnection'] = JSON.stringify({ + scheme: 'WorkloadIdentityFederation', + parameters: { + serviceprincipalid: 'test-sp-id', + tenantid: 'test-tenant-id' + } +}); +process.env['ENDPOINT_AUTH_SCHEME_TestAzureDevOpsConnection'] = 'WorkloadIdentityFederation'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_SERVICEPRINCIPALID'] = 'test-sp-id'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_TENANTID'] = 'test-tenant-id'; + +process.env['ENDPOINT_DATA_TestAzureDevOpsConnection'] = JSON.stringify({ + organizationUrl: 'https://dev.azure.com/testorg/' +}); +process.env['ENDPOINT_URL_TestAzureDevOpsConnection'] = 'https://dev.azure.com/testorg/'; + +process.env['SYSTEM_COLLECTIONURI'] = 'https://dev.azure.com/testorg/'; +process.env['SYSTEM_TEAMPROJECT'] = 'TestProject'; +process.env['SYSTEM_JOBID'] = 'test-job-id'; +process.env['SYSTEM_PLANID'] = 'test-plan-id'; +process.env['SYSTEM_TEAMPROJECTID'] = 'test-project-id'; +process.env['SYSTEM_HOSTTYPE'] = 'build'; +process.env['AGENT_TEMPDIRECTORY'] = __dirname; +process.env['AGENT_WORKFOLDER'] = __dirname; + +process.env['AZP_AZURECLIV2_SETUP_PROXY_ENV'] = 'false'; +process.env['ShowWarningOnOlderAzureModules'] = 'false'; +process.env['UseAzVersion'] = 'false'; + +let mockAnswers: ma.TaskLibAnswers = { + "which": { + "az": "az", + "bash": "bash" + }, + "checkPath": { + "az": true, + "bash": true + }, + "exec": { + "az --version": { + "code": 0, + "stdout": "azure-cli 2.50.0" + }, + "az version": { + "code": 0, + "stdout": "{\"azure-cli\": \"2.50.0\", \"azure-cli-core\": \"2.50.0\"}" + }, + "az extension show --name azure-devops": { + "code": 0, + "stdout": "{\n \"name\": \"azure-devops\",\n \"version\": \"1.0.2\"\n}" + }, + "az login --service-principal -u \"test-sp-id\" --tenant \"test-tenant-id\" --allow-no-subscriptions --federated-token \"mock-token\" --output none": { + "code": 0, + "stdout": "Login successful" + }, + "az devops configure --defaults organization=\"https://dev.azure.com/testorg/\"": { + "code": 0, + "stdout": "organization configured" + }, + "az devops configure --defaults project=\"TestProject\"": { + "code": 0, + "stdout": "project configured" + }, + "az devops configure --defaults project='' organization=": { + "code": 0, + "stdout": "configuration cleared" + }, + "az account clear": { + "code": 0, + "stdout": "account cleared" + }, + "bash*": { + "code": 0, + "stdout": "test completed" + }, + "*": { + "code": 0, + "stdout": "test completed" + } + }, + "exists": { + "bash": true + } +}; + +tmr.setAnswers(mockAnswers); + +tmr.registerMock('azure-devops-node-api', { + getHandlerFromToken: () => ({}), + WebApi: function() { + return { + getTaskApi: () => Promise.resolve({ + createOidcToken: () => Promise.resolve({ oidcToken: 'mock-token' }) + }) + }; + } +}); + +tmr.registerMock('azure-pipelines-tasks-artifacts-common/webapi', { + getSystemAccessToken: () => 'system-token' +}); + +tmr.registerMock('./src/Utility', { + Utility: { + checkIfAzurePythonSdkIsInstalled: function() { + return true; + }, + throwIfError: function(result: any, errormsg?: string) { + if (result && result.code !== 0) { + throw new Error(errormsg || 'Command failed'); + } + }, + getScriptPath: function(scriptLocation: string, fileExtensions: string[]) { + return Promise.resolve(path.join(__dirname, 'test-script.sh')); + }, + getPowerShellScriptPath: function(scriptLocation: string, fileExtensions: string[], scriptArguments: string) { + return Promise.resolve(path.join(__dirname, 'test-script.ps1')); + }, + createFile: function(filePath: string, data: string, options?: any) { + return Promise.resolve(); + }, + deleteFile: function(filePath: string) { + return Promise.resolve(); + } + } +}); + +// Mock the ScriptType module +tmr.registerMock('./src/ScriptType', { + ScriptTypeFactory: { + getScriptType: function() { + return { + getTool: function() { + return Promise.resolve({ + on: function(event: string, callback: Function) { + // No-op for event handlers + }, + line: function(args: string) { + // No-op for argument line + }, + arg: function(args: string) { + // No-op for arguments + return this; + }, + exec: function(options?: any) { + console.log('Mock script execution completed'); + return Promise.resolve(0); + } + }); + }, + cleanUp: function() { + return Promise.resolve(); + } + }; + } + } +}); + +tmr.run(); \ No newline at end of file diff --git a/Tasks/AzureCLIV3/Tests/L0AzureDevOpsConfigurationCleanup.ts b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsConfigurationCleanup.ts new file mode 100644 index 000000000000..0483819f896e --- /dev/null +++ b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsConfigurationCleanup.ts @@ -0,0 +1,129 @@ +import ma = require('azure-pipelines-task-lib/mock-answer'); +import tmrm = require('azure-pipelines-task-lib/mock-run'); +import path = require('path'); + +let taskPath = path.join(__dirname, '..', 'azureclitask.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +tmr.setInput('connectionType', 'azureDevOps'); +tmr.setInput('azureDevOpsServiceConnection', 'TestAzureDevOpsConnection'); +tmr.setInput('scriptType', 'bash'); +tmr.setInput('scriptLocation', 'inlineScript'); +tmr.setInput('inlineScript', 'echo "test"'); +tmr.setInput('failOnStandardError', 'false'); +tmr.setInput('visibleAzLogin', 'false'); +tmr.setInput('useGlobalConfig', 'false'); +tmr.setInput('cwd', __dirname); + +process.env['ENDPOINT_AUTH_TestAzureDevOpsConnection'] = JSON.stringify({ + scheme: 'WorkloadIdentityFederation', + parameters: { + serviceprincipalid: 'test-sp-id', + tenantid: 'test-tenant-id' + } +}); +process.env['ENDPOINT_AUTH_SCHEME_TestAzureDevOpsConnection'] = 'WorkloadIdentityFederation'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_SERVICEPRINCIPALID'] = 'test-sp-id'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_TENANTID'] = 'test-tenant-id'; + +process.env['SYSTEM_COLLECTIONURI'] = 'https://dev.azure.com/testorg/'; +process.env['SYSTEM_TEAMPROJECT'] = 'TestProject'; +process.env['SYSTEM_JOBID'] = 'test-job-id'; +process.env['SYSTEM_PLANID'] = 'test-plan-id'; +process.env['SYSTEM_TEAMPROJECTID'] = 'test-project-id'; +process.env['SYSTEM_HOSTTYPE'] = 'build'; +process.env['AGENT_TEMPDIRECTORY'] = __dirname; + +process.env['AZP_AZURECLIV2_SETUP_PROXY_ENV'] = 'false'; +process.env['ShowWarningOnOlderAzureModules'] = 'false'; +process.env['UseAzVersion'] = 'false'; + +let mockAnswers: ma.TaskLibAnswers = { + "which": { + "az": "az", + "bash": "bash" + }, + "checkPath": { + "az": true, + "bash": true + }, + "exec": { + "az --version": { + "code": 0, + "stdout": "azure-cli 2.50.0" + }, + "az version": { + "code": 0, + "stdout": "{\"azure-cli\": \"2.50.0\", \"azure-cli-core\": \"2.50.0\"}" + }, + "az extension show --name azure-devops": { + "code": 1, + "stdout": "Extension not found" + }, + "az extension add -n azure-devops -y": { + "code": 0, + "stdout": "Azure DevOps CLI extension installed" + }, + "az login --service-principal -u \"test-sp-id\" --tenant \"test-tenant-id\" --allow-no-subscriptions --federated-token \"mock-token\" --output none": { + "code": 0, + "stdout": "Login successful" + }, + "az devops configure --defaults organization=\"https://dev.azure.com/testorg/\"": { + "code": 0, + "stdout": "organization configured" + }, + "az devops configure --defaults project=\"TestProject\"": { + "code": 0, + "stdout": "project configured" + }, + "az devops configure --defaults project='' organization=": { + "code": 0, + "stdout": "configuration cleared" + }, + "bash*": { + "code": 0, + "stdout": "test completed" + } + }, + "exists": { + "bash": true + } +}; + +tmr.setAnswers(mockAnswers); + +tmr.registerMock('azure-devops-node-api', { + getHandlerFromToken: () => ({}), + WebApi: function() { + return { + getTaskApi: () => Promise.resolve({ + createOidcToken: () => Promise.resolve({ oidcToken: 'mock-token' }) + }) + }; + } +}); + +tmr.registerMock('azure-pipelines-tasks-artifacts-common/webapi', { + getSystemAccessToken: () => 'system-token' +}); + +tmr.registerMock('./src/Utility', { + Utility: { + throwIfError: () => {}, + checkIfAzurePythonSdkIsInstalled: () => true + } +}); + +tmr.registerMock('./src/ScriptType', { + ScriptTypeFactory: { + getScriptType: () => ({ + getTool: () => Promise.resolve({ + on: () => {}, + exec: () => Promise.resolve(0) + }), + cleanUp: () => Promise.resolve() + }) + } +}); + +tmr.run(); \ No newline at end of file diff --git a/Tasks/AzureCLIV3/Tests/L0AzureDevOpsEnvironmentVariables.ts b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsEnvironmentVariables.ts new file mode 100644 index 000000000000..a7a4cb50ea3c --- /dev/null +++ b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsEnvironmentVariables.ts @@ -0,0 +1,171 @@ +import ma = require('azure-pipelines-task-lib/mock-answer'); +import tmrm = require('azure-pipelines-task-lib/mock-run'); +import path = require('path'); + +let taskPath = path.join(__dirname, '..', 'azureclitask.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +tmr.setInput('connectionType', 'azureDevOps'); +tmr.setInput('azureDevOpsServiceConnection', 'TestAzureDevOpsConnection'); +tmr.setInput('scriptType', 'bash'); +tmr.setInput('scriptLocation', 'inlineScript'); +tmr.setInput('inlineScript', 'echo "test"'); +tmr.setInput('failOnStandardError', 'false'); +tmr.setInput('visibleAzLogin', 'false'); +tmr.setInput('useGlobalConfig', 'false'); +tmr.setInput('cwd', __dirname); + +process.env['ENDPOINT_AUTH_TestAzureDevOpsConnection'] = JSON.stringify({ + scheme: 'WorkloadIdentityFederation', + parameters: { + serviceprincipalid: 'test-sp-id', + tenantid: 'test-tenant-id' + } +}); +process.env['ENDPOINT_AUTH_SCHEME_TestAzureDevOpsConnection'] = 'WorkloadIdentityFederation'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_SERVICEPRINCIPALID'] = 'test-sp-id'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_TENANTID'] = 'test-tenant-id'; + +process.env['SYSTEM_COLLECTIONURI'] = 'https://dev.azure.com/testorg/'; +process.env['SYSTEM_TEAMPROJECT'] = 'TestProject'; +process.env['SYSTEM_JOBID'] = 'test-job-id'; +process.env['SYSTEM_PLANID'] = 'test-plan-id'; +process.env['SYSTEM_TEAMPROJECTID'] = 'test-project-id'; +process.env['SYSTEM_HOSTTYPE'] = 'build'; +process.env['AGENT_TEMPDIRECTORY'] = __dirname; +process.env['AGENT_WORKFOLDER'] = __dirname; + +process.env['AZP_AZURECLIV2_SETUP_PROXY_ENV'] = 'false'; +process.env['ShowWarningOnOlderAzureModules'] = 'false'; +process.env['UseAzVersion'] = 'false'; + +let mockAnswers: ma.TaskLibAnswers = { + "which": { + "az": "az", + "bash": "bash" + }, + "checkPath": { + "az": true, + "bash": true + }, + "exec": { + "az --version": { + "code": 0, + "stdout": "azure-cli 2.50.0" + }, + "az version": { + "code": 0, + "stdout": "{\"azure-cli\": \"2.50.0\", \"azure-cli-core\": \"2.50.0\"}" + }, + "az extension show --name azure-devops": { + "code": 1, + "stdout": "Extension not found" + }, + "az extension add -n azure-devops -y": { + "code": 0, + "stdout": "Azure DevOps CLI extension installed" + }, + "az login --service-principal -u \"test-sp-id\" --tenant \"test-tenant-id\" --allow-no-subscriptions --federated-token \"mock-token\" --output none": { + "code": 0, + "stdout": "Login successful" + }, + "az devops configure --defaults organization=\"https://dev.azure.com/testorg/\"": { + "code": 0, + "stdout": "organization configured" + }, + "az devops configure --defaults project=\"TestProject\"": { + "code": 0, + "stdout": "project configured" + }, + "az devops configure --defaults project='' organization=": { + "code": 0, + "stdout": "configuration cleared" + }, + "bash*": { + "code": 0, + "stdout": "test completed" + }, + "*": { + "code": 0, + "stdout": "test completed" + } + }, + "exists": { + "bash": true + } +}; + +tmr.setAnswers(mockAnswers); + +tmr.registerMock('azure-devops-node-api', { + getHandlerFromToken: () => ({}), + WebApi: function() { + return { + getTaskApi: () => Promise.resolve({ + createOidcToken: () => Promise.resolve({ oidcToken: 'mock-token' }) + }) + }; + } +}); + +tmr.registerMock('azure-pipelines-tasks-artifacts-common/webapi', { + getSystemAccessToken: () => 'system-token' +}); + +tmr.registerMock('./src/Utility', { + Utility: { + checkIfAzurePythonSdkIsInstalled: function() { + return true; + }, + throwIfError: function(result: any, errormsg?: string) { + if (result && result.code !== 0) { + throw new Error(errormsg || 'Command failed'); + } + }, + getScriptPath: function(scriptLocation: string, fileExtensions: string[]) { + return Promise.resolve(path.join(__dirname, 'test-script.sh')); + }, + getPowerShellScriptPath: function(scriptLocation: string, fileExtensions: string[], scriptArguments: string) { + return Promise.resolve(path.join(__dirname, 'test-script.ps1')); + }, + createFile: function(filePath: string, data: string, options?: any) { + return Promise.resolve(); + }, + deleteFile: function(filePath: string) { + return Promise.resolve(); + } + } +}); + +// Mock the ScriptType module +tmr.registerMock('./src/ScriptType', { + ScriptTypeFactory: { + getScriptType: function() { + return { + getTool: function() { + return Promise.resolve({ + on: function(event: string, callback: Function) { + // No-op for event handlers + }, + line: function(args: string) { + // No-op for argument line + }, + arg: function(args: string) { + // No-op for arguments + return this; + }, + exec: function(options?: any) { + console.log('Mock script execution completed'); + return Promise.resolve(0); + } + }); + }, + cleanUp: function() { + return Promise.resolve(); + } + }; + } + } +}); + +tmr.run(); \ No newline at end of file diff --git a/Tasks/AzureCLIV3/Tests/L0AzureDevOpsExtensionAlreadyInstalled.ts b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsExtensionAlreadyInstalled.ts new file mode 100644 index 000000000000..0053096d6657 --- /dev/null +++ b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsExtensionAlreadyInstalled.ts @@ -0,0 +1,172 @@ +import ma = require('azure-pipelines-task-lib/mock-answer'); +import tmrm = require('azure-pipelines-task-lib/mock-run'); +import path = require('path'); + +let taskPath = path.join(__dirname, '..', 'azureclitask.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +tmr.setInput('connectionType', 'azureDevOps'); +tmr.setInput('azureDevOpsServiceConnection', 'TestAzureDevOpsConnection'); +tmr.setInput('scriptType', 'bash'); +tmr.setInput('scriptLocation', 'inlineScript'); +tmr.setInput('inlineScript', 'echo "test"'); +tmr.setInput('failOnStandardError', 'false'); +tmr.setInput('visibleAzLogin', 'false'); +tmr.setInput('useGlobalConfig', 'false'); +tmr.setInput('cwd', __dirname); + +process.env['ENDPOINT_AUTH_TestAzureDevOpsConnection'] = JSON.stringify({ + scheme: 'WorkloadIdentityFederation', + parameters: { + serviceprincipalid: 'test-sp-id', + tenantid: 'test-tenant-id' + } +}); +process.env['ENDPOINT_AUTH_SCHEME_TestAzureDevOpsConnection'] = 'WorkloadIdentityFederation'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_SERVICEPRINCIPALID'] = 'test-sp-id'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_TENANTID'] = 'test-tenant-id'; + +process.env['ENDPOINT_DATA_TestAzureDevOpsConnection'] = JSON.stringify({ + organizationUrl: 'https://dev.azure.com/testorg/' +}); +process.env['ENDPOINT_URL_TestAzureDevOpsConnection'] = 'https://dev.azure.com/testorg/'; + +process.env['SYSTEM_COLLECTIONURI'] = 'https://dev.azure.com/testorg/'; +process.env['SYSTEM_TEAMPROJECT'] = 'TestProject'; +process.env['SYSTEM_JOBID'] = 'test-job-id'; +process.env['SYSTEM_PLANID'] = 'test-plan-id'; +process.env['SYSTEM_TEAMPROJECTID'] = 'test-project-id'; +process.env['SYSTEM_HOSTTYPE'] = 'build'; +process.env['AGENT_TEMPDIRECTORY'] = __dirname; +process.env['AGENT_WORKFOLDER'] = __dirname; + +process.env['AZP_AZURECLIV2_SETUP_PROXY_ENV'] = 'false'; +process.env['ShowWarningOnOlderAzureModules'] = 'false'; +process.env['UseAzVersion'] = 'false'; + +let mockAnswers: ma.TaskLibAnswers = { + "which": { + "az": "az", + "bash": "bash" + }, + "checkPath": { + "az": true, + "bash": true + }, + "exec": { + "az --version": { + "code": 0, + "stdout": "azure-cli 2.50.0" + }, + "az version": { + "code": 0, + "stdout": "{\"azure-cli\": \"2.50.0\", \"azure-cli-core\": \"2.50.0\"}" + }, + "az extension show --name azure-devops": { + "code": 0, + "stdout": "{\n \"name\": \"azure-devops\",\n \"version\": \"1.0.2\"\n}" + }, + "az login --service-principal -u \"test-sp-id\" --tenant \"test-tenant-id\" --allow-no-subscriptions --federated-token \"mock-token\" --output none": { + "code": 0, + "stdout": "Login successful" + }, + "az devops configure --defaults organization=\"https://dev.azure.com/testorg/\"": { + "code": 0, + "stdout": "organization configured" + }, + "az devops configure --defaults project=\"TestProject\"": { + "code": 0, + "stdout": "project configured" + }, + "az devops configure --defaults project='' organization=": { + "code": 0, + "stdout": "configuration cleared" + }, + "bash*": { + "code": 0, + "stdout": "test completed" + }, + "*": { + "code": 0, + "stdout": "test completed" + } + }, + "exists": { + "bash": true + } +}; + +tmr.setAnswers(mockAnswers); + +tmr.registerMock('azure-devops-node-api', { + getHandlerFromToken: () => ({}), + WebApi: function() { + return { + getTaskApi: () => Promise.resolve({ + createOidcToken: () => Promise.resolve({ oidcToken: 'mock-token' }) + }) + }; + } +}); + +tmr.registerMock('azure-pipelines-tasks-artifacts-common/webapi', { + getSystemAccessToken: () => 'system-token' +}); + +tmr.registerMock('./src/Utility', { + Utility: { + checkIfAzurePythonSdkIsInstalled: function() { + return true; + }, + throwIfError: function(result: any, errormsg?: string) { + if (result && result.code !== 0) { + throw new Error(errormsg || 'Command failed'); + } + }, + getScriptPath: function(scriptLocation: string, fileExtensions: string[]) { + return Promise.resolve(path.join(__dirname, 'test-script.sh')); + }, + getPowerShellScriptPath: function(scriptLocation: string, fileExtensions: string[], scriptArguments: string) { + return Promise.resolve(path.join(__dirname, 'test-script.ps1')); + }, + createFile: function(filePath: string, data: string, options?: any) { + return Promise.resolve(); + }, + deleteFile: function(filePath: string) { + return Promise.resolve(); + } + } +}); + +// Mock the ScriptType module +tmr.registerMock('./src/ScriptType', { + ScriptTypeFactory: { + getScriptType: function() { + return { + getTool: function() { + return Promise.resolve({ + on: function(event: string, callback: Function) { + // No-op for event handlers + }, + line: function(args: string) { + // No-op for argument line + }, + arg: function(args: string) { + // No-op for arguments + return this; + }, + exec: function(options?: any) { + console.log('Mock script execution completed'); + return Promise.resolve(0); + } + }); + }, + cleanUp: function() { + return Promise.resolve(); + } + }; + } + } +}); + +tmr.run(); diff --git a/Tasks/AzureCLIV3/Tests/L0AzureDevOpsExtensionInstallFailure.ts b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsExtensionInstallFailure.ts new file mode 100644 index 000000000000..85a6258ac5e8 --- /dev/null +++ b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsExtensionInstallFailure.ts @@ -0,0 +1,157 @@ +import ma = require('azure-pipelines-task-lib/mock-answer'); +import tmrm = require('azure-pipelines-task-lib/mock-run'); +import path = require('path'); + +let taskPath = path.join(__dirname, '..', 'azureclitask.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +tmr.setInput('connectionType', 'azureDevOps'); +tmr.setInput('azureDevOpsServiceConnection', 'TestAzureDevOpsConnection'); +tmr.setInput('scriptType', 'bash'); +tmr.setInput('scriptLocation', 'inlineScript'); +tmr.setInput('inlineScript', 'echo "test"'); +tmr.setInput('failOnStandardError', 'false'); +tmr.setInput('visibleAzLogin', 'false'); +tmr.setInput('useGlobalConfig', 'false'); +tmr.setInput('cwd', __dirname); + +process.env['ENDPOINT_AUTH_TestAzureDevOpsConnection'] = JSON.stringify({ + scheme: 'WorkloadIdentityFederation', + parameters: { + serviceprincipalid: 'test-sp-id', + tenantid: 'test-tenant-id' + } +}); +process.env['ENDPOINT_AUTH_SCHEME_TestAzureDevOpsConnection'] = 'WorkloadIdentityFederation'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_SERVICEPRINCIPALID'] = 'test-sp-id'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_TENANTID'] = 'test-tenant-id'; + +process.env['SYSTEM_COLLECTIONURI'] = 'https://dev.azure.com/testorg/'; +process.env['SYSTEM_TEAMPROJECT'] = 'TestProject'; +process.env['SYSTEM_JOBID'] = 'test-job-id'; +process.env['SYSTEM_PLANID'] = 'test-plan-id'; +process.env['SYSTEM_TEAMPROJECTID'] = 'test-project-id'; +process.env['SYSTEM_HOSTTYPE'] = 'build'; +process.env['AGENT_TEMPDIRECTORY'] = __dirname; + +process.env['AZP_AZURECLIV2_SETUP_PROXY_ENV'] = 'false'; +process.env['ShowWarningOnOlderAzureModules'] = 'false'; +process.env['UseAzVersion'] = 'false'; + +let mockAnswers: ma.TaskLibAnswers = { + "which": { + "az": "az", + "bash": "bash" + }, + "checkPath": { + "az": true, + "bash": true + }, + "exec": { + "az --version": { + "code": 0, + "stdout": "azure-cli 2.50.0" + }, + "az version": { + "code": 0, + "stdout": "{\"azure-cli\": \"2.50.0\", \"azure-cli-core\": \"2.50.0\"}" + }, + "az extension show --name azure-devops": { + "code": 1, + "stdout": "Extension not found" + }, + "az extension add -n azure-devops -y": { + "code": 1, + "stdout": "Failed to install extension: permission denied" + }, + "bash*": { + "code": 0, + "stdout": "test completed" + }, + "*": { + "code": 0, + "stdout": "test completed" + } + }, + "exists": { + "bash": true + } +}; + +tmr.setAnswers(mockAnswers); + +tmr.registerMock('azure-devops-node-api', { + getHandlerFromToken: () => ({}), + WebApi: function() { + return { + getTaskApi: () => Promise.resolve({ + createOidcToken: () => Promise.resolve({ oidcToken: 'mock-token' }) + }) + }; + } +}); + +tmr.registerMock('azure-pipelines-tasks-artifacts-common/webapi', { + getSystemAccessToken: () => 'system-token' +}); + +tmr.registerMock('./src/Utility', { + Utility: { + checkIfAzurePythonSdkIsInstalled: function() { + return true; + }, + throwIfError: function(result: any, errormsg?: string) { + if (result && result.code !== 0) { + throw new Error(errormsg || 'Command failed'); + } + }, + getScriptPath: function(scriptLocation: string, fileExtensions: string[]) { + return Promise.resolve(path.join(__dirname, 'test-script.sh')); + }, + getPowerShellScriptPath: function(scriptLocation: string, fileExtensions: string[], scriptArguments: string) { + return Promise.resolve(path.join(__dirname, 'test-script.ps1')); + }, + createFile: function(filePath: string, data: string, options?: any) { + return Promise.resolve(); + }, + deleteFile: function(filePath: string) { + return Promise.resolve(); + } + } +}); + +// Mock the task library localization +tmr.setVariableName('FailedToInstallAzureDevOpsCLI', 'loc_mock_FailedToInstallAzureDevOpsCLI'); + +// Mock the ScriptType module +tmr.registerMock('./src/ScriptType', { + ScriptTypeFactory: { + getScriptType: function() { + return { + getTool: function() { + return Promise.resolve({ + on: function(event: string, callback: Function) { + // No-op for event handlers + }, + line: function(args: string) { + // No-op for argument line + }, + arg: function(args: string) { + // No-op for arguments + return this; + }, + exec: function(options?: any) { + console.log('Mock script execution completed'); + return Promise.resolve(0); + } + }); + }, + cleanUp: function() { + return Promise.resolve(); + } + }; + } + } +}); + +tmr.run(); \ No newline at end of file diff --git a/Tasks/AzureCLIV3/Tests/L0AzureDevOpsMissingOrganization.ts b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsMissingOrganization.ts new file mode 100644 index 000000000000..ddc64587a7f6 --- /dev/null +++ b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsMissingOrganization.ts @@ -0,0 +1,200 @@ +import ma = require('azure-pipelines-task-lib/mock-answer'); +import tmrm = require('azure-pipelines-task-lib/mock-run'); +import path = require('path'); + +let taskPath = path.join(__dirname, '..', 'azureclitask.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +tmr.setInput('connectionType', 'azureDevOps'); +tmr.setInput('azureDevOpsServiceConnection', 'TestAzureDevOpsConnection'); +tmr.setInput('scriptType', 'bash'); +tmr.setInput('scriptLocation', 'inlineScript'); +tmr.setInput('inlineScript', 'echo "test"'); +tmr.setInput('failOnStandardError', 'false'); +tmr.setInput('visibleAzLogin', 'false'); +tmr.setInput('useGlobalConfig', 'false'); +tmr.setInput('cwd', __dirname); + +process.env['ENDPOINT_AUTH_TestAzureDevOpsConnection'] = JSON.stringify({ + scheme: 'WorkloadIdentityFederation', + parameters: { + serviceprincipalid: 'test-sp-id', + tenantid: 'test-tenant-id' + } +}); +process.env['ENDPOINT_AUTH_SCHEME_TestAzureDevOpsConnection'] = 'WorkloadIdentityFederation'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_SERVICEPRINCIPALID'] = 'test-sp-id'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_TENANTID'] = 'test-tenant-id'; + +process.env['ENDPOINT_DATA_TestAzureDevOpsConnection'] = JSON.stringify({ + organizationUrl: 'https://dev.azure.com/testorg/' +}); +process.env['ENDPOINT_URL_TestAzureDevOpsConnection'] = 'https://dev.azure.com/testorg/'; +delete process.env['SYSTEM_COLLECTIONURI']; + +process.env['SYSTEM_TEAMPROJECT'] = 'TestProject'; +process.env['SYSTEM_JOBID'] = 'test-job-id'; +process.env['SYSTEM_PLANID'] = 'test-plan-id'; +process.env['SYSTEM_TEAMPROJECTID'] = 'test-project-id'; +process.env['SYSTEM_HOSTTYPE'] = 'build'; +process.env['AGENT_TEMPDIRECTORY'] = __dirname; +process.env['AGENT_WORKFOLDER'] = __dirname; + +process.env['AZP_AZURECLIV2_SETUP_PROXY_ENV'] = 'false'; +process.env['ShowWarningOnOlderAzureModules'] = 'false'; +process.env['UseAzVersion'] = 'false'; + +let mockAnswers: ma.TaskLibAnswers = { + "which": { + "az": "az", + "bash": "bash" + }, + "checkPath": { + "az": true, + "bash": true + }, + "exec": { + "az --version": { + "code": 0, + "stdout": "azure-cli 2.50.0" + }, + "az version": { + "code": 0, + "stdout": "{\"azure-cli\": \"2.50.0\", \"azure-cli-core\": \"2.50.0\"}" + }, + "az extension show --name azure-devops": { + "code": 1, + "stdout": "Extension 'azure-devops' is not installed." + }, + "az extension add -n azure-devops -y": { + "code": 0, + "stdout": "Azure DevOps CLI extension installed" + }, + "az login --service-principal -u \"test-sp-id\" --tenant \"test-tenant-id\" --allow-no-subscriptions --federated-token \"mock-token\" --output none": { + "code": 0, + "stdout": "Login successful" + }, + "az devops configure --defaults project=\"TestProject\"": { + "code": 0, + "stdout": "project configured" + }, + "az devops configure --defaults organization=\"undefined\"": { + "code": 1, + "stderr": "Code attempted to configure organization with 'undefined' value! This should be skipped when SYSTEM_COLLECTIONURI is missing." + }, + "az devops configure --defaults organization=\"null\"": { + "code": 1, + "stderr": "Code attempted to configure organization with 'null' value! This should be skipped when SYSTEM_COLLECTIONURI is missing." + }, + "az devops configure --defaults organization=\"\"": { + "code": 1, + "stderr": "Code attempted to configure organization with empty string! This should be skipped when SYSTEM_COLLECTIONURI is missing." + }, + "az devops configure --defaults organization=undefined": { + "code": 1, + "stderr": "Code attempted to configure organization with unquoted undefined! This should be skipped when SYSTEM_COLLECTIONURI is missing." + }, + "az devops configure --defaults organization=null": { + "code": 1, + "stderr": "Code attempted to configure organization with unquoted null! This should be skipped when SYSTEM_COLLECTIONURI is missing." + }, + "az devops configure --defaults organization=": { + "code": 1, + "stderr": "Code attempted to configure organization with no value! This should be skipped when SYSTEM_COLLECTIONURI is missing." + }, + "az devops configure --defaults organization=\"https://dev.azure.com/testorg/\"": { + "code": 1, + "stderr": "Code attempted to configure organization when SYSTEM_COLLECTIONURI is missing! This should be skipped." + }, + "az devops configure --defaults project='' organization=": { + "code": 0, + "stdout": "configuration cleared" + }, + "bash*": { + "code": 0, + "stdout": "test completed" + }, + "*": { + "code": 0, + "stdout": "test completed" + } + }, + "exists": { + "bash": true + } +}; + +tmr.setAnswers(mockAnswers); + +tmr.registerMock('azure-devops-node-api', { + getHandlerFromToken: () => ({}), + WebApi: function() { + return { + getTaskApi: () => Promise.resolve({ + createOidcToken: () => Promise.resolve({ oidcToken: 'mock-token' }) + }) + }; + } +}); + +tmr.registerMock('azure-pipelines-tasks-artifacts-common/webapi', { + getSystemAccessToken: () => 'system-token' +}); + +tmr.registerMock('./src/Utility', { + Utility: { + checkIfAzurePythonSdkIsInstalled: function() { + return true; + }, + throwIfError: function(result: any, errormsg?: string) { + if (result && result.code !== 0) { + throw new Error(errormsg || 'Command failed'); + } + }, + getScriptPath: function(scriptLocation: string, fileExtensions: string[]) { + return Promise.resolve(path.join(__dirname, 'test-script.sh')); + }, + getPowerShellScriptPath: function(scriptLocation: string, fileExtensions: string[], scriptArguments: string) { + return Promise.resolve(path.join(__dirname, 'test-script.ps1')); + }, + createFile: function(filePath: string, data: string, options?: any) { + return Promise.resolve(); + }, + deleteFile: function(filePath: string) { + return Promise.resolve(); + } + } +}); + +// Mock the ScriptType module +tmr.registerMock('./src/ScriptType', { + ScriptTypeFactory: { + getScriptType: function() { + return { + getTool: function() { + return Promise.resolve({ + on: function(event: string, callback: Function) { + // No-op for event handlers + }, + line: function(args: string) { + // No-op for argument line + }, + arg: function(args: string) { + // No-op for arguments + return this; + }, + exec: function(options?: any) { + console.log('Mock script execution completed'); + return Promise.resolve(0); + } + }); + }, + cleanUp: function() { + return Promise.resolve(); + } + }; + } + } +}); + +tmr.run(); \ No newline at end of file diff --git a/Tasks/AzureCLIV3/Tests/L0AzureDevOpsMissingProject.ts b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsMissingProject.ts new file mode 100644 index 000000000000..6f7fcf1b00a7 --- /dev/null +++ b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsMissingProject.ts @@ -0,0 +1,204 @@ +import ma = require('azure-pipelines-task-lib/mock-answer'); +import tmrm = require('azure-pipelines-task-lib/mock-run'); +import path = require('path'); + +let taskPath = path.join(__dirname, '..', 'azureclitask.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +tmr.setInput('connectionType', 'azureDevOps'); +tmr.setInput('azureDevOpsServiceConnection', 'TestAzureDevOpsConnection'); +tmr.setInput('scriptType', 'bash'); +tmr.setInput('scriptLocation', 'inlineScript'); +tmr.setInput('inlineScript', 'echo "test"'); +tmr.setInput('failOnStandardError', 'false'); +tmr.setInput('visibleAzLogin', 'false'); +tmr.setInput('useGlobalConfig', 'false'); +tmr.setInput('cwd', __dirname); + +process.env['ENDPOINT_AUTH_TestAzureDevOpsConnection'] = JSON.stringify({ + scheme: 'WorkloadIdentityFederation', + parameters: { + serviceprincipalid: 'test-sp-id', + tenantid: 'test-tenant-id' + } +}); +process.env['ENDPOINT_AUTH_SCHEME_TestAzureDevOpsConnection'] = 'WorkloadIdentityFederation'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_SERVICEPRINCIPALID'] = 'test-sp-id'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_TENANTID'] = 'test-tenant-id'; + +process.env['ENDPOINT_DATA_TestAzureDevOpsConnection'] = JSON.stringify({ + organizationUrl: 'https://dev.azure.com/testorg/' +}); +process.env['ENDPOINT_URL_TestAzureDevOpsConnection'] = 'https://dev.azure.com/testorg/'; + +process.env['SYSTEM_COLLECTIONURI'] = 'https://dev.azure.com/testorg/'; +// process.env['SYSTEM_TEAMPROJECT'] = 'TestProject'; +process.env['SYSTEM_JOBID'] = 'test-job-id'; +process.env['SYSTEM_PLANID'] = 'test-plan-id'; +process.env['SYSTEM_TEAMPROJECTID'] = 'test-project-id'; +process.env['SYSTEM_HOSTTYPE'] = 'build'; +process.env['AGENT_TEMPDIRECTORY'] = __dirname; +process.env['AGENT_WORKFOLDER'] = __dirname; + +process.env['AZP_AZURECLIV2_SETUP_PROXY_ENV'] = 'false'; +process.env['ShowWarningOnOlderAzureModules'] = 'false'; +process.env['UseAzVersion'] = 'false'; + +let mockAnswers: ma.TaskLibAnswers = { + "which": { + "az": "az", + "bash": "bash" + }, + "checkPath": { + "az": true, + "bash": true + }, + "exec": { + "az --version": { + "code": 0, + "stdout": "azure-cli 2.50.0" + }, + "az version": { + "code": 0, + "stdout": "{\"azure-cli\": \"2.50.0\", \"azure-cli-core\": \"2.50.0\"}" + }, + "az extension show --name azure-devops": { + "code": 1, + "stdout": "Extension 'azure-devops' is not installed." + }, + "az extension add -n azure-devops -y": { + "code": 0, + "stdout": "Azure DevOps CLI extension installed" + }, + "az login --service-principal -u \"test-sp-id\" --tenant \"test-tenant-id\" --allow-no-subscriptions --federated-token \"mock-token\" --output none": { + "code": 0, + "stdout": "Login successful" + }, + "az devops configure --defaults organization=\"https://dev.azure.com/testorg/\"": { + "code": 0, + "stdout": "organization configured" + }, + "az devops configure --defaults project=\"TestProject\"": { + "code": 1, + "stderr": "Code attempted to configure project when SYSTEM_TEAMPROJECT is missing! This should be skipped." + }, + "az devops configure --defaults project=\"undefined\"": { + "code": 1, + "stderr": "Code attempted to configure project with 'undefined' value! This should be skipped when SYSTEM_TEAMPROJECT is missing." + }, + "az devops configure --defaults project=\"null\"": { + "code": 1, + "stderr": "Code attempted to configure project with 'null' value! This should be skipped when SYSTEM_TEAMPROJECT is missing." + }, + "az devops configure --defaults project=\"\"": { + "code": 1, + "stderr": "Code attempted to configure project with empty string! This should be skipped when SYSTEM_TEAMPROJECT is missing." + }, + "az devops configure --defaults project=undefined": { + "code": 1, + "stderr": "Code attempted to configure project with unquoted undefined! This should be skipped when SYSTEM_TEAMPROJECT is missing." + }, + "az devops configure --defaults project=null": { + "code": 1, + "stderr": "Code attempted to configure project with unquoted null! This should be skipped when SYSTEM_TEAMPROJECT is missing." + }, + "az devops configure --defaults project=": { + "code": 1, + "stderr": "Code attempted to configure project with no value! This should be skipped when SYSTEM_TEAMPROJECT is missing." + }, + "az devops configure --defaults project=TestProject": { + "code": 1, + "stderr": "Code attempted to configure project with unquoted project name! This should be skipped when SYSTEM_TEAMPROJECT is missing." + }, + "az devops configure --defaults project='' organization=": { + "code": 0, + "stdout": "configuration cleared" + }, + "bash*": { + "code": 0, + "stdout": "test completed" + }, + "*": { + "code": 0, + "stdout": "test completed" + } + }, + "exists": { + "bash": true + } +}; + +tmr.setAnswers(mockAnswers); + +tmr.registerMock('azure-devops-node-api', { + getHandlerFromToken: () => ({}), + WebApi: function() { + return { + getTaskApi: () => Promise.resolve({ + createOidcToken: () => Promise.resolve({ oidcToken: 'mock-token' }) + }) + }; + } +}); + +tmr.registerMock('azure-pipelines-tasks-artifacts-common/webapi', { + getSystemAccessToken: () => 'system-token' +}); + +tmr.registerMock('./src/Utility', { + Utility: { + checkIfAzurePythonSdkIsInstalled: function() { + return true; + }, + throwIfError: function(result: any, errormsg?: string) { + if (result && result.code !== 0) { + throw new Error(errormsg || 'Command failed'); + } + }, + getScriptPath: function(scriptLocation: string, fileExtensions: string[]) { + return Promise.resolve(path.join(__dirname, 'test-script.sh')); + }, + getPowerShellScriptPath: function(scriptLocation: string, fileExtensions: string[], scriptArguments: string) { + return Promise.resolve(path.join(__dirname, 'test-script.ps1')); + }, + createFile: function(filePath: string, data: string, options?: any) { + return Promise.resolve(); + }, + deleteFile: function(filePath: string) { + return Promise.resolve(); + } + } +}); + +// Mock the ScriptType module +tmr.registerMock('./src/ScriptType', { + ScriptTypeFactory: { + getScriptType: function() { + return { + getTool: function() { + return Promise.resolve({ + on: function(event: string, callback: Function) { + // No-op for event handlers + }, + line: function(args: string) { + // No-op for argument line + }, + arg: function(args: string) { + // No-op for arguments + return this; + }, + exec: function(options?: any) { + console.log('Mock script execution completed'); + return Promise.resolve(0); + } + }); + }, + cleanUp: function() { + return Promise.resolve(); + } + }; + } + } +}); + +tmr.run(); diff --git a/Tasks/AzureCLIV3/Tests/L0AzureDevOpsOidcTokenFailure.ts b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsOidcTokenFailure.ts new file mode 100644 index 000000000000..a3f48c054f90 --- /dev/null +++ b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsOidcTokenFailure.ts @@ -0,0 +1,147 @@ +import ma = require('azure-pipelines-task-lib/mock-answer'); +import tmrm = require('azure-pipelines-task-lib/mock-run'); +import path = require('path'); + +let taskPath = path.join(__dirname, '..', 'azureclitask.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +tmr.setInput('connectionType', 'azureDevOps'); +tmr.setInput('azureDevOpsServiceConnection', 'TestAzureDevOpsConnection'); +tmr.setInput('scriptType', 'bash'); +tmr.setInput('scriptLocation', 'inlineScript'); +tmr.setInput('inlineScript', 'echo "test"'); +tmr.setInput('failOnStandardError', 'false'); +tmr.setInput('visibleAzLogin', 'false'); +tmr.setInput('useGlobalConfig', 'false'); +tmr.setInput('cwd', __dirname); + +process.env['ENDPOINT_AUTH_TestAzureDevOpsConnection'] = JSON.stringify({ + scheme: 'WorkloadIdentityFederation', + parameters: { + serviceprincipalid: 'test-sp-id', + tenantid: 'test-tenant-id' + } +}); +process.env['ENDPOINT_AUTH_SCHEME_TestAzureDevOpsConnection'] = 'WorkloadIdentityFederation'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_SERVICEPRINCIPALID'] = 'test-sp-id'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_TENANTID'] = 'test-tenant-id'; + +process.env['SYSTEM_COLLECTIONURI'] = 'https://dev.azure.com/testorg/'; +process.env['SYSTEM_TEAMPROJECT'] = 'TestProject'; +process.env['SYSTEM_JOBID'] = 'job-123'; +process.env['SYSTEM_PLANID'] = 'plan-456'; +process.env['SYSTEM_TEAMPROJECTID'] = 'project-789'; +process.env['SYSTEM_HOSTTYPE'] = 'build'; +process.env['AGENT_TEMPDIRECTORY'] = __dirname; + +process.env['AZP_AZURECLIV2_SETUP_PROXY_ENV'] = 'false'; +process.env['ShowWarningOnOlderAzureModules'] = 'false'; +process.env['UseAzVersion'] = 'false'; + +let mockAnswers: ma.TaskLibAnswers = { + "which": { + "az": "az", + "bash": "bash" + }, + "checkPath": { + "az": true, + "bash": true + }, + "exec": { + "az --version": { + "code": 0, + "stdout": "azure-cli 2.50.0" + }, + "az version": { + "code": 0, + "stdout": "{\"azure-cli\": \"2.50.0\", \"azure-cli-core\": \"2.50.0\"}" + }, + "az extension show --name azure-devops": { + "code": 1, + "stdout": "Extension not found" + }, + "az extension add -n azure-devops -y": { + "code": 0, + "stdout": "Azure DevOps CLI extension installed" + } + }, + "exists": { + "bash": true + } +}; + +tmr.setAnswers(mockAnswers); + +// Mock to simulate OIDC token retrieval failure +tmr.registerMock('azure-devops-node-api', { + getHandlerFromToken: () => ({}), + WebApi: function() { + return { + getTaskApi: () => Promise.resolve({ + createOidcToken: () => Promise.reject(new Error('Failed to retrieve OIDC token')) + }) + }; + } +}); + +tmr.registerMock('azure-pipelines-tasks-artifacts-common/webapi', { + getSystemAccessToken: () => 'system-token' +}); + +tmr.registerMock('./src/Utility', { + Utility: { + checkIfAzurePythonSdkIsInstalled: function() { + return true; + }, + throwIfError: function(result: any, errormsg?: string) { + if (result && result.code !== 0) { + throw new Error(errormsg || 'Command failed'); + } + }, + getScriptPath: function(scriptLocation: string, fileExtensions: string[]) { + return Promise.resolve(path.join(__dirname, 'test-script.sh')); + }, + getPowerShellScriptPath: function(scriptLocation: string, fileExtensions: string[], scriptArguments: string) { + return Promise.resolve(path.join(__dirname, 'test-script.ps1')); + }, + createFile: function(filePath: string, data: string, options?: any) { + return Promise.resolve(); + }, + deleteFile: function(filePath: string) { + return Promise.resolve(); + } + } +}); + +// Mock the ScriptType module +tmr.registerMock('./src/ScriptType', { + ScriptTypeFactory: { + getScriptType: function() { + return { + getTool: function() { + return Promise.resolve({ + on: function(event: string, callback: Function) { + // No-op for event handlers + }, + line: function(args: string) { + // No-op for argument line + }, + arg: function(args: string) { + // No-op for arguments + return this; + }, + exec: function(options?: any) { + console.log('Mock script execution completed'); + return Promise.resolve(0); + } + }); + }, + cleanUp: function() { + return Promise.resolve(); + } + }; + } + } +}); + +tmr.run(); \ No newline at end of file diff --git a/Tasks/AzureCLIV3/Tests/L0AzureDevOpsOidcTokenRetrieval.ts b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsOidcTokenRetrieval.ts new file mode 100644 index 000000000000..7a25b613be27 --- /dev/null +++ b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsOidcTokenRetrieval.ts @@ -0,0 +1,177 @@ +import ma = require('azure-pipelines-task-lib/mock-answer'); +import tmrm = require('azure-pipelines-task-lib/mock-run'); +import path = require('path'); + +let taskPath = path.join(__dirname, '..', 'azureclitask.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +tmr.setInput('connectionType', 'azureDevOps'); +tmr.setInput('azureDevOpsServiceConnection', 'TestAzureDevOpsConnection'); +tmr.setInput('scriptType', 'bash'); +tmr.setInput('scriptLocation', 'inlineScript'); +tmr.setInput('inlineScript', 'echo "test"'); +tmr.setInput('failOnStandardError', 'false'); +tmr.setInput('visibleAzLogin', 'false'); +tmr.setInput('useGlobalConfig', 'false'); +tmr.setInput('cwd', __dirname); + +process.env['ENDPOINT_AUTH_TestAzureDevOpsConnection'] = JSON.stringify({ + scheme: 'WorkloadIdentityFederation', + parameters: { + serviceprincipalid: 'test-sp-id', + tenantid: 'test-tenant-id' + } +}); +process.env['ENDPOINT_AUTH_SCHEME_TestAzureDevOpsConnection'] = 'WorkloadIdentityFederation'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_SERVICEPRINCIPALID'] = 'test-sp-id'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_TENANTID'] = 'test-tenant-id'; + +process.env['ENDPOINT_DATA_TestAzureDevOpsConnection'] = JSON.stringify({ + organizationUrl: 'https://dev.azure.com/testorg/' +}); +process.env['ENDPOINT_URL_TestAzureDevOpsConnection'] = 'https://dev.azure.com/testorg/'; + +process.env['SYSTEM_COLLECTIONURI'] = 'https://dev.azure.com/testorg/'; +process.env['SYSTEM_TEAMPROJECT'] = 'TestProject'; +process.env['SYSTEM_JOBID'] = 'test-job-id'; +process.env['SYSTEM_PLANID'] = 'test-plan-id'; +process.env['SYSTEM_TEAMPROJECTID'] = 'test-project-id'; +process.env['SYSTEM_HOSTTYPE'] = 'build'; +process.env['AGENT_TEMPDIRECTORY'] = __dirname; +process.env['AGENT_WORKFOLDER'] = __dirname; + +process.env['AZP_AZURECLIV2_SETUP_PROXY_ENV'] = 'false'; +process.env['ShowWarningOnOlderAzureModules'] = 'false'; +process.env['UseAzVersion'] = 'false'; + +let mockAnswers: ma.TaskLibAnswers = { + "which": { + "az": "az", + "bash": "bash" + }, + "checkPath": { + "az": true, + "bash": true + }, + "exec": { + "az --version": { + "code": 0, + "stdout": "azure-cli 2.50.0" + }, + "az version": { + "code": 0, + "stdout": "{\"azure-cli\": \"2.50.0\", \"azure-cli-core\": \"2.50.0\"}" + }, + "az extension show --name azure-devops": { + "code": 1, + "stdout": "Extension not found" + }, + "az extension add -n azure-devops -y": { + "code": 0, + "stdout": "Azure DevOps CLI extension installed" + }, + "az login --service-principal -u \"test-sp-id\" --tenant \"test-tenant-id\" --allow-no-subscriptions --federated-token \"mock-token\" --output none": { + "code": 0, + "stdout": "Login successful" + }, + "az devops configure --defaults organization=\"https://dev.azure.com/testorg/\"": { + "code": 0, + "stdout": "organization configured" + }, + "az devops configure --defaults project=\"TestProject\"": { + "code": 0, + "stdout": "project configured" + }, + "az devops configure --defaults project='' organization=": { + "code": 0, + "stdout": "configuration cleared" + }, + "bash*": { + "code": 0, + "stdout": "test completed" + }, + "*": { + "code": 0, + "stdout": "test completed" + } + }, + "exists": { + "bash": true + } +}; + +tmr.setAnswers(mockAnswers); + +// Mock the Utility module +tmr.registerMock('./src/Utility', { + Utility: { + checkIfAzurePythonSdkIsInstalled: function() { + return true; + }, + throwIfError: function(result: any, errormsg?: string) { + if (result && result.code !== 0) { + throw new Error(errormsg || 'Command failed'); + } + }, + getScriptPath: function(scriptLocation: string, fileExtensions: string[]) { + return Promise.resolve(path.join(__dirname, 'test-script.sh')); + }, + getPowerShellScriptPath: function(scriptLocation: string, fileExtensions: string[], scriptArguments: string) { + return Promise.resolve(path.join(__dirname, 'test-script.ps1')); + }, + createFile: function(filePath: string, data: string, options?: any) { + return Promise.resolve(); + }, + deleteFile: function(filePath: string) { + return Promise.resolve(); + } + } +}); + +// Mock the ScriptType module +tmr.registerMock('./src/ScriptType', { + ScriptTypeFactory: { + getScriptType: function() { + return { + getTool: function() { + return Promise.resolve({ + on: function(event: string, callback: Function) { + // No-op for event handlers + }, + line: function(args: string) { + // No-op for argument line + }, + arg: function(args: string) { + // No-op for arguments + return this; + }, + exec: function(options?: any) { + console.log('Mock script execution completed'); + return Promise.resolve(0); + } + }); + }, + cleanUp: function() { + return Promise.resolve(); + } + }; + } + } +}); + +tmr.registerMock('azure-devops-node-api', { + getHandlerFromToken: () => ({}), + WebApi: function() { + return { + getTaskApi: () => Promise.resolve({ + createOidcToken: () => Promise.resolve({ oidcToken: 'mock-token' }) + }) + }; + } +}); + +tmr.registerMock('azure-pipelines-tasks-artifacts-common/webapi', { + getSystemAccessToken: () => 'system-token' +}); + +tmr.run(); \ No newline at end of file diff --git a/Tasks/AzureCLIV3/Tests/L0AzureDevOpsOrganizationConfigError.ts b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsOrganizationConfigError.ts new file mode 100644 index 000000000000..a72f2ecb58ee --- /dev/null +++ b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsOrganizationConfigError.ts @@ -0,0 +1,170 @@ +import ma = require('azure-pipelines-task-lib/mock-answer'); +import tmrm = require('azure-pipelines-task-lib/mock-run'); +import path = require('path'); + +let taskPath = path.join(__dirname, '..', 'azureclitask.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +tmr.setInput('connectionType', 'azureDevOps'); +tmr.setInput('azureDevOpsServiceConnection', 'TestAzureDevOpsConnection'); +tmr.setInput('scriptType', 'bash'); +tmr.setInput('scriptLocation', 'inlineScript'); +tmr.setInput('inlineScript', 'echo "test"'); +tmr.setInput('failOnStandardError', 'false'); +tmr.setInput('visibleAzLogin', 'false'); +tmr.setInput('useGlobalConfig', 'false'); +tmr.setInput('cwd', __dirname); + +process.env['ENDPOINT_AUTH_TestAzureDevOpsConnection'] = JSON.stringify({ + scheme: 'WorkloadIdentityFederation', + parameters: { + serviceprincipalid: 'test-sp-id', + tenantid: 'test-tenant-id' + } +}); +process.env['ENDPOINT_AUTH_SCHEME_TestAzureDevOpsConnection'] = 'WorkloadIdentityFederation'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_SERVICEPRINCIPALID'] = 'test-sp-id'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_TENANTID'] = 'test-tenant-id'; + +process.env['SYSTEM_COLLECTIONURI'] = 'https://dev.azure.com/testorg/'; +process.env['SYSTEM_TEAMPROJECT'] = 'TestProject'; +process.env['SYSTEM_JOBID'] = 'job-123'; +process.env['SYSTEM_PLANID'] = 'plan-456'; +process.env['SYSTEM_TEAMPROJECTID'] = 'project-789'; +process.env['SYSTEM_HOSTTYPE'] = 'build'; +process.env['AGENT_TEMPDIRECTORY'] = __dirname; + +process.env['AZP_AZURECLIV2_SETUP_PROXY_ENV'] = 'false'; +process.env['ShowWarningOnOlderAzureModules'] = 'false'; +process.env['UseAzVersion'] = 'false'; + +let mockAnswers: ma.TaskLibAnswers = { + "which": { + "az": "az", + "bash": "bash" + }, + "checkPath": { + "az": true, + "bash": true + }, + "exec": { + "az --version": { + "code": 0, + "stdout": "azure-cli 2.50.0" + }, + "az version": { + "code": 0, + "stdout": "{\"azure-cli\": \"2.50.0\", \"azure-cli-core\": \"2.50.0\"}" + }, + "az extension show --name azure-devops": { + "code": 1, + "stdout": "Extension not found" + }, + "az extension add -n azure-devops -y": { + "code": 0, + "stdout": "Azure DevOps CLI extension installed" + }, + "az login --service-principal -u \"test-sp-id\" --tenant \"test-tenant-id\" --allow-no-subscriptions --federated-token \"mock-token\" --output none": { + "code": 0, + "stdout": "Login successful" + }, + // This will fail to test FailedToSetAzureDevOpsOrganization error message + "az devops configure --defaults organization=\"https://dev.azure.com/testorg/\"": { + "code": 1, + "stdout": "Failed to configure organization" + }, + "az devops configure --defaults project='' organization=": { + "code": 0, + "stdout": "configuration cleared" + }, + "bash*": { + "code": 0, + "stdout": "test completed" + }, + "*": { + "code": 0, + "stdout": "test completed" + } + }, + "exists": { + "bash": true + } +}; + +tmr.setAnswers(mockAnswers); + +tmr.registerMock('azure-devops-node-api', { + getHandlerFromToken: () => ({}), + WebApi: function() { + return { + getTaskApi: () => Promise.resolve({ + createOidcToken: () => Promise.resolve({ oidcToken: 'mock-token' }) + }) + }; + } +}); + +tmr.registerMock('azure-pipelines-tasks-artifacts-common/webapi', { + getSystemAccessToken: () => 'system-token' +}); + +// Mock the task library localization for organization error +tmr.setVariableName('FailedToSetAzureDevOpsOrganization', 'loc_mock_FailedToSetAzureDevOpsOrganization'); + +tmr.registerMock('./src/Utility', { + Utility: { + checkIfAzurePythonSdkIsInstalled: function() { + return true; + }, + throwIfError: function(result: any, errormsg?: string) { + if (result && result.code !== 0) { + throw new Error(errormsg || 'Command failed'); + } + }, + getScriptPath: function(scriptLocation: string, fileExtensions: string[]) { + return Promise.resolve(path.join(__dirname, 'test-script.sh')); + }, + getPowerShellScriptPath: function(scriptLocation: string, fileExtensions: string[], scriptArguments: string) { + return Promise.resolve(path.join(__dirname, 'test-script.ps1')); + }, + createFile: function(filePath: string, data: string, options?: any) { + return Promise.resolve(); + }, + deleteFile: function(filePath: string) { + return Promise.resolve(); + } + } +}); + +// Mock the ScriptType module +tmr.registerMock('./src/ScriptType', { + ScriptTypeFactory: { + getScriptType: function() { + return { + getTool: function() { + return Promise.resolve({ + on: function(event: string, callback: Function) { + // No-op for event handlers + }, + line: function(args: string) { + // No-op for argument line + }, + arg: function(args: string) { + // No-op for arguments + return this; + }, + exec: function(options?: any) { + console.log('Mock script execution completed'); + return Promise.resolve(0); + } + }); + }, + cleanUp: function() { + return Promise.resolve(); + } + }; + } + } +}); + +tmr.run(); \ No newline at end of file diff --git a/Tasks/AzureCLIV3/Tests/L0AzureDevOpsProjectConfigError.ts b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsProjectConfigError.ts new file mode 100644 index 000000000000..3ff86aaf7904 --- /dev/null +++ b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsProjectConfigError.ts @@ -0,0 +1,174 @@ +import ma = require('azure-pipelines-task-lib/mock-answer'); +import tmrm = require('azure-pipelines-task-lib/mock-run'); +import path = require('path'); + +let taskPath = path.join(__dirname, '..', 'azureclitask.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +tmr.setInput('connectionType', 'azureDevOps'); +tmr.setInput('azureDevOpsServiceConnection', 'TestAzureDevOpsConnection'); +tmr.setInput('scriptType', 'bash'); +tmr.setInput('scriptLocation', 'inlineScript'); +tmr.setInput('inlineScript', 'echo "test"'); +tmr.setInput('failOnStandardError', 'false'); +tmr.setInput('visibleAzLogin', 'false'); +tmr.setInput('useGlobalConfig', 'false'); +tmr.setInput('cwd', __dirname); + +process.env['ENDPOINT_AUTH_TestAzureDevOpsConnection'] = JSON.stringify({ + scheme: 'WorkloadIdentityFederation', + parameters: { + serviceprincipalid: 'test-sp-id', + tenantid: 'test-tenant-id' + } +}); +process.env['ENDPOINT_AUTH_SCHEME_TestAzureDevOpsConnection'] = 'WorkloadIdentityFederation'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_SERVICEPRINCIPALID'] = 'test-sp-id'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_TENANTID'] = 'test-tenant-id'; + +process.env['SYSTEM_COLLECTIONURI'] = 'https://dev.azure.com/testorg/'; +process.env['SYSTEM_TEAMPROJECT'] = 'TestProject'; +process.env['SYSTEM_JOBID'] = 'job-123'; +process.env['SYSTEM_PLANID'] = 'plan-456'; +process.env['SYSTEM_TEAMPROJECTID'] = 'project-789'; +process.env['SYSTEM_HOSTTYPE'] = 'build'; +process.env['AGENT_TEMPDIRECTORY'] = __dirname; + +process.env['AZP_AZURECLIV2_SETUP_PROXY_ENV'] = 'false'; +process.env['ShowWarningOnOlderAzureModules'] = 'false'; +process.env['UseAzVersion'] = 'false'; + +let mockAnswers: ma.TaskLibAnswers = { + "which": { + "az": "az", + "bash": "bash" + }, + "checkPath": { + "az": true, + "bash": true + }, + "exec": { + "az --version": { + "code": 0, + "stdout": "azure-cli 2.50.0" + }, + "az version": { + "code": 0, + "stdout": "{\"azure-cli\": \"2.50.0\", \"azure-cli-core\": \"2.50.0\"}" + }, + "az extension show --name azure-devops": { + "code": 1, + "stdout": "Extension not found" + }, + "az extension add -n azure-devops -y": { + "code": 0, + "stdout": "Azure DevOps CLI extension installed" + }, + "az login --service-principal -u \"test-sp-id\" --tenant \"test-tenant-id\" --allow-no-subscriptions --federated-token \"mock-token\" --output none": { + "code": 0, + "stdout": "Login successful" + }, + "az devops configure --defaults organization=\"https://dev.azure.com/testorg/\"": { + "code": 0, + "stdout": "organization configured" + }, + // This will fail to test FailedToSetAzureDevOpsProject error message + "az devops configure --defaults project=\"TestProject\"": { + "code": 1, + "stdout": "Failed to configure project" + }, + "az devops configure --defaults project='' organization=": { + "code": 0, + "stdout": "configuration cleared" + }, + "bash*": { + "code": 0, + "stdout": "test completed" + }, + "*": { + "code": 0, + "stdout": "test completed" + } + }, + "exists": { + "bash": true + } +}; + +tmr.setAnswers(mockAnswers); + +tmr.registerMock('azure-devops-node-api', { + getHandlerFromToken: () => ({}), + WebApi: function() { + return { + getTaskApi: () => Promise.resolve({ + createOidcToken: () => Promise.resolve({ oidcToken: 'mock-token' }) + }) + }; + } +}); + +tmr.registerMock('azure-pipelines-tasks-artifacts-common/webapi', { + getSystemAccessToken: () => 'system-token' +}); + +// Mock the task library localization for project error +tmr.setVariableName('FailedToSetAzureDevOpsProject', 'loc_mock_FailedToSetAzureDevOpsProject'); + +tmr.registerMock('./src/Utility', { + Utility: { + checkIfAzurePythonSdkIsInstalled: function() { + return true; + }, + throwIfError: function(result: any, errormsg?: string) { + if (result && result.code !== 0) { + throw new Error(errormsg || 'Command failed'); + } + }, + getScriptPath: function(scriptLocation: string, fileExtensions: string[]) { + return Promise.resolve(path.join(__dirname, 'test-script.sh')); + }, + getPowerShellScriptPath: function(scriptLocation: string, fileExtensions: string[], scriptArguments: string) { + return Promise.resolve(path.join(__dirname, 'test-script.ps1')); + }, + createFile: function(filePath: string, data: string, options?: any) { + return Promise.resolve(); + }, + deleteFile: function(filePath: string) { + return Promise.resolve(); + } + } +}); + +// Mock the ScriptType module +tmr.registerMock('./src/ScriptType', { + ScriptTypeFactory: { + getScriptType: function() { + return { + getTool: function() { + return Promise.resolve({ + on: function(event: string, callback: Function) { + // No-op for event handlers + }, + line: function(args: string) { + // No-op for argument line + }, + arg: function(args: string) { + // No-op for arguments + return this; + }, + exec: function(options?: any) { + console.log('Mock script execution completed'); + return Promise.resolve(0); + } + }); + }, + cleanUp: function() { + return Promise.resolve(); + } + }; + } + } +}); + +tmr.run(); \ No newline at end of file diff --git a/Tasks/AzureCLIV3/Tests/L0AzureDevOpsSpecialCharacterOrg.ts b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsSpecialCharacterOrg.ts new file mode 100644 index 000000000000..42c1229b75b6 --- /dev/null +++ b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsSpecialCharacterOrg.ts @@ -0,0 +1,130 @@ +import ma = require('azure-pipelines-task-lib/mock-answer'); +import tmrm = require('azure-pipelines-task-lib/mock-run'); +import path = require('path'); + +let taskPath = path.join(__dirname, '..', 'azureclitask.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +tmr.setInput('connectionType', 'azureDevOps'); +tmr.setInput('azureDevOpsServiceConnection', 'TestAzureDevOpsConnection'); +tmr.setInput('scriptType', 'bash'); +tmr.setInput('scriptLocation', 'inlineScript'); +tmr.setInput('inlineScript', 'echo "test"'); +tmr.setInput('failOnStandardError', 'false'); +tmr.setInput('visibleAzLogin', 'false'); +tmr.setInput('useGlobalConfig', 'false'); +tmr.setInput('cwd', __dirname); + +process.env['ENDPOINT_AUTH_TestAzureDevOpsConnection'] = JSON.stringify({ + scheme: 'WorkloadIdentityFederation', + parameters: { + serviceprincipalid: 'test-sp-id', + tenantid: 'test-tenant-id' + } +}); +process.env['ENDPOINT_AUTH_SCHEME_TestAzureDevOpsConnection'] = 'WorkloadIdentityFederation'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_SERVICEPRINCIPALID'] = 'test-sp-id'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_TENANTID'] = 'test-tenant-id'; + +// Special characters in organization URL +process.env['SYSTEM_COLLECTIONURI'] = 'https://dev.azure.com/test-org_with-special/'; +process.env['SYSTEM_TEAMPROJECT'] = 'Test Project (2024)'; +process.env['SYSTEM_JOBID'] = 'test-job-id'; +process.env['SYSTEM_PLANID'] = 'test-plan-id'; +process.env['SYSTEM_TEAMPROJECTID'] = 'test-project-id'; +process.env['SYSTEM_HOSTTYPE'] = 'build'; +process.env['AGENT_TEMPDIRECTORY'] = __dirname; + +process.env['AZP_AZURECLIV2_SETUP_PROXY_ENV'] = 'false'; +process.env['ShowWarningOnOlderAzureModules'] = 'false'; +process.env['UseAzVersion'] = 'false'; + +let mockAnswers: ma.TaskLibAnswers = { + "which": { + "az": "az", + "bash": "bash" + }, + "checkPath": { + "az": true, + "bash": true + }, + "exec": { + "az --version": { + "code": 0, + "stdout": "azure-cli 2.50.0" + }, + "az version": { + "code": 0, + "stdout": "{\"azure-cli\": \"2.50.0\", \"azure-cli-core\": \"2.50.0\"}" + }, + "az extension show --name azure-devops": { + "code": 1, + "stdout": "Extension not found" + }, + "az extension add -n azure-devops -y": { + "code": 0, + "stdout": "Azure DevOps CLI extension installed" + }, + "az login --service-principal -u \"test-sp-id\" --tenant \"test-tenant-id\" --allow-no-subscriptions --federated-token \"mock-token\" --output none": { + "code": 0, + "stdout": "Login successful" + }, + "az devops configure --defaults organization=\"https://dev.azure.com/test-org_with-special/\"": { + "code": 0, + "stdout": "organization configured" + }, + "az devops configure --defaults project=\"Test Project (2024)\"": { + "code": 0, + "stdout": "project configured" + }, + "az devops configure --defaults project='' organization=": { + "code": 0, + "stdout": "configuration cleared" + }, + "bash*": { + "code": 0, + "stdout": "test completed" + } + }, + "exists": { + "bash": true + } +}; + +tmr.setAnswers(mockAnswers); + +tmr.registerMock('azure-devops-node-api', { + getHandlerFromToken: () => ({}), + WebApi: function() { + return { + getTaskApi: () => Promise.resolve({ + createOidcToken: () => Promise.resolve({ oidcToken: 'mock-token' }) + }) + }; + } +}); + +tmr.registerMock('azure-pipelines-tasks-artifacts-common/webapi', { + getSystemAccessToken: () => 'system-token' +}); + +tmr.registerMock('./src/Utility', { + Utility: { + throwIfError: () => {}, + checkIfAzurePythonSdkIsInstalled: () => true + } +}); + +tmr.registerMock('./src/ScriptType', { + ScriptTypeFactory: { + getScriptType: () => ({ + getTool: () => Promise.resolve({ + on: () => {}, + exec: () => Promise.resolve(0) + }), + cleanUp: () => Promise.resolve() + }) + } +}); + +tmr.run(); \ No newline at end of file diff --git a/Tasks/AzureCLIV3/Tests/L0AzureDevOpsSpecialCharacters.ts b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsSpecialCharacters.ts new file mode 100644 index 000000000000..e440885ac522 --- /dev/null +++ b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsSpecialCharacters.ts @@ -0,0 +1,172 @@ +import ma = require('azure-pipelines-task-lib/mock-answer'); +import tmrm = require('azure-pipelines-task-lib/mock-run'); +import path = require('path'); + +let taskPath = path.join(__dirname, '..', 'azureclitask.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +tmr.setInput('connectionType', 'azureDevOps'); +tmr.setInput('azureDevOpsServiceConnection', 'TestAzureDevOpsConnection'); +tmr.setInput('scriptType', 'bash'); +tmr.setInput('scriptLocation', 'inlineScript'); +tmr.setInput('inlineScript', 'echo "test"'); +tmr.setInput('failOnStandardError', 'false'); +tmr.setInput('visibleAzLogin', 'false'); +tmr.setInput('useGlobalConfig', 'false'); +tmr.setInput('cwd', __dirname); + +process.env['ENDPOINT_AUTH_TestAzureDevOpsConnection'] = JSON.stringify({ + scheme: 'WorkloadIdentityFederation', + parameters: { + serviceprincipalid: 'test-sp-id', + tenantid: 'test-tenant-id' + } +}); +process.env['ENDPOINT_AUTH_SCHEME_TestAzureDevOpsConnection'] = 'WorkloadIdentityFederation'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_SERVICEPRINCIPALID'] = 'test-sp-id'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_TENANTID'] = 'test-tenant-id'; + +process.env['ENDPOINT_DATA_TestAzureDevOpsConnection'] = JSON.stringify({ + organizationUrl: 'https://dev.azure.com/test-org%20with%20spaces/' +}); +process.env['ENDPOINT_URL_TestAzureDevOpsConnection'] = 'https://dev.azure.com/test-org%20with%20spaces/'; + +process.env['SYSTEM_COLLECTIONURI'] = 'https://dev.azure.com/test-org%20with%20spaces/'; +process.env['SYSTEM_TEAMPROJECT'] = 'Test Project With Spaces'; +process.env['SYSTEM_JOBID'] = 'test-job-id'; +process.env['SYSTEM_PLANID'] = 'test-plan-id'; +process.env['SYSTEM_TEAMPROJECTID'] = 'test-project-id'; +process.env['SYSTEM_HOSTTYPE'] = 'build'; +process.env['AGENT_TEMPDIRECTORY'] = __dirname; +process.env['AGENT_WORKFOLDER'] = __dirname; + +process.env['AZP_AZURECLIV2_SETUP_PROXY_ENV'] = 'false'; +process.env['ShowWarningOnOlderAzureModules'] = 'false'; +process.env['UseAzVersion'] = 'false'; + +let mockAnswers: ma.TaskLibAnswers = { + "which": { + "az": "az", + "bash": "bash" + }, + "checkPath": { + "az": true, + "bash": true + }, + "exec": { + "az --version": { + "code": 0, + "stdout": "azure-cli 2.50.0" + }, + "az version": { + "code": 0, + "stdout": "{\"azure-cli\": \"2.50.0\", \"azure-cli-core\": \"2.50.0\"}" + }, + "az extension show --name azure-devops": { + "code": 0, + "stdout": "{\n \"name\": \"azure-devops\",\n \"version\": \"1.0.2\"\n}" + }, + "az login --service-principal -u \"test-sp-id\" --tenant \"test-tenant-id\" --allow-no-subscriptions --federated-token \"mock-token\" --output none": { + "code": 0, + "stdout": "Login successful" + }, + "az devops configure --defaults organization=\"https://dev.azure.com/test-org%20with%20spaces/\"": { + "code": 0, + "stdout": "organization configured with special characters" + }, + "az devops configure --defaults project=\"Test Project With Spaces\"": { + "code": 0, + "stdout": "project configured with spaces" + }, + "az devops configure --defaults project='' organization=": { + "code": 0, + "stdout": "configuration cleared" + }, + "bash*": { + "code": 0, + "stdout": "test completed" + }, + "*": { + "code": 0, + "stdout": "test completed" + } + }, + "exists": { + "bash": true + } +}; + +tmr.setAnswers(mockAnswers); + +tmr.registerMock('azure-devops-node-api', { + getHandlerFromToken: () => ({}), + WebApi: function() { + return { + getTaskApi: () => Promise.resolve({ + createOidcToken: () => Promise.resolve({ oidcToken: 'mock-token' }) + }) + }; + } +}); + +tmr.registerMock('azure-pipelines-tasks-artifacts-common/webapi', { + getSystemAccessToken: () => 'system-token' +}); + +tmr.registerMock('./src/Utility', { + Utility: { + checkIfAzurePythonSdkIsInstalled: function() { + return true; + }, + throwIfError: function(result: any, errormsg?: string) { + if (result && result.code !== 0) { + throw new Error(errormsg || 'Command failed'); + } + }, + getScriptPath: function(scriptLocation: string, fileExtensions: string[]) { + return Promise.resolve(path.join(__dirname, 'test-script.sh')); + }, + getPowerShellScriptPath: function(scriptLocation: string, fileExtensions: string[], scriptArguments: string) { + return Promise.resolve(path.join(__dirname, 'test-script.ps1')); + }, + createFile: function(filePath: string, data: string, options?: any) { + return Promise.resolve(); + }, + deleteFile: function(filePath: string) { + return Promise.resolve(); + } + } +}); + +// Mock the ScriptType module +tmr.registerMock('./src/ScriptType', { + ScriptTypeFactory: { + getScriptType: function() { + return { + getTool: function() { + return Promise.resolve({ + on: function(event: string, callback: Function) { + // No-op for event handlers + }, + line: function(args: string) { + // No-op for argument line + }, + arg: function(args: string) { + // No-op for arguments + return this; + }, + exec: function(options?: any) { + console.log('Mock script execution completed'); + return Promise.resolve(0); + } + }); + }, + cleanUp: function() { + return Promise.resolve(); + } + }; + } + } +}); + +tmr.run(); \ No newline at end of file diff --git a/Tasks/AzureCLIV3/Tests/L0AzureDevOpsUnsupportedAuthScheme.ts b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsUnsupportedAuthScheme.ts new file mode 100644 index 000000000000..304a7f026927 --- /dev/null +++ b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsUnsupportedAuthScheme.ts @@ -0,0 +1,155 @@ +import ma = require('azure-pipelines-task-lib/mock-answer'); +import tmrm = require('azure-pipelines-task-lib/mock-run'); +import path = require('path'); + +let taskPath = path.join(__dirname, '..', 'azureclitask.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +tmr.setInput('connectionType', 'azureDevOps'); +tmr.setInput('azureDevOpsServiceConnection', 'TestAzureDevOpsConnection'); +tmr.setInput('scriptType', 'bash'); +tmr.setInput('scriptLocation', 'inlineScript'); +tmr.setInput('inlineScript', 'echo "test"'); +tmr.setInput('failOnStandardError', 'false'); +tmr.setInput('visibleAzLogin', 'false'); +tmr.setInput('useGlobalConfig', 'false'); +tmr.setInput('cwd', __dirname); + +process.env['ENDPOINT_AUTH_TestAzureDevOpsConnection'] = JSON.stringify({ + scheme: 'ServicePrincipal', + parameters: { + serviceprincipalid: 'test-sp-id', + serviceprincipalkey: 'test-sp-key', + tenantid: 'test-tenant-id' + } +}); +process.env['ENDPOINT_AUTH_SCHEME_TestAzureDevOpsConnection'] = 'ServicePrincipal'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_SERVICEPRINCIPALID'] = 'test-sp-id'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_SERVICEPRINCIPALKEY'] = 'test-sp-key'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_TENANTID'] = 'test-tenant-id'; + +process.env['ENDPOINT_DATA_TestAzureDevOpsConnection'] = JSON.stringify({ + organizationUrl: 'https://dev.azure.com/testorg/' +}); +process.env['ENDPOINT_URL_TestAzureDevOpsConnection'] = 'https://dev.azure.com/testorg/'; + +process.env['SYSTEM_COLLECTIONURI'] = 'https://dev.azure.com/testorg/'; +process.env['SYSTEM_TEAMPROJECT'] = 'TestProject'; +process.env['SYSTEM_JOBID'] = 'test-job-id'; +process.env['SYSTEM_PLANID'] = 'test-plan-id'; +process.env['SYSTEM_TEAMPROJECTID'] = 'test-project-id'; +process.env['SYSTEM_HOSTTYPE'] = 'build'; +process.env['AGENT_TEMPDIRECTORY'] = __dirname; +process.env['AGENT_WORKFOLDER'] = __dirname; + +process.env['AZP_AZURECLIV2_SETUP_PROXY_ENV'] = 'false'; +process.env['ShowWarningOnOlderAzureModules'] = 'false'; +process.env['UseAzVersion'] = 'false'; + +let mockAnswers: ma.TaskLibAnswers = { + "which": { + "az": "az", + "bash": "bash" + }, + "checkPath": { + "az": true, + "bash": true + }, + "exec": { + "az --version": { + "code": 0, + "stdout": "azure-cli 2.50.0" + }, + "az version": { + "code": 0, + "stdout": "{\"azure-cli\": \"2.50.0\", \"azure-cli-core\": \"2.50.0\"}" + }, + "az extension show --name azure-devops": { + "code": 1, + "stdout": "Extension not found" + }, + "az extension add -n azure-devops -y": { + "code": 0, + "stdout": "Azure DevOps CLI extension installed" + } + }, + "exists": { + "bash": true + } +}; + +tmr.setAnswers(mockAnswers); + +tmr.registerMock('azure-devops-node-api', { + getHandlerFromToken: () => ({}), + WebApi: function() { + return { + getTaskApi: () => Promise.resolve({ + createOidcToken: () => Promise.resolve({ oidcToken: 'mock-token' }) + }) + }; + } +}); + +tmr.registerMock('azure-pipelines-tasks-artifacts-common/webapi', { + getSystemAccessToken: () => 'system-token' +}); + +// Mock the Utility module +tmr.registerMock('./src/Utility', { + Utility: { + checkIfAzurePythonSdkIsInstalled: function() { + return true; + }, + throwIfError: function(result: any, errormsg?: string) { + if (result && result.code !== 0) { + throw new Error(errormsg || 'Command failed'); + } + }, + getScriptPath: function(scriptLocation: string, fileExtensions: string[]) { + return Promise.resolve(path.join(__dirname, 'test-script.sh')); + }, + getPowerShellScriptPath: function(scriptLocation: string, fileExtensions: string[], scriptArguments: string) { + return Promise.resolve(path.join(__dirname, 'test-script.ps1')); + }, + createFile: function(filePath: string, data: string, options?: any) { + return Promise.resolve(); + }, + deleteFile: function(filePath: string) { + return Promise.resolve(); + } + } +}); + +// Mock the ScriptType module +tmr.registerMock('./src/ScriptType', { + ScriptTypeFactory: { + getScriptType: function() { + return { + getTool: function() { + return Promise.resolve({ + on: function(event: string, callback: Function) { + // No-op for event handlers + }, + line: function(args: string) { + // No-op for argument line + }, + arg: function(args: string) { + // No-op for arguments + return this; + }, + exec: function(options?: any) { + console.log('Mock script execution completed'); + return Promise.resolve(0); + } + }); + }, + cleanUp: function() { + return Promise.resolve(); + } + }; + } + } +}); + +tmr.run(); diff --git a/Tasks/AzureCLIV3/Tests/L0AzureDevOpsVisibleLogin.ts b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsVisibleLogin.ts new file mode 100644 index 000000000000..8dbcd425f334 --- /dev/null +++ b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsVisibleLogin.ts @@ -0,0 +1,170 @@ +import ma = require('azure-pipelines-task-lib/mock-answer'); +import tmrm = require('azure-pipelines-task-lib/mock-run'); +import path = require('path'); + +let taskPath = path.join(__dirname, '..', 'azureclitask.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +tmr.setInput('connectionType', 'azureDevOps'); +tmr.setInput('azureDevOpsServiceConnection', 'TestAzureDevOpsConnection'); +tmr.setInput('scriptType', 'bash'); +tmr.setInput('scriptLocation', 'inlineScript'); +tmr.setInput('inlineScript', 'echo "test"'); +tmr.setInput('failOnStandardError', 'false'); +tmr.setInput('visibleAzLogin', 'true'); // Enable visible login +tmr.setInput('useGlobalConfig', 'false'); +tmr.setInput('cwd', __dirname); + +process.env['ENDPOINT_AUTH_TestAzureDevOpsConnection'] = JSON.stringify({ + scheme: 'WorkloadIdentityFederation', + parameters: { + serviceprincipalid: 'test-sp-id', + tenantid: 'test-tenant-id' + } +}); +process.env['ENDPOINT_AUTH_SCHEME_TestAzureDevOpsConnection'] = 'WorkloadIdentityFederation'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_SERVICEPRINCIPALID'] = 'test-sp-id'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_TENANTID'] = 'test-tenant-id'; + +process.env['SYSTEM_COLLECTIONURI'] = 'https://dev.azure.com/testorg/'; +process.env['SYSTEM_TEAMPROJECT'] = 'TestProject'; +process.env['SYSTEM_JOBID'] = 'test-job-id'; +process.env['SYSTEM_PLANID'] = 'test-plan-id'; +process.env['SYSTEM_TEAMPROJECTID'] = 'test-project-id'; +process.env['SYSTEM_HOSTTYPE'] = 'build'; +process.env['AGENT_TEMPDIRECTORY'] = __dirname; + +process.env['AZP_AZURECLIV2_SETUP_PROXY_ENV'] = 'false'; +process.env['ShowWarningOnOlderAzureModules'] = 'false'; +process.env['UseAzVersion'] = 'false'; + +let mockAnswers: ma.TaskLibAnswers = { + "which": { + "az": "az", + "bash": "bash" + }, + "checkPath": { + "az": true, + "bash": true + }, + "exec": { + "az --version": { + "code": 0, + "stdout": "azure-cli 2.50.0" + }, + "az version": { + "code": 0, + "stdout": "{\"azure-cli\": \"2.50.0\", \"azure-cli-core\": \"2.50.0\"}" + }, + "az extension show --name azure-devops": { + "code": 1, + "stdout": "Extension not found" + }, + "az extension add -n azure-devops -y": { + "code": 0, + "stdout": "Azure DevOps CLI extension installed" + }, + "az login --service-principal -u \"test-sp-id\" --tenant \"test-tenant-id\" --allow-no-subscriptions --federated-token \"mock-token\"": { + "code": 0, + "stdout": "Login successful with visible output" + }, + "az devops configure --defaults organization=\"https://dev.azure.com/testorg/\"": { + "code": 0, + "stdout": "organization configured" + }, + "az devops configure --defaults project=\"TestProject\"": { + "code": 0, + "stdout": "project configured" + }, + "az devops configure --defaults project='' organization=": { + "code": 0, + "stdout": "configuration cleared" + }, + "bash*": { + "code": 0, + "stdout": "test completed" + }, + "*": { + "code": 0, + "stdout": "test completed" + } + }, + "exists": { + "bash": true + } +}; + +tmr.setAnswers(mockAnswers); + +tmr.registerMock('azure-devops-node-api', { + getHandlerFromToken: () => ({}), + WebApi: function() { + return { + getTaskApi: () => Promise.resolve({ + createOidcToken: () => Promise.resolve({ oidcToken: 'mock-token' }) + }) + }; + } +}); + +tmr.registerMock('azure-pipelines-tasks-artifacts-common/webapi', { + getSystemAccessToken: () => 'system-token' +}); + +tmr.registerMock('./src/Utility', { + Utility: { + checkIfAzurePythonSdkIsInstalled: function() { + return true; + }, + throwIfError: function(result: any, errormsg?: string) { + if (result && result.code !== 0) { + throw new Error(errormsg || 'Command failed'); + } + }, + getScriptPath: function(scriptLocation: string, fileExtensions: string[]) { + return Promise.resolve(path.join(__dirname, 'test-script.sh')); + }, + getPowerShellScriptPath: function(scriptLocation: string, fileExtensions: string[], scriptArguments: string) { + return Promise.resolve(path.join(__dirname, 'test-script.ps1')); + }, + createFile: function(filePath: string, data: string, options?: any) { + return Promise.resolve(); + }, + deleteFile: function(filePath: string) { + return Promise.resolve(); + } + } +}); + +// Mock the ScriptType module +tmr.registerMock('./src/ScriptType', { + ScriptTypeFactory: { + getScriptType: function() { + return { + getTool: function() { + return Promise.resolve({ + on: function(event: string, callback: Function) { + // No-op for event handlers + }, + line: function(args: string) { + // No-op for argument line + }, + arg: function(args: string) { + // No-op for arguments + return this; + }, + exec: function(options?: any) { + console.log('Mock script execution completed'); + return Promise.resolve(0); + } + }); + }, + cleanUp: function() { + return Promise.resolve(); + } + }; + } + } +}); + +tmr.run(); \ No newline at end of file diff --git a/Tasks/AzureCLIV3/Tests/L0AzureDevOpsWifConnection.ts b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsWifConnection.ts new file mode 100644 index 000000000000..5069c517affb --- /dev/null +++ b/Tasks/AzureCLIV3/Tests/L0AzureDevOpsWifConnection.ts @@ -0,0 +1,188 @@ +import ma = require('azure-pipelines-task-lib/mock-answer'); +import tmrm = require('azure-pipelines-task-lib/mock-run'); +import path = require('path'); + +let taskPath = path.join(__dirname, '..', 'azureclitask.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +tmr.setInput('connectionType', 'azureDevOps'); +tmr.setInput('azureDevOpsServiceConnection', 'TestAzureDevOpsConnection'); +tmr.setInput('scriptType', 'bash'); +tmr.setInput('scriptLocation', 'inlineScript'); +tmr.setInput('inlineScript', 'echo "test"'); +tmr.setInput('failOnStandardError', 'false'); +tmr.setInput('visibleAzLogin', 'false'); +tmr.setInput('useGlobalConfig', 'false'); +tmr.setInput('cwd', __dirname); + +process.env['ENDPOINT_AUTH_TestAzureDevOpsConnection'] = JSON.stringify({ + scheme: 'WorkloadIdentityFederation', + parameters: { + serviceprincipalid: 'test-sp-id', + tenantid: 'test-tenant-id' + } +}); +process.env['ENDPOINT_AUTH_SCHEME_TestAzureDevOpsConnection'] = 'WorkloadIdentityFederation'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_SERVICEPRINCIPALID'] = 'test-sp-id'; +process.env['ENDPOINT_AUTH_PARAMETER_TestAzureDevOpsConnection_TENANTID'] = 'test-tenant-id'; + +process.env['ENDPOINT_DATA_TestAzureDevOpsConnection'] = JSON.stringify({ + organizationUrl: 'https://dev.azure.com/testorg/' +}); +process.env['ENDPOINT_URL_TestAzureDevOpsConnection'] = 'https://dev.azure.com/testorg/'; + +process.env['SYSTEM_COLLECTIONURI'] = 'https://dev.azure.com/testorg/'; +process.env['SYSTEM_TEAMPROJECT'] = 'TestProject'; +process.env['SYSTEM_JOBID'] = 'test-job-id'; +process.env['SYSTEM_PLANID'] = 'test-plan-id'; +process.env['SYSTEM_TEAMPROJECTID'] = 'test-project-id'; +process.env['SYSTEM_HOSTTYPE'] = 'build'; +process.env['AGENT_TEMPDIRECTORY'] = __dirname; +process.env['AGENT_WORKFOLDER'] = __dirname; + +process.env['AZP_AZURECLIV2_SETUP_PROXY_ENV'] = 'false'; +process.env['ShowWarningOnOlderAzureModules'] = 'false'; +process.env['UseAzVersion'] = 'false'; + +let mockAnswers: ma.TaskLibAnswers = { + "which": { + "az": "az", + "bash": "bash" + }, + "checkPath": { + "az": true, + "bash": true + }, + "exec": { + "az --version": { + "code": 0, + "stdout": "azure-cli 2.50.0" + }, + "az version": { + "code": 0, + "stdout": "{\"azure-cli\": \"2.50.0\", \"azure-cli-core\": \"2.50.0\"}" + }, + "az extension show --name azure-devops": { + "code": 1, + "stdout": "Extension not found" + }, + "az extension add -n azure-devops -y": { + "code": 0, + "stdout": "Azure DevOps CLI extension installed" + }, + "az login --service-principal -u \"test-sp-id\" --tenant \"test-tenant-id\" --allow-no-subscriptions --federated-token \"mock-token\" --output none": { + "code": 0, + "stdout": "Login successful" + }, + "az devops configure --defaults organization=\"https://dev.azure.com/testorg/\"": { + "code": 0, + "stdout": "organization configured" + }, + "az devops configure --defaults project=\"TestProject\"": { + "code": 0, + "stdout": "project configured" + }, + "*": { + "code": 0, + "stdout": "test completed" + } + }, + "exists": { + "bash": true + } +}; + +tmr.setAnswers(mockAnswers); + +// Mock the Utility module +tmr.registerMock('./src/Utility', { + Utility: { + checkIfAzurePythonSdkIsInstalled: function() { + return true; + }, + throwIfError: function(result: any, errormsg?: string) { + if (result && result.code !== 0) { + throw new Error(errormsg || 'Command failed'); + } + }, + getScriptPath: function(scriptLocation: string, fileExtensions: string[]) { + return Promise.resolve(path.join(__dirname, 'test-script.sh')); + }, + getPowerShellScriptPath: function(scriptLocation: string, fileExtensions: string[], scriptArguments: string) { + return Promise.resolve(path.join(__dirname, 'test-script.ps1')); + }, + createFile: function(filePath: string, data: string, options?: any) { + return Promise.resolve(); + }, + deleteFile: function(filePath: string) { + return Promise.resolve(); + } + } +}); + +// Mock the ScriptType module +tmr.registerMock('./src/ScriptType', { + ScriptTypeFactory: { + getScriptType: function() { + return { + getTool: function() { + return Promise.resolve({ + on: function(event: string, callback: Function) { + // No-op for event handlers + }, + line: function(args: string) { + // No-op for argument line + }, + arg: function(args: string) { + // No-op for arguments + return this; + }, + exec: function(options?: any) { + console.log('Mock script execution completed'); + return Promise.resolve(0); + } + }); + }, + cleanUp: function() { + return Promise.resolve(); + } + }; + } + } +}); + +tmr.registerMock('azure-devops-node-api', { + getHandlerFromToken: () => ({}), + WebApi: function() { + return { + getTaskApi: () => Promise.resolve({ + createOidcToken: () => Promise.resolve({ oidcToken: 'mock-token' }) + }) + }; + } +}); + +tmr.registerMock('azure-pipelines-tasks-artifacts-common/webapi', { + getSystemAccessToken: () => 'system-token' +}); + +tmr.registerMock('./src/Utility', { + Utility: { + throwIfError: () => {}, + checkIfAzurePythonSdkIsInstalled: () => true + } +}); + +tmr.registerMock('./src/ScriptType', { + ScriptTypeFactory: { + getScriptType: () => ({ + getTool: () => Promise.resolve({ + on: () => {}, + exec: () => Promise.resolve(0) + }), + cleanUp: () => Promise.resolve() + }) + } +}); + +tmr.run(); diff --git a/Tasks/AzureCLIV3/Tests/L0ConnectionTypeValidation.ts b/Tasks/AzureCLIV3/Tests/L0ConnectionTypeValidation.ts new file mode 100644 index 000000000000..bc303fbd8d8a --- /dev/null +++ b/Tasks/AzureCLIV3/Tests/L0ConnectionTypeValidation.ts @@ -0,0 +1,126 @@ +import ma = require('azure-pipelines-task-lib/mock-answer'); +import tmrm = require('azure-pipelines-task-lib/mock-run'); +import path = require('path'); + +let taskPath = path.join(__dirname, '..', 'azureclitask.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +// Test invalid connectionType input - should throw error +tmr.setInput('connectionType', 'invalidConnectionType'); +tmr.setInput('scriptType', 'bash'); +tmr.setInput('scriptLocation', 'inlineScript'); +tmr.setInput('inlineScript', 'echo "test"'); +tmr.setInput('failOnStandardError', 'false'); +tmr.setInput('visibleAzLogin', 'false'); +tmr.setInput('useGlobalConfig', 'false'); +tmr.setInput('cwd', __dirname); + +process.env['AGENT_TEMPDIRECTORY'] = __dirname; +process.env['AGENT_WORKFOLDER'] = __dirname; +process.env['SYSTEM_JOBID'] = 'test-job-id'; +process.env['SYSTEM_PLANID'] = 'test-plan-id'; +process.env['SYSTEM_TEAMPROJECTID'] = 'test-project-id'; +process.env['SYSTEM_HOSTTYPE'] = 'build'; + +process.env['AZP_AZURECLIV2_SETUP_PROXY_ENV'] = 'false'; +process.env['ShowWarningOnOlderAzureModules'] = 'false'; +process.env['UseAzVersion'] = 'false'; + +let mockAnswers: ma.TaskLibAnswers = { + "which": { + "az": "az", + "bash": "bash" + }, + "checkPath": { + "az": true, + "bash": true + }, + "exec": { + "az --version": { + "code": 0, + "stdout": "azure-cli 2.50.0" + }, + "az version": { + "code": 0, + "stdout": "{\"azure-cli\": \"2.50.0\", \"azure-cli-core\": \"2.50.0\"}" + } + }, + "exists": { + "bash": true + } +}; + +tmr.setAnswers(mockAnswers); + +tmr.registerMock('azure-devops-node-api', { + getHandlerFromToken: () => ({}), + WebApi: function() { + return { + getTaskApi: () => Promise.resolve({ + createOidcToken: () => Promise.resolve({ oidcToken: 'mock-token' }) + }) + }; + } +}); + +tmr.registerMock('azure-pipelines-tasks-artifacts-common/webapi', { + getSystemAccessToken: () => 'system-token' +}); + +tmr.registerMock('./src/Utility', { + Utility: { + checkIfAzurePythonSdkIsInstalled: function() { + return true; + }, + throwIfError: function(result: any, errormsg?: string) { + if (result && result.code !== 0) { + throw new Error(errormsg || 'Command failed'); + } + }, + getScriptPath: function(scriptLocation: string, fileExtensions: string[]) { + return Promise.resolve(path.join(__dirname, 'test-script.sh')); + }, + getPowerShellScriptPath: function(scriptLocation: string, fileExtensions: string[], scriptArguments: string) { + return Promise.resolve(path.join(__dirname, 'test-script.ps1')); + }, + createFile: function(filePath: string, data: string, options?: any) { + return Promise.resolve(); + }, + deleteFile: function(filePath: string) { + return Promise.resolve(); + } + } +}); + +// Mock the ScriptType module +tmr.registerMock('./src/ScriptType', { + ScriptTypeFactory: { + getScriptType: function() { + return { + getTool: function() { + return Promise.resolve({ + on: function(event: string, callback: Function) { + // No-op for event handlers + }, + line: function(args: string) { + // No-op for argument line + }, + arg: function(args: string) { + // No-op for arguments + return this; + }, + exec: function(options?: any) { + console.log('Mock script execution completed'); + return Promise.resolve(0); + } + }); + }, + cleanUp: function() { + return Promise.resolve(); + } + }; + } + } +}); + +tmr.run(); \ No newline at end of file diff --git a/Tasks/AzureCLIV3/Tests/package-lock.json b/Tasks/AzureCLIV3/Tests/package-lock.json new file mode 100644 index 000000000000..d40c37932e06 --- /dev/null +++ b/Tasks/AzureCLIV3/Tests/package-lock.json @@ -0,0 +1,1502 @@ +{ + "name": "azure-cli-tests", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "azure-cli-tests", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "@types/mocha": "^5.2.0", + "@types/node": "^16.0.0", + "azure-pipelines-task-lib": "^4.0.0", + "mocha": "^8.0.0", + "nock": "^13.0.0" + } + }, + "node_modules/@types/mocha": { + "version": "5.2.7", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha1-MV1XDMtWxTRS/4Y4c432BybVtuo=", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "16.18.126", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/@types/node/-/node-16.18.126.tgz", + "integrity": "sha1-J4dfqikmwPR1s5qLseVGwBdvjUs=", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha1-qlgEJxHW4ydd033Fl+XTHowpCkQ=", + "dev": true, + "license": "ISC" + }, + "node_modules/adm-zip": { + "version": "0.5.16", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/adm-zip/-/adm-zip-0.5.16.tgz", + "integrity": "sha1-C15Md58H3t6lgFzcyxFHBx2UqQk=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha1-Sf/1hXfP7j83F2/qtMIuAPhtf3c=", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha1-y7muJWv3UK8eqzRPIpqif+lLo0g=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha1-Ej1keekq1FrYl9QFTjx8p9tJROE=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha1-7dgDYornHATIWuegkG7a00tkiTc=", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha1-eQxYsZuhcgqEIFtXxhjVrYUklz4=", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha1-JG9Q88p4oyQPbJl+ipvR6sSeSzg=", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/azure-pipelines-task-lib": { + "version": "4.17.3", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/azure-pipelines-task-lib/-/azure-pipelines-task-lib-4.17.3.tgz", + "integrity": "sha1-/VMnGollIKefO6iDOcwLNiv51bk=", + "dev": true, + "license": "MIT", + "dependencies": { + "adm-zip": "^0.5.10", + "minimatch": "3.0.5", + "nodejs-file-downloader": "^4.11.1", + "q": "^1.5.1", + "semver": "^5.7.2", + "shelljs": "^0.8.5", + "uuid": "^3.0.1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha1-6D46fj8wCzTLnYf2FfoMvzV2kO4=", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha1-9uFKl4WNMnJSIAJC1Mz+UixEVSI=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha1-q5tFRGblqMw6GHvqrVgEEqnFuEM=", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/braces/-/braces-3.0.3.tgz", + "integrity": "sha1-SQMy9AkZRSJy1VqEgK3AxEE1h4k=", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha1-uqVZ7hTO1zRSIputcyZGfGH6vWA=", + "dev": true, + "license": "ISC" + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha1-VoW5XrIJrJwMF3Rnd4ychN9Yupo=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha1-qsTit3NKdAhnrrFr8CqtVWoeegE=", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha1-G33NyzK4E4gBs+R4umpRyqiWSNo=", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.5.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha1-7pznu+vSt59J8wR5nVRo4x4U5oo=", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.1" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha1-oCZe5lVHb8gHrqnfPfjfd4OAi08=", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha1-CCyyyJyf6GWaMRpTvWpNxTAdswQ=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha1-8Rb4Bk/pCz94RKOJl8C3UFEmnx0=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha1-JpxxF9J7Ba0uU2gwqOyJXvnG0BA=", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha1-nibGPTD1NEPpSJSVshBdN7Z6hdk=", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.3.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/debug/-/debug-4.3.1.tgz", + "integrity": "sha1-8NIpxQXgxtjEmsVT0bE9wYP2su4=", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/ms/-/ms-2.1.2.tgz", + "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", + "dev": true, + "license": "MIT" + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha1-qkcte/Zg6xXzSU79UxyrfypwmDc=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/diff/-/diff-5.0.0.tgz", + "integrity": "sha1-ftatdthZ0DB4fsNYVfWx2vMdhSs=", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha1-6Bj9ac5cz8tARZT4QpY79TFkzDc=", + "dev": true, + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha1-ARo/aYVroYnf+n3I/M6Z0qh5A+U=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha1-FLqDpdNz49MR5a/KKc9b+tllvzQ=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha1-RCZdPKwH4+p9wkdRY4BkN1SgUpI=", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha1-TJKBnstwg1YeT0okCoa+UZj1Nvw=", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/flat/-/flat-5.0.2.tgz", + "integrity": "sha1-jKb+MyBp/6nTJMMnGYxZglnOskE=", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha1-d31z1yqS+OxNLkEOtHNSpWuOg0A=", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha1-ysZAd4XQNnWipeGlMFxpezR9kNY=", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha1-LALYZNl/PqbIgwxGTL0Rq26rehw=", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha1-T5RBKoLbMvNuOwuXQfipf+sDH34=", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/glob/-/glob-7.1.6.tgz", + "integrity": "sha1-FB8zuBp8JJLhJVlDB0gMRmeSeKY=", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha1-hpgyxYA0/mikCTwX3BXoNA2EAcQ=", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/growl/-/growl-1.10.5.tgz", + "integrity": "sha1-8nNdwig2dPpnR4sQGBBZNVw2nl4=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.x" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha1-AD6vkb563DcuhOxZ3DclLO24AAM=", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/he/-/he-1.2.0.tgz", + "integrity": "sha1-hK5l+n6vsWX922FWauFLrwVmTw8=", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha1-xZ7yJKBP6LdU89sAY6Jeow0ABdY=", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w=", + "dev": true, + "license": "ISC" + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha1-Zlq4vE2iendKQFhOgS4+D6RbGh4=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha1-6h9/O4DwZCNug0cPhsCcJU+0Wwk=", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha1-KpiAGoSfQ+Kt1kT7trxiKbGaTvQ=", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha1-ZPYeQsu7LuwgcanawLKLoeZdUIQ=", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha1-ReQuN/zPH0Dajl927iFRWEDAkoc=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true, + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "4.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha1-9Ca8D/S0BRkmzViMcRExg0CaEh8=", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true, + "license": "ISC" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha1-VTIeswn+u8WcSAHZMackUqaB0oY=", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha1-abPMRtIPRI7M23XqH6cz2eghySA=", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha1-u6vNwChZ9JhzAchW4zh85exDv3A=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha1-OBqHG2KnNEUGYK497uRIE/cNlZo=", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.0.5", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha1-TajxKQ7g8PjoPWDKafjxNAaGBKM=", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha": { + "version": "8.4.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/mocha/-/mocha-8.4.0.tgz", + "integrity": "sha1-Z3voi/FZgKPK4Dpz4QoPw5l/DP8=", + "dev": true, + "license": "MIT", + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.0.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.20", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.1.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 10.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/ms/-/ms-2.1.3.tgz", + "integrity": "sha1-V0yBOM4dK1hh8LRFedut1gxmFbI=", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.1.20", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/nanoid/-/nanoid-3.1.20.tgz", + "integrity": "sha1-utwmPGsdzxS3HvqoX2q0wdbPx4g=", + "dev": true, + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nock": { + "version": "13.5.6", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/nock/-/nock-13.5.6.tgz", + "integrity": "sha1-Xmk+wjALv2A7Ydrm3wIlZz5sSZc=", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">= 10.13" + } + }, + "node_modules/nodejs-file-downloader": { + "version": "4.13.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/nodejs-file-downloader/-/nodejs-file-downloader-4.13.0.tgz", + "integrity": "sha1-2ofDAIHeX/TouGQGLJjN7APmatA=", + "dev": true, + "license": "ISC", + "dependencies": { + "follow-redirects": "^1.15.6", + "https-proxy-agent": "^5.0.0", + "mime-types": "^2.1.27", + "sanitize-filename": "^1.6.3" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha1-Dc1p/yOhybEf0JeDFmRKA4ghamU=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha1-4drMvnjQ0TiMoYxk/qOOPlfjcGs=", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha1-g8gxXGeFAF470CGDlBHJ4RDm2DQ=", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha1-UTvb4tO5XXdi6METfvoZXGxhtbM=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha1-+8EUtgykKzDZ2vWFjkvWi77bZzU=", + "dev": true, + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha1-O6ODNzNkbZ0+SZWUbBNlpn+wekI=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/propagate": { + "version": "2.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha1-QM3tqxgIXHkjNOZPCsFyVtOPmkU=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha1-32+ENy8CcNxlzfYpE0mrekc9Tyo=", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.5.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha1-m6dMAZsV02UnjS6Ru4xI17TULJ4=", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha1-tmPoP/sJu/I4aURza6roAwKbizk=", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha1-Hq+fqb2x/dTsdfWPnNtOa3gn7sY=", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.3", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha1-dV69dSBFkxl34wsgJdNA18kJA3g=", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/semver/-/semver-5.7.2.tgz", + "integrity": "sha1-SNVdtzfDKHzUg14X+hP+rOHEHvg=", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/serialize-javascript": { + "version": "5.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha1-eIbshIBJpGJGepfT2Rjrsqr5NPQ=", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha1-3gVUCNg2G+1mxmnS8ABTjO2O4gw=", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=", + "dev": true, + "license": "MIT", + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha1-MfEoGzgyYwQ0gxwxDAHMzajL4AY=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha1-zW/BfihQDP9WwbhsCn/UpUpzAFw=", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha1-btpL00SjyUrqN21MwxvHcxEDngk=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha1-FkjESq58jZiKMmAY7XL1tN0DkuQ=", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha1-+fY5ENFVNu4rLV3UZlOJcV6sXB4=", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha1-sj5DWK+oogL+ehAK8fX4g/AgB+4=", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/which/-/which-2.0.2.tgz", + "integrity": "sha1-fGqN0KY2oDJ+ELWckobu6T8/UbE=", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wide-align": { + "version": "1.1.3", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha1-rgdOa9wMFKQx6ATmJFScYzsABFc=", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/workerpool": { + "version": "6.1.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/workerpool/-/workerpool-6.1.0.tgz", + "integrity": "sha1-qOA4tMlFaVloUt56jqQiju/es3s=", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha1-Z+FFz/UQpqaYS98RUpEdadLrnkM=", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha1-CCyyyJyf6GWaMRpTvWpNxTAdswQ=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha1-8Rb4Bk/pCz94RKOJl8C3UFEmnx0=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha1-JpxxF9J7Ba0uU2gwqOyJXvnG0BA=", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha1-nibGPTD1NEPpSJSVshBdN7Z6hdk=", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true, + "license": "ISC" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha1-f0k00PfKjFb5UxSTndzS3ZHOHVU=", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha1-HIK/D2tqZur85+8w43b0mhJHf2Y=", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha1-tCiQ8UVmeW+Fro46JSkNIF8VSlQ=", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha1-8TH5ImkRrl2a04xDL+gJNmwjJes=", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha1-CCyyyJyf6GWaMRpTvWpNxTAdswQ=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha1-8Rb4Bk/pCz94RKOJl8C3UFEmnx0=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha1-JpxxF9J7Ba0uU2gwqOyJXvnG0BA=", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha1-nibGPTD1NEPpSJSVshBdN7Z6hdk=", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha1-ApTrPe4FAo0x7hpfosVWpqrxChs=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/Tasks/AzureCLIV3/Tests/package.json b/Tasks/AzureCLIV3/Tests/package.json new file mode 100644 index 000000000000..fb323543a75d --- /dev/null +++ b/Tasks/AzureCLIV3/Tests/package.json @@ -0,0 +1,26 @@ +{ + "name": "azure-cli-tests", + "version": "1.0.0", + "description": "Azure Pipelines Azure CLI V3 Task Tests", + "main": "L0.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/Microsoft/azure-pipelines-tasks.git" + }, + "author": "Microsoft Corporation", + "license": "MIT", + "bugs": { + "url": "https://github.com/Microsoft/azure-pipelines-tasks/issues" + }, + "homepage": "https://github.com/Microsoft/azure-pipelines-tasks#readme", + "devDependencies": { + "@types/mocha": "^5.2.0", + "@types/node": "^16.0.0", + "azure-pipelines-task-lib": "^4.0.0", + "mocha": "^8.0.0", + "nock": "^13.0.0" + } +} diff --git a/Tasks/AzureCLIV3/ThirdPartyNotices.txt b/Tasks/AzureCLIV3/ThirdPartyNotices.txt new file mode 100644 index 000000000000..d7a6c7d37e38 --- /dev/null +++ b/Tasks/AzureCLIV3/ThirdPartyNotices.txt @@ -0,0 +1,503 @@ + +THIRD-PARTY SOFTWARE NOTICES AND INFORMATION +Do Not Translate or Localize + +Microsoft Azure CLI Task incorporates third party material from the projects listed below. The original copyright notice and the license under which Microsoft received such third party material are set forth below. Microsoft reserves all other rights not expressly granted, whether by implication, estoppel or otherwise. + +1. balanced-match (https://github.com/juliangruber/balanced-match) +2. brace-expansion (https://github.com/juliangruber/brace-expansion) +3. concat-map (https://github.com/substack/node-concat-map) +4. Glob (https://github.com/isaacs/node-glob) +5. inflight (https://github.com/npm/inflight) +6. inherits (https://github.com/isaacs/inherits) +7. minimatch (https://github.com/isaacs/minimatch) +8. Mockery (https://github.com/mfncooper/mockery) +9. Node.js (https://nodejs.org/) +10. node-uuid (https://github.com/broofa/node-uuid/) +11. once (https://github.com/isaacs/once) +12. path-is-absolute (https://github.com/sindresorhus/path-is-absolute) +13. Q (https://github.com/kriskowal/q) +14. semver (https://github.com/npm/node-semver/) +15. ShellJS (https://github.com/shelljs/shelljs) +16. vso-node-api (https://github.com/Microsoft/vsts-node-api) +17. azure-pipelines-task-lib (https://github.com/Microsoft/azure-pipelines-task-lib) +18. wrappy (https://github.com/npm/wrappy) + +%% balanced-match NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +(MIT) + +Copyright (c) 2013 Julian Gruber + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +========================================= +END OF balanced-match NOTICES, INFORMATION, AND LICENSE + +%% brace-expansion NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +(MIT) + +Copyright (c) 2013 Julian Gruber + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +========================================= +END OF brace-expansion NOTICES, INFORMATION, AND LICENSE + +%% concat-map NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Copyright (c) James Halliday/substack + +This software is released under the MIT license: + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +========================================= +END OF concat-map NOTICES, INFORMATION, AND LICENSE + +%% Glob NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF Glob NOTICES, INFORMATION, AND LICENSE + +%% inflight NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +The ISC License + +Copyright (c) Isaac Z. Schlueter + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF inflight NOTICES, INFORMATION, AND LICENSE + +%% inherits NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +The ISC License + +Copyright (c) Isaac Z. Schlueter + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF inherits NOTICES, INFORMATION, AND LICENSE + +%% minimatch NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF minimatch NOTICES, INFORMATION, AND LICENSE + +%% Mockery NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Copyrights for code authored by Yahoo! Inc. is licensed under the following +terms: + + MIT License + + Copyright (c) 2011 Yahoo! Inc. All Rights Reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to + deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +========================================= +END OF Mockery NOTICES, INFORMATION, AND LICENSE + +%% Node.js NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Node.js is licensed for use as follows: + +""" +Copyright Node.js contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" + +This license applies to parts of Node.js originating from the +https://github.com/joyent/node repository: + +""" +Copyright Joyent, Inc. and other Node contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" + +The Node.js license applies to all parts of Node.js that are not externally +maintained libraries. +========================================= +END OF Node.js NOTICES, INFORMATION, AND LICENSE + +%% node-uuid NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +The MIT License (MIT) + +Copyright (c) 2010-2012 Robert Kieffer + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the Software), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +========================================= +END OF node-uuid NOTICES, INFORMATION, AND LICENSE + +%% once NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF once NOTICES, INFORMATION, AND LICENSE + +%% path-is-absolute NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +path-is-absolute + +The MIT License (MIT) + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the Software), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +node.js: + +Copyright Joyent, Inc. and other Node contributors. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +Software), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +========================================= +END OF path-is-absolute NOTICES, INFORMATION, AND LICENSE + +%% Q NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Copyright 2009�2014 Kristopher Michael Kowal. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. + +The file q.js is prefaced by the following additional third-party subcomponent information: + +/*! + * + * Copyright 2009-2012 Kris Kowal under the terms of the MIT + * license found at http://github.com/kriskowal/q/raw/master/LICENSE + * + * With parts by Tyler Close + * Copyright 2007-2009 Tyler Close under the terms of the MIT X license found + * at http://www.opensource.org/licenses/mit-license.html + * Forked at ref_send.js version: 2009-05-11 + * + * With parts by Mark Miller + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +========================================= +END OF Q NOTICES, INFORMATION, AND LICENSE + +%% semver NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF semver NOTICES, INFORMATION, AND LICENSE + +%% ShellJS NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Copyright (c) 2012, Artur Adib +All rights reserved. + +You may use this project under the terms of the New BSD license as follows: + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Artur Adib nor the + names of the contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL ARTUR ADIB BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +========================================= +END OF ShellJS NOTICES, INFORMATION, AND LICENSE + +%% vso-node-api NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Copyright (c) Microsoft Corporation + +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +========================================= +END OF vso-node-api NOTICES, INFORMATION, AND LICENSE + +%% VSTS-task-lib NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +The MIT License (MIT) + +Copyright (c) Microsoft Corporation. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +========================================= +END OF VSTS-task-lib NOTICES, INFORMATION, AND LICENSE + +%% wrappy NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF wrappy NOTICES, INFORMATION, AND LICENSE \ No newline at end of file diff --git a/Tasks/AzureCLIV3/azureclitask.ts b/Tasks/AzureCLIV3/azureclitask.ts new file mode 100644 index 000000000000..d948cb8f2b2a --- /dev/null +++ b/Tasks/AzureCLIV3/azureclitask.ts @@ -0,0 +1,479 @@ +import path = require("path"); +import tl = require("azure-pipelines-task-lib/task"); +import fs = require("fs"); +import { IExecSyncResult } from 'azure-pipelines-task-lib/toolrunner'; +import { Utility } from "./src/Utility"; +import { ScriptType, ScriptTypeFactory } from "./src/ScriptType"; +import { getSystemAccessToken } from 'azure-pipelines-tasks-artifacts-common/webapi'; +import { getHandlerFromToken, WebApi } from "azure-devops-node-api"; +import { ITaskApi } from "azure-devops-node-api/TaskApi"; +import { validateAzModuleVersion } from "azure-pipelines-tasks-azure-arm-rest/azCliUtility"; + +const nodeVersion = parseInt(process.version.split('.')[0].replace('v', '')); +if (nodeVersion > 16) { + require("dns").setDefaultResultOrder("ipv4first"); + tl.debug("Set default DNS lookup order to ipv4 first"); +} + +if (nodeVersion > 19) { + require("net").setDefaultAutoSelectFamily(false); + tl.debug("Set default auto select family to false"); +} +const FAIL_ON_STDERR: string = "FAIL_ON_STDERR"; +const AZ_SESSION_REFRESH_INTERVAL_MS: number = 480000; // 8 minutes, 2 minutes before IdToken expiry date + +export class azureclitask { + + public static async runMain(): Promise { + var toolExecutionError = null; + var exitCode: number = 0; + var connectionType: string = ""; + + if(tl.getBoolFeatureFlag('AZP_AZURECLIV2_SETUP_PROXY_ENV')) { + const proxyConfig: tl.ProxyConfiguration | null = tl.getHttpProxyConfiguration(); + if (proxyConfig) { + process.env['HTTP_PROXY'] = proxyConfig.proxyFormattedUrl; + process.env['HTTPS_PROXY'] = proxyConfig.proxyFormattedUrl; + tl.debug(tl.loc('ProxyConfigMessage', proxyConfig.proxyUrl)); + } + } + + try{ + var scriptType: ScriptType = ScriptTypeFactory.getScriptType(); + var tool: any = await scriptType.getTool(); + var cwd: string = tl.getPathInput("cwd", true, false); + if (tl.getInput("scriptLocation", true).toLowerCase() === "scriptPath" && !tl.filePathSupplied("cwd")) { + cwd = path.dirname(tl.getPathInput("scriptPath", true, true)); + } + // determines whether output to stderr will fail a task. + // some tools write progress and other warnings to stderr. scripts can also redirect. + var failOnStdErr: boolean = tl.getBoolInput("failOnStandardError", false); + tl.mkdirP(cwd); + tl.cd(cwd); + + const versionCommand = tl.getPipelineFeature('UseAzVersion') ? "version" : "--version" + const minorVersionTolerance = 5 + const azVersionResult: IExecSyncResult = tl.execSync("az", versionCommand); + Utility.throwIfError(azVersionResult); + this.isSupportCertificateParameter = this.isAzVersionGreaterOrEqual(azVersionResult.stdout, "2.66.0"); + await validateAzModuleVersion("azure-Cli", azVersionResult.stdout, "Azure-Cli", minorVersionTolerance) + + // set az cli config dir + this.setConfigDirectory(); + + connectionType = tl.getInput("connectionType", false) || "azureRM"; + var connectedService: string; + var authorizationScheme: string; + + if (connectionType === "azureRM") { + this.setAzureCloudBasedOnServiceEndpoint(); + connectedService = tl.getInput("connectedServiceNameARM", true); + authorizationScheme = tl.getEndpointAuthorizationScheme(connectedService, true).toLowerCase(); + + await this.loginAzureRM(connectedService); + } else if (connectionType === "azureDevOps") { + connectedService = tl.getInput("azureDevOpsServiceConnection", true); + authorizationScheme = tl.getEndpointAuthorizationScheme(connectedService, true).toLowerCase(); + + await this.setupAzureDevOpsCLI(connectedService); + } else { + throw new Error(`Unsupported connection type: ${connectionType}`); + } + + var keepAzSessionActive: boolean = tl.getBoolInput('keepAzSessionActive', false); + var stopRefreshingSession: () => void = () => {}; + if (keepAzSessionActive) { + // This is a tactical workaround to keep the session active for the duration of the task to avoid AADSTS700024 errors. + // This is a temporary solution until the az cli provides a way to refresh the session. + if (authorizationScheme !== 'workloadidentityfederation') { + const errorMessage = tl.loc('KeepingAzSessionActiveUnsupportedScheme', authorizationScheme); + tl.error(errorMessage); + throw errorMessage; + } + stopRefreshingSession = this.keepRefreshingAzSession(connectedService); + } + + let errLinesCount: number = 0; + let aggregatedErrorLines: string[] = []; + tool.on('errline', (errorLine: string) => { + if (errLinesCount < 10) { + aggregatedErrorLines.push(errorLine); + } + errLinesCount++; + }); + + const addSpnToEnvironment: boolean = tl.getBoolInput('addSpnToEnvironment', false); + if (!!addSpnToEnvironment && authorizationScheme == 'serviceprincipal') { + exitCode = await tool.exec({ + failOnStdErr: false, + ignoreReturnCode: true, + env: { + ...process.env, + ...{ servicePrincipalId: this.servicePrincipalId, servicePrincipalKey: this.servicePrincipalKey, tenantId: this.tenantId } + } + }); + } else if (!!addSpnToEnvironment && authorizationScheme == 'workloadidentityfederation') { + exitCode = await tool.exec({ + failOnStdErr: false, + ignoreReturnCode: true, + env: { + ...process.env, + ...{ servicePrincipalId: this.servicePrincipalId, idToken: this.federatedToken, tenantId: this.tenantId } + } + }); + } else { + exitCode = await tool.exec({ + failOnStdErr: false, + ignoreReturnCode: true + }); + } + + if (failOnStdErr && aggregatedErrorLines.length > 0) { + let error = FAIL_ON_STDERR; + tl.error(aggregatedErrorLines.join("\n"), tl.IssueSource.CustomerScript); + throw error; + } + } + catch (err) { + toolExecutionError = err; + if (err.stderr) { + toolExecutionError = err.stderr; + } + } + finally { + if (keepAzSessionActive) { + stopRefreshingSession(); + } + + if (scriptType) { + await scriptType.cleanUp(); + } + + if (this.cliPasswordPath) { + tl.debug('Removing spn certificate file'); + tl.rmRF(this.cliPasswordPath); + } + + //set the task result to either succeeded or failed based on error was thrown or not + if(toolExecutionError === FAIL_ON_STDERR) { + tl.setResult(tl.TaskResult.Failed, tl.loc("ScriptFailedStdErr")); + } else if (toolExecutionError) { + let message = tl.loc('ScriptFailed', toolExecutionError); + + if (typeof toolExecutionError === 'string') { + const expiredSecretErrorCode = 'AADSTS7000222'; + let serviceEndpointSecretIsExpired = toolExecutionError.indexOf(expiredSecretErrorCode) >= 0; + + if (serviceEndpointSecretIsExpired) { + const organizationURL = tl.getVariable('System.CollectionUri'); + const projectName = tl.getVariable('System.TeamProject'); + const serviceConnectionLink = encodeURI(`${organizationURL}${projectName}/_settings/adminservices?resourceId=${connectedService}`); + + message = tl.loc('ExpiredServicePrincipalMessageWithLink', serviceConnectionLink); + } + } + + // only Aggregation error contains array of errors + if (toolExecutionError.errors) { + // Iterates through array and log errors separately + toolExecutionError.errors.forEach((error) => { + tl.error(error.message, tl.IssueSource.TaskInternal); + }); + + // fail with main message + tl.setResult(tl.TaskResult.Failed, toolExecutionError.message); + } else { + tl.setResult(tl.TaskResult.Failed, message); + } + + tl.setResult(tl.TaskResult.Failed, message); + } else if (exitCode != 0){ + tl.setResult(tl.TaskResult.Failed, tl.loc("ScriptFailedWithExitCode", exitCode)); + } + else { + tl.setResult(tl.TaskResult.Succeeded, tl.loc("ScriptReturnCode", 0)); + } + + //Logout of Azure if logged in + if (this.isLoggedIn) { + this.logoutAzure(); + } + + // Clean up Azure DevOps CLI configuration if it was set + if (connectionType === "azureDevOps") { + tl.execSync("az", `devops configure --defaults project='' organization=`); + } + + if (process.env.AZURESUBSCRIPTION_SERVICE_CONNECTION_ID && process.env.AZURESUBSCRIPTION_SERVICE_CONNECTION_ID !== "") + { + process.env.AZURESUBSCRIPTION_SERVICE_CONNECTION_ID = ''; + process.env.AZURESUBSCRIPTION_CLIENT_ID = ''; + process.env.AZURESUBSCRIPTION_TENANT_ID = ''; + } + } + } + + private static isLoggedIn: boolean = false; + private static cliPasswordPath: string = null; + private static servicePrincipalId: string = null; + private static servicePrincipalKey: string = null; + private static federatedToken: string = null; + private static tenantId: string = null; + private static isSupportCertificateParameter: boolean = false; + + private static isAzVersionGreaterOrEqual(azVersionResultOutput, versionToCompare) { + try { + let versionMatch = []; + if (tl.getPipelineFeature('UseAzVersion')) { + // gets azure-cli version from both az version output which is in JSON format and az --version output text format + versionMatch = azVersionResultOutput.match(/["']?azure-cli["']?\s*[:\s]\s*["']?(\d+\.\d+\.\d+)["']?/); + }else{ + // gets azure-cli version from az --version output text format + versionMatch = azVersionResultOutput.match(/azure-cli\s+(\d+\.\d+\.\d+)/); + } + + if (!versionMatch || versionMatch.length < 2) { + tl.error(`Can't parse az version from: ${azVersionResultOutput}`); + return false; + } + + const currentVersion = versionMatch[1]; + tl.debug(`Current Azure CLI version: ${currentVersion}`); + + // Parse both versions into major, minor, patch components + const [currentMajor, currentMinor, currentPatch] = currentVersion.split('.').map(Number); + const [compareMajor, compareMinor, comparePatch] = versionToCompare.split('.').map(Number); + + // Compare versions + if (currentMajor > compareMajor) return true; + if (currentMajor < compareMajor) return false; + + if (currentMinor > compareMinor) return true; + if (currentMinor < compareMinor) return false; + + return currentPatch >= comparePatch; + } catch (error) { + tl.error(`Error checking Azure CLI version: ${error.message}`); + return false; + } + } + + private static isAzureDevOpsExtensionInstalled(): boolean { + tl.debug("Checking if Azure DevOps extension is installed..."); + try { + const result: IExecSyncResult = tl.execSync("az", "extension show --name azure-devops", { + silent: true + }); + if (result.code === 0) { + tl.debug("Azure DevOps extension is already installed, skipping installation."); + return true; + } + return false; + } catch (error) { + tl.debug(`Azure DevOps extension not found: ${error}`); + return false; + } + } + + private static async loginWithWorkloadIdentityFederation(connectedService: string, visibleAzLogin: boolean): Promise { + var servicePrincipalId: string = tl.getEndpointAuthorizationParameter(connectedService, "serviceprincipalid", false); + var tenantId: string = tl.getEndpointAuthorizationParameter(connectedService, "tenantid", false); + + const federatedToken = await this.getIdToken(connectedService); + tl.setSecret(federatedToken); + let args = `login --service-principal -u "${servicePrincipalId}" --tenant "${tenantId}" --allow-no-subscriptions --federated-token "${federatedToken}"`; + + if (!visibleAzLogin) { + args += ` --output none`; + } + + //login using OpenID Connect federation + Utility.throwIfError(tl.execSync("az", args), tl.loc("LoginFailed")); + + this.servicePrincipalId = servicePrincipalId; + this.federatedToken = federatedToken; + this.tenantId = tenantId; + + process.env.AZURESUBSCRIPTION_SERVICE_CONNECTION_ID = connectedService; + process.env.AZURESUBSCRIPTION_CLIENT_ID = servicePrincipalId; + process.env.AZURESUBSCRIPTION_TENANT_ID = tenantId; + } + + private static async loginAzureRM(connectedService: string):Promise { + var authScheme: string = tl.getEndpointAuthorizationScheme(connectedService, true); + var subscriptionID: string = tl.getEndpointDataParameter(connectedService, "SubscriptionID", true); + var visibleAzLogin: boolean = tl.getBoolInput("visibleAzLogin", true); + + if (authScheme.toLowerCase() == "workloadidentityfederation") { + await this.loginWithWorkloadIdentityFederation(connectedService, visibleAzLogin); + } + else if (authScheme.toLowerCase() == "serviceprincipal") { + let authType: string = tl.getEndpointAuthorizationParameter(connectedService, 'authenticationType', true); + let cliPassword: string = null; + let authParam: string = "--password"; + var servicePrincipalId: string = tl.getEndpointAuthorizationParameter(connectedService, "serviceprincipalid", false); + var tenantId: string = tl.getEndpointAuthorizationParameter(connectedService, "tenantid", false); + + this.servicePrincipalId = servicePrincipalId; + this.tenantId = tenantId; + + if (authType == "spnCertificate") { + tl.debug('certificate based endpoint'); + if(this.isSupportCertificateParameter) { + authParam = "--certificate"; + } + let certificateContent: string = tl.getEndpointAuthorizationParameter(connectedService, "servicePrincipalCertificate", false); + cliPassword = path.join(tl.getVariable('Agent.TempDirectory') || tl.getVariable('system.DefaultWorkingDirectory'), 'spnCert.pem'); + fs.writeFileSync(cliPassword, certificateContent); + this.cliPasswordPath = cliPassword; + } + else { + tl.debug('key based endpoint'); + cliPassword = tl.getEndpointAuthorizationParameter(connectedService, "serviceprincipalkey", false); + this.servicePrincipalKey = cliPassword; + } + + let escapedCliPassword = cliPassword.replace(/"/g, '\\"'); + tl.setSecret(escapedCliPassword.replace(/\\/g, '\"')); + //login using svn + if (visibleAzLogin) { + Utility.throwIfError(tl.execSync("az", `login --service-principal -u "${servicePrincipalId}" ${authParam}="${escapedCliPassword}" --tenant "${tenantId}" --allow-no-subscriptions`), tl.loc("LoginFailed")); + } + else { + Utility.throwIfError(tl.execSync("az", `login --service-principal -u "${servicePrincipalId}" ${authParam}="${escapedCliPassword}" --tenant "${tenantId}" --allow-no-subscriptions --output none`), tl.loc("LoginFailed")); + } + } + else if(authScheme.toLowerCase() == "managedserviceidentity") { + //login using msi + if (visibleAzLogin) { + Utility.throwIfError(tl.execSync("az", "login --identity"), tl.loc("MSILoginFailed")); + } + else { + Utility.throwIfError(tl.execSync("az", "login --identity --output none"), tl.loc("MSILoginFailed")); + } + } + else { + throw tl.loc('AuthSchemeNotSupportedForAzureRM', authScheme); + } + + this.isLoggedIn = true; + if (!!subscriptionID) { + //set the subscription imported to the current subscription + Utility.throwIfError(tl.execSync("az", "account set --subscription \"" + subscriptionID + "\""), tl.loc("ErrorInSettingUpSubscription")); + } + } + + private static async setupAzureDevOpsCLI(connectedService: string): Promise { + try { + var authScheme: string = tl.getEndpointAuthorizationScheme(connectedService, true); + var visibleAzLogin: boolean = tl.getBoolInput("visibleAzLogin", true); + + if (authScheme.toLowerCase() == "workloadidentityfederation") { + // Install Azure DevOps extension if not already installed + const extensionInstalled = this.isAzureDevOpsExtensionInstalled(); + if (!extensionInstalled) { + console.log("Azure DevOps extension not found in working environment. Attempting installation."); + Utility.throwIfError(tl.execSync("az", "extension add -n azure-devops -y"), tl.loc("FailedToInstallAzureDevOpsCLI")); + } else { + console.log("Azure DevOps extension is already installed, skipping installation."); + } + + await this.loginWithWorkloadIdentityFederation(connectedService, visibleAzLogin); + + const organization = tl.getVariable('System.CollectionUri'); + const project = tl.getVariable('System.TeamProject'); + + if (organization) { + Utility.throwIfError(tl.execSync("az", `devops configure --defaults organization="${organization}"`), tl.loc("FailedToSetAzureDevOpsOrganization")); + } + if (project) { + Utility.throwIfError(tl.execSync("az", `devops configure --defaults project="${project}"`), tl.loc("FailedToSetAzureDevOpsProject")); + } + } + else { + throw tl.loc('AuthSchemeNotSupportedForAzureDevOps', authScheme); + } + } catch (error) { + const errorMessage = error?.message || error?.toString() || String(error); + throw new Error(`Failed to setup Azure DevOps CLI: ${errorMessage}`); + } + } + + private static setConfigDirectory(): void { + if (tl.getBoolInput("useGlobalConfig")) { + return; + } + + if (!!tl.getVariable('Agent.TempDirectory')) { + var azCliConfigPath = path.join(tl.getVariable('Agent.TempDirectory'), ".azclitask"); + console.log(tl.loc('SettingAzureConfigDir', azCliConfigPath)); + process.env['AZURE_CONFIG_DIR'] = azCliConfigPath; + } else { + console.warn(tl.loc('GlobalCliConfigAgentVersionWarning')); + } + } + + private static setAzureCloudBasedOnServiceEndpoint(): void { + var connectedService: string = tl.getInput("connectedServiceNameARM", true); + var environment = tl.getEndpointDataParameter(connectedService, 'environment', true); + if (!!environment) { + console.log(tl.loc('SettingAzureCloud', environment)); + Utility.throwIfError(tl.execSync("az", "cloud set -n " + environment)); + } + } + + private static logoutAzure() { + try { + tl.execSync("az", " account clear"); + } + catch (err) { + // task should not fail if logout doesn`t occur + tl.warning(tl.loc("FailedToLogout")); + } + } + + private static async getIdToken(connectedService: string) : Promise { + // since node19 default node's GlobalAgent has timeout 5sec + // keepAlive is set to true to avoid creating default node's GlobalAgent + const webApiOptions = { + keepAlive: true + } + const jobId = tl.getVariable("System.JobId"); + const planId = tl.getVariable("System.PlanId"); + const projectId = tl.getVariable("System.TeamProjectId"); + const hub = tl.getVariable("System.HostType"); + const uri = tl.getVariable("System.CollectionUri"); + const token = getSystemAccessToken(); + + const authHandler = getHandlerFromToken(token); + const connection = new WebApi(uri, authHandler, webApiOptions); + const api: ITaskApi = await connection.getTaskApi(); + const response = await api.createOidcToken({}, projectId, hub, planId, jobId, connectedService); + if (response == null) { + return null; + } + + return response.oidcToken; + } + + private static keepRefreshingAzSession(connectedService: string): () => void { + const intervalId = setInterval(async () => { + try { + tl.debug(tl.loc('RefreshingAzSession')); + await this.loginAzureRM(connectedService); + } catch (error) { + tl.warning(tl.loc('FailedToRefreshAzSession', error)); + } + }, AZ_SESSION_REFRESH_INTERVAL_MS); + + return () => clearInterval(intervalId); + } +} + +tl.setResourcePath(path.join(__dirname, "task.json")); + +if (!Utility.checkIfAzurePythonSdkIsInstalled()) { + tl.setResult(tl.TaskResult.Failed, tl.loc("AzureSDKNotFound")); +} + +azureclitask.runMain(); diff --git a/Tasks/AzureCLIV3/icon.png b/Tasks/AzureCLIV3/icon.png new file mode 100644 index 000000000000..039b77995289 Binary files /dev/null and b/Tasks/AzureCLIV3/icon.png differ diff --git a/Tasks/AzureCLIV3/icon.svg b/Tasks/AzureCLIV3/icon.svg new file mode 100644 index 000000000000..3debdb04649f --- /dev/null +++ b/Tasks/AzureCLIV3/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/Tasks/AzureCLIV3/make.json b/Tasks/AzureCLIV3/make.json new file mode 100644 index 000000000000..c1b41d2d04a9 --- /dev/null +++ b/Tasks/AzureCLIV3/make.json @@ -0,0 +1,11 @@ +{ + "rm": [ + { + "items": [ + "node_modules/azure-pipelines-tasks-azure-arm-rest/node_modules/agent-base", + "node_modules/azure-pipelines-tasks-azure-arm-rest/node_modules/azure-pipelines-task-lib" + ], + "options": "-Rf" + } + ] +} \ No newline at end of file diff --git a/Tasks/AzureCLIV3/package-lock.json b/Tasks/AzureCLIV3/package-lock.json new file mode 100644 index 000000000000..3f06b8a60be6 --- /dev/null +++ b/Tasks/AzureCLIV3/package-lock.json @@ -0,0 +1,1105 @@ +{ + "name": "vsts-tasks-azurecli", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vsts-tasks-azurecli", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@types/mocha": "^5.2.7", + "@types/node": "^20.3.1", + "@types/q": "1.0.7", + "azure-devops-node-api": "14.0.2", + "azure-pipelines-task-lib": "^4.17.2", + "azure-pipelines-tasks-artifacts-common": "^2.245.1", + "azure-pipelines-tasks-azure-arm-rest": "3.259.1" + }, + "devDependencies": { + "typescript": "5.1.6" + } + }, + "node_modules/@azure/msal-common": { + "version": "13.3.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/@azure/msal-common/-/msal-common-13.3.1.tgz", + "integrity": "sha1-ASRlv5QNEjddxHOHt1TM+da5IYA=", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@types/fs-extra": { + "version": "8.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/@types/fs-extra/-/fs-extra-8.0.0.tgz", + "integrity": "sha512-bCtL5v9zdbQW86yexOlXWTEGvLNqWxMFyi7gQA7Gcthbezr2cPSOb8SkESVKA937QD5cIwOFLDFt0MQoXOEr9Q==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/jsonwebtoken": { + "version": "8.5.9", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/@types/jsonwebtoken/-/jsonwebtoken-8.5.9.tgz", + "integrity": "sha1-LAZOywsxKNg30nZKoLEXsP9uRYY=", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mocha": { + "version": "5.2.7", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==" + }, + "node_modules/@types/node": { + "version": "20.8.9", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/@types/node/-/node-20.8.9.tgz", + "integrity": "sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/q": { + "version": "1.0.7", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/@types/q/-/q-1.0.7.tgz", + "integrity": "sha512-0WS7XU7sXzQ7J1nbnMKKYdjrrFoO3YtZYgUzeV8JFXffPnHfvSJQleR70I8BOAsOm14i4dyaAZ3YzqIl1YhkXQ==" + }, + "node_modules/adm-zip": { + "version": "0.5.12", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/adm-zip/-/adm-zip-0.5.12.tgz", + "integrity": "sha512-6TVU49mK6KZb4qG6xWaaM4C7sA/sgUMLy/JYMOzkcp3BvVLpW0fXDFQiIzAuxFCt/2+xD7fNIiPFAoLZPhVNLQ==", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/async-mutex": { + "version": "0.4.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/async-mutex/-/async-mutex-0.4.1.tgz", + "integrity": "sha1-vM9VuW8rr435DteYy1VEofbuTCw=", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/azure-devops-node-api": { + "version": "14.0.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/azure-devops-node-api/-/azure-devops-node-api-14.0.2.tgz", + "integrity": "sha512-TwjAEnWnOSZ2oypkDyqppgvJw43qArEfPiJtEWLL3NBgdvAuOuB0xgFz/Eiz4H6Dk0Yv52wCodZxtZvAMhJXwQ==", + "dependencies": { + "tunnel": "0.0.6", + "typed-rest-client": "^2.0.1" + }, + "engines": { + "node": ">= 16.0.0" + } + }, + "node_modules/azure-pipelines-task-lib": { + "version": "4.17.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/azure-pipelines-task-lib/-/azure-pipelines-task-lib-4.17.2.tgz", + "integrity": "sha512-kKG1I2cpHM0kqn/YlnZiA2J59/x4OraEZZ1/Cp6A7XOu0e+E1PfrfldVVOU/tdeW/xOFoexqA4EEV27LfH0YqQ==", + "license": "MIT", + "dependencies": { + "adm-zip": "^0.5.10", + "minimatch": "3.0.5", + "nodejs-file-downloader": "^4.11.1", + "q": "^1.5.1", + "semver": "^5.7.2", + "shelljs": "^0.8.5", + "uuid": "^3.0.1" + } + }, + "node_modules/azure-pipelines-tasks-artifacts-common": { + "version": "2.245.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/azure-pipelines-tasks-artifacts-common/-/azure-pipelines-tasks-artifacts-common-2.245.1.tgz", + "integrity": "sha512-rzshWpWS8AX4YSuRSLr+HOj96CyYJY28u0jWr4oPS5xLeckLFhrqOzqHRiAnhZ5TjoCdGrkUFkS2cHtmS+fDIA==", + "license": "MIT", + "dependencies": { + "@types/fs-extra": "8.0.0", + "@types/mocha": "^5.2.6", + "@types/node": "^16.11.39", + "azure-devops-node-api": "^14.0.2", + "azure-pipelines-task-lib": "^4.13.0", + "fs-extra": "8.1.0", + "node-fetch": "^2.7.0", + "semver": "^6.3.1" + } + }, + "node_modules/azure-pipelines-tasks-artifacts-common/node_modules/@types/node": { + "version": "16.18.59", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/@types/node/-/node-16.18.59.tgz", + "integrity": "sha512-PJ1w2cNeKUEdey4LiPra0ZuxZFOGvetswE8qHRriV/sUkL5Al4tTmPV9D2+Y/TPIxTHHgxTfRjZVKWhPw/ORhQ==" + }, + "node_modules/azure-pipelines-tasks-artifacts-common/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/azure-pipelines-tasks-azure-arm-rest": { + "version": "3.259.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/azure-pipelines-tasks-azure-arm-rest/-/azure-pipelines-tasks-azure-arm-rest-3.259.1.tgz", + "integrity": "sha1-mMsaDc1lwDZwbVcqOoJbL2KVOzE=", + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "^8.5.8", + "@types/mocha": "^5.2.7", + "@types/node": "^10.17.0", + "@types/q": "1.5.4", + "async-mutex": "^0.4.0", + "azure-devops-node-api": "^14.0.1", + "azure-pipelines-task-lib": "^4.11.0", + "https-proxy-agent": "^4.0.0", + "jsonwebtoken": "^9.0.0", + "msalv1": "npm:@azure/msal-node@^1.18.4", + "msalv2": "npm:@azure/msal-node@^2.7.0", + "msalv3": "npm:@azure/msal-node@^3.5.3", + "node-fetch": "^2.6.7", + "q": "1.5.1", + "typed-rest-client": "^2.0.1", + "xml2js": "0.6.2" + } + }, + "node_modules/azure-pipelines-tasks-azure-arm-rest/node_modules/@types/node": { + "version": "10.17.60", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/@types/node/-/node-10.17.60.tgz", + "integrity": "sha1-NfPWIT2u2V2n8Pc+dbzGmA6QWXs=", + "license": "MIT" + }, + "node_modules/azure-pipelines-tasks-azure-arm-rest/node_modules/@types/q": { + "version": "1.5.4", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/@types/q/-/q-1.5.4.tgz", + "integrity": "sha1-FZJUFOCtLNdlv+9YhC9+JqesyyQ=", + "license": "MIT" + }, + "node_modules/azure-pipelines-tasks-azure-arm-rest/node_modules/agent-base": { + "version": "5.1.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha1-6Ps/JClZ20TWO+Zl23qOc5U3oyw=", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/azure-pipelines-tasks-azure-arm-rest/node_modules/https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha1-cCtx+1UgoTKmbeH2dUHZ5iFU2Cs=", + "license": "MIT", + "dependencies": { + "agent-base": "5", + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=", + "license": "BSD-3-Clause" + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha1-rg8PothQRe8UqBfao86azQSJ5b8=", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/js-md4": { + "version": "0.3.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/js-md4/-/js-md4-0.3.2.tgz", + "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==" + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha1-Zf+R9KvvF4RpfUCVK7GZjFBMqvM=", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/semver/-/semver-7.7.1.tgz", + "integrity": "sha1-q9UJjYKxjGyB9gdP8mR/0+ciDJ8=", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha1-dDwymFy56YZVUw1TZBtmyGRbA5o=", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/jws/-/jws-3.2.2.tgz", + "integrity": "sha1-ABCZ82OUaMlBQADpmZX6UvtHgwQ=", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimatch": { + "version": "3.0.5", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/msalv1": { + "name": "@azure/msal-node", + "version": "1.18.4", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/@azure/msal-node/-/msal-node-1.18.4.tgz", + "integrity": "sha1-ySGwRHyS+zsMsev1qadvytLsfCE=", + "deprecated": "A newer major version of this library is available. Please upgrade to the latest available version.", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "13.3.1", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": "10 || 12 || 14 || 16 || 18" + } + }, + "node_modules/msalv1/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha1-gNW1ztJxu5r2xEXyGhoExgbO++I=", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/msalv2": { + "name": "@azure/msal-node", + "version": "2.16.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/@azure/msal-node/-/msal-node-2.16.2.tgz", + "integrity": "sha1-Prdo02iD6m+ak5wLW0Z7UY54//w=", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "14.16.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/msalv2/node_modules/@azure/msal-common": { + "version": "14.16.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/@azure/msal-common/-/msal-common-14.16.0.tgz", + "integrity": "sha1-80cPyux4jb5QhZlSzUmTQL2iPXo=", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/msalv2/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha1-gNW1ztJxu5r2xEXyGhoExgbO++I=", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/msalv3": { + "name": "@azure/msal-node", + "version": "3.5.3", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/@azure/msal-node/-/msal-node-3.5.3.tgz", + "integrity": "sha1-AveiNEosKZQ1SgzsElue+ajnEJs=", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "15.6.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/msalv3/node_modules/@azure/msal-common": { + "version": "15.6.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/@azure/msal-common/-/msal-common-15.6.0.tgz", + "integrity": "sha1-B2TWRG7v85cCIZleJfJl/bIY2mY=", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/msalv3/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha1-gNW1ztJxu5r2xEXyGhoExgbO++I=", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/nodejs-file-downloader": { + "version": "4.12.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/nodejs-file-downloader/-/nodejs-file-downloader-4.12.1.tgz", + "integrity": "sha512-LpfCTNhh805AlLnJnzt1PuEj+RmbrccbAQZ6hBRw2e6QPVR0Qntuo6qqyvPHG5s77/0w0IEKgRAD4nbSnr/X4w==", + "dependencies": { + "follow-redirects": "^1.15.1", + "https-proxy-agent": "^5.0.0", + "mime-types": "^2.1.27", + "sanitize-filename": "^1.6.3" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha1-Hq+fqb2x/dTsdfWPnNtOa3gn7sY=", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.3", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/sax/-/sax-1.4.1.tgz", + "integrity": "sha1-RMyJiDd/EmME07P8EBDHM7kp7w8=", + "license": "ISC" + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha1-YS7+TtI11Wfoq6Xypfq3AoCt6D8=", + "license": "0BSD" + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/typed-rest-client": { + "version": "2.0.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/typed-rest-client/-/typed-rest-client-2.0.2.tgz", + "integrity": "sha512-rmAQM2gZw/PQpK5+5aSs+I6ZBv4PFC2BT1o+0ADS1SgSejA+14EmbI2Lt8uXwkX7oeOMkwFmg0pHKwe8D9IT5A==", + "dependencies": { + "des.js": "^1.1.0", + "js-md4": "^0.3.2", + "qs": "^6.10.3", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + }, + "engines": { + "node": ">= 16.0.0" + } + }, + "node_modules/typescript": { + "version": "5.1.6", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.4", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", + "integrity": "sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==" + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha1-3QtjAIOqCcFh4lpNCQHisqkptJk=", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/PipelineTools_PublicPackages/npm/registry/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha1-vpuuHIoEbnazESdyY0fQrXACvrM=", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + } + } +} diff --git a/Tasks/AzureCLIV3/package.json b/Tasks/AzureCLIV3/package.json new file mode 100644 index 000000000000..f79f0168f108 --- /dev/null +++ b/Tasks/AzureCLIV3/package.json @@ -0,0 +1,31 @@ +{ + "name": "vsts-tasks-azurecli", + "version": "1.0.0", + "description": "Azure Pipelines Azure CLI Task", + "main": "azureclitask.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/Microsoft/azure-pipelines-tasks.git" + }, + "author": "Microsoft Corporation", + "license": "MIT", + "bugs": { + "url": "https://github.com/Microsoft/azure-pipelines-tasks/issues" + }, + "homepage": "https://github.com/Microsoft/azure-pipelines-tasks#readme", + "dependencies": { + "@types/mocha": "^5.2.7", + "@types/node": "^20.3.1", + "@types/q": "1.0.7", + "azure-devops-node-api": "14.0.2", + "azure-pipelines-task-lib": "^4.17.2", + "azure-pipelines-tasks-artifacts-common": "^2.245.1", + "azure-pipelines-tasks-azure-arm-rest": "3.259.1" + }, + "devDependencies": { + "typescript": "5.1.6" + } +} diff --git a/Tasks/AzureCLIV3/src/ScriptType.ts b/Tasks/AzureCLIV3/src/ScriptType.ts new file mode 100644 index 000000000000..bc04f10c4f9e --- /dev/null +++ b/Tasks/AzureCLIV3/src/ScriptType.ts @@ -0,0 +1,106 @@ +import { Utility } from './Utility'; +import tl = require("azure-pipelines-task-lib/task"); + +export class ScriptTypeFactory { + public static getScriptType(): ScriptType { + let scriptType: string = tl.getInput("scriptType", true).toLowerCase(); + let scriptLocation: string = tl.getInput("scriptLocation", true); + if (!(['inlinescript', 'scriptpath'].find((acceptedValue) => { return scriptLocation.toLowerCase() === acceptedValue; }))) { + throw new Error(tl.loc('UnacceptedScriptLocationValue', scriptLocation)); + } + + let scriptArguments: string = tl.getInput("scriptArguments", false); + switch(scriptType){ + case 'ps': + return new WindowsPowerShell(scriptLocation, scriptArguments); + case 'pscore': + return new PowerShellCore(scriptLocation, scriptArguments); + case 'bash': + return new Bash(scriptLocation, scriptArguments); + case 'batch': + default: + return new Batch(scriptLocation, scriptArguments); + } + } +} + +export abstract class ScriptType { + + protected _scriptLocation: string; + protected _scriptArguments: string; + protected _scriptPath: string; + + constructor(scriptLocation: string, scriptArguments: string) { + this._scriptLocation = scriptLocation; + this._scriptArguments = scriptArguments; + } + + public abstract getTool(): Promise; + + public async cleanUp(): Promise { + if(this._scriptLocation.toLowerCase() === 'inlinescript') { + await Utility.deleteFile(this._scriptPath); + } + } +} + +export class WindowsPowerShell extends ScriptType { + + public async getTool(): Promise { + this._scriptPath = await Utility.getPowerShellScriptPath(this._scriptLocation, ['ps1'], this._scriptArguments); + let tool: any = tl.tool(tl.which('powershell', true)) + .arg('-NoLogo') + .arg('-NoProfile') + .arg('-NonInteractive') + .arg('-ExecutionPolicy') + .arg('Unrestricted') + .arg('-Command') + .arg(`. '${this._scriptPath.replace(/'/g, "''")}'`); + return tool; + } + + public async cleanUp(): Promise { + await Utility.deleteFile(this._scriptPath); + } +} + +export class PowerShellCore extends ScriptType { + + public async getTool(): Promise { + this._scriptPath = await Utility.getPowerShellScriptPath(this._scriptLocation, ['ps1'], this._scriptArguments); + let tool: any = tl.tool(tl.which('pwsh', true)) + .arg('-NoLogo') + .arg('-NoProfile') + .arg('-NonInteractive') + .arg('-ExecutionPolicy') + .arg('Unrestricted') + .arg('-Command') + .arg(`. '${this._scriptPath.replace(/'/g, "''")}'`); + return tool; + } + + public async cleanUp(): Promise { + await Utility.deleteFile(this._scriptPath); + } +} + +export class Bash extends ScriptType { + + public async getTool(): Promise { + this._scriptPath = await Utility.getScriptPath(this._scriptLocation, ['sh']); + let tool: any = tl.tool(tl.which("bash", true)); + tool.arg(this._scriptPath); + tool.line(this._scriptArguments); // additional scriptArguments should always call line. line() parses quoted arg strings + return tool; + } +} + +export class Batch extends ScriptType { + + public async getTool(): Promise { + this._scriptPath = await Utility.getScriptPath(this._scriptLocation, ['bat', 'cmd']); + let tool: any = tl.tool(tl.which(this._scriptPath, true)); + tool.line(this._scriptArguments); // additional scriptArguments should always call line. line() parses quoted arg strings + return tool; + } +} \ No newline at end of file diff --git a/Tasks/AzureCLIV3/src/Utility.ts b/Tasks/AzureCLIV3/src/Utility.ts new file mode 100644 index 000000000000..b7e3a3976afe --- /dev/null +++ b/Tasks/AzureCLIV3/src/Utility.ts @@ -0,0 +1,123 @@ +import tl = require("azure-pipelines-task-lib/task"); +import os = require("os"); +import path = require("path"); +import { IExecSyncResult } from 'azure-pipelines-task-lib/toolrunner'; +import fs = require("fs"); + +export class Utility { + + public static async getScriptPath(scriptLocation: string, fileExtensions: string[]): Promise { + if (scriptLocation.toLowerCase() === "scriptpath") { + let filePath: string = tl.getPathInput("scriptPath", true, false); + if (Utility.checkIfFileExists(filePath, fileExtensions)) { + return filePath; + } + throw new Error(tl.loc('JS_InvalidFilePath', filePath)); + } + let tempDirectory = tl.getVariable('Agent.TempDirectory') || os.tmpdir(); + let inlineScript: string = tl.getInput("inlineScript", true); + let scriptPath: string = path.join(tempDirectory, `azureclitaskscript${new Date().getTime()}.${fileExtensions[0]}`); + await Utility.createFile(scriptPath, inlineScript); + return scriptPath; + } + + public static async getPowerShellScriptPath(scriptLocation: string, fileExtensions: string[], scriptArguments: string): Promise { + let powerShellErrorActionPreference: string = tl.getInput('powerShellErrorActionPreference', false) || 'Stop'; + switch (powerShellErrorActionPreference.toUpperCase()) { + case 'STOP': + case 'CONTINUE': + case 'SILENTLYCONTINUE': + break; + default: + throw new Error(tl.loc('JS_InvalidErrorActionPreference', powerShellErrorActionPreference)); + } + + // Write the script to disk. + tl.assertAgent('2.115.0'); + let tempDirectory = tl.getVariable('Agent.TempDirectory') || os.tmpdir(); + + let contents: string[] = []; + contents.push(`$ErrorActionPreference = '${powerShellErrorActionPreference}'`); + contents.push(`$ErrorView = 'NormalView'`); + let filePath: string = tl.getPathInput("scriptPath", false, false); + if (scriptLocation.toLowerCase() === 'inlinescript') { + let inlineScript: string = tl.getInput("inlineScript", true); + filePath = path.join(tempDirectory, `azureclitaskscript${new Date().getTime()}_inlinescript.${fileExtensions[0]}`); + await Utility.createFile(filePath, inlineScript); + } + else{ + if (!Utility.checkIfFileExists(filePath, fileExtensions)) { + throw new Error(tl.loc('JS_InvalidFilePath', filePath)); + } + } + + let content: string = `. '${filePath.replace(/'/g, "''")}' `; + if (scriptArguments) { + content += scriptArguments; + } + contents.push(content.trim()); + + let powerShellIgnoreLASTEXITCODE: boolean = tl.getBoolInput('powerShellIgnoreLASTEXITCODE', false); + if (!powerShellIgnoreLASTEXITCODE) { + contents.push(`if (!(Test-Path -LiteralPath variable:\LASTEXITCODE)) {`); + contents.push(` Write-Host '##vso[task.debug]$LASTEXITCODE is not set.'`); + contents.push(`} else {`); + contents.push(` Write-Host ('##vso[task.debug]$LASTEXITCODE: {0}' -f $LASTEXITCODE)`); + contents.push(` exit $LASTEXITCODE`); + contents.push(`}`); + } + + let scriptPath: string = path.join(tempDirectory, `azureclitaskscript${new Date().getTime()}.${fileExtensions[0]}`); + await Utility.createFile(scriptPath, '\ufeff' + contents.join(os.EOL), { encoding: 'utf8' }); + return scriptPath; + } + + public static checkIfAzurePythonSdkIsInstalled() { + return !!tl.which("az", false); + } + + public static throwIfError(resultOfToolExecution: IExecSyncResult, errormsg?: string): void { + if (resultOfToolExecution.code != 0) { + tl.error("Error Code: [" + resultOfToolExecution.code + "]"); + if (errormsg) { + tl.error("Error: " + errormsg); + } + throw resultOfToolExecution; + } + } + + public static async createFile(filePath: string, data: string, options?: any): Promise { + try { + fs.writeFileSync(filePath, data, options); + } + catch (err) { + Utility.deleteFile(filePath); + throw err; + } + } + + public static checkIfFileExists(filePath: string, fileExtensions: string[]): boolean { + let matchingFiles: string[] = fileExtensions.filter((fileExtension: string) => { + if (tl.stats(filePath).isFile() && filePath.toUpperCase().match(new RegExp(`\.${fileExtension.toUpperCase()}$`))) { + return true; + } + }); + if (matchingFiles.length > 0) { + return true; + } + return false; + } + + public static async deleteFile(filePath: string): Promise { + if (fs.existsSync(filePath)) { + try { + //delete the publishsetting file created earlier + fs.unlinkSync(filePath); + } + catch (err) { + //error while deleting should not result in task failure + console.error(err.toString()); + } + } + } +} diff --git a/Tasks/AzureCLIV3/task.json b/Tasks/AzureCLIV3/task.json new file mode 100644 index 000000000000..044cca22607f --- /dev/null +++ b/Tasks/AzureCLIV3/task.json @@ -0,0 +1,261 @@ +{ + "id": "46E4BE58-730B-4389-8A2F-EA10B3E5E815", + "name": "AzureCLI", + "friendlyName": "Azure CLI", + "description": "Run Azure CLI commands against an Azure subscription in a PowerShell Core/Shell script when running on Linux agent or PowerShell/PowerShell Core/Batch script when running on Windows agent.", + "author": "Microsoft Corporation", + "helpUrl": "https://docs.microsoft.com/azure/devops/pipelines/tasks/deploy/azure-cli", + "helpMarkDown": "[Learn more about this task](http://github.com/microsoft/azure-pipelines-tasks/blob/master/Tasks/AzureCLIV3/Readme.md) or [see the Azure CLI documentation](https://docs.microsoft.com/cli/azure/)", + "releaseNotes": "What's new in Version 3.0:\n- Support for dual connection types: Azure Resource Manager and Azure DevOps service connections. \n- Azure DevOps CLI integration with automatic extension installation and configuration. \n- Workload Identity Federation support for Azure DevOps connections.", + "category": "Deploy", + "visibility": [ + "Build", + "Release" + ], + "runsOn": [ + "Agent", + "DeploymentGroup" + ], + "demands": [], + "version": { + "Major": 3, + "Minor": 267, + "Patch": 0 + }, + "minimumAgentVersion": "2.0.0", + "instanceNameFormat": "Azure CLI $(scriptPath)", + "showEnvironmentVariables": true, + "groups": [ + { + "name": "advanced", + "displayName": "Advanced", + "isExpanded": true + } + ], + "inputs": [ + { + "name": "connectionType", + "type": "pickList", + "label": "Connection Type", + "defaultValue": "azureRM", + "required": true, + "helpMarkDown": "Type of service connection to use", + "options": { + "azureRM": "Azure Resource Manager", + "azureDevOps": "Azure DevOps" + } + }, + { + "name": "connectedServiceNameARM", + "aliases": [ + "azureSubscription" + ], + "type": "connectedService:AzureRM", + "label": "Azure Resource Manager connection", + "required": true, + "helpMarkDown": "Select an Azure Resource Manager service connection for the deployment", + "visibleRule": "connectionType = azureRM", + "properties": { + "EndpointFilterRule": "" + } + }, + { + "name": "azureDevOpsServiceConnection", + "type": "connectedService:WorkloadIdentityUser", + "label": "Azure DevOps Service Connection", + "required": true, + "helpMarkDown": "Select an Azure DevOps service connection", + "visibleRule": "connectionType = azureDevOps", + "properties": { + "EndpointFilterRule": "" + } + }, + { + "name": "scriptType", + "type": "pickList", + "label": "Script Type", + "defaultValue": "", + "required": true, + "helpMarkDown": "Type of script: PowerShell/PowerShell Core/Bat/Shell script. Select Shell/PowerShell Core script when running on Linux agent or Batch/PowerShell/PowerShell Core script when running on Windows agent. PowerShell Core script can run on cross-platform agents (Linux, macOS, or Windows).", + "options": { + "ps": "PowerShell", + "pscore": "PowerShell Core", + "batch": "Batch", + "bash": "Shell" + } + }, + { + "name": "scriptLocation", + "type": "pickList", + "label": "Script Location", + "defaultValue": "scriptPath", + "required": true, + "helpMarkDown": "Path to script: File path or Inline script", + "options": { + "inlineScript": "Inline script", + "scriptPath": "Script path" + } + }, + { + "name": "scriptPath", + "type": "filePath", + "label": "Script Path", + "defaultValue": "", + "required": true, + "visibleRule": "scriptLocation = scriptPath", + "helpMarkDown": "Fully qualified path of the script(.ps1 or .bat or .cmd when using Windows based agent else .ps1 or .sh when using linux based agent) or a path relative to the the default working directory" + }, + { + "name": "inlineScript", + "type": "multiLine", + "label": "Inline Script", + "defaultValue": "", + "required": true, + "visibleRule": "scriptLocation = inlineScript", + "helpMarkDown": "You can write your scripts inline here. When using Windows agent, use PowerShell or PowerShell Core or batch scripting whereas use PowerShell Core or shell scripting when using Linux based agents. For batch files use the prefix \"call\" before every azure command. You can also pass predefined and custom variables to this script using arguments \n\n example for PowerShell/PowerShellCore/shell: \naz --version \naz account show \n\n example for batch:\ncall az --version \ncall az account show", + "properties": { + "resizable": "true", + "rows": "10", + "maxLength": "5000" + } + }, + { + "name": "scriptArguments", + "aliases": [ + "arguments" + ], + "type": "string", + "label": "Script Arguments", + "defaultValue": "", + "required": false, + "helpMarkDown": "Arguments passed to the script", + "properties": { + "editorExtension": "ms.vss-services-azure.parameters-grid" + } + }, + { + "name": "powerShellErrorActionPreference", + "type": "pickList", + "label": "ErrorActionPreference", + "required": false, + "defaultValue": "stop", + "options": { + "stop": "Stop", + "continue": "Continue", + "silentlyContinue": "SilentlyContinue" + }, + "visibleRule": "scriptType = ps || scriptType = pscore", + "helpMarkDown": "Prepends the line `$ErrorActionPreference = 'VALUE'` at the top of your powershell/powershell core script." + }, + { + "name": "addSpnToEnvironment", + "type": "boolean", + "label": "Access service principal details in script", + "defaultValue": "false", + "required": false, + "helpMarkDown": "Adds service principal id, service principal key and tenant id of the Azure endpoint you chose to the script's execution environment. You can use variables: `servicePrincipalId`, `servicePrincipalKey` and `tenantId` in your script.\n\nThis is honored only when the Azure endpoint has Service Principal authentication scheme.\n\nSyntax to access environment variables based on script type.\n\nPowershell script: $env:servicePrincipalId\n\nBatch script: %servicePrincipalId% \n\nShell script: $servicePrincipalId", + "groupName": "advanced" + }, + { + "name": "useGlobalConfig", + "type": "boolean", + "label": "Use global Azure CLI configuration", + "defaultValue": "false", + "required": false, + "helpMarkDown": "If this is false, this task will use its own separate [Azure CLI configuration directory](https://docs.microsoft.com/en-us/cli/azure/azure-cli-configuration?view=azure-cli-latest#cli-configuration-file). This can be used to run Azure CLI tasks in *parallel* releases", + "groupName": "advanced" + }, + { + "name": "cwd", + "aliases": [ + "workingDirectory" + ], + "type": "filePath", + "label": "Working Directory", + "defaultValue": "", + "required": false, + "helpMarkDown": "Current working directory where the script is run. Empty is the root of the repo (build) or artifacts (release), which is $(System.DefaultWorkingDirectory)", + "groupName": "advanced" + }, + { + "name": "failOnStandardError", + "type": "boolean", + "label": "Fail on Standard Error", + "defaultValue": "false", + "required": false, + "helpMarkDown": "If this is true, this task will fail when any errors are written to the StandardError stream. Unselect the checkbox to ignore standard errors and rely on exit codes to determine the status", + "groupName": "advanced" + }, + { + "name": "powerShellIgnoreLASTEXITCODE", + "type": "boolean", + "label": "Ignore $LASTEXITCODE", + "required": false, + "defaultValue": "false", + "visibleRule": "scriptType = ps || scriptType = pscore", + "helpMarkDown": "If this is false, the line `if ((Test-Path -LiteralPath variable:\\LASTEXITCODE)) { exit $LASTEXITCODE }` is appended to the end of your script. This will cause the last exit code from an external command to be propagated as the exit code of powershell. Otherwise the line is not appended to the end of your script.", + "groupName": "advanced" + }, + { + "name": "visibleAzLogin", + "type": "boolean", + "label": "az login output visibility", + "defaultValue": "true", + "required": false, + "helpMarkDown": "If this is set to true, az login command will output to the task. Setting it to false will suppress the az login output", + "groupName": "advanced" + }, + { + "name": "keepAzSessionActive", + "type": "boolean", + "label": "[Experimental] Keep Azure CLI session active", + "defaultValue": "false", + "required": false, + "helpMarkDown": "When enabled, this task will continuously sign into Azure to avoid AADSTS700024 errors when requesting access tokens beyond the IdToken expiry date. Note that this feature is EXPERIMENTAL, may not work in all scenarios and you are using it without any guarantees. Valid only for service connections using the Workload Identity Federation authentication scheme.", + "groupName": "advanced" + } + ], + "execution": { + "Node10": { + "target": "azureclitask.js", + "argumentFormat": "" + }, + "Node16": { + "target": "azureclitask.js", + "argumentFormat": "" + }, + "Node20_1": { + "target": "azureclitask.js", + "argumentFormat": "" + } + }, + "messages": { + "ScriptReturnCode": "Script exited with return code: %d", + "ScriptFailed": "Script failed with error: %s", + "ScriptFailedStdErr": "Script has output to stderr. Failing as failOnStdErr is set to true.", + "ScriptFailedWithExitCode": "Script failed with exit code: %d", + "UnsupportedEndpointScheme": "Unsupported service connection authorization scheme: Service Principal for AzureRM", + "AzureSDKNotFound": "Azure CLI 2.x is not installed on this machine.", + "FailedToLogout": "The following error occurred while logging out: %s", + "LoginFailed": "Azure login failed", + "MSILoginFailed": "Azure login failed using Managed Service Identity", + "AuthSchemeNotSupportedForAzureRM": "The authentication scheme '%s' is not supported for Azure Resource Manager connections. Please update your Azure Resource Manager service connection to use one of the supported schemes: Service Principal, Workload Identity Federation (WIF), or Managed Identity.", + "AuthSchemeNotSupportedForAzureDevOps": "The authentication scheme '%s' is not supported for Azure DevOps service connections. Please update your Azure DevOps service connection to use one of the supported schemes: Workload Identity Federation. ", + "ErrorInSettingUpSubscription": "Error in setting up subscription", + "SettingAzureConfigDir": "Setting AZURE_CONFIG_DIR env variable to: %s", + "SettingAzureCloud": "Setting active cloud to: %s", + "JS_InvalidFilePath": "Script file could not be found at specified script location: '%s'. Please verify the script exists at the specified path. If you want to use inline script, specify input `Script Location` as `inlineScript`.", + "JS_InvalidErrorActionPreference": "Invalid ErrorActionPreference '%s'. The value must be one of: 'Stop', 'Continue', or 'SilentlyContinue'", + "GlobalCliConfigAgentVersionWarning": "For agent version < 2.115.0, only global Azure CLI configuration can be used", + "UnacceptedScriptLocationValue": "%s is not a valid value for task input 'Script Location' (scriptLocation in YAML). Value can either be'inlineScript' or 'scriptPath'", + "ExpiredServicePrincipalMessageWithLink": "Secret expired, update service connection at %s See https://aka.ms/azdo-rm-workload-identity-conversion to learn more about conversion to secret-less service connections.", + "ProxyConfig": "az tool is configured to use %s as proxy server", + "FailedToRefreshAzSession": "The following error occurred while trying to refresh az-cli session: %s", + "RefreshingAzSession": "Attempting to refresh az-cli session...", + "KeepingAzSessionActiveUnsupportedScheme": "The 'keepAzSessionActive' input might be used only for workload identity federation ARM service connection. The referenced service endpoint auth scheme was unexpected: %s. Change the scheme or remove 'keepAzSessionActive' input.", + "FailedToInstallAzureDevOpsCLI": "Failed to install Azure DevOps CLI extension", + "FailedToLoginAzureDevOpsCLI": "Failed to login to Azure DevOps CLI", + "FailedToSetAzureDevOpsOrganization": "Failed to set Azure DevOps organization", + "FailedToSetAzureDevOpsProject": "Failed to set Azure DevOps project" + } +} \ No newline at end of file diff --git a/Tasks/AzureCLIV3/task.loc.json b/Tasks/AzureCLIV3/task.loc.json new file mode 100644 index 000000000000..df4c9c32fd3d --- /dev/null +++ b/Tasks/AzureCLIV3/task.loc.json @@ -0,0 +1,261 @@ +{ + "id": "46E4BE58-730B-4389-8A2F-EA10B3E5E815", + "name": "AzureCLI", + "friendlyName": "ms-resource:loc.friendlyName", + "description": "ms-resource:loc.description", + "author": "Microsoft Corporation", + "helpUrl": "https://docs.microsoft.com/azure/devops/pipelines/tasks/deploy/azure-cli", + "helpMarkDown": "ms-resource:loc.helpMarkDown", + "releaseNotes": "ms-resource:loc.releaseNotes", + "category": "Deploy", + "visibility": [ + "Build", + "Release" + ], + "runsOn": [ + "Agent", + "DeploymentGroup" + ], + "demands": [], + "version": { + "Major": 3, + "Minor": 267, + "Patch": 0 + }, + "minimumAgentVersion": "2.0.0", + "instanceNameFormat": "ms-resource:loc.instanceNameFormat", + "showEnvironmentVariables": true, + "groups": [ + { + "name": "advanced", + "displayName": "ms-resource:loc.group.displayName.advanced", + "isExpanded": true + } + ], + "inputs": [ + { + "name": "connectionType", + "type": "pickList", + "label": "ms-resource:loc.input.label.connectionType", + "defaultValue": "azureRM", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.connectionType", + "options": { + "azureRM": "Azure Resource Manager", + "azureDevOps": "Azure DevOps" + } + }, + { + "name": "connectedServiceNameARM", + "aliases": [ + "azureSubscription" + ], + "type": "connectedService:AzureRM", + "label": "ms-resource:loc.input.label.connectedServiceNameARM", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.connectedServiceNameARM", + "visibleRule": "connectionType = azureRM", + "properties": { + "EndpointFilterRule": "" + } + }, + { + "name": "azureDevOpsServiceConnection", + "type": "connectedService:WorkloadIdentityUser", + "label": "ms-resource:loc.input.label.azureDevOpsServiceConnection", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.azureDevOpsServiceConnection", + "visibleRule": "connectionType = azureDevOps", + "properties": { + "EndpointFilterRule": "" + } + }, + { + "name": "scriptType", + "type": "pickList", + "label": "ms-resource:loc.input.label.scriptType", + "defaultValue": "", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.scriptType", + "options": { + "ps": "PowerShell", + "pscore": "PowerShell Core", + "batch": "Batch", + "bash": "Shell" + } + }, + { + "name": "scriptLocation", + "type": "pickList", + "label": "ms-resource:loc.input.label.scriptLocation", + "defaultValue": "scriptPath", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.scriptLocation", + "options": { + "inlineScript": "Inline script", + "scriptPath": "Script path" + } + }, + { + "name": "scriptPath", + "type": "filePath", + "label": "ms-resource:loc.input.label.scriptPath", + "defaultValue": "", + "required": true, + "visibleRule": "scriptLocation = scriptPath", + "helpMarkDown": "ms-resource:loc.input.help.scriptPath" + }, + { + "name": "inlineScript", + "type": "multiLine", + "label": "ms-resource:loc.input.label.inlineScript", + "defaultValue": "", + "required": true, + "visibleRule": "scriptLocation = inlineScript", + "helpMarkDown": "ms-resource:loc.input.help.inlineScript", + "properties": { + "resizable": "true", + "rows": "10", + "maxLength": "5000" + } + }, + { + "name": "scriptArguments", + "aliases": [ + "arguments" + ], + "type": "string", + "label": "ms-resource:loc.input.label.scriptArguments", + "defaultValue": "", + "required": false, + "helpMarkDown": "ms-resource:loc.input.help.scriptArguments", + "properties": { + "editorExtension": "ms.vss-services-azure.parameters-grid" + } + }, + { + "name": "powerShellErrorActionPreference", + "type": "pickList", + "label": "ms-resource:loc.input.label.powerShellErrorActionPreference", + "required": false, + "defaultValue": "stop", + "options": { + "stop": "Stop", + "continue": "Continue", + "silentlyContinue": "SilentlyContinue" + }, + "visibleRule": "scriptType = ps || scriptType = pscore", + "helpMarkDown": "ms-resource:loc.input.help.powerShellErrorActionPreference" + }, + { + "name": "addSpnToEnvironment", + "type": "boolean", + "label": "ms-resource:loc.input.label.addSpnToEnvironment", + "defaultValue": "false", + "required": false, + "helpMarkDown": "ms-resource:loc.input.help.addSpnToEnvironment", + "groupName": "advanced" + }, + { + "name": "useGlobalConfig", + "type": "boolean", + "label": "ms-resource:loc.input.label.useGlobalConfig", + "defaultValue": "false", + "required": false, + "helpMarkDown": "ms-resource:loc.input.help.useGlobalConfig", + "groupName": "advanced" + }, + { + "name": "cwd", + "aliases": [ + "workingDirectory" + ], + "type": "filePath", + "label": "ms-resource:loc.input.label.cwd", + "defaultValue": "", + "required": false, + "helpMarkDown": "ms-resource:loc.input.help.cwd", + "groupName": "advanced" + }, + { + "name": "failOnStandardError", + "type": "boolean", + "label": "ms-resource:loc.input.label.failOnStandardError", + "defaultValue": "false", + "required": false, + "helpMarkDown": "ms-resource:loc.input.help.failOnStandardError", + "groupName": "advanced" + }, + { + "name": "powerShellIgnoreLASTEXITCODE", + "type": "boolean", + "label": "ms-resource:loc.input.label.powerShellIgnoreLASTEXITCODE", + "required": false, + "defaultValue": "false", + "visibleRule": "scriptType = ps || scriptType = pscore", + "helpMarkDown": "ms-resource:loc.input.help.powerShellIgnoreLASTEXITCODE", + "groupName": "advanced" + }, + { + "name": "visibleAzLogin", + "type": "boolean", + "label": "ms-resource:loc.input.label.visibleAzLogin", + "defaultValue": "true", + "required": false, + "helpMarkDown": "ms-resource:loc.input.help.visibleAzLogin", + "groupName": "advanced" + }, + { + "name": "keepAzSessionActive", + "type": "boolean", + "label": "ms-resource:loc.input.label.keepAzSessionActive", + "defaultValue": "false", + "required": false, + "helpMarkDown": "ms-resource:loc.input.help.keepAzSessionActive", + "groupName": "advanced" + } + ], + "execution": { + "Node10": { + "target": "azureclitask.js", + "argumentFormat": "" + }, + "Node16": { + "target": "azureclitask.js", + "argumentFormat": "" + }, + "Node20_1": { + "target": "azureclitask.js", + "argumentFormat": "" + } + }, + "messages": { + "ScriptReturnCode": "ms-resource:loc.messages.ScriptReturnCode", + "ScriptFailed": "ms-resource:loc.messages.ScriptFailed", + "ScriptFailedStdErr": "ms-resource:loc.messages.ScriptFailedStdErr", + "ScriptFailedWithExitCode": "ms-resource:loc.messages.ScriptFailedWithExitCode", + "UnsupportedEndpointScheme": "ms-resource:loc.messages.UnsupportedEndpointScheme", + "AzureSDKNotFound": "ms-resource:loc.messages.AzureSDKNotFound", + "FailedToLogout": "ms-resource:loc.messages.FailedToLogout", + "LoginFailed": "ms-resource:loc.messages.LoginFailed", + "MSILoginFailed": "ms-resource:loc.messages.MSILoginFailed", + "AuthSchemeNotSupportedForAzureRM": "ms-resource:loc.messages.AuthSchemeNotSupportedForAzureRM", + "AuthSchemeNotSupportedForAzureDevOps": "ms-resource:loc.messages.AuthSchemeNotSupportedForAzureDevOps", + "ErrorInSettingUpSubscription": "ms-resource:loc.messages.ErrorInSettingUpSubscription", + "SettingAzureConfigDir": "ms-resource:loc.messages.SettingAzureConfigDir", + "SettingAzureCloud": "ms-resource:loc.messages.SettingAzureCloud", + "JS_InvalidFilePath": "ms-resource:loc.messages.JS_InvalidFilePath", + "JS_InvalidErrorActionPreference": "ms-resource:loc.messages.JS_InvalidErrorActionPreference", + "GlobalCliConfigAgentVersionWarning": "ms-resource:loc.messages.GlobalCliConfigAgentVersionWarning", + "UnacceptedScriptLocationValue": "ms-resource:loc.messages.UnacceptedScriptLocationValue", + "ExpiredServicePrincipalMessageWithLink": "ms-resource:loc.messages.ExpiredServicePrincipalMessageWithLink", + "ProxyConfig": "ms-resource:loc.messages.ProxyConfig", + "FailedToRefreshAzSession": "ms-resource:loc.messages.FailedToRefreshAzSession", + "RefreshingAzSession": "ms-resource:loc.messages.RefreshingAzSession", + "KeepingAzSessionActiveUnsupportedScheme": "ms-resource:loc.messages.KeepingAzSessionActiveUnsupportedScheme", + "FailedToInstallAzureDevOpsCLI": "ms-resource:loc.messages.FailedToInstallAzureDevOpsCLI", + "FailedToLoginAzureDevOpsCLI": "ms-resource:loc.messages.FailedToLoginAzureDevOpsCLI", + "FailedToSetAzureDevOpsOrganization": "ms-resource:loc.messages.FailedToSetAzureDevOpsOrganization", + "FailedToSetAzureDevOpsProject": "ms-resource:loc.messages.FailedToSetAzureDevOpsProject" + } +} \ No newline at end of file diff --git a/Tasks/AzureCLIV3/tsconfig.json b/Tasks/AzureCLIV3/tsconfig.json new file mode 100644 index 000000000000..0438b79f69ac --- /dev/null +++ b/Tasks/AzureCLIV3/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "commonjs" + } +} \ No newline at end of file diff --git a/make-options.json b/make-options.json index 326941b00b3d..77a09dda3d5e 100644 --- a/make-options.json +++ b/make-options.json @@ -15,6 +15,7 @@ "AzureAppServiceSettingsV1", "AzureCLIV1", "AzureCLIV2", + "AzureCLIV3", "AzureCloudPowerShellDeploymentV1", "AzureCloudPowerShellDeploymentV2", "AzureContainerAppsV0",