Skip to content
Open
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
174 changes: 164 additions & 10 deletions scripts/release/rpm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,26 @@ _Enterprise Linux:_
```bash
docker build --target build-env -f ./scripts/release/rpm/centos7.dockerfile -t azure/azure-cli:centos7-builder .
```
_Fedora:_
_Fedora 44 (local build, not production publication):_

```bash
docker build --target build-env -f ./scripts/release/rpm/fedora.dockerfile -t azure/azure-cli:fedora29-builder .
CLI_VERSION=$(sed -n 's/^__version__ = "\(.*\)"/\1/p' src/azure-cli/azure/cli/__main__.py)
docker build --target build-env \
--build-arg image=registry.fedoraproject.org/fedora:44 \
--build-arg cli_version="$CLI_VERSION" --build-arg python_package=python3 \
-f ./scripts/release/rpm/fedora.dockerfile -t azure/azure-cli:fedora44-builder .
```

Fedora 44's native `python3` package provides Python 3.14 and `/usr/bin/python3`.
Use `python3-devel` for building and `python3-pip` (`pip3`) for the package tests,
not the UBI matrix's `python3.12`/`pip3.12` selectors. The Python libraries require
`python-pip-wheel`, which supplies the virtual environment's pip bootstrap.
These package names and executable paths are present in the
[Fedora 44 RPM metadata](https://dl.fedoraproject.org/pub/fedora/linux/releases/44/Everything/x86_64/os/Packages/p/).
The [Fedora image manifest](https://registry.fedoraproject.org/v2/fedora/manifests/44)
includes both `amd64` and `arm64`; a successful build and package run on each
architecture is still required.

_Azure Linux:_

```bash
Expand All @@ -25,7 +39,7 @@ docker build --target build-env --build-arg image=mcr.microsoft.com/azurelinux/b
-f ./scripts/release/rpm/azurelinux.dockerfile -t azure/azure-cli:azurelinux3-builder .
```

After several minutes, this will have created a Docker image named `azure/azure-cli:centos7-builder` containing an
After several minutes, this will have created the selected Docker image containing an
unsigned `.rpm` built from the current contents of your azure-cli directory. To extract the build product from the image
you can run the following command:

Expand All @@ -34,9 +48,13 @@ _Enterprise Linux:_
docker run azure/azure-cli:centos7-builder cat /root/rpmbuild/RPMS/x86_64/azure-cli-dev-1.el7.x86_64.rpm > ./bin/azure-cli-dev-1.el7.x86_64.rpm
```

_Fedora:_
_Fedora 44:_
```bash
docker run azure/azure-cli:fedora29-builder cat /root/rpmbuild/RPMS/x86_64/azure-cli-dev-1.fc29.x86_64.rpm > ./bin/azure-cli-dev-1.fc29.x86_64.rpm
id=$(docker create azure/azure-cli:fedora44-builder)
docker cp "$id:/out/." ./bin/
docker rm "$id"
# Native amd64 output: azure-cli-${CLI_VERSION}-1.fc44.x86_64.rpm
# Native arm64 output: azure-cli-${CLI_VERSION}-1.fc44.aarch64.rpm
```

_Azure Linux:_
Expand All @@ -48,19 +66,26 @@ docker run azure/azure-cli:azurelinux-builder cat /root/rpmbuild/RPMS/x86_64/azu
docker run azure/azure-cli:azurelinux3-builder cat /usr/src/azl/RPMS/x86_64/azure-cli-dev-1.azl3.x86_64.rpm > ./bin/azure-cli-dev-1.azl3.x86_64.rpm
```

This launches a container running from the image built and tagged by the previous command, prints the contents of the
built package to standard out, and pipes it to a file on your host machine.
The `docker run ... cat` commands copy a specific package through standard output.
The Fedora example copies `/out/` without assuming the native RPM architecture.

### Additional Build Flags

`--build-arg cli_version={your version string}`

This will allow you to name your build. If not specified, the value "dev" is assumed.
Use the CLI source version as above for release builds. The package tests require
the reported CLI version to match the RPM version, except for the default `dev`
label, which does not change the CLI's embedded source version. Development builds
still run the version command and all other package checks.

`--build-arg tag={centos/fedora version}`
`--build-arg image={container image}`

RPMs must be built using a Red Hat distro or derivative. By default, this build uses CentOS7, but one could easily tweak
it to include slightly different packages for distribution.
The Fedora Dockerfile defaults to `registry.fedoraproject.org/fedora:44`. Its
`python_package` argument defaults to `python3`, and `python_cmd` defaults to the
selected package name. Both can be overridden when building directly with Docker.
The existing `pipeline.sh` selects the image and Python package through `IMAGE`
and `PYTHON_PACKAGE`; its Fedora defaults therefore use the same Python executable.

### Verification

Expand All @@ -73,6 +98,13 @@ On a machine with Docker, execute the following command from the root directory
docker build -f ./scripts/release/rpm/centos7.dockerfile -t azure/azure-cli:centos7 .
```

For Fedora 44, run both installation smoke checks (`az --version` and `az self-test`):

```bash
docker build --build-arg cli_version="$CLI_VERSION" \
-f ./scripts/release/rpm/fedora.dockerfile -t azure/azure-cli:fedora44 .
```

If you had previously followed this instructions above for building an RPM package, this should finish very quickly.
Otherwise, it'll take a few minutes to create an image with a copy of the azure-cli installed.
> Note: The image that is created by this command does not contain the source code of the azure-cli.
Expand Down Expand Up @@ -101,6 +133,128 @@ To remove:
sudo rpm -e azure-cli
```

### Fedora 44 local package tests

Use a disposable, normal Git checkout on each native Linux architecture. The
existing test script builds test wheels with `scripts/ci/build.sh`, which requires
Git metadata and modifies the mounted checkout. It cannot run from a source-only
snapshot. After extracting the unsigned RPM to `bin/` as above:

```bash
RPM_TEST_RESULTS=$(mktemp -d)
docker run --rm \
-v "$(pwd):/azure-cli" -v "$(pwd)/bin:/mnt/rpm:ro" \
-v "$RPM_TEST_RESULTS:/azure_cli_test_result" \
-e RPM_NAME="azure-cli-${CLI_VERSION}-1.fc44.*.rpm" \
-e PYTHON_PACKAGE=python3 -e PYTHON_CMD=python3 -e PIP_CMD=pip3 \
registry.fedoraproject.org/fedora:44 \
bash /azure-cli/scripts/release/rpm/test_rpm_in_docker.sh
```

This runs the existing self-test, version smoke check and package suites. The
package checks also require the installed RPM to match the requested version and
distro suffix, have the native architecture, report the same CLI version (except
for the `dev` label), and declare an installed dependency on the selected Python
package. The local installer's `--nogpgcheck` is only for unsigned build artifacts;
it is not suitable for production-feed acceptance.

Credential-free regression checks for the packaging scripts use the existing
Python unittest tooling:

```bash
python -m unittest discover -s scripts/release/rpm/tests
```

These checks do not build an RPM or replace Fedora/UBI/Azure Linux container CI.

### Fedora 44 CI and release-owner gates

The main `azure-pipelines.yml` currently has no Fedora 44 entry in either
`BuildRpmPackages` or `TestRpmPackage`. The Dockerfile change alone does not add
those jobs. After release-owner approval, the paired entries under the existing
architecture loops must use the same image, artifact and Python selection:
Comment on lines +172 to +175

```yaml
# BuildRpmPackages matrix
Fedora 44 ${{ arch.name }}:
dockerfile: fedora
image: registry.fedoraproject.org/fedora:44
artifact: rpm-fedora44-${{ arch.value }}
python_package: python3
pool: ${{ arch.pool }}

# TestRpmPackage matrix
Fedora 44 ${{ arch.name }}:
artifact: rpm-fedora44-${{ arch.value }}
distro: fc44
image: registry.fedoraproject.org/fedora:44
python_package: python3
python_cmd: python3
pip_cmd: pip3
pool: ${{ arch.pool }}
```

Keep the existing build/test dependencies, trigger conditions, UBI entries and
separate Azure Linux jobs unchanged. `rpm-fedora44-amd64` must contain the
`1.fc44.x86_64.rpm` artifact, and `rpm-fedora44-arm64` the `1.fc44.aarch64.rpm`
artifact; the install jobs must download the corresponding artifact.

`PublishPipelineArtifact` only uploads an Azure Pipelines artifact. No approved
Fedora 44 signing/production publication mapping is defined in this repository.
The `/scripts/` CODEOWNERS teams can route a release-owner review, but code
ownership is not approval to publish to a feed. The release owner must confirm
the supported destination and signing key, onboard the artifact through the
existing approved signing/publication process, and complete post-publication
acceptance. Do not infer a destination from an issue URL or substitute a
RHEL/CentOS feed.

Local builds, CI artifacts and even successful local installations do not
establish Fedora 44 production availability or Microsoft support.

### Fedora 44 repository acceptance (after approved publication)

The release owner must first configure the approved repository and trusted signing
keys in a clean Fedora 44 container on each native architecture. Do not use a
container that already has the Azure CLI RPM installed or an `az` executable on
`PATH`. From the mounted repository root, run as root:

```bash
# Use the approved repository ID and the exact version the owner published.
RPM_REPOSITORY_ID="${APPROVED_REPO_ID:?Set the approved Fedora repository ID}" \
CLI_VERSION="${EXPECTED_VERSION:?Set the published X.Y.Z CLI version}" \
RPM_RELEASE=1.fc44 \
bash scripts/release/rpm/verify_rpm_in_docker.sh
```

`RPM_REPOSITORY_ID` selects an existing repository, not a URL. `CLI_VERSION` must be
an exact `X.Y.Z` version, not `dev` or `latest`; set `RPM_RELEASE` to the published
numeric release with the `.fc44` suffix. The verifier refreshes available metadata
restricted to the selected repository, then installs that exact version, release
and native architecture from it. Other configured repositories may satisfy
dependencies, but cannot substitute their Azure CLI package.

Package signature checking is required, and any configured repository-metadata
signature policy is preserved. This mode does not configure a repository or choose
signing keys. Never copy the local installer's `--nogpgcheck` option into this
acceptance procedure.

| Scenario | Required result |
| --- | --- |
| Requested version, release and native architecture are available from the approved repository | Install succeeds; installed RPM metadata, DNF origin, `/usr/bin/az` ownership and reported CLI version match; `az --version` and `az self-test` succeed. |
| Selected repository has no package, only an older version, or the wrong release/architecture | Fail, even if another enabled repository has a matching package. |
| Package is available only from Fedora's downstream repository or another unrelated feed | Fail without falling back to that feed. |
| Azure CLI RPM or an `az` executable is already present | Fail; retry in a fresh container. |
| Package signature checking or an enabled metadata-signature check fails | Fail; do not bypass the signature check. |
| Installed package origin/version/ownership differs, or either smoke check fails | Fail. |

The script neither starts a release nor waits for a package to appear. Run it only
after the release owner confirms approved publication. A successful local build
or unsigned artifact installation cannot satisfy this acceptance gate.

Leaving `RPM_REPOSITORY_ID` unset preserves the existing generic-yum verification
path; that path is not Fedora 44 repository acceptance. An explicitly empty
selector is rejected rather than silently selecting the legacy path.

Links
-----

Expand Down
8 changes: 5 additions & 3 deletions scripts/release/rpm/fedora.dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
ARG image=fedora:35
ARG image=registry.fedoraproject.org/fedora:44

FROM ${image} AS build-env
ARG cli_version=dev
ARG python_package=python3
ARG python_cmd=${python_package}

RUN dnf update -y
RUN dnf install -y wget rpm-build gcc libffi-devel ${python_package}-devel openssl-devel make bash coreutils diffutils patch dos2unix perl
Expand All @@ -12,7 +13,7 @@ WORKDIR /azure-cli
COPY . .

RUN dos2unix ./scripts/release/rpm/azure-cli.spec && \
REPO_PATH=$(pwd) CLI_VERSION=$cli_version PYTHON_PACKAGE=$python_package PYTHON_CMD=python3 \
REPO_PATH=$(pwd) CLI_VERSION=$cli_version PYTHON_PACKAGE=$python_package PYTHON_CMD=$python_cmd \
Comment on lines 15 to +16
rpmbuild -v -bb --clean scripts/release/rpm/azure-cli.spec && \
cp /root/rpmbuild/RPMS/*/azure-cli-${cli_version}-1.*.rpm /azure-cli-dev.rpm && \
mkdir /out && cp /root/rpmbuild/RPMS/*/azure-cli-${cli_version}-1.*.rpm /out/
Expand All @@ -21,4 +22,5 @@ FROM ${image} AS execution-env

COPY --from=build-env /azure-cli-dev.rpm ./
RUN dnf install -y ./azure-cli-dev.rpm && \
az --version
az --version && \
az self-test
2 changes: 1 addition & 1 deletion scripts/release/rpm/test_rpm_in_docker.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bash

# This script should be run in a ubi8, ubi9 docker.
# This script should be run in a UBI or Fedora container.
set -exv

export USERNAME=azureuser
Expand Down
86 changes: 61 additions & 25 deletions scripts/release/rpm/test_rpm_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,67 @@
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

import fnmatch
import os
import sys
import subprocess
import sys


def verify_rpm_installation(rpm_name=None, python_package=None):
metadata = subprocess.check_output(
['rpm', '-q', '--queryformat', '%{NAME} %{VERSION} %{RELEASE} %{ARCH}', 'azure-cli'],
text=True).split()
if len(metadata) != 4 or metadata[0] != 'azure-cli':
raise RuntimeError(f'Expected one installed azure-cli RPM, got: {metadata}')
name, version, release, arch = metadata
filename = f'{name}-{version}-{release}.{arch}.rpm'
if rpm_name and not fnmatch.fnmatchcase(filename, rpm_name):
raise RuntimeError(f'Installed RPM {filename} does not match the requested artifact {rpm_name}')

native_arch = subprocess.check_output(['rpm', '--eval', '%{_arch}'], text=True).strip()
if arch != native_arch:
raise RuntimeError(f'Installed RPM architecture {arch} does not match {native_arch}')

cli_version = subprocess.check_output(
['az', 'version', '--query', '"azure-cli"', '--output', 'tsv'], text=True).strip()
# Development builds label the RPM "dev" without changing the embedded CLI version.
if version != 'dev' and cli_version != version:
raise RuntimeError(f'Installed CLI version {cli_version} does not match RPM version {version}')

if python_package:
requires = subprocess.check_output(['rpm', '-q', '--requires', 'azure-cli'], text=True)
if not any(line.split()[0] == python_package for line in requires.splitlines() if line.strip()):
raise RuntimeError(f'Installed RPM does not require the selected Python package {python_package}')
subprocess.check_call(['rpm', '-q', python_package])


def main():
verify_rpm_installation(os.environ.get('RPM_NAME'), os.environ.get('PYTHON_PACKAGE'))
python_version = os.listdir('/usr/lib64/az/lib/')[0]
root_dir = f'/usr/lib64/az/lib/{python_version}/site-packages/azure/cli/command_modules'
mod_list = [mod for mod in sorted(os.listdir(root_dir)) if os.path.isdir(os.path.join(root_dir, mod)) and mod != '__pycache__']

pytest_base_cmd = f'PYTHONPATH=/usr/lib64/az/lib/{python_version}/site-packages python -m pytest -v --forked -p no:warnings --log-level=WARN'
pytest_parallel_cmd = '{} -n logical'.format(pytest_base_cmd)

# cloud: https://github.com/Azure/azure-cli/pull/14994
# appservice: https://github.com/Azure/azure-cli/pull/19810
# iot, resource, azure-cli-core: https://github.com/Azure/azure-cli/pull/26176
serial_test_modules = ['botservice', 'network', 'cloud', 'appservice', 'iot', 'resource']

for mod_name in mod_list:
cmd = '{} --junit-xml /azure_cli_test_result/{}.xml --pyargs azure.cli.command_modules.{}'.format(
pytest_base_cmd if mod_name in serial_test_modules else pytest_parallel_cmd, mod_name, mod_name)
print('Running:', cmd, flush=True)
exit_code = subprocess.call(cmd, shell=True)
if exit_code == 5:
print('No tests found for {}'.format(mod_name))
elif exit_code != 0:
sys.exit(exit_code)

exit_code = subprocess.call(['{} --junit-xml /azure_cli_test_result/azure-cli-core.xml --pyargs azure.cli.core'.format(pytest_base_cmd)], shell=True)
sys.exit(exit_code)


python_version = os.listdir('/usr/lib64/az/lib/')[0]
root_dir = f'/usr/lib64/az/lib/{python_version}/site-packages/azure/cli/command_modules'
mod_list = [mod for mod in sorted(os.listdir(root_dir)) if os.path.isdir(os.path.join(root_dir, mod)) and mod != '__pycache__']

pytest_base_cmd = f'PYTHONPATH=/usr/lib64/az/lib/{python_version}/site-packages python -m pytest -v --forked -p no:warnings --log-level=WARN'
pytest_parallel_cmd = '{} -n logical'.format(pytest_base_cmd)

# cloud: https://github.com/Azure/azure-cli/pull/14994
# appservice: https://github.com/Azure/azure-cli/pull/19810
# iot, resource, azure-cli-core: https://github.com/Azure/azure-cli/pull/26176
serial_test_modules = ['botservice', 'network', 'cloud', 'appservice', 'iot', 'resource']

for mod_name in mod_list:
cmd = '{} --junit-xml /azure_cli_test_result/{}.xml --pyargs azure.cli.command_modules.{}'.format(
pytest_base_cmd if mod_name in serial_test_modules else pytest_parallel_cmd, mod_name, mod_name)
print('Running:', cmd, flush=True)
exit_code = subprocess.call(cmd, shell=True)
if exit_code == 5:
print('No tests found for {}'.format(mod_name))
elif exit_code != 0:
sys.exit(exit_code)

exit_code = subprocess.call(['{} --junit-xml /azure_cli_test_result/azure-cli-core.xml --pyargs azure.cli.core'.format(pytest_base_cmd)], shell=True)
sys.exit(exit_code)
if __name__ == '__main__':
main()
Loading
Loading