Skip to content

Conversation

@bebound
Copy link
Contributor

@bebound bebound commented May 17, 2023

Description

Close #22741, close #23947

This PR changes the behavior of az upgrade on Windows.
If the Python version changes in new version, the upgrade is blocked.
(This issue can be reproduced by installing 2.44 and running az upgrade)
image

In this PR, az upgrade exits after MSI installation dialog is opened. So the related file is not opened during upgrade.

Three breaking changes:

  1. az upgrade becomes non-blocking command, it exit after MSI installation dialog is opened.
  2. az does not know if the upgrade is successful, the exit code is always 0 once installation starts.
  3. This command does not upgrade extensions if new version is available. Users need to run az upgrade again to upgrade extensions.

Testing Guide

set this in upgrade_version

local_version = '2.50.0'
installer = 'MSI'

This checklist is used to make sure that common guidelines for a pull request are followed.

@azure-client-tools-bot-prd
Copy link

azure-client-tools-bot-prd bot commented May 17, 2023

🔄AzureCLI-FullTest
️✔️acr
️✔️2020-09-01-hybrid
️✔️3.11
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.9
️✔️acs
️✔️2020-09-01-hybrid
️✔️3.11
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.9
️✔️advisor
️✔️latest
️✔️3.11
️✔️3.9
️✔️ams
️✔️latest
️✔️3.11
️✔️3.9
️✔️apim
️✔️latest
️✔️3.11
️✔️3.9
️✔️appconfig
️✔️latest
️✔️3.11
️✔️3.9
️✔️appservice
️✔️latest
️✔️3.11
️✔️3.9
️✔️aro
️✔️latest
️✔️3.11
️✔️3.9
️✔️backup
️✔️latest
️✔️3.11
️✔️3.9
️✔️batch
️✔️latest
️✔️3.11
️✔️3.9
️✔️batchai
️✔️latest
️✔️3.11
️✔️3.9
️✔️billing
️✔️latest
️✔️3.11
️✔️3.9
️✔️botservice
️✔️latest
️✔️3.11
️✔️3.9
️✔️cdn
️✔️latest
️✔️3.11
️✔️3.9
️✔️cloud
️✔️latest
️✔️3.11
️✔️3.9
️✔️cognitiveservices
️✔️latest
️✔️3.11
️✔️3.9
️✔️config
️✔️latest
️✔️3.11
️✔️3.9
️✔️configure
️✔️latest
️✔️3.11
️✔️3.9
️✔️consumption
️✔️latest
️✔️3.11
️✔️3.9
️✔️container
️✔️latest
️✔️3.11
️✔️3.9
️✔️containerapp
️✔️latest
️✔️3.11
️✔️3.9
️✔️core
️✔️2018-03-01-hybrid
️✔️3.11
️✔️3.9
️✔️2019-03-01-hybrid
️✔️3.11
️✔️3.9
️✔️2020-09-01-hybrid
️✔️3.11
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.9
️✔️cosmosdb
️✔️latest
️✔️3.11
️✔️3.9
️✔️databoxedge
️✔️2019-03-01-hybrid
️✔️3.11
️✔️3.9
️✔️2020-09-01-hybrid
️✔️3.11
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.9
️✔️dla
️✔️latest
️✔️3.11
️✔️3.9
️✔️dls
️✔️latest
️✔️3.11
️✔️3.9
️✔️dms
️✔️latest
️✔️3.11
️✔️3.9
️✔️eventgrid
️✔️latest
️✔️3.11
️✔️3.9
️✔️eventhubs
️✔️latest
️✔️3.11
️✔️3.9
️✔️feedback
️✔️latest
️✔️3.11
️✔️3.9
️✔️find
️✔️latest
️✔️3.11
️✔️3.9
️✔️hdinsight
️✔️latest
️✔️3.11
️✔️3.9
️✔️identity
️✔️latest
️✔️3.11
️✔️3.9
️✔️iot
️✔️2019-03-01-hybrid
️✔️3.11
️✔️3.9
️✔️2020-09-01-hybrid
️✔️3.11
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.9
️✔️keyvault
️✔️2018-03-01-hybrid
️✔️3.11
️✔️3.9
️✔️2020-09-01-hybrid
️✔️3.11
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.9
️✔️kusto
️✔️latest
️✔️3.11
️✔️3.9
️✔️lab
️✔️latest
️✔️3.11
️✔️3.9
️✔️managedservices
️✔️latest
️✔️3.11
️✔️3.9
️✔️maps
️✔️latest
️✔️3.11
️✔️3.9
️✔️marketplaceordering
️✔️latest
️✔️3.11
️✔️3.9
️✔️monitor
️✔️latest
️✔️3.11
️✔️3.9
🔄mysql
🔄latest
️✔️3.11
🔄3.9
️✔️netappfiles
️✔️latest
️✔️3.11
️✔️3.9
️✔️network
️✔️2018-03-01-hybrid
️✔️3.11
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.9
️✔️policyinsights
️✔️latest
️✔️3.11
️✔️3.9
️✔️privatedns
️✔️latest
️✔️3.11
️✔️3.9
️✔️profile
️✔️latest
️✔️3.11
️✔️3.9
️✔️rdbms
️✔️latest
️✔️3.11
️✔️3.9
️✔️redis
️✔️latest
️✔️3.11
️✔️3.9
️✔️relay
️✔️latest
️✔️3.11
️✔️3.9
️✔️resource
️✔️2018-03-01-hybrid
️✔️3.11
️✔️3.9
️✔️2019-03-01-hybrid
️✔️3.11
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.9
️✔️role
️✔️latest
️✔️3.11
️✔️3.9
️✔️search
️✔️latest
️✔️3.11
️✔️3.9
️✔️security
️✔️latest
️✔️3.11
️✔️3.9
️✔️servicebus
️✔️latest
️✔️3.11
️✔️3.9
️✔️serviceconnector
️✔️latest
️✔️3.11
️✔️3.9
️✔️servicefabric
️✔️latest
️✔️3.11
️✔️3.9
️✔️signalr
️✔️latest
️✔️3.11
️✔️3.9
️✔️sql
️✔️latest
️✔️3.11
️✔️3.9
️✔️sqlvm
️✔️latest
️✔️3.11
️✔️3.9
️✔️storage
️✔️2018-03-01-hybrid
️✔️3.11
️✔️3.9
️✔️2019-03-01-hybrid
️✔️3.11
️✔️3.9
️✔️2020-09-01-hybrid
️✔️3.11
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.9
️✔️synapse
️✔️latest
️✔️3.11
️✔️3.9
️✔️telemetry
️✔️2018-03-01-hybrid
️✔️3.11
️✔️3.9
️✔️2019-03-01-hybrid
️✔️3.11
️✔️3.9
️✔️2020-09-01-hybrid
️✔️3.11
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.9
️✔️util
️✔️latest
️✔️3.11
️✔️3.9
️✔️vm
️✔️2018-03-01-hybrid
️✔️3.11
️✔️3.9
️✔️2019-03-01-hybrid
️✔️3.11
️✔️3.9
️✔️2020-09-01-hybrid
️✔️3.11
️✔️3.9
️✔️latest
️✔️3.11
️✔️3.9

@ghost ghost requested review from jiasli and wangzelin007 May 17, 2023 09:18
@yonzhan
Copy link
Collaborator

yonzhan commented May 17, 2023

az upgrade improvement for Windows when Python is upgrade.

@ghost ghost requested a review from yonzhan May 17, 2023 09:18
@ghost ghost added the Auto-Assign Auto assign by bot label May 17, 2023
@ghost ghost assigned bebound May 17, 2023
@ghost ghost added the Packaging label May 17, 2023
Comment on lines 193 to 196
subprocess.Popen(['msiexec.exe', '/i', msi_path])
logger.warning("Installation started, please wait for a few minutes.")
import sys
sys.exit(0)
Copy link
Member

@jiasli jiasli May 18, 2023

Choose a reason for hiding this comment

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

As stated in the PR description, this makes us be unable to tell if the upgrade is successful. Better to confirm with PM whether this is an acceptable solution. Otherwise, we can develop some "upgrader.exe" written in C.

Copy link
Contributor Author

@bebound bebound May 19, 2023

Choose a reason for hiding this comment

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

We can create a upgrader to update az to prevent running az, but we need to call az to map az upgrade command to that upgrader.exe. We still need to keep az running to retrieve the upgrade status, and we face this issue again.

az upgrade is very complicated. It downloads MSI, waits for installation to finish and updates extensions in one command.
I can hardly come up with an idea to fix this issue without breaking change.

Copy link
Member

Choose a reason for hiding this comment

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

I thought about it again and feel it is indeed not possible. A GUI application can have such upgrader.exe to upgrade the product itself, but this can't be done for a console application.

Consider this invocation chain: python.exe(1) -> upgrader.exe -> python.exe(2)

  • To keep the second python.exe connected to the original terminal context, every executable on the invocation chain must be alive and wait for its downstream executable to exit. In order words, if python.exe(1) subprocesses upgrader.exe and exits, then after upgrader.exe upgrades python.exe, subprocesses python.exe(2) and exits, it is no longer possible to connect python.exe(2) back to the original terminal context - the output is out of order.
  • If the first python.exe is alive, upgrader.exe cannot update it.

-> A contradiction!

Consider the following code:

import subprocess

p = subprocess.Popen(['python.exe', '-c', 'import time; time.sleep(3); print("awaken")'])
print('Exiting')

Output:

PS D:\cli\testproj> python.exe main.py
Exiting
PS D:\cli\testproj> awaken
|

The first python.exe will exits and gives control back to the terminal. The second python.exe can print, yes, but it is out of order.

How to solve it? Wait for the subprocess to exit:

import subprocess

p = subprocess.Popen(['python.exe', '-c', 'import time; time.sleep(3); print("awaken")'])
p.wait()
print('Exiting')

Output:

PS D:\cli\testproj> python.exe main.py
awaken
Exiting
PS D:\cli\testproj> |

Copy link
Contributor Author

@bebound bebound May 19, 2023

Choose a reason for hiding this comment

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

Yes, exactly.


I come up an idea to break the python dependency of python.exe(1) -> upgrader.exe.
We can modify az.cmd, if the param is upgrade, call upgrader.exe, otherwise, run az.

Furthermore, we can write the upgrade logic in bat, it looks feasible. But is it worth writing dedicated logic for Windows?

@bebound bebound changed the title [Packaging] Fix #22741: az upgrade: Fix Python file in use error [Packaging] Fix #22741: az upgrade: This command is non-blocking on Windows May 18, 2023
@bebound bebound changed the title [Packaging] Fix #22741: az upgrade: This command is non-blocking on Windows [Packaging] Fix #22741: az upgrade: This command becomes non-blocking on Windows May 18, 2023
import sys
sys.exit(0)

if exit_code:
Copy link
Member

Choose a reason for hiding this comment

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

These below lines can never be reached, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, they are not reachable. This is draft PR for test only currently.

@azure-client-tools-bot-prd
Copy link

azure-client-tools-bot-prd bot commented Sep 13, 2023

️✔️AzureCLI-BreakingChangeTest
️✔️Non Breaking Changes

@bebound bebound marked this pull request as ready for review September 15, 2023 07:19
@bebound bebound requested a review from zhoxing-ms as a code owner September 15, 2023 07:19
@bebound bebound requested a review from evelyn-ys as a code owner September 15, 2023 07:19
else:
from azure.cli.core.util import rmtree_with_retry
logger.warning("Succeeded. Deleting %s", tmp_dir)
rmtree_with_retry(tmp_dir)
Copy link
Member

Choose a reason for hiding this comment

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

I suggest we find a way to preserve this logic, otherwise the temporary MSI will not be deleted.

Copy link
Contributor Author

@bebound bebound Sep 18, 2023

Choose a reason for hiding this comment

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

The MSI is in the tempfile.mkdtemp() directory (C:\\Users\\xx\\AppData\\Local\\Temp\\tmp1s_cgvau), it appears that windows can delete it automatically.
Manage drive space with Storage Sense

Alternatively, we can save the MSI in .azure or Temp\azure-cli and add logic to clean MSI when run az command.

import subprocess
exit_code = subprocess.call(['msiexec.exe', '/i', msi_path])
# Save MSI to ~\AppData\Local\Temp\azure-cli-msi, clean up the folder first
msi_dir = Path(tempfile.gettempdir()) / 'azure-cli-msi'
Copy link
Member

Choose a reason for hiding this comment

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

Azure CLI doesn't really use pathlib. Consider using os.path to be consistent with the rest of the code.

Copy link
Contributor Author

@bebound bebound Oct 16, 2023

Choose a reason for hiding this comment

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

pathlib is the recommended way to deal with path operations now.

If you’ve never used this module before or just aren’t sure which class is right for your task, Path is most likely what you need.
For low-level path manipulation on strings, you can also use the os.path module.
--- https://docs.python.org/3/library/pathlib.html

# Save MSI to ~\AppData\Local\Temp\azure-cli-msi, clean up the folder first
msi_dir = Path(tempfile.gettempdir()) / 'azure-cli-msi'
rmtree_with_retry(msi_dir)
msi_dir.mkdir()
Copy link
Member

@jiasli jiasli Oct 16, 2023

Choose a reason for hiding this comment

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

Will this line raise exception if rmtree_with_retry can't delete the directory successfully? If so, existing-directory error should be ignored.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

rmtree_with_retry ignores the FileNotFoundError by default, which is my desired behavior.

Copy link
Member

Choose a reason for hiding this comment

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

rmtree_with_retry also ingores cannot-delete-folder error, and that's why the directory can still exist and cause error to mkdir.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Auto-Assign Auto assign by bot Packaging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

az upgrade under Windows waits for end of msiexec which wants to update python.exe Improve the az upgrade locking

3 participants