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
120 changes: 120 additions & 0 deletions .pipelines/CosmosDB-Shell-Official.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions CosmosDBShell.Tests/CommandTests/ConnectCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
51 changes: 35 additions & 16 deletions CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ShellInterpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,18 @@ internal ShellInterpreter(string? configPath = null)
/// </summary>
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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions CosmosDBShell/lang/en.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@
<ItemGroup Condition="'$(ManagePackageVersionsCentrally)' == 'false'">
<PackageReference Include="Nerdbank.GitVersioning" PrivateAssets="all" Version="3.9.50" />
</ItemGroup>
<PropertyGroup Condition="'$(CosmosDBShellExcludeMsalRuntime)' == 'true'">
<DefineConstants>$(DefineConstants);COSMOSDBSHELL_NO_MSAL_RUNTIME</DefineConstants>
</PropertyGroup>
</Project>
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-<version>-<release>.x86_64.rpm`
- `cosmosdbshell-<version>-<release>.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-<version>-<release>.<architecture>.rpm
cosmosdbshell
```

On other RPM-based distributions, use `dnf`:

```bash
sudo dnf install ./cosmosdbshell-<version>-<release>.<architecture>.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 |
Expand Down
47 changes: 47 additions & 0 deletions packaging/rpm/cosmosdbshell.spec
Original file line number Diff line number Diff line change
@@ -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 <cosmosdbshell@microsoft.com> - %{package_version}-%{package_release}
- Build from the framework-dependent .NET 10 publish output.