From c9fa1bb2ae2bb31ce0d9fe77477ddfb60b1d2855 Mon Sep 17 00:00:00 2001 From: Blind-Striker Date: Sun, 25 Oct 2026 07:51:47 +0300 Subject: [PATCH] ci: post test results to BadgeSmith and serve README badges from it --- .github/actions/update-test-badge/README.md | 183 ------------------- .github/actions/update-test-badge/action.yml | 125 ------------- .github/workflows/ci-cd.yml | 34 ++-- README.md | 2 +- docs/engineering/quality-gates.md | 2 +- src/LocalStack.Client.Extensions/README.md | 2 +- src/LocalStack.Client/README.md | 2 +- 7 files changed, 25 insertions(+), 325 deletions(-) delete mode 100644 .github/actions/update-test-badge/README.md delete mode 100644 .github/actions/update-test-badge/action.yml diff --git a/.github/actions/update-test-badge/README.md b/.github/actions/update-test-badge/README.md deleted file mode 100644 index 596bc61..0000000 --- a/.github/actions/update-test-badge/README.md +++ /dev/null @@ -1,183 +0,0 @@ -# Update Test Results Badge Action - -A reusable GitHub Action that updates test result badges by uploading test data to GitHub Gist files and displaying badge URLs for README files. - -## Purpose - -This action simplifies the process of maintaining dynamic test result badges by: - -- Creating structured JSON data from test results -- Uploading the data to platform-specific files in a single GitHub Gist -- Providing ready-to-use badge URLs for documentation - -## Usage - -```yaml -- name: "Update Test Results Badge" - uses: ./.github/actions/update-test-badge - with: - platform: "Linux" - gist_id: "472c59b7c2a1898c48a29f3c88897c5a" - filename: "test-results-linux.json" - gist_token: ${{ secrets.GIST_SECRET }} - test_passed: 1099 - test_failed: 0 - test_skipped: 0 - test_url_html: "https://github.com/owner/repo/runs/12345" - commit_sha: ${{ github.sha }} - run_id: ${{ github.run_id }} - repository: ${{ github.repository }} - server_url: ${{ github.server_url }} -``` - -## Gist Structure - -This action uses a **single Gist** with **multiple files** for different platforms: - -``` -Gist ID: 472c59b7c2a1898c48a29f3c88897c5a -├── test-results-linux.json -├── test-results-windows.json -└── test-results-macos.json -``` - -## Inputs - -| Input | Description | Required | Default | -|-------|-------------|----------|---------| -| `platform` | Platform name (Linux, Windows, macOS) | ✅ | - | -| `gist_id` | GitHub Gist ID for storing test results | ✅ | - | -| `filename` | Filename for platform-specific JSON (e.g., test-results-linux.json) | ✅ | - | -| `gist_token` | GitHub token with gist permissions | ✅ | - | -| `test_passed` | Number of passed tests | ✅ | - | -| `test_failed` | Number of failed tests | ✅ | - | -| `test_skipped` | Number of skipped tests | ✅ | - | -| `test_url_html` | URL to test results page | ❌ | `''` | -| `commit_sha` | Git commit SHA | ✅ | - | -| `run_id` | GitHub Actions run ID | ✅ | - | -| `repository` | Repository in owner/repo format | ✅ | - | -| `server_url` | GitHub server URL | ✅ | - | -| `api_domain` | Badge API domain for URLs | ❌ | `your-api-domain` | - -## Outputs - -This action produces: - -- **Gist File Update**: Updates the platform-specific file in the single Gist -- **Console Output**: Displays badge URLs ready for README usage -- **Debug Info**: Shows HTTP status and error details - -## Generated JSON Format - -The action creates JSON data in this format for each platform file: - -```json -{ - "platform": "Linux", - "passed": 1099, - "failed": 0, - "skipped": 0, - "total": 1099, - "url_html": "https://github.com/owner/repo/runs/12345", - "timestamp": "2025-01-16T10:30:00Z", - "commit": "abc123def456", - "run_id": "12345678", - "workflow_run_url": "https://github.com/owner/repo/actions/runs/12345678" -} -``` - -## Error Handling - -- **Non-essential**: Uses `continue-on-error: true` to prevent workflow failures -- **Graceful degradation**: Provides detailed error messages without stopping execution -- **HTTP status reporting**: Shows API response codes for debugging -- **File-specific updates**: Only updates the specific platform file, doesn't affect other platform data - -## Integration with Badge API - -This action is designed to work with the LocalStack .NET Client Badge API that: - -- Reads from the updated Gist files -- Generates shields.io-compatible badge JSON -- Provides redirect endpoints to test result pages - -## Matrix Integration Example - -```yaml -env: - BADGE_GIST_ID: "472c59b7c2a1898c48a29f3c88897c5a" - -strategy: - matrix: - include: - - os: ubuntu-22.04 - name: "Linux" - filename: "test-results-linux.json" - - os: windows-latest - name: "Windows" - filename: "test-results-windows.json" - - os: macos-latest - name: "macOS" - filename: "test-results-macos.json" - -steps: - - name: "Update Test Results Badge" - uses: ./.github/actions/update-test-badge - with: - platform: ${{ matrix.name }} - gist_id: ${{ env.BADGE_GIST_ID }} - filename: ${{ matrix.filename }} - gist_token: ${{ secrets.GIST_SECRET }} - # ... other inputs -``` - -## Required Setup - -1. **Create single GitHub Gist** with platform-specific files: - - `test-results-linux.json` - - `test-results-windows.json` - - `test-results-macos.json` -2. **Generate GitHub PAT** with `gist` scope -3. **Add to repository secrets** as `GIST_SECRET` -4. **Deploy Badge API** to consume the Gist data - -## Badge URLs Generated - -The action displays ready-to-use markdown for README files: - -```markdown -[![Test Results (Linux)](https://your-api-domain/badge/tests/linux)](https://your-api-domain/redirect/tests/linux) -``` - -## Advantages of Explicit Filename Configuration - -- ✅ **No String Manipulation**: Eliminates brittle string transformation logic -- ✅ **Declarative**: Filenames are explicitly declared in workflow configuration -- ✅ **Predictable**: No risk of unexpected filename generation -- ✅ **Reusable**: Action works with any filename structure -- ✅ **Debuggable**: Easy to see exactly what files will be created -- ✅ **Flexible**: Supports any naming convention without code changes - -## Advantages of Single Gist Approach - -- ✅ **Simplified Management**: One Gist to manage instead of three -- ✅ **Atomic Operations**: All platform data in one place -- ✅ **Better Organization**: Clear file structure with descriptive names -- ✅ **Easier Debugging**: Single location to check all test data -- ✅ **Cost Efficient**: Fewer API calls and resources - -## Troubleshooting - -**Common Issues:** - -- **403 Forbidden**: Check `GIST_SECRET` permissions -- **404 Not Found**: Verify `gist_id` is correct -- **JSON Errors**: Ensure `jq` is available in runner -- **File Missing**: Gist files are created automatically on first update - -**Debug Steps:** - -1. Check action output for HTTP status codes -2. Verify Gist exists and is publicly accessible -3. Confirm token has proper `gist` scope -4. Check individual file URLs: `https://gist.githubusercontent.com/{gist_id}/raw/test-results-{platform}.json` diff --git a/.github/actions/update-test-badge/action.yml b/.github/actions/update-test-badge/action.yml deleted file mode 100644 index 38d7c1c..0000000 --- a/.github/actions/update-test-badge/action.yml +++ /dev/null @@ -1,125 +0,0 @@ -name: 'Update Test Results Badge' -description: 'Updates test results badge data in GitHub Gist and displays badge URLs' -author: 'LocalStack .NET Team' - -inputs: - platform: - description: 'Platform name (Linux, Windows, macOS)' - required: true - gist_id: - description: 'GitHub Gist ID for storing test results' - required: true - filename: - description: 'Filename for the platform-specific JSON file (e.g., test-results-linux.json)' - required: true - gist_token: - description: 'GitHub token with gist permissions' - required: true - test_passed: - description: 'Number of passed tests' - required: true - test_failed: - description: 'Number of failed tests' - required: true - test_skipped: - description: 'Number of skipped tests' - required: true - test_url_html: - description: 'URL to test results page' - required: false - default: '' - commit_sha: - description: 'Git commit SHA' - required: true - run_id: - description: 'GitHub Actions run ID' - required: true - repository: - description: 'Repository in owner/repo format' - required: true - server_url: - description: 'GitHub server URL' - required: true - api_domain: - description: 'Badge API domain for displaying URLs' - required: false - default: 'your-api-domain' - -runs: - using: 'composite' - steps: - - name: 'Update Test Results Badge Data' - shell: bash - run: | - # Use explicit filename from input - FILENAME="${{ inputs.filename }}" - - # Calculate totals - TOTAL=$((${{ inputs.test_passed }} + ${{ inputs.test_failed }} + ${{ inputs.test_skipped }})) - - # Create JSON payload for badge API - cat > test-results.json << EOF - { - "platform": "${{ inputs.platform }}", - "passed": ${{ inputs.test_passed }}, - "failed": ${{ inputs.test_failed }}, - "skipped": ${{ inputs.test_skipped }}, - "total": ${TOTAL}, - "url_html": "${{ inputs.test_url_html }}", - "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", - "commit": "${{ inputs.commit_sha }}", - "run_id": "${{ inputs.run_id }}", - "workflow_run_url": "${{ inputs.server_url }}/${{ inputs.repository }}/actions/runs/${{ inputs.run_id }}" - } - EOF - - echo "📊 Generated test results JSON for ${{ inputs.platform }}:" - cat test-results.json | jq '.' 2>/dev/null || cat test-results.json - - # Upload to single Gist with platform-specific filename - echo "📤 Uploading to Gist: ${{ inputs.gist_id }} (file: ${FILENAME})" - - # Create gist update payload - only update the specific platform file - cat > gist-payload.json << EOF - { - "files": { - "${FILENAME}": { - "content": $(cat test-results.json | jq -R -s '.') - } - } - } - EOF - - # Update Gist using GitHub API - HTTP_STATUS=$(curl -s -X PATCH \ - -H "Accept: application/vnd.github.v3+json" \ - -H "Authorization: token ${{ inputs.gist_token }}" \ - "https://api.github.com/gists/${{ inputs.gist_id }}" \ - -d @gist-payload.json \ - -w "%{http_code}" \ - -o response.json) - - if [ "$HTTP_STATUS" -eq 200 ]; then - echo "✅ Successfully updated Gist file ${FILENAME} (HTTP $HTTP_STATUS)" - else - echo "⚠️ Failed to update Gist file ${FILENAME} (HTTP $HTTP_STATUS)" - echo "Response:" - cat response.json 2>/dev/null || echo "No response body" - fi - - - name: 'Display Badge URLs' - shell: bash - run: | - PLATFORM_LOWER=$(echo "${{ inputs.platform }}" | tr '[:upper:]' '[:lower:]') - FILENAME="${{ inputs.filename }}" - - echo "🎯 Badge URL for ${{ inputs.platform }}:" - echo "" - echo "**${{ inputs.platform }} Badge:**" - echo "[![Test Results (${{ inputs.platform }})](https://${{ inputs.api_domain }}/badge/tests/${PLATFORM_LOWER})](https://${{ inputs.api_domain }}/redirect/tests/${PLATFORM_LOWER})" - echo "" - echo "**Raw URLs:**" - echo "- Badge: https://${{ inputs.api_domain }}/badge/tests/${PLATFORM_LOWER}" - echo "- Redirect: https://${{ inputs.api_domain }}/redirect/tests/${PLATFORM_LOWER}" - echo "- Gist: https://gist.github.com/${{ inputs.gist_id }}" - echo "- Gist File: https://gist.githubusercontent.com/Blind-Striker/${{ inputs.gist_id }}/raw/${FILENAME}" \ No newline at end of file diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index c060ec8..8b55782 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -29,11 +29,10 @@ jobs: runs-on: ${{ matrix.os }} env: NUGET_PACKAGES: ${{ contains(matrix.os, 'windows') && format('{0}\.nuget\packages', github.workspace) || format('{0}/.nuget/packages', github.workspace) }} - BADGE_GIST_ID: "472c59b7c2a1898c48a29f3c88897c5a" LOCALSTACK_AUTH_TOKEN_MISSING: ${{ secrets.LOCALSTACK_AUTH_TOKEN == '' }} - # dorny/test-reporter publishes a check run. The badge step authenticates with its own - # PAT (secrets.GIST_SECRET), so it needs nothing from GITHUB_TOKEN. + # dorny/test-reporter publishes a check run. The badge step signs its request with the + # organization's BadgeSmith HMAC key, so it needs nothing from GITHUB_TOKEN. permissions: contents: read actions: read @@ -46,17 +45,14 @@ jobs: - os: windows-latest name: "Windows" script: "./build.ps1" - filename: "test-results-windows.json" - os: ubuntu-22.04 name: "Linux" script: "./build.sh" - filename: "test-results-linux.json" - os: macos-latest name: "macOS" script: "./build.sh" - filename: "test-results-macos.json" steps: - name: "Checkout" @@ -102,15 +98,25 @@ jobs: fail-on-error: true max-annotations: 50 - - name: "Update Test Results Badge" - if: always() # Run even if tests failed or were skipped - continue-on-error: true # Don't fail workflow if badge update fails - uses: ./.github/actions/update-test-badge + # BadgeSmith's update action lives under .github/workflows/ in its repository, so a step + # cannot reference it remotely; check it out at the commit api.localstackfor.net runs and + # use it by local path, as opencode-sdk-dotnet does. The HMAC key is the organization's + # TESTDATASECRET. + - name: "Checkout BadgeSmith action" + if: always() && github.event_name == 'push' && github.ref == 'refs/heads/master' + continue-on-error: true + uses: actions/checkout@v7 + with: + repository: localstack-dotnet/badge-smith + ref: bc7ed07860ec4b0e702add6370c2636daccb2adf + path: .badge-smith + + - name: "Update test badge via BadgeSmith API" + if: always() && github.event_name == 'push' && github.ref == 'refs/heads/master' + continue-on-error: true + uses: ./.badge-smith/.github/workflows/update-test-badge with: platform: ${{ matrix.name }} - gist_id: ${{ env.BADGE_GIST_ID }} - filename: ${{ matrix.filename }} - gist_token: ${{ secrets.GIST_SECRET }} test_passed: ${{ steps.test-results.outputs.passed || 0 }} test_failed: ${{ steps.test-results.outputs.failed || 0 }} test_skipped: ${{ steps.test-results.outputs.skipped || 0 }} @@ -119,6 +125,8 @@ jobs: run_id: ${{ github.run_id }} repository: ${{ github.repository }} server_url: ${{ github.server_url }} + api_base_url: "https://api.localstackfor.net" + hmac_secret: ${{ secrets.TESTDATASECRET }} - name: "Upload Test Artifacts" uses: actions/upload-artifact@v7 diff --git a/README.md b/README.md index f4a3bbb..a0d55ae 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # LocalStack.NET -[![LocalStack.Client](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Fpackages%2Flocalstack.client%3Fsource%3Dnuget%26label%3DLocalStack.Client)](https://www.nuget.org/packages/LocalStack.Client/) [![LocalStack.Client.Extensions](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Fpackages%2Flocalstack.client.extensions%3Fsource%3Dnuget%26label%3DLocalStack.Client.Extensions)](https://www.nuget.org/packages/LocalStack.Client.Extensions/) [![Downloads](https://img.shields.io/nuget/dt/LocalStack.Client)](https://www.nuget.org/packages/LocalStack.Client/) [![CI/CD Pipeline](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/ci-cd.yml) [![Security](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/github-code-scanning/codeql) [![Tests](https://img.shields.io/endpoint?url=https://yvfdbfas85.execute-api.eu-central-1.amazonaws.com/live/badge/tests/linux?label=Tests)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/ci-cd.yml) +[![LocalStack.Client](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.localstackfor.net%2Fbadges%2Fpackages%2Fnuget%2FLocalStack.Client&label=LocalStack.Client)](https://www.nuget.org/packages/LocalStack.Client/) [![LocalStack.Client.Extensions](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.localstackfor.net%2Fbadges%2Fpackages%2Fnuget%2FLocalStack.Client.Extensions&label=LocalStack.Client.Extensions)](https://www.nuget.org/packages/LocalStack.Client.Extensions/) [![Downloads](https://img.shields.io/nuget/dt/LocalStack.Client)](https://www.nuget.org/packages/LocalStack.Client/) [![CI/CD Pipeline](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/ci-cd.yml) [![Security](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/github-code-scanning/codeql) [![Tests](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.localstackfor.net%2Fbadges%2Ftests%2Flinux%2Flocalstack-dotnet%2Flocalstack-dotnet-client%2Fmaster&label=Tests)](https://api.localstackfor.net/redirect/test-results/linux/localstack-dotnet/localstack-dotnet-client/master) ![LocalStack](https://github.com/localstack-dotnet/localstack-dotnet-client/blob/master/assets/localstack-dotnet.png?raw=true) diff --git a/docs/engineering/quality-gates.md b/docs/engineering/quality-gates.md index 448f9a8..7ddf6e9 100644 --- a/docs/engineering/quality-gates.md +++ b/docs/engineering/quality-gates.md @@ -31,4 +31,4 @@ In the main CI workflow only the Linux leg runs functional tests. Its test step CI's separate `native-aot-tests` job publishes/runs both targets on `windows-latest` (`win-x64`) and `ubuntu-22.04` (`linux-x64`). Linux waits for LocalStack 4.14.0 health and sets `LOCALSTACK_NATIVE_AOT_HOST`; live cases otherwise skip. The [project](../../tests/LocalStack.Client.NativeAot.Tests/LocalStack.Client.NativeAot.Tests.csproj) and pinned xUnit v3 AOT package define the runner; this lane qualifies the [Native AOT boundary](../architecture/platform-and-packaging.md#native-aot-and-trimming), not every service/deployment. The same job then packs both libraries into `artifacts/packages` and publishes [`LocalStack.Client.PackageConsumer.Tests`](../../tests/LocalStack.Client.PackageConsumer.Tests/) from those packages for every modern target framework with a fresh NuGet cache; that project is outside the solution and isolated from the repository's build props, so it proves the generator reaches a consumer through the package's `buildTransitive` target on both operating systems. -The badge-update step is `continue-on-error`, and its action logs a non-200 Gist response without turning that response into a failed step. Neither a badge nor a fixed test count in prose proves that the current revision passed. Report the actual command, target frameworks, result, and any tests deliberately not run. Do not infer NuGet publication, Native AOT support, or full LocalStack service behavior from a successful build or client-construction test. +On pushes to `master`, each `build-and-test` leg posts its counts to [BadgeSmith](https://github.com/localstack-dotnet/badge-smith) (`api.localstackfor.net`), signed with the organization's `TESTDATASECRET`; the step is `continue-on-error`, so a rejected post does not fail CI. Neither a badge nor a fixed test count in prose proves that the current revision passed. Report the actual command, target frameworks, result, and any tests deliberately not run. Do not infer NuGet publication, Native AOT support, or full LocalStack service behavior from a successful build or client-construction test. diff --git a/src/LocalStack.Client.Extensions/README.md b/src/LocalStack.Client.Extensions/README.md index f4a3bbb..a0d55ae 100644 --- a/src/LocalStack.Client.Extensions/README.md +++ b/src/LocalStack.Client.Extensions/README.md @@ -1,6 +1,6 @@ # LocalStack.NET -[![LocalStack.Client](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Fpackages%2Flocalstack.client%3Fsource%3Dnuget%26label%3DLocalStack.Client)](https://www.nuget.org/packages/LocalStack.Client/) [![LocalStack.Client.Extensions](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Fpackages%2Flocalstack.client.extensions%3Fsource%3Dnuget%26label%3DLocalStack.Client.Extensions)](https://www.nuget.org/packages/LocalStack.Client.Extensions/) [![Downloads](https://img.shields.io/nuget/dt/LocalStack.Client)](https://www.nuget.org/packages/LocalStack.Client/) [![CI/CD Pipeline](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/ci-cd.yml) [![Security](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/github-code-scanning/codeql) [![Tests](https://img.shields.io/endpoint?url=https://yvfdbfas85.execute-api.eu-central-1.amazonaws.com/live/badge/tests/linux?label=Tests)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/ci-cd.yml) +[![LocalStack.Client](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.localstackfor.net%2Fbadges%2Fpackages%2Fnuget%2FLocalStack.Client&label=LocalStack.Client)](https://www.nuget.org/packages/LocalStack.Client/) [![LocalStack.Client.Extensions](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.localstackfor.net%2Fbadges%2Fpackages%2Fnuget%2FLocalStack.Client.Extensions&label=LocalStack.Client.Extensions)](https://www.nuget.org/packages/LocalStack.Client.Extensions/) [![Downloads](https://img.shields.io/nuget/dt/LocalStack.Client)](https://www.nuget.org/packages/LocalStack.Client/) [![CI/CD Pipeline](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/ci-cd.yml) [![Security](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/github-code-scanning/codeql) [![Tests](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.localstackfor.net%2Fbadges%2Ftests%2Flinux%2Flocalstack-dotnet%2Flocalstack-dotnet-client%2Fmaster&label=Tests)](https://api.localstackfor.net/redirect/test-results/linux/localstack-dotnet/localstack-dotnet-client/master) ![LocalStack](https://github.com/localstack-dotnet/localstack-dotnet-client/blob/master/assets/localstack-dotnet.png?raw=true) diff --git a/src/LocalStack.Client/README.md b/src/LocalStack.Client/README.md index f4a3bbb..a0d55ae 100644 --- a/src/LocalStack.Client/README.md +++ b/src/LocalStack.Client/README.md @@ -1,6 +1,6 @@ # LocalStack.NET -[![LocalStack.Client](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Fpackages%2Flocalstack.client%3Fsource%3Dnuget%26label%3DLocalStack.Client)](https://www.nuget.org/packages/LocalStack.Client/) [![LocalStack.Client.Extensions](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Fpackages%2Flocalstack.client.extensions%3Fsource%3Dnuget%26label%3DLocalStack.Client.Extensions)](https://www.nuget.org/packages/LocalStack.Client.Extensions/) [![Downloads](https://img.shields.io/nuget/dt/LocalStack.Client)](https://www.nuget.org/packages/LocalStack.Client/) [![CI/CD Pipeline](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/ci-cd.yml) [![Security](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/github-code-scanning/codeql) [![Tests](https://img.shields.io/endpoint?url=https://yvfdbfas85.execute-api.eu-central-1.amazonaws.com/live/badge/tests/linux?label=Tests)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/ci-cd.yml) +[![LocalStack.Client](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.localstackfor.net%2Fbadges%2Fpackages%2Fnuget%2FLocalStack.Client&label=LocalStack.Client)](https://www.nuget.org/packages/LocalStack.Client/) [![LocalStack.Client.Extensions](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.localstackfor.net%2Fbadges%2Fpackages%2Fnuget%2FLocalStack.Client.Extensions&label=LocalStack.Client.Extensions)](https://www.nuget.org/packages/LocalStack.Client.Extensions/) [![Downloads](https://img.shields.io/nuget/dt/LocalStack.Client)](https://www.nuget.org/packages/LocalStack.Client/) [![CI/CD Pipeline](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/ci-cd.yml) [![Security](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/github-code-scanning/codeql) [![Tests](https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.localstackfor.net%2Fbadges%2Ftests%2Flinux%2Flocalstack-dotnet%2Flocalstack-dotnet-client%2Fmaster&label=Tests)](https://api.localstackfor.net/redirect/test-results/linux/localstack-dotnet/localstack-dotnet-client/master) ![LocalStack](https://github.com/localstack-dotnet/localstack-dotnet-client/blob/master/assets/localstack-dotnet.png?raw=true)