diff --git a/.github/actions/update-test-badge/README.md b/.github/actions/update-test-badge/README.md new file mode 100644 index 0000000..596bc61 --- /dev/null +++ b/.github/actions/update-test-badge/README.md @@ -0,0 +1,183 @@ +# 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 new file mode 100644 index 0000000..38d7c1c --- /dev/null +++ b/.github/actions/update-test-badge/action.yml @@ -0,0 +1,125 @@ +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/nuget.config b/.github/nuget.config new file mode 100644 index 0000000..af21e40 --- /dev/null +++ b/.github/nuget.config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + 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-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 0000000..5367bb5 --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,230 @@ +name: "CI/CD Pipeline" + +on: + push: + paths-ignore: + - "**.md" + - LICENSE + branches: + - "sdkv3-lts" + - "feature/*" + pull_request: + paths-ignore: + - "**.md" + - LICENSE + branches: + - sdkv3-lts + - "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) }} + BADGE_GIST_ID: "fab5b0837878e8cad455ad28190e0ef0" + + strategy: + fail-fast: false + matrix: + include: + - 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" + 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-v1-${{ hashFiles('**/packages.lock.json', '**/*.csproj', '**/Directory.Packages.props') }} + restore-keys: | + ${{ runner.os }}-nuget-v1- + + - 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" + id: 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: "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 + 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 }} + test_url_html: ${{ steps.test-results.outputs.url_html || '' }} + commit_sha: ${{ github.sha }} + run_id: ${{ github.run_id }} + repository: ${{ github.repository }} + server_url: ${{ github.server_url }} + + - name: "Upload Test Artifacts" + uses: actions/upload-artifact@v4 + if: failure() + with: + name: test-results-${{ matrix.name }} + path: | + **/*.trx + **/TestResults/**/* + retention-days: 7 + + continuous-deployment: + name: "Continuous Deployment" + runs-on: ubuntu-22.04 + needs: build-and-test + if: | + github.repository == 'localstack-dotnet/localstack-dotnet-client' && + github.event_name == 'push' && + (github.ref == 'refs/heads/sdkv3-lts' || startsWith(github.ref, 'refs/heads/feature/')) + 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-v1-${{ hashFiles('**/packages.lock.json', '**/*.csproj', '**/Directory.Packages.props') }} + restore-keys: | + ${{ runner.os }}-nuget-v1- + + - name: "Make build script executable" + run: chmod +x ./build.sh + + - name: "Setup GitHub Packages Configuration" + run: | + echo "🔐 Adding GitHub Packages authentication..." + 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 + + echo "🔧 Original nuget.config..." + cat nuget.config + + echo "📝 Backing up original nuget.config..." + cp nuget.config nuget.config.backup + + echo "🔧 Using GitHub-optimized nuget.config..." + cp .github/nuget.config nuget.config + + echo "🔧 Replaced nuget.config..." + cat nuget.config + + - name: "Pack & Publish LocalStack.Client" + id: pack-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: "Prepare Extensions Project" + run: | + echo "🔧 Preparing Extensions project to use LocalStack.Client package..." + ./build.sh --target nuget-prepare-extensions \ + --package-source github \ + --package-id LocalStack.Client.Extensions \ + --client-version ${{ steps.pack-client.outputs.client-version }} + + - 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: "packages-${{ github.ref_name }}-${{ 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 }} + + - name: "Cleanup Configuration" + if: always() + run: | + echo "🧹 Restoring original nuget.config..." + mv nuget.config.backup nuget.config || echo "⚠️ Original config not found, skipping restore" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..8cd92b0 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,27 @@ +name: Dependency Review + +on: + pull_request: + branches: + - sdkv3-lts + - "feature/*" + +permissions: + contents: read + pull-requests: write + +jobs: + dependency-review: + name: "Dependency Review" + runs-on: ubuntu-22.04 + 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-nuget.yml b/.github/workflows/publish-nuget.yml index 17d60ba..fe3bafa 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: @@ -6,14 +6,17 @@ on: package-version: description: "Package Version" required: true + localstack-client-version: + description: "LocalStack Client Version" + required: true package-source: type: choice description: Package Source required: true - default: "myget" + default: "github" options: - - myget - nuget + - github package-id: type: choice description: Package Id @@ -23,48 +26,108 @@ 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: "Checkout" + uses: actions/checkout@v4 - - name: Install NuGet - uses: NuGet/setup-nuget@v1.0.5 - - - 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: ${{ github.workspace }}/.nuget/packages + key: ${{ runner.os }}-nuget-v1-${{ hashFiles('**/packages.lock.json', '**/*.csproj', '**/Directory.Packages.props') }} + restore-keys: | + ${{ runner.os }}-nuget-v1- + + - name: "Make build script executable" + run: chmod +x ./build.sh - - name: Build & Test - run: ./build.sh + - name: "Build & Test" + run: ./build.sh --target tests --skipFunctionalTest true - - name: "Print Version" + - name: "Print Package Information" run: | - echo "Package Version: ${{ github.event.inputs.package-version }}" + 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: Remove Project Ref & Add latest pack + - name: "Setup GitHub Packages Configuration" + if: ${{ github.event.inputs.package-source == 'github' }} + run: | + echo "🔐 Adding GitHub Packages authentication..." + 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 + + echo "📝 Backing up original nuget.config..." + cp nuget.config nuget.config.backup + + echo "🔧 Using GitHub-optimized nuget.config..." + cp .github/nuget.config nuget.config + + - name: "Prepare Extensions Project" 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: | + ./build.sh --target nuget-prepare-extensions \ + --package-source ${{ github.event.inputs.package-source }} \ + --package-id ${{ github.event.inputs.package-id }} \ + --client-version ${{ github.event.inputs.localstack-client-version }} - - 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/Directory.Build.props b/Directory.Build.props index 532556c..ef6041f 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 + 1.6.1 + 1.4.1 true snupkg 13.0 diff --git a/Directory.Packages.props b/Directory.Packages.props index 99f4395..020608e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,19 +1,21 @@ - - + + - + - + + + @@ -28,7 +30,9 @@ + + @@ -36,7 +40,11 @@ + + + + @@ -57,6 +65,7 @@ + @@ -80,6 +89,7 @@ + @@ -111,12 +121,14 @@ + + @@ -146,8 +158,8 @@ - - + + diff --git a/README.md b/README.md index 1067aad..caea49a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# LocalStack .NET Client ![Nuget](https://img.shields.io/nuget/dt/LocalStack.Client) [![NuGet](https://img.shields.io/nuget/v/LocalStack.Client.svg)](https://www.nuget.org/packages/LocalStack.Client/) [![Space Metric](https://localstack-dotnet.testspace.com/spaces/232580/badge?token=bc6aa170f4388c662b791244948f6d2b14f16983)](https://localstack-dotnet.testspace.com/spaces/232580?utm_campaign=metric&utm_medium=referral&utm_source=badge "Test Cases") +# LocalStack .NET Client + +[![Nuget](https://img.shields.io/nuget/dt/LocalStack.Client)](https://www.nuget.org/packages/LocalStack.Client/) [![NuGet v2.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2F%3Fpackage%3Dlocalstack.client%26source%3Dnuget%26track%3D2%26includeprerelease%3Dtrue%26label%3Dnuget)](https://www.nuget.org/packages/LocalStack.Client/) [![NuGet v1.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2F%3Fpackage%3Dlocalstack.client%26source%3Dnuget%26track%3D1%26includeprerelease%3Dtrue%26label%3Dnuget)](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) [![macOS Tests](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Ftests%2Fmacos%3Flabel%3DTests%26track%3Dv1)](https://yvfdbfas85.execute-api.eu-central-1.amazonaws.com/live/redirect/test-results/linux?track=v1) > ## ⚠️ Maintenance Branch for AWS SDK v3 ⚠️ > @@ -14,31 +16,41 @@ > - 🚀 **[Go to master branch for v2.0 Development →](https://github.com/localstack-dotnet/localstack-dotnet-client/tree/master)** > - 📖 **[Read Full Roadmap & Migration Guide →](https://github.com/localstack-dotnet/localstack-dotnet-client/discussions/45)** +**Version Strategy**: + +- v2.x (AWS SDK v4) active development on [master branch](https://github.com/localstack-dotnet/localstack-dotnet-client/tree/master) +- v1.x (AWS SDK v3) Available on [sdkv3-lts branch](https://github.com/localstack-dotnet/localstack-dotnet-client/tree/sdkv3-lts), maintenance until July 2026 + ![LocalStack](https://github.com/localstack-dotnet/localstack-dotnet-client/blob/master/assets/localstack-dotnet.png?raw=true) 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. -## Version Compatibility +## 🚀 Platform Compatibility & Quality Status -| 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) | +### Supported Platforms -## Package Status +- [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) | [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) +- [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) +- [.NET Framework 4.7.2 and Above](https://dotnet.microsoft.com/download/dotnet-framework) -| Package | v1.x (AWS SDK v3) | v2.x (AWS SDK v4) - Development | -| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| LocalStack.Client | [![NuGet](https://img.shields.io/nuget/v/LocalStack.Client.svg)](https://www.nuget.org/packages/LocalStack.Client/) | [![MyGet](https://img.shields.io/myget/localstack-dotnet-client/v/LocalStack.Client.svg?label=myget)](https://www.myget.org/feed/localstack-dotnet-client/package/nuget/LocalStack.Client) | -| LocalStack.Client.Extensions | [![NuGet](https://img.shields.io/nuget/v/LocalStack.Client.Extensions.svg)](https://www.nuget.org/packages/LocalStack.Client.Extensions/) | [![MyGet](https://img.shields.io/myget/localstack-dotnet-client/v/LocalStack.Client.Extensions.svg?label=myget)](https://www.myget.org/feed/localstack-dotnet-client/package/nuget/LocalStack.Client.Extensions) | +### Build & Test Matrix -## Continuous Integration +| Category | Platform/Type | Status | Description | +|----------|---------------|--------|-------------| +| **🔧 Build** | Cross-Platform | [![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) | Matrix testing: Windows, Linux, macOS | +| **🔒 Security** | Static Analysis | [![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) | CodeQL analysis & dependency review | +| **🧪 Tests** | Linux | [![Linux Tests](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Ftests%2Flinux%3Flabel%3DTests%26track%3Dv1)](https://yvfdbfas85.execute-api.eu-central-1.amazonaws.com/live/redirect/test-results/linux?track=v1) | All framework targets | +| **🧪 Tests** | Windows | [![Windows Tests](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Ftests%2Fwindows%3Flabel%3DTests%26track%3Dv1)](https://yvfdbfas85.execute-api.eu-central-1.amazonaws.com/live/redirect/test-results/windows?track=v1) | All framework targets | +| **🧪 Tests** | macOS | [![macOS Tests](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Ftests%2Fmacos%3Flabel%3DTests%26track%3Dv1)](https://yvfdbfas85.execute-api.eu-central-1.amazonaws.com/live/redirect/test-results/macos?track=v1) | All framework targets | -| Build server | Platform | Build status | -| -------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Github Actions | Ubuntu | [![build-ubuntu](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-ubuntu.yml/badge.svg)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-ubuntu.yml) | -| Github Actions | Windows | [![build-windows](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-windows.yml/badge.svg)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-windows.yml) | -| Github Actions | macOS | [![build-macos](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-macos.yml/badge.svg)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-macos.yml) | +## Package Status + +| Package | NuGet.org | GitHub Packages (Nightly) | +|---------|-----------|---------------------------| +| **LocalStack.Client v1.x** | [![NuGet v1.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Fpackages%2Flocalstack.client%3Fsource%3Dnuget%26track%3D1%26label%3Dnuget)](https://www.nuget.org/packages/LocalStack.Client/) | [![Github v1.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Fpackages%2Flocalstack.client%3Fsource%3Dgithub%26track%3D1%26includeprerelease%3Dtrue%26label%3Dgithub)](https://github.com/localstack-dotnet/localstack-dotnet-client/pkgs/nuget/LocalStack.Client) | +| **LocalStack.Client v2.x** | [![NuGet v2.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Fpackages%2Flocalstack.client%3Fsource%3Dnuget%26track%3D2%26includeprerelease%3Dtrue%26label%3Dnuget)](https://www.nuget.org/packages/LocalStack.Client/) | [![Github v2.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Fpackages%2Flocalstack.client%3Fsource%3Dgithub%26track%3D2%26includeprerelease%3Dtrue%26label%3Dgithub)](https://github.com/localstack-dotnet/localstack-dotnet-client/pkgs/nuget/LocalStack.Client) | +| **LocalStack.Client.Extensions v1.x** | [![NuGet v1.x](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%26track%3D1%26label%3Dnuget)](https://www.nuget.org/packages/LocalStack.Client.Extensions/) | [![GitHub Packages v1.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2F%3Fpackage%3Dlocalstack.client.extensions%26source%3Dgithub%26track%3D1%26includeprerelease%3Dtrue%26label%3Dgithub)](https://github.com/localstack-dotnet/localstack-dotnet-client/pkgs/nuget/LocalStack.Client.Extensions) | +| **LocalStack.Client.Extensions v2.x** | [![NuGet v2.x](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%26track%3D2%26includeprerelease%3Dtrue%26label%3Dnuget)](https://www.nuget.org/packages/LocalStack.Client.Extensions/) | [![GitHub Packages v2.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2F%3Fpackage%3Dlocalstack.client.extensions%26source%3Dgithub%26track%3D2%26includeprerelease%3Dtrue%26label%3Dgithub)](https://github.com/localstack-dotnet/localstack-dotnet-client/pkgs/nuget/LocalStack.Client.Extensions) | ## Table of Contents @@ -56,13 +68,6 @@ Localstack.NET is an easy-to-use .NET client for [LocalStack](https://github.com 7. [Changelog](#changelog) 8. [License](#license) -## Supported Platforms - -- [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) -- [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) -- [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) -- [.NET 4.6.2 and Above](https://dotnet.microsoft.com/download/dotnet-framework) - ## Why LocalStack.NET Client? - **Consistent Client Configuration:** LocalStack.NET eliminates the need for manual endpoint configuration, providing a standardized and familiar approach to initializing clients. diff --git a/build.sh b/build.sh old mode 100644 new mode 100755 index 2b2c46b..e926b66 --- a/build.sh +++ b/build.sh @@ -1,2 +1,4 @@ +#!/bin/bash + dotnet build ./build/LocalStack.Build/LocalStack.Build.csproj > /dev/null 2>&1 dotnet run --project ./build/LocalStack.Build/LocalStack.Build.csproj --no-launch-profile --no-build -- "$@" diff --git a/build/LocalStack.Build/BuildContext.cs b/build/LocalStack.Build/BuildContext.cs index 4000e4b..9550076 100644 --- a/build/LocalStack.Build/BuildContext.cs +++ b/build/LocalStack.Build/BuildContext.cs @@ -4,23 +4,39 @@ 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"; + + // Cached package versions to ensure consistency across pack/publish operations + private string? _clientPackageVersion; + private string? _extensionsPackageVersion; + 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"); + ClientVersion = context.Argument("client-version", default(string)); 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", "sdkv3-lts"); 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 +44,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(); } @@ -53,12 +69,18 @@ public BuildContext(ICakeContext context) : base(context) public string PackageVersion { get; } + public string ClientVersion { get; } + public string PackageId { get; } public string PackageSecret { get; } public string PackageSource { get; } + public bool UseDirectoryPropsVersion { get; } + + public string BranchName { get; } + public ImmutableDictionary PackageSourceMap { get; } public ImmutableDictionary PackageIdProjMap { get; } @@ -83,35 +105,56 @@ public BuildContext(ICakeContext context) : base(context) public ConvertableFilePath LocalStackClientExtProjFile { get; } - public static void ValidateArgument(string argumentName, string argument) + /// + /// Gets the effective package version for LocalStack.Client package. + /// This value is cached to ensure consistency across pack and publish operations. + /// + public string GetClientPackageVersion() { - if (string.IsNullOrWhiteSpace(argument)) - { - throw new Exception($"{argumentName} can not be null or empty"); - } + return _clientPackageVersion ??= UseDirectoryPropsVersion + ? GetDynamicVersionFromProps("PackageMainVersion") + : PackageVersion; } - public void InstallXUnitNugetPackage() + /// + /// Gets the effective package version for LocalStack.Client.Extensions package. + /// This value is cached to ensure consistency across pack and publish operations. + /// + public string GetExtensionsPackageVersion() { - if (!Directory.Exists("testrunner")) - { - Directory.CreateDirectory("testrunner"); - } + return _extensionsPackageVersion ??= UseDirectoryPropsVersion + ? GetDynamicVersionFromProps("PackageExtensionVersion") + : PackageVersion; + } - var nugetInstallSettings = new NuGetInstallSettings + /// + /// Gets the effective package version for the specified package ID. + /// This method provides a unified interface for accessing cached package versions. + /// + /// The package ID (LocalStack.Client or LocalStack.Client.Extensions) + /// The cached package version + public string GetEffectivePackageVersion(string packageId) + { + return packageId switch { - Version = "2.8.1", Verbosity = NuGetVerbosity.Normal, OutputDirectory = "testrunner", WorkingDirectory = "." + LocalStackClientProjName => GetClientPackageVersion(), + LocalStackClientExtensionsProjName => GetExtensionsPackageVersion(), + _ => throw new ArgumentException($"Unknown package ID: {packageId}", nameof(packageId)), }; + } - this.NuGetInstall("xunit.runner.console", nugetInstallSettings); + public static void ValidateArgument(string argumentName, string argument) + { + if (string.IsNullOrWhiteSpace(argument)) + { + throw new Exception($"{argumentName} can not be null or empty"); + } } 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,47 +173,161 @@ 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 (exitCode != 0) + if (result == 0) { - throw new InvalidOperationException($"Exit code: {exitCode}"); + this.Information("✅ Mono is already installed. Skipping installation."); + return; } - } - public string GetProjectVersion() - { - FilePath file = this.File("./src/Directory.Build.props"); + this.Information("Mono not found. Starting installation on Linux for .NET Framework test platform support..."); - this.Information(file.FullPath); + // Add Mono repository key + int exitCode1 = this.StartProcess("sudo", new ProcessSettings + { + Arguments = "apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF", + }); - string project = File.ReadAllText(file.FullPath, Encoding.UTF8); - int startIndex = project.IndexOf("", StringComparison.Ordinal) + "".Length; - int endIndex = project.IndexOf("", startIndex, StringComparison.Ordinal); + if (exitCode1 != 0) + { + this.Warning($"⚠️ Failed to add Mono repository key (exit code: {exitCode1})"); + return; + } - string version = project.Substring(startIndex, endIndex - startIndex); - version = $"{version}.{PackageVersion}"; + // 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" }); - return version; + if (exitCode4 != 0) + { + 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 GetExtensionProjectVersion() + /// + /// 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) { - FilePath file = this.File(LocalStackClientExtProjFile); + if (!PackageIdProjMap.TryGetValue(packageId, out FilePath? projectFile) || projectFile == null) + { + throw new ArgumentException($"Unknown package ID: {packageId}", nameof(packageId)); + } - this.Information(file.FullPath); + string[] frameworks = GetProjectTargetFrameworks(projectFile.FullPath); + return string.Join(", ", frameworks); + } - string project = File.ReadAllText(file.FullPath, Encoding.UTF8); - int startIndex = project.IndexOf("", StringComparison.Ordinal) + "".Length; - int endIndex = project.IndexOf("", startIndex, StringComparison.Ordinal); + /// + /// 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 = $""; - string version = project.Substring(startIndex, endIndex - startIndex); - version = $"{version}.{PackageVersion}"; + 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 == "sdkv3-lts") + { + // For sdkv3-lts: 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}"); + } - return version; + // Fallback to timestamp-based identifier + return DateTime.UtcNow.ToString("HHmmss", System.Globalization.CultureInfo.InvariantCulture); } private string[] GetProjectTargetFrameworks(string csprojPath) @@ -185,7 +342,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 +361,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/CakeTasks/BuildTask.cs b/build/LocalStack.Build/CakeTasks/BuildTask.cs new file mode 100644 index 0000000..e07da72 --- /dev/null +++ b/build/LocalStack.Build/CakeTasks/BuildTask.cs @@ -0,0 +1,8 @@ +[TaskName("build"), IsDependentOn(typeof(InitTask))] +public sealed class BuildTask : FrostingTask +{ + public override void Run(BuildContext context) + { + context.DotNetBuild(context.SlnFilePath, new DotNetBuildSettings { Configuration = context.BuildConfiguration }); + } +} \ No newline at end of file diff --git a/build/LocalStack.Build/CakeTasks/InitTask.cs b/build/LocalStack.Build/CakeTasks/InitTask.cs new file mode 100644 index 0000000..d438009 --- /dev/null +++ b/build/LocalStack.Build/CakeTasks/InitTask.cs @@ -0,0 +1,18 @@ +[TaskName("init")] +public sealed class InitTask : FrostingTask +{ + public override void Run(BuildContext context) + { + ConsoleHelper.WriteRule("Initialization"); + ConsoleHelper.WriteHeader(); + + context.StartProcess("dotnet", new ProcessSettings { Arguments = "--info" }); + + if (!context.IsRunningOnUnix()) + { + return; + } + + context.StartProcess("git", new ProcessSettings { Arguments = "config --global core.autocrlf true" }); + } +} \ No newline at end of file diff --git a/build/LocalStack.Build/CakeTasks/Nuget/NugetPackAndPublishTask.cs b/build/LocalStack.Build/CakeTasks/Nuget/NugetPackAndPublishTask.cs new file mode 100644 index 0000000..215220b --- /dev/null +++ b/build/LocalStack.Build/CakeTasks/Nuget/NugetPackAndPublishTask.cs @@ -0,0 +1,22 @@ +using LocalStack.Build.CakeTasks.Nuget.Services; + +[TaskName("nuget-pack-and-publish")] +public sealed class NugetPackAndPublishTask : FrostingTask +{ + public override void Run(BuildContext context) + { + ConsoleHelper.WriteRule("Pack & Publish Pipeline"); + + string effectiveVersion = context.GetEffectivePackageVersion(context.PackageId); + ConsoleHelper.WriteInfo($"Using consistent version: {effectiveVersion}"); + + ConsoleHelper.WriteProcessing("Step 1: Creating package..."); + PackageOperations.PackSinglePackage(context, context.PackageId); + + ConsoleHelper.WriteProcessing("Step 2: Publishing package..."); + PackageOperations.PublishSinglePackage(context, context.PackageId); + + ConsoleHelper.WriteSuccess("Pack & Publish pipeline completed successfully!"); + ConsoleHelper.WriteRule(); + } +} \ No newline at end of file diff --git a/build/LocalStack.Build/CakeTasks/Nuget/NugetPackTask.cs b/build/LocalStack.Build/CakeTasks/Nuget/NugetPackTask.cs new file mode 100644 index 0000000..c1fe0f0 --- /dev/null +++ b/build/LocalStack.Build/CakeTasks/Nuget/NugetPackTask.cs @@ -0,0 +1,37 @@ +using LocalStack.Build.CakeTasks.Nuget.Services; + +[TaskName("nuget-pack")] +public sealed class NugetPackTask : FrostingTask +{ + public override void Run(BuildContext 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) + { + PackageOperations.PackSinglePackage(context, packageId); + } +} \ No newline at end of file diff --git a/build/LocalStack.Build/CakeTasks/Nuget/NugetPrepareExtensionsTask.cs b/build/LocalStack.Build/CakeTasks/Nuget/NugetPrepareExtensionsTask.cs new file mode 100644 index 0000000..053dcc9 --- /dev/null +++ b/build/LocalStack.Build/CakeTasks/Nuget/NugetPrepareExtensionsTask.cs @@ -0,0 +1,65 @@ +[TaskName("nuget-prepare-extensions")] +public sealed class NugetPrepareExtensionsTask : FrostingTask +{ + public override void Run(BuildContext context) + { + ConsoleHelper.WriteRule("Prepare Extensions Project"); + + // Validate that this is for Extensions package + if (context.PackageId != BuildContext.LocalStackClientExtensionsProjName) + { + throw new InvalidOperationException($"This task is only for {BuildContext.LocalStackClientExtensionsProjName}, but received: {context.PackageId}"); + } + + // Client version must be explicitly provided + if (string.IsNullOrWhiteSpace(context.ClientVersion)) + { + throw new InvalidOperationException("Client version must be specified via --client-version parameter. This task does not generate versions automatically."); + } + + ConsoleHelper.WriteInfo($"Preparing Extensions project for LocalStack.Client v{context.ClientVersion}"); + ConsoleHelper.WriteInfo($"Package source: {context.PackageSource}"); + + PrepareExtensionsProject(context, context.ClientVersion); + + ConsoleHelper.WriteSuccess("Extensions project preparation completed!"); + ConsoleHelper.WriteRule(); + } + + private static void PrepareExtensionsProject(BuildContext context, string version) + { + ConsoleHelper.WriteProcessing("Updating Extensions project dependencies..."); + + try + { + // Use the Extensions project file path directly + string extensionsProject = context.LocalStackClientExtProjFile.Path.FullPath; + var clientProjectRef = context.File(context.LocalStackClientProjFile.Path.FullPath); + + // Remove project reference + context.DotNetRemoveReference(extensionsProject, [clientProjectRef]); + ConsoleHelper.WriteInfo("Removed project reference to LocalStack.Client"); + + // Add package reference with specific version and source + var packageSettings = new DotNetPackageAddSettings + { + Version = version, + }; + + // Add source if not NuGet (GitHub Packages, MyGet, etc.) + if (context.PackageSource != BuildContext.NuGetPackageSource) + { + packageSettings.Source = context.PackageSourceMap[context.PackageSource]; + ConsoleHelper.WriteInfo($"Using package source: {context.PackageSource}"); + } + + context.DotNetAddPackage(BuildContext.LocalStackClientProjName, extensionsProject, packageSettings); + ConsoleHelper.WriteSuccess($"Added package reference for {BuildContext.LocalStackClientProjName} v{version}"); + } + catch (Exception ex) + { + ConsoleHelper.WriteError($"Failed to prepare Extensions project: {ex.Message}"); + throw; + } + } +} \ No newline at end of file diff --git a/build/LocalStack.Build/CakeTasks/Nuget/NugetPushTask.cs b/build/LocalStack.Build/CakeTasks/Nuget/NugetPushTask.cs new file mode 100644 index 0000000..aea54d8 --- /dev/null +++ b/build/LocalStack.Build/CakeTasks/Nuget/NugetPushTask.cs @@ -0,0 +1,14 @@ +using LocalStack.Build.CakeTasks.Nuget.Services; + +[TaskName("nuget-push")] +public sealed class NugetPushTask : FrostingTask +{ + public override void Run(BuildContext context) + { + ConsoleHelper.WriteRule("Package Publishing"); + + PackageOperations.PublishSinglePackage(context, context.PackageId); + + ConsoleHelper.WriteRule(); + } +} \ No newline at end of file diff --git a/build/LocalStack.Build/CakeTasks/Nuget/Services/PackageOperations.cs b/build/LocalStack.Build/CakeTasks/Nuget/Services/PackageOperations.cs new file mode 100644 index 0000000..afdf69a --- /dev/null +++ b/build/LocalStack.Build/CakeTasks/Nuget/Services/PackageOperations.cs @@ -0,0 +1,227 @@ +#pragma warning disable CA1515 // Consider making public types internal + +namespace LocalStack.Build.CakeTasks.Nuget.Services; + +/// +/// Provides high-level package operations shared across NuGet tasks. +/// Two simple methods that handle all the complexity internally. +/// +public static class PackageOperations +{ + /// + /// Complete pack operation for a single package - handles everything from validation to success message. + /// + /// The build context + /// The package identifier + public static void PackSinglePackage(BuildContext context, string packageId) + { + string effectiveVersion = context.GetEffectivePackageVersion(packageId); + string packageTargetFrameworks = context.GetPackageTargetFrameworks(packageId); + + // Display package info + ConsoleHelper.WritePackageInfoTable(packageId, effectiveVersion, packageTargetFrameworks, context.BuildConfiguration, context.PackageSource); + + // Validate inputs + ValidatePackInputs(context, packageId, effectiveVersion); + + // Create package with progress + ConsoleHelper.WithProgress($"Creating {packageId} package", _ => CreatePackage(context, packageId, effectiveVersion)); + + // Success message + ConsoleHelper.WriteSuccess($"Successfully created {packageId} v{effectiveVersion}"); + ConsoleHelper.WriteInfo($"Package location: {context.ArtifactOutput}"); + + // Output version to GitHub Actions if this is LocalStack.Client + if (packageId == BuildContext.LocalStackClientProjName) + { + OutputVersionToGitHubActions(effectiveVersion); + } + } + + /// + /// Complete publish operation for a single package - handles everything from validation to success message. + /// + /// The build context + /// The package identifier + public static void PublishSinglePackage(BuildContext context, string packageId) + { + string effectiveVersion = context.GetEffectivePackageVersion(packageId); + string packageTargetFrameworks = context.GetPackageTargetFrameworks(packageId); + + // Validate inputs for publishing + ValidatePublishInputs(context, packageId); + + // Show version info + if (context.UseDirectoryPropsVersion) + { + ConsoleHelper.WriteInfo($"Using dynamic version: {effectiveVersion}"); + } + else + { + ConsoleHelper.WriteInfo($"Using version: {effectiveVersion}"); + } + + // Display package info + ConsoleHelper.WritePackageInfoTable(packageId, effectiveVersion, packageTargetFrameworks, context.BuildConfiguration, context.PackageSource); + + // Publish package with progress + ConsoleHelper.WithProgress("Publishing package", _ => PublishPackage(context, packageId, effectiveVersion)); + + // Success summary + var downloadUrl = GetDownloadUrl(context.PackageSource, packageId, effectiveVersion); + ConsoleHelper.WritePublicationSummary(packageId, effectiveVersion, context.PackageSource, downloadUrl); + } + + #region Private Implementation Details + + private static void CreatePackage(BuildContext context, string packageId, string version) + { + if (!Directory.Exists(context.ArtifactOutput)) + { + Directory.CreateDirectory(context.ArtifactOutput); + } + + 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, + NoBuild = false, + NoRestore = false, + MSBuildSettings = new DotNetMSBuildSettings(), + }; + + settings.MSBuildSettings.SetVersion(version); + context.DotNetPack(packageCsProj.FullPath, settings); + } + + private static void PublishPackage(BuildContext context, string packageId, string version) + { + ConvertableFilePath packageFile = context.ArtifactOutput + context.File($"{packageId}.{version}.nupkg"); + + if (!context.FileExists(packageFile)) + { + 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 {packageId} to {context.PackageSource}..."); + + context.DotNetNuGetPush(packageFile.Path.FullPath, new DotNetNuGetPushSettings() + { + ApiKey = packageSecret, + Source = packageSource, + }); + + ConsoleHelper.WriteSuccess($"Successfully published {packageId} v{version}"); + } + + private static void ValidatePackInputs(BuildContext context, string packageId, string effectiveVersion) + { + BuildContext.ValidateArgument("package-id", packageId); + BuildContext.ValidateArgument("package-source", context.PackageSource); + + if (context.UseDirectoryPropsVersion) + { + ConsoleHelper.WriteInfo("Using dynamic version generation from Directory.Build.props"); + return; + } + + ValidatePackageVersion(context, packageId, effectiveVersion); + } + + private static void ValidatePublishInputs(BuildContext context, string packageId) + { + BuildContext.ValidateArgument("package-id", packageId); + BuildContext.ValidateArgument("package-secret", context.PackageSecret); + BuildContext.ValidateArgument("package-source", context.PackageSource); + + if (!context.UseDirectoryPropsVersion) + { + BuildContext.ValidateArgument("package-version", context.PackageVersion); + } + } + + private static void ValidatePackageVersion(BuildContext context, string packageId, string version) + { + Match match = Regex.Match(version, @"^(\d+)\.(\d+)\.(\d+)([\.\-].*)*$", RegexOptions.IgnoreCase); + + if (!match.Success) + { + throw new Exception($"Invalid version: {version}"); + } + + if (context.PackageSource == BuildContext.GitHubPackageSource) + { + ConsoleHelper.WriteInfo("Skipping version validation for GitHub Packages source"); + return; + } + + try + { + string packageSource = context.PackageSourceMap[context.PackageSource]; + var nuGetListSettings = new NuGetListSettings { AllVersions = false, Source = [packageSource] }; + NuGetListItem nuGetListItem = context.NuGetList(packageId, nuGetListSettings).Single(item => item.Name == packageId); + string latestPackVersionStr = nuGetListItem.Version; + + Version packageVersion = Version.Parse(version); + Version latestPackVersion = Version.Parse(latestPackVersionStr); + + 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) + { + ConsoleHelper.WriteWarning($"Could not validate version against existing packages: {ex.Message}"); + } + } + + private static string GetDownloadUrl(string packageSource, string packageId, string version) + { + return packageSource switch + { + BuildContext.GitHubPackageSource => $"https://github.com/localstack-dotnet/localstack-dotnet-client/packages/nuget/{packageId}", + BuildContext.NuGetPackageSource => $"https://www.nuget.org/packages/{packageId}/{version}", + BuildContext.MyGetPackageSource => $"https://www.myget.org/packages/{packageId}/{version}", + _ => "Unknown package source", + }; + } + + /// + /// Outputs the package version to GitHub Actions for use in subsequent steps. + /// Only outputs if running in GitHub Actions environment. + /// + /// The package version to output + private static void OutputVersionToGitHubActions(string version) + { + string? githubOutput = Environment.GetEnvironmentVariable("GITHUB_OUTPUT"); + + if (string.IsNullOrWhiteSpace(githubOutput)) + { + return; + } + + try + { + var outputLine = $"client-version={version}"; + File.AppendAllText(githubOutput, outputLine + Environment.NewLine); + ConsoleHelper.WriteInfo($"📤 GitHub Actions Output: {outputLine}"); + } + catch (Exception ex) + { + ConsoleHelper.WriteWarning($"Failed to write to GitHub Actions output: {ex.Message}"); + } + } + + #endregion +} \ No newline at end of file diff --git a/build/LocalStack.Build/CakeTasks/TestTask.cs b/build/LocalStack.Build/CakeTasks/TestTask.cs new file mode 100644 index 0000000..f9f5036 --- /dev/null +++ b/build/LocalStack.Build/CakeTasks/TestTask.cs @@ -0,0 +1,70 @@ +[TaskName("tests"), IsDependentOn(typeof(BuildTask))] +public sealed class TestTask : FrostingTask +{ + public override void Run(BuildContext context) + { + const string testResults = "results.trx"; + + var settings = new DotNetTestSettings + { + NoRestore = !context.ForceRestore, NoBuild = !context.ForceBuild, Configuration = context.BuildConfiguration, Blame = true, + }; + + IEnumerable projMetadata = context.GetProjMetadata(); + + foreach (ProjMetadata testProj in projMetadata) + { + string testProjectPath = testProj.CsProjPath; + string targetFrameworks = string.Join(',', testProj.TargetFrameworks); + + ConsoleHelper.WriteInfo($"Target Frameworks: {targetFrameworks}"); + + foreach (string targetFramework in testProj.TargetFrameworks) + { + if (context.SkipFunctionalTest && testProj.AssemblyName == "LocalStack.Client.Functional.Tests") + { + ConsoleHelper.WriteWarning("Skipping Functional Tests"); + + continue; + } + + ConsoleHelper.WriteRule($"Running {targetFramework.ToUpper(System.Globalization.CultureInfo.CurrentCulture)} tests for {testProj.AssemblyName}"); + settings.Framework = targetFramework; + + if (testProj.AssemblyName == "LocalStack.Client.Functional.Tests") + { + ConsoleHelper.WriteProcessing("Deleting running docker containers"); + + try + { + string psOutput = context.DockerPs(new DockerContainerPsSettings() { All = true, Quiet = true }); + + if (!string.IsNullOrEmpty(psOutput)) + { + ConsoleHelper.WriteInfo($"Found containers: {psOutput}"); + + string[] containers = psOutput.Split([Environment.NewLine], StringSplitOptions.None); + context.DockerRm(containers); + } + } + catch + { + // ignored + } + } + + if (targetFramework == "net462" && context.IsRunningOnLinux()) + { + ConsoleHelper.WriteWarning("Skipping net462 tests on Linux platform (requires external Mono)"); + continue; + } + + string testFilePrefix = targetFramework.Replace('.', '-'); + settings.ArgumentCustomization = args => args.Append($" --logger \"trx;LogFileName={testFilePrefix}_{testResults}\""); + context.DotNetTest(testProjectPath, settings); + + ConsoleHelper.WriteSuccess($"Completed {targetFramework} tests for {testProj.AssemblyName}"); + } + } + } +} \ No newline at end of file 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..7ce57ab 100644 --- a/build/LocalStack.Build/GlobalUsings.cs +++ b/build/LocalStack.Build/GlobalUsings.cs @@ -2,15 +2,18 @@ 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.DotNet.Package.Add; 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 +25,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..388a455 100644 --- a/build/LocalStack.Build/Program.cs +++ b/build/LocalStack.Build/Program.cs @@ -3,192 +3,4 @@ return new CakeHost().UseContext().Run(args); [TaskName("Default"), IsDependentOn(typeof(TestTask))] -public class DefaultTask : FrostingTask -{ -} - -[TaskName("init")] -public sealed class InitTask : FrostingTask -{ - public override void Run(BuildContext context) - { - context.StartProcess("dotnet", new ProcessSettings { Arguments = "--info" }); - - if (!context.IsRunningOnUnix()) - { - return; - } - - 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)),] -public sealed class BuildTask : FrostingTask -{ - public override void Run(BuildContext context) - { - context.DotNetBuild(context.SlnFilePath, new DotNetBuildSettings { Configuration = context.BuildConfiguration }); - } -} - -[TaskName("tests"), IsDependentOn(typeof(BuildTask))] -public sealed class TestTask : FrostingTask -{ - public override void Run(BuildContext context) - { - const string testResults = "results.trx"; - - var settings = new DotNetTestSettings - { - NoRestore = !context.ForceRestore, NoBuild = !context.ForceBuild, Configuration = context.BuildConfiguration, Blame = true - }; - - IEnumerable projMetadata = context.GetProjMetadata(); - - foreach (ProjMetadata testProj in projMetadata) - { - string testProjectPath = testProj.CsProjPath; - string targetFrameworks = string.Join(",", testProj.TargetFrameworks); - - context.Warning($"Target Frameworks {targetFrameworks}"); - - foreach (string targetFramework in testProj.TargetFrameworks) - { - if (context.SkipFunctionalTest && testProj.AssemblyName == "LocalStack.Client.Functional.Tests") - { - context.Warning("Skipping Functional Tests"); - - continue; - } - - context.Warning( - $"=============Running {targetFramework.ToUpper(System.Globalization.CultureInfo.CurrentCulture)} tests for {testProj.AssemblyName}============="); - settings.Framework = targetFramework; - - if (testProj.AssemblyName == "LocalStack.Client.Functional.Tests") - { - context.Warning("Deleting running docker containers"); - - try - { - string psOutput = context.DockerPs(new DockerContainerPsSettings() { All = true, Quiet = true }); - - if (!string.IsNullOrEmpty(psOutput)) - { - context.Warning(psOutput); - - string[] containers = psOutput.Split(new[] { Environment.NewLine }, StringSplitOptions.None); - context.DockerRm(containers); - } - } - catch - { - // ignored - } - } - - 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 - { - string testFilePrefix = targetFramework.Replace(".", "-"); - settings.ArgumentCustomization = args => args.Append($" --logger \"trx;LogFileName={testFilePrefix}_{testResults}\""); - context.DotNetTest(testProjectPath, settings); - } - - context.Warning("=============================================================="); - } - } - } -} - -[TaskName("nuget-pack")] -public sealed class NugetPackTask : FrostingTask -{ - public override void Run(BuildContext context) - { - ValidatePackageVersion(context); - - if (!Directory.Exists(context.ArtifactOutput)) - { - Directory.CreateDirectory(context.ArtifactOutput); - } - - FilePath packageCsProj = context.PackageIdProjMap[context.PackageId]; - - var settings = new DotNetPackSettings - { - Configuration = context.BuildConfiguration, OutputDirectory = context.ArtifactOutput, MSBuildSettings = new DotNetMSBuildSettings() - }; - - settings.MSBuildSettings.SetVersion(context.PackageVersion); - - context.DotNetPack(packageCsProj.FullPath, settings); - } - - private static void ValidatePackageVersion(BuildContext context) - { - BuildContext.ValidateArgument("package-id", context.PackageId); - BuildContext.ValidateArgument("package-version", context.PackageVersion); - BuildContext.ValidateArgument("package-source", context.PackageSource); - - Match match = Regex.Match(context.PackageVersion, @"^(\d+)\.(\d+)\.(\d+)(\.(\d+))*$", RegexOptions.IgnoreCase); - - if (!match.Success) - { - throw new Exception($"Invalid version: {context.PackageVersion}"); - } - - string packageSource = context.PackageSourceMap[context.PackageSource]; - - 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; - - Version packageVersion = Version.Parse(context.PackageVersion); - Version latestPackVersion = Version.Parse(latestPackVersionStr); - - if (packageVersion <= latestPackVersion) - { - throw new Exception($"The new package version {context.PackageVersion} should be greater than the latest package version {latestPackVersionStr}"); - } - } -} - -[TaskName("nuget-push")] -public sealed class NugetPushTask : FrostingTask -{ - public override void Run(BuildContext context) - { - 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; - - ConvertableFilePath packageFile = context.ArtifactOutput + context.File($"{packageId}.{packageVersion}.nupkg"); - - if (!context.FileExists(packageFile)) - { - throw new Exception($"The specified {packageFile.Path} package file does not exists"); - } - - string packageSecret = context.PackageSecret; - string packageSource = context.PackageSourceMap[context.PackageSource]; - - context.DotNetNuGetPush(packageFile.Path.FullPath, new DotNetNuGetPushSettings() { ApiKey = packageSecret, Source = packageSource, }); - } -} \ No newline at end of file +public class DefaultTask : FrostingTask; \ 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..0062d08 --- /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.GetClientPackageVersion(), + BuildContext.LocalStackClientExtensionsProjName => context.GetExtensionsPackageVersion(), + _ => "Unknown", + }; + } +} \ No newline at end of file diff --git a/src/LocalStack.Client.Extensions/README.md b/src/LocalStack.Client.Extensions/README.md index 4bec638..a8c0441 100644 --- a/src/LocalStack.Client.Extensions/README.md +++ b/src/LocalStack.Client.Extensions/README.md @@ -1,21 +1,56 @@ -# LocalStack .NET Client ![Nuget](https://img.shields.io/nuget/dt/LocalStack.Client) [![NuGet](https://img.shields.io/nuget/v/LocalStack.Client.svg)](https://www.nuget.org/packages/LocalStack.Client/) [![Space Metric](https://localstack-dotnet.testspace.com/spaces/232580/badge?token=bc6aa170f4388c662b791244948f6d2b14f16983)](https://localstack-dotnet.testspace.com/spaces/232580?utm_campaign=metric&utm_medium=referral&utm_source=badge "Test Cases") +# LocalStack .NET Client + +[![Nuget](https://img.shields.io/nuget/dt/LocalStack.Client)](https://www.nuget.org/packages/LocalStack.Client/) [![NuGet v2.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2F%3Fpackage%3Dlocalstack.client%26source%3Dnuget%26track%3D2%26includeprerelease%3Dtrue%26label%3Dnuget)](https://www.nuget.org/packages/LocalStack.Client/) [![NuGet v1.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2F%3Fpackage%3Dlocalstack.client%26source%3Dnuget%26track%3D1%26includeprerelease%3Dtrue%26label%3Dnuget)](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) [![Linux Tests](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Ftests%2Flinux%3Flabel%3DTests%26track%3Dv1)](https://yvfdbfas85.execute-api.eu-central-1.amazonaws.com/live/redirect/test-results/linux?track=v1) + +> ## ⚠️ Maintenance Branch for AWS SDK v3 ⚠️ +> +> **Current Status**: This branch contains the legacy v1.x codebase which supports AWS SDK v3. It is now in **maintenance mode**. +> +> **Support Lifecycle**: +> +> - This version will only receive **critical security and bug fixes** +> - **End-of-Life (EOL)**: July 31, 2026 +> +> For active development, AWS SDK v4 support, and future features like Native AOT, please see the master branch. +> +> - 🚀 **[Go to master branch for v2.0 Development →](https://github.com/localstack-dotnet/localstack-dotnet-client/tree/master)** +> - 📖 **[Read Full Roadmap & Migration Guide →](https://github.com/localstack-dotnet/localstack-dotnet-client/discussions/45)** + +**Version Strategy**: + +- v2.x (AWS SDK v4) active development on [master branch](https://github.com/localstack-dotnet/localstack-dotnet-client/tree/master) +- v1.x (AWS SDK v3) Available on [sdkv3-lts branch](https://github.com/localstack-dotnet/localstack-dotnet-client/tree/sdkv3-lts), maintenance until July 2026 ![LocalStack](https://github.com/localstack-dotnet/localstack-dotnet-client/blob/master/assets/localstack-dotnet.png?raw=true) 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 | -| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| LocalStack.Client | [![NuGet](https://img.shields.io/nuget/v/LocalStack.Client.svg)](https://www.nuget.org/packages/LocalStack.Client/) | [![MyGet](https://img.shields.io/myget/localstack-dotnet-client/v/LocalStack.Client.svg?label=myget)](https://www.myget.org/feed/localstack-dotnet-client/package/nuget/LocalStack.Client) | -| LocalStack.Client.Extensions | [![NuGet](https://img.shields.io/nuget/v/LocalStack.Client.Extensions.svg)](https://www.nuget.org/packages/LocalStack.Client.Extensions/) | [![MyGet](https://img.shields.io/myget/localstack-dotnet-client/v/LocalStack.Client.Extensions.svg?label=myget)](https://www.myget.org/feed/localstack-dotnet-client/package/nuget/LocalStack.Client.Extensions) | +## 🚀 Platform Compatibility & Quality Status + +### Supported Platforms + +- [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) | [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) +- [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) +- [.NET Framework 4.7.2 and Above](https://dotnet.microsoft.com/download/dotnet-framework) + +### Build & Test Matrix + +| Category | Platform/Type | Status | Description | +|----------|---------------|--------|-------------| +| **🔧 Build** | Cross-Platform | [![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) | Matrix testing: Windows, Linux, macOS | +| **🔒 Security** | Static Analysis | [![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) | CodeQL analysis & dependency review | +| **🧪 Tests** | Linux | [![Linux Tests](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Ftests%2Flinux%3Flabel%3DTests%26track%3Dv1)](https://yvfdbfas85.execute-api.eu-central-1.amazonaws.com/live/redirect/test-results/linux?track=v1) | All framework targets | +| **🧪 Tests** | Windows | [![Windows Tests](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Ftests%2Fwindows%3Flabel%3DTests%26track%3Dv1)](https://yvfdbfas85.execute-api.eu-central-1.amazonaws.com/live/redirect/test-results/windows?track=v1) | All framework targets | +| **🧪 Tests** | macOS | [![macOS Tests](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Ftests%2Fmacos%3Flabel%3DTests%26track%3Dv1)](https://yvfdbfas85.execute-api.eu-central-1.amazonaws.com/live/redirect/test-results/macos?track=v1) | All framework targets | -## Continuous Integration +## Package Status -| Build server | Platform | Build status | -| -------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Github Actions | Ubuntu | [![build-ubuntu](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-ubuntu.yml/badge.svg)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-ubuntu.yml) | -| Github Actions | Windows | [![build-windows](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-windows.yml/badge.svg)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-windows.yml) | -| Github Actions | macOS | [![build-macos](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-macos.yml/badge.svg)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-macos.yml) | +| Package | NuGet.org | GitHub Packages (Nightly) | +|---------|-----------|---------------------------| +| **LocalStack.Client v1.x** | [![NuGet v1.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Fpackages%2Flocalstack.client%3Fsource%3Dnuget%26track%3D1%26label%3Dnuget)](https://www.nuget.org/packages/LocalStack.Client/) | [![Github v1.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Fpackages%2Flocalstack.client%3Fsource%3Dgithub%26track%3D1%26includeprerelease%3Dtrue%26label%3Dgithub)](https://github.com/localstack-dotnet/localstack-dotnet-client/pkgs/nuget/LocalStack.Client) | +| **LocalStack.Client v2.x** | [![NuGet v2.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Fpackages%2Flocalstack.client%3Fsource%3Dnuget%26track%3D2%26includeprerelease%3Dtrue%26label%3Dnuget)](https://www.nuget.org/packages/LocalStack.Client/) | [![Github v2.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Fpackages%2Flocalstack.client%3Fsource%3Dgithub%26track%3D2%26includeprerelease%3Dtrue%26label%3Dgithub)](https://github.com/localstack-dotnet/localstack-dotnet-client/pkgs/nuget/LocalStack.Client) | +| **LocalStack.Client.Extensions v1.x** | [![NuGet v1.x](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%26track%3D1%26label%3Dnuget)](https://www.nuget.org/packages/LocalStack.Client.Extensions/) | [![GitHub Packages v1.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2F%3Fpackage%3Dlocalstack.client.extensions%26source%3Dgithub%26track%3D1%26includeprerelease%3Dtrue%26label%3Dgithub)](https://github.com/localstack-dotnet/localstack-dotnet-client/pkgs/nuget/LocalStack.Client.Extensions) | +| **LocalStack.Client.Extensions v2.x** | [![NuGet v2.x](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%26track%3D2%26includeprerelease%3Dtrue%26label%3Dnuget)](https://www.nuget.org/packages/LocalStack.Client.Extensions/) | [![GitHub Packages v2.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2F%3Fpackage%3Dlocalstack.client.extensions%26source%3Dgithub%26track%3D2%26includeprerelease%3Dtrue%26label%3Dgithub)](https://github.com/localstack-dotnet/localstack-dotnet-client/pkgs/nuget/LocalStack.Client.Extensions) | ## Table of Contents @@ -33,13 +68,6 @@ Localstack.NET is an easy-to-use .NET client for [LocalStack](https://github.com 7. [Changelog](#changelog) 8. [License](#license) -## Supported Platforms - -- [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) -- [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) -- [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) -- [.NET 4.6.2 and Above](https://dotnet.microsoft.com/download/dotnet-framework) - ## Why LocalStack.NET Client? - **Consistent Client Configuration:** LocalStack.NET eliminates the need for manual endpoint configuration, providing a standardized and familiar approach to initializing clients. @@ -189,4 +217,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/Enums/AwsService.cs b/src/LocalStack.Client/Enums/AwsService.cs index 1ad3c53..22dd038 100644 --- a/src/LocalStack.Client/Enums/AwsService.cs +++ b/src/LocalStack.Client/Enums/AwsService.cs @@ -112,4 +112,16 @@ public enum AwsService AppConfigData, Pinpoint, Pipes, + Account, + ACMPCA, + Bedrock, + CloudControl, + CodeBuild, + CodeConnections, + CodeDeploy, + CodePipeline, + ElasticTranscoder, + MemoryDb, + Shield, + VerifiedPermissions, } \ No newline at end of file diff --git a/src/LocalStack.Client/Enums/AwsServiceEndpointMetadata.cs b/src/LocalStack.Client/Enums/AwsServiceEndpointMetadata.cs index 83c0001..b5405fd 100644 --- a/src/LocalStack.Client/Enums/AwsServiceEndpointMetadata.cs +++ b/src/LocalStack.Client/Enums/AwsServiceEndpointMetadata.cs @@ -115,6 +115,18 @@ public class AwsServiceEndpointMetadata public static readonly AwsServiceEndpointMetadata AppConfigData = new("AppConfigData", "appconfigdata", CommonEndpointPattern, 4632, AwsService.AppConfigData); public static readonly AwsServiceEndpointMetadata Pinpoint = new("Pinpoint", "pinpoint", CommonEndpointPattern, 4566, AwsService.Pinpoint); public static readonly AwsServiceEndpointMetadata Pipes = new("Pipes", "pipes", CommonEndpointPattern, 4566, AwsService.Pipes); + public static readonly AwsServiceEndpointMetadata Account = new("Account", "account", CommonEndpointPattern, 4567, AwsService.Account); + public static readonly AwsServiceEndpointMetadata ACMPCA = new("ACM PCA", "acm-pca", CommonEndpointPattern, 4567, AwsService.ACMPCA); + public static readonly AwsServiceEndpointMetadata Bedrock = new("Bedrock", "bedrock", CommonEndpointPattern, 4567, AwsService.Bedrock); + public static readonly AwsServiceEndpointMetadata CloudControl = new("CloudControl", "cloudcontrol", CommonEndpointPattern, 4567, AwsService.CloudControl); + public static readonly AwsServiceEndpointMetadata CodeBuild = new("CodeBuild", "codebuild", CommonEndpointPattern, 4567, AwsService.CodeBuild); + public static readonly AwsServiceEndpointMetadata CodeConnections = new("CodeConnections", "codeconnections", CommonEndpointPattern, 4567, AwsService.CodeConnections); + public static readonly AwsServiceEndpointMetadata CodeDeploy = new("CodeDeploy", "codedeploy", CommonEndpointPattern, 4567, AwsService.CodeDeploy); + public static readonly AwsServiceEndpointMetadata CodePipeline = new("CodePipeline", "codepipeline", CommonEndpointPattern, 4567, AwsService.CodePipeline); + public static readonly AwsServiceEndpointMetadata ElasticTranscoder = new("Elastic Transcoder", "elastictranscoder", CommonEndpointPattern, 4567, AwsService.ElasticTranscoder); + public static readonly AwsServiceEndpointMetadata MemoryDb = new("MemoryDB", "memorydb", CommonEndpointPattern, 4567, AwsService.MemoryDb); + public static readonly AwsServiceEndpointMetadata Shield = new("Shield", "shield", CommonEndpointPattern, 4567, AwsService.Shield); + public static readonly AwsServiceEndpointMetadata VerifiedPermissions = new("VerifiedPermissions", "verifiedpermissions", CommonEndpointPattern, 4567, AwsService.VerifiedPermissions); public static readonly AwsServiceEndpointMetadata[] All = [ @@ -124,7 +136,7 @@ public class AwsServiceEndpointMetadata CloudTrail, Glacier, Batch, Organizations, AutoScaling, MediaStore, MediaStoreData, Transfer, Acm, CodeCommit, KinesisAnalytics, KinesisAnalyticsV2, Amplify, ApplicationAutoscaling, Kafka, ApiGatewayManagementApi, TimeStreamQuery, TimeStreamWrite, S3Control, ElbV2, Support, Neptune, DocDb, ServiceDiscovery, ServerlessApplicationRepository, AppConfig, CostExplorer, MediaConvert, ResourceGroupsTaggingApi, ResourceGroups, Efs, Backup, LakeFormation, Waf, WafV2, ConfigService, Mwaa, EventBridge, Fis, MarketplaceMetering, Transcribe, Mq, EmrServerless, Appflow, Route53Domains, Keyspaces, Scheduler, Ram, AppConfigData, - Pinpoint, Pipes, + Pinpoint, Pipes, Account, ACMPCA, Bedrock, CloudControl, CodeBuild, CodeConnections, CodeDeploy, CodePipeline, ElasticTranscoder, MemoryDb, Shield, VerifiedPermissions, ]; private AwsServiceEndpointMetadata(string serviceId, string cliName, string endPointPattern, int port, AwsService @enum) diff --git a/src/LocalStack.Client/README.md b/src/LocalStack.Client/README.md index 4bec638..a8c0441 100644 --- a/src/LocalStack.Client/README.md +++ b/src/LocalStack.Client/README.md @@ -1,21 +1,56 @@ -# LocalStack .NET Client ![Nuget](https://img.shields.io/nuget/dt/LocalStack.Client) [![NuGet](https://img.shields.io/nuget/v/LocalStack.Client.svg)](https://www.nuget.org/packages/LocalStack.Client/) [![Space Metric](https://localstack-dotnet.testspace.com/spaces/232580/badge?token=bc6aa170f4388c662b791244948f6d2b14f16983)](https://localstack-dotnet.testspace.com/spaces/232580?utm_campaign=metric&utm_medium=referral&utm_source=badge "Test Cases") +# LocalStack .NET Client + +[![Nuget](https://img.shields.io/nuget/dt/LocalStack.Client)](https://www.nuget.org/packages/LocalStack.Client/) [![NuGet v2.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2F%3Fpackage%3Dlocalstack.client%26source%3Dnuget%26track%3D2%26includeprerelease%3Dtrue%26label%3Dnuget)](https://www.nuget.org/packages/LocalStack.Client/) [![NuGet v1.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2F%3Fpackage%3Dlocalstack.client%26source%3Dnuget%26track%3D1%26includeprerelease%3Dtrue%26label%3Dnuget)](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) [![Linux Tests](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Ftests%2Flinux%3Flabel%3DTests%26track%3Dv1)](https://yvfdbfas85.execute-api.eu-central-1.amazonaws.com/live/redirect/test-results/linux?track=v1) + +> ## ⚠️ Maintenance Branch for AWS SDK v3 ⚠️ +> +> **Current Status**: This branch contains the legacy v1.x codebase which supports AWS SDK v3. It is now in **maintenance mode**. +> +> **Support Lifecycle**: +> +> - This version will only receive **critical security and bug fixes** +> - **End-of-Life (EOL)**: July 31, 2026 +> +> For active development, AWS SDK v4 support, and future features like Native AOT, please see the master branch. +> +> - 🚀 **[Go to master branch for v2.0 Development →](https://github.com/localstack-dotnet/localstack-dotnet-client/tree/master)** +> - 📖 **[Read Full Roadmap & Migration Guide →](https://github.com/localstack-dotnet/localstack-dotnet-client/discussions/45)** + +**Version Strategy**: + +- v2.x (AWS SDK v4) active development on [master branch](https://github.com/localstack-dotnet/localstack-dotnet-client/tree/master) +- v1.x (AWS SDK v3) Available on [sdkv3-lts branch](https://github.com/localstack-dotnet/localstack-dotnet-client/tree/sdkv3-lts), maintenance until July 2026 ![LocalStack](https://github.com/localstack-dotnet/localstack-dotnet-client/blob/master/assets/localstack-dotnet.png?raw=true) 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 | -| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| LocalStack.Client | [![NuGet](https://img.shields.io/nuget/v/LocalStack.Client.svg)](https://www.nuget.org/packages/LocalStack.Client/) | [![MyGet](https://img.shields.io/myget/localstack-dotnet-client/v/LocalStack.Client.svg?label=myget)](https://www.myget.org/feed/localstack-dotnet-client/package/nuget/LocalStack.Client) | -| LocalStack.Client.Extensions | [![NuGet](https://img.shields.io/nuget/v/LocalStack.Client.Extensions.svg)](https://www.nuget.org/packages/LocalStack.Client.Extensions/) | [![MyGet](https://img.shields.io/myget/localstack-dotnet-client/v/LocalStack.Client.Extensions.svg?label=myget)](https://www.myget.org/feed/localstack-dotnet-client/package/nuget/LocalStack.Client.Extensions) | +## 🚀 Platform Compatibility & Quality Status + +### Supported Platforms + +- [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) | [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) +- [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) +- [.NET Framework 4.7.2 and Above](https://dotnet.microsoft.com/download/dotnet-framework) + +### Build & Test Matrix + +| Category | Platform/Type | Status | Description | +|----------|---------------|--------|-------------| +| **🔧 Build** | Cross-Platform | [![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) | Matrix testing: Windows, Linux, macOS | +| **🔒 Security** | Static Analysis | [![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) | CodeQL analysis & dependency review | +| **🧪 Tests** | Linux | [![Linux Tests](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Ftests%2Flinux%3Flabel%3DTests%26track%3Dv1)](https://yvfdbfas85.execute-api.eu-central-1.amazonaws.com/live/redirect/test-results/linux?track=v1) | All framework targets | +| **🧪 Tests** | Windows | [![Windows Tests](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Ftests%2Fwindows%3Flabel%3DTests%26track%3Dv1)](https://yvfdbfas85.execute-api.eu-central-1.amazonaws.com/live/redirect/test-results/windows?track=v1) | All framework targets | +| **🧪 Tests** | macOS | [![macOS Tests](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Ftests%2Fmacos%3Flabel%3DTests%26track%3Dv1)](https://yvfdbfas85.execute-api.eu-central-1.amazonaws.com/live/redirect/test-results/macos?track=v1) | All framework targets | -## Continuous Integration +## Package Status -| Build server | Platform | Build status | -| -------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Github Actions | Ubuntu | [![build-ubuntu](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-ubuntu.yml/badge.svg)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-ubuntu.yml) | -| Github Actions | Windows | [![build-windows](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-windows.yml/badge.svg)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-windows.yml) | -| Github Actions | macOS | [![build-macos](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-macos.yml/badge.svg)](https://github.com/localstack-dotnet/localstack-dotnet-client/actions/workflows/build-macos.yml) | +| Package | NuGet.org | GitHub Packages (Nightly) | +|---------|-----------|---------------------------| +| **LocalStack.Client v1.x** | [![NuGet v1.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Fpackages%2Flocalstack.client%3Fsource%3Dnuget%26track%3D1%26label%3Dnuget)](https://www.nuget.org/packages/LocalStack.Client/) | [![Github v1.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Fpackages%2Flocalstack.client%3Fsource%3Dgithub%26track%3D1%26includeprerelease%3Dtrue%26label%3Dgithub)](https://github.com/localstack-dotnet/localstack-dotnet-client/pkgs/nuget/LocalStack.Client) | +| **LocalStack.Client v2.x** | [![NuGet v2.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Fpackages%2Flocalstack.client%3Fsource%3Dnuget%26track%3D2%26includeprerelease%3Dtrue%26label%3Dnuget)](https://www.nuget.org/packages/LocalStack.Client/) | [![Github v2.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2Fbadge%2Fpackages%2Flocalstack.client%3Fsource%3Dgithub%26track%3D2%26includeprerelease%3Dtrue%26label%3Dgithub)](https://github.com/localstack-dotnet/localstack-dotnet-client/pkgs/nuget/LocalStack.Client) | +| **LocalStack.Client.Extensions v1.x** | [![NuGet v1.x](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%26track%3D1%26label%3Dnuget)](https://www.nuget.org/packages/LocalStack.Client.Extensions/) | [![GitHub Packages v1.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2F%3Fpackage%3Dlocalstack.client.extensions%26source%3Dgithub%26track%3D1%26includeprerelease%3Dtrue%26label%3Dgithub)](https://github.com/localstack-dotnet/localstack-dotnet-client/pkgs/nuget/LocalStack.Client.Extensions) | +| **LocalStack.Client.Extensions v2.x** | [![NuGet v2.x](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%26track%3D2%26includeprerelease%3Dtrue%26label%3Dnuget)](https://www.nuget.org/packages/LocalStack.Client.Extensions/) | [![GitHub Packages v2.x](https://img.shields.io/endpoint?url=https%3A%2F%2Fyvfdbfas85.execute-api.eu-central-1.amazonaws.com%2Flive%2F%3Fpackage%3Dlocalstack.client.extensions%26source%3Dgithub%26track%3D2%26includeprerelease%3Dtrue%26label%3Dgithub)](https://github.com/localstack-dotnet/localstack-dotnet-client/pkgs/nuget/LocalStack.Client.Extensions) | ## Table of Contents @@ -33,13 +68,6 @@ Localstack.NET is an easy-to-use .NET client for [LocalStack](https://github.com 7. [Changelog](#changelog) 8. [License](#license) -## Supported Platforms - -- [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) -- [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) -- [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) -- [.NET 4.6.2 and Above](https://dotnet.microsoft.com/download/dotnet-framework) - ## Why LocalStack.NET Client? - **Consistent Client Configuration:** LocalStack.NET eliminates the need for manual endpoint configuration, providing a standardized and familiar approach to initializing clients. @@ -189,4 +217,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.Functional.Tests/CloudFormation/CloudFormationStackExecutor.cs b/tests/LocalStack.Client.Functional.Tests/CloudFormation/CloudFormationStackExecutor.cs index 679dc50..3cdc629 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); @@ -400,8 +402,9 @@ private async Task WaitStackToCompleteAsync(DateTimeOffset minTimeStampFo var mostRecentEventId = string.Empty; var waitingMessage = $"... Waiting for CloudFormation stack {cloudFormationResource.Name} to be ready"; + logger.LogInformation(waitingMessage); - logger.LogInformation(new string('-', waitingMessage.Length)); + logger.LogInformation(new('-', waitingMessage.Length)); Stack stack; diff --git a/tests/LocalStack.Client.Functional.Tests/Fixtures/LocalStackCollections.cs b/tests/LocalStack.Client.Functional.Tests/Fixtures/LocalStackCollections.cs index 827d3f4..94888ef 100644 --- a/tests/LocalStack.Client.Functional.Tests/Fixtures/LocalStackCollections.cs +++ b/tests/LocalStack.Client.Functional.Tests/Fixtures/LocalStackCollections.cs @@ -5,5 +5,5 @@ namespace LocalStack.Client.Functional.Tests.Fixtures; [CollectionDefinition(nameof(LocalStackCollectionV37))] public class LocalStackCollectionV37 : ICollectionFixture, ICollectionFixture; -[CollectionDefinition(nameof(LocalStackCollectionV43))] -public class LocalStackCollectionV43 : ICollectionFixture, ICollectionFixture; \ No newline at end of file +[CollectionDefinition(nameof(LocalStackCollectionV46))] +public class LocalStackCollectionV46 : ICollectionFixture, ICollectionFixture; \ No newline at end of file diff --git a/tests/LocalStack.Client.Functional.Tests/Fixtures/LocalStackFixtures.cs b/tests/LocalStack.Client.Functional.Tests/Fixtures/LocalStackFixtures.cs index 6528ff9..56959a4 100644 --- a/tests/LocalStack.Client.Functional.Tests/Fixtures/LocalStackFixtures.cs +++ b/tests/LocalStack.Client.Functional.Tests/Fixtures/LocalStackFixtures.cs @@ -31,9 +31,9 @@ public LocalStackFixtureV37() : base(TestContainers.LocalStackBuilder(TestConsta } } -public sealed class LocalStackFixtureV43 : LocalStackFixtureBase +public sealed class LocalStackFixtureV46 : LocalStackFixtureBase { - public LocalStackFixtureV43() : base(TestContainers.LocalStackBuilder(TestConstants.LocalStackV43)) + public LocalStackFixtureV46() : base(TestContainers.LocalStackBuilder(TestConstants.LocalStackV46)) { } } diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/CloudFormation/CloudFormationScenario.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/CloudFormation/CloudFormationScenario.cs index 54d49e2..72c8d63 100644 --- a/tests/LocalStack.Client.Functional.Tests/Scenarios/CloudFormation/CloudFormationScenario.cs +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/CloudFormation/CloudFormationScenario.cs @@ -10,10 +10,10 @@ public CloudFormationScenarioV37(TestFixture testFixture, LocalStackFixtureV37 l } } -[Collection(nameof(LocalStackCollectionV43))] -public sealed class CloudFormationScenarioV43 : BaseCloudFormationScenario +[Collection(nameof(LocalStackCollectionV46))] +public sealed class CloudFormationScenarioV46 : BaseCloudFormationScenario { - public CloudFormationScenarioV43(TestFixture testFixture, LocalStackFixtureV43 localStackFixtureV43) : base(testFixture, localStackFixtureV43) + public CloudFormationScenarioV46(TestFixture testFixture, LocalStackFixtureV46 localStackFixtureV46) : base(testFixture, localStackFixtureV46) { } } \ No newline at end of file diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/DynamoDbScenario.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/DynamoDbScenario.cs index d6ead44..15a3e8c 100644 --- a/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/DynamoDbScenario.cs +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/DynamoDbScenario.cs @@ -10,10 +10,10 @@ public DynamoDbScenarioV37(TestFixture testFixture, LocalStackFixtureV37 localSt } } -[Collection(nameof(LocalStackCollectionV43))] -public sealed class DynamoDbScenarioV43 : BaseDynamoDbScenario +[Collection(nameof(LocalStackCollectionV46))] +public sealed class DynamoDbScenarioV46 : BaseDynamoDbScenario { - public DynamoDbScenarioV43(TestFixture testFixture, LocalStackFixtureV43 localStackFixtureV43) : base(testFixture, localStackFixtureV43) + public DynamoDbScenarioV46(TestFixture testFixture, LocalStackFixtureV46 localStackFixtureV46) : base(testFixture, localStackFixtureV46) { } } \ No newline at end of file diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/RealLife/SnsToSqsScenarios.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/RealLife/SnsToSqsScenarios.cs index 557ac75..78ea378 100644 --- a/tests/LocalStack.Client.Functional.Tests/Scenarios/RealLife/SnsToSqsScenarios.cs +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/RealLife/SnsToSqsScenarios.cs @@ -10,10 +10,10 @@ public SnsToSqsScenarioV37(TestFixture testFixture, LocalStackFixtureV37 localSt } } -[Collection(nameof(LocalStackCollectionV43))] -public sealed class SnsToSqsScenarioV43 : BaseRealLife +[Collection(nameof(LocalStackCollectionV46))] +public sealed class SnsToSqsScenarioV46 : BaseRealLife { - public SnsToSqsScenarioV43(TestFixture testFixture, LocalStackFixtureV43 localStackFixtureV43) : base(testFixture, localStackFixtureV43) + public SnsToSqsScenarioV46(TestFixture testFixture, LocalStackFixtureV46 localStackFixtureV46) : base(testFixture, localStackFixtureV46) { } } \ No newline at end of file diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/S3/S3Scenarios.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/S3/S3Scenarios.cs index 7f34f4c..dd1ce9c 100644 --- a/tests/LocalStack.Client.Functional.Tests/Scenarios/S3/S3Scenarios.cs +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/S3/S3Scenarios.cs @@ -10,10 +10,10 @@ public S3ScenarioV37(TestFixture testFixture, LocalStackFixtureV37 localStackFix } } -[Collection(nameof(LocalStackCollectionV43))] -public sealed class S3ScenarioV43 : BaseS3Scenario +[Collection(nameof(LocalStackCollectionV46))] +public sealed class S3ScenarioV46 : BaseS3Scenario { - public S3ScenarioV43(TestFixture testFixture, LocalStackFixtureV43 localStackFixtureV43) : base(testFixture, localStackFixtureV43) + public S3ScenarioV46(TestFixture testFixture, LocalStackFixtureV46 localStackFixtureV46) : base(testFixture, localStackFixtureV46) { } } \ No newline at end of file diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/SnsScenarios.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/SnsScenarios.cs index 45a3fdc..8576088 100644 --- a/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/SnsScenarios.cs +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/SnsScenarios.cs @@ -10,10 +10,10 @@ public SnsScenarioV37(TestFixture testFixture, LocalStackFixtureV37 localStackFi } } -[Collection(nameof(LocalStackCollectionV43))] -public sealed class SnsScenarioV43 : BaseSnsScenario +[Collection(nameof(LocalStackCollectionV46))] +public sealed class SnsScenarioV46 : BaseSnsScenario { - public SnsScenarioV43(TestFixture testFixture, LocalStackFixtureV43 localStackFixtureV43) : base(testFixture, localStackFixtureV43) + public SnsScenarioV46(TestFixture testFixture, LocalStackFixtureV46 localStackFixtureV46) : base(testFixture, localStackFixtureV46) { } } \ No newline at end of file diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/SQS/SqsScenarios.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/SQS/SqsScenarios.cs index 717b644..ecdb77e 100644 --- a/tests/LocalStack.Client.Functional.Tests/Scenarios/SQS/SqsScenarios.cs +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/SQS/SqsScenarios.cs @@ -10,10 +10,10 @@ public SqsScenarioV37(TestFixture testFixture, LocalStackFixtureV37 localStackFi } } -[Collection(nameof(LocalStackCollectionV43))] -public sealed class SqsScenarioV43 : BaseSqsScenario +[Collection(nameof(LocalStackCollectionV46))] +public sealed class SqsScenarioV46 : BaseSqsScenario { - public SqsScenarioV43(TestFixture testFixture, LocalStackFixtureV43 localStackFixtureV43) : base(testFixture, localStackFixtureV43) + public SqsScenarioV46(TestFixture testFixture, LocalStackFixtureV46 localStackFixtureV46) : base(testFixture, localStackFixtureV46) { } } \ No newline at end of file diff --git a/tests/LocalStack.Client.Functional.Tests/TestConstants.cs b/tests/LocalStack.Client.Functional.Tests/TestConstants.cs index 3fa982e..2acdeb8 100644 --- a/tests/LocalStack.Client.Functional.Tests/TestConstants.cs +++ b/tests/LocalStack.Client.Functional.Tests/TestConstants.cs @@ -5,7 +5,7 @@ public static class TestConstants public const string LocalStackConfig = "appsettings.LocalStack.json"; public const string LocalStackV37 = "3.7.1"; - public const string LocalStackV43 = "4.3.0"; + public const string LocalStackV46 = "4.6.0"; public const string MovieTableMovieIdGsi = "MoiveTableMovie-Index"; } \ No newline at end of file diff --git a/tests/LocalStack.Client.Integration.Tests/CreateClientByImplementationTests.cs b/tests/LocalStack.Client.Integration.Tests/CreateClientByImplementationTests.cs index f9478e7..bbba6ec 100644 --- a/tests/LocalStack.Client.Integration.Tests/CreateClientByImplementationTests.cs +++ b/tests/LocalStack.Client.Integration.Tests/CreateClientByImplementationTests.cs @@ -1,9 +1,4 @@ -using Amazon.AppConfigData; -using Amazon.Pinpoint; -using Amazon.Pipes; -using Amazon.RAM; - -namespace LocalStack.Client.Integration.Tests; +namespace LocalStack.Client.Integration.Tests; public class CreateClientByImplementationTests { @@ -1041,4 +1036,112 @@ public void Should_Able_To_Create_AmazonPipes() Assert.NotNull(amazonPipesClient); AssertAmazonClient.AssertClientConfiguration(amazonPipesClient); } + + [Fact] + public void Should_Able_To_Create_AmazonAccount() + { + var amazonAccountClient = Session.CreateClientByImplementation(); + + Assert.NotNull(amazonAccountClient); + AssertAmazonClient.AssertClientConfiguration(amazonAccountClient); + } + + [Fact] + public void Should_Able_To_Create_AmazonACMPCAClient() + { + var amazonAcmpcaClient = Session.CreateClientByImplementation(); + + Assert.NotNull(amazonAcmpcaClient); + AssertAmazonClient.AssertClientConfiguration(amazonAcmpcaClient); + } + + [Fact] + public void Should_Able_To_Create_AmazonBedrockClient() + { + var amazonBedrockClient = Session.CreateClientByImplementation(); + + Assert.NotNull(amazonBedrockClient); + AssertAmazonClient.AssertClientConfiguration(amazonBedrockClient); + } + + [Fact] + public void Should_Able_To_Create_AmazonCloudControlApiClient() + { + var amazonCloudControlApiClient = Session.CreateClientByImplementation(); + + Assert.NotNull(amazonCloudControlApiClient); + AssertAmazonClient.AssertClientConfiguration(amazonCloudControlApiClient); + } + + [Fact] + public void Should_Able_To_Create_AmazonCodeBuildClient() + { + var amazonCodeBuildClient = Session.CreateClientByImplementation(); + + Assert.NotNull(amazonCodeBuildClient); + AssertAmazonClient.AssertClientConfiguration(amazonCodeBuildClient); + } + + [Fact] + public void Should_Able_To_Create_AmazonCodeConnectionsClient() + { + var amazonCodeConnectionsClient = Session.CreateClientByImplementation(); + + Assert.NotNull(amazonCodeConnectionsClient); + AssertAmazonClient.AssertClientConfiguration(amazonCodeConnectionsClient); + } + + [Fact] + public void Should_Able_To_Create_AmazonCodeDeployClient() + { + var amazonCodeDeployClient = Session.CreateClientByImplementation(); + + Assert.NotNull(amazonCodeDeployClient); + AssertAmazonClient.AssertClientConfiguration(amazonCodeDeployClient); + } + + [Fact] + public void Should_Able_To_Create_AmazonCodePipelineClient() + { + var amazonCodePipelineClient = Session.CreateClientByImplementation(); + + Assert.NotNull(amazonCodePipelineClient); + AssertAmazonClient.AssertClientConfiguration(amazonCodePipelineClient); + } + + [Fact] + public void Should_Able_To_Create_AmazonElasticTranscoderClient() + { + var amazonElasticTranscoderClient = Session.CreateClientByImplementation(); + + Assert.NotNull(amazonElasticTranscoderClient); + AssertAmazonClient.AssertClientConfiguration(amazonElasticTranscoderClient); + } + + [Fact] + public void Should_Able_To_Create_AmazonMemoryDBClient() + { + var amazonMemoryDbClient = Session.CreateClientByImplementation(); + + Assert.NotNull(amazonMemoryDbClient); + AssertAmazonClient.AssertClientConfiguration(amazonMemoryDbClient); + } + + [Fact] + public void Should_Able_To_Create_AmazonShieldClient() + { + var amazonShieldClient = Session.CreateClientByImplementation(); + + Assert.NotNull(amazonShieldClient); + AssertAmazonClient.AssertClientConfiguration(amazonShieldClient); + } + + [Fact] + public void Should_Able_To_Create_AmazonVerifiedPermissionsClient() + { + var amazonVerifiedPermissionsClient = Session.CreateClientByImplementation(); + + Assert.NotNull(amazonVerifiedPermissionsClient); + AssertAmazonClient.AssertClientConfiguration(amazonVerifiedPermissionsClient); + } } \ No newline at end of file diff --git a/tests/LocalStack.Client.Integration.Tests/CreateClientByInterfaceTests.cs b/tests/LocalStack.Client.Integration.Tests/CreateClientByInterfaceTests.cs index 5025960..71b6cdc 100644 --- a/tests/LocalStack.Client.Integration.Tests/CreateClientByInterfaceTests.cs +++ b/tests/LocalStack.Client.Integration.Tests/CreateClientByInterfaceTests.cs @@ -1,9 +1,4 @@ -using Amazon.AppConfigData; -using Amazon.Pinpoint; -using Amazon.Pipes; -using Amazon.RAM; - -namespace LocalStack.Client.Integration.Tests; +namespace LocalStack.Client.Integration.Tests; public class CreateClientByInterfaceTests { @@ -1041,4 +1036,103 @@ public void Should_Able_To_Create_AmazonPipes() Assert.NotNull(amazonPipes); AssertAmazonClient.AssertClientConfiguration(amazonPipes); } + + [Fact] + public void Should_Able_To_Create_AmazonAccount() + { + AmazonServiceClient amazonAccount = Session.CreateClientByInterface(); + + Assert.NotNull(amazonAccount); + AssertAmazonClient.AssertClientConfiguration(amazonAccount); + } + + [Fact] + public void Should_Able_To_Create_AmazonACMPCA() + { + AmazonServiceClient amazonAcmpca = Session.CreateClientByInterface(); + + Assert.NotNull(amazonAcmpca); + AssertAmazonClient.AssertClientConfiguration(amazonAcmpca); + } + + [Fact] + public void Should_Able_To_Create_AmazonBedrock() + { + AmazonServiceClient amazonBedrock = Session.CreateClientByInterface(); + + Assert.NotNull(amazonBedrock); + AssertAmazonClient.AssertClientConfiguration(amazonBedrock); + } + + [Fact] + public void Should_Able_To_Create_AmazonCloudControlApi() + { + AmazonServiceClient amazonCloudControlApi = Session.CreateClientByInterface(); + + Assert.NotNull(amazonCloudControlApi); + AssertAmazonClient.AssertClientConfiguration(amazonCloudControlApi); + } + + [Fact] + public void Should_Able_To_Create_AmazonCodeConnections() + { + AmazonServiceClient amazonCodeConnections = Session.CreateClientByInterface(); + + Assert.NotNull(amazonCodeConnections); + AssertAmazonClient.AssertClientConfiguration(amazonCodeConnections); + } + + [Fact] + public void Should_Able_To_Create_AmazonCodeDeploy() + { + AmazonServiceClient amazonCodeDeploy = Session.CreateClientByInterface(); + + Assert.NotNull(amazonCodeDeploy); + AssertAmazonClient.AssertClientConfiguration(amazonCodeDeploy); + } + + [Fact] + public void Should_Able_To_Create_AmazonCodePipeline() + { + AmazonServiceClient amazonCodePipeline = Session.CreateClientByInterface(); + + Assert.NotNull(amazonCodePipeline); + AssertAmazonClient.AssertClientConfiguration(amazonCodePipeline); + } + + [Fact] + public void Should_Able_To_Create_AmazonElasticTranscoder() + { + AmazonServiceClient amazonElasticTranscoder = Session.CreateClientByInterface(); + + Assert.NotNull(amazonElasticTranscoder); + AssertAmazonClient.AssertClientConfiguration(amazonElasticTranscoder); + } + + [Fact] + public void Should_Able_To_Create_AmazonMemoryDB() + { + AmazonServiceClient amazonMemoryDb = Session.CreateClientByInterface(); + + Assert.NotNull(amazonMemoryDb); + AssertAmazonClient.AssertClientConfiguration(amazonMemoryDb); + } + + [Fact] + public void Should_Able_To_Create_AmazonShield() + { + AmazonServiceClient amazonShield = Session.CreateClientByInterface(); + + Assert.NotNull(amazonShield); + AssertAmazonClient.AssertClientConfiguration(amazonShield); + } + + [Fact] + public void Should_Able_To_Create_AmazonVerifiedPermissions() + { + AmazonServiceClient amazonVerifiedPermissions = Session.CreateClientByInterface(); + + Assert.NotNull(amazonVerifiedPermissions); + AssertAmazonClient.AssertClientConfiguration(amazonVerifiedPermissions); + } } \ No newline at end of file diff --git a/tests/LocalStack.Client.Integration.Tests/GlobalUsings.cs b/tests/LocalStack.Client.Integration.Tests/GlobalUsings.cs index 1e93c58..a23d2cc 100644 --- a/tests/LocalStack.Client.Integration.Tests/GlobalUsings.cs +++ b/tests/LocalStack.Client.Integration.Tests/GlobalUsings.cs @@ -1,17 +1,27 @@ -global using Amazon; +global using System; +global using System.Diagnostics.CodeAnalysis; +global using System.Reflection; + +global using Amazon; +global using Amazon.Account; +global using Amazon.ACMPCA; global using Amazon.Amplify; global using Amazon.APIGateway; global using Amazon.ApiGatewayManagementApi; global using Amazon.ApiGatewayV2; global using Amazon.AppConfig; +global using Amazon.AppConfigData; +global using Amazon.Appflow; global using Amazon.AppSync; global using Amazon.Athena; global using Amazon.AutoScaling; -global using Amazon.AWSSupport; global using Amazon.AWSMarketplaceMetering; +global using Amazon.AWSSupport; global using Amazon.Backup; global using Amazon.Batch; +global using Amazon.Bedrock; global using Amazon.CertificateManager; +global using Amazon.CloudControlApi; global using Amazon.CloudFormation; global using Amazon.CloudFront; global using Amazon.CloudSearch; @@ -19,7 +29,11 @@ global using Amazon.CloudWatch; global using Amazon.CloudWatchEvents; global using Amazon.CloudWatchLogs; +global using Amazon.CodeBuild; global using Amazon.CodeCommit; +global using Amazon.CodeConnections; +global using Amazon.CodeDeploy; +global using Amazon.CodePipeline; global using Amazon.CognitoIdentity; global using Amazon.CognitoIdentityProvider; global using Amazon.ConfigService; @@ -37,6 +51,8 @@ global using Amazon.ElasticLoadBalancingV2; global using Amazon.ElasticMapReduce; global using Amazon.Elasticsearch; +global using Amazon.ElasticTranscoder; +global using Amazon.EMRServerless; global using Amazon.EventBridge; global using Amazon.FIS; global using Amazon.Glue; @@ -50,6 +66,7 @@ global using Amazon.IoTWireless; global using Amazon.Kafka; global using Amazon.KeyManagementService; +global using Amazon.Keyspaces; global using Amazon.KinesisAnalytics; global using Amazon.KinesisAnalyticsV2; global using Amazon.KinesisFirehose; @@ -58,11 +75,17 @@ global using Amazon.MediaConvert; global using Amazon.MediaStore; global using Amazon.MediaStoreData; +global using Amazon.MemoryDB; +global using Amazon.MQ; global using Amazon.MWAA; global using Amazon.Neptune; global using Amazon.OpenSearchService; global using Amazon.Organizations; +global using Amazon.Pinpoint; +global using Amazon.Pipes; global using Amazon.QLDB; +global using Amazon.QLDBSession; +global using Amazon.RAM; global using Amazon.RDS; global using Amazon.RDSDataService; global using Amazon.Redshift; @@ -70,16 +93,19 @@ global using Amazon.ResourceGroups; global using Amazon.ResourceGroupsTaggingAPI; global using Amazon.Route53; +global using Amazon.Route53Domains; global using Amazon.Route53Resolver; global using Amazon.Runtime; global using Amazon.S3; global using Amazon.S3Control; global using Amazon.SageMaker; global using Amazon.SageMakerRuntime; +global using Amazon.Scheduler; global using Amazon.SecretsManager; global using Amazon.SecurityToken; global using Amazon.ServerlessApplicationRepository; global using Amazon.ServiceDiscovery; +global using Amazon.Shield; global using Amazon.SimpleEmail; global using Amazon.SimpleEmailV2; global using Amazon.SimpleNotificationService; @@ -89,27 +115,16 @@ global using Amazon.StepFunctions; global using Amazon.TimestreamQuery; global using Amazon.TimestreamWrite; +global using Amazon.TranscribeService; global using Amazon.Transfer; +global using Amazon.VerifiedPermissions; global using Amazon.WAF; global using Amazon.WAFV2; global using Amazon.XRay; -global using Amazon.MQ; -global using Amazon.TranscribeService; global using LocalStack.Client.Contracts; global using LocalStack.Client.Exceptions; global using LocalStack.Client.Models; global using LocalStack.Client.Options; -global using System; -global using System.Diagnostics.CodeAnalysis; -global using System.Reflection; - -global using Amazon.Appflow; -global using Amazon.EMRServerless; -global using Amazon.Keyspaces; -global using Amazon.QLDBSession; -global using Amazon.Route53Domains; -global using Amazon.Scheduler; - global using Xunit; \ 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..1a13b04 100644 --- a/tests/LocalStack.Client.Integration.Tests/LocalStack.Client.Integration.Tests.csproj +++ b/tests/LocalStack.Client.Integration.Tests/LocalStack.Client.Integration.Tests.csproj @@ -7,6 +7,8 @@ + + @@ -21,7 +23,9 @@ + + @@ -29,7 +33,11 @@ + + + + @@ -50,6 +58,7 @@ + @@ -73,6 +82,7 @@ + @@ -104,12 +114,14 @@ + +