diff --git a/.pipelines/CosmosDB-Shell-Official.yml b/.pipelines/CosmosDB-Shell-Official.yml index 3432ffc3..2658090e 100644 --- a/.pipelines/CosmosDB-Shell-Official.yml +++ b/.pipelines/CosmosDB-Shell-Official.yml @@ -31,6 +31,10 @@ variables: BuildConfiguration: Release WindowsContainerImage: "onebranch.azurecr.io/windows/ltsc2022/vse2022:latest" # Docker image which is used to build the project https://aka.ms/obpipelines/containers + # Azure Linux is RPM-native and resolves packages from packages.microsoft.com. + # The build containers cannot reach the public Ubuntu archives, so an + # Ubuntu image cannot install the RPM tooling the rpm job needs. + LinuxContainerImage: "mcr.microsoft.com/onebranch/azurelinux/build:3.0" resources: repositories: @@ -822,6 +826,122 @@ extends: publishVstsFeed: "CosmosDB/CosmosDBShell" allowPackageConflicts: true + - job: rpm + displayName: Build RPM packages + pool: + type: linux + variables: + ob_outputDirectory: "$(Build.SourcesDirectory)/out" + ob_artifactBaseName: cosmos_shell_rpm + ob_git_fetchDepth: -1 + steps: + - task: UseDotNet@2 + inputs: + packageType: "sdk" + useGlobalJson: true + performMultiLevelLookup: true + + - script: | + set -euo pipefail + dotnet tool restore --configfile "$(Build.SourcesDirectory)/.pipelines/nuget.config" + package_version="$(dotnet tool run nbgv get-version -v NuGetPackageVersion)" + package_version="${package_version%%+*}" + if [[ ! "$package_version" =~ ^([0-9]+\.[0-9]+\.[0-9]+)(-(.+))?$ ]]; then + echo "Unsupported RPM package version: $package_version" >&2 + exit 1 + fi + + echo "##vso[task.setvariable variable=CosmosDBShell_RpmVersion]${BASH_REMATCH[1]}" + if [[ -n "${BASH_REMATCH[3]:-}" ]]; then + rpm_release="0.${BASH_REMATCH[3]//[^[:alnum:].]/.}" + else + rpm_release="1" + fi + echo "##vso[task.setvariable variable=CosmosDBShell_RpmRelease]$rpm_release" + displayName: Compute RPM version + + - script: | + set -euo pipefail + if ! command -v rpmbuild >/dev/null 2>&1; then + if ! command -v tdnf >/dev/null 2>&1; then + echo "rpmbuild is unavailable and tdnf is not installed; run this job in the configured Azure Linux container." >&2 + exit 1 + fi + tdnf install -y rpm-build + fi + + rpm_root="$(Build.SourcesDirectory)/.rpmbuild" + output_dir="$(Build.SourcesDirectory)/out/rpm" + mkdir -p "$rpm_root"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS} "$output_dir" + cp "$(Build.SourcesDirectory)/packaging/rpm/cosmosdbshell.spec" "$rpm_root/SPECS/" + cp "$(Build.SourcesDirectory)/LICENSE.md" "$(Build.SourcesDirectory)/NOTICE.html" "$rpm_root/SOURCES/" + + for entry in "linux-x64:x86_64" "linux-arm64:aarch64"; do + rid="${entry%%:*}" + rpm_arch="${entry##*:}" + publish_dir="$(Build.SourcesDirectory)/.rpm-publish/$rid" + + rm -rf "$publish_dir" + dotnet publish "$(Build.SourcesDirectory)/CosmosDBShell/CosmosDBShell.csproj" \ + --configuration "$(BuildConfiguration)" \ + --runtime "$rid" \ + --self-contained false \ + -p:PublishSingleFile=false \ + -p:PackAsTool=false \ + -p:CosmosDBShellExcludeMsalRuntime=true \ + --output "$publish_dir" \ + --configfile "$(Build.SourcesDirectory)/.pipelines/nuget.config" + + # Ships for linux-x64 only and doubles the package size. The VS Code + # broker credential falls back when it is absent, as it already does + # on linux-arm64, where this library does not exist at all. + rm -f "$publish_dir/libmsalruntime.so" + + for required in CosmosDBShell CosmosDBShell.dll; do + if [[ ! -f "$publish_dir/$required" ]]; then + echo "Expected $required in $publish_dir." >&2 + exit 1 + fi + done + + tar -czf "$rpm_root/SOURCES/cosmosdbshell-payload.tar.gz" -C "$publish_dir" . + rpmbuild -bb "$rpm_root/SPECS/cosmosdbshell.spec" \ + --target "$rpm_arch" \ + --define "_topdir $rpm_root" \ + --define "package_version $(CosmosDBShell_RpmVersion)" \ + --define "package_release $(CosmosDBShell_RpmRelease)" + done + + find "$rpm_root/RPMS" -type f -name '*.rpm' -exec cp {} "$output_dir/" \; + displayName: Build framework-dependent RPMs + + - script: | + set -euo pipefail + output_dir="$(Build.SourcesDirectory)/out/rpm" + mapfile -t packages < <(find "$output_dir" -maxdepth 1 -type f -name '*.rpm' | sort) + if [[ ${#packages[@]} -ne 2 ]]; then + echo "Expected two RPM packages; found ${#packages[@]}." >&2 + exit 1 + fi + + for package in "${packages[@]}"; do + size="$(stat -c %s "$package")" + if (( size > 25000000 )); then + echo "$(basename "$package") is $size bytes and exceeds the 25 MB Cloud Shell limit." >&2 + exit 1 + fi + rpm -qpR "$package" | grep -Fx 'dotnet-runtime-10.0 >= 10.0' + rpm -qlp "$package" | grep -Fx '/usr/bin/cosmosdbshell' + rpm -qlp "$package" | grep -Fx '/usr/libexec/cosmosdbshell/CosmosDBShell' + rpm -qlp "$package" | grep -Fx '/usr/libexec/cosmosdbshell/CosmosDBShell.dll' + if rpm -qlp "$package" | grep -q 'libmsalruntime\.so'; then + echo "$(basename "$package") still carries libmsalruntime.so." >&2 + exit 1 + fi + echo "Validated $(basename "$package") [$size bytes]" + done + displayName: Validate RPM packages + - job: CodeQLAnalyze displayName: CodeQL (C#) pool: diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c95e294..ba02fc3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Build & pipeline + +- The official pipeline now produces compressed `x86_64` and `aarch64` RPM packages from the framework-dependent Linux builds. The packages require the .NET 10 runtime and are validated against the Azure Cloud Shell 25 MB package-size limit. + ## 1.1.209-preview — 2026-08-26 ### New features diff --git a/CosmosDBShell.Tests/CommandTests/ConnectCommandTests.cs b/CosmosDBShell.Tests/CommandTests/ConnectCommandTests.cs index 137147aa..27fefbd8 100644 --- a/CosmosDBShell.Tests/CommandTests/ConnectCommandTests.cs +++ b/CosmosDBShell.Tests/CommandTests/ConnectCommandTests.cs @@ -15,6 +15,16 @@ namespace CosmosShell.Tests.CommandTests; [Collection(CosmosShell.Tests.Shell.ThemeStateTestCollection.Name)] public class ConnectCommandTests { + [Fact] + public void VSCodeCredential_SupportMatchesBuildCapability() + { +#if COSMOSDBSHELL_NO_MSAL_RUNTIME + Assert.False(ShellInterpreter.IsVSCodeCredentialSupported); +#else + Assert.True(ShellInterpreter.IsVSCodeCredentialSupported); +#endif + } + [Fact] public async Task ConnectAsync_CanceledToken_CancelsConnectionAttempt() { diff --git a/CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ShellInterpreter.cs b/CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ShellInterpreter.cs index 4826518f..3640001a 100644 --- a/CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ShellInterpreter.cs +++ b/CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ShellInterpreter.cs @@ -110,6 +110,18 @@ internal ShellInterpreter(string? configPath = null) /// public bool Echo { get; set; } = true; + internal static bool IsVSCodeCredentialSupported + { + get + { +#if COSMOSDBSHELL_NO_MSAL_RUNTIME + return false; +#else + return true; +#endif + } + } + internal static CancellationTokenSource TokenSource { get @@ -970,27 +982,34 @@ internal async Task ConnectAsync(string connectionString, string? loginHint = nu // Step 2: VisualStudioCodeCredential (when launched from VS Code extension) if (client == null && credentialMethod == CredentialMethod.VSCode) { - WriteLine(MessageService.GetString("shell-connect-vscode-credential-auth")); - - var vscOptions = new VisualStudioCodeCredentialOptions(); - if (!string.IsNullOrWhiteSpace(tenantId)) + if (!IsVSCodeCredentialSupported) { - vscOptions.TenantId = tenantId; + WriteLine(MessageService.GetString("shell-connect-vscode-credential-msal-runtime-missing")); } - - if (authorityHostUri != null) + else { - vscOptions.AuthorityHost = authorityHostUri; - } + WriteLine(MessageService.GetString("shell-connect-vscode-credential-auth")); - var vscCredential = new VisualStudioCodeCredential(vscOptions); - if (await this.TryConnectWithTokenCredentialAsync(tokenEndpoint, vscCredential, options, subscriptionId, resourceGroupName, authorityHostUri, allowCredentialFallback: true, token)) - { - return; - } + var vscOptions = new VisualStudioCodeCredentialOptions(); + if (!string.IsNullOrWhiteSpace(tenantId)) + { + vscOptions.TenantId = tenantId; + } - // VS Code credential unavailable or expired; continue the credential chain. - WriteLine(MessageService.GetString("shell-connect-vscode-credential-fallback")); + if (authorityHostUri != null) + { + vscOptions.AuthorityHost = authorityHostUri; + } + + var vscCredential = new VisualStudioCodeCredential(vscOptions); + if (await this.TryConnectWithTokenCredentialAsync(tokenEndpoint, vscCredential, options, subscriptionId, resourceGroupName, authorityHostUri, allowCredentialFallback: true, token)) + { + return; + } + + // VS Code credential unavailable or expired; continue the credential chain. + WriteLine(MessageService.GetString("shell-connect-vscode-credential-fallback")); + } } // Step 3: Static token from COSMOSDB_SHELL_TOKEN environment variable diff --git a/CosmosDBShell/lang/en.ftl b/CosmosDBShell/lang/en.ftl index d0a5ae95..02ebb4cf 100644 --- a/CosmosDBShell/lang/en.ftl +++ b/CosmosDBShell/lang/en.ftl @@ -17,6 +17,7 @@ shell-connect-static-token-auth = Connecting with externally provided access tok shell-connect-static-token-expiry = Expires in { $timespan } (expiration: { $expiration }). shell-connect-vscode-credential-auth = Connecting with Visual Studio Code credential... shell-connect-vscode-credential-fallback = Visual Studio Code credential unavailable, falling back... +shell-connect-vscode-credential-msal-runtime-missing = WARNING: Visual Studio Code credential is unavailable because this build excludes the native MSAL runtime. Falling back to other credentials; use --azure-cli to select the Azure CLI identity explicitly. shell-connect-devicecode-fallback = Browser authentication failed, falling back to device code authentication... shell-connect-arm-discovery-failed = Using Cosmos DB data plane. shell-connect-arm-discovery-ambiguous = Multiple ARM Cosmos DB accounts match the connected endpoint. Reconnect with --subscription and --resource-group, or use --connect-subscription and --connect-resource-group at startup, to specify which account to use. Using Cosmos DB data plane for now. diff --git a/Directory.Build.props b/Directory.Build.props index 8dcb9c5e..c6db7613 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -15,4 +15,7 @@ + + $(DefineConstants);COSMOSDBSHELL_NO_MSAL_RUNTIME + diff --git a/README.md b/README.md index 21011f6d..5efd2b7a 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,35 @@ Run the tests: dotnet test CosmosDBShell.sln ``` +## Install from RPM Artifacts + +The official pipeline produces framework-dependent RPMs for Azure Linux and +other RPM-based distributions: + +- `cosmosdbshell--.x86_64.rpm` +- `cosmosdbshell--.aarch64.rpm` + +The RPM requires the .NET 10 runtime package (`dotnet-runtime-10.0`), but does +not require the .NET SDK. On Azure Linux, install the package that matches the +host architecture with `tdnf`: + +```bash +sudo tdnf install ./cosmosdbshell--..rpm +cosmosdbshell +``` + +On other RPM-based distributions, use `dnf`: + +```bash +sudo dnf install ./cosmosdbshell--..rpm +cosmosdbshell +``` + +The RPM excludes the native MSAL runtime to remain within the Azure Cloud Shell +package-size limit. Visual Studio Code credential authentication is unavailable +in this build; selecting it prints a warning and falls back to other credentials. +Use `--azure-cli` to select the signed-in Azure CLI identity explicitly. + ## Architecture | Folder | Purpose | diff --git a/packaging/rpm/cosmosdbshell.spec b/packaging/rpm/cosmosdbshell.spec new file mode 100644 index 00000000..6f967763 --- /dev/null +++ b/packaging/rpm/cosmosdbshell.spec @@ -0,0 +1,47 @@ +Name: cosmosdbshell +Version: %{package_version} +Release: %{package_release}%{?dist} +Summary: Interactive shell for Azure Cosmos DB +License: MIT +URL: https://github.com/Azure/CosmosDBShell +Source0: cosmosdbshell-payload.tar.gz +Source1: LICENSE.md +Source2: NOTICE.html +Requires: dotnet-runtime-10.0 >= 10.0 +# The payload is prebuilt and only needs the .NET runtime, so skip the ELF scan +# that would otherwise derive dependencies from the build host. +AutoReqProv: no + +%global _binary_payload w19.zstdio +%{!?_licensedir: %global _licensedir %{_datadir}/licenses} + +# Prebuilt binaries are shipped as published; stripping them breaks the .NET host. +%global debug_package %{nil} +%global __os_install_post %{nil} + +%description +Azure Cosmos DB Shell is a command-line tool for interactive navigation, +queries, scripting, and MCP server workflows with Azure Cosmos DB. + +%prep + +%build + +%install +mkdir -p %{buildroot}%{_libexecdir}/cosmosdbshell +tar -xzf %{SOURCE0} -C %{buildroot}%{_libexecdir}/cosmosdbshell +chmod 0755 %{buildroot}%{_libexecdir}/cosmosdbshell/CosmosDBShell +install -D -m 0644 %{SOURCE1} %{buildroot}%{_licensedir}/%{name}/LICENSE.md +install -D -m 0644 %{SOURCE2} %{buildroot}%{_licensedir}/%{name}/NOTICE.html +mkdir -p %{buildroot}%{_bindir} +ln -s %{_libexecdir}/cosmosdbshell/CosmosDBShell %{buildroot}%{_bindir}/cosmosdbshell + +%files +%{_bindir}/cosmosdbshell +%{_libexecdir}/cosmosdbshell +%license %{_licensedir}/%{name}/LICENSE.md +%license %{_licensedir}/%{name}/NOTICE.html + +%changelog +* Thu Aug 27 2026 Microsoft Corporation - %{package_version}-%{package_release} +- Build from the framework-dependent .NET 10 publish output. \ No newline at end of file