Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion scripts/curl_install_pypi/install
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,28 @@
# Bash script to install the Azure CLI
#
INSTALL_SCRIPT_URL="https://azurecliprod.blob.core.windows.net/install.py"
INSTALL_SCRIPT_SHA256=7419f49b066015d863f398198c4ac5ad026f5aa3705e898b552e4e03fc352552
INSTALL_SCRIPT_SHA256=bf6b7f778937b7a212e498ccc7235d36d244140721b3cb6cea6a6bc264d1d78d
_TTY=/dev/tty

install_script=$(mktemp -t azure_cli_install_tmp_XXXXXX) || exit
echo "Downloading Azure CLI install script from $INSTALL_SCRIPT_URL to $install_script."
curl -# $INSTALL_SCRIPT_URL > $install_script || exit

if command -v sha256sum >/dev/null 2>&1
then
echo "$INSTALL_SCRIPT_SHA256 $install_script" | sha256sum -c - || exit
elif command -v shasum >/dev/null 2>&1
then
echo "$INSTALL_SCRIPT_SHA256 $install_script" | shasum -a 256 -c - || exit
fi

if ! command -v python >/dev/null 2>&1
then
echo "ERROR: Python not found. 'command -v python' returned failure."
echo "If python is available on the system, add it to PATH. For example 'sudo ln -s /usr/bin/python3 /usr/bin/python'"
exit 1
fi

chmod 775 $install_script
echo "Running install script."
$install_script < $_TTY
54 changes: 49 additions & 5 deletions scripts/curl_install_pypi/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,26 @@
complete -o nospace -F _python_argcomplete "az"
"""


class CLIInstallError(Exception):
pass


Comment thread
fengzhou-msft marked this conversation as resolved.
def print_status(msg=''):
print('-- '+msg)


def prompt_input(msg):
return input('\n===> '+msg)


def prompt_input_with_default(msg, default):
if default:
return prompt_input("{} (leave blank to use '{}'): ".format(msg, default)) or default
else:
return prompt_input('{}: '.format(msg))


def prompt_y_n(msg, default=None):
if default not in [None, 'y', 'n']:
raise ValueError("Valid values for default are 'y', 'n' or None")
Expand All @@ -95,26 +100,31 @@ def prompt_y_n(msg, default=None):
if default and not ans:
return default == y.lower()


def exec_command(command_list, cwd=None, env=None):
print_status('Executing: '+str(command_list))
subprocess.check_call(command_list, cwd=cwd, env=env)


def create_tmp_dir():
tmp_dir = tempfile.mkdtemp()
return tmp_dir


def create_dir(dir):
if not os.path.isdir(dir):
print_status("Creating directory '{}'.".format(dir))
os.makedirs(dir)


def is_valid_sha256sum(a_file, expected_sum):
sha256 = hashlib.sha256()
with open(a_file, 'rb') as f:
sha256.update(f.read())
computed_hash = sha256.hexdigest()
return expected_sum == computed_hash


def create_virtualenv(tmp_dir, install_dir):
download_location = os.path.join(tmp_dir, VIRTUALENV_ARCHIVE)
print_status('Downloading virtualenv package from {}.'.format(VIRTUALENV_DOWNLOAD_URL))
Expand All @@ -134,6 +144,7 @@ def create_virtualenv(tmp_dir, install_dir):
cmd = [sys.executable, 'virtualenv.py', '--python', sys.executable, install_dir]
exec_command(cmd, cwd=working_dir)


def install_cli(install_dir, tmp_dir):
path_to_pip = os.path.join(install_dir, 'bin', 'pip')
cmd = [path_to_pip, 'install', '--cache-dir', tmp_dir, 'azure-cli', '--upgrade']
Expand All @@ -143,6 +154,7 @@ def install_cli(install_dir, tmp_dir):
fixupcmd = [path_to_pip, 'install', '--cache-dir', tmp_dir, '--upgrade', '--force-reinstall', 'azure-nspkg', 'azure-mgmt-nspkg']
exec_command(fixupcmd)


def create_executable(exec_dir, install_dir):
create_dir(exec_dir)
exec_filepath = os.path.join(exec_dir, EXECUTABLE_NAME)
Expand All @@ -153,6 +165,7 @@ def create_executable(exec_dir, install_dir):
print_status("The executable is available at '{}'.".format(exec_filepath))
return exec_filepath


def get_install_dir():
install_dir = None
while not install_dir:
Expand All @@ -177,6 +190,7 @@ def get_install_dir():
print_status("We will install at '{}'.".format(install_dir))
return install_dir


def get_exec_dir():
exec_dir = None
while not exec_dir:
Expand All @@ -190,13 +204,15 @@ def get_exec_dir():
print_status("The executable will be in '{}'.".format(exec_dir))
return exec_dir


def _backup_rc(rc_file):
try:
shutil.copyfile(rc_file, rc_file+'.backup')
print_status("Backed up '{}' to '{}'".format(rc_file, rc_file+'.backup'))
except (OSError, IOError):
pass


def _get_default_rc_file():
bashrc_exists = os.path.isfile(USER_BASH_RC)
bash_profile_exists = os.path.isfile(USER_BASH_PROFILE)
Expand All @@ -206,6 +222,7 @@ def _get_default_rc_file():
return USER_BASH_PROFILE
return USER_BASH_RC if bashrc_exists else None


def _default_rc_file_creation_step():
rcfile = USER_BASH_PROFILE if platform.system().lower() == 'darwin' else USER_BASH_RC
ans_yes = prompt_y_n('Could not automatically find a suitable file to use. Create {} now?'.format(rcfile), default='y')
Expand All @@ -214,6 +231,7 @@ def _default_rc_file_creation_step():
return rcfile
return None


def _find_line_in_file(file_path, search_pattern):
try:
with open(file_path, 'r', encoding="utf-8") as search_file:
Expand All @@ -224,16 +242,19 @@ def _find_line_in_file(file_path, search_pattern):
pass
return False


def _modify_rc(rc_file_path, line_to_add):
if not _find_line_in_file(rc_file_path, line_to_add):
with open(rc_file_path, 'a', encoding="utf-8") as rc_file:
rc_file.write('\n'+line_to_add+'\n')


def create_tab_completion_file(filename):
with open(filename, 'w') as completion_file:
completion_file.write(PYTHON_ARGCOMPLETE_CODE)
print_status("Created tab completion file at '{}'".format(filename))


def get_rc_file_path():
rc_file = None
default_rc_file = _get_default_rc_file()
Expand All @@ -247,6 +268,7 @@ def get_rc_file_path():
print_status("The file '{}' could not be found.".format(rc_file_path))
return None


def warn_other_azs_on_path(exec_dir, exec_filepath):
env_path = os.environ.get('PATH')
conflicting_paths = []
Expand All @@ -261,6 +283,7 @@ def warn_other_azs_on_path(exec_dir, exec_filepath):
print_status("Conflicting paths: {}".format(', '.join(conflicting_paths)))
print_status("You can run this installation of the CLI with '{}'.".format(exec_filepath))


def handle_path_and_tab_completion(completion_file_path, exec_filepath, exec_dir):
ans_yes = prompt_y_n('Modify profile to update your $PATH and enable shell/tab completion now?', 'y')
if ans_yes:
Expand All @@ -282,6 +305,7 @@ def handle_path_and_tab_completion(completion_file_path, exec_filepath, exec_dir
print_status("If you change your mind, add 'source {}' to your rc file and restart your shell to enable tab completion.".format(completion_file_path))
print_status("You can run the CLI with '{}'.".format(exec_filepath))


def verify_python_version():
print_status('Verifying Python version.')
v = sys.version_info
Expand All @@ -292,6 +316,7 @@ def verify_python_version():
"Create an Anaconda virtual environment and install with 'pip'")
print_status('Python version {}.{}.{} okay.'.format(v.major, v.minor, v.micro))


def _native_dependencies_for_dist(verify_cmd_args, install_cmd_args, dep_list):
try:
print_status("Executing: '{} {}'".format(' '.join(verify_cmd_args), ' '.join(dep_list)))
Expand All @@ -305,17 +330,32 @@ def _native_dependencies_for_dist(verify_cmd_args, install_cmd_args, dep_list):
if not ans_yes:
raise CLIInstallError('Please install the native dependencies and try again.')


def _get_linux_distro():
with open('/etc/os-release') as lines:
tokens = [line.strip() for line in lines]

release_info = {}
for token in tokens:
if '=' in token:
k, v = token.split('=', 1)
release_info[k.lower()] = v.strip('"')

return release_info.get('name', None), release_info.get('version_id', None)


def verify_native_dependencies():
distname, version, _ = platform.linux_distribution()
distname, version = _get_linux_distro()

if not distname:
# There's no distribution name so can't determine native dependencies required / or they may not be needed like on OS X
return

print_status('Verifying native dependencies.')
is_python3 = sys.version_info[0] == 3
distname = distname.lower().strip()
verify_cmd_args = None
install_cmd_args = None
dep_list = None

verify_cmd_args, install_cmd_args, dep_list = None, None, None
if any(x in distname for x in ['ubuntu', 'debian']):
verify_cmd_args = ['dpkg', '-s']
install_cmd_args = ['apt-get', 'update', '&&', 'apt-get', 'install', '-y']
Expand All @@ -330,20 +370,23 @@ def verify_native_dependencies():
# python3-devel not available on yum but python3Xu-devel versions available.
python_dep = 'python3{}u-devel'.format(sys.version_info[1]) if is_python3 else 'python-devel'
dep_list = ['gcc', 'libffi-devel', python_dep, 'openssl-devel']
elif any(x in distname for x in ['opensuse', 'suse']):
elif any(x in distname for x in ['opensuse', 'suse', 'sles']):
verify_cmd_args = ['rpm', '-q']
install_cmd_args = ['zypper', 'refresh', '&&', 'zypper', '--non-interactive', 'install']
python_dep = 'python3-devel' if is_python3 else 'python-devel'
dep_list = ['gcc', 'libffi-devel', python_dep, 'openssl-devel']

if verify_cmd_args and install_cmd_args and dep_list:
_native_dependencies_for_dist(verify_cmd_args, install_cmd_args, dep_list)
else:
print_status("Unable to verify native dependencies. dist={}, version={}. Continuing...".format(distname, version))


def verify_install_dir_exec_path_conflict(install_dir, exec_path):
if install_dir == exec_path:
raise CLIInstallError("The executable file '{}' would clash with the install directory of '{}'. Choose either a different install directory or directory to place the executable.".format(exec_path, install_dir))


def main():
verify_python_version()
verify_native_dependencies()
Expand All @@ -365,6 +408,7 @@ def main():
print_status("Installation successful.")
print_status("Run the CLI with {} --help".format(exec_filepath))


if __name__ == '__main__':
try:
main()
Expand Down