aboutsummaryrefslogtreecommitdiff
path: root/src/azure-cli/install.sh
blob: 9b0d72db3b325aa29b50512ccf53ce094f5ac1d4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
#!/usr/bin/env bash
#-------------------------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
#-------------------------------------------------------------------------------------------------------------
#
# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/azcli.md
# Maintainer: The VS Code and Codespaces Teams

set -e

# Clean up
rm -rf /var/lib/apt/lists/*

AZ_VERSION=${VERSION:-"latest"}
AZ_EXTENSIONS=${EXTENSIONS}
AZ_INSTALLBICEP=${INSTALLBICEP:-false}
INSTALL_USING_PYTHON=${INSTALL_USING_PYTHON:-true}
MICROSOFT_GPG_KEYS_URI="https://packages.microsoft.com/keys/microsoft.asc"
AZCLI_ARCHIVE_ARCHITECTURES="amd64"
AZCLI_ARCHIVE_VERSION_CODENAMES="stretch buster bullseye bionic focal jammy"

if [ "$(id -u)" -ne 0 ]; then
    echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
    exit 1
fi

if [ -z "${_REMOTE_USER}" ]; then
    echo -e 'Feature script must be executed by a tool that implements the dev container specification. See https://containers.dev/ for more information.'
    exit 1
fi

echo "Effective REMOTE_USER: ${_REMOTE_USER}"

# Get central common setting
get_common_setting() {
    if [ "${common_settings_file_loaded}" != "true" ]; then
        curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
        common_settings_file_loaded=true
    fi
    if [ -f "/tmp/vsdc-settings.env" ]; then
        local multi_line=""
        if [ "$2" = "true" ]; then multi_line="-z"; fi
        local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
        if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
    fi
    echo "$1=${!1}"
}

apt_get_update()
{
    echo "Running apt-get update..."
    apt-get update -y
}

# Checks if packages are installed and installs them if not
check_packages() {
    if ! dpkg -s "$@" > /dev/null 2>&1; then
        if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then
            echo "Running apt-get update..."
            apt-get update -y
        fi
        apt-get -y install --no-install-recommends "$@"
    fi
}

# Soft version matching that resolves a version for a given package in the *current apt-cache*
# Return value is stored in first argument (the unprocessed version)
apt_cache_version_soft_match() {
    
    # Version
    local variable_name="$1"
    local requested_version=${!variable_name}
    # Package Name
    local package_name="$2"
    # Exit on no match?
    local exit_on_no_match="${3:-true}"

    # Ensure we've exported useful variables
    . /etc/os-release
    local architecture="$(dpkg --print-architecture)"
    
    dot_escaped="${requested_version//./\\.}"
    dot_plus_escaped="${dot_escaped//+/\\+}"
    # Regex needs to handle debian package version number format: https://www.systutorials.com/docs/linux/man/5-deb-version/
    version_regex="^(.+:)?${dot_plus_escaped}([\\.\\+ ~:-]|$)"
    set +e # Don't exit if finding version fails - handle gracefully
        fuzzy_version="$(apt-cache madison ${package_name} | awk -F"|" '{print $2}' | sed -e 's/^[ \t]*//' | grep -E -m 1 "${version_regex}")"
    set -e
    if [ -z "${fuzzy_version}" ]; then
        echo "(!) No full or partial for package \"${package_name}\" match found in apt-cache for \"${requested_version}\" on OS ${ID} ${VERSION_CODENAME} (${architecture})."

        if $exit_on_no_match; then
            echo "Available versions:"
            apt-cache madison ${package_name} | awk -F"|" '{print $2}' | grep -oP '^(.+:)?\K.+'
            exit 1 # Fail entire script
        else
            echo "Continuing to fallback method (if available)"
            return 1;
        fi
    fi

    # Globally assign fuzzy_version to this value
    # Use this value as the return value of this function
    declare -g ${variable_name}="=${fuzzy_version}"
    echo "${variable_name} ${!variable_name}"
}

install_using_apt() {
    # Install dependencies
    check_packages apt-transport-https curl ca-certificates gnupg2 dirmngr
    # Import key safely (new 'signed-by' method rather than deprecated apt-key approach) and install
    get_common_setting MICROSOFT_GPG_KEYS_URI
    curl -sSL ${MICROSOFT_GPG_KEYS_URI} | gpg --dearmor > /usr/share/keyrings/microsoft-archive-keyring.gpg
    echo "deb [arch=${architecture} signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] https://packages.microsoft.com/repos/azure-cli/ ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/azure-cli.list
    apt-get update

    if [ "${AZ_VERSION}" = "latest" ] || [ "${AZ_VERSION}" = "lts" ] || [ "${AZ_VERSION}" = "stable" ]; then
        # Empty, meaning grab the "latest" in the apt repo
        AZ_VERSION=""
    else
        # Sets AZ_VERSION to our desired version, if match found.
        apt_cache_version_soft_match AZ_VERSION "azure-cli" false
        if [ "$?" != 0 ]; then
            return 1
        fi
    fi

    if ! (apt-get install -yq azure-cli${AZ_VERSION}); then
        rm -f /etc/apt/sources.list.d/azure-cli.list
        return 1
    fi
}

install_using_pip_strategy() {
    local ver=""
    if [ "${AZ_VERSION}" = "latest" ] || [ "${AZ_VERSION}" = "lts" ] || [ "${AZ_VERSION}" = "stable" ]; then
        # Empty, meaning grab the "latest" in the apt repo
        ver=""
    else
        ver="==${AZ_VERSION}"
    fi

    # Temprary quick fix for https://github.com/devcontainers/features/issues/624
    if [ "${INSTALL_USING_PYTHON}" = "true" ]; then
        install_with_complete_python_installation "${ver}" || install_with_pipx "${ver}" || return 1
    else
        install_with_pipx "${ver}" || install_with_complete_python_installation "${ver}" || return 1
    fi
}

install_with_pipx() {
    echo "(*) Attempting to install globally with pipx..."
    local ver="$1"
    export 
    local 

    if ! type pipx > /dev/null 2>&1; then
        echo "(*) Installing pipx..."
        check_packages pipx
        pipx ensurepath # Ensures PIPX_BIN_DIR is on the PATH
    fi

    PIPX_HOME="/usr/local/pipx" \
    PIPX_BIN_DIR=/usr/local/bin \
    pipx install azure-cli${ver}

    echo "(*) Finished installing globally with pipx."
}

install_with_complete_python_installation() {
    local ver="$1"
    echo "(*) No pre-built binaries available in apt-cache. Installing via pip3."
    if ! dpkg -s python3-minimal python3-pip libffi-dev python3-venv > /dev/null 2>&1; then
        apt_get_update
        apt-get -y install python3-minimal python3-pip libffi-dev python3-venv
    fi
    export PIPX_HOME=/usr/local/pipx
    mkdir -p ${PIPX_HOME}
    export PIPX_BIN_DIR=/usr/local/bin
    export PYTHONUSERBASE=/tmp/pip-tmp
    export PIP_CACHE_DIR=/tmp/pip-tmp/cache
    pipx_bin=pipx
    if ! type pipx > /dev/null 2>&1; then
        pip3 install --disable-pip-version-check --no-cache-dir --user pipx
        pipx_bin=/tmp/pip-tmp/bin/pipx
    fi

    set +e
        ${pipx_bin} install --pip-args '--no-cache-dir --force-reinstall' -f azure-cli${ver}

        # Fail gracefully
        if [ "$?" != 0 ]; then
            echo "Could not install azure-cli${ver} via pip3"
            rm -rf /tmp/pip-tmp
            return 1
        fi
    set -e
}

export DEBIAN_FRONTEND=noninteractive

# See if we're on x86_64 and if so, install via apt-get, otherwise use pip3
echo "(*) Installing Azure CLI..."
. /etc/os-release
architecture="$(dpkg --print-architecture)"
CACHED_AZURE_VERSION="${AZ_VERSION}" # In case we need to fallback to pip and the apt path has modified the AZ_VERSION variable.
if [[ "${AZCLI_ARCHIVE_ARCHITECTURES}" = *"${architecture}"* ]] && [[  "${AZCLI_ARCHIVE_VERSION_CODENAMES}" = *"${VERSION_CODENAME}"* ]]; then
    install_using_apt || use_pip="true"
else
    use_pip="true"
fi

if [ "${use_pip}" = "true" ]; then
    AZ_VERSION=${CACHED_AZURE_VERSION}
    install_using_pip_strategy

    if [ "$?" != 0 ]; then
        echo "Please provide a valid version for your distribution ${ID} ${VERSION_CODENAME} (${architecture})."
        echo
        echo "Valid versions in current apt-cache"
        apt-cache madison azure-cli | awk -F"|" '{print $2}' | grep -oP '^(.+:)?\K.+'
        exit 1
    fi
fi

# If Azure CLI extensions are requested, loop through and install 
if [ ${#AZ_EXTENSIONS[@]} -gt 0 ]; then
    echo "Installing Azure CLI extensions: ${AZ_EXTENSIONS}"
    extensions=(`echo ${AZ_EXTENSIONS} | tr ',' ' '`)
    for i in "${extensions[@]}"
    do
        echo "Installing ${i}"
        su ${_REMOTE_USER} -c "az extension add --name ${i} -y" || continue
    done
fi

if [ "${AZ_INSTALLBICEP}" = "true" ]; then
    # Install dependencies
    check_packages apt-transport-https curl 
    
    # Properly install Azure Bicep based on current architecture
    # The `az bicep install` command installs the linux-x64 binary even on arm64 devcontainers
    # The `az bicep install --target-platform` could be a solution; however, linux-arm64 is not an allowed value for this argument yet
    # Manually installing Bicep and moving to the appropriate directory where az expects it to be
    
    if [ "${architecture}" = "arm64" ]; then
        curl -Lo bicep https://github.com/Azure/bicep/releases/latest/download/bicep-linux-arm64
    else 
        curl -Lo bicep https://github.com/Azure/bicep/releases/latest/download/bicep-linux-x64
    fi
    
    chmod +x ./bicep
    mv ./bicep /usr/local/bin/bicep

    # Add a symlink so bicep can be accessed as a standalone executable or as part of az
    mkdir -p ${_REMOTE_USER_HOME}/.azure/bin
    chown -hR ${_REMOTE_USER}:${_REMOTE_USER} ${_REMOTE_USER_HOME}/.azure
    ln -s /usr/local/bin/bicep ${_REMOTE_USER_HOME}/.azure/bin/bicep
fi

# Clean up
rm -rf /var/lib/apt/lists/*

echo "Done!"