diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml
deleted file mode 100644
index bef1750..0000000
--- a/.github/workflows/build-macos.yml
+++ /dev/null
@@ -1,55 +0,0 @@
-name: build-macos
-
-on:
- push:
- paths-ignore:
- - "**.md"
- - LICENSE
- branches:
- - "master"
- pull_request:
- paths-ignore:
- - "**.md"
- - LICENSE
- branches:
- - master
-
-jobs:
- build-and-test:
- runs-on: macos-latest
-
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Init
- run: chmod +x ./build.sh
-
- - name: Install NuGet
- uses: NuGet/setup-nuget@v1.0.5
-
- - name: Setup Testspace
- uses: testspace-com/setup-testspace@v1
- with:
- domain: ${{github.repository_owner}}
-
- - name: Install .NET 8
- uses: actions/setup-dotnet@v1
- with:
- dotnet-version: "8.0.x"
-
- - name: Install .NET 9
- uses: actions/setup-dotnet@v1
- with:
- dotnet-version: "9.0.x"
-
- - name: Build
- run: ./build.sh --target build
-
- - name: Run Tests
- run: ./build.sh --target tests --exclusive
-
- - name: Push result to Testspace server
- run: |
- testspace [macos]**/*.trx
- if: always()
\ No newline at end of file
diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml
deleted file mode 100644
index f82fb3e..0000000
--- a/.github/workflows/build-ubuntu.yml
+++ /dev/null
@@ -1,55 +0,0 @@
-name: build-ubuntu
-
-on:
- push:
- paths-ignore:
- - "**.md"
- - LICENSE
- branches:
- - "master"
- pull_request:
- paths-ignore:
- - "**.md"
- - LICENSE
- branches:
- - master
-
-jobs:
- build-and-test:
- runs-on: ubuntu-20.04
-
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Init
- run: chmod +x ./build.sh
-
- - name: Install NuGet
- uses: NuGet/setup-nuget@v1.0.5
-
- - name: Setup Testspace
- uses: testspace-com/setup-testspace@v1
- with:
- domain: ${{github.repository_owner}}
-
- - name: Install .NET 8
- uses: actions/setup-dotnet@v1
- with:
- dotnet-version: "8.0.x"
-
- - name: Install .NET 9
- uses: actions/setup-dotnet@v1
- with:
- dotnet-version: "9.0.x"
-
- - name: Build
- run: ./build.sh --target build
-
- - name: Run Tests
- run: ./build.sh --target tests --skipFunctionalTest false --exclusive
-
- - name: Push result to Testspace server
- run: |
- testspace [linux]**/*.trx
- if: always()
\ No newline at end of file
diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml
deleted file mode 100644
index f41f8a9..0000000
--- a/.github/workflows/build-windows.yml
+++ /dev/null
@@ -1,49 +0,0 @@
-name: build-windows
-
-on:
- push:
- paths-ignore:
- - "**.md"
- - LICENSE
- branches:
- - "master"
- pull_request:
- paths-ignore:
- - "**.md"
- - LICENSE
- branches:
- - master
-
-jobs:
- build-and-test:
- runs-on: windows-latest
-
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Setup Testspace
- uses: testspace-com/setup-testspace@v1
- with:
- domain: ${{github.repository_owner}}
-
- - name: Install .NET 8
- uses: actions/setup-dotnet@v1
- with:
- dotnet-version: "8.0.x"
-
- - name: Install .NET 9
- uses: actions/setup-dotnet@v1
- with:
- dotnet-version: "9.0.x"
-
- - name: Build
- run: .\build.ps1 --target build
-
- - name: Run Tests
- run: .\build.ps1 --target tests --exclusive
-
- - name: Push result to Testspace server
- run: |
- testspace [windows]**/*.trx
- if: always()
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..a494c1a
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,95 @@
+name: "CI/CD Pipeline"
+
+on:
+ push:
+ paths-ignore:
+ - "**.md"
+ - LICENSE
+ branches:
+ - "master"
+ pull_request:
+ paths-ignore:
+ - "**.md"
+ - LICENSE
+ branches:
+ - master
+ - "feature/*"
+
+env:
+ DOTNET_CLI_TELEMETRY_OPTOUT: true
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
+ DOTNET_NOLOGO: true
+
+jobs:
+ build-and-test:
+ name: "Build & Test (${{ matrix.name }})"
+ runs-on: ${{ matrix.os }}
+ env:
+ NUGET_PACKAGES: ${{ contains(matrix.os, 'windows') && format('{0}\.nuget\packages', github.workspace) || format('{0}/.nuget/packages', github.workspace) }}
+
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - os: windows-latest
+ name: "Windows"
+ script: "./build.ps1"
+
+ - os: ubuntu-22.04
+ name: "Linux"
+ script: "./build.sh"
+
+ - os: macos-latest
+ name: "macOS"
+ script: "./build.sh"
+
+ steps:
+ - name: "Checkout"
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # Full history for better caching
+
+ - name: "Setup .NET SDK"
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: |
+ 8.0.x
+ 9.0.x
+
+ - name: "Make build script executable"
+ if: runner.os != 'Windows'
+ run: chmod +x ./build.sh
+
+ - name: "Cache NuGet packages"
+ uses: actions/cache@v4
+ with:
+ path: ${{ runner.os == 'Windows' && format('{0}\.nuget\packages', github.workspace) || format('{0}/.nuget/packages', github.workspace) }}
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json', '**/*.csproj', '**/Directory.Packages.props') }}
+ restore-keys: |
+ ${{ runner.os }}-nuget-
+
+ - name: "Build"
+ run: ${{ matrix.script }} --target build
+
+ - name: "Run Tests"
+ run: ${{ matrix.script }} --target tests --skipFunctionalTest ${{ runner.os == 'Linux' && 'false' || 'true' }} --exclusive
+
+ - name: "Publish Test Results"
+ uses: dorny/test-reporter@v1
+ if: success() || failure()
+ with:
+ name: 'Test Results (${{ matrix.name }})'
+ path: '**/TestResults/*.trx'
+ reporter: 'dotnet-trx'
+ fail-on-error: true
+ max-annotations: 50
+
+ - name: "Upload Test Artifacts"
+ uses: actions/upload-artifact@v4
+ if: failure()
+ with:
+ name: test-results-${{ matrix.name }}
+ path: |
+ **/*.trx
+ **/TestResults/**/*
+ retention-days: 7
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
new file mode 100644
index 0000000..2f7ccaf
--- /dev/null
+++ b/.github/workflows/dependency-review.yml
@@ -0,0 +1,27 @@
+name: Dependency Review
+
+on:
+ pull_request:
+ branches:
+ - master
+ - "feature/*"
+
+permissions:
+ contents: read
+ pull-requests: write
+
+jobs:
+ dependency-review:
+ name: "Dependency Review"
+ runs-on: ubuntu-latest
+ steps:
+ - name: "Checkout"
+ uses: actions/checkout@v4
+
+ - name: "Dependency Review"
+ uses: actions/dependency-review-action@v4
+ with:
+ # Fail the check if a vulnerability with 'moderate' severity or higher is found.
+ fail-on-severity: moderate
+ # Always post a summary of the check as a comment on the PR.
+ comment-summary-in-pr: always
\ No newline at end of file
diff --git a/.github/workflows/publish-dev-github.yml b/.github/workflows/publish-dev-github.yml
new file mode 100644
index 0000000..141bea0
--- /dev/null
+++ b/.github/workflows/publish-dev-github.yml
@@ -0,0 +1,99 @@
+name: "Auto Publish to GitHub Packages"
+
+on:
+ push:
+ branches:
+ - master
+ paths-ignore:
+ - "**.md"
+ - LICENSE
+ - ".github/**"
+ - "docs/**"
+
+env:
+ DOTNET_CLI_TELEMETRY_OPTOUT: true
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
+ DOTNET_NOLOGO: true
+
+jobs:
+ auto-publish:
+ name: "Auto Publish Development Build"
+ runs-on: ubuntu-22.04
+ if: github.repository == 'localstack-dotnet/localstack-dotnet-client'
+ env:
+ NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
+
+ permissions:
+ contents: read
+ packages: write
+
+ steps:
+ - name: "Checkout"
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: "Setup .NET SDK"
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: |
+ 8.0.x
+ 9.0.x
+
+ - name: "Cache NuGet packages"
+ uses: actions/cache@v4
+ with:
+ path: ${{ github.workspace }}/.nuget/packages
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json', '**/*.csproj', '**/Directory.Packages.props') }}
+ restore-keys: |
+ ${{ runner.os }}-nuget-
+
+ - name: "Make build script executable"
+ run: chmod +x ./build.sh
+
+ - name: "Build & Test"
+ run: ./build.sh --target tests --skipFunctionalTest true
+
+ - name: "Setup GitHub Packages Authentication"
+ run: |
+ dotnet nuget add source https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json \
+ --name github-packages \
+ --username ${{ github.actor }} \
+ --password ${{ secrets.GITHUB_TOKEN }} \
+ --store-password-in-clear-text
+
+ - name: "Pack & Publish LocalStack.Client"
+ run: |
+ echo "🔨 Building and publishing LocalStack.Client package..."
+ ./build.sh --target nuget-pack-and-publish \
+ --package-source github \
+ --package-id LocalStack.Client \
+ --use-directory-props-version true \
+ --branch-name ${{ github.ref_name }} \
+ --package-secret ${{ secrets.GITHUB_TOKEN }}
+
+ - name: "Pack & Publish LocalStack.Client.Extensions"
+ run: |
+ echo "🔨 Building and publishing LocalStack.Client.Extensions package..."
+ ./build.sh --target nuget-pack-and-publish \
+ --package-source github \
+ --package-id LocalStack.Client.Extensions \
+ --use-directory-props-version true \
+ --branch-name ${{ github.ref_name }} \
+ --package-secret ${{ secrets.GITHUB_TOKEN }}
+
+ - name: "Upload Package Artifacts"
+ uses: actions/upload-artifact@v4
+ with:
+ name: "dev-packages-${{ github.run_number }}"
+ path: |
+ artifacts/*.nupkg
+ artifacts/*.snupkg
+ retention-days: 7
+
+ - name: "Generate Build Summary"
+ run: |
+ echo "📦 Generating build summary..."
+ ./build.sh --target workflow-summary \
+ --use-directory-props-version true \
+ --branch-name ${{ github.ref_name }}
diff --git a/.github/workflows/publish-nuget.yml b/.github/workflows/publish-nuget.yml
index 17d60ba..afb009e 100644
--- a/.github/workflows/publish-nuget.yml
+++ b/.github/workflows/publish-nuget.yml
@@ -1,4 +1,4 @@
-name: "publish-nuget"
+name: "Manual Package Publishing"
on:
workflow_dispatch:
@@ -10,10 +10,10 @@ on:
type: choice
description: Package Source
required: true
- default: "myget"
+ default: "nuget"
options:
- - myget
- nuget
+ - github
package-id:
type: choice
description: Package Id
@@ -23,48 +23,111 @@ on:
- LocalStack.Client
- LocalStack.Client.Extensions
+env:
+ DOTNET_CLI_TELEMETRY_OPTOUT: true
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
+ DOTNET_NOLOGO: true
+
jobs:
- publish-nuget:
- runs-on: ubuntu-20.04
+ publish-manual:
+ name: "Publish to ${{ github.event.inputs.package-source }}"
+ runs-on: ubuntu-22.04
+ env:
+ NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
+
+ permissions:
+ contents: read
+ packages: write
steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Init
- run: chmod +x ./build.sh
-
- - name: Install NuGet
- uses: NuGet/setup-nuget@v1.0.5
+ - name: "Checkout"
+ uses: actions/checkout@v4
- - name: Install .NET 8
- uses: actions/setup-dotnet@v1
+ - name: "Setup .NET SDK"
+ uses: actions/setup-dotnet@v4
with:
- dotnet-version: "8.0.x"
+ dotnet-version: |
+ 8.0.x
+ 9.0.x
- - name: Install .NET 9
- uses: actions/setup-dotnet@v1
+ - name: "Cache NuGet packages"
+ uses: actions/cache@v4
with:
- dotnet-version: "9.0.x"
+ path: ~/.nuget/packages
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json', '**/*.csproj', '**/Directory.Packages.props') }}
+ restore-keys: |
+ ${{ runner.os }}-nuget-
+
+ - name: "Make build script executable"
+ run: chmod +x ./build.sh
+
+ - name: "Build & Test"
+ run: ./build.sh --target tests --skipFunctionalTest true
- - name: Build & Test
- run: ./build.sh
+ - name: "Print Package Information"
+ run: |
+ echo "📦 Package: ${{ github.event.inputs.package-id }}"
+ echo "🏷️ Version: ${{ github.event.inputs.package-version }}"
+ echo "🎯 Target: ${{ github.event.inputs.package-source }}"
+ echo "🔗 Repository: ${{ github.repository }}"
- - name: "Print Version"
+ - name: "Setup GitHub Packages Authentication"
+ if: ${{ github.event.inputs.package-source == 'github' }}
run: |
- echo "Package Version: ${{ github.event.inputs.package-version }}"
+ dotnet nuget add source https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json \
+ --name github-packages \
+ --username ${{ github.actor }} \
+ --password ${{ secrets.GITHUB_TOKEN }} \
+ --store-password-in-clear-text
- - name: Remove Project Ref & Add latest pack
+ - name: "Remove Project Reference & Add Package Reference"
if: ${{ github.event.inputs.package-id == 'LocalStack.Client.Extensions' }}
- run: cd src/LocalStack.Client.Extensions/ && dotnet remove reference ../LocalStack.Client/LocalStack.Client.csproj && dotnet add package LocalStack.Client
+ run: |
+ cd src/LocalStack.Client.Extensions/
+
+ # Remove project reference
+ dotnet remove reference ../LocalStack.Client/LocalStack.Client.csproj
+
+ # Add package reference based on target source
+ if [ "${{ github.event.inputs.package-source }}" == "github" ]; then
+ dotnet add package LocalStack.Client \
+ --version ${{ github.event.inputs.package-version }} \
+ --source github-packages
+ else
+ dotnet add package LocalStack.Client \
+ --version ${{ github.event.inputs.package-version }}
+ fi
- - name: Nuget Pack
- run: ./build.sh --target nuget-pack --package-source ${{ github.event.inputs.package-source }} --package-id ${{ github.event.inputs.package-id }} --package-version ${{ github.event.inputs.package-version }}
+ - name: "Pack NuGet Package"
+ run: |
+ ./build.sh --target nuget-pack \
+ --package-source ${{ github.event.inputs.package-source }} \
+ --package-id ${{ github.event.inputs.package-id }} \
+ --package-version ${{ github.event.inputs.package-version }}
- - name: MyGet Push
- if: ${{ github.event.inputs.package-source == 'myget' }}
- run: ./build.sh --target nuget-push --package-source ${{ github.event.inputs.package-source }} --package-id ${{ github.event.inputs.package-id }} --package-version ${{ github.event.inputs.package-version }} --package-secret ${{secrets.MYGET_API_KEY}}
+ - name: "Publish to GitHub Packages"
+ if: ${{ github.event.inputs.package-source == 'github' }}
+ run: |
+ ./build.sh --target nuget-push \
+ --package-source github \
+ --package-id ${{ github.event.inputs.package-id }} \
+ --package-version ${{ github.event.inputs.package-version }} \
+ --package-secret ${{ secrets.GITHUB_TOKEN }}
- - name: NuGet Push
+ - name: "Publish to NuGet.org"
if: ${{ github.event.inputs.package-source == 'nuget' }}
- run: ./build.sh --target nuget-push --package-source ${{ github.event.inputs.package-source }} --package-id ${{ github.event.inputs.package-id }} --package-version ${{ github.event.inputs.package-version }} --package-secret ${{secrets.NUGET_API_KEY}}
\ No newline at end of file
+ run: |
+ ./build.sh --target nuget-push \
+ --package-source nuget \
+ --package-id ${{ github.event.inputs.package-id }} \
+ --package-version ${{ github.event.inputs.package-version }} \
+ --package-secret ${{ secrets.NUGET_API_KEY }}
+
+ - name: "Upload Package Artifacts"
+ uses: actions/upload-artifact@v4
+ with:
+ name: "packages-${{ github.event.inputs.package-id }}-${{ github.event.inputs.package-version }}"
+ path: |
+ artifacts/*.nupkg
+ artifacts/*.snupkg
+ retention-days: 30
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ed6d1c8..896aa33 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,45 @@
# LocalStack .NET Client Change Log
+### [v2.0.0-preview1](https://github.com/localstack-dotnet/localstack-dotnet-client/releases/tag/v2.0.0-preview1)
+
+#### 1. Breaking Changes
+
+- **Framework Support Updates:**
+ - **Deprecated** support for **.NET Framework 4.6.2**.
+ - **Added** support for **.NET Framework 4.7.2** (required for AWS SDK v4 compatibility).
+
+#### 2. General
+
+- **AWS SDK v4 Migration:**
+ - **Complete migration** from AWS SDK for .NET v3 to v4.
+ - **AWSSDK.Core** minimum version set to **4.0.0.15**.
+ - **AWSSDK.Extensions.NETCore.Setup** updated to **4.0.2**.
+ - All 70+ AWS SDK service packages updated to v4.x series.
+
+- **Framework Support:**
+ - **.NET 9**
+ - **.NET 8**
+ - **.NET Standard 2.0**
+ - **.NET Framework 4.7.2**
+
+- **Testing Validation:**
+ - **1,099 total tests** passing across all target frameworks.
+ - Successfully tested with AWS SDK v4 across all supported .NET versions.
+ - Tested against following LocalStack versions:
+ - **v3.7.1**
+ - **v4.3.0**
+
+#### 3. Important Notes
+
+- **Preview Release**: This is a preview release for early adopters and testing. See the [v2.0.0 Roadmap & Migration Guide](https://github.com/localstack-dotnet/localstack-dotnet-client/discussions/45) for the complete migration plan.
+- **No API Changes**: LocalStack.NET public APIs remain unchanged. All changes are internal to support AWS SDK v4 compatibility.
+- **Feedback Welcome**: Please report issues or feedback on [GitHub Issues](https://github.com/localstack-dotnet/localstack-dotnet-client/issues).
+- **v2.x series requires AWS SDK v4**: This version is only compatible with AWS SDK for .NET v4.x packages.
+- **Migration from v1.x**: Users upgrading from v1.x should ensure their projects reference AWS SDK v4 packages.
+- **Framework Requirement**: .NET Framework 4.7.2 or higher is now required (upgrade from 4.6.2).
+
+---
+
### [v1.6.0](https://github.com/localstack-dotnet/localstack-dotnet-client/releases/tag/v1.6.0)
#### 1. General
diff --git a/Directory.Build.props b/Directory.Build.props
index 532556c..9ed3370 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -5,8 +5,8 @@
LocalStack.NET
https://github.com/localstack-dotnet/localstack-dotnet-client
localstack-dotnet-square.png
- 1.6.0
- 1.4.0
+ 2.0.0-preview1
+ 2.0.0-preview1
true
snupkg
13.0
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 99f4395..6863198 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -1,125 +1,125 @@
-
-
+
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -146,8 +146,8 @@
-
-
+
+
@@ -159,7 +159,7 @@
runtime; build; native; contentfiles; analyzers
-
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 95224e5..51c4faa 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# LocalStack .NET Client  [](https://www.nuget.org/packages/LocalStack.Client/) [](https://localstack-dotnet.testspace.com/spaces/232580?utm_campaign=metric&utm_medium=referral&utm_source=badge "Test Cases")
+# LocalStack .NET Client  [](https://www.nuget.org/packages/LocalStack.Client/) [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/ci.yml) [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/security.yml)
> ## ⚠️ AWS SDK v4 Transition Notice
>
@@ -27,16 +27,16 @@ Localstack.NET is an easy-to-use .NET client for [LocalStack](https://github.com
| Package | v1.x (AWS SDK v3) | v2.x (AWS SDK v4) - Development |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| LocalStack.Client | [](https://www.nuget.org/packages/LocalStack.Client/) | [](https://www.myget.org/feed/localstack-dotnet-client/package/nuget/LocalStack.Client) |
-| LocalStack.Client.Extensions | [](https://www.nuget.org/packages/LocalStack.Client.Extensions/) | [](https://www.myget.org/feed/localstack-dotnet-client/package/nuget/LocalStack.Client.Extensions) |
+| LocalStack.Client | [](https://www.nuget.org/packages/LocalStack.Client/) | [](https://github.com/localstack-dotnet/localstack-dotnet-client/packages) |
+| LocalStack.Client.Extensions | [](https://www.nuget.org/packages/LocalStack.Client.Extensions/) | [](https://github.com/localstack-dotnet/localstack-dotnet-client/packages) |
## Continuous Integration
-| Build server | Platform | Build status |
-| -------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| Github Actions | Ubuntu | [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-ubuntu.yml) |
-| Github Actions | Windows | [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-windows.yml) |
-| Github Actions | macOS | [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-macos.yml) |
+| Build Platform | Status | Description |
+|----------------|--------|-------------|
+| **Cross-Platform CI** | [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/ci.yml) | Matrix testing: Windows, Linux, macOS |
+| **Security Analysis** | [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/security.yml) | CodeQL analysis & dependency review |
+| **Automated Publishing** | [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/publish-dev-github.yml) | Daily GitHub Packages builds |
## Table of Contents
@@ -81,13 +81,37 @@ For detailed installation and setup instructions, please refer to the [official
## Getting Started
-LocalStack.NET is installed from NuGet. To work with LocalStack in your .NET applications, you'll need the main library and its extensions. Here's how you can install them:
+LocalStack.NET is available through multiple package sources to support different development workflows.
+
+### 📦 Package Installation
+
+#### Stable Releases (NuGet.org)
+
+For production use and stable releases:
```bash
dotnet add package LocalStack.Client
dotnet add package LocalStack.Client.Extensions
```
+#### Development Builds (GitHub Packages)
+
+For testing latest features and bug fixes:
+
+```bash
+# Add GitHub Packages source
+dotnet nuget add source https://nuget.pkg.github.com/localstack-dotnet/index.json \
+ --name github-localstack \
+ --username YOUR_GITHUB_USERNAME \
+ --password YOUR_GITHUB_TOKEN
+
+# Install development packages
+dotnet add package LocalStack.Client --prerelease --source github-localstack
+dotnet add package LocalStack.Client.Extensions --prerelease --source github-localstack
+```
+
+> **🔑 GitHub Packages Authentication**: You'll need a GitHub account and [Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) with `read:packages` permission.
+
Refer to [documentation](https://github.com/localstack-dotnet/localstack-dotnet-client/wiki/Getting-Started#installation) for more information on how to install LocalStack.NET.
`LocalStack.NET` is a library that provides a wrapper around the [aws-sdk-net](https://github.com/aws/aws-sdk-net). This means you can use it in a similar way to the `AWS SDK for .NET` and to [AWSSDK.Extensions.NETCore.Setup](https://docs.aws.amazon.com/sdk-for-net/latest/developer-guide/net-dg-config-netcore.html) with a few differences. For more on how to use the AWS SDK for .NET, see [Getting Started with the AWS SDK for .NET](https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-setup.html).
diff --git a/build/LocalStack.Build/BuildContext.cs b/build/LocalStack.Build/BuildContext.cs
index 4000e4b..b8ae64f 100644
--- a/build/LocalStack.Build/BuildContext.cs
+++ b/build/LocalStack.Build/BuildContext.cs
@@ -4,23 +4,34 @@ namespace LocalStack.Build;
public sealed class BuildContext : FrostingContext
{
+ public const string LocalStackClientProjName = "LocalStack.Client";
+ public const string LocalStackClientExtensionsProjName = "LocalStack.Client.Extensions";
+
+ public const string GitHubPackageSource = "github";
+ public const string NuGetPackageSource = "nuget";
+ public const string MyGetPackageSource = "myget";
+
public BuildContext(ICakeContext context) : base(context)
{
BuildConfiguration = context.Argument("config", "Release");
- ForceBuild = context.Argument("force-build", false);
- ForceRestore = context.Argument("force-restore", false);
+ ForceBuild = context.Argument("force-build", defaultValue: false);
+ ForceRestore = context.Argument("force-restore", defaultValue: false);
PackageVersion = context.Argument("package-version", "x.x.x");
PackageId = context.Argument("package-id", default(string));
PackageSecret = context.Argument("package-secret", default(string));
- PackageSource = context.Argument("package-source", default(string));
- SkipFunctionalTest = context.Argument("skipFunctionalTest", true);
+ PackageSource = context.Argument("package-source", GitHubPackageSource);
+ SkipFunctionalTest = context.Argument("skipFunctionalTest", defaultValue: true);
+
+ // New version generation arguments
+ UseDirectoryPropsVersion = context.Argument("use-directory-props-version", defaultValue: false);
+ BranchName = context.Argument("branch-name", "master");
var sourceBuilder = ImmutableDictionary.CreateBuilder();
- sourceBuilder.AddRange(new[]
- {
- new KeyValuePair("myget", "https://www.myget.org/F/localstack-dotnet-client/api/v3/index.json"),
- new KeyValuePair("nuget", "https://api.nuget.org/v3/index.json")
- });
+ sourceBuilder.AddRange([
+ new KeyValuePair(MyGetPackageSource, "https://www.myget.org/F/localstack-dotnet-client/api/v3/index.json"),
+ new KeyValuePair(NuGetPackageSource, "https://api.nuget.org/v3/index.json"),
+ new KeyValuePair(GitHubPackageSource, "https://nuget.pkg.github.com/localstack-dotnet/index.json"),
+ ]);
PackageSourceMap = sourceBuilder.ToImmutable();
SolutionRoot = context.Directory("../../");
@@ -28,18 +39,18 @@ public BuildContext(ICakeContext context) : base(context)
TestsPath = SolutionRoot + context.Directory("tests");
BuildPath = SolutionRoot + context.Directory("build");
ArtifactOutput = SolutionRoot + context.Directory("artifacts");
- LocalStackClientFolder = SrcPath + context.Directory("LocalStack.Client");
- LocalStackClientExtFolder = SrcPath + context.Directory("LocalStack.Client.Extensions");
+ LocalStackClientFolder = SrcPath + context.Directory(LocalStackClientProjName);
+ LocalStackClientExtFolder = SrcPath + context.Directory(LocalStackClientExtensionsProjName);
SlnFilePath = SolutionRoot + context.File("LocalStack.sln");
- LocalStackClientProjFile = LocalStackClientFolder + context.File("LocalStack.Client.csproj");
- LocalStackClientExtProjFile = LocalStackClientExtFolder + context.File("LocalStack.Client.Extensions.csproj");
+ LocalStackClientProjFile = LocalStackClientFolder + context.File($"{LocalStackClientProjName}.csproj");
+ LocalStackClientExtProjFile = LocalStackClientExtFolder + context.File($"{LocalStackClientExtensionsProjName}.csproj");
var packIdBuilder = ImmutableDictionary.CreateBuilder();
- packIdBuilder.AddRange(new[]
- {
- new KeyValuePair("LocalStack.Client", LocalStackClientProjFile),
- new KeyValuePair("LocalStack.Client.Extensions", LocalStackClientExtProjFile)
- });
+ packIdBuilder.AddRange(
+ [
+ new KeyValuePair(LocalStackClientProjName, LocalStackClientProjFile),
+ new KeyValuePair(LocalStackClientExtensionsProjName, LocalStackClientExtProjFile),
+ ]);
PackageIdProjMap = packIdBuilder.ToImmutable();
}
@@ -59,6 +70,10 @@ public BuildContext(ICakeContext context) : base(context)
public string PackageSource { get; }
+ public bool UseDirectoryPropsVersion { get; }
+
+ public string BranchName { get; }
+
public ImmutableDictionary PackageSourceMap { get; }
public ImmutableDictionary PackageIdProjMap { get; }
@@ -91,27 +106,10 @@ public static void ValidateArgument(string argumentName, string argument)
}
}
- public void InstallXUnitNugetPackage()
- {
- if (!Directory.Exists("testrunner"))
- {
- Directory.CreateDirectory("testrunner");
- }
-
- var nugetInstallSettings = new NuGetInstallSettings
- {
- Version = "2.8.1", Verbosity = NuGetVerbosity.Normal, OutputDirectory = "testrunner", WorkingDirectory = "."
- };
-
- this.NuGetInstall("xunit.runner.console", nugetInstallSettings);
- }
-
public IEnumerable GetProjMetadata()
{
DirectoryPath testsRoot = this.Directory(TestsPath);
- List csProjFile = this.GetFiles($"{testsRoot}/**/*.csproj")
- .Where(fp => fp.FullPath.EndsWith("Tests.csproj", StringComparison.InvariantCulture))
- .ToList();
+ List csProjFile = [.. this.GetFiles($"{testsRoot}/**/*.csproj").Where(fp => fp.FullPath.EndsWith("Tests.csproj", StringComparison.InvariantCulture))];
var projMetadata = new List();
@@ -130,28 +128,85 @@ public IEnumerable GetProjMetadata()
return projMetadata;
}
- public void RunXUnitUsingMono(string targetFramework, string assemblyPath)
+ public void InstallMonoOnLinux()
{
- int exitCode = this.StartProcess(
- "mono", new ProcessSettings { Arguments = $"./testrunner/xunit.runner.console.2.8.1/tools/{targetFramework}/xunit.console.exe {assemblyPath}" });
+ int result = this.StartProcess("mono", new ProcessSettings
+ {
+ Arguments = "--version",
+ RedirectStandardOutput = true,
+ NoWorkingDirectory = true,
+ });
+
+ if (result == 0)
+ {
+ this.Information("✅ Mono is already installed. Skipping installation.");
+ return;
+ }
+
+ this.Information("Mono not found. Starting installation on Linux for .NET Framework test platform support...");
+
+ // Add Mono repository key
+ int exitCode1 = this.StartProcess("sudo", new ProcessSettings
+ {
+ Arguments = "apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF",
+ });
+
+ if (exitCode1 != 0)
+ {
+ this.Warning($"⚠️ Failed to add Mono repository key (exit code: {exitCode1})");
+ return;
+ }
+
+ // Add Mono repository
+ int exitCode2 = this.StartProcess("bash", new ProcessSettings
+ {
+ Arguments = "-c \"echo 'deb https://download.mono-project.com/repo/ubuntu focal main' | sudo tee /etc/apt/sources.list.d/mono-official-stable.list\"",
+ });
+
+ if (exitCode2 != 0)
+ {
+ this.Warning($"⚠️ Failed to add Mono repository (exit code: {exitCode2})");
+ return;
+ }
+
+ // Update package list
+ int exitCode3 = this.StartProcess("sudo", new ProcessSettings { Arguments = "apt update" });
+
+ if (exitCode3 != 0)
+ {
+ this.Warning($"⚠️ Failed to update package list (exit code: {exitCode3})");
+ return;
+ }
+
+ // Install Mono
+ int exitCode4 = this.StartProcess("sudo", new ProcessSettings { Arguments = "apt install -y mono-complete" });
- if (exitCode != 0)
+ if (exitCode4 != 0)
{
- throw new InvalidOperationException($"Exit code: {exitCode}");
+ this.Warning($"⚠️ Failed to install Mono (exit code: {exitCode4})");
+ this.Warning("This may cause .NET Framework tests to fail on Linux");
+ return;
}
+
+ this.Information("✅ Mono installation completed successfully");
}
public string GetProjectVersion()
{
- FilePath file = this.File("./src/Directory.Build.props");
+ if (UseDirectoryPropsVersion)
+ {
+ return GetDynamicVersionFromProps("PackageMainVersion");
+ }
+ // Original logic for backward compatibility
+ FilePath file = this.File("./src/Directory.Build.props");
this.Information(file.FullPath);
string project = File.ReadAllText(file.FullPath, Encoding.UTF8);
int startIndex = project.IndexOf("", StringComparison.Ordinal) + "".Length;
int endIndex = project.IndexOf("", startIndex, StringComparison.Ordinal);
- string version = project.Substring(startIndex, endIndex - startIndex);
+ string version = project[startIndex..endIndex];
version = $"{version}.{PackageVersion}";
return version;
@@ -159,20 +214,119 @@ public string GetProjectVersion()
public string GetExtensionProjectVersion()
{
- FilePath file = this.File(LocalStackClientExtProjFile);
+ if (UseDirectoryPropsVersion)
+ {
+ return GetDynamicVersionFromProps("PackageExtensionVersion");
+ }
+ // Original logic for backward compatibility
+ FilePath file = this.File(LocalStackClientExtProjFile);
this.Information(file.FullPath);
string project = File.ReadAllText(file.FullPath, Encoding.UTF8);
int startIndex = project.IndexOf("", StringComparison.Ordinal) + "".Length;
int endIndex = project.IndexOf("", startIndex, StringComparison.Ordinal);
- string version = project.Substring(startIndex, endIndex - startIndex);
+ string version = project[startIndex..endIndex];
version = $"{version}.{PackageVersion}";
return version;
}
+ ///
+ /// Gets the target frameworks for a specific package using the existing proven method
+ ///
+ /// The package identifier
+ /// Comma-separated target frameworks
+ public string GetPackageTargetFrameworks(string packageId)
+ {
+ if (!PackageIdProjMap.TryGetValue(packageId, out FilePath? projectFile) || projectFile == null)
+ {
+ throw new ArgumentException($"Unknown package ID: {packageId}", nameof(packageId));
+ }
+
+ string[] frameworks = GetProjectTargetFrameworks(projectFile.FullPath);
+ return string.Join(", ", frameworks);
+ }
+
+ ///
+ /// Generates dynamic version from Directory.Build.props with build metadata
+ ///
+ /// The property name to extract (PackageMainVersion or PackageExtensionVersion)
+ /// Version with build metadata (e.g., 2.0.0-preview1.20240715.a1b2c3d)
+ private string GetDynamicVersionFromProps(string versionPropertyName)
+ {
+ // Extract base version from Directory.Build.props
+ FilePath propsFile = this.File("../../Directory.Build.props");
+ string content = File.ReadAllText(propsFile.FullPath, Encoding.UTF8);
+
+ string startElement = $"<{versionPropertyName}>";
+ string endElement = $"{versionPropertyName}>";
+
+ int startIndex = content.IndexOf(startElement, StringComparison.Ordinal) + startElement.Length;
+ int endIndex = content.IndexOf(endElement, startIndex, StringComparison.Ordinal);
+
+ if (startIndex < startElement.Length || endIndex < 0)
+ {
+ throw new InvalidOperationException($"Could not find {versionPropertyName} in Directory.Build.props");
+ }
+
+ string baseVersion = content[startIndex..endIndex];
+
+ // Generate build metadata
+ string buildDate = DateTime.UtcNow.ToString("yyyyMMdd", System.Globalization.CultureInfo.InvariantCulture);
+ string commitSha = GetGitCommitSha();
+ string safeBranchName = BranchName.Replace('/', '-').Replace('_', '-');
+
+ // Simplified NuGet-compliant version format
+ if (BranchName == "master")
+ {
+ // For master: 2.0.0-preview1-20240715-a1b2c3d
+ return $"{baseVersion}-{buildDate}-{commitSha}";
+ }
+ else
+ {
+ // For feature branches: 2.0.0-preview1-feature-branch-20240715-a1b2c3d
+ return $"{baseVersion}-{safeBranchName}-{buildDate}-{commitSha}";
+ }
+ }
+
+ ///
+ /// Gets the short git commit SHA for version metadata
+ ///
+ /// Short commit SHA or timestamp fallback
+ private string GetGitCommitSha()
+ {
+ try
+ {
+ var processSettings = new ProcessSettings
+ {
+ Arguments = "rev-parse --short HEAD",
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ Silent = true,
+ };
+
+ var exitCode = this.StartProcess("git", processSettings, out IEnumerable output);
+
+ if (exitCode == 0 && output?.Any() == true)
+ {
+ string? commitSha = output.FirstOrDefault()?.Trim();
+ if (!string.IsNullOrEmpty(commitSha))
+ {
+ return commitSha;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ this.Warning($"Failed to get git commit SHA: {ex.Message}");
+ }
+
+ // Fallback to timestamp-based identifier
+ return DateTime.UtcNow.ToString("HHmmss", System.Globalization.CultureInfo.InvariantCulture);
+ }
+
private string[] GetProjectTargetFrameworks(string csprojPath)
{
FilePath file = this.File(csprojPath);
@@ -185,7 +339,7 @@ private string[] GetProjectTargetFrameworks(string csprojPath)
int startIndex = project.IndexOf(startElement, StringComparison.Ordinal) + startElement.Length;
int endIndex = project.IndexOf(endElement, startIndex, StringComparison.Ordinal);
- string targetFrameworks = project.Substring(startIndex, endIndex - startIndex);
+ string targetFrameworks = project[startIndex..endIndex];
return targetFrameworks.Split(';');
}
@@ -204,14 +358,14 @@ private string GetAssemblyName(string csprojPath)
int startIndex = project.IndexOf("", StringComparison.Ordinal) + "".Length;
int endIndex = project.IndexOf("", startIndex, StringComparison.Ordinal);
- assemblyName = project.Substring(startIndex, endIndex - startIndex);
+ assemblyName = project[startIndex..endIndex];
}
else
{
int startIndex = csprojPath.LastIndexOf('/') + 1;
int endIndex = csprojPath.IndexOf(".csproj", startIndex, StringComparison.Ordinal);
- assemblyName = csprojPath.Substring(startIndex, endIndex - startIndex);
+ assemblyName = csprojPath[startIndex..endIndex];
}
return assemblyName;
diff --git a/build/LocalStack.Build/ConsoleHelper.cs b/build/LocalStack.Build/ConsoleHelper.cs
new file mode 100644
index 0000000..b226587
--- /dev/null
+++ b/build/LocalStack.Build/ConsoleHelper.cs
@@ -0,0 +1,198 @@
+#pragma warning disable CA1515 // Consider making public types internal
+#pragma warning disable CA1055 // Change the return type of method 'ConsoleHelper.GetDownloadUrl(string, string, string, [string])' from 'string' to 'System.Uri'
+
+namespace LocalStack.Build;
+
+///
+/// Helper class for rich console output using Spectre.Console
+///
+public static class ConsoleHelper
+{
+ ///
+ /// Displays a large LocalStack.NET header with FigletText
+ ///
+ public static void WriteHeader()
+ {
+ AnsiConsole.Write(new FigletText("LocalStack.NET").LeftJustified().Color(Color.Blue));
+ }
+
+ ///
+ /// Displays a success message with green checkmark
+ ///
+ /// The success message to display
+ public static void WriteSuccess(string message)
+ {
+ AnsiConsole.MarkupLine($"[green]✅ {message.EscapeMarkup()}[/]");
+ }
+
+ ///
+ /// Displays a warning message with yellow warning symbol
+ ///
+ /// The warning message to display
+ public static void WriteWarning(string message)
+ {
+ AnsiConsole.MarkupLine($"[yellow]⚠️ {message.EscapeMarkup()}[/]");
+ }
+
+ ///
+ /// Displays an error message with red X symbol
+ ///
+ /// The error message to display
+ public static void WriteError(string message)
+ {
+ AnsiConsole.MarkupLine($"[red]❌ {message.EscapeMarkup()}[/]");
+ }
+
+ ///
+ /// Displays an informational message with blue info symbol
+ ///
+ /// The info message to display
+ public static void WriteInfo(string message)
+ {
+ AnsiConsole.MarkupLine($"[cyan]ℹ️ {message.EscapeMarkup()}[/]");
+ }
+
+ ///
+ /// Displays a processing message with gear symbol
+ ///
+ /// The processing message to display
+ public static void WriteProcessing(string message)
+ {
+ AnsiConsole.MarkupLine($"[yellow]🔧 {message.EscapeMarkup()}[/]");
+ }
+
+ ///
+ /// Displays a package-related message with package symbol
+ ///
+ /// The package message to display
+ public static void WritePackage(string message)
+ {
+ AnsiConsole.MarkupLine($"[cyan]📦 {message.EscapeMarkup()}[/]");
+ }
+
+ ///
+ /// Displays an upload/publish message with rocket symbol
+ ///
+ /// The upload message to display
+ public static void WriteUpload(string message)
+ {
+ AnsiConsole.MarkupLine($"[green]📤 {message.EscapeMarkup()}[/]");
+ }
+
+ ///
+ /// Creates and displays a package information table
+ ///
+ /// The package identifier
+ /// The package version
+ /// The target frameworks
+ /// The build configuration
+ /// The package source
+ public static void WritePackageInfoTable(string packageId, string version, string targetFrameworks, string buildConfig, string packageSource)
+ {
+ var table = new Table().Border(TableBorder.Rounded)
+ .BorderColor(Color.Grey)
+ .AddColumn(new TableColumn("[yellow]Property[/]").Centered())
+ .AddColumn(new TableColumn("[cyan]Value[/]").LeftAligned())
+ .AddRow("Package ID", packageId.EscapeMarkup())
+ .AddRow("Version", version.EscapeMarkup())
+ .AddRow("Target Frameworks", targetFrameworks.EscapeMarkup())
+ .AddRow("Build Configuration", buildConfig.EscapeMarkup())
+ .AddRow("Package Source", packageSource.EscapeMarkup());
+
+ AnsiConsole.Write(table);
+ AnsiConsole.WriteLine();
+ }
+
+ ///
+ /// Creates and displays a publication summary panel
+ ///
+ /// The package identifier
+ /// The package version
+ /// The package source
+ /// The download URL
+#pragma warning disable MA0006 // Use String.Create instead of string concatenation
+ public static void WritePublicationSummary(string packageId, string version, string packageSource, string downloadUrl)
+ {
+ var panel = new Panel(new Markup($"""
+ [bold]📦 Package:[/] {packageId.EscapeMarkup()}
+ [bold]🏷️ Version:[/] {version.EscapeMarkup()}
+ [bold]🎯 Published to:[/] {packageSource.EscapeMarkup()}
+ [bold]🔗 Download URL:[/] [link]{downloadUrl.EscapeMarkup()}[/]
+ """)).Header(new PanelHeader("[bold green]✅ Publication Complete[/]").Centered())
+ .BorderColor(Color.Green)
+ .Padding(1, 1);
+
+ AnsiConsole.Write(panel);
+ AnsiConsole.WriteLine();
+ }
+
+ ///
+ /// Executes a function with a progress bar
+ ///
+ /// Description of the operation
+ /// The action to execute with progress context
+ public static void WithProgress(string description, Action action)
+ {
+ AnsiConsole.Progress()
+ .Start(ctx =>
+ {
+ var task = ctx.AddTask($"[green]{description.EscapeMarkup()}[/]");
+ action(ctx);
+ task.Increment(100);
+ });
+ }
+
+ ///
+ /// Displays a rule separator with optional text
+ ///
+ /// Optional title for the rule
+ public static void WriteRule(string title = "")
+ {
+ var rule = string.IsNullOrEmpty(title) ? new Rule() : new Rule($"[bold blue]{title.EscapeMarkup()}[/]");
+
+ AnsiConsole.Write(rule);
+ }
+
+ ///
+ /// Displays version generation information
+ ///
+ /// The base version from Directory.Build.props
+ /// The final generated version with metadata
+ /// The build date
+ /// The git commit SHA
+ /// The git branch name
+ public static void WriteVersionInfo(string baseVersion, string finalVersion, string buildDate, string commitSha, string branchName)
+ {
+ var table = new Table().Border(TableBorder.Simple)
+ .BorderColor(Color.Grey)
+ .AddColumn(new TableColumn("[yellow]Version Component[/]").Centered())
+ .AddColumn(new TableColumn("[cyan]Value[/]").LeftAligned())
+ .AddRow("Base Version", baseVersion.EscapeMarkup())
+ .AddRow("Build Date", buildDate.EscapeMarkup())
+ .AddRow("Commit SHA", commitSha.EscapeMarkup())
+ .AddRow("Branch", branchName.EscapeMarkup())
+ .AddRow("[bold]Final Version[/]", $"[bold green]{finalVersion.EscapeMarkup()}[/]");
+
+ AnsiConsole.Write(table);
+ AnsiConsole.WriteLine();
+ }
+
+ ///
+ /// Generates a download URL based on package source
+ ///
+ /// The package source (github, nuget, myget)
+ /// The package identifier
+ /// The package version
+ /// The repository owner (for GitHub packages)
+ /// The download URL
+ public static string GetDownloadUrl(string packageSource, string packageId, string version, string repositoryOwner = "localstack-dotnet")
+ {
+ return packageSource?.ToUpperInvariant() switch
+ {
+ "GITHUB" => $"https://github.com/{repositoryOwner}/localstack-dotnet-client/packages",
+ "NUGET" => $"https://www.nuget.org/packages/{packageId}/{version}",
+ "MYGET" => $"https://www.myget.org/packages/{packageId}",
+ _ => "Package published successfully",
+ };
+ }
+}
\ No newline at end of file
diff --git a/build/LocalStack.Build/GlobalUsings.cs b/build/LocalStack.Build/GlobalUsings.cs
index e0ccdec..14ccd72 100644
--- a/build/LocalStack.Build/GlobalUsings.cs
+++ b/build/LocalStack.Build/GlobalUsings.cs
@@ -2,15 +2,17 @@
global using Cake.Common.Diagnostics;
global using Cake.Common.IO;
global using Cake.Common.IO.Paths;
+global using Cake.Common.Tools.DotNet;
global using Cake.Common.Tools.DotNet.MSBuild;
global using Cake.Common.Tools.NuGet;
-global using Cake.Common.Tools.NuGet.Install;
global using Cake.Common.Tools.NuGet.List;
global using Cake.Core;
global using Cake.Core.IO;
global using Cake.Docker;
global using Cake.Frosting;
+global using Spectre.Console;
+
global using LocalStack.Build;
global using LocalStack.Build.Models;
@@ -22,7 +24,6 @@
global using System.Text;
global using System.Text.RegularExpressions;
-global using Cake.Common.Tools.DotNet;
global using Cake.Common.Tools.DotNet.Build;
global using Cake.Common.Tools.DotNet.NuGet.Push;
global using Cake.Common.Tools.DotNet.Pack;
diff --git a/build/LocalStack.Build/LocalStack.Build.csproj b/build/LocalStack.Build/LocalStack.Build.csproj
index 0840af6..4b263e5 100644
--- a/build/LocalStack.Build/LocalStack.Build.csproj
+++ b/build/LocalStack.Build/LocalStack.Build.csproj
@@ -5,7 +5,7 @@
$(MSBuildProjectDirectory)
latest
- $(NoWarn);CA1303;CA1707;CS8601;CS8618;MA0047;MA0048;CA1050;S3903;MA0006;CA1031;CA1062;MA0051;S112;CA2201;CA1307;MA0074;MA0023;MA0009;CA1307;CA1310;CA1515
+ $(NoWarn);CA1303;CA1707;CS8601;CS8618;MA0047;MA0048;CA1050;S3903;MA0006;CA1031;CA1062;MA0051;S112;CA2201;CA1307;MA0074;MA0023;MA0009;CA1307;CA1310;CA1515;CA1054;CA1055
diff --git a/build/LocalStack.Build/Program.cs b/build/LocalStack.Build/Program.cs
index c8a5855..dfe0c52 100644
--- a/build/LocalStack.Build/Program.cs
+++ b/build/LocalStack.Build/Program.cs
@@ -12,6 +12,9 @@ public sealed class InitTask : FrostingTask
{
public override void Run(BuildContext context)
{
+ ConsoleHelper.WriteHeader();
+ ConsoleHelper.WriteRule("Initialization");
+
context.StartProcess("dotnet", new ProcessSettings { Arguments = "--info" });
if (!context.IsRunningOnUnix())
@@ -20,14 +23,10 @@ public override void Run(BuildContext context)
}
context.StartProcess("git", new ProcessSettings { Arguments = "config --global core.autocrlf true" });
-
- context.StartProcess("mono", new ProcessSettings { Arguments = "--version" });
-
- context.InstallXUnitNugetPackage();
}
}
-[TaskName("build"), IsDependentOn(typeof(InitTask)),]
+[TaskName("build"), IsDependentOn(typeof(InitTask))]
public sealed class BuildTask : FrostingTask
{
public override void Run(BuildContext context)
@@ -45,7 +44,7 @@ public override void Run(BuildContext context)
var settings = new DotNetTestSettings
{
- NoRestore = !context.ForceRestore, NoBuild = !context.ForceBuild, Configuration = context.BuildConfiguration, Blame = true
+ NoRestore = !context.ForceRestore, NoBuild = !context.ForceBuild, Configuration = context.BuildConfiguration, Blame = true,
};
IEnumerable projMetadata = context.GetProjMetadata();
@@ -53,7 +52,7 @@ public override void Run(BuildContext context)
foreach (ProjMetadata testProj in projMetadata)
{
string testProjectPath = testProj.CsProjPath;
- string targetFrameworks = string.Join(",", testProj.TargetFrameworks);
+ string targetFrameworks = string.Join(',', testProj.TargetFrameworks);
context.Warning($"Target Frameworks {targetFrameworks}");
@@ -82,7 +81,7 @@ public override void Run(BuildContext context)
{
context.Warning(psOutput);
- string[] containers = psOutput.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
+ string[] containers = psOutput.Split([Environment.NewLine], StringSplitOptions.None);
context.DockerRm(containers);
}
}
@@ -92,21 +91,19 @@ public override void Run(BuildContext context)
}
}
- if (context.IsRunningOnLinux() && targetFramework == "net462")
- {
- context.Warning("Temporarily disabled running net462 tests on Linux because of a problem in mono runtime");
- }
- else if (context.IsRunningOnMacOs() && targetFramework == "net462")
- {
- context.RunXUnitUsingMono(targetFramework, $"{testProj.DirectoryPath}/bin/{context.BuildConfiguration}/{targetFramework}/{testProj.AssemblyName}.dll");
- }
- else
+ // .NET Framework testing on non-Windows platforms
+ // - Modern .NET includes built-in Mono runtime
+ // - Test platform still requires external Mono installation on Linux
+ if (targetFramework == "net472" && !context.IsRunningOnWindows())
{
- string testFilePrefix = targetFramework.Replace(".", "-");
- settings.ArgumentCustomization = args => args.Append($" --logger \"trx;LogFileName={testFilePrefix}_{testResults}\"");
- context.DotNetTest(testProjectPath, settings);
+ string platform = context.IsRunningOnLinux() ? "Linux (with external Mono)" : "macOS (built-in Mono)";
+ context.Information($"Running .NET Framework tests on {platform}");
}
+ string testFilePrefix = targetFramework.Replace('.', '-');
+ settings.ArgumentCustomization = args => args.Append($" --logger \"trx;LogFileName={testFilePrefix}_{testResults}\"");
+ context.DotNetTest(testProjectPath, settings);
+
context.Warning("==============================================================");
}
}
@@ -118,50 +115,192 @@ public sealed class NugetPackTask : FrostingTask
{
public override void Run(BuildContext context)
{
- ValidatePackageVersion(context);
+ // Display header
+ ConsoleHelper.WriteRule("Package Creation");
+
+ // If no specific package ID is provided, pack all packages
+ if (string.IsNullOrEmpty(context.PackageId))
+ {
+ PackAllPackages(context);
+ }
+ else
+ {
+ PackSinglePackage(context, context.PackageId);
+ }
+
+ ConsoleHelper.WriteRule();
+ }
+
+ private static void PackAllPackages(BuildContext context)
+ {
+ foreach (string packageId in context.PackageIdProjMap.Keys)
+ {
+ ConsoleHelper.WriteInfo($"Creating package: {packageId}");
+ PackSinglePackage(context, packageId);
+ }
+ }
+
+ private static void PackSinglePackage(BuildContext context, string packageId)
+ {
+ // Get effective version using enhanced methods
+ string effectiveVersion = GetEffectiveVersion(context, packageId);
+
+ // Display package information
+ ConsoleHelper.WritePackageInfoTable(packageId, effectiveVersion, GetTargetFrameworks(context, packageId), context.BuildConfiguration, context.PackageSource);
+
+ // Validate inputs
+ ValidatePackageInputs(context, packageId, effectiveVersion);
+
+ // Handle Extensions project dependency switching if needed
+ if (packageId == BuildContext.LocalStackClientExtensionsProjName &&
+ context is
+ {
+ PackageSource: BuildContext.GitHubPackageSource,
+ UseDirectoryPropsVersion: false,
+ })
+ {
+ PrepareExtensionsProject(context, effectiveVersion);
+ }
+ // Create packages with progress indication
+ ConsoleHelper.WithProgress($"Creating {packageId} package", _ => CreatePackage(context, packageId, effectiveVersion));
+
+ // Success message
+ ConsoleHelper.WriteSuccess($"Successfully created {packageId} v{effectiveVersion}");
+ ConsoleHelper.WriteInfo($"Package location: {context.ArtifactOutput}");
+ }
+
+ private static string GetEffectiveVersion(BuildContext context, string packageId)
+ {
+ return packageId switch
+ {
+ BuildContext.LocalStackClientProjName => context.GetProjectVersion(),
+ BuildContext.LocalStackClientExtensionsProjName => context.GetExtensionProjectVersion(),
+ _ => throw new ArgumentException($"Unknown package ID: {packageId}", nameof(packageId)),
+ };
+ }
+
+ private static string GetTargetFrameworks(BuildContext context, string packageId)
+ {
+ // Use the existing proven method to get actual target frameworks
+ return context.GetPackageTargetFrameworks(packageId);
+ }
+
+ private static void PrepareExtensionsProject(BuildContext context, string version)
+ {
+ ConsoleHelper.WriteProcessing("Updating Extensions project dependencies...");
+
+ try
+ {
+ // Set working directory to Extensions project
+ var originalWorkingDir = context.Environment.WorkingDirectory;
+ context.Environment.WorkingDirectory = context.LocalStackClientExtFolder;
+
+ try
+ {
+ // Remove project reference using Cake built-in method
+ var projectRef = context.File("../LocalStack.Client/LocalStack.Client.csproj");
+ context.DotNetRemoveReference([projectRef]);
+ ConsoleHelper.WriteInfo("Removed project reference to LocalStack.Client");
+
+ // Add package reference using Cake built-in method
+ context.DotNetAddPackage(BuildContext.LocalStackClientProjName, version);
+ ConsoleHelper.WriteSuccess($"Added package reference for {BuildContext.LocalStackClientProjName} v{version}");
+ }
+ finally
+ {
+ // Restore original working directory
+ context.Environment.WorkingDirectory = originalWorkingDir;
+ }
+ }
+ catch (Exception ex)
+ {
+ ConsoleHelper.WriteError($"Failed to prepare Extensions project: {ex.Message}");
+
+ throw;
+ }
+ }
+
+ private static void CreatePackage(BuildContext context, string packageId, string effectiveVersion)
+ {
if (!Directory.Exists(context.ArtifactOutput))
{
Directory.CreateDirectory(context.ArtifactOutput);
}
- FilePath packageCsProj = context.PackageIdProjMap[context.PackageId];
+ if (!context.PackageIdProjMap.TryGetValue(packageId, out FilePath? packageCsProj) || packageCsProj == null)
+ {
+ throw new ArgumentException($"Unknown package ID: {packageId}", nameof(packageId));
+ }
var settings = new DotNetPackSettings
{
- Configuration = context.BuildConfiguration, OutputDirectory = context.ArtifactOutput, MSBuildSettings = new DotNetMSBuildSettings()
+ Configuration = context.BuildConfiguration,
+ OutputDirectory = context.ArtifactOutput,
+ NoBuild = false,
+ NoRestore = false,
+ MSBuildSettings = new DotNetMSBuildSettings(),
};
- settings.MSBuildSettings.SetVersion(context.PackageVersion);
+ settings.MSBuildSettings.SetVersion(effectiveVersion);
context.DotNetPack(packageCsProj.FullPath, settings);
}
- private static void ValidatePackageVersion(BuildContext context)
+ private static void ValidatePackageInputs(BuildContext context, string packageId, string effectiveVersion)
{
- BuildContext.ValidateArgument("package-id", context.PackageId);
- BuildContext.ValidateArgument("package-version", context.PackageVersion);
+ BuildContext.ValidateArgument("package-id", packageId);
BuildContext.ValidateArgument("package-source", context.PackageSource);
- Match match = Regex.Match(context.PackageVersion, @"^(\d+)\.(\d+)\.(\d+)(\.(\d+))*$", RegexOptions.IgnoreCase);
+ // Skip detailed version validation when using directory props version
+ if (context.UseDirectoryPropsVersion)
+ {
+ ConsoleHelper.WriteInfo("Using dynamic version generation from Directory.Build.props");
+
+ return;
+ }
+
+ // Original validation for manual version input
+ ValidatePackageVersion(context, effectiveVersion);
+ }
+
+ private static void ValidatePackageVersion(BuildContext context, string version)
+ {
+ Match match = Regex.Match(version, @"^(\d+)\.(\d+)\.(\d+)([\.\-].*)*$", RegexOptions.IgnoreCase);
if (!match.Success)
{
- throw new Exception($"Invalid version: {context.PackageVersion}");
+ throw new Exception($"Invalid version: {version}");
}
- string packageSource = context.PackageSourceMap[context.PackageSource];
+ // Skip version validation for GitHub Packages - allows overwriting dev builds
+ if (context.PackageSource == BuildContext.GitHubPackageSource)
+ {
+ ConsoleHelper.WriteInfo("Skipping version validation for GitHub Packages source");
+
+ return;
+ }
- var nuGetListSettings = new NuGetListSettings { AllVersions = false, Source = new List() { packageSource } };
- NuGetListItem nuGetListItem = context.NuGetList(context.PackageId, nuGetListSettings).Single(item => item.Name == context.PackageId);
- string latestPackVersionStr = nuGetListItem.Version;
+ try
+ {
+ string packageSource = context.PackageSourceMap[context.PackageSource];
+ var nuGetListSettings = new NuGetListSettings { AllVersions = false, Source = [packageSource] };
+ NuGetListItem nuGetListItem = context.NuGetList(context.PackageId, nuGetListSettings).Single(item => item.Name == context.PackageId);
+ string latestPackVersionStr = nuGetListItem.Version;
- Version packageVersion = Version.Parse(context.PackageVersion);
- Version latestPackVersion = Version.Parse(latestPackVersionStr);
+ Version packageVersion = Version.Parse(version);
+ Version latestPackVersion = Version.Parse(latestPackVersionStr);
- if (packageVersion <= latestPackVersion)
+ if (packageVersion <= latestPackVersion)
+ {
+ throw new Exception($"The new package version {version} should be greater than the latest package version {latestPackVersionStr}");
+ }
+
+ ConsoleHelper.WriteSuccess($"Version validation passed: {version} > {latestPackVersionStr}");
+ }
+ catch (Exception ex) when (ex is not InvalidOperationException)
{
- throw new Exception($"The new package version {context.PackageVersion} should be greater than the latest package version {latestPackVersionStr}");
+ ConsoleHelper.WriteWarning($"Could not validate version against existing packages: {ex.Message}");
}
}
}
@@ -170,25 +309,104 @@ private static void ValidatePackageVersion(BuildContext context)
public sealed class NugetPushTask : FrostingTask
{
public override void Run(BuildContext context)
+ {
+ // Display header
+ ConsoleHelper.WriteRule("Package Publishing");
+
+ // Get effective version using enhanced methods
+ string effectiveVersion = GetEffectiveVersion(context);
+
+ // Validate inputs
+ ValidatePublishInputs(context, effectiveVersion);
+
+ // Display package information
+ ConsoleHelper.WritePackageInfoTable(context.PackageId, effectiveVersion, GetTargetFrameworks(context), context.BuildConfiguration, context.PackageSource);
+
+ // Perform publishing with progress indication
+ ConsoleHelper.WithProgress("Publishing package", progressCtx =>
+ {
+ PublishPackage(context, effectiveVersion);
+ });
+
+ // Success message with download URL
+ var downloadUrl = ConsoleHelper.GetDownloadUrl(context.PackageSource, context.PackageId, effectiveVersion);
+ ConsoleHelper.WritePublicationSummary(context.PackageId, effectiveVersion, context.PackageSource, downloadUrl);
+ ConsoleHelper.WriteRule();
+ }
+
+ private static string GetEffectiveVersion(BuildContext context)
+ {
+ return context.PackageId switch
+ {
+ BuildContext.LocalStackClientProjName => context.GetProjectVersion(),
+ BuildContext.LocalStackClientExtensionsProjName => context.GetExtensionProjectVersion(),
+ _ => throw new ArgumentException($"Unknown package ID: {context.PackageId}", nameof(context)),
+ };
+ }
+
+ private static string GetTargetFrameworks(BuildContext context)
+ {
+ return context.GetPackageTargetFrameworks(context.PackageId);
+ }
+
+ private static void ValidatePublishInputs(BuildContext context, string effectiveVersion)
{
BuildContext.ValidateArgument("package-id", context.PackageId);
- BuildContext.ValidateArgument("package-version", context.PackageVersion);
BuildContext.ValidateArgument("package-secret", context.PackageSecret);
BuildContext.ValidateArgument("package-source", context.PackageSource);
- string packageId = context.PackageId;
- string packageVersion = context.PackageVersion;
+ // For dynamic version generation, validate the effective version instead of PackageVersion
+ if (context.UseDirectoryPropsVersion)
+ {
+ ConsoleHelper.WriteInfo($"Using dynamic version: {effectiveVersion}");
+ }
+ else
+ {
+ BuildContext.ValidateArgument("package-version", context.PackageVersion);
+ }
+ }
- ConvertableFilePath packageFile = context.ArtifactOutput + context.File($"{packageId}.{packageVersion}.nupkg");
+ private static void PublishPackage(BuildContext context, string effectiveVersion)
+ {
+ // Use the effective version for both dynamic and manual version scenarios
+ string packageVersion = context.UseDirectoryPropsVersion ? effectiveVersion : context.PackageVersion;
+
+ ConvertableFilePath packageFile = context.ArtifactOutput + context.File($"{context.PackageId}.{packageVersion}.nupkg");
if (!context.FileExists(packageFile))
{
- throw new Exception($"The specified {packageFile.Path} package file does not exists");
+ throw new Exception($"The specified {packageFile.Path} package file does not exist");
}
string packageSecret = context.PackageSecret;
string packageSource = context.PackageSourceMap[context.PackageSource];
+ ConsoleHelper.WriteUpload($"Publishing {context.PackageId} to {context.PackageSource}...");
+
context.DotNetNuGetPush(packageFile.Path.FullPath, new DotNetNuGetPushSettings() { ApiKey = packageSecret, Source = packageSource, });
+
+ ConsoleHelper.WriteSuccess($"Successfully published {context.PackageId} v{packageVersion}");
+ }
+}
+
+[TaskName("nuget-pack-and-publish")]
+public sealed class NugetPackAndPublishTask : FrostingTask
+{
+ public override void Run(BuildContext context)
+ {
+ ConsoleHelper.WriteRule("Pack & Publish Pipeline");
+
+ // First pack the package
+ ConsoleHelper.WriteProcessing("Step 1: Creating package...");
+ var packTask = new NugetPackTask();
+ packTask.Run(context);
+
+ // Then publish the package
+ ConsoleHelper.WriteProcessing("Step 2: Publishing package...");
+ var pushTask = new NugetPushTask();
+ pushTask.Run(context);
+
+ ConsoleHelper.WriteSuccess("Pack & Publish pipeline completed successfully!");
+ ConsoleHelper.WriteRule();
}
}
\ No newline at end of file
diff --git a/build/LocalStack.Build/SummaryTask.cs b/build/LocalStack.Build/SummaryTask.cs
new file mode 100644
index 0000000..15a3a72
--- /dev/null
+++ b/build/LocalStack.Build/SummaryTask.cs
@@ -0,0 +1,168 @@
+using System.Globalization;
+
+[TaskName("workflow-summary")]
+public sealed class SummaryTask : FrostingTask
+{
+ private const string GitHubOwner = "localstack-dotnet";
+
+ public override void Run(BuildContext context)
+ {
+ ConsoleHelper.WriteRule("Build Summary");
+
+ GenerateBuildSummary(context);
+ GenerateInstallationInstructions(context);
+ GenerateMetadataTable(context);
+
+ ConsoleHelper.WriteRule();
+ }
+
+ private static void GenerateBuildSummary(BuildContext context)
+ {
+ var panel = new Panel(GetSummaryContent(context))
+ .Border(BoxBorder.Rounded)
+ .BorderColor(Color.Green)
+ .Header("[bold green]✅ Build Complete[/]")
+ .HeaderAlignment(Justify.Center);
+
+ AnsiConsole.Write(panel);
+ AnsiConsole.WriteLine();
+ }
+
+ private static string GetSummaryContent(BuildContext context)
+ {
+ var content = new StringBuilder();
+
+ if (string.IsNullOrEmpty(context.PackageId))
+ {
+ // Summary for all packages
+ content.AppendLine(CultureInfo.InvariantCulture, $"[bold]📦 Packages Built:[/]");
+
+ foreach (string packageId in context.PackageIdProjMap.Keys)
+ {
+ string version = GetPackageVersion(context, packageId);
+ content.AppendLine(CultureInfo.InvariantCulture, $" • [cyan]{packageId}[/] [yellow]v{version}[/]");
+ }
+ }
+ else
+ {
+ // Summary for specific package
+ string version = GetPackageVersion(context, context.PackageId);
+ content.AppendLine(CultureInfo.InvariantCulture, $"[bold]📦 Package:[/] [cyan]{context.PackageId}[/]");
+ content.AppendLine(CultureInfo.InvariantCulture, $"[bold]🏷️ Version:[/] [yellow]{version}[/]");
+ content.AppendLine(CultureInfo.InvariantCulture, $"[bold]🎯 Target:[/] [blue]{context.PackageSource}[/]");
+ content.AppendLine(CultureInfo.InvariantCulture, $"[bold]⚙️ Config:[/] [green]{context.BuildConfiguration}[/]");
+ }
+
+ return content.ToString().TrimEnd();
+ }
+
+ private static void GenerateInstallationInstructions(BuildContext context)
+ {
+ var panel = new Panel(GetInstallationContent(context))
+ .Border(BoxBorder.Rounded)
+ .BorderColor(Color.Blue)
+ .Header("[bold blue]🚀 Installation Instructions[/]")
+ .HeaderAlignment(Justify.Center);
+
+ AnsiConsole.Write(panel);
+ AnsiConsole.WriteLine();
+ }
+
+ private static string GetInstallationContent(BuildContext context)
+ {
+ var content = new StringBuilder();
+
+ if (context.PackageSource == BuildContext.GitHubPackageSource)
+ {
+ content.AppendLine("[bold]1. Add GitHub Packages source:[/]");
+ content.AppendLine(CultureInfo.InvariantCulture, $"[grey]dotnet nuget add source https://nuget.pkg.github.com/{GitHubOwner}/index.json \\[/]");
+ content.AppendLine("[grey] --name github-localstack \\[/]");
+ content.AppendLine("[grey] --username YOUR_USERNAME \\[/]");
+ content.AppendLine("[grey] --password YOUR_GITHUB_TOKEN[/]");
+ content.AppendLine();
+ }
+
+ content.AppendLine("[bold]2. Install package(s):[/]");
+
+ if (string.IsNullOrEmpty(context.PackageId))
+ {
+ // Installation for all packages
+ foreach (string packageId in context.PackageIdProjMap.Keys)
+ {
+ string version = GetPackageVersion(context, packageId);
+ content.AppendLine(GetInstallCommand(packageId, version, context.PackageSource));
+ }
+ }
+ else
+ {
+ // Installation for specific package
+ string version = GetPackageVersion(context, context.PackageId);
+ content.AppendLine(GetInstallCommand(context.PackageId, version, context.PackageSource));
+ }
+
+ return content.ToString().TrimEnd();
+ }
+
+ private static string GetInstallCommand(string packageId, string version, string packageSource)
+ {
+ string sourceFlag = packageSource == BuildContext.GitHubPackageSource ? " --source github-localstack" : "";
+ return $"[grey]dotnet add package {packageId} --version {version}{sourceFlag}[/]";
+ }
+
+ private static void GenerateMetadataTable(BuildContext context)
+ {
+ var table = new Table()
+ .Border(TableBorder.Rounded)
+ .BorderColor(Color.Grey)
+ .Title("[bold]📊 Build Metadata[/]")
+ .AddColumn("[yellow]Property[/]")
+ .AddColumn("[cyan]Value[/]");
+
+ // Add build information
+ table.AddRow("Build Date", DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss UTC", CultureInfo.InvariantCulture));
+ table.AddRow("Build Configuration", context.BuildConfiguration);
+
+ if (context.UseDirectoryPropsVersion)
+ {
+ table.AddRow("Version Source", "Directory.Build.props (Dynamic)");
+ table.AddRow("Branch Name", context.BranchName);
+
+ try
+ {
+ // Simply skip git commit info since the method is private
+ table.AddRow("Git Commit", "See build output");
+ }
+ catch
+ {
+ table.AddRow("Git Commit", "Not available");
+ }
+ }
+ else
+ {
+ table.AddRow("Version Source", "Manual");
+ }
+
+ // Add package information
+ if (!string.IsNullOrEmpty(context.PackageId))
+ {
+ string targetFrameworks = context.GetPackageTargetFrameworks(context.PackageId);
+ table.AddRow("Target Frameworks", targetFrameworks);
+
+ string downloadUrl = ConsoleHelper.GetDownloadUrl(context.PackageSource, context.PackageId, GetPackageVersion(context, context.PackageId));
+ table.AddRow("Download URL", downloadUrl);
+ }
+
+ AnsiConsole.Write(table);
+ AnsiConsole.WriteLine();
+ }
+
+ private static string GetPackageVersion(BuildContext context, string packageId)
+ {
+ return packageId switch
+ {
+ BuildContext.LocalStackClientProjName => context.GetProjectVersion(),
+ BuildContext.LocalStackClientExtensionsProjName => context.GetExtensionProjectVersion(),
+ _ => "Unknown",
+ };
+ }
+}
diff --git a/src/LocalStack.Client.Extensions/AwsClientFactoryWrapper.cs b/src/LocalStack.Client.Extensions/AwsClientFactoryWrapper.cs
index 19bb4bb..a707578 100644
--- a/src/LocalStack.Client.Extensions/AwsClientFactoryWrapper.cs
+++ b/src/LocalStack.Client.Extensions/AwsClientFactoryWrapper.cs
@@ -5,39 +5,36 @@ namespace LocalStack.Client.Extensions;
public sealed class AwsClientFactoryWrapper : IAwsClientFactoryWrapper
{
- private static readonly string ClientFactoryFullName = "Amazon.Extensions.NETCore.Setup.ClientFactory";
+ private static readonly string ClientFactoryGenericTypeName = "Amazon.Extensions.NETCore.Setup.ClientFactory`1";
private static readonly string CreateServiceClientMethodName = "CreateServiceClient";
public AmazonServiceClient CreateServiceClient(IServiceProvider provider, AWSOptions? awsOptions) where TClient : IAmazonService
{
- Type? clientFactoryType = typeof(ConfigurationException).Assembly.GetType(ClientFactoryFullName);
+ Type? genericFactoryType = typeof(ConfigurationException).Assembly.GetType(ClientFactoryGenericTypeName);
- if (clientFactoryType == null)
+ if (genericFactoryType == null)
{
- throw new LocalStackClientConfigurationException($"Failed to find internal ClientFactory in {ClientFactoryFullName}");
+ throw new LocalStackClientConfigurationException($"Failed to find internal ClientFactory in {ClientFactoryGenericTypeName}");
}
- ConstructorInfo? constructorInfo =
- clientFactoryType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(Type), typeof(AWSOptions) }, null);
+ // Create ClientFactory
+ Type concreteFactoryType = genericFactoryType.MakeGenericType(typeof(TClient));
+ ConstructorInfo? constructor = concreteFactoryType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(AWSOptions) }, null);
- if (constructorInfo == null)
+ if (constructor == null)
{
- throw new LocalStackClientConfigurationException("ClientFactory missing a constructor with parameters Type and AWSOptions.");
+ throw new LocalStackClientConfigurationException("ClientFactory missing constructor with AWSOptions parameter.");
}
- Type clientType = typeof(TClient);
+ object factory = constructor.Invoke(new object[] { awsOptions! });
+ MethodInfo? createMethod = factory.GetType().GetMethod(CreateServiceClientMethodName, BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(IServiceProvider) }, null);
- object clientFactory = constructorInfo.Invoke(new object[] { clientType, awsOptions! });
-
- MethodInfo? methodInfo = clientFactory.GetType().GetMethod(CreateServiceClientMethodName, BindingFlags.NonPublic | BindingFlags.Instance);
-
- if (methodInfo == null)
+ if (createMethod == null)
{
- throw new LocalStackClientConfigurationException($"Failed to find internal method {CreateServiceClientMethodName} in {ClientFactoryFullName}");
+ throw new LocalStackClientConfigurationException($"ClientFactory missing {CreateServiceClientMethodName}(IServiceProvider) method.");
}
- object serviceInstance = methodInfo.Invoke(clientFactory, new object[] { provider });
-
+ object serviceInstance = createMethod.Invoke(factory, new object[] { provider });
return (AmazonServiceClient)serviceInstance;
}
}
\ No newline at end of file
diff --git a/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj b/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj
index 32eaa4d..e0aec55 100644
--- a/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj
+++ b/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj
@@ -1,64 +1,75 @@
-
- netstandard2.0;net8.0;net9.0
- LocalStack.Client.Extensions
- LocalStack.Client.Extensions
- $(PackageExtensionVersion)
+
+ netstandard2.0;net8.0;net9.0
+ LocalStack.Client.Extensions
+ LocalStack.Client.Extensions
+ $(PackageExtensionVersion)
- LocalStack.NET Client
-
- Extensions for the LocalStack.NET Client to integrate with .NET Core configuration and dependency injection frameworks. The extensions also provides wrapper around AWSSDK.Extensions.NETCore.Setup to use both LocalStack and AWS side-by-side
-
- aws-sdk, localstack, client-library, dotnet, dotnet-core
- LICENSE.txt
- README.md
- true
- 1.2.2
- true
- true
- $(NoWarn);CA1510
-
+ LocalStack.NET Client
+
+ Extensions for the LocalStack.NET Client to integrate with .NET Core configuration and dependency injection frameworks. The extensions also provides wrapper around AWSSDK.Extensions.NETCore.Setup to use both LocalStack and AWS side-by-side
+
+ aws-sdk, localstack, client-library, dotnet, dotnet-core
+ LICENSE.txt
+ README.md
+ true
+ 1.2.2
+ true
+ true
+ $(NoWarn);CA1510
+
-
- true
-
+
+ true
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
- Always
-
-
- Always
-
-
-
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+ Always
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/LocalStack.Client.Extensions/README.md b/src/LocalStack.Client.Extensions/README.md
index 4bec638..51c4faa 100644
--- a/src/LocalStack.Client.Extensions/README.md
+++ b/src/LocalStack.Client.Extensions/README.md
@@ -1,21 +1,42 @@
-# LocalStack .NET Client  [](https://www.nuget.org/packages/LocalStack.Client/) [](https://localstack-dotnet.testspace.com/spaces/232580?utm_campaign=metric&utm_medium=referral&utm_source=badge "Test Cases")
+# LocalStack .NET Client  [](https://www.nuget.org/packages/LocalStack.Client/) [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/ci.yml) [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/security.yml)
+
+> ## ⚠️ AWS SDK v4 Transition Notice
+>
+> **Current Status**: This main branch is under active development for **AWS SDK v4 support (v2.0)**
+>
+> **Version Strategy**:
+> - **v1.x** (AWS SDK v3): Maintenance mode → Available on [sdkv3-lts branch](https://github.com/localstack-dotnet/localstack-dotnet-client/tree/sdkv3-lts) (EOL: July 31, 2026)
+> - **v2.x** (AWS SDK v4): Active development → Native AOT support in subsequent v2.x releases
+>
+> **Migration Timeline**: Q3 2025 for v2.0.0 GA
+>
+> 📖 **[Read Full Roadmap & Migration Guide →](../../discussions)**

Localstack.NET is an easy-to-use .NET client for [LocalStack](https://github.com/localstack/localstack), a fully functional local AWS cloud stack. The client library provides a thin wrapper around [aws-sdk-net](https://github.com/aws/aws-sdk-net) which automatically configures the target endpoints to use LocalStack for your local cloud application development.
-| Package | Stable | Nightly |
+## Version Compatibility
+
+| LocalStack.NET Version | AWS SDK Version | .NET Support | Status | Branch |
+|------------------------|-----------------|--------------|---------|---------|
+| v1.x | AWS SDK v3 | .NET 8, 9, Standard 2.0, Framework 4.6.2 | Maintenance (until July 2026) | [sdkv3-lts](../../tree/sdkv3-lts) |
+| v2.x | AWS SDK v4 | .NET 8, 9, Standard 2.0, Framework 4.7.2 | Active Development | [master](../../tree/main) |
+
+## Package Status
+
+| Package | v1.x (AWS SDK v3) | v2.x (AWS SDK v4) - Development |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| LocalStack.Client | [](https://www.nuget.org/packages/LocalStack.Client/) | [](https://www.myget.org/feed/localstack-dotnet-client/package/nuget/LocalStack.Client) |
-| LocalStack.Client.Extensions | [](https://www.nuget.org/packages/LocalStack.Client.Extensions/) | [](https://www.myget.org/feed/localstack-dotnet-client/package/nuget/LocalStack.Client.Extensions) |
+| LocalStack.Client | [](https://www.nuget.org/packages/LocalStack.Client/) | [](https://github.com/localstack-dotnet/localstack-dotnet-client/packages) |
+| LocalStack.Client.Extensions | [](https://www.nuget.org/packages/LocalStack.Client.Extensions/) | [](https://github.com/localstack-dotnet/localstack-dotnet-client/packages) |
## Continuous Integration
-| Build server | Platform | Build status |
-| -------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| Github Actions | Ubuntu | [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-ubuntu.yml) |
-| Github Actions | Windows | [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-windows.yml) |
-| Github Actions | macOS | [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-macos.yml) |
+| Build Platform | Status | Description |
+|----------------|--------|-------------|
+| **Cross-Platform CI** | [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/ci.yml) | Matrix testing: Windows, Linux, macOS |
+| **Security Analysis** | [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/security.yml) | CodeQL analysis & dependency review |
+| **Automated Publishing** | [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/publish-dev-github.yml) | Daily GitHub Packages builds |
## Table of Contents
@@ -60,13 +81,37 @@ For detailed installation and setup instructions, please refer to the [official
## Getting Started
-LocalStack.NET is installed from NuGet. To work with LocalStack in your .NET applications, you'll need the main library and its extensions. Here's how you can install them:
+LocalStack.NET is available through multiple package sources to support different development workflows.
+
+### 📦 Package Installation
+
+#### Stable Releases (NuGet.org)
+
+For production use and stable releases:
```bash
dotnet add package LocalStack.Client
dotnet add package LocalStack.Client.Extensions
```
+#### Development Builds (GitHub Packages)
+
+For testing latest features and bug fixes:
+
+```bash
+# Add GitHub Packages source
+dotnet nuget add source https://nuget.pkg.github.com/localstack-dotnet/index.json \
+ --name github-localstack \
+ --username YOUR_GITHUB_USERNAME \
+ --password YOUR_GITHUB_TOKEN
+
+# Install development packages
+dotnet add package LocalStack.Client --prerelease --source github-localstack
+dotnet add package LocalStack.Client.Extensions --prerelease --source github-localstack
+```
+
+> **🔑 GitHub Packages Authentication**: You'll need a GitHub account and [Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) with `read:packages` permission.
+
Refer to [documentation](https://github.com/localstack-dotnet/localstack-dotnet-client/wiki/Getting-Started#installation) for more information on how to install LocalStack.NET.
`LocalStack.NET` is a library that provides a wrapper around the [aws-sdk-net](https://github.com/aws/aws-sdk-net). This means you can use it in a similar way to the `AWS SDK for .NET` and to [AWSSDK.Extensions.NETCore.Setup](https://docs.aws.amazon.com/sdk-for-net/latest/developer-guide/net-dg-config-netcore.html) with a few differences. For more on how to use the AWS SDK for .NET, see [Getting Started with the AWS SDK for .NET](https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-setup.html).
@@ -189,4 +234,4 @@ Please refer to [`CHANGELOG.md`](CHANGELOG.md) to see the complete list of chang
## License
-Licensed under MIT, see [LICENSE](LICENSE) for the full text.
\ No newline at end of file
+Licensed under MIT, see [LICENSE](LICENSE) for the full text.
diff --git a/src/LocalStack.Client/GlobalUsings.cs b/src/LocalStack.Client/GlobalUsings.cs
index e7aff8f..df142da 100644
--- a/src/LocalStack.Client/GlobalUsings.cs
+++ b/src/LocalStack.Client/GlobalUsings.cs
@@ -18,7 +18,7 @@
global using LocalStack.Client.Utils;
#pragma warning disable MA0048 // File name must match type name
-#if NETSTANDARD || NET462
+#if NETSTANDARD || NET472
namespace System.Runtime.CompilerServices
{
using System.ComponentModel;
diff --git a/src/LocalStack.Client/LocalStack.Client.csproj b/src/LocalStack.Client/LocalStack.Client.csproj
index 6812224..3a7e54c 100644
--- a/src/LocalStack.Client/LocalStack.Client.csproj
+++ b/src/LocalStack.Client/LocalStack.Client.csproj
@@ -1,7 +1,7 @@
- netstandard2.0;net462;net8.0;net9.0
+ netstandard2.0;net472;net8.0;net9.0
LocalStack.Client
LocalStack.Client
@@ -51,8 +51,8 @@
-
- NET462
+
+ NET472
\ No newline at end of file
diff --git a/src/LocalStack.Client/README.md b/src/LocalStack.Client/README.md
index 4bec638..51c4faa 100644
--- a/src/LocalStack.Client/README.md
+++ b/src/LocalStack.Client/README.md
@@ -1,21 +1,42 @@
-# LocalStack .NET Client  [](https://www.nuget.org/packages/LocalStack.Client/) [](https://localstack-dotnet.testspace.com/spaces/232580?utm_campaign=metric&utm_medium=referral&utm_source=badge "Test Cases")
+# LocalStack .NET Client  [](https://www.nuget.org/packages/LocalStack.Client/) [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/ci.yml) [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/security.yml)
+
+> ## ⚠️ AWS SDK v4 Transition Notice
+>
+> **Current Status**: This main branch is under active development for **AWS SDK v4 support (v2.0)**
+>
+> **Version Strategy**:
+> - **v1.x** (AWS SDK v3): Maintenance mode → Available on [sdkv3-lts branch](https://github.com/localstack-dotnet/localstack-dotnet-client/tree/sdkv3-lts) (EOL: July 31, 2026)
+> - **v2.x** (AWS SDK v4): Active development → Native AOT support in subsequent v2.x releases
+>
+> **Migration Timeline**: Q3 2025 for v2.0.0 GA
+>
+> 📖 **[Read Full Roadmap & Migration Guide →](../../discussions)**

Localstack.NET is an easy-to-use .NET client for [LocalStack](https://github.com/localstack/localstack), a fully functional local AWS cloud stack. The client library provides a thin wrapper around [aws-sdk-net](https://github.com/aws/aws-sdk-net) which automatically configures the target endpoints to use LocalStack for your local cloud application development.
-| Package | Stable | Nightly |
+## Version Compatibility
+
+| LocalStack.NET Version | AWS SDK Version | .NET Support | Status | Branch |
+|------------------------|-----------------|--------------|---------|---------|
+| v1.x | AWS SDK v3 | .NET 8, 9, Standard 2.0, Framework 4.6.2 | Maintenance (until July 2026) | [sdkv3-lts](../../tree/sdkv3-lts) |
+| v2.x | AWS SDK v4 | .NET 8, 9, Standard 2.0, Framework 4.7.2 | Active Development | [master](../../tree/main) |
+
+## Package Status
+
+| Package | v1.x (AWS SDK v3) | v2.x (AWS SDK v4) - Development |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| LocalStack.Client | [](https://www.nuget.org/packages/LocalStack.Client/) | [](https://www.myget.org/feed/localstack-dotnet-client/package/nuget/LocalStack.Client) |
-| LocalStack.Client.Extensions | [](https://www.nuget.org/packages/LocalStack.Client.Extensions/) | [](https://www.myget.org/feed/localstack-dotnet-client/package/nuget/LocalStack.Client.Extensions) |
+| LocalStack.Client | [](https://www.nuget.org/packages/LocalStack.Client/) | [](https://github.com/localstack-dotnet/localstack-dotnet-client/packages) |
+| LocalStack.Client.Extensions | [](https://www.nuget.org/packages/LocalStack.Client.Extensions/) | [](https://github.com/localstack-dotnet/localstack-dotnet-client/packages) |
## Continuous Integration
-| Build server | Platform | Build status |
-| -------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| Github Actions | Ubuntu | [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-ubuntu.yml) |
-| Github Actions | Windows | [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-windows.yml) |
-| Github Actions | macOS | [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-macos.yml) |
+| Build Platform | Status | Description |
+|----------------|--------|-------------|
+| **Cross-Platform CI** | [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/ci.yml) | Matrix testing: Windows, Linux, macOS |
+| **Security Analysis** | [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/security.yml) | CodeQL analysis & dependency review |
+| **Automated Publishing** | [](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/publish-dev-github.yml) | Daily GitHub Packages builds |
## Table of Contents
@@ -60,13 +81,37 @@ For detailed installation and setup instructions, please refer to the [official
## Getting Started
-LocalStack.NET is installed from NuGet. To work with LocalStack in your .NET applications, you'll need the main library and its extensions. Here's how you can install them:
+LocalStack.NET is available through multiple package sources to support different development workflows.
+
+### 📦 Package Installation
+
+#### Stable Releases (NuGet.org)
+
+For production use and stable releases:
```bash
dotnet add package LocalStack.Client
dotnet add package LocalStack.Client.Extensions
```
+#### Development Builds (GitHub Packages)
+
+For testing latest features and bug fixes:
+
+```bash
+# Add GitHub Packages source
+dotnet nuget add source https://nuget.pkg.github.com/localstack-dotnet/index.json \
+ --name github-localstack \
+ --username YOUR_GITHUB_USERNAME \
+ --password YOUR_GITHUB_TOKEN
+
+# Install development packages
+dotnet add package LocalStack.Client --prerelease --source github-localstack
+dotnet add package LocalStack.Client.Extensions --prerelease --source github-localstack
+```
+
+> **🔑 GitHub Packages Authentication**: You'll need a GitHub account and [Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) with `read:packages` permission.
+
Refer to [documentation](https://github.com/localstack-dotnet/localstack-dotnet-client/wiki/Getting-Started#installation) for more information on how to install LocalStack.NET.
`LocalStack.NET` is a library that provides a wrapper around the [aws-sdk-net](https://github.com/aws/aws-sdk-net). This means you can use it in a similar way to the `AWS SDK for .NET` and to [AWSSDK.Extensions.NETCore.Setup](https://docs.aws.amazon.com/sdk-for-net/latest/developer-guide/net-dg-config-netcore.html) with a few differences. For more on how to use the AWS SDK for .NET, see [Getting Started with the AWS SDK for .NET](https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-setup.html).
@@ -189,4 +234,4 @@ Please refer to [`CHANGELOG.md`](CHANGELOG.md) to see the complete list of chang
## License
-Licensed under MIT, see [LICENSE](LICENSE) for the full text.
\ No newline at end of file
+Licensed under MIT, see [LICENSE](LICENSE) for the full text.
diff --git a/tests/LocalStack.Client.Extensions.Tests/AwsClientFactoryWrapperTests.cs b/tests/LocalStack.Client.Extensions.Tests/AwsClientFactoryWrapperTests.cs
index dba4981..936e416 100644
--- a/tests/LocalStack.Client.Extensions.Tests/AwsClientFactoryWrapperTests.cs
+++ b/tests/LocalStack.Client.Extensions.Tests/AwsClientFactoryWrapperTests.cs
@@ -19,19 +19,40 @@ public void CreateServiceClient_Should_Throw_LocalStackClientConfigurationExcept
Type type = _awsClientFactoryWrapper.GetType();
const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Static;
- FieldInfo? clientFactoryFullNameField = type.GetField("ClientFactoryFullName", bindingFlags);
+ FieldInfo? clientFactoryGenericTypeNameField = type.GetField("ClientFactoryGenericTypeName", bindingFlags);
FieldInfo? createServiceClientMethodNameFieldInfo = type.GetField("CreateServiceClientMethodName", bindingFlags);
- Assert.NotNull(clientFactoryFullNameField);
+ Assert.NotNull(clientFactoryGenericTypeNameField);
Assert.NotNull(createServiceClientMethodNameFieldInfo);
- SetPrivateReadonlyField(clientFactoryFullNameField, "NonExistingType");
+ SetPrivateReadonlyField(clientFactoryGenericTypeNameField, "NonExistingType");
SetPrivateReadonlyField(createServiceClientMethodNameFieldInfo, "NonExistingMethod");
Assert.Throws(
() => _awsClientFactoryWrapper.CreateServiceClient(_mockServiceProvider.Object, _awsOptions));
}
+ [Fact]
+ public void CreateServiceClient_Should_Throw_LocalStackClientConfigurationException_When_ClientFactory_Type_Not_Found()
+ {
+ Type type = _awsClientFactoryWrapper.GetType();
+ const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Static;
+
+ FieldInfo? clientFactoryGenericTypeNameField = type.GetField("ClientFactoryGenericTypeName", bindingFlags);
+ FieldInfo? createServiceClientMethodNameFieldInfo = type.GetField("CreateServiceClientMethodName", bindingFlags);
+
+ Assert.NotNull(clientFactoryGenericTypeNameField);
+ Assert.NotNull(createServiceClientMethodNameFieldInfo);
+
+ SetPrivateReadonlyField(clientFactoryGenericTypeNameField, "NonExistingType");
+ SetPrivateReadonlyField(createServiceClientMethodNameFieldInfo, "NonExistingMethod");
+
+ var exception = Assert.Throws(
+ () => _awsClientFactoryWrapper.CreateServiceClient(_mockServiceProvider.Object, _awsOptions));
+
+ Assert.Contains("Failed to find internal ClientFactory", exception.Message, StringComparison.Ordinal);
+ }
+
[Fact]
public void CreateServiceClient_Should_Create_Client_When_UseLocalStack_False()
{
diff --git a/tests/LocalStack.Client.Extensions.Tests/ServiceCollectionExtensionsTests.cs b/tests/LocalStack.Client.Extensions.Tests/ServiceCollectionExtensionsTests.cs
index ec73040..23a9934 100644
--- a/tests/LocalStack.Client.Extensions.Tests/ServiceCollectionExtensionsTests.cs
+++ b/tests/LocalStack.Client.Extensions.Tests/ServiceCollectionExtensionsTests.cs
@@ -324,9 +324,9 @@ public void GetRequiredService_Should_Use_Suitable_ClientFactory_To_Create_AwsSe
ServiceProvider provider = serviceCollection.BuildServiceProvider();
mockSession.Setup(session => session.CreateClientByInterface(It.IsAny()))
- .Returns(() => new MockAmazonServiceClient("tsada", "sadasdas", "sadasda", new MockClientConfig()));
+ .Returns(() => new MockAmazonServiceClient("tsada", "sadasdas", "sadasda", MockClientConfig.CreateDefaultMockClientConfig()));
mockClientFactory.Setup(wrapper => wrapper.CreateServiceClient(It.IsAny(), It.IsAny()))
- .Returns(() => new MockAmazonServiceClient("tsada", "sadasdas", "sadasda", new MockClientConfig()));
+ .Returns(() => new MockAmazonServiceClient("tsada", "sadasdas", "sadasda", MockClientConfig.CreateDefaultMockClientConfig()));
var mockAmazonService = provider.GetRequiredService();
diff --git a/tests/LocalStack.Client.Functional.Tests/CloudFormation/CloudFormationStackExecutor.cs b/tests/LocalStack.Client.Functional.Tests/CloudFormation/CloudFormationStackExecutor.cs
index 679dc50..d17323e 100644
--- a/tests/LocalStack.Client.Functional.Tests/CloudFormation/CloudFormationStackExecutor.cs
+++ b/tests/LocalStack.Client.Functional.Tests/CloudFormation/CloudFormationStackExecutor.cs
@@ -1,4 +1,6 @@
-using Tag = Amazon.CloudFormation.Model.Tag;
+#pragma warning disable CA2254
+
+using Tag = Amazon.CloudFormation.Model.Tag;
namespace LocalStack.Client.Functional.Tests.CloudFormation;
@@ -190,11 +192,11 @@ private async Task ExecuteChangeSetAsync(string changeSetId, ChangeSetTyp
if (changeSetType == ChangeSetType.CREATE)
{
- logger.LogInformation($"Initiated CloudFormation stack creation for {cloudFormationResource.Name}");
+ logger.LogInformation("Initiated CloudFormation stack creation for {Name}", cloudFormationResource.Name);
}
else
{
- logger.LogInformation($"Initiated CloudFormation stack update on {cloudFormationResource.Name}");
+ logger.LogInformation("Initiated CloudFormation stack update on {Name}", cloudFormationResource.Name);
}
}
catch (Exception e)
@@ -244,7 +246,7 @@ private async Task DetermineChangeSetTypeAsync(Stack? stack, Canc
changeSetType = ChangeSetType.CREATE;
}
- // If the status was DELETE_IN_PROGRESS then just wait for delete to complete
+ // If the status was DELETE_IN_PROGRESS then just wait for delete to complete
else if (stack.StackStatus == StackStatus.DELETE_IN_PROGRESS)
{
await WaitForNoLongerInProgressAsync(cancellationToken).ConfigureAwait(false);
@@ -327,7 +329,7 @@ private async Task DeleteRollbackCompleteStackAsync(Stack stack, CancellationTok
if (currentStack != null)
{
logger.LogInformation(
- $"... Waiting for stack's state to change from {currentStack.StackStatus}: {TimeSpan.FromTicks(DateTime.UtcNow.Ticks - start).TotalSeconds.ToString("0", CultureInfo.InvariantCulture).PadLeft(3)} secs");
+ "... Waiting for stack's state to change from {CurrentStackStackStatus}: {PadLeft} secs", currentStack.StackStatus, TimeSpan.FromTicks(DateTime.UtcNow.Ticks - start).TotalSeconds.ToString("0", CultureInfo.InvariantCulture).PadLeft(3));
}
await Task.Delay(StackPollingDelay, cancellation).ConfigureAwait(false);
@@ -422,7 +424,7 @@ private async Task WaitStackToCompleteAsync(DateTimeOffset minTimeStampFo
for (int i = events.Count - 1; i >= 0; i--)
{
var line = new StringBuilder();
- line.Append(events[i].Timestamp.ToString("g", CultureInfo.InvariantCulture).PadRight(TIMESTAMP_WIDTH));
+ line.Append(events[i].Timestamp?.ToString("g", CultureInfo.InvariantCulture).PadRight(TIMESTAMP_WIDTH));
line.Append(' ');
line.Append(events[i].LogicalResourceId.PadRight(LOGICAL_RESOURCE_WIDTH));
line.Append(' ');
@@ -435,9 +437,9 @@ private async Task WaitStackToCompleteAsync(DateTimeOffset minTimeStampFo
line.Append(events[i].ResourceStatusReason);
}
- if (minTimeStampForEvents < events[i].Timestamp)
+ if (minTimeStampForEvents < events[i].Timestamp && events[i].Timestamp != null)
{
- minTimeStampForEvents = events[i].Timestamp;
+ minTimeStampForEvents = (DateTimeOffset)events[i].Timestamp!;
}
logger.LogInformation(line.ToString());
diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/BaseDynamoDbScenario.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/BaseDynamoDbScenario.cs
index a97e3b3..79261b5 100644
--- a/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/BaseDynamoDbScenario.cs
+++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/BaseDynamoDbScenario.cs
@@ -10,7 +10,7 @@ protected BaseDynamoDbScenario(TestFixture testFixture, ILocalStackFixture local
bool useServiceUrl = false) : base(testFixture, localStackFixture, configFile, useServiceUrl)
{
DynamoDb = ServiceProvider.GetRequiredService();
- DynamoDbContext = new DynamoDBContext(DynamoDb);
+ DynamoDbContext = new DynamoDBContextBuilder().WithDynamoDBClient(() => DynamoDb).Build();
}
protected IAmazonDynamoDB DynamoDb { get; private set; }
@@ -39,19 +39,36 @@ public virtual async Task DynamoDbService_Should_Delete_A_DynamoDb_Table_Async()
public virtual async Task DynamoDbService_Should_Add_A_Record_To_A_DynamoDb_Table_Async()
{
var tableName = Guid.NewGuid().ToString();
- var dynamoDbOperationConfig = new DynamoDBOperationConfig() { OverrideTableName = tableName };
+
+ // Fix: Use GetTargetTableConfig instead of DynamoDBOperationConfig
+ var getTargetTableConfig = new GetTargetTableConfig() { OverrideTableName = tableName };
+
await CreateTestTableAsync(tableName);
- Table targetTable = DynamoDbContext.GetTargetTable(dynamoDbOperationConfig);
+ var describeResponse = await DynamoDb.DescribeTableAsync(new DescribeTableRequest(tableName));
+ var gsiExists = describeResponse.Table.GlobalSecondaryIndexes?.Exists(gsi => gsi.IndexName == TestConstants.MovieTableMovieIdGsi) == true;
+
+ if (!gsiExists)
+ {
+ var availableGsis = describeResponse.Table.GlobalSecondaryIndexes?.Select(g => g.IndexName).ToArray() ?? ["none"];
+
+ throw new System.InvalidOperationException($"GSI '{TestConstants.MovieTableMovieIdGsi}' was not found on table '{tableName}'. " +
+ $"Available GSIs: {string.Join(", ", availableGsis)}");
+ }
+
+ // Fix: Cast to Table and use GetTargetTableConfig
+ var targetTable = (Table)DynamoDbContext.GetTargetTable(getTargetTableConfig);
var movieEntity = new Fixture().Create();
string modelJson = JsonSerializer.Serialize(movieEntity);
Document item = Document.FromJson(modelJson);
await targetTable.PutItemAsync(item);
- dynamoDbOperationConfig.IndexName = TestConstants.MovieTableMovieIdGsi;
- List movieEntities =
- await DynamoDbContext.QueryAsync(movieEntity.MovieId, dynamoDbOperationConfig).GetRemainingAsync();
+
+ // Fix: Use QueryConfig instead of DynamoDBOperationConfig
+ var queryConfig = new QueryConfig() { OverrideTableName = tableName, IndexName = TestConstants.MovieTableMovieIdGsi };
+
+ List movieEntities = await DynamoDbContext.QueryAsync(movieEntity.MovieId, queryConfig).GetRemainingAsync();
Assert.True(movieEntity.DeepEquals(movieEntities[0]));
}
@@ -62,28 +79,30 @@ public virtual async Task DynamoDbService_Should_List_Records_In_A_DynamoDb_Tabl
var tableName = Guid.NewGuid().ToString();
const int recordCount = 5;
- var dynamoDbOperationConfig = new DynamoDBOperationConfig() { OverrideTableName = tableName };
+ // Fix: Use GetTargetTableConfig instead of DynamoDBOperationConfig
+ var getTargetTableConfig = new GetTargetTableConfig() { OverrideTableName = tableName };
await CreateTestTableAsync(tableName);
- Table targetTable = DynamoDbContext.GetTargetTable(dynamoDbOperationConfig);
- List movieEntities = new Fixture().CreateMany(recordCount).ToList();
- List documents = movieEntities.Select(entity =>
+ // Fix: Cast to Table and use GetTargetTableConfig
+ var targetTable = (Table)DynamoDbContext.GetTargetTable(getTargetTableConfig);
+ List movieEntities = [.. new Fixture().CreateMany(recordCount)];
+ List documents = [.. movieEntities.Select(entity =>
{
string serialize = JsonSerializer.Serialize(entity);
Document item = Document.FromJson(serialize);
return item;
- })
- .ToList();
+ }),];
foreach (Document document in documents)
{
await targetTable.PutItemAsync(document);
}
- dynamoDbOperationConfig.IndexName = TestConstants.MovieTableMovieIdGsi;
- List returnedMovieEntities =
- await DynamoDbContext.ScanAsync(new List(), dynamoDbOperationConfig).GetRemainingAsync();
+ // Fix: Use ScanConfig instead of DynamoDBOperationConfig
+ var scanConfig = new ScanConfig() { OverrideTableName = tableName, IndexName = TestConstants.MovieTableMovieIdGsi };
+
+ List returnedMovieEntities = await DynamoDbContext.ScanAsync(new List(), scanConfig).GetRemainingAsync();
Assert.NotNull(movieEntities);
Assert.NotEmpty(movieEntities);
@@ -101,29 +120,28 @@ protected Task CreateTestTableAsync(string? tableName = nul
var postTableCreateRequest = new CreateTableRequest
{
AttributeDefinitions =
- new List
- {
- new() { AttributeName = nameof(MovieEntity.DirectorId), AttributeType = ScalarAttributeType.S },
- new() { AttributeName = nameof(MovieEntity.CreateDate), AttributeType = ScalarAttributeType.S },
- new() { AttributeName = nameof(MovieEntity.MovieId), AttributeType = ScalarAttributeType.S },
- },
+ [
+ new AttributeDefinition { AttributeName = nameof(MovieEntity.DirectorId), AttributeType = ScalarAttributeType.S },
+ new AttributeDefinition { AttributeName = nameof(MovieEntity.CreateDate), AttributeType = ScalarAttributeType.S },
+ new AttributeDefinition { AttributeName = nameof(MovieEntity.MovieId), AttributeType = ScalarAttributeType.S },
+ ],
TableName = tableName ?? TestTableName,
KeySchema =
- new List()
- {
- new() { AttributeName = nameof(MovieEntity.DirectorId), KeyType = KeyType.HASH },
- new() { AttributeName = nameof(MovieEntity.CreateDate), KeyType = KeyType.RANGE },
- },
- GlobalSecondaryIndexes = new List
- {
- new()
+ [
+ new KeySchemaElement { AttributeName = nameof(MovieEntity.DirectorId), KeyType = KeyType.HASH },
+ new KeySchemaElement { AttributeName = nameof(MovieEntity.CreateDate), KeyType = KeyType.RANGE },
+ ],
+ GlobalSecondaryIndexes =
+ [
+ new GlobalSecondaryIndex
{
Projection = new Projection { ProjectionType = ProjectionType.ALL },
IndexName = TestConstants.MovieTableMovieIdGsi,
- KeySchema = new List { new() { AttributeName = nameof(MovieEntity.MovieId), KeyType = KeyType.HASH } },
- ProvisionedThroughput = new ProvisionedThroughput { ReadCapacityUnits = 5, WriteCapacityUnits = 5 }
- }
- },
+ KeySchema = [new KeySchemaElement { AttributeName = nameof(MovieEntity.MovieId), KeyType = KeyType.HASH }],
+ ProvisionedThroughput = new ProvisionedThroughput { ReadCapacityUnits = 5, WriteCapacityUnits = 5 },
+ },
+
+ ],
ProvisionedThroughput = new ProvisionedThroughput { ReadCapacityUnits = 5, WriteCapacityUnits = 6 },
};
diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/Entities/MovieEntity.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/Entities/MovieEntity.cs
index 3ae67ca..1e5d4be 100644
--- a/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/Entities/MovieEntity.cs
+++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/Entities/MovieEntity.cs
@@ -8,10 +8,10 @@ public class MovieEntity
public Guid DirectorId { get; set; }
[DynamoDBRangeKey]
-
public string CreateDate { get; set; }
+ [DynamoDBGlobalSecondaryIndexHashKey(TestConstants.MovieTableMovieIdGsi)]
public Guid MovieId { get; set; }
public string MovieName { get; set; }
-}
+}
\ No newline at end of file
diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/RealLife/BaseRealLife.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/RealLife/BaseRealLife.cs
index 9a18454..11af890 100644
--- a/tests/LocalStack.Client.Functional.Tests/Scenarios/RealLife/BaseRealLife.cs
+++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/RealLife/BaseRealLife.cs
@@ -66,7 +66,7 @@ public virtual async Task
Assert.Equal(HttpStatusCode.OK, receiveMessageResponse.HttpStatusCode);
- if (receiveMessageResponse.Messages.Count == 0)
+ if ((receiveMessageResponse.Messages?.Count ?? 0) == 0)
{
await Task.Delay(2000);
receiveMessageResponse = await AmazonSqs.ReceiveMessageAsync(receiveMessageRequest);
@@ -75,7 +75,7 @@ public virtual async Task
}
Assert.NotNull(receiveMessageResponse.Messages);
- Assert.NotEmpty(receiveMessageResponse.Messages);
+ Assert.NotEmpty(receiveMessageResponse.Messages!);
Assert.Single(receiveMessageResponse.Messages);
dynamic? deserializedMessage = JsonConvert.DeserializeObject(receiveMessageResponse.Messages[0].Body, new ExpandoObjectConverter());
diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/BaseSnsScenario.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/BaseSnsScenario.cs
index 45bcaef..97f2912 100644
--- a/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/BaseSnsScenario.cs
+++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/BaseSnsScenario.cs
@@ -22,7 +22,7 @@ public async Task SnsService_Should_Create_A_Sns_Topic_Async()
Assert.Equal(HttpStatusCode.OK, createTopicResponse.HttpStatusCode);
ListTopicsResponse listTopicsResponse = await AmazonSimpleNotificationService.ListTopicsAsync();
- Topic? snsTopic = listTopicsResponse.Topics.SingleOrDefault(topic => topic.TopicArn == createTopicResponse.TopicArn);
+ Topic? snsTopic = listTopicsResponse.Topics?.SingleOrDefault(topic => topic.TopicArn == createTopicResponse.TopicArn);
Assert.NotNull(snsTopic);
Assert.EndsWith(topicName, snsTopic.TopicArn, StringComparison.Ordinal);
@@ -41,7 +41,7 @@ public async Task SnsService_Should_Delete_A_Sns_Topic_Async()
Assert.Equal(HttpStatusCode.OK, deleteTopicResponse.HttpStatusCode);
ListTopicsResponse listTopicsResponse = await AmazonSimpleNotificationService.ListTopicsAsync();
- bool hasAny = listTopicsResponse.Topics.Exists(topic => topic.TopicArn == createTopicResponse.TopicArn);
+ bool hasAny = listTopicsResponse.Topics?.Exists(topic => topic.TopicArn == createTopicResponse.TopicArn) ?? false;
Assert.False(hasAny);
}
@@ -91,9 +91,10 @@ public virtual async Task Multi_Region_Tests_Async(string systemName)
var topicArn = $"arn:aws:sns:{systemName}:000000000000:{topicName}";
ListTopicsResponse listTopicsResponse = await AmazonSimpleNotificationService.ListTopicsAsync();
- Topic? snsTopic = listTopicsResponse.Topics.SingleOrDefault(topic => topic.TopicArn == topicArn);
+ Topic? snsTopic = listTopicsResponse.Topics?.SingleOrDefault(topic => topic.TopicArn == topicArn);
Assert.NotNull(snsTopic);
+ Assert.NotNull(listTopicsResponse.Topics);
Assert.Single(listTopicsResponse.Topics);
await DeleteSnsTopicAsync(topicArn); //Cleanup
diff --git a/tests/LocalStack.Client.Functional.Tests/TestConstants.cs b/tests/LocalStack.Client.Functional.Tests/TestConstants.cs
index 3fa982e..8f34b0e 100644
--- a/tests/LocalStack.Client.Functional.Tests/TestConstants.cs
+++ b/tests/LocalStack.Client.Functional.Tests/TestConstants.cs
@@ -7,5 +7,5 @@ public static class TestConstants
public const string LocalStackV37 = "3.7.1";
public const string LocalStackV43 = "4.3.0";
- public const string MovieTableMovieIdGsi = "MoiveTableMovie-Index";
+ public const string MovieTableMovieIdGsi = "MovieTableMovie-Index";
}
\ No newline at end of file
diff --git a/tests/LocalStack.Client.Integration.Tests/LocalStack.Client.Integration.Tests.csproj b/tests/LocalStack.Client.Integration.Tests/LocalStack.Client.Integration.Tests.csproj
index a2c62b6..b0fafab 100644
--- a/tests/LocalStack.Client.Integration.Tests/LocalStack.Client.Integration.Tests.csproj
+++ b/tests/LocalStack.Client.Integration.Tests/LocalStack.Client.Integration.Tests.csproj
@@ -1,7 +1,7 @@
- net462;net8.0;net9.0
+ net472;net8.0;net9.0
$(NoWarn);CA1707;MA0006;CA1510
@@ -128,7 +128,7 @@
-
+
diff --git a/tests/LocalStack.Client.Tests/LocalStack.Client.Tests.csproj b/tests/LocalStack.Client.Tests/LocalStack.Client.Tests.csproj
index 77b86ff..e59c51e 100644
--- a/tests/LocalStack.Client.Tests/LocalStack.Client.Tests.csproj
+++ b/tests/LocalStack.Client.Tests/LocalStack.Client.Tests.csproj
@@ -1,7 +1,7 @@
- net462;net8.0;net9.0
+ net472;net8.0;net9.0
$(NoWarn);CA1707;MA0006
@@ -21,7 +21,7 @@
-
+
diff --git a/tests/LocalStack.Client.Tests/SessionTests/SessionLocalStackTests.cs b/tests/LocalStack.Client.Tests/SessionTests/SessionLocalStackTests.cs
index 682c0d1..b28ba87 100644
--- a/tests/LocalStack.Client.Tests/SessionTests/SessionLocalStackTests.cs
+++ b/tests/LocalStack.Client.Tests/SessionTests/SessionLocalStackTests.cs
@@ -37,7 +37,7 @@ public void CreateClientByImplementation_Should_Create_SessionAWSCredentials_Wit
var mockSession = MockSession.Create();
var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = new MockClientConfig();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
(string awsAccessKeyId, string awsAccessKey, string awsSessionToken, _) = mockSession.SessionOptionsMock.SetupDefault();
@@ -71,7 +71,7 @@ public void CreateClientByImplementation_Should_Create_ClientConfig_With_UseHttp
var mockSession = MockSession.Create();
var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = new MockClientConfig();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
mockSession.SessionOptionsMock.SetupDefault();
@@ -98,7 +98,7 @@ public void CreateClientByImplementation_Should_Create_ClientConfig_With_UseHttp
var mockSession = MockSession.Create();
var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = new MockClientConfig();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
var configOptions = new ConfigOptions();
mockSession.SessionOptionsMock.SetupDefault();
@@ -134,7 +134,7 @@ public void CreateClientByImplementation_Should_Set_RegionEndpoint_By_RegionName
var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = new MockClientConfig();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
(_, _, _, string regionName) = mockSession.SessionOptionsMock.SetupDefault(regionName: systemName);
@@ -161,7 +161,7 @@ public void CreateClientByImplementation_Should_Set_ServiceUrl_By_ServiceEndpoin
var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = new MockClientConfig();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
#pragma warning disable CS8604 // Possible null reference argument.
mockSession.SessionOptionsMock.SetupDefault(regionName: systemName);
@@ -187,7 +187,7 @@ public void CreateClientByImplementation_Should_Pass_The_ClientConfig_To_SetForc
var mockSession = MockSession.Create();
var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = new MockClientConfig();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
mockSession.SessionOptionsMock.SetupDefault();
@@ -211,7 +211,7 @@ public void CreateClientByImplementation_Should_Create_AmazonServiceClient_By_Gi
var mockSession = MockSession.Create();
var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = new MockClientConfig();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
(_, _, _, string regionName) = mockSession.SessionOptionsMock.SetupDefault();
@@ -288,7 +288,7 @@ public void CreateClientByInterface_Should_Create_ClientConfig_With_UseHttp_Set_
var mockSession = MockSession.Create();
var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = new MockClientConfig();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
mockSession.SessionOptionsMock.SetupDefault();
@@ -315,7 +315,7 @@ public void CreateClientByInterface_Should_Create_SessionAWSCredentials_With_Aws
var mockSession = MockSession.Create();
var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = new MockClientConfig();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
(string awsAccessKeyId, string awsAccessKey, string awsSessionToken, _) = mockSession.SessionOptionsMock.SetupDefault();
@@ -347,7 +347,7 @@ public void CreateClientByInterface_Should_Create_ClientConfig_With_UseHttp_And_
var mockSession = MockSession.Create();
var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = new MockClientConfig();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
var configOptions = new ConfigOptions();
mockSession.SessionOptionsMock.SetupDefault();
@@ -385,7 +385,7 @@ public void CreateClientByInterface_Should_Set_RegionEndpoint_By_RegionName_Prop
var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = new MockClientConfig();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
(_, _, _, string regionName) = mockSession.SessionOptionsMock.SetupDefault(regionName: systemName);
@@ -413,7 +413,7 @@ public void CreateClientByInterface_Should_Set_ServiceUrl_By_ServiceEndpoint_Con
var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = new MockClientConfig();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
#pragma warning disable CS8604 // Possible null reference argument.
mockSession.SessionOptionsMock.SetupDefault(regionName: systemName);
@@ -439,7 +439,7 @@ public void CreateClientByInterface_Should_Pass_The_ClientConfig_To_SetForcePath
var mockSession = MockSession.Create();
var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = new MockClientConfig();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
mockSession.SessionOptionsMock.SetupDefault();
@@ -464,7 +464,7 @@ public void CreateClientByInterface_Should_Create_AmazonServiceClient_By_Given_G
var mockSession = MockSession.Create();
var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = new MockClientConfig();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
var configOptions = new ConfigOptions();
(_, _, _, string regionName) = mockSession.SessionOptionsMock.SetupDefault();
diff --git a/tests/LocalStack.Client.Tests/SessionTests/SessionReflectionTests.cs b/tests/LocalStack.Client.Tests/SessionTests/SessionReflectionTests.cs
index 0b3e9e7..90778e7 100644
--- a/tests/LocalStack.Client.Tests/SessionTests/SessionReflectionTests.cs
+++ b/tests/LocalStack.Client.Tests/SessionTests/SessionReflectionTests.cs
@@ -36,9 +36,9 @@ public void CreateClientConfig_Should_Create_ClientConfig_By_Given_Generic_Servi
public void SetForcePathStyle_Should_Return_False_If_Given_ClientConfig_Does_Not_Have_ForcePathStyle()
{
var sessionReflection = new SessionReflection();
- var clientConfig = new MockClientConfig();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
- bool set = sessionReflection.SetForcePathStyle(clientConfig, true);
+ bool set = sessionReflection.SetForcePathStyle(mockClientConfig, true);
Assert.False(set);
}
@@ -47,7 +47,7 @@ public void SetForcePathStyle_Should_Return_False_If_Given_ClientConfig_Does_Not
public void SetForcePathStyle_Should_Set_ForcePathStyle_Of_ClientConfig_If_It_Exists()
{
var sessionReflection = new SessionReflection();
- var clientConfig = new MockClientConfigWithForcePathStyle();
+ var clientConfig = MockClientConfigWithForcePathStyle.CreateDefaultMockClientConfigWithForcePathStyle();
Assert.False(clientConfig.ForcePathStyle);
@@ -57,11 +57,11 @@ public void SetForcePathStyle_Should_Set_ForcePathStyle_Of_ClientConfig_If_It_Ex
Assert.True(clientConfig.ForcePathStyle);
}
- [Theory,
- InlineData("eu-central-1"),
- InlineData("us-west-1"),
+ [Theory,
+ InlineData("eu-central-1"),
+ InlineData("us-west-1"),
InlineData("af-south-1"),
- InlineData("ap-southeast-1"),
+ InlineData("ap-southeast-1"),
InlineData("ca-central-1"),
InlineData("eu-west-2"),
InlineData("sa-east-1")]
@@ -77,4 +77,4 @@ public void SetClientRegion_Should_Set_RegionEndpoint_Of_The_Given_Client_By_Sys
Assert.NotNull(mockAmazonServiceClient.Config.RegionEndpoint);
Assert.Equal(RegionEndpoint.GetBySystemName(systemName), mockAmazonServiceClient.Config.RegionEndpoint);
}
-}
+}
\ No newline at end of file
diff --git a/tests/common/LocalStack.Tests.Common/LocalStack.Tests.Common.csproj b/tests/common/LocalStack.Tests.Common/LocalStack.Tests.Common.csproj
index 27948d3..d7d39e1 100644
--- a/tests/common/LocalStack.Tests.Common/LocalStack.Tests.Common.csproj
+++ b/tests/common/LocalStack.Tests.Common/LocalStack.Tests.Common.csproj
@@ -1,7 +1,7 @@
- net462;net8.0;net9.0
+ net472;net8.0;net9.0
$(NoWarn);CA1707;MA0006;CA1510
@@ -10,7 +10,7 @@
-
+
diff --git a/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/IMockAmazonService.cs b/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/IMockAmazonService.cs
index 956c53f..d5c2a9c 100644
--- a/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/IMockAmazonService.cs
+++ b/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/IMockAmazonService.cs
@@ -2,5 +2,14 @@
public interface IMockAmazonService : IDisposable, IAmazonService
{
+#if NET8_0_OR_GREATER
+#pragma warning disable CA1033
+ static ClientConfig IAmazonService.CreateDefaultClientConfig() => MockClientConfig.CreateDefaultMockClientConfig();
-}
+ static IAmazonService IAmazonService.CreateDefaultServiceClient(AWSCredentials awsCredentials, ClientConfig clientConfig)
+ {
+ return new MockAmazonServiceClient(awsCredentials, MockClientConfig.CreateDefaultMockClientConfig());
+ }
+#pragma warning restore CA1033
+#endif
+}
\ No newline at end of file
diff --git a/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/IMockAmazonServiceWithServiceMetadata.cs b/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/IMockAmazonServiceWithServiceMetadata.cs
index 6ec4cb9..c1dbf29 100644
--- a/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/IMockAmazonServiceWithServiceMetadata.cs
+++ b/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/IMockAmazonServiceWithServiceMetadata.cs
@@ -2,5 +2,14 @@
public interface IMockAmazonServiceWithServiceMetadata : IDisposable, IAmazonService
{
+#if NET8_0_OR_GREATER
+#pragma warning disable CA1033
+ static ClientConfig IAmazonService.CreateDefaultClientConfig() => MockClientConfig.CreateDefaultMockClientConfig();
-}
+ static IAmazonService IAmazonService.CreateDefaultServiceClient(AWSCredentials awsCredentials, ClientConfig clientConfig)
+ {
+ return new MockAmazonServiceWithServiceMetadataClient(awsCredentials, MockClientConfig.CreateDefaultMockClientConfig());
+ }
+#pragma warning restore CA1033
+#endif
+}
\ No newline at end of file
diff --git a/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockAmazonServiceClient.cs b/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockAmazonServiceClient.cs
index fbf19bb..01b2e73 100644
--- a/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockAmazonServiceClient.cs
+++ b/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockAmazonServiceClient.cs
@@ -1,8 +1,10 @@
-namespace LocalStack.Tests.Common.Mocks.MockServiceClients;
+#pragma warning disable S2325,CA1822
+
+namespace LocalStack.Tests.Common.Mocks.MockServiceClients;
public class MockAmazonServiceClient : AmazonServiceClient, IMockAmazonService
{
- public MockAmazonServiceClient() : base(new MockCredentials(), new MockClientConfig())
+ public MockAmazonServiceClient() : base(new MockCredentials(), new MockClientConfig(new MockConfigurationProvider()))
{
}
@@ -20,10 +22,17 @@ public MockAmazonServiceClient(string awsAccessKeyId, string awsSecretAccessKey,
{
}
- public AWSCredentials AwsCredentials => Credentials;
+ public AWSCredentials AwsCredentials => Config.DefaultAWSCredentials;
+
+#if NET8_0_OR_GREATER
+ public static ClientConfig CreateDefaultClientConfig()
+ {
+ return MockClientConfig.CreateDefaultMockClientConfig();
+ }
- protected override AbstractAWSSigner CreateSigner()
+ public static IAmazonService CreateDefaultServiceClient(AWSCredentials awsCredentials, ClientConfig clientConfig)
{
- return new NullSigner();
+ return new MockAmazonServiceClient(awsCredentials, MockClientConfig.CreateDefaultMockClientConfig());
}
-}
+#endif
+}
\ No newline at end of file
diff --git a/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockAmazonServiceWithServiceMetadataClient.cs b/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockAmazonServiceWithServiceMetadataClient.cs
index 4a2343c..05d93b0 100644
--- a/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockAmazonServiceWithServiceMetadataClient.cs
+++ b/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockAmazonServiceWithServiceMetadataClient.cs
@@ -1,11 +1,13 @@
-#pragma warning disable S1144, CA1823
+using Amazon.Runtime.Credentials;
+
+#pragma warning disable S1144, CA1823
namespace LocalStack.Tests.Common.Mocks.MockServiceClients;
public class MockAmazonServiceWithServiceMetadataClient : AmazonServiceClient, IMockAmazonServiceWithServiceMetadata
{
private static IServiceMetadata serviceMetadata = new MockServiceMetadata();
- public MockAmazonServiceWithServiceMetadataClient() : base(FallbackCredentialsFactory.GetCredentials(), new MockClientConfig())
+ public MockAmazonServiceWithServiceMetadataClient() : base(DefaultAWSCredentialsIdentityResolver.GetCredentials(), MockClientConfig.CreateDefaultMockClientConfig())
{
}
@@ -23,10 +25,15 @@ public MockAmazonServiceWithServiceMetadataClient(string awsAccessKeyId, string
{
}
- public AWSCredentials AwsCredentials => Credentials;
+#if NET8_0_OR_GREATER
+ public static ClientConfig CreateDefaultClientConfig()
+ {
+ return MockClientConfig.CreateDefaultMockClientConfig();
+ }
- protected override AbstractAWSSigner CreateSigner()
+ public static IAmazonService CreateDefaultServiceClient(AWSCredentials awsCredentials, ClientConfig clientConfig)
{
- return new NullSigner();
+ return new MockAmazonServiceWithServiceMetadataClient(awsCredentials, MockClientConfig.CreateDefaultMockClientConfig());
}
-}
+#endif
+}
\ No newline at end of file
diff --git a/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockClientConfig.cs b/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockClientConfig.cs
index 09a9618..d19c23e 100644
--- a/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockClientConfig.cs
+++ b/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockClientConfig.cs
@@ -1,8 +1,14 @@
-namespace LocalStack.Tests.Common.Mocks.MockServiceClients;
+using Amazon.Runtime.Endpoints;
+
+namespace LocalStack.Tests.Common.Mocks.MockServiceClients;
public class MockClientConfig : ClientConfig, IClientConfig
{
- public MockClientConfig()
+ public MockClientConfig() : this(new MockConfigurationProvider())
+ {
+ }
+
+ public MockClientConfig(IDefaultConfigurationProvider configurationProvider) : base(configurationProvider)
{
ServiceURL = "http://localhost";
}
@@ -11,5 +17,12 @@ public MockClientConfig()
public override string UserAgent => InternalSDKUtils.BuildUserAgentString(ServiceVersion);
+ public override Endpoint DetermineServiceOperationEndpoint(ServiceOperationEndpointParameters parameters)
+ {
+ return new Endpoint(ServiceURL);
+ }
+
public override string RegionEndpointServiceName => "mock-service";
-}
+
+ public static MockClientConfig CreateDefaultMockClientConfig() => new(new MockConfigurationProvider());
+}
\ No newline at end of file
diff --git a/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockClientConfigWithForcePathStyle.cs b/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockClientConfigWithForcePathStyle.cs
index e6c7346..688b506 100644
--- a/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockClientConfigWithForcePathStyle.cs
+++ b/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockClientConfigWithForcePathStyle.cs
@@ -2,5 +2,12 @@
public class MockClientConfigWithForcePathStyle : MockClientConfig
{
+ public MockClientConfigWithForcePathStyle(IDefaultConfigurationProvider configurationProvider, bool forcePathStyle) : base(configurationProvider)
+ {
+ ForcePathStyle = forcePathStyle;
+ }
+
public bool ForcePathStyle { get; set; }
-}
+
+ public static MockClientConfigWithForcePathStyle CreateDefaultMockClientConfigWithForcePathStyle() => new(new MockConfigurationProvider(), forcePathStyle: false);
+}
\ No newline at end of file
diff --git a/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockConfiguration.cs b/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockConfiguration.cs
new file mode 100644
index 0000000..36b1e30
--- /dev/null
+++ b/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockConfiguration.cs
@@ -0,0 +1,18 @@
+namespace LocalStack.Tests.Common.Mocks.MockServiceClients;
+
+public class MockConfiguration : IDefaultConfiguration
+{
+ public DefaultConfigurationMode Name { get; }
+
+ public RequestRetryMode RetryMode { get; }
+
+ public S3UsEast1RegionalEndpointValue S3UsEast1RegionalEndpoint { get; }
+
+ public TimeSpan? ConnectTimeout { get; }
+
+ public TimeSpan? TlsNegotiationTimeout { get; }
+
+ public TimeSpan? TimeToFirstByteTimeout { get; }
+
+ public TimeSpan? HttpRequestTimeout { get; }
+}
\ No newline at end of file
diff --git a/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockConfigurationProvider.cs b/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockConfigurationProvider.cs
new file mode 100644
index 0000000..f4e2a39
--- /dev/null
+++ b/tests/common/LocalStack.Tests.Common/Mocks/MockServiceClients/MockConfigurationProvider.cs
@@ -0,0 +1,11 @@
+using Amazon;
+
+namespace LocalStack.Tests.Common.Mocks.MockServiceClients;
+
+public class MockConfigurationProvider : IDefaultConfigurationProvider
+{
+ public IDefaultConfiguration GetDefaultConfiguration(RegionEndpoint clientRegion, DefaultConfigurationMode? requestedConfigurationMode = null)
+ {
+ return new MockConfiguration();
+ }
+}
\ No newline at end of file
diff --git a/tests/sandboxes/LocalStack.Client.Sandbox/LocalStack.Client.Sandbox.csproj b/tests/sandboxes/LocalStack.Client.Sandbox/LocalStack.Client.Sandbox.csproj
index 129fa3a..a35f787 100644
--- a/tests/sandboxes/LocalStack.Client.Sandbox/LocalStack.Client.Sandbox.csproj
+++ b/tests/sandboxes/LocalStack.Client.Sandbox/LocalStack.Client.Sandbox.csproj
@@ -2,7 +2,7 @@
Exe
- net462;net8.0;net9.0
+ net472;net8.0;net9.0
$(NoWarn);CS0246;S125;CA1305;CA1031;CA1303;CA1848;MA0004;CA2007