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
138 changes: 138 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,140 @@ extends:
publishVstsFeed: "CosmosDB/CosmosDBShell"
allowPackageConflicts: true

- job: rpm
displayName: Build lightweight RPM packages
pool:
type: linux
variables:
ob_outputDirectory: "$(Build.SourcesDirectory)/out"
ob_artifactBaseName: cosmos_shell_lite_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-lite.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 \
-p:CosmosDBShellExcludeMcpAndLsp=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

set +e
"$publish_dir/CosmosDBShell" --output json --lsp >lsp.stdout 2>lsp.stderr
lsp_exit=$?
set -e
if [[ $lsp_exit -ne 2 || -s lsp.stdout ]]; then
echo "Lightweight --lsp smoke test did not return a machine-mode usage error." >&2
exit 1
fi
grep -F '"status":"error"' lsp.stderr
grep -F '"error":"LSP support is not included in this build."' lsp.stderr
rm -f lsp.stdout lsp.stderr

tar -czf "$rpm_root/SOURCES/cosmosdbshell-lite-payload.tar.gz" -C "$publish_dir" .
rpmbuild -bb "$rpm_root/SPECS/cosmosdbshell-lite.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 'cosmosdbshell-lite-*.rpm' -exec cp {} "$output_dir/" \;
displayName: Build lightweight framework-dependent RPMs

- script: |
set -euo pipefail
output_dir="$(Build.SourcesDirectory)/out/rpm"
mapfile -t packages < <(find "$output_dir" -maxdepth 1 -type f -name 'cosmosdbshell-lite-*.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 -qp --queryformat '%{NAME}\n' "$package" | grep -Fx 'cosmosdbshell-lite'
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
if rpm -qlp "$package" | grep -Eq '/(ModelContextProtocol|OmniSharp|Azure\.Identity\.Broker|Microsoft\.Identity\.Client\.Broker).*\.dll$'; then
echo "$(basename "$package") unexpectedly contains an excluded feature assembly." >&2
exit 1
fi
echo "Validated $(basename "$package") [$size bytes]"
done
displayName: Validate lightweight RPM packages

- job: CodeQLAnalyze
displayName: CodeQL (C#)
pool:
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

### Build & pipeline

- The official pipeline now produces compressed `cosmosdbshell-lite` RPM packages for `x86_64` and `aarch64` from lightweight framework-dependent Linux builds. The packages retain the interactive shell, scripting, ARM management, direct/gateway connectivity, and OpenTelemetry export while excluding MCP, LSP, and brokered Visual Studio Code authentication. They require the .NET 10 runtime and are validated against the Azure Cloud Shell 25 MB package-size limit.
### Improvements

- Cosmos DB data-plane commands now consistently expose their aggregate observed request charge in structured output and connection-scoped `info` telemetry, including metadata/configuration operations, scripts, change feed reads, paginated operations, handled probes, and charged failures. Azure Resource Manager control-plane operations remain uncharged.
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 @@ -16,6 +16,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 void CreateClientOptions_Emulator_UsesShortRequestTimeout()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ namespace Azure.Data.Cosmos.Shell.Core;
using System.Linq;
using System.Text;
using Azure.Data.Cosmos.Shell.Parser;
using Microsoft.AspNetCore.Http.Metadata;
using RadLine;
using Spectre.Console;
using Spectre.Console.Rendering;
Expand Down
53 changes: 37 additions & 16 deletions CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ShellInterpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,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 UserCancellationTokenSource
{
get
Expand Down Expand Up @@ -1174,27 +1186,36 @@ 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)
#if !COSMOSDBSHELL_NO_MSAL_RUNTIME
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;
}

if (authorityHostUri != null)
{
vscOptions.AuthorityHost = authorityHostUri;
}

// VS Code credential unavailable or expired; continue the credential chain.
WriteLine(MessageService.GetString("shell-connect-vscode-credential-fallback"));
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"));
}
#endif
}

// Step 3: Static token from COSMOSDB_SHELL_TOKEN environment variable
Expand Down
24 changes: 16 additions & 8 deletions CosmosDBShell/CosmosDBShell.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@
<ItemGroup>
<EmbeddedResource Include="lang\en.ftl" />
<EmbeddedResource Include="cosmos_welcome.ans" />
<EmbeddedResource Include="Azure.Data.Cosmos.Shell.Mcp\ServerInstructions.md" />
<EmbeddedResource Include="..\docs\programming.md" Link="docs\programming.md" />
<EmbeddedResource Include="..\docs\nosql-query-language.md" Link="docs\nosql-query-language.md" />
<EmbeddedResource Include="Azure.Data.Cosmos.Shell.Mcp\ServerInstructions.md" Condition="'$(CosmosDBShellExcludeMcpAndLsp)' != 'true'" />
<EmbeddedResource Include="..\docs\programming.md" Link="docs\programming.md" Condition="'$(CosmosDBShellExcludeMcpAndLsp)' != 'true'" />
<EmbeddedResource Include="..\docs\nosql-query-language.md" Link="docs\nosql-query-language.md" Condition="'$(CosmosDBShellExcludeMcpAndLsp)' != 'true'" />
</ItemGroup>

<ItemGroup>
Expand All @@ -86,16 +86,17 @@

<ItemGroup>
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Azure.Identity.Broker" />
<PackageReference Include="Azure.Identity.Broker" Condition="'$(CosmosDBShellExcludeMsalRuntime)' != 'true'" />
<PackageReference Include="Azure.ResourceManager" />
<PackageReference Include="Azure.ResourceManager.CosmosDB" />
<PackageReference Include="System.CommandLine" />
<PackageReference Include="Fluent.Net" />
<PackageReference Include="Microsoft.Azure.Cosmos" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="ModelContextProtocol" />
<PackageReference Include="ModelContextProtocol.AspNetCore" />
<PackageReference Include="OmniSharp.Extensions.LanguageServer" />
<PackageReference Include="Newtonsoft.Json" Condition="'$(CosmosDBShellExcludeMcpAndLsp)' == 'true'" />
<PackageReference Include="Microsoft.Extensions.Hosting" Condition="'$(CosmosDBShellExcludeMcpAndLsp)' != 'true'" />
<PackageReference Include="ModelContextProtocol" Condition="'$(CosmosDBShellExcludeMcpAndLsp)' != 'true'" />
Comment thread
mkrueger marked this conversation as resolved.
<PackageReference Include="ModelContextProtocol.AspNetCore" Condition="'$(CosmosDBShellExcludeMcpAndLsp)' != 'true'" />
<PackageReference Include="OmniSharp.Extensions.LanguageServer" Condition="'$(CosmosDBShellExcludeMcpAndLsp)' != 'true'" />
<PackageReference Include="OpenTelemetry" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
<PackageReference Include="RadLine" />
Expand All @@ -108,6 +109,13 @@
</PackageReference>
</ItemGroup>

<ItemGroup Condition="'$(CosmosDBShellExcludeMcpAndLsp)' == 'true'">
<Compile Remove="Azure.Data.Cosmos.Shell.Lsp\**\*.cs" />
<Compile Remove="Azure.Data.Cosmos.Shell.Lsp.Semantics\**\*.cs" />
<Compile Remove="Azure.Data.Cosmos.Shell.Mcp\**\*.cs" />
<Compile Include="Azure.Data.Cosmos.Shell.Mcp\McpAnnotationAttribute.cs" />
</ItemGroup>

<ItemGroup>
<Using Include="Microsoft.Azure.Cosmos" />
<Using Include="Microsoft.Azure.Cosmos.Linq" />
Expand Down
Loading