From f37aa3aa3787e9859d6a47b7e9943c96b7bc1536 Mon Sep 17 00:00:00 2001 From: Zelin Wang Date: Wed, 31 Dec 2025 16:08:28 +0800 Subject: [PATCH 1/8] build snap --- azure-pipelines.yml | 7 + scripts/release/snap/azure-pipelines-snap.yml | 149 ++++++++++++ scripts/release/snap/build-snap.sh | 215 ++++++++++++++++++ scripts/release/snap/snapcraft.yaml.template | 61 +++++ 4 files changed, 432 insertions(+) create mode 100644 scripts/release/snap/azure-pipelines-snap.yml create mode 100644 scripts/release/snap/build-snap.sh create mode 100644 scripts/release/snap/snapcraft.yaml.template diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f2fcdaca15e..142be95f8f8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -501,6 +501,13 @@ jobs: pip install --find-links ./ azure_cli-$CLI_VERSION*whl && az self-test && az --version && sleep 5 displayName: 'Test pip Install' + +# Snap Package Build +- template: scripts/release/snap/azure-pipelines-snap.yml + parameters: + multi_arch: false + publish_to_store: false + - job: TestCore displayName: Unit Test for Core timeoutInMinutes: 10 diff --git a/scripts/release/snap/azure-pipelines-snap.yml b/scripts/release/snap/azure-pipelines-snap.yml new file mode 100644 index 00000000000..601dec2228c --- /dev/null +++ b/scripts/release/snap/azure-pipelines-snap.yml @@ -0,0 +1,149 @@ +# Azure CLI Snap Build Pipeline Template +# +# Builds snap package from wheel artifacts +# +# Usage in azure-pipelines.yml: +# - template: scripts/release/snap/azure-pipelines-snap.yml +# parameters: +# multi_arch: true + +parameters: + - name: multi_arch + type: boolean + default: true + - name: publish_to_store + type: boolean + default: false + - name: release_channel + type: string + default: 'edge' + +jobs: + - job: BuildSnapPackage + displayName: Build Snap Package + dependsOn: BuildPythonWheel + condition: and(succeeded(), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'Manual', 'Schedule')) + pool: + name: $(ubuntu_pool) + timeoutInMinutes: 90 + + steps: + - checkout: self + fetchDepth: 1 + + - task: DownloadPipelineArtifact@1 + displayName: 'Download Metadata' + inputs: + TargetPath: '$(Build.ArtifactStagingDirectory)/metadata' + artifactName: metadata + + - task: DownloadPipelineArtifact@1 + displayName: 'Download PyPI Packages' + inputs: + TargetPath: '$(Build.ArtifactStagingDirectory)/pypi' + artifactName: pypi + + - task: Bash@3 + displayName: 'Install Snapcraft' + inputs: + targetType: 'inline' + script: | + set -e + sudo snap install snapcraft --classic + + # LXD only needed for single-arch local build + if [ "${{ parameters.multi_arch }}" != "true" ]; then + sudo snap install lxd + sudo lxd init --auto + sudo usermod -aG lxd $(whoami) + fi + + - task: Bash@3 + displayName: 'Build Snap Package' + env: + SNAPCRAFT_STORE_CREDENTIALS: $(SNAPCRAFT_STORE_CREDENTIALS) + inputs: + targetType: 'inline' + script: | + set -e + cd scripts/release/snap + chmod +x build-snap.sh + + CLI_VERSION=$(cat $(Build.ArtifactStagingDirectory)/metadata/version) + echo "Building snap for Azure CLI version: $CLI_VERSION" + + export WHEEL_DIR="$(Build.ArtifactStagingDirectory)/pypi" + export OUTPUT_DIR="$(Build.ArtifactStagingDirectory)/snap" + + if [ "${{ parameters.multi_arch }}" = "true" ]; then + ./build-snap.sh "$CLI_VERSION" --multi-arch + else + sg lxd -c "./build-snap.sh $CLI_VERSION" + fi + + - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 + displayName: 'SBOM' + condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/release') + inputs: + BuildDropPath: $(Build.ArtifactStagingDirectory)/snap + + - task: PublishPipelineArtifact@0 + displayName: 'Publish Artifact: snap' + inputs: + TargetPath: $(Build.ArtifactStagingDirectory)/snap + ArtifactName: snap + + - ${{ if eq(parameters.publish_to_store, true) }}: + - task: Bash@3 + displayName: 'Publish to Snap Store' + env: + SNAPCRAFT_STORE_CREDENTIALS: $(SNAPCRAFT_STORE_CREDENTIALS) + inputs: + targetType: 'inline' + script: | + cd $(Build.ArtifactStagingDirectory)/snap + for snap_file in *.snap; do + echo "Uploading $snap_file to ${{ parameters.release_channel }}..." + snapcraft upload "$snap_file" --release=${{ parameters.release_channel }} + done + + - job: TestSnapPackage + displayName: Test Snap Package + dependsOn: BuildSnapPackage + condition: succeeded() + pool: + name: $(ubuntu_pool) + + steps: + - task: DownloadPipelineArtifact@1 + displayName: 'Download Metadata' + inputs: + TargetPath: '$(Build.ArtifactStagingDirectory)/metadata' + artifactName: metadata + + - task: DownloadPipelineArtifact@1 + displayName: 'Download Snap Package' + inputs: + TargetPath: '$(Build.ArtifactStagingDirectory)/snap' + artifactName: snap + + - task: Bash@3 + displayName: 'Test Snap Installation' + inputs: + targetType: 'inline' + script: | + set -e + CLI_VERSION=$(cat $(Build.ArtifactStagingDirectory)/metadata/version) + + # Test amd64 snap (agent is amd64) + SNAP_FILE=$(Build.ArtifactStagingDirectory)/snap/azure-cli_${CLI_VERSION}_amd64.snap + + echo "Installing snap: $SNAP_FILE" + sudo snap install "$SNAP_FILE" --dangerous + + echo "Testing Azure CLI..." + azure-cli.az --version + azure-cli.az self-test + + echo "Snap info:" + snap info azure-cli \ No newline at end of file diff --git a/scripts/release/snap/build-snap.sh b/scripts/release/snap/build-snap.sh new file mode 100644 index 00000000000..6360a9bd448 --- /dev/null +++ b/scripts/release/snap/build-snap.sh @@ -0,0 +1,215 @@ +#!/bin/bash +# Azure CLI Snap Build Script +# Build snap package from wheel files +# +# Usage: ./build-snap.sh [VERSION] [--multi-arch] +# +# Environment variables: +# WHEEL_DIR Directory containing wheel files (default: /mnt/pypi) +# OUTPUT_DIR Output directory for snap file (default: /mnt/output) + +set -e + +VERSION="" +MULTI_ARCH=false +WHEEL_DIR="${WHEEL_DIR:-/mnt/pypi}" +OUTPUT_DIR="${OUTPUT_DIR:-/mnt/output}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BUILD_DIR="${SCRIPT_DIR}/snap-build" + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --multi-arch) + MULTI_ARCH=true + shift + ;; + help|--help|-h) + echo "Azure CLI Snap Build Script" + echo "" + echo "Usage: $0 [VERSION] [--multi-arch]" + echo "" + echo "Arguments:" + echo " VERSION CLI version (auto-detected from wheel if not specified)" + echo " --multi-arch Build for amd64 and arm64 using Launchpad remote-build" + echo "" + echo "Environment Variables:" + echo " WHEEL_DIR Directory containing wheel files (default: /mnt/pypi)" + echo " OUTPUT_DIR Output directory for snap file (default: /mnt/output)" + echo "" + echo "Examples:" + echo " $0 # Build amd64 only" + echo " $0 --multi-arch # Build amd64 + arm64" + echo " $0 2.81.0 --multi-arch # Build specific version, multi-arch" + exit 0 + ;; + *) + if [[ "$1" =~ ^[0-9]+\.[0-9]+ ]]; then + VERSION="$1" + fi + shift + ;; + esac +done + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } +log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } +log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +log_error() { echo -e "${RED}[ERROR]${NC} $1"; } + +# Get version from wheel filename +get_version() { + if [ -n "$VERSION" ]; then + echo "$VERSION" + return + fi + + local wheel_file + wheel_file=$(ls -1 "${WHEEL_DIR}"/azure_cli-*.whl 2>/dev/null | head -1) + if [ -n "$wheel_file" ]; then + basename "$wheel_file" | sed -E 's/azure_cli-([0-9]+\.[0-9]+\.[0-9]+).*/\1/' + return + fi + + log_error "Cannot detect version. Please specify: $0 2.81.0" + exit 1 +} + +# Validate wheel files exist +validate_wheels() { + log_info "Validating wheel files in ${WHEEL_DIR}..." + + if [ ! -d "$WHEEL_DIR" ]; then + log_error "Wheel directory not found: $WHEEL_DIR" + exit 1 + fi + + local wheel_count + wheel_count=$(ls -1 "${WHEEL_DIR}"/*.whl 2>/dev/null | wc -l) + if [ "$wheel_count" -eq 0 ]; then + log_error "No wheel files found in $WHEEL_DIR" + exit 1 + fi + + log_info "Found $wheel_count wheel files" +} + +# Create snapcraft.yaml from template +create_snapcraft_yaml() { + local version=$1 + + log_info "Creating snapcraft.yaml for version $version..." + + mkdir -p "$BUILD_DIR/scripts" + mkdir -p "$BUILD_DIR/wheels" + + # Copy wheel files + cp "${WHEEL_DIR}"/*.whl "$BUILD_DIR/wheels/" + + # Wrapper script + cat > "$BUILD_DIR/scripts/az-wrapper" << 'WRAPPER' +#!/bin/bash +export PYTHONPATH="${SNAP}/opt/az/lib/python3.11/site-packages" +export PATH="${SNAP}/opt/az/bin:${PATH}" +export AZ_INSTALLER="SNAP" +exec "${SNAP}/opt/az/bin/python3" -m azure.cli "$@" +WRAPPER + chmod +x "$BUILD_DIR/scripts/az-wrapper" + + # Determine architecture section + local arch_section + if [ "$MULTI_ARCH" = true ]; then + arch_section="architectures:\\ + - build-on: [amd64]\\ + build-for: [amd64]\\ + - build-on: [arm64]\\ + build-for: [arm64]" + else + arch_section="architectures:\\ + - build-on: amd64" + fi + + # Generate snapcraft.yaml from template + # Template variables: + # ${CLI_VERSION} - Azure CLI version (e.g., 2.81.0) + # ${ARCHITECTURES} - Architecture configuration block + sed -e "s/\${CLI_VERSION}/${version}/g" \ + -e "s/\${ARCHITECTURES}/${arch_section}/" \ + "${SCRIPT_DIR}/snapcraft.yaml.template" > "$BUILD_DIR/snapcraft.yaml" + + log_success "snapcraft.yaml created from template" +} + +# Build snap +build_snap() { + local version=$1 + cd "$BUILD_DIR" + + if [ "$MULTI_ARCH" = true ]; then + log_info "Building for amd64 + arm64 using Launchpad remote-build..." + log_warn "This requires SNAPCRAFT_STORE_CREDENTIALS and may take 10-20 minutes" + + if [ -z "$SNAPCRAFT_STORE_CREDENTIALS" ]; then + log_error "SNAPCRAFT_STORE_CREDENTIALS not set. Required for remote-build." + exit 1 + fi + + snapcraft remote-build --launchpad-accept-public-upload + + log_success "Multi-arch build completed!" + ls -la *.snap 2>/dev/null || true + else + log_info "Building for local architecture (amd64)..." + + # Clean old build + if [ -d "parts" ] || [ -d "stage" ] || [ -d "prime" ]; then + snapcraft clean 2>/dev/null || true + fi + + snapcraft --use-lxd --verbosity=verbose + + local snap_file + snap_file=$(ls -1 *.snap 2>/dev/null | head -1) + + if [ -n "$snap_file" ]; then + log_success "Build successful: $snap_file" + log_info "Size: $(du -h "$snap_file" | cut -f1)" + else + log_error "Build failed - no snap file generated" + exit 1 + fi + fi + + # Copy to output directory + mkdir -p "$OUTPUT_DIR" + cp *.snap "$OUTPUT_DIR/" 2>/dev/null || true + log_success "Copied snap files to: $OUTPUT_DIR" +} + +# Main +main() { + log_info "Azure CLI Snap Build Script" + log_info "============================" + + validate_wheels + + VERSION=$(get_version) + log_info "CLI Version: $VERSION" + log_info "Multi-arch: $MULTI_ARCH" + log_info "Wheel Dir: $WHEEL_DIR" + log_info "Output Dir: $OUTPUT_DIR" + + create_snapcraft_yaml "$VERSION" + build_snap "$VERSION" + + log_success "Build completed successfully!" +} + +main \ No newline at end of file diff --git a/scripts/release/snap/snapcraft.yaml.template b/scripts/release/snap/snapcraft.yaml.template new file mode 100644 index 00000000000..0c1dec096bd --- /dev/null +++ b/scripts/release/snap/snapcraft.yaml.template @@ -0,0 +1,61 @@ +name: azure-cli +version: "${CLI_VERSION}" +title: Azure CLI +summary: Microsoft Azure Command-Line Interface +description: | + The Azure CLI is a command-line tool for managing Azure resources. + It provides a consistent interface across Azure services with features + including resource management, scripting support, and cross-platform + compatibility. + + Documentation: https://docs.microsoft.com/cli/azure/ + +license: MIT +contact: https://github.com/Azure/azure-cli/issues +issues: https://github.com/Azure/azure-cli/issues +source-code: https://github.com/Azure/azure-cli +website: https://learn.microsoft.com/cli/azure/ + +grade: stable +confinement: strict +base: core22 + +${ARCHITECTURES} + +apps: + az: + command: bin/az-wrapper + plugs: + - home + - network + - network-bind + - ssh-keys + - removable-media + +parts: + wrapper: + plugin: dump + source: scripts/ + organize: + az-wrapper: bin/az-wrapper + + azure-cli: + plugin: python + source: wheels/ + python-packages: + - ./azure_cli-${CLI_VERSION}-py3-none-any.whl + build-packages: + - python3-dev + - libffi-dev + - libssl-dev + stage-packages: + - python3 + - python3-pip + - libffi8 + - libssl3 + override-build: | + craftctl default + ${CRAFT_PART_INSTALL}/bin/pip3 install --no-index --find-links=${CRAFT_PART_SRC} ${CRAFT_PART_SRC}/*.whl + organize: + bin: opt/az/bin + lib: opt/az/lib From 106b896c41caf3649eecb31c8595a9b1e8757753 Mon Sep 17 00:00:00 2001 From: ZelinWang Date: Wed, 31 Dec 2025 16:32:13 +0800 Subject: [PATCH 2/8] Update scripts/release/snap/snapcraft.yaml.template Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/release/snap/snapcraft.yaml.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release/snap/snapcraft.yaml.template b/scripts/release/snap/snapcraft.yaml.template index 0c1dec096bd..15d122b5b5f 100644 --- a/scripts/release/snap/snapcraft.yaml.template +++ b/scripts/release/snap/snapcraft.yaml.template @@ -8,7 +8,7 @@ description: | including resource management, scripting support, and cross-platform compatibility. - Documentation: https://docs.microsoft.com/cli/azure/ + Documentation: https://learn.microsoft.com/cli/azure/ license: MIT contact: https://github.com/Azure/azure-cli/issues From 0ea695b5776eaaa2279242dc7318f21fd5a0866d Mon Sep 17 00:00:00 2001 From: ZelinWang Date: Wed, 31 Dec 2025 16:33:28 +0800 Subject: [PATCH 3/8] Update scripts/release/snap/build-snap.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/release/snap/build-snap.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release/snap/build-snap.sh b/scripts/release/snap/build-snap.sh index 6360a9bd448..04d64830f26 100644 --- a/scripts/release/snap/build-snap.sh +++ b/scripts/release/snap/build-snap.sh @@ -44,7 +44,7 @@ while [[ $# -gt 0 ]]; do exit 0 ;; *) - if [[ "$1" =~ ^[0-9]+\.[0-9]+ ]]; then + if [[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then VERSION="$1" fi shift From 1266bd1bcf2b677a52ccd914b00886424a6d1ed5 Mon Sep 17 00:00:00 2001 From: ZelinWang Date: Wed, 31 Dec 2025 16:36:29 +0800 Subject: [PATCH 4/8] Update scripts/release/snap/build-snap.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/release/snap/build-snap.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/release/snap/build-snap.sh b/scripts/release/snap/build-snap.sh index 04d64830f26..ff085b63541 100644 --- a/scripts/release/snap/build-snap.sh +++ b/scripts/release/snap/build-snap.sh @@ -116,7 +116,12 @@ create_snapcraft_yaml() { # Wrapper script cat > "$BUILD_DIR/scripts/az-wrapper" << 'WRAPPER' #!/bin/bash -export PYTHONPATH="${SNAP}/opt/az/lib/python3.11/site-packages" +PYTHON_VERSION="$("${SNAP}/opt/az/bin/python3" - << 'EOF' +import sys +print(f"python{sys.version_info.major}.{sys.version_info.minor}") +EOF +)" +export PYTHONPATH="${SNAP}/opt/az/lib/${PYTHON_VERSION}/site-packages" export PATH="${SNAP}/opt/az/bin:${PATH}" export AZ_INSTALLER="SNAP" exec "${SNAP}/opt/az/bin/python3" -m azure.cli "$@" From bda37a9eabfc9811585fb97efe870943c1c6b9bb Mon Sep 17 00:00:00 2001 From: Zelin Wang Date: Wed, 31 Dec 2025 16:47:59 +0800 Subject: [PATCH 5/8] minor fix --- scripts/release/snap/azure-pipelines-snap.yml | 1 - scripts/release/snap/snapcraft.yaml.template | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/release/snap/azure-pipelines-snap.yml b/scripts/release/snap/azure-pipelines-snap.yml index 601dec2228c..2b9c4b744b6 100644 --- a/scripts/release/snap/azure-pipelines-snap.yml +++ b/scripts/release/snap/azure-pipelines-snap.yml @@ -22,7 +22,6 @@ jobs: - job: BuildSnapPackage displayName: Build Snap Package dependsOn: BuildPythonWheel - condition: and(succeeded(), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'Manual', 'Schedule')) pool: name: $(ubuntu_pool) timeoutInMinutes: 90 diff --git a/scripts/release/snap/snapcraft.yaml.template b/scripts/release/snap/snapcraft.yaml.template index 15d122b5b5f..b3bace71589 100644 --- a/scripts/release/snap/snapcraft.yaml.template +++ b/scripts/release/snap/snapcraft.yaml.template @@ -42,8 +42,6 @@ parts: azure-cli: plugin: python source: wheels/ - python-packages: - - ./azure_cli-${CLI_VERSION}-py3-none-any.whl build-packages: - python3-dev - libffi-dev @@ -55,7 +53,7 @@ parts: - libssl3 override-build: | craftctl default - ${CRAFT_PART_INSTALL}/bin/pip3 install --no-index --find-links=${CRAFT_PART_SRC} ${CRAFT_PART_SRC}/*.whl + ${CRAFT_PART_INSTALL}/bin/pip3 install --find-links=${CRAFT_PART_SRC} ${CRAFT_PART_SRC}/azure_cli*.whl organize: bin: opt/az/bin lib: opt/az/lib From 993f141e8b2f36db1da972a617a1826848c689a0 Mon Sep 17 00:00:00 2001 From: Zelin Wang Date: Wed, 31 Dec 2025 17:25:47 +0800 Subject: [PATCH 6/8] minor fix --- scripts/release/snap/build-snap.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/release/snap/build-snap.sh b/scripts/release/snap/build-snap.sh index ff085b63541..d4b3d59749a 100644 --- a/scripts/release/snap/build-snap.sh +++ b/scripts/release/snap/build-snap.sh @@ -178,7 +178,14 @@ build_snap() { snapcraft clean 2>/dev/null || true fi - snapcraft --use-lxd --verbosity=verbose + # Use --destructive-mode in CI (no LXD container, better network access) + # Use --use-lxd for local builds (isolated environment) + if [ -n "$CI" ] || [ -n "$BUILD_BUILDID" ]; then + log_info "CI environment detected, using destructive mode..." + snapcraft --destructive-mode --verbosity=verbose + else + snapcraft --use-lxd --verbosity=verbose + fi local snap_file snap_file=$(ls -1 *.snap 2>/dev/null | head -1) From fd86627a59c262a6ce80751534f7dfb1cd4995eb Mon Sep 17 00:00:00 2001 From: Zelin Wang Date: Wed, 31 Dec 2025 17:29:31 +0800 Subject: [PATCH 7/8] add log --- scripts/release/snap/build-snap.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/release/snap/build-snap.sh b/scripts/release/snap/build-snap.sh index d4b3d59749a..3c167eb1e3e 100644 --- a/scripts/release/snap/build-snap.sh +++ b/scripts/release/snap/build-snap.sh @@ -203,6 +203,13 @@ build_snap() { mkdir -p "$OUTPUT_DIR" cp *.snap "$OUTPUT_DIR/" 2>/dev/null || true log_success "Copied snap files to: $OUTPUT_DIR" + + # Copy build logs to output directory + local log_dir="$HOME/.local/state/snapcraft/log" + if [ -d "$log_dir" ]; then + cp "$log_dir"/snapcraft-*.log "$OUTPUT_DIR/" 2>/dev/null || true + log_info "Copied build logs to: $OUTPUT_DIR" + fi } # Main From 1fa6c1200f4661f909b3f6e161c598de220cf265 Mon Sep 17 00:00:00 2001 From: Zelin Wang Date: Wed, 7 Jan 2026 10:58:30 +0800 Subject: [PATCH 8/8] update --- scripts/release/snap/snapcraft.yaml.template | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/release/snap/snapcraft.yaml.template b/scripts/release/snap/snapcraft.yaml.template index b3bace71589..fdc59360f32 100644 --- a/scripts/release/snap/snapcraft.yaml.template +++ b/scripts/release/snap/snapcraft.yaml.template @@ -31,6 +31,8 @@ apps: - network-bind - ssh-keys - removable-media + - desktop # Allow opening URLs in browser (for az login) + - browser-support # Browser integration support parts: wrapper: