Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fail on standard error option for azure powershell task #6324

Merged
merged 17 commits into from
Mar 14, 2018

Conversation

arjgupta
Copy link
Member

  • This change is meant to introduce "Fail on Standard Error" option for the Azure PowerShell task. The default value of the option is true. This is because the current behaviour of the task is that if anything is written to the error stream ( terminating or non-terminating ), the task fails.

  • If the option is unchecked, then it should be possible to use write-error in the script without causing the task to fail.

… set to false and non-terminating errors are written to the error stream
@arjgupta
Copy link
Member Author

arjgupta commented Feb 1, 2018

for fixing the issue #5182

@@ -105,7 +106,7 @@ try {
,$_

# Set the task result to failed if the object is an error record.
if ($_ -is [System.Management.Automation.ErrorRecord]) {
if ($_ -is [System.Management.Automation.ErrorRecord] -and $failOnStandardError -eq $true) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered adding two options like the latest version of the powerShell task? ErrorActionPreference and FailOnStderr?

Or trying to limit the change to actual stderr from external processes? We added the ignore stderr flag because folks had a hard time suppressing stderr from external tools.

Does the AzurePowerShell task have a different problem? What happens today if someone sets the error action preference to continue in their script. Does it always fail the task due to this line?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the current problem is that if a script writes a non-terminating error, or if a cmdlet writes a non-terminating error, the task fails. also, stderr from external commands is converted into error records. Today we set the erroractionpreference to 'continue' before we execute the script. So unless the user's script overrides it, the task always fails for non-terminating errors.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gotcha

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally the options would be aligned with the powershell, so customers get a consistent experience.

In the new powershell task, I switched the default error action preference to 'Stop'. Although this is different from the powershell default, I think Stop generally aligns with customer scripting patterns (and they can override if needed). By setting to Stop, powershell will exit with a non-zero exit code if there is an error written to the error pipeline (and stderr will not interfere).

And then it's an explicit choice for the user to opt into failing on stderr. The key is, i run a separate powershell process for the user's script. So stderr means anything written to the error pipeline or any nested processes that wrote to the inherited stderr stream from powershell. Programs commonly write to stderr, so we definitely wanted failing on stderr to be opt-in.

The Azure Powershell task would need to major-version-rev'd to match exactly that behavior. Otherwise it would break compat since historically the task has always failed when any error is written to the error pipeline, and also when any nested process write to stderr.

So I think you have two options today without rev'ing the major version.

  1. You could match the same options but with different defaults. I think you could achieve this by adding the current logic into a separate script, and invoke that as a separate process. Then you could monitor stderr from the outer process.
  2. What you are doing here conflates stderr and error-action-preference. If you choose to move forward with this approach I would recommend being very clear in the help markdown that if they unset fail-on-stderr then they need to rely on bubbling a terminating error or exception to communicate task failure.

Copy link
Member Author

@arjgupta arjgupta Feb 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So for option no. 1, I'll have to add both error action preference and a fail on std error inputs, and they'll have the values 'stop' and true respectively ( this won't be an opt-in experience) so as to match the current behaviour. But how does starting a new process help in this? Currently the way both tasks(azureps and powershell) monitor the output stream, any error written by an external tool comes off as a non-terminating error record, and then whether or not the task fails depends on the Bool failonstderr (the last part being true only for the powershell task as of now). So if i handle the setting up of the error action preference for the user script, shouldn't the current changes work?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This statement is not true: Currently the way both tasks(azureps and powershell) monitor the output stream, any error written by an external tool comes off as a non-terminating error record

Often times when you run an external command (separate process), the external process inherits the STDOUT and STDERR streams. When the streams are inherited, the calling powershell process doesn't even know that anything was written to STDERR, and therefore no error record gets created. For example, paste this into a powershell.exe prompt:

& powershell.exe -noninteractive -command "[system.console]::Error.WriteLine('foo')"

and you will notice that you do not get an error record.

I'm mentally trying to walk through the cases of error-action-preference and fail-on-stderr to determine whether this change would be consistent with the current v2 powershell task...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the only difference would be related to the same differences that exists today. When using the Azure PowerShell task, when an external processes writes to STDERR an error record will get created in powershell. This was an explicit choice because I migrated this task from the legacy handler to the powershell3 handler, and I explicitly held compat on that behavior. The legacy handler used the powershell engine rather than powershell.exe - and that is a behavioral difference of between the two.

So error-action=stop, fail-on-stderr=false, would behave differently between the two tasks, when an external command writes to STDERR. The powershell task would not create an error record and the task would succeed. The Azure PowerShell task would create a terminating error.

And also a subtle behavior difference for error-action=stop, fail-on-stderr=true. The powershell task would run to completion and fail due to stderr. The Azure PowerShell task would again create a terminating error.

Without bumping the major version, I think you want to hold compat on the behavior that causes stderr to create error records.

Let me know if you have all the info you need.

I think the differences that we are down to, are minimal and a result of holding back compat.

The differences are subtle, but it's also the subtle differences that causes customer confusion. It's the motivation for attempting to match the powershell.exe behavior. So customers aren't confused why it works one way on their machine, and then differently in vsts.

@kmkumaran kmkumaran self-requested a review February 2, 2018 03:29
@kmkumaran kmkumaran self-assigned this Feb 2, 2018
# Trace the expression as it will be invoked.
$__vstsAzPSInlineScriptPath = $null
If ($scriptType -eq "InlineScript") {
$scriptArguments = $null
$__vstsAzPSInlineScriptPath = [System.IO.Path]::Combine(([System.IO.Path]::GetTempPath()), ([guid]::NewGuid().ToString() + ".ps1"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be using Agent.TempDirectory instead of users temp path?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will update

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

if ($_ -is [System.Management.Automation.ErrorRecord]) {
"##vso[task.complete result=Failed]"
if($_ -is [System.Management.Automation.ErrorRecord]) {
if($_.FullyQualifiedErrorId -eq "NativeCommandError" -or $_.FullyQualifiedErrorId -eq "NativeCommandErrorMessage") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this errorId localizable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No

@@ -6,6 +6,8 @@ $scriptType = Get-VstsInput -Name ScriptType -Require
$scriptPath = Get-VstsInput -Name ScriptPath
$scriptInline = Get-VstsInput -Name Inline
$scriptArguments = Get-VstsInput -Name ScriptArguments
$__vsts_input_errorActionPreference = Get-VstsInput -Name errorActionPreference
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why have a different naming convention just for these inputs?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

erroractionpreference is a well known name. the other reason is that we remove the variables and functions from the current scope before executing the script, but we cannot remove these variables since we need them as the script is executing. And so, to avoid a conflict between these names and similar names defined in the user script, we use this scheme.

@@ -15,14 +15,22 @@
],
"author": "Microsoft Corporation",
"version": {
"Major": 2,
"Major": 3,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add "releaseNotes" attribute to call out what is changed in this major version.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

} else {
throw (Get-VstsLocString -Key AZ_ModuleNotFound -ArgumentList "Any version")
}
ThrowAzureModuleNotFoundException -azurePsVersion $azurePsVersion -modules "Azure, AzureRM"
}
} elseif ($PreferredModule -contains 'Azure') {
# Attempt to import Azure but fallback to AzureRM.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic and this comment do not match anymore, since we don't fallback always.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

{
Write-Host "##[command]Add-AzureRMAccount -ServicePrincipal -Tenant $($Endpoint.Auth.Parameters.TenantId) -Credential $psCredential -EnvironmentName $environmentName"
$null = Add-AzureRMAccount -ServicePrincipal -Tenant $Endpoint.Auth.Parameters.TenantId -Credential $psCredential -EnvironmentName $environmentName
if (Get-Command -Name "Add-AzureRmAccount" -ErrorAction "SilentlyContinue") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should you update in releases/M131 so that earlier version of task also get this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@arjgupta arjgupta merged commit a5ac2c0 into master Mar 14, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants