From 01b7d67a53701a9b8b641846e23affaa6b9771e3 Mon Sep 17 00:00:00 2001 From: "deniz_irgin@yahoo.com" Date: Thu, 18 Nov 2021 12:12:56 +0300 Subject: [PATCH 01/24] fix test reports path problem --- .github/workflows/build-ubuntu.yml | 3 +-- .github/workflows/test-report.yml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 6438b6b..5b2f06b 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -47,8 +47,7 @@ jobs: run: ./build.sh --target build - name: Run Tests - # run: ./build.sh --target tests --skipFunctionalTest false --exclusive - run: ./build.sh --target tests --exclusive + run: ./build.sh --target tests --skipFunctionalTest false --exclusive - name: Upload Test Results uses: actions/upload-artifact@v2 diff --git a/.github/workflows/test-report.yml b/.github/workflows/test-report.yml index 3bf8a47..550cfb9 100644 --- a/.github/workflows/test-report.yml +++ b/.github/workflows/test-report.yml @@ -12,5 +12,5 @@ jobs: with: artifact: test-results-ubuntu name: .NET Tests - path: "*.trx" + path: "**/*.trx" reporter: dotnet-trx From 8ea0bd46b2d0a8a1787d79fc9cf54c1ffcf535a4 Mon Sep 17 00:00:00 2001 From: "deniz_irgin@yahoo.com" Date: Thu, 18 Nov 2021 13:10:37 +0300 Subject: [PATCH 02/24] test publish nuget [skip ci] --- .github/workflows/publish-extension-nuget.yml | 13 +++++++++++++ .github/workflows/publish-nuget.yml | 13 +++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 .github/workflows/publish-extension-nuget.yml create mode 100644 .github/workflows/publish-nuget.yml diff --git a/.github/workflows/publish-extension-nuget.yml b/.github/workflows/publish-extension-nuget.yml new file mode 100644 index 0000000..fa6bf47 --- /dev/null +++ b/.github/workflows/publish-extension-nuget.yml @@ -0,0 +1,13 @@ +name: "publish-extension-nuget" +on: + workflow_dispatch: + inputs: + package-version: + description: "Package Version" + required: true +jobs: + printInputs: + runs-on: ubuntu-20.04 + steps: + - run: | + echo "Package Version: ${{ github.event.inputs.package-version }}" diff --git a/.github/workflows/publish-nuget.yml b/.github/workflows/publish-nuget.yml new file mode 100644 index 0000000..c430233 --- /dev/null +++ b/.github/workflows/publish-nuget.yml @@ -0,0 +1,13 @@ +name: "publish-nuget" +on: + workflow_dispatch: + inputs: + package-version: + description: "Package Version" + required: true +jobs: + printInputs: + runs-on: ubuntu-20.04 + steps: + - run: | + echo "Package Version: ${{ github.event.inputs.package-version }}" From 84bb02a66afc48be82dea0a966222e4ead95e8f9 Mon Sep 17 00:00:00 2001 From: "deniz_irgin@yahoo.com" Date: Thu, 18 Nov 2021 17:49:11 +0300 Subject: [PATCH 03/24] add nuget push pipeline --- .github/workflows/publish-extension-nuget.yml | 13 --- .github/workflows/publish-nuget.yml | 62 +++++++++++++- build/LocalStack.Build/BuildContext.cs | 82 +++++++++++-------- build/LocalStack.Build/GlobalUsings.cs | 6 +- build/LocalStack.Build/Program.cs | 75 ++++++++++++----- 5 files changed, 167 insertions(+), 71 deletions(-) delete mode 100644 .github/workflows/publish-extension-nuget.yml diff --git a/.github/workflows/publish-extension-nuget.yml b/.github/workflows/publish-extension-nuget.yml deleted file mode 100644 index fa6bf47..0000000 --- a/.github/workflows/publish-extension-nuget.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: "publish-extension-nuget" -on: - workflow_dispatch: - inputs: - package-version: - description: "Package Version" - required: true -jobs: - printInputs: - runs-on: ubuntu-20.04 - steps: - - run: | - echo "Package Version: ${{ github.event.inputs.package-version }}" diff --git a/.github/workflows/publish-nuget.yml b/.github/workflows/publish-nuget.yml index c430233..6d7f52f 100644 --- a/.github/workflows/publish-nuget.yml +++ b/.github/workflows/publish-nuget.yml @@ -1,13 +1,71 @@ name: "publish-nuget" + on: workflow_dispatch: inputs: package-version: description: "Package Version" required: true + package-source: + type: choice + description: Package Source + required: true + default: "myget" + options: + - myget + - nuget + package-id: + type: choice + description: Package Id + required: true + default: "LocalStack.Client" + options: + - LocalStack.Client + - LocalStack.Client.Extensions + jobs: - printInputs: + build-and-test: runs-on: ubuntu-20.04 + steps: - - run: | + - name: Checkout + uses: actions/checkout@v2 + + - name: Init + run: chmod +x ./build.sh + + - name: Install NuGet + uses: NuGet/setup-nuget@v1.0.5 + + - name: Install .NET Core 3.1 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: "3.1.x" + + - name: Install .NET 5 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: "5.0.x" + + - name: Install .NET 6 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: "6.0.x" + + - name: Build & Test + run: ./build.sh + + - name: "Print Version" + run: | echo "Package Version: ${{ github.event.inputs.package-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: 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: NuGet Push + 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}} diff --git a/build/LocalStack.Build/BuildContext.cs b/build/LocalStack.Build/BuildContext.cs index beaaf5c..1239d00 100644 --- a/build/LocalStack.Build/BuildContext.cs +++ b/build/LocalStack.Build/BuildContext.cs @@ -1,44 +1,44 @@ -using Cake.Common.IO; - -namespace LocalStack.Build; +namespace LocalStack.Build; public sealed class BuildContext : FrostingContext { public BuildContext(ICakeContext context) : base(context) { -//#if DEBUG -// FilePath buildFile = context.Environment.WorkingDirectory.GetFilePath("build.sh"); -// if (!context.FileExists(buildFile)) -// { -// var dir = new DirectoryPath("."); -// while (!context.FileExists(buildFile)) -// { -// dir = new DirectoryPath(Directory.GetParent(dir.FullPath)?.FullName); -// buildFile = dir.GetFilePath(buildFile); -// } - -// context.Environment.WorkingDirectory = dir; -// } -//#endif - BuildConfiguration = context.Argument("config", "Release"); ForceBuild = context.Argument("force-build", false); ForceRestore = context.Argument("force-restore", false); - BuildNumber = context.Argument("build-number", 1); + PackageVersion = context.Argument("package-version", "x.x.x"); + PackageId = context.Argument("package-id", default(string)); + PackageSecret = context.Argument("package-secret", default(string)); + PackageSource = context.Argument("package-source", default(string)); SkipFunctionalTest = context.Argument("skipFunctionalTest", true); + 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") + }); + PackageSourceMap = sourceBuilder.ToImmutable(); + SolutionRoot = context.Directory("../../"); SrcPath = SolutionRoot + context.Directory("src"); TestsPath = SolutionRoot + context.Directory("tests"); BuildPath = SolutionRoot + context.Directory("build"); ArtifactOutput = SolutionRoot + context.Directory("artifacts"); - ArtifactExtensionsOutput = SolutionRoot + context.Directory("artifacts-extensions"); LocalStackClientFolder = SrcPath + context.Directory("LocalStack.Client"); LocalStackClientExtFolder = SrcPath + context.Directory("LocalStack.Client.Extensions"); - SlnFilePath = SolutionRoot + context.File("LocalStack.sln"); LocalStackClientProjFile = LocalStackClientFolder + context.File("LocalStack.Client.csproj"); LocalStackClientExtProjFile = LocalStackClientExtFolder + context.File("LocalStack.Client.Extensions.csproj"); + + var packIdBuilder = ImmutableDictionary.CreateBuilder(); + packIdBuilder.AddRange(new[] + { + new KeyValuePair("LocalStack.Client", LocalStackClientProjFile), + new KeyValuePair("LocalStack.Client.Extensions", LocalStackClientExtProjFile) + }); + PackageIdProjMap = packIdBuilder.ToImmutable(); } public string BuildConfiguration { get; } @@ -47,13 +47,21 @@ public BuildContext(ICakeContext context) : base(context) public bool ForceRestore { get; } - public string TestingMode { get; } + public bool SkipFunctionalTest { get; } - public bool SkipFunctionalTest { get; set; } + public string PackageVersion { get; } - public int BuildNumber { get; set; } + public string PackageId { get; } - public ConvertableFilePath SlnFilePath { get; set; } + public string PackageSecret { get; } + + public string PackageSource { get; } + + public ImmutableDictionary PackageSourceMap { get; } + + public ImmutableDictionary PackageIdProjMap { get; } + + public ConvertableFilePath SlnFilePath { get; } public ConvertableDirectoryPath SolutionRoot { get; } @@ -63,17 +71,25 @@ public BuildContext(ICakeContext context) : base(context) public ConvertableDirectoryPath BuildPath { get; } - public ConvertableDirectoryPath ArtifactOutput { get; set; } + public ConvertableDirectoryPath ArtifactOutput { get; } + + public ConvertableDirectoryPath ArtifactExtensionsOutput { get; } - public ConvertableDirectoryPath ArtifactExtensionsOutput { get; set; } + public ConvertableDirectoryPath LocalStackClientFolder { get; } - public ConvertableDirectoryPath LocalStackClientFolder { get; set; } + public ConvertableDirectoryPath LocalStackClientExtFolder { get; } - public ConvertableDirectoryPath LocalStackClientExtFolder { get; set; } + public ConvertableFilePath LocalStackClientProjFile { get; } - public ConvertableFilePath LocalStackClientProjFile { get; set; } + public ConvertableFilePath LocalStackClientExtProjFile { get; } - public ConvertableFilePath LocalStackClientExtProjFile { get; set; } + public static void ValidateArgument(string argumentName, string argument) + { + if (string.IsNullOrWhiteSpace(argument)) + { + throw new Exception($"{argumentName} can not be null or empty"); + } + } public void InstallXUnitNugetPackage() { @@ -139,7 +155,7 @@ public string GetProjectVersion() int endIndex = project.IndexOf("", startIndex, StringComparison.Ordinal); string version = project.Substring(startIndex, endIndex - startIndex); - version = $"{version}.{BuildNumber}"; + version = $"{version}.{PackageVersion}"; return version; } @@ -155,7 +171,7 @@ public string GetExtensionProjectVersion() int endIndex = project.IndexOf("", startIndex, StringComparison.Ordinal); string version = project.Substring(startIndex, endIndex - startIndex); - version = $"{version}.{BuildNumber}"; + version = $"{version}.{PackageVersion}"; return version; } diff --git a/build/LocalStack.Build/GlobalUsings.cs b/build/LocalStack.Build/GlobalUsings.cs index b7520e9..dda252f 100644 --- a/build/LocalStack.Build/GlobalUsings.cs +++ b/build/LocalStack.Build/GlobalUsings.cs @@ -1,10 +1,11 @@ global using System; global using System.IO; -global using System.Collections; global using System.Collections.Generic; +global using System.Collections.Immutable; global using System.Linq; global using System.Text; - +global using System.Text.RegularExpressions; + global using Cake.Common; global using Cake.Common.IO; global using Cake.Common.IO.Paths; @@ -16,6 +17,7 @@ global using Cake.Common.Tools.DotNetCore.MSBuild; global using Cake.Common.Tools.DotNetCore.Pack; global using Cake.Common.Tools.DotNetCore.Test; +global using Cake.Common.Tools.NuGet.List; global using Cake.Core; global using Cake.Core.IO; global using Cake.Frosting; diff --git a/build/LocalStack.Build/Program.cs b/build/LocalStack.Build/Program.cs index 0d40b56..2ebf0ad 100644 --- a/build/LocalStack.Build/Program.cs +++ b/build/LocalStack.Build/Program.cs @@ -1,4 +1,6 @@ -return new CakeHost() +using Cake.Common.Tools.DotNetCore.NuGet.Push; + +return new CakeHost() .UseContext() .Run(args); @@ -103,11 +105,15 @@ 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 DotNetCorePackSettings { Configuration = context.BuildConfiguration, @@ -115,40 +121,67 @@ public override void Run(BuildContext context) MSBuildSettings = new DotNetCoreMSBuildSettings() }; - settings.MSBuildSettings.SetVersion(context.GetProjectVersion()); + settings.MSBuildSettings.SetVersion(context.PackageVersion); - context.DotNetCorePack(context.LocalStackClientProjFile, settings); + context.DotNetCorePack(packageCsProj.FullPath, settings); } -} -[TaskName("nuget-pack-extensions")] -public sealed class NugetPackExtensionTask : FrostingTask -{ - public override void Run(BuildContext context) + private static void ValidatePackageVersion(BuildContext context) { - if (!Directory.Exists(context.ArtifactExtensionsOutput)) + 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) { - Directory.CreateDirectory(context.ArtifactExtensionsOutput); + throw new Exception($"Invalid version: {context.PackageVersion}"); } - var settings = new DotNetCorePackSettings - { - Configuration = context.BuildConfiguration, - OutputDirectory = context.ArtifactOutput, - MSBuildSettings = new DotNetCoreMSBuildSettings() - }; + string packageSource = context.PackageSourceMap[context.PackageSource]; - settings.MSBuildSettings.SetVersion(context.GetExtensionProjectVersion()); + 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; - context.DotNetCorePack(context.LocalStackClientExtProjFile, settings); + 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("get-version")] -public sealed class GetVersionTask : FrostingTask +[TaskName("nuget-push")] +public sealed class NugetPushTask : FrostingTask { public override void Run(BuildContext context) { - context.Warning(context.GetProjectVersion()); + 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.DotNetCoreNuGetPush(packageFile.Path.FullPath, new DotNetCoreNuGetPushSettings() + { + ApiKey = packageSecret, + Source = packageSource, + }); } } \ No newline at end of file From c5e4d3c5ba41e74347dee96aa4dcfaf7659b9375 Mon Sep 17 00:00:00 2001 From: "deniz_irgin@yahoo.com" Date: Thu, 18 Nov 2021 18:09:56 +0300 Subject: [PATCH 04/24] remove azure pipeline yamls --- build/azure-pipelines.artifact.yml | 60 ------------------ build/azure-pipelines.extensions.artifact.yml | 63 ------------------- build/azure-pipelines.macos.yml | 60 ------------------ build/azure-pipelines.ubuntu.yml | 51 --------------- build/azure-pipelines.windows.yml | 33 ---------- 5 files changed, 267 deletions(-) delete mode 100644 build/azure-pipelines.artifact.yml delete mode 100644 build/azure-pipelines.extensions.artifact.yml delete mode 100644 build/azure-pipelines.macos.yml delete mode 100644 build/azure-pipelines.ubuntu.yml delete mode 100644 build/azure-pipelines.windows.yml diff --git a/build/azure-pipelines.artifact.yml b/build/azure-pipelines.artifact.yml deleted file mode 100644 index 5047f3c..0000000 --- a/build/azure-pipelines.artifact.yml +++ /dev/null @@ -1,60 +0,0 @@ -trigger: - branches: - include: - - master - - releases/* - - feature/* - paths: - exclude: - - README.md - - CONTRIBUTING.md - - LICENSE -pr: none -pool: - vmImage: ubuntu-latest -variables: - Version.MajorMinor: 1.2 - Version.Revision: $[counter(variables['Version.MajorMinor'], 0)] -steps: - - bash: "sudo apt install nuget && mkdir ./testrunner && sudo chmod -R 777 ./testrunner && ls" - displayName: "Init Task" - - - task: UseDotNet@2 - displayName: ".NET Core 3.1.x" - inputs: - version: "3.1.x" - packageType: sdk - - - task: UseDotNet@2 - displayName: ".NET 5.0.x" - inputs: - version: "5.0.x" - packageType: sdk - - - task: UseDotNet@2 - displayName: ".NET 6.x" - inputs: - version: "6.x" - packageType: sdk - - - task: Bash@3 - displayName: "Compile & Tests" - inputs: - targetType: filePath - filePath: ./build.sh - - - bash: echo $(Version.Revision) && chmod +x ./build.sh && sudo ./build.sh --target=get-version --buildnumber=$BUILD_ID - displayName: "Package Version" - env: - BUILD_ID: $(Version.Revision) - - - bash: chmod +x ./build.sh && sudo ./build.sh --target=nuget-pack --buildnumber=$BUILD_ID - displayName: "Nuget Pack" - env: - BUILD_ID: $(Version.Revision) - - - task: PublishBuildArtifacts@1 - displayName: "Publish Artifact: LocalStack.Client" - inputs: - PathtoPublish: artifacts/ - ArtifactName: LocalStack.Client diff --git a/build/azure-pipelines.extensions.artifact.yml b/build/azure-pipelines.extensions.artifact.yml deleted file mode 100644 index f9b2813..0000000 --- a/build/azure-pipelines.extensions.artifact.yml +++ /dev/null @@ -1,63 +0,0 @@ -trigger: - branches: - include: - - master - - releases/* - - feature/* - paths: - exclude: - - README.md - - CONTRIBUTING.md - - LICENSE -pr: none -pool: - vmImage: ubuntu-latest -variables: - Version.MajorMinor: 1.1 - Version.Revision: $[counter(variables['Version.MajorMinor'], 0)] -steps: - - bash: "sudo apt install nuget && mkdir ./testrunner && sudo chmod -R 777 ./testrunner && ls" - displayName: "Init Task" - - - task: UseDotNet@2 - displayName: ".NET Core 3.1.x" - inputs: - version: "3.1.x" - packageType: sdk - - - task: UseDotNet@2 - displayName: ".NET 5.0.x" - inputs: - version: "5.0.x" - packageType: sdk - - - task: UseDotNet@2 - displayName: ".NET 6.x" - inputs: - version: "6.x" - packageType: sdk - - - task: Bash@3 - displayName: "Compile & Tests" - inputs: - targetType: filePath - filePath: ./build.sh - - - bash: echo $(Version.Revision) && chmod +x ./build.sh && sudo ./build.sh --target=get-version --buildnumber=$BUILD_ID - displayName: "Package Version" - env: - BUILD_ID: $(Version.Revision) - - - bash: cd src/LocalStack.Client.Extensions/ && dotnet remove reference ../LocalStack.Client/LocalStack.Client.csproj && dotnet add package LocalStack.Client - displayName: "Remove Project Ref & Add latest pack" - - - bash: chmod +x ./build.sh && sudo ./build.sh --target=nuget-pack-extensions --buildnumber=$BUILD_ID - displayName: "Nuget Pack" - env: - BUILD_ID: $(Version.Revision) - - - task: PublishBuildArtifacts@1 - displayName: "Publish Artifact: LocalStack.Client.Extensions" - inputs: - PathtoPublish: artifacts-extensions/ - ArtifactName: LocalStack.Client.Extensions diff --git a/build/azure-pipelines.macos.yml b/build/azure-pipelines.macos.yml deleted file mode 100644 index 1357d0d..0000000 --- a/build/azure-pipelines.macos.yml +++ /dev/null @@ -1,60 +0,0 @@ -trigger: - branches: - include: - - "*" - paths: - exclude: - - README.md - - CONTRIBUTING.md - - LICENSE -pr: - branches: - include: - - "*" -schedules: - - cron: "0 12 * * 0" - displayName: Weekly Sunday build - branches: - include: - - master - always: true -pool: - vmImage: macOS-10.14 -steps: - - bash: "sudo curl -o /usr/local/bin/nuget.exe https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" - displayName: "Install Nuget" - - - bash: "alias nuget='mono /usr/local/bin/nuget.exe'" - displayName: "Set nuget alias" - - - bash: "mkdir ./testrunner && sudo chmod -R 777 ./testrunner && ls" - displayName: "Create test runner folder" - - - task: UseDotNet@2 - displayName: ".NET 6.x" - inputs: - version: "6.x" - packageType: sdk - - - task: UseDotNet@2 - displayName: ".NET 5.0.x" - inputs: - version: "5.0.x" - packageType: sdk - - - task: UseDotNet@2 - displayName: ".NET Core 3.1.x" - inputs: - version: "3.1.x" - packageType: sdk - - - task: Bash@3 - displayName: "Compile & Tests" - inputs: - targetType: filePath - filePath: ./build.sh - - - task: PublishTestResults@2 - inputs: - testResultsFormat: "VSTest" - testResultsFiles: "**/*.trx" diff --git a/build/azure-pipelines.ubuntu.yml b/build/azure-pipelines.ubuntu.yml deleted file mode 100644 index 03a1499..0000000 --- a/build/azure-pipelines.ubuntu.yml +++ /dev/null @@ -1,51 +0,0 @@ -trigger: - branches: - include: - - "*" - paths: - exclude: - - README.md - - CONTRIBUTING.md - - LICENSE -pr: - branches: - include: - - "*" -schedules: - - cron: "0 12 * * 0" - displayName: Weekly Sunday build - branches: - include: - - master - always: true -pool: - vmImage: ubuntu-latest -steps: - - bash: "sudo apt install nuget && mkdir ./testrunner && sudo chmod -R 777 ./testrunner && ls" - displayName: "Init Task" - - - task: UseDotNet@2 - displayName: ".NET Core 3.1.x" - inputs: - version: "3.1.x" - packageType: sdk - - - task: UseDotNet@2 - displayName: ".NET 5.0.x" - inputs: - version: "5.0.x" - packageType: sdk - - - task: UseDotNet@2 - displayName: ".NET 6.x" - inputs: - version: "6.x" - packageType: sdk - - - script: dotnet --info && chmod +x ./build.sh && sudo ./build.sh --skipFunctionalTest="0" - displayName: "Compile & Tests" - - - task: PublishTestResults@2 - inputs: - testResultsFormat: "VSTest" - testResultsFiles: "**/*.trx" \ No newline at end of file diff --git a/build/azure-pipelines.windows.yml b/build/azure-pipelines.windows.yml deleted file mode 100644 index 84b685b..0000000 --- a/build/azure-pipelines.windows.yml +++ /dev/null @@ -1,33 +0,0 @@ -trigger: - branches: - include: - - "*" - paths: - exclude: - - README.md - - CONTRIBUTING.md - - LICENSE -pr: - branches: - include: - - "*" -schedules: - - cron: "0 12 * * 0" - displayName: Weekly Sunday build - branches: - include: - - master - always: true -pool: - vmImage: windows-2019 -steps: - - task: PowerShell@2 - displayName: "Compile & Tests" - inputs: - targetType: filePath - filePath: ./build.ps1 - - - task: PublishTestResults@2 - inputs: - testResultsFormat: "VSTest" - testResultsFiles: "**/*.trx" From 5e686801553065af76c9fd7b0f1cee29f97e634c Mon Sep 17 00:00:00 2001 From: "deniz_irgin@yahoo.com" Date: Thu, 18 Nov 2021 18:10:25 +0300 Subject: [PATCH 05/24] add support for LocalStack.Client.Extensions to publish-nuget --- .github/workflows/publish-nuget.yml | 6 +++++- .../Properties/launchSettings-example.json | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 build/LocalStack.Build/Properties/launchSettings-example.json diff --git a/.github/workflows/publish-nuget.yml b/.github/workflows/publish-nuget.yml index 6d7f52f..155d376 100644 --- a/.github/workflows/publish-nuget.yml +++ b/.github/workflows/publish-nuget.yml @@ -24,7 +24,7 @@ on: - LocalStack.Client.Extensions jobs: - build-and-test: + publish-nuget: runs-on: ubuntu-20.04 steps: @@ -59,6 +59,10 @@ jobs: run: | echo "Package Version: ${{ github.event.inputs.package-version }}" + - name: Remove Project Ref & Add latest pack + 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 + - 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 }} diff --git a/build/LocalStack.Build/Properties/launchSettings-example.json b/build/LocalStack.Build/Properties/launchSettings-example.json new file mode 100644 index 0000000..e58c2de --- /dev/null +++ b/build/LocalStack.Build/Properties/launchSettings-example.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "LocalStack.Build": { + "commandName": "Project", + "commandLineArgs": "--target nuget-push --package-version 1.2.3 --package-source myget --package-id LocalStack.Client --package-secret ************************" + }, + "WSL 2": { + "commandName": "WSL2", + "commandLineArgs": "--target get-version", + "distributionName": "Ubuntu-18.04" + } + } +} \ No newline at end of file From 54bd8f3b9284ba726cded6ce84c1245315045fb2 Mon Sep 17 00:00:00 2001 From: "deniz_irgin@yahoo.com" Date: Thu, 18 Nov 2021 18:57:52 +0300 Subject: [PATCH 06/24] some clean ups [skip ci] --- LocalStack.sln | 13 +- build.cake | 302 ------------------------- build/LocalStack.Build/GlobalUsings.cs | 1 + build/LocalStack.Build/Program.cs | 4 +- 4 files changed, 9 insertions(+), 311 deletions(-) delete mode 100644 build.cake diff --git a/LocalStack.sln b/LocalStack.sln index 34ff9de..3f33526 100644 --- a/LocalStack.sln +++ b/LocalStack.sln @@ -22,13 +22,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LocalStack.Client.Sandbox.D EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Solution Items", "_Solution Items", "{06034ACF-97AD-4266-8E46-42B1804C89B6}" ProjectSection(SolutionItems) = preProject - build\azure-pipelines.artifact.yml = build\azure-pipelines.artifact.yml - build\azure-pipelines.extensions.artifact.yml = build\azure-pipelines.extensions.artifact.yml - build\azure-pipelines.macos.yml = build\azure-pipelines.macos.yml - build\azure-pipelines.ubuntu.yml = build\azure-pipelines.ubuntu.yml - build\azure-pipelines.windows.yml = build\azure-pipelines.windows.yml - build.cake = build.cake + .github\workflows\build-macos.yml = .github\workflows\build-macos.yml + .github\workflows\build-ubuntu.yml = .github\workflows\build-ubuntu.yml + .github\workflows\build-windows.yml = .github\workflows\build-windows.yml + build.ps1 = build.ps1 + build.sh = build.sh src\Directory.Build.props = src\Directory.Build.props + .github\workflows\publish-nuget.yml = .github\workflows\publish-nuget.yml + .github\workflows\test-report.yml = .github\workflows\test-report.yml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LocalStack.Client.Extensions", "src\LocalStack.Client.Extensions\LocalStack.Client.Extensions.csproj", "{74035094-A726-44E2-9B88-42D6425D8548}" diff --git a/build.cake b/build.cake deleted file mode 100644 index eccf25d..0000000 --- a/build.cake +++ /dev/null @@ -1,302 +0,0 @@ -using System; -using System.Text.RegularExpressions; -using System.Diagnostics; - -var target = Argument("target", "default"); -var configuration = Argument("config", "Release"); -var buildNumber = Argument("buildnumber", 1); -var skipFunctionalTest = Argument("skipFunctionalTest", "1"); - -var artifactOutput = "./artifacts"; -var artifactExtensionsOutput = "./artifacts-extensions"; -var testResults = "results.trx"; -string projectPath = "./src/LocalStack.Client/LocalStack.Client.csproj"; -string projectPathExtensions = "./src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj"; - - -Task("Default") - .IsDependentOn("init") - .IsDependentOn("tests"); - -Task("init") - .Description("Initialize task prerequisites") - .Does(() => - { - StartProcess("dotnet", new ProcessSettings { - Arguments = "--info" - }); - - if(IsRunningOnUnix()) - { - StartProcess("git", new ProcessSettings { - Arguments = "config --global core.autocrlf true" - }); - - StartProcess("mono", new ProcessSettings { - Arguments = "--version" - }); - - InstallXUnitNugetPackage(); - } - }); - -Task("compile") - .Description("Builds all the projects in the solution") - .Does(() => - { - string slnPath = "./src/LocalStack.sln"; - - DotNetCoreBuildSettings settings = new DotNetCoreBuildSettings(); - settings.Configuration = configuration; - DotNetCoreBuild(slnPath, settings); - }); - -Task("tests") - .Description("Run Tests") - .IsDependentOn("compile") - .Does(() => - { - DotNetCoreTestSettings settings = new DotNetCoreTestSettings(); - settings.NoRestore = true; - settings.NoBuild = true; - settings.Configuration = configuration; - - IList testProjMetadatas = GetProjMetadata(); - - foreach (var testProj in testProjMetadatas) - { - string testProjectPath = testProj.CsProjPath; - - Warning($"Target Frameworks {string.Join(" ",testProj.TargetFrameworks)}"); - - foreach(string targetFramework in testProj.TargetFrameworks) - { - if(skipFunctionalTest == "1" && testProj.AssemblyName == "LocalStack.Client.Functional.Tests") - { - Warning("Skipping Functional Tests"); - continue; - } - - Warning($"Running {targetFramework.ToUpper()} tests for {testProj.AssemblyName}"); - settings.Framework = targetFramework; - - if(IsRunningOnUnix() && targetFramework == "net461") - { - RunXunitUsingMono(targetFramework, $"{testProj.DirectoryPath}/bin/{configuration}/{targetFramework}/{testProj.AssemblyName}.dll"); - } - else - { - string testFilePrefix = targetFramework.Replace(".","-"); - settings.ArgumentCustomization = args => args.Append($" --logger \"trx;LogFileName={testFilePrefix}_{testResults}\""); - DotNetCoreTest(testProjectPath, settings); - } - } - } - }); - -Task("nuget-pack") - .Does(() => - { - string outputDirectory = MakeAbsolute(Directory(artifactOutput)).FullPath; - string projectFullPath = MakeAbsolute(File(projectPath)).FullPath; - - if(!System.IO.Directory.Exists(outputDirectory)) - { - System.IO.Directory.CreateDirectory(outputDirectory); - } - - var settings = new DotNetCorePackSettings(); - settings.Configuration = configuration; - settings.OutputDirectory = artifactOutput; - settings.MSBuildSettings = new DotNetCoreMSBuildSettings(); - settings.MSBuildSettings.SetVersion(GetProjectVersion()); - - DotNetCorePack(projectFullPath, settings); - }); - -Task("nuget-pack-extensions") - .Does(() => - { - string outputDirectory = MakeAbsolute(Directory(artifactExtensionsOutput)).FullPath; - string projectFullPath = MakeAbsolute(File(projectPathExtensions)).FullPath; - - if(!System.IO.Directory.Exists(outputDirectory)) - { - System.IO.Directory.CreateDirectory(outputDirectory); - } - - var settings = new DotNetCorePackSettings(); - settings.Configuration = configuration; - settings.OutputDirectory = artifactExtensionsOutput; - settings.MSBuildSettings = new DotNetCoreMSBuildSettings(); - settings.MSBuildSettings.SetVersion(GetExtensionProjectVersion()); - - DotNetCorePack(projectFullPath, settings); - }); - -Task("get-version") - .Description("Get version") - .Does(() => - { - Warning(GetProjectVersion()); - }); - -RunTarget(target); - -/* -/ HELPER METHODS -*/ -private void InstallXUnitNugetPackage() -{ - NuGetInstallSettings nugetInstallSettings = new NuGetInstallSettings(); - nugetInstallSettings.Version = "2.4.1"; - nugetInstallSettings.Verbosity = NuGetVerbosity.Normal; - nugetInstallSettings.OutputDirectory = "testrunner"; - nugetInstallSettings.WorkingDirectory = "."; - - NuGetInstall("xunit.runner.console", nugetInstallSettings); -} - -private void RunXunitUsingMono(string targetFramework, string assemblyPath) -{ - int exitCode = StartProcess("mono", new ProcessSettings { - Arguments = $"./testrunner/xunit.runner.console.2.4.1/tools/{targetFramework}/xunit.console.exe {assemblyPath}" - }); - - if(exitCode != 0) - { - throw new InvalidOperationException($"Exit code: {exitCode}"); - } -} - -private IList GetProjMetadata() -{ - var testsRoot = MakeAbsolute(Directory("./tests/")); - var csProjs = GetFiles($"{testsRoot}/**/*.csproj").Where(fp => fp.FullPath.EndsWith("Tests.csproj")).ToList(); - - IList testProjMetadatas = new List(); - - foreach (var csProj in csProjs) - { - string csProjPath = csProj.FullPath; - - string[] targetFrameworks = GetProjectTargetFrameworks(csProjPath); - string directoryPath = csProj.GetDirectory().FullPath; - string assemblyName = GetAssemblyName(csProjPath); - - var testProjMetadata = new TestProjMetadata(directoryPath, csProjPath, targetFrameworks, assemblyName); - testProjMetadatas.Add(testProjMetadata); - } - - return testProjMetadatas; -} - -private string[] GetProjectTargetFrameworks(string csprojPath) -{ - var file = MakeAbsolute(File(csprojPath)); - var project = System.IO.File.ReadAllText(file.FullPath, Encoding.UTF8); - - bool multipleFrameworks = project.Contains(""); - string startElement = multipleFrameworks ? "" : ""; - string endElement = multipleFrameworks ? "" : ""; - - int startIndex = project.IndexOf(startElement) + startElement.Length; - int endIndex = project.IndexOf(endElement, startIndex); - - string targetFrameworks = project.Substring(startIndex, endIndex - startIndex); - return targetFrameworks.Split(';'); -} - -private string GetAssemblyName(string csprojPath) -{ - var file = MakeAbsolute(File(csprojPath)); - var project = System.IO.File.ReadAllText(file.FullPath, Encoding.UTF8); - - bool assemblyNameElementExists = project.Contains(""); - - string assemblyName = string.Empty; - - if(assemblyNameElementExists) - { - int startIndex = project.IndexOf("") + "".Length; - int endIndex = project.IndexOf("", startIndex); - - assemblyName = project.Substring(startIndex, endIndex - startIndex); - } - else - { - int startIndex = csprojPath.LastIndexOf("/") + 1; - int endIndex = csprojPath.IndexOf(".csproj", startIndex); - - assemblyName = csprojPath.Substring(startIndex, endIndex - startIndex); - } - - return assemblyName; -} - -private void UpdateProjectVersion(string version) -{ - Information("Setting version to " + version); - - if(string.IsNullOrWhiteSpace(version)) - { - throw new CakeException("No version specified! You need to pass in --targetversion=\"x.y.z\""); - } - - var file = MakeAbsolute(File("./src/Directory.Build.props")); - - Information(file.FullPath); - - var project = System.IO.File.ReadAllText(file.FullPath, Encoding.UTF8); - - var projectVersion = new Regex(@".+<\/Version>"); - project = projectVersion.Replace(project, string.Concat("", version, "")); - - System.IO.File.WriteAllText(file.FullPath, project, Encoding.UTF8); -} - -private string GetProjectVersion() -{ - var file = MakeAbsolute(File("./src/Directory.Build.props")); - - Information(file.FullPath); - - var project = System.IO.File.ReadAllText(file.FullPath, Encoding.UTF8); - int startIndex = project.IndexOf("") + "".Length; - int endIndex = project.IndexOf("", startIndex); - - string version = project.Substring(startIndex, endIndex - startIndex); - version = $"{version}.{buildNumber}"; - - return version; -} - -private string GetExtensionProjectVersion() -{ - var file = MakeAbsolute(File("./src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj")); - - Information(file.FullPath); - - var project = System.IO.File.ReadAllText(file.FullPath, Encoding.UTF8); - int startIndex = project.IndexOf("") + "".Length; - int endIndex = project.IndexOf("", startIndex); - - string version = project.Substring(startIndex, endIndex - startIndex); - version = $"{version}.{buildNumber}"; - - return version; -} - -/* -/ MODELS -*/ -public class TestProjMetadata -{ - public TestProjMetadata(string directoryPath, string csProjPath, string[] targetFrameworks, string assemblyName) - => (DirectoryPath, CsProjPath, TargetFrameworks, AssemblyName) = (directoryPath, csProjPath, targetFrameworks, assemblyName); - - public string DirectoryPath { get; } - public string CsProjPath { get; } - public string AssemblyName { get; set; } - public string[] TargetFrameworks { get; } -} \ No newline at end of file diff --git a/build/LocalStack.Build/GlobalUsings.cs b/build/LocalStack.Build/GlobalUsings.cs index dda252f..cee24ea 100644 --- a/build/LocalStack.Build/GlobalUsings.cs +++ b/build/LocalStack.Build/GlobalUsings.cs @@ -12,6 +12,7 @@ global using Cake.Common.Tools.NuGet; global using Cake.Common.Tools.NuGet.Install; global using Cake.Common.Diagnostics; +global using Cake.Common.Tools.DotNetCore.NuGet.Push; global using Cake.Common.Tools.DotNetCore; global using Cake.Common.Tools.DotNetCore.Build; global using Cake.Common.Tools.DotNetCore.MSBuild; diff --git a/build/LocalStack.Build/Program.cs b/build/LocalStack.Build/Program.cs index 2ebf0ad..1a5ccae 100644 --- a/build/LocalStack.Build/Program.cs +++ b/build/LocalStack.Build/Program.cs @@ -1,6 +1,4 @@ -using Cake.Common.Tools.DotNetCore.NuGet.Push; - -return new CakeHost() +return new CakeHost() .UseContext() .Run(args); From abf914b52072a5542d86f126e2eaf50ef8831412 Mon Sep 17 00:00:00 2001 From: "deniz_irgin@yahoo.com" Date: Thu, 18 Nov 2021 19:18:05 +0300 Subject: [PATCH 07/24] migrate main project to C# 10 minimal style --- src/LocalStack.Client/Config.cs | 100 +++--- src/LocalStack.Client/Contracts/IConfig.cs | 21 +- .../Contracts/IConfigOptions.cs | 17 +- .../Contracts/ILocalStackOptions.cs | 13 +- src/LocalStack.Client/Contracts/ISession.cs | 17 +- .../Contracts/ISessionOptions.cs | 15 +- .../Contracts/ISessionReflection.cs | 20 +- .../Contracts/ISessionStandalone.cs | 13 +- .../Enums/AwsServiceEndpointMetadata.cs | 303 +++++++++--------- src/LocalStack.Client/Enums/AwsServiceEnum.cs | 189 ++++++----- src/LocalStack.Client/GlobalUsings.cs | 30 ++ .../LocalStack.Client.csproj | 78 ++--- .../Models/AwsServiceEndpoint.cs | 33 +- src/LocalStack.Client/Models/Constants.cs | 25 +- .../Options/ConfigOptions.cs | 42 ++- .../Options/LocalStackOptions.cs | 31 +- .../Options/SessionOptions.cs | 40 ++- src/LocalStack.Client/Session.cs | 130 ++++---- src/LocalStack.Client/SessionStandalone.cs | 59 ++-- .../Utils/SessionReflection.cs | 116 +++---- .../MockAwsServiceEndpoint.cs | 9 +- 21 files changed, 615 insertions(+), 686 deletions(-) create mode 100644 src/LocalStack.Client/GlobalUsings.cs diff --git a/src/LocalStack.Client/Config.cs b/src/LocalStack.Client/Config.cs index f581501..c215f57 100644 --- a/src/LocalStack.Client/Config.cs +++ b/src/LocalStack.Client/Config.cs @@ -1,66 +1,56 @@ -using LocalStack.Client.Contracts; -using LocalStack.Client.Models; +namespace LocalStack.Client; -using System.Collections.Generic; -using System.Linq; - -using LocalStack.Client.Enums; -using LocalStack.Client.Options; - -namespace LocalStack.Client +public class Config : IConfig { - public class Config : IConfig - { - private readonly AwsServiceEndpointMetadata[] _serviceEndpointMetadata = AwsServiceEndpointMetadata.All; - private readonly IEnumerable _awsServiceEndpoints; - - public Config() - : this(new ConfigOptions()) - { - } - - public Config(IConfigOptions configOptions) - { - string localStackHost = configOptions.LocalStackHost; - string protocol = configOptions.UseSsl ? "https" : "http"; - bool useLegacyPorts = configOptions.UseLegacyPorts; - int edgePort = configOptions.EdgePort; + private readonly AwsServiceEndpointMetadata[] _serviceEndpointMetadata = AwsServiceEndpointMetadata.All; + private readonly IEnumerable _awsServiceEndpoints; - int GetServicePort(int metadataPort) => useLegacyPorts ? metadataPort : edgePort; + public Config() + : this(new ConfigOptions()) + { + } - _awsServiceEndpoints = _serviceEndpointMetadata.Select(metadata => new AwsServiceEndpoint(metadata.ServiceId, - metadata.CliName, - metadata.Enum, - GetServicePort(metadata.Port), - localStackHost, - metadata.GetServiceUrl(protocol, localStackHost, GetServicePort(metadata.Port)))); - } + public Config(IConfigOptions configOptions) + { + string localStackHost = configOptions.LocalStackHost; + string protocol = configOptions.UseSsl ? "https" : "http"; + bool useLegacyPorts = configOptions.UseLegacyPorts; + int edgePort = configOptions.EdgePort; + + int GetServicePort(int metadataPort) => useLegacyPorts ? metadataPort : edgePort; + + _awsServiceEndpoints = _serviceEndpointMetadata.Select(metadata => new AwsServiceEndpoint(metadata.ServiceId, + metadata.CliName, + metadata.Enum, + GetServicePort(metadata.Port), + localStackHost, + metadata.GetServiceUrl(protocol, localStackHost, GetServicePort(metadata.Port)))); + } - public IEnumerable GetAwsServiceEndpoints() - { - return _awsServiceEndpoints; - } + public IEnumerable GetAwsServiceEndpoints() + { + return _awsServiceEndpoints; + } - public AwsServiceEndpoint GetAwsServiceEndpoint(AwsServiceEnum awsServiceEnum) - { - return _awsServiceEndpoints.SingleOrDefault(endpoint => endpoint.AwsServiceEnum == awsServiceEnum); - } + public AwsServiceEndpoint GetAwsServiceEndpoint(AwsServiceEnum awsServiceEnum) + { + return _awsServiceEndpoints.SingleOrDefault(endpoint => endpoint.AwsServiceEnum == awsServiceEnum); + } - public AwsServiceEndpoint GetAwsServiceEndpoint(string serviceId) - { - return _awsServiceEndpoints.SingleOrDefault(endpoint => endpoint.ServiceId == serviceId); - } + public AwsServiceEndpoint GetAwsServiceEndpoint(string serviceId) + { + return _awsServiceEndpoints.SingleOrDefault(endpoint => endpoint.ServiceId == serviceId); + } - public IDictionary GetAwsServicePorts() - { - return _awsServiceEndpoints.ToDictionary(endpoint => endpoint.AwsServiceEnum, endpoint => endpoint.Port); - } + public IDictionary GetAwsServicePorts() + { + return _awsServiceEndpoints.ToDictionary(endpoint => endpoint.AwsServiceEnum, endpoint => endpoint.Port); + } - public int GetAwsServicePort(AwsServiceEnum awsServiceEnum) - { - return _awsServiceEndpoints - .First(endpoint => endpoint.AwsServiceEnum == awsServiceEnum) - .Port; - } + public int GetAwsServicePort(AwsServiceEnum awsServiceEnum) + { + return _awsServiceEndpoints + .First(endpoint => endpoint.AwsServiceEnum == awsServiceEnum) + .Port; } } \ No newline at end of file diff --git a/src/LocalStack.Client/Contracts/IConfig.cs b/src/LocalStack.Client/Contracts/IConfig.cs index 88e119e..db60929 100644 --- a/src/LocalStack.Client/Contracts/IConfig.cs +++ b/src/LocalStack.Client/Contracts/IConfig.cs @@ -1,21 +1,14 @@ -using LocalStack.Client.Models; +namespace LocalStack.Client.Contracts; -using System.Collections.Generic; - -using LocalStack.Client.Enums; - -namespace LocalStack.Client.Contracts +public interface IConfig { - public interface IConfig - { - IEnumerable GetAwsServiceEndpoints(); + IEnumerable GetAwsServiceEndpoints(); - AwsServiceEndpoint GetAwsServiceEndpoint(AwsServiceEnum awsServiceEnum); + AwsServiceEndpoint GetAwsServiceEndpoint(AwsServiceEnum awsServiceEnum); - AwsServiceEndpoint GetAwsServiceEndpoint(string serviceId); + AwsServiceEndpoint GetAwsServiceEndpoint(string serviceId); - IDictionary GetAwsServicePorts(); + IDictionary GetAwsServicePorts(); - int GetAwsServicePort(AwsServiceEnum awsServiceEnum); - } + int GetAwsServicePort(AwsServiceEnum awsServiceEnum); } \ No newline at end of file diff --git a/src/LocalStack.Client/Contracts/IConfigOptions.cs b/src/LocalStack.Client/Contracts/IConfigOptions.cs index d26fb62..7e8d30b 100644 --- a/src/LocalStack.Client/Contracts/IConfigOptions.cs +++ b/src/LocalStack.Client/Contracts/IConfigOptions.cs @@ -1,13 +1,12 @@ -namespace LocalStack.Client.Contracts +namespace LocalStack.Client.Contracts; + +public interface IConfigOptions { - public interface IConfigOptions - { - string LocalStackHost { get; } + string LocalStackHost { get; } - bool UseSsl { get; } + bool UseSsl { get; } - bool UseLegacyPorts { get; } + bool UseLegacyPorts { get; } - int EdgePort { get; } - } -} + int EdgePort { get; } +} \ No newline at end of file diff --git a/src/LocalStack.Client/Contracts/ILocalStackOptions.cs b/src/LocalStack.Client/Contracts/ILocalStackOptions.cs index 9e204a9..c778eab 100644 --- a/src/LocalStack.Client/Contracts/ILocalStackOptions.cs +++ b/src/LocalStack.Client/Contracts/ILocalStackOptions.cs @@ -1,13 +1,12 @@ using LocalStack.Client.Options; -namespace LocalStack.Client.Contracts +namespace LocalStack.Client.Contracts; + +public interface ILocalStackOptions { - public interface ILocalStackOptions - { - bool UseLocalStack { get; } + bool UseLocalStack { get; } - SessionOptions Session { get; } + SessionOptions Session { get; } - ConfigOptions Config { get; } - } + ConfigOptions Config { get; } } \ No newline at end of file diff --git a/src/LocalStack.Client/Contracts/ISession.cs b/src/LocalStack.Client/Contracts/ISession.cs index 539c38d..6b89703 100644 --- a/src/LocalStack.Client/Contracts/ISession.cs +++ b/src/LocalStack.Client/Contracts/ISession.cs @@ -1,17 +1,12 @@ -using System; +namespace LocalStack.Client.Contracts; -using Amazon.Runtime; - -namespace LocalStack.Client.Contracts +public interface ISession { - public interface ISession - { - TClient CreateClientByImplementation() where TClient : AmazonServiceClient; + TClient CreateClientByImplementation() where TClient : AmazonServiceClient; - AmazonServiceClient CreateClientByImplementation(Type implType); + AmazonServiceClient CreateClientByImplementation(Type implType); - AmazonServiceClient CreateClientByInterface() where TClient: IAmazonService; + AmazonServiceClient CreateClientByInterface() where TClient: IAmazonService; - AmazonServiceClient CreateClientByInterface(Type serviceInterfaceType); - } + AmazonServiceClient CreateClientByInterface(Type serviceInterfaceType); } \ No newline at end of file diff --git a/src/LocalStack.Client/Contracts/ISessionOptions.cs b/src/LocalStack.Client/Contracts/ISessionOptions.cs index 89b975d..660d7d4 100644 --- a/src/LocalStack.Client/Contracts/ISessionOptions.cs +++ b/src/LocalStack.Client/Contracts/ISessionOptions.cs @@ -1,13 +1,12 @@ -namespace LocalStack.Client.Contracts +namespace LocalStack.Client.Contracts; + +public interface ISessionOptions { - public interface ISessionOptions - { - string AwsAccessKeyId { get; } + string AwsAccessKeyId { get; } - string AwsAccessKey { get; } + string AwsAccessKey { get; } - string AwsSessionToken { get; } + string AwsSessionToken { get; } - string RegionName { get; } - } + string RegionName { get; } } \ No newline at end of file diff --git a/src/LocalStack.Client/Contracts/ISessionReflection.cs b/src/LocalStack.Client/Contracts/ISessionReflection.cs index 1951fb9..89fd0c1 100644 --- a/src/LocalStack.Client/Contracts/ISessionReflection.cs +++ b/src/LocalStack.Client/Contracts/ISessionReflection.cs @@ -1,20 +1,14 @@ -using System; +namespace LocalStack.Client.Contracts; -using Amazon.Runtime; -using Amazon.Runtime.Internal; - -namespace LocalStack.Client.Contracts +public interface ISessionReflection { - public interface ISessionReflection - { - IServiceMetadata ExtractServiceMetadata() where TClient : AmazonServiceClient; + IServiceMetadata ExtractServiceMetadata() where TClient : AmazonServiceClient; - IServiceMetadata ExtractServiceMetadata(Type clientType); + IServiceMetadata ExtractServiceMetadata(Type clientType); - ClientConfig CreateClientConfig() where TClient : AmazonServiceClient; + ClientConfig CreateClientConfig() where TClient : AmazonServiceClient; - ClientConfig CreateClientConfig(Type clientType); + ClientConfig CreateClientConfig(Type clientType); - bool SetForcePathStyle(ClientConfig clientConfig, bool value = true); - } + bool SetForcePathStyle(ClientConfig clientConfig, bool value = true); } \ No newline at end of file diff --git a/src/LocalStack.Client/Contracts/ISessionStandalone.cs b/src/LocalStack.Client/Contracts/ISessionStandalone.cs index 1baf144..065b2a5 100644 --- a/src/LocalStack.Client/Contracts/ISessionStandalone.cs +++ b/src/LocalStack.Client/Contracts/ISessionStandalone.cs @@ -1,11 +1,10 @@ -namespace LocalStack.Client.Contracts +namespace LocalStack.Client.Contracts; + +public interface ISessionStandalone { - public interface ISessionStandalone - { - ISessionStandalone WithSessionOptions(ISessionOptions sessionOptions); + ISessionStandalone WithSessionOptions(ISessionOptions sessionOptions); - ISessionStandalone WithConfigurationOptions(IConfigOptions configOptions); + ISessionStandalone WithConfigurationOptions(IConfigOptions configOptions); - ISession Create(); - } + ISession Create(); } \ No newline at end of file diff --git a/src/LocalStack.Client/Enums/AwsServiceEndpointMetadata.cs b/src/LocalStack.Client/Enums/AwsServiceEndpointMetadata.cs index 3599d4c..7d6c4c7 100644 --- a/src/LocalStack.Client/Enums/AwsServiceEndpointMetadata.cs +++ b/src/LocalStack.Client/Enums/AwsServiceEndpointMetadata.cs @@ -1,175 +1,170 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Linq; +namespace LocalStack.Client.Enums; -namespace LocalStack.Client.Enums +[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] +public class AwsServiceEndpointMetadata { - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] - public class AwsServiceEndpointMetadata - { - private const string CommonEndpointPattern = "{0}://{1}:{2}"; - - public static readonly AwsServiceEndpointMetadata ApiGateway = new("API Gateway", "apigateway", CommonEndpointPattern, 4567, AwsServiceEnum.ApiGateway); - public static readonly AwsServiceEndpointMetadata ApiGatewayV2 = new("ApiGatewayV2", "apigatewayv2", CommonEndpointPattern, 4567, AwsServiceEnum.ApiGatewayV2); - public static readonly AwsServiceEndpointMetadata Kinesis = new("Kinesis", "kinesis", CommonEndpointPattern, 4568, AwsServiceEnum.Kinesis); - public static readonly AwsServiceEndpointMetadata DynamoDb = new("DynamoDB", "dynamodb", CommonEndpointPattern, 4569, AwsServiceEnum.DynamoDb); - public static readonly AwsServiceEndpointMetadata DynamoDbStreams = new("DynamoDB Streams", "dynamodbstreams", CommonEndpointPattern, 4570, AwsServiceEnum.DynamoDbStreams); - public static readonly AwsServiceEndpointMetadata ElasticSearch = new("Elasticsearch Service", "elasticsearch", CommonEndpointPattern, 4571, AwsServiceEnum.ElasticSearch); - public static readonly AwsServiceEndpointMetadata S3 = new("S3", "s3", CommonEndpointPattern, 4572, AwsServiceEnum.S3); - public static readonly AwsServiceEndpointMetadata Firehose = new("Firehose", "firehose", CommonEndpointPattern, 4573, AwsServiceEnum.Firehose); - public static readonly AwsServiceEndpointMetadata Lambda = new("Lambda", "lambda", CommonEndpointPattern, 4574, AwsServiceEnum.Lambda); - public static readonly AwsServiceEndpointMetadata Sns = new("SNS", "sns", CommonEndpointPattern, 4575, AwsServiceEnum.Sns); - public static readonly AwsServiceEndpointMetadata Sqs = new("SQS", "sqs", CommonEndpointPattern, 4576, AwsServiceEnum.Sqs); - public static readonly AwsServiceEndpointMetadata Redshift = new("Redshift", "redshift", CommonEndpointPattern, 4577, AwsServiceEnum.Redshift); - public static readonly AwsServiceEndpointMetadata RedshiftData = new("Redshift Data", "redshift-data", CommonEndpointPattern, 4577, AwsServiceEnum.RedshiftData); - public static readonly AwsServiceEndpointMetadata Es = new("ES", "es", CommonEndpointPattern, 4578, AwsServiceEnum.Es); - public static readonly AwsServiceEndpointMetadata Ses = new("SES", "ses", CommonEndpointPattern, 4579, AwsServiceEnum.Ses); - public static readonly AwsServiceEndpointMetadata Route53 = new("Route 53", "route53", CommonEndpointPattern, 4580, AwsServiceEnum.Route53); - public static readonly AwsServiceEndpointMetadata CloudFormation = new("CloudFormation", "cloudformation", CommonEndpointPattern, 4581, AwsServiceEnum.CloudFormation); - public static readonly AwsServiceEndpointMetadata CloudWatch = new("CloudWatch", "cloudwatch", CommonEndpointPattern, 4582, AwsServiceEnum.CloudWatch); - public static readonly AwsServiceEndpointMetadata Ssm = new("SSM", "ssm", CommonEndpointPattern, 4583, AwsServiceEnum.Ssm); - public static readonly AwsServiceEndpointMetadata SecretsManager = new("Secrets Manager", "secretsmanager", CommonEndpointPattern, 4584, AwsServiceEnum.SecretsManager); - public static readonly AwsServiceEndpointMetadata StepFunctions = new("SFN", "stepfunctions", CommonEndpointPattern, 4585, AwsServiceEnum.StepFunctions); - public static readonly AwsServiceEndpointMetadata Logs = new("CloudWatch Logs", "logs", CommonEndpointPattern, 4586, AwsServiceEnum.Logs); - public static readonly AwsServiceEndpointMetadata Events = new("CloudWatch Events", "events", CommonEndpointPattern, 4587, AwsServiceEnum.Events); - public static readonly AwsServiceEndpointMetadata Elb = new("Elastic Load Balancing", "elb", CommonEndpointPattern, 4588, AwsServiceEnum.Elb); - public static readonly AwsServiceEndpointMetadata Iot = new("IoT", "iot", CommonEndpointPattern, 4589, AwsServiceEnum.Iot); - public static readonly AwsServiceEndpointMetadata IoTAnalytics = new("IoTAnalytics", "iotanalytics", CommonEndpointPattern, 4589, AwsServiceEnum.IoTAnalytics); - public static readonly AwsServiceEndpointMetadata IoTEvents = new("IoT Events", "iotevents", CommonEndpointPattern, 4589, AwsServiceEnum.IoTEvents); - public static readonly AwsServiceEndpointMetadata IoTEventsData = new("IoT Events Data", "iotevents-data", CommonEndpointPattern, 4589, AwsServiceEnum.IoTEventsData); - public static readonly AwsServiceEndpointMetadata IoTWireless = new("IoT Wireless", "iotwireless", CommonEndpointPattern, 4589, AwsServiceEnum.IoTWireless); - public static readonly AwsServiceEndpointMetadata IoTDataPlane = new("IoT Data Plane", "iot-data", CommonEndpointPattern, 4589, AwsServiceEnum.IoTDataPlane); - public static readonly AwsServiceEndpointMetadata IoTJobsDataPlane = new("IoT Jobs Data Plane", "iot-jobs-data", CommonEndpointPattern, 4589, AwsServiceEnum.IoTJobsDataPlane); - public static readonly AwsServiceEndpointMetadata CognitoIdp = new("Cognito Identity Provider", "cognito-idp", CommonEndpointPattern, 4590, AwsServiceEnum.CognitoIdp); - public static readonly AwsServiceEndpointMetadata CognitoIdentity = new("Cognito Identity", "cognito-identity", CommonEndpointPattern, 4591, AwsServiceEnum.CognitoIdentity); - public static readonly AwsServiceEndpointMetadata Sts = new("STS", "sts", CommonEndpointPattern, 4592, AwsServiceEnum.Sts); - public static readonly AwsServiceEndpointMetadata Iam = new("IAM", "iam", CommonEndpointPattern, 4593, AwsServiceEnum.Iam); - public static readonly AwsServiceEndpointMetadata Rds = new("RDS", "rds", CommonEndpointPattern, 4594, AwsServiceEnum.Rds); - public static readonly AwsServiceEndpointMetadata RdsData = new("RDS Data", "rds-data", CommonEndpointPattern, 4594, AwsServiceEnum.RdsData); - public static readonly AwsServiceEndpointMetadata CloudSearch = new("CloudSearch", "cloudsearch", CommonEndpointPattern, 4595, AwsServiceEnum.CloudSearch); - public static readonly AwsServiceEndpointMetadata Swf = new("SWF", "swf", CommonEndpointPattern, 4596, AwsServiceEnum.Swf); - public static readonly AwsServiceEndpointMetadata Ec2 = new("EC2", "ec2", CommonEndpointPattern, 4597, AwsServiceEnum.Ec2); - public static readonly AwsServiceEndpointMetadata ElastiCache = new("ElastiCache", "elasticache", CommonEndpointPattern, 4598, AwsServiceEnum.ElastiCache); - public static readonly AwsServiceEndpointMetadata Kms = new("KMS", "kms", CommonEndpointPattern, 4599, AwsServiceEnum.Kms); - public static readonly AwsServiceEndpointMetadata Emr = new("EMR", "emr", CommonEndpointPattern, 4600, AwsServiceEnum.Emr); - public static readonly AwsServiceEndpointMetadata Ecs = new("ECS", "ecs", CommonEndpointPattern, 4601, AwsServiceEnum.Ecs); - public static readonly AwsServiceEndpointMetadata Eks = new("EKS", "eks", CommonEndpointPattern, 4602, AwsServiceEnum.Eks); - public static readonly AwsServiceEndpointMetadata XRay = new("XRay", "xray", CommonEndpointPattern, 4603, AwsServiceEnum.XRay); - public static readonly AwsServiceEndpointMetadata ElasticBeanstalk = new("Elastic Beanstalk", "elasticbeanstalk", CommonEndpointPattern, 4604, AwsServiceEnum.ElasticBeanstalk); - public static readonly AwsServiceEndpointMetadata AppSync = new("AppSync", "appsync", CommonEndpointPattern, 4605, AwsServiceEnum.AppSync); - public static readonly AwsServiceEndpointMetadata CloudFront = new("CloudFront", "cloudfront", CommonEndpointPattern, 4606, AwsServiceEnum.CloudFront); - public static readonly AwsServiceEndpointMetadata Athena = new("Athena", "athena", CommonEndpointPattern, 4607, AwsServiceEnum.Athena); - public static readonly AwsServiceEndpointMetadata Glue = new("Glue", "glue", CommonEndpointPattern, 4608, AwsServiceEnum.Glue); - public static readonly AwsServiceEndpointMetadata SageMaker = new("SageMaker", "sagemaker", CommonEndpointPattern, 4609, AwsServiceEnum.SageMaker); - public static readonly AwsServiceEndpointMetadata SageMakerRuntime = new("SageMaker Runtime", "sagemaker-runtime", CommonEndpointPattern, 4609, AwsServiceEnum.SageMakerRuntime); - public static readonly AwsServiceEndpointMetadata Ecr = new("ECR", "ecr", CommonEndpointPattern, 4610, AwsServiceEnum.Ecr); - public static readonly AwsServiceEndpointMetadata Qldb = new("QLDB", "qldb", CommonEndpointPattern, 4611, AwsServiceEnum.Qldb); - public static readonly AwsServiceEndpointMetadata QldbSession = new("QLDB Session", "qldb-session", CommonEndpointPattern, 4611, AwsServiceEnum.QldbSession); - public static readonly AwsServiceEndpointMetadata CloudTrail = new("CloudTrail", "cloudtrail", CommonEndpointPattern, 4612, AwsServiceEnum.CloudTrail); - public static readonly AwsServiceEndpointMetadata Glacier = new("Glacier", "glacier", CommonEndpointPattern, 4613, AwsServiceEnum.Glacier); - public static readonly AwsServiceEndpointMetadata Batch = new("Batch", "batch", CommonEndpointPattern, 4614, AwsServiceEnum.Batch); - public static readonly AwsServiceEndpointMetadata Organizations = new("Organizations", "organizations", CommonEndpointPattern, 4615, AwsServiceEnum.Organizations); - public static readonly AwsServiceEndpointMetadata AutoScaling = new("Auto Scaling", "autoscaling", CommonEndpointPattern, 4616, AwsServiceEnum.AutoScaling); - public static readonly AwsServiceEndpointMetadata MediaStore = new("MediaStore", "mediastore", CommonEndpointPattern, 4617, AwsServiceEnum.MediaStore); - public static readonly AwsServiceEndpointMetadata MediaStoreData = new("MediaStore Data", "mediastore-data", CommonEndpointPattern, 4617, AwsServiceEnum.MediaStoreData); - public static readonly AwsServiceEndpointMetadata Transfer = new("Transfer", "transfer", CommonEndpointPattern, 4618, AwsServiceEnum.Transfer); - public static readonly AwsServiceEndpointMetadata Acm = new("ACM", "acm", CommonEndpointPattern, 4619, AwsServiceEnum.Acm); - public static readonly AwsServiceEndpointMetadata CodeCommit = new("CodeCommit", "codecommit", CommonEndpointPattern, 4620, AwsServiceEnum.CodeCommit); - public static readonly AwsServiceEndpointMetadata KinesisAnalytics = new("Kinesis Analytics", "kinesisanalytics", CommonEndpointPattern, 4621, AwsServiceEnum.KinesisAnalytics); - public static readonly AwsServiceEndpointMetadata Amplify = new("Amplify", "amplify", CommonEndpointPattern, 4622, AwsServiceEnum.Amplify); - public static readonly AwsServiceEndpointMetadata ApplicationAutoscaling = new("Application Auto Scaling", "application-autoscaling", CommonEndpointPattern, 4623, AwsServiceEnum.ApplicationAutoscaling); - public static readonly AwsServiceEndpointMetadata Kafka = new("Kafka", "kafka", CommonEndpointPattern, 4624, AwsServiceEnum.Kafka); - public static readonly AwsServiceEndpointMetadata ApiGatewayManagementApi = new("ApiGatewayManagementApi", "apigatewaymanagementapi", CommonEndpointPattern, 4625, AwsServiceEnum.ApiGatewayManagementApi); - public static readonly AwsServiceEndpointMetadata TimeStreamQuery = new("Timestream Query", "timestream-query", CommonEndpointPattern, 4626, AwsServiceEnum.TimeStreamQuery); - public static readonly AwsServiceEndpointMetadata TimeStreamWrite = new("Timestream Write", "timestream-write", CommonEndpointPattern, 4626, AwsServiceEnum.TimeStreamWrite); - public static readonly AwsServiceEndpointMetadata S3Control = new("S3 Control", "s3control", CommonEndpointPattern, 4627, AwsServiceEnum.S3Control); - public static readonly AwsServiceEndpointMetadata ElbV2 = new("Elastic Load Balancing v2", "elbv2", CommonEndpointPattern, 4628, AwsServiceEnum.ElbV2); - public static readonly AwsServiceEndpointMetadata Support = new("Support", "support", CommonEndpointPattern, 4629, AwsServiceEnum.Support); - public static readonly AwsServiceEndpointMetadata Neptune = new("Neptune", "neptune", CommonEndpointPattern, 4594, AwsServiceEnum.Neptune); - public static readonly AwsServiceEndpointMetadata DocDb = new("DocDB", "docdb", CommonEndpointPattern, 4594, AwsServiceEnum.DocDb); - public static readonly AwsServiceEndpointMetadata ServiceDiscovery = new("ServiceDiscovery", "servicediscovery", CommonEndpointPattern, 4630, AwsServiceEnum.ServiceDiscovery); - public static readonly AwsServiceEndpointMetadata ServerlessApplicationRepository = new("ServerlessApplicationRepository", "serverlessrepo", CommonEndpointPattern, 4631, AwsServiceEnum.ServerlessApplicationRepository); - public static readonly AwsServiceEndpointMetadata AppConfig = new("AppConfig", "appconfig", CommonEndpointPattern, 4632, AwsServiceEnum.AppConfig); - public static readonly AwsServiceEndpointMetadata CostExplorer = new("Cost Explorer", "ce", CommonEndpointPattern, 4633, AwsServiceEnum.CostExplorer); - public static readonly AwsServiceEndpointMetadata MediaConvert = new("MediaConvert", "mediaconvert", CommonEndpointPattern, 4634, AwsServiceEnum.MediaConvert); - public static readonly AwsServiceEndpointMetadata ResourceGroupsTaggingApi = new("Resource Groups Tagging API", "resourcegroupstaggingapi", CommonEndpointPattern, 4635, AwsServiceEnum.ResourceGroupsTaggingApi); - public static readonly AwsServiceEndpointMetadata ResourceGroups = new("Resource Groups", "resource-groups", CommonEndpointPattern, 4636, AwsServiceEnum.ResourceGroups); - public static readonly AwsServiceEndpointMetadata Efs = new("EFS", "efs", CommonEndpointPattern, 4637, AwsServiceEnum.Efs); - public static readonly AwsServiceEndpointMetadata Backup = new("Backup", "backup", CommonEndpointPattern, 4638, AwsServiceEnum.Backup); - public static readonly AwsServiceEndpointMetadata LakeFormation = new("LakeFormation", "lakeformation", CommonEndpointPattern, 4639, AwsServiceEnum.LakeFormation); - public static readonly AwsServiceEndpointMetadata Waf = new("WAF", "waf", CommonEndpointPattern, 4640, AwsServiceEnum.Waf); - public static readonly AwsServiceEndpointMetadata WafV2 = new("WAFV2", "wafv2", CommonEndpointPattern, 4640, AwsServiceEnum.WafV2); - public static readonly AwsServiceEndpointMetadata ConfigService = new("Config Service", "config", CommonEndpointPattern, 4641, AwsServiceEnum.ConfigService); + private const string CommonEndpointPattern = "{0}://{1}:{2}"; + + public static readonly AwsServiceEndpointMetadata ApiGateway = new("API Gateway", "apigateway", CommonEndpointPattern, 4567, AwsServiceEnum.ApiGateway); + public static readonly AwsServiceEndpointMetadata ApiGatewayV2 = new("ApiGatewayV2", "apigatewayv2", CommonEndpointPattern, 4567, AwsServiceEnum.ApiGatewayV2); + public static readonly AwsServiceEndpointMetadata Kinesis = new("Kinesis", "kinesis", CommonEndpointPattern, 4568, AwsServiceEnum.Kinesis); + public static readonly AwsServiceEndpointMetadata DynamoDb = new("DynamoDB", "dynamodb", CommonEndpointPattern, 4569, AwsServiceEnum.DynamoDb); + public static readonly AwsServiceEndpointMetadata DynamoDbStreams = new("DynamoDB Streams", "dynamodbstreams", CommonEndpointPattern, 4570, AwsServiceEnum.DynamoDbStreams); + public static readonly AwsServiceEndpointMetadata ElasticSearch = new("Elasticsearch Service", "elasticsearch", CommonEndpointPattern, 4571, AwsServiceEnum.ElasticSearch); + public static readonly AwsServiceEndpointMetadata S3 = new("S3", "s3", CommonEndpointPattern, 4572, AwsServiceEnum.S3); + public static readonly AwsServiceEndpointMetadata Firehose = new("Firehose", "firehose", CommonEndpointPattern, 4573, AwsServiceEnum.Firehose); + public static readonly AwsServiceEndpointMetadata Lambda = new("Lambda", "lambda", CommonEndpointPattern, 4574, AwsServiceEnum.Lambda); + public static readonly AwsServiceEndpointMetadata Sns = new("SNS", "sns", CommonEndpointPattern, 4575, AwsServiceEnum.Sns); + public static readonly AwsServiceEndpointMetadata Sqs = new("SQS", "sqs", CommonEndpointPattern, 4576, AwsServiceEnum.Sqs); + public static readonly AwsServiceEndpointMetadata Redshift = new("Redshift", "redshift", CommonEndpointPattern, 4577, AwsServiceEnum.Redshift); + public static readonly AwsServiceEndpointMetadata RedshiftData = new("Redshift Data", "redshift-data", CommonEndpointPattern, 4577, AwsServiceEnum.RedshiftData); + public static readonly AwsServiceEndpointMetadata Es = new("ES", "es", CommonEndpointPattern, 4578, AwsServiceEnum.Es); + public static readonly AwsServiceEndpointMetadata Ses = new("SES", "ses", CommonEndpointPattern, 4579, AwsServiceEnum.Ses); + public static readonly AwsServiceEndpointMetadata Route53 = new("Route 53", "route53", CommonEndpointPattern, 4580, AwsServiceEnum.Route53); + public static readonly AwsServiceEndpointMetadata CloudFormation = new("CloudFormation", "cloudformation", CommonEndpointPattern, 4581, AwsServiceEnum.CloudFormation); + public static readonly AwsServiceEndpointMetadata CloudWatch = new("CloudWatch", "cloudwatch", CommonEndpointPattern, 4582, AwsServiceEnum.CloudWatch); + public static readonly AwsServiceEndpointMetadata Ssm = new("SSM", "ssm", CommonEndpointPattern, 4583, AwsServiceEnum.Ssm); + public static readonly AwsServiceEndpointMetadata SecretsManager = new("Secrets Manager", "secretsmanager", CommonEndpointPattern, 4584, AwsServiceEnum.SecretsManager); + public static readonly AwsServiceEndpointMetadata StepFunctions = new("SFN", "stepfunctions", CommonEndpointPattern, 4585, AwsServiceEnum.StepFunctions); + public static readonly AwsServiceEndpointMetadata Logs = new("CloudWatch Logs", "logs", CommonEndpointPattern, 4586, AwsServiceEnum.Logs); + public static readonly AwsServiceEndpointMetadata Events = new("CloudWatch Events", "events", CommonEndpointPattern, 4587, AwsServiceEnum.Events); + public static readonly AwsServiceEndpointMetadata Elb = new("Elastic Load Balancing", "elb", CommonEndpointPattern, 4588, AwsServiceEnum.Elb); + public static readonly AwsServiceEndpointMetadata Iot = new("IoT", "iot", CommonEndpointPattern, 4589, AwsServiceEnum.Iot); + public static readonly AwsServiceEndpointMetadata IoTAnalytics = new("IoTAnalytics", "iotanalytics", CommonEndpointPattern, 4589, AwsServiceEnum.IoTAnalytics); + public static readonly AwsServiceEndpointMetadata IoTEvents = new("IoT Events", "iotevents", CommonEndpointPattern, 4589, AwsServiceEnum.IoTEvents); + public static readonly AwsServiceEndpointMetadata IoTEventsData = new("IoT Events Data", "iotevents-data", CommonEndpointPattern, 4589, AwsServiceEnum.IoTEventsData); + public static readonly AwsServiceEndpointMetadata IoTWireless = new("IoT Wireless", "iotwireless", CommonEndpointPattern, 4589, AwsServiceEnum.IoTWireless); + public static readonly AwsServiceEndpointMetadata IoTDataPlane = new("IoT Data Plane", "iot-data", CommonEndpointPattern, 4589, AwsServiceEnum.IoTDataPlane); + public static readonly AwsServiceEndpointMetadata IoTJobsDataPlane = new("IoT Jobs Data Plane", "iot-jobs-data", CommonEndpointPattern, 4589, AwsServiceEnum.IoTJobsDataPlane); + public static readonly AwsServiceEndpointMetadata CognitoIdp = new("Cognito Identity Provider", "cognito-idp", CommonEndpointPattern, 4590, AwsServiceEnum.CognitoIdp); + public static readonly AwsServiceEndpointMetadata CognitoIdentity = new("Cognito Identity", "cognito-identity", CommonEndpointPattern, 4591, AwsServiceEnum.CognitoIdentity); + public static readonly AwsServiceEndpointMetadata Sts = new("STS", "sts", CommonEndpointPattern, 4592, AwsServiceEnum.Sts); + public static readonly AwsServiceEndpointMetadata Iam = new("IAM", "iam", CommonEndpointPattern, 4593, AwsServiceEnum.Iam); + public static readonly AwsServiceEndpointMetadata Rds = new("RDS", "rds", CommonEndpointPattern, 4594, AwsServiceEnum.Rds); + public static readonly AwsServiceEndpointMetadata RdsData = new("RDS Data", "rds-data", CommonEndpointPattern, 4594, AwsServiceEnum.RdsData); + public static readonly AwsServiceEndpointMetadata CloudSearch = new("CloudSearch", "cloudsearch", CommonEndpointPattern, 4595, AwsServiceEnum.CloudSearch); + public static readonly AwsServiceEndpointMetadata Swf = new("SWF", "swf", CommonEndpointPattern, 4596, AwsServiceEnum.Swf); + public static readonly AwsServiceEndpointMetadata Ec2 = new("EC2", "ec2", CommonEndpointPattern, 4597, AwsServiceEnum.Ec2); + public static readonly AwsServiceEndpointMetadata ElastiCache = new("ElastiCache", "elasticache", CommonEndpointPattern, 4598, AwsServiceEnum.ElastiCache); + public static readonly AwsServiceEndpointMetadata Kms = new("KMS", "kms", CommonEndpointPattern, 4599, AwsServiceEnum.Kms); + public static readonly AwsServiceEndpointMetadata Emr = new("EMR", "emr", CommonEndpointPattern, 4600, AwsServiceEnum.Emr); + public static readonly AwsServiceEndpointMetadata Ecs = new("ECS", "ecs", CommonEndpointPattern, 4601, AwsServiceEnum.Ecs); + public static readonly AwsServiceEndpointMetadata Eks = new("EKS", "eks", CommonEndpointPattern, 4602, AwsServiceEnum.Eks); + public static readonly AwsServiceEndpointMetadata XRay = new("XRay", "xray", CommonEndpointPattern, 4603, AwsServiceEnum.XRay); + public static readonly AwsServiceEndpointMetadata ElasticBeanstalk = new("Elastic Beanstalk", "elasticbeanstalk", CommonEndpointPattern, 4604, AwsServiceEnum.ElasticBeanstalk); + public static readonly AwsServiceEndpointMetadata AppSync = new("AppSync", "appsync", CommonEndpointPattern, 4605, AwsServiceEnum.AppSync); + public static readonly AwsServiceEndpointMetadata CloudFront = new("CloudFront", "cloudfront", CommonEndpointPattern, 4606, AwsServiceEnum.CloudFront); + public static readonly AwsServiceEndpointMetadata Athena = new("Athena", "athena", CommonEndpointPattern, 4607, AwsServiceEnum.Athena); + public static readonly AwsServiceEndpointMetadata Glue = new("Glue", "glue", CommonEndpointPattern, 4608, AwsServiceEnum.Glue); + public static readonly AwsServiceEndpointMetadata SageMaker = new("SageMaker", "sagemaker", CommonEndpointPattern, 4609, AwsServiceEnum.SageMaker); + public static readonly AwsServiceEndpointMetadata SageMakerRuntime = new("SageMaker Runtime", "sagemaker-runtime", CommonEndpointPattern, 4609, AwsServiceEnum.SageMakerRuntime); + public static readonly AwsServiceEndpointMetadata Ecr = new("ECR", "ecr", CommonEndpointPattern, 4610, AwsServiceEnum.Ecr); + public static readonly AwsServiceEndpointMetadata Qldb = new("QLDB", "qldb", CommonEndpointPattern, 4611, AwsServiceEnum.Qldb); + public static readonly AwsServiceEndpointMetadata QldbSession = new("QLDB Session", "qldb-session", CommonEndpointPattern, 4611, AwsServiceEnum.QldbSession); + public static readonly AwsServiceEndpointMetadata CloudTrail = new("CloudTrail", "cloudtrail", CommonEndpointPattern, 4612, AwsServiceEnum.CloudTrail); + public static readonly AwsServiceEndpointMetadata Glacier = new("Glacier", "glacier", CommonEndpointPattern, 4613, AwsServiceEnum.Glacier); + public static readonly AwsServiceEndpointMetadata Batch = new("Batch", "batch", CommonEndpointPattern, 4614, AwsServiceEnum.Batch); + public static readonly AwsServiceEndpointMetadata Organizations = new("Organizations", "organizations", CommonEndpointPattern, 4615, AwsServiceEnum.Organizations); + public static readonly AwsServiceEndpointMetadata AutoScaling = new("Auto Scaling", "autoscaling", CommonEndpointPattern, 4616, AwsServiceEnum.AutoScaling); + public static readonly AwsServiceEndpointMetadata MediaStore = new("MediaStore", "mediastore", CommonEndpointPattern, 4617, AwsServiceEnum.MediaStore); + public static readonly AwsServiceEndpointMetadata MediaStoreData = new("MediaStore Data", "mediastore-data", CommonEndpointPattern, 4617, AwsServiceEnum.MediaStoreData); + public static readonly AwsServiceEndpointMetadata Transfer = new("Transfer", "transfer", CommonEndpointPattern, 4618, AwsServiceEnum.Transfer); + public static readonly AwsServiceEndpointMetadata Acm = new("ACM", "acm", CommonEndpointPattern, 4619, AwsServiceEnum.Acm); + public static readonly AwsServiceEndpointMetadata CodeCommit = new("CodeCommit", "codecommit", CommonEndpointPattern, 4620, AwsServiceEnum.CodeCommit); + public static readonly AwsServiceEndpointMetadata KinesisAnalytics = new("Kinesis Analytics", "kinesisanalytics", CommonEndpointPattern, 4621, AwsServiceEnum.KinesisAnalytics); + public static readonly AwsServiceEndpointMetadata Amplify = new("Amplify", "amplify", CommonEndpointPattern, 4622, AwsServiceEnum.Amplify); + public static readonly AwsServiceEndpointMetadata ApplicationAutoscaling = new("Application Auto Scaling", "application-autoscaling", CommonEndpointPattern, 4623, AwsServiceEnum.ApplicationAutoscaling); + public static readonly AwsServiceEndpointMetadata Kafka = new("Kafka", "kafka", CommonEndpointPattern, 4624, AwsServiceEnum.Kafka); + public static readonly AwsServiceEndpointMetadata ApiGatewayManagementApi = new("ApiGatewayManagementApi", "apigatewaymanagementapi", CommonEndpointPattern, 4625, AwsServiceEnum.ApiGatewayManagementApi); + public static readonly AwsServiceEndpointMetadata TimeStreamQuery = new("Timestream Query", "timestream-query", CommonEndpointPattern, 4626, AwsServiceEnum.TimeStreamQuery); + public static readonly AwsServiceEndpointMetadata TimeStreamWrite = new("Timestream Write", "timestream-write", CommonEndpointPattern, 4626, AwsServiceEnum.TimeStreamWrite); + public static readonly AwsServiceEndpointMetadata S3Control = new("S3 Control", "s3control", CommonEndpointPattern, 4627, AwsServiceEnum.S3Control); + public static readonly AwsServiceEndpointMetadata ElbV2 = new("Elastic Load Balancing v2", "elbv2", CommonEndpointPattern, 4628, AwsServiceEnum.ElbV2); + public static readonly AwsServiceEndpointMetadata Support = new("Support", "support", CommonEndpointPattern, 4629, AwsServiceEnum.Support); + public static readonly AwsServiceEndpointMetadata Neptune = new("Neptune", "neptune", CommonEndpointPattern, 4594, AwsServiceEnum.Neptune); + public static readonly AwsServiceEndpointMetadata DocDb = new("DocDB", "docdb", CommonEndpointPattern, 4594, AwsServiceEnum.DocDb); + public static readonly AwsServiceEndpointMetadata ServiceDiscovery = new("ServiceDiscovery", "servicediscovery", CommonEndpointPattern, 4630, AwsServiceEnum.ServiceDiscovery); + public static readonly AwsServiceEndpointMetadata ServerlessApplicationRepository = new("ServerlessApplicationRepository", "serverlessrepo", CommonEndpointPattern, 4631, AwsServiceEnum.ServerlessApplicationRepository); + public static readonly AwsServiceEndpointMetadata AppConfig = new("AppConfig", "appconfig", CommonEndpointPattern, 4632, AwsServiceEnum.AppConfig); + public static readonly AwsServiceEndpointMetadata CostExplorer = new("Cost Explorer", "ce", CommonEndpointPattern, 4633, AwsServiceEnum.CostExplorer); + public static readonly AwsServiceEndpointMetadata MediaConvert = new("MediaConvert", "mediaconvert", CommonEndpointPattern, 4634, AwsServiceEnum.MediaConvert); + public static readonly AwsServiceEndpointMetadata ResourceGroupsTaggingApi = new("Resource Groups Tagging API", "resourcegroupstaggingapi", CommonEndpointPattern, 4635, AwsServiceEnum.ResourceGroupsTaggingApi); + public static readonly AwsServiceEndpointMetadata ResourceGroups = new("Resource Groups", "resource-groups", CommonEndpointPattern, 4636, AwsServiceEnum.ResourceGroups); + public static readonly AwsServiceEndpointMetadata Efs = new("EFS", "efs", CommonEndpointPattern, 4637, AwsServiceEnum.Efs); + public static readonly AwsServiceEndpointMetadata Backup = new("Backup", "backup", CommonEndpointPattern, 4638, AwsServiceEnum.Backup); + public static readonly AwsServiceEndpointMetadata LakeFormation = new("LakeFormation", "lakeformation", CommonEndpointPattern, 4639, AwsServiceEnum.LakeFormation); + public static readonly AwsServiceEndpointMetadata Waf = new("WAF", "waf", CommonEndpointPattern, 4640, AwsServiceEnum.Waf); + public static readonly AwsServiceEndpointMetadata WafV2 = new("WAFV2", "wafv2", CommonEndpointPattern, 4640, AwsServiceEnum.WafV2); + public static readonly AwsServiceEndpointMetadata ConfigService = new("Config Service", "config", CommonEndpointPattern, 4641, AwsServiceEnum.ConfigService); - public static readonly AwsServiceEndpointMetadata[] All = - { - ApiGateway, ApiGatewayV2, Kinesis, DynamoDb, DynamoDbStreams, ElasticSearch, S3, Firehose, Lambda, Sns, Sqs, Redshift, RedshiftData, Es, Ses, Route53, CloudFormation, CloudWatch, - Ssm, SecretsManager, StepFunctions, Logs, Events, Elb, Iot, IoTAnalytics, IoTEvents, IoTEventsData, IoTWireless, IoTDataPlane, IoTJobsDataPlane, CognitoIdp, CognitoIdentity, Sts, - Iam, Rds, RdsData, CloudSearch, Swf, Ec2, ElastiCache, Kms, Emr, Ecs, Eks, XRay, ElasticBeanstalk, AppSync, CloudFront, Athena, Glue, SageMaker, SageMakerRuntime, Ecr, Qldb, QldbSession, - CloudTrail, Glacier, Batch, Organizations, AutoScaling, MediaStore, MediaStoreData, Transfer, Acm, CodeCommit, KinesisAnalytics, Amplify, ApplicationAutoscaling, Kafka, ApiGatewayManagementApi, - TimeStreamQuery, TimeStreamWrite, S3Control, ElbV2, Support, Neptune, DocDb, ServiceDiscovery, ServerlessApplicationRepository, AppConfig, CostExplorer, MediaConvert, ResourceGroupsTaggingApi, - ResourceGroups, Efs, Backup, LakeFormation, Waf, WafV2, ConfigService - }; - - private AwsServiceEndpointMetadata() - { - } - - private AwsServiceEndpointMetadata(string serviceId, string cliName, string endPointPattern, int port, AwsServiceEnum @enum) - { - ServiceId = serviceId; - CliName = cliName; - EndPointPattern = endPointPattern; - Enum = @enum; - Port = port; - } - - public string ServiceId { get; } + public static readonly AwsServiceEndpointMetadata[] All = + { + ApiGateway, ApiGatewayV2, Kinesis, DynamoDb, DynamoDbStreams, ElasticSearch, S3, Firehose, Lambda, Sns, Sqs, Redshift, RedshiftData, Es, Ses, Route53, CloudFormation, CloudWatch, + Ssm, SecretsManager, StepFunctions, Logs, Events, Elb, Iot, IoTAnalytics, IoTEvents, IoTEventsData, IoTWireless, IoTDataPlane, IoTJobsDataPlane, CognitoIdp, CognitoIdentity, Sts, + Iam, Rds, RdsData, CloudSearch, Swf, Ec2, ElastiCache, Kms, Emr, Ecs, Eks, XRay, ElasticBeanstalk, AppSync, CloudFront, Athena, Glue, SageMaker, SageMakerRuntime, Ecr, Qldb, QldbSession, + CloudTrail, Glacier, Batch, Organizations, AutoScaling, MediaStore, MediaStoreData, Transfer, Acm, CodeCommit, KinesisAnalytics, Amplify, ApplicationAutoscaling, Kafka, ApiGatewayManagementApi, + TimeStreamQuery, TimeStreamWrite, S3Control, ElbV2, Support, Neptune, DocDb, ServiceDiscovery, ServerlessApplicationRepository, AppConfig, CostExplorer, MediaConvert, ResourceGroupsTaggingApi, + ResourceGroups, Efs, Backup, LakeFormation, Waf, WafV2, ConfigService + }; + + private AwsServiceEndpointMetadata() + { + } - public string CliName { get; } + private AwsServiceEndpointMetadata(string serviceId, string cliName, string endPointPattern, int port, AwsServiceEnum @enum) + { + ServiceId = serviceId; + CliName = cliName; + EndPointPattern = endPointPattern; + Enum = @enum; + Port = port; + } - public string EndPointPattern { get; } + public string ServiceId { get; } - public int Port { get; } + public string CliName { get; } - public AwsServiceEnum Enum { get; } + public string EndPointPattern { get; } - public static AwsServiceEndpointMetadata ByName(string name) - { - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } + public int Port { get; } - return All.SingleOrDefault(service => service.ServiceId == name); - } + public AwsServiceEnum Enum { get; } - public static AwsServiceEndpointMetadata ByEnum(AwsServiceEnum @enum) + public static AwsServiceEndpointMetadata ByName(string name) + { + if (name == null) { - return All.SingleOrDefault(service => service.Enum == @enum); + throw new ArgumentNullException(nameof(name)); } - public static AwsServiceEndpointMetadata ByPort(int port) - { - if (port <= 0) - { - throw new ArgumentException("Your port number must be greater than 0", nameof(port)); - } + return All.SingleOrDefault(service => service.ServiceId == name); + } - return All.SingleOrDefault(service => service.Port == port); - } + public static AwsServiceEndpointMetadata ByEnum(AwsServiceEnum @enum) + { + return All.SingleOrDefault(service => service.Enum == @enum); + } - public string GetServiceUrl(string proto, string host, int? port = null) + public static AwsServiceEndpointMetadata ByPort(int port) + { + if (port <= 0) { - return proto == null || host == null - ? throw new ArgumentNullException(proto == null ? nameof(proto) : nameof(host)) - : string.Format(EndPointPattern, proto, host, port ?? Port); + throw new ArgumentException("Your port number must be greater than 0", nameof(port)); } - public override string ToString() - { - return $"{ServiceId} - {CliName} - {Port}"; - } + return All.SingleOrDefault(service => service.Port == port); + } + + public string GetServiceUrl(string proto, string host, int? port = null) + { + return proto == null || host == null + ? throw new ArgumentNullException(proto == null ? nameof(proto) : nameof(host)) + : string.Format(EndPointPattern, proto, host, port ?? Port); + } + + public override string ToString() + { + return $"{ServiceId} - {CliName} - {Port}"; } } \ No newline at end of file diff --git a/src/LocalStack.Client/Enums/AwsServiceEnum.cs b/src/LocalStack.Client/Enums/AwsServiceEnum.cs index d2e23c8..2984c7d 100644 --- a/src/LocalStack.Client/Enums/AwsServiceEnum.cs +++ b/src/LocalStack.Client/Enums/AwsServiceEnum.cs @@ -1,97 +1,96 @@ -namespace LocalStack.Client.Enums +namespace LocalStack.Client.Enums; + +public enum AwsServiceEnum { - public enum AwsServiceEnum - { - ApiGateway, - ApiGatewayV2, - Kinesis, - DynamoDb, - DynamoDbStreams, - ElasticSearch, - S3, - Firehose, - Lambda, - Sns, - Sqs, - Redshift, - RedshiftData, - Es, - Ses, - Route53, - CloudFormation, - CloudWatch, - Ssm, - SecretsManager, - StepFunctions, - Logs, - Events, - Elb, - Iot, - IoTAnalytics, - IoTEvents, - IoTEventsData, - IoTWireless, - IoTDataPlane, - IoTJobsDataPlane, - CognitoIdp, - CognitoIdentity, - Sts, - Iam, - Rds, - RdsData, - CloudSearch, - Swf, - Ec2, - ElastiCache, - Kms, - Emr, - Ecs, - Eks, - XRay, - ElasticBeanstalk, - AppSync, - CloudFront, - Athena, - Glue, - SageMaker, - SageMakerRuntime, - Ecr, - Qldb, - QldbSession, - CloudTrail, - Glacier, - Batch, - Organizations, - AutoScaling, - MediaStore, - MediaStoreData, - Transfer, - Acm, - CodeCommit, - KinesisAnalytics, - Amplify, - ApplicationAutoscaling, - Kafka, - ApiGatewayManagementApi, - TimeStreamQuery, - TimeStreamWrite, - S3Control, - ElbV2, - Support, - Neptune, - DocDb, - ServiceDiscovery, - ServerlessApplicationRepository, - AppConfig, - CostExplorer, - MediaConvert, - ResourceGroupsTaggingApi, - ResourceGroups, - Efs, - Backup, - LakeFormation, - Waf, - WafV2, - ConfigService - } + ApiGateway, + ApiGatewayV2, + Kinesis, + DynamoDb, + DynamoDbStreams, + ElasticSearch, + S3, + Firehose, + Lambda, + Sns, + Sqs, + Redshift, + RedshiftData, + Es, + Ses, + Route53, + CloudFormation, + CloudWatch, + Ssm, + SecretsManager, + StepFunctions, + Logs, + Events, + Elb, + Iot, + IoTAnalytics, + IoTEvents, + IoTEventsData, + IoTWireless, + IoTDataPlane, + IoTJobsDataPlane, + CognitoIdp, + CognitoIdentity, + Sts, + Iam, + Rds, + RdsData, + CloudSearch, + Swf, + Ec2, + ElastiCache, + Kms, + Emr, + Ecs, + Eks, + XRay, + ElasticBeanstalk, + AppSync, + CloudFront, + Athena, + Glue, + SageMaker, + SageMakerRuntime, + Ecr, + Qldb, + QldbSession, + CloudTrail, + Glacier, + Batch, + Organizations, + AutoScaling, + MediaStore, + MediaStoreData, + Transfer, + Acm, + CodeCommit, + KinesisAnalytics, + Amplify, + ApplicationAutoscaling, + Kafka, + ApiGatewayManagementApi, + TimeStreamQuery, + TimeStreamWrite, + S3Control, + ElbV2, + Support, + Neptune, + DocDb, + ServiceDiscovery, + ServerlessApplicationRepository, + AppConfig, + CostExplorer, + MediaConvert, + ResourceGroupsTaggingApi, + ResourceGroups, + Efs, + Backup, + LakeFormation, + Waf, + WafV2, + ConfigService } \ No newline at end of file diff --git a/src/LocalStack.Client/GlobalUsings.cs b/src/LocalStack.Client/GlobalUsings.cs new file mode 100644 index 0000000..1f98f27 --- /dev/null +++ b/src/LocalStack.Client/GlobalUsings.cs @@ -0,0 +1,30 @@ +global using System; +global using System.Collections.Generic; +global using System.Diagnostics.CodeAnalysis; +global using System.Linq; +global using System.Reflection; + +global using Amazon.Runtime; +global using Amazon.Runtime.Internal; + +global using LocalStack.Client.Contracts; +global using LocalStack.Client.Enums; +global using LocalStack.Client.Models; +global using LocalStack.Client.Options; +global using LocalStack.Client.Utils; + + +#if NETSTANDARD || NET461 +namespace System.Runtime.CompilerServices +{ + using System.ComponentModel; + /// + /// Reserved to be used by the compiler for tracking metadata. + /// This class should not be used by developers in source code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class IsExternalInit + { + } +} +#endif \ No newline at end of file diff --git a/src/LocalStack.Client/LocalStack.Client.csproj b/src/LocalStack.Client/LocalStack.Client.csproj index ef66b76..0011265 100644 --- a/src/LocalStack.Client/LocalStack.Client.csproj +++ b/src/LocalStack.Client/LocalStack.Client.csproj @@ -1,42 +1,44 @@  - - netstandard2.0;net461;net5.0;net6.0 - true - LocalStack.Client - LocalStack.Client - latest - - LocalStack.NET Client - - This is an easy-to-use .NET client for LocalStack. The client library provides a thin wrapper around aws-sdk-net which automatically configures the target endpoints to use LocalStack for your local cloud application development. - - aws-sdk, localstack, client-library, dotnet, dotnet-core - LICENSE.txt - - - - - - - - - - - - - - - - - - Always - - - - - - - + + netstandard2.0;net461;net5.0;net6.0 + true + LocalStack.Client + LocalStack.Client + latest + + LocalStack.NET Client + + This is an easy-to-use .NET client for LocalStack. The client library provides a thin wrapper around aws-sdk-net which automatically configures the target endpoints to use LocalStack for your local cloud application development. + + aws-sdk, localstack, client-library, dotnet, dotnet-core + LICENSE.txt + + + + + + + + + + + + + + + + + + + Always + + + + + + + NET461 + \ No newline at end of file diff --git a/src/LocalStack.Client/Models/AwsServiceEndpoint.cs b/src/LocalStack.Client/Models/AwsServiceEndpoint.cs index e69b14c..3b516e9 100644 --- a/src/LocalStack.Client/Models/AwsServiceEndpoint.cs +++ b/src/LocalStack.Client/Models/AwsServiceEndpoint.cs @@ -1,34 +1,9 @@ -using LocalStack.Client.Enums; +namespace LocalStack.Client.Models; -namespace LocalStack.Client.Models +public record AwsServiceEndpoint(string ServiceId, string CliName, AwsServiceEnum AwsServiceEnum, int Port, string Host, string ServiceUrl) { - public class AwsServiceEndpoint + public override string ToString() { - public AwsServiceEndpoint(string serviceId, string cliName, AwsServiceEnum @enum, int port, string host, string serviceUrl) - { - ServiceId = serviceId; - CliName = cliName; - AwsServiceEnum = @enum; - Port = port; - Host = host; - ServiceUrl = serviceUrl; - } - - public string ServiceId { get; } - - public string CliName { get; } - - public AwsServiceEnum AwsServiceEnum { get; } - - public int Port { get; } - - public string Host { get; } - - public string ServiceUrl { get; } - - public override string ToString() - { - return $"{ServiceId} - {ServiceUrl}"; - } + return $"{ServiceId} - {ServiceUrl}"; } } \ No newline at end of file diff --git a/src/LocalStack.Client/Models/Constants.cs b/src/LocalStack.Client/Models/Constants.cs index c549d70..c94c1bb 100644 --- a/src/LocalStack.Client/Models/Constants.cs +++ b/src/LocalStack.Client/Models/Constants.cs @@ -1,21 +1,20 @@ -namespace LocalStack.Client.Models +namespace LocalStack.Client.Models; + +public static class Constants { - public static class Constants - { - public const string LocalStackHost = "localhost"; + public const string LocalStackHost = "localhost"; - public const bool UseSsl = false; + public const bool UseSsl = false; - public const bool UseLegacyPorts = false; + public const bool UseLegacyPorts = false; - public const int EdgePort = 4566; + public const int EdgePort = 4566; - public const string AwsAccessKeyId = "accessKey"; + public const string AwsAccessKeyId = "accessKey"; - public const string AwsAccessKey = "secretKey"; + public const string AwsAccessKey = "secretKey"; - public const string AwsSessionToken = "token"; + public const string AwsSessionToken = "token"; - public const string RegionName = "us-east-1"; - } -} + public const string RegionName = "us-east-1"; +} \ No newline at end of file diff --git a/src/LocalStack.Client/Options/ConfigOptions.cs b/src/LocalStack.Client/Options/ConfigOptions.cs index 57c0a99..a4d10b5 100644 --- a/src/LocalStack.Client/Options/ConfigOptions.cs +++ b/src/LocalStack.Client/Options/ConfigOptions.cs @@ -1,32 +1,28 @@ -using LocalStack.Client.Contracts; -using LocalStack.Client.Models; +namespace LocalStack.Client.Options; -namespace LocalStack.Client.Options +public class ConfigOptions : IConfigOptions { - public class ConfigOptions : IConfigOptions + public ConfigOptions() { - public ConfigOptions() - { - } + } - public ConfigOptions( - string localStackHost = Constants.LocalStackHost, - bool useSsl = Constants.UseSsl, - bool useLegacyPorts = Constants.UseLegacyPorts, - int edgePort = Constants.EdgePort) - { - LocalStackHost = localStackHost; - UseSsl = useSsl; - UseLegacyPorts = useLegacyPorts; - EdgePort = edgePort; - } + public ConfigOptions( + string localStackHost = Constants.LocalStackHost, + bool useSsl = Constants.UseSsl, + bool useLegacyPorts = Constants.UseLegacyPorts, + int edgePort = Constants.EdgePort) + { + LocalStackHost = localStackHost; + UseSsl = useSsl; + UseLegacyPorts = useLegacyPorts; + EdgePort = edgePort; + } - public string LocalStackHost { get; private set; } = Constants.LocalStackHost; + public string LocalStackHost { get; private set; } = Constants.LocalStackHost; - public bool UseSsl { get; private set; } = Constants.UseSsl; + public bool UseSsl { get; private set; } = Constants.UseSsl; - public bool UseLegacyPorts { get; private set; } = Constants.UseLegacyPorts; + public bool UseLegacyPorts { get; private set; } = Constants.UseLegacyPorts; - public int EdgePort { get; private set; } = Constants.EdgePort; - } + public int EdgePort { get; private set; } = Constants.EdgePort; } \ No newline at end of file diff --git a/src/LocalStack.Client/Options/LocalStackOptions.cs b/src/LocalStack.Client/Options/LocalStackOptions.cs index 2102f0f..146df6a 100644 --- a/src/LocalStack.Client/Options/LocalStackOptions.cs +++ b/src/LocalStack.Client/Options/LocalStackOptions.cs @@ -1,24 +1,21 @@ -using LocalStack.Client.Contracts; +namespace LocalStack.Client.Options; -namespace LocalStack.Client.Options +public class LocalStackOptions : ILocalStackOptions { - public class LocalStackOptions : ILocalStackOptions + public LocalStackOptions() { - public LocalStackOptions() - { - } + } - public LocalStackOptions(bool useLocalStack, SessionOptions sessionOptions, ConfigOptions configOptions) - { - UseLocalStack = useLocalStack; - Session = sessionOptions; - Config = configOptions; - } + public LocalStackOptions(bool useLocalStack, SessionOptions sessionOptions, ConfigOptions configOptions) + { + UseLocalStack = useLocalStack; + Session = sessionOptions; + Config = configOptions; + } - public bool UseLocalStack { get; private set; } = false; + public bool UseLocalStack { get; private set; } = false; - public SessionOptions Session { get; private set; } = new SessionOptions(); + public SessionOptions Session { get; private set; } = new SessionOptions(); - public ConfigOptions Config { get; private set; } = new ConfigOptions(); - } -} + public ConfigOptions Config { get; private set; } = new ConfigOptions(); +} \ No newline at end of file diff --git a/src/LocalStack.Client/Options/SessionOptions.cs b/src/LocalStack.Client/Options/SessionOptions.cs index 484e741..af09fc5 100644 --- a/src/LocalStack.Client/Options/SessionOptions.cs +++ b/src/LocalStack.Client/Options/SessionOptions.cs @@ -1,31 +1,27 @@ -using LocalStack.Client.Contracts; -using LocalStack.Client.Models; +namespace LocalStack.Client.Options; -namespace LocalStack.Client.Options +public class SessionOptions : ISessionOptions { - public class SessionOptions : ISessionOptions + public SessionOptions() { - public SessionOptions() - { - } + } - public SessionOptions(string awsAccessKeyId = Constants.AwsAccessKeyId, - string awsAccessKey = Constants.AwsAccessKey, - string awsSessionToken = Constants.AwsSessionToken, - string regionName = Constants.RegionName) - { - AwsAccessKeyId = awsAccessKeyId; - AwsAccessKey = awsAccessKey; - AwsSessionToken = awsSessionToken; - RegionName = regionName; - } + public SessionOptions(string awsAccessKeyId = Constants.AwsAccessKeyId, + string awsAccessKey = Constants.AwsAccessKey, + string awsSessionToken = Constants.AwsSessionToken, + string regionName = Constants.RegionName) + { + AwsAccessKeyId = awsAccessKeyId; + AwsAccessKey = awsAccessKey; + AwsSessionToken = awsSessionToken; + RegionName = regionName; + } - public string AwsAccessKeyId { get; private set; } = Constants.AwsAccessKeyId; + public string AwsAccessKeyId { get; } = Constants.AwsAccessKeyId; - public string AwsAccessKey { get; private set; } = Constants.AwsAccessKey; + public string AwsAccessKey { get; } = Constants.AwsAccessKey; - public string AwsSessionToken { get; private set; } = Constants.AwsSessionToken; + public string AwsSessionToken { get; } = Constants.AwsSessionToken; - public string RegionName { get; private set; } = Constants.RegionName; - } + public string RegionName { get; } = Constants.RegionName; } \ No newline at end of file diff --git a/src/LocalStack.Client/Session.cs b/src/LocalStack.Client/Session.cs index 03f5256..63432bf 100644 --- a/src/LocalStack.Client/Session.cs +++ b/src/LocalStack.Client/Session.cs @@ -1,96 +1,86 @@ -using Amazon.Runtime; -using Amazon.Runtime.Internal; +namespace LocalStack.Client; -using LocalStack.Client.Contracts; -using LocalStack.Client.Models; +public class Session : ISession +{ + private readonly IConfig _config; + private readonly ISessionOptions _sessionOptions; + private readonly ISessionReflection _sessionReflection; -using System; -using System.Reflection; + public Session(ISessionOptions sessionOptions, IConfig config, ISessionReflection sessionReflection) + { + _sessionOptions = sessionOptions; + _config = config; + _sessionReflection = sessionReflection; + } -namespace LocalStack.Client -{ - public class Session : ISession + public TClient CreateClientByImplementation() where TClient : AmazonServiceClient { - private readonly IConfig _config; - private readonly ISessionOptions _sessionOptions; - private readonly ISessionReflection _sessionReflection; + Type clientType = typeof(TClient); - public Session(ISessionOptions sessionOptions, IConfig config, ISessionReflection sessionReflection) - { - _sessionOptions = sessionOptions; - _config = config; - _sessionReflection = sessionReflection; - } + return (TClient) CreateClientByImplementation(clientType); + } - public TClient CreateClientByImplementation() where TClient : AmazonServiceClient - { - Type clientType = typeof(TClient); + public AmazonServiceClient CreateClientByImplementation(Type implType) + { + IServiceMetadata serviceMetadata = _sessionReflection.ExtractServiceMetadata(implType); + AwsServiceEndpoint awsServiceEndpoint = _config.GetAwsServiceEndpoint(serviceMetadata.ServiceId) ?? + throw new InvalidOperationException($"{serviceMetadata.ServiceId} is not supported by this mock session."); - return (TClient) CreateClientByImplementation(clientType); - } + AWSCredentials awsCredentials = new SessionAWSCredentials(_sessionOptions.AwsAccessKeyId, _sessionOptions.AwsAccessKey, _sessionOptions.AwsSessionToken); + ClientConfig clientConfig = _sessionReflection.CreateClientConfig(implType); - public AmazonServiceClient CreateClientByImplementation(Type implType) - { - IServiceMetadata serviceMetadata = _sessionReflection.ExtractServiceMetadata(implType); - AwsServiceEndpoint awsServiceEndpoint = _config.GetAwsServiceEndpoint(serviceMetadata.ServiceId) ?? - throw new InvalidOperationException($"{serviceMetadata.ServiceId} is not supported by this mock session."); + clientConfig.ServiceURL = awsServiceEndpoint.ServiceUrl; + clientConfig.UseHttp = true; + _sessionReflection.SetForcePathStyle(clientConfig); + clientConfig.ProxyHost = awsServiceEndpoint.Host; + clientConfig.ProxyPort = awsServiceEndpoint.Port; - AWSCredentials awsCredentials = new SessionAWSCredentials(_sessionOptions.AwsAccessKeyId, _sessionOptions.AwsAccessKey, _sessionOptions.AwsSessionToken); - ClientConfig clientConfig = _sessionReflection.CreateClientConfig(implType); + var clientInstance = (AmazonServiceClient) Activator.CreateInstance(implType, awsCredentials, clientConfig); - clientConfig.ServiceURL = awsServiceEndpoint.ServiceUrl; - clientConfig.UseHttp = true; - _sessionReflection.SetForcePathStyle(clientConfig); - clientConfig.ProxyHost = awsServiceEndpoint.Host; - clientConfig.ProxyPort = awsServiceEndpoint.Port; + return clientInstance; + } - var clientInstance = (AmazonServiceClient) Activator.CreateInstance(implType, awsCredentials, clientConfig); + public AmazonServiceClient CreateClientByInterface() where TClient: IAmazonService + { + Type serviceInterfaceType = typeof(TClient); - return clientInstance; - } + return (AmazonServiceClient) CreateClientByInterface(serviceInterfaceType); + } - public AmazonServiceClient CreateClientByInterface() where TClient: IAmazonService + public AmazonServiceClient CreateClientByInterface(Type serviceInterfaceType) + { + var clientTypeName = $"{serviceInterfaceType.Namespace}.{serviceInterfaceType.Name.Substring(1)}Client"; + Type clientType = serviceInterfaceType.GetTypeInfo().Assembly.GetType(clientTypeName); + + if (clientType == null) { - Type serviceInterfaceType = typeof(TClient); - - return (AmazonServiceClient) CreateClientByInterface(serviceInterfaceType); + throw new AmazonClientException($"Failed to find service client {clientTypeName} which implements {serviceInterfaceType.FullName}."); } - public AmazonServiceClient CreateClientByInterface(Type serviceInterfaceType) - { - var clientTypeName = $"{serviceInterfaceType.Namespace}.{serviceInterfaceType.Name.Substring(1)}Client"; - Type clientType = serviceInterfaceType.GetTypeInfo().Assembly.GetType(clientTypeName); - - if (clientType == null) - { - throw new AmazonClientException($"Failed to find service client {clientTypeName} which implements {serviceInterfaceType.FullName}."); - } - - IServiceMetadata serviceMetadata = _sessionReflection.ExtractServiceMetadata(clientType); + IServiceMetadata serviceMetadata = _sessionReflection.ExtractServiceMetadata(clientType); - AwsServiceEndpoint awsServiceEndpoint = _config.GetAwsServiceEndpoint(serviceMetadata.ServiceId) ?? - throw new InvalidOperationException($"{serviceMetadata.ServiceId} is not supported by this mock session."); + AwsServiceEndpoint awsServiceEndpoint = _config.GetAwsServiceEndpoint(serviceMetadata.ServiceId) ?? + throw new InvalidOperationException($"{serviceMetadata.ServiceId} is not supported by this mock session."); - AWSCredentials awsCredentials = new SessionAWSCredentials(_sessionOptions.AwsAccessKeyId, _sessionOptions.AwsAccessKey, _sessionOptions.AwsSessionToken); + AWSCredentials awsCredentials = new SessionAWSCredentials(_sessionOptions.AwsAccessKeyId, _sessionOptions.AwsAccessKey, _sessionOptions.AwsSessionToken); - ClientConfig clientConfig = _sessionReflection.CreateClientConfig(clientType); + ClientConfig clientConfig = _sessionReflection.CreateClientConfig(clientType); - clientConfig.ServiceURL = awsServiceEndpoint.ServiceUrl; - clientConfig.UseHttp = true; - _sessionReflection.SetForcePathStyle(clientConfig); - clientConfig.ProxyHost = awsServiceEndpoint.Host; - clientConfig.ProxyPort = awsServiceEndpoint.Port; + clientConfig.ServiceURL = awsServiceEndpoint.ServiceUrl; + clientConfig.UseHttp = true; + _sessionReflection.SetForcePathStyle(clientConfig); + clientConfig.ProxyHost = awsServiceEndpoint.Host; + clientConfig.ProxyPort = awsServiceEndpoint.Port; - ConstructorInfo constructor = clientType.GetConstructor(new Type[] { typeof(AWSCredentials), clientConfig.GetType() }); + ConstructorInfo constructor = clientType.GetConstructor(new Type[] { typeof(AWSCredentials), clientConfig.GetType() }); - if (constructor == null) - { - throw new AmazonClientException($"Service client {clientTypeName} missing a constructor with parameters AWSCredentials and {clientConfig.GetType().FullName}."); - } + if (constructor == null) + { + throw new AmazonClientException($"Service client {clientTypeName} missing a constructor with parameters AWSCredentials and {clientConfig.GetType().FullName}."); + } - var client = (AmazonServiceClient) constructor.Invoke(new object[] { awsCredentials, clientConfig }); + var client = (AmazonServiceClient) constructor.Invoke(new object[] { awsCredentials, clientConfig }); - return client; - } + return client; } } \ No newline at end of file diff --git a/src/LocalStack.Client/SessionStandalone.cs b/src/LocalStack.Client/SessionStandalone.cs index 19ffa4e..bb25bb3 100644 --- a/src/LocalStack.Client/SessionStandalone.cs +++ b/src/LocalStack.Client/SessionStandalone.cs @@ -1,44 +1,39 @@ -using LocalStack.Client.Contracts; -using LocalStack.Client.Options; -using LocalStack.Client.Utils; +namespace LocalStack.Client; -namespace LocalStack.Client +public class SessionStandalone : ISessionStandalone { - public class SessionStandalone : ISessionStandalone - { - private ISessionOptions _sessionOptions; - private IConfigOptions _configOptions; + private ISessionOptions _sessionOptions; + private IConfigOptions _configOptions; - private SessionStandalone() - { - } + private SessionStandalone() + { + } - public ISessionStandalone WithSessionOptions(ISessionOptions sessionOptions) - { - _sessionOptions = sessionOptions; + public ISessionStandalone WithSessionOptions(ISessionOptions sessionOptions) + { + _sessionOptions = sessionOptions; - return this; - } + return this; + } - public ISessionStandalone WithConfigurationOptions(IConfigOptions configOptions) - { - _configOptions = configOptions; + public ISessionStandalone WithConfigurationOptions(IConfigOptions configOptions) + { + _configOptions = configOptions; - return this; - } + return this; + } - public ISession Create() - { - ISessionOptions sessionOptions = _sessionOptions ?? new SessionOptions(); - IConfig config = new Config(_configOptions ?? new ConfigOptions()); - ISessionReflection sessionReflection = new SessionReflection(); + public ISession Create() + { + ISessionOptions sessionOptions = _sessionOptions ?? new SessionOptions(); + IConfig config = new Config(_configOptions ?? new ConfigOptions()); + ISessionReflection sessionReflection = new SessionReflection(); - return new Session(sessionOptions, config, sessionReflection); - } + return new Session(sessionOptions, config, sessionReflection); + } - public static ISessionStandalone Init() - { - return new SessionStandalone(); - } + public static ISessionStandalone Init() + { + return new SessionStandalone(); } } \ No newline at end of file diff --git a/src/LocalStack.Client/Utils/SessionReflection.cs b/src/LocalStack.Client/Utils/SessionReflection.cs index b239826..dae3865 100644 --- a/src/LocalStack.Client/Utils/SessionReflection.cs +++ b/src/LocalStack.Client/Utils/SessionReflection.cs @@ -1,84 +1,74 @@ -using Amazon.Runtime; -using Amazon.Runtime.Internal; +namespace LocalStack.Client.Utils; -using LocalStack.Client.Contracts; - -using System; -using System.Linq; -using System.Reflection; - -namespace LocalStack.Client.Utils +public class SessionReflection : ISessionReflection { - public class SessionReflection : ISessionReflection + public IServiceMetadata ExtractServiceMetadata() where TClient : AmazonServiceClient { - public IServiceMetadata ExtractServiceMetadata() where TClient : AmazonServiceClient - { - Type clientType = typeof(TClient); + Type clientType = typeof(TClient); - return ExtractServiceMetadata(clientType); - } + return ExtractServiceMetadata(clientType); + } - public IServiceMetadata ExtractServiceMetadata(Type clientType) - { - FieldInfo serviceMetadataField = clientType.GetField("serviceMetadata", BindingFlags.Static | BindingFlags.NonPublic) ?? - throw new InvalidOperationException($"Invalid service type {clientType}"); + public IServiceMetadata ExtractServiceMetadata(Type clientType) + { + FieldInfo serviceMetadataField = clientType.GetField("serviceMetadata", BindingFlags.Static | BindingFlags.NonPublic) ?? + throw new InvalidOperationException($"Invalid service type {clientType}"); - var serviceMetadata = (IServiceMetadata) serviceMetadataField.GetValue(null); + var serviceMetadata = (IServiceMetadata) serviceMetadataField.GetValue(null); - return serviceMetadata; - } + return serviceMetadata; + } - public ClientConfig CreateClientConfig() where TClient : AmazonServiceClient - { - Type clientType = typeof(TClient); + public ClientConfig CreateClientConfig() where TClient : AmazonServiceClient + { + Type clientType = typeof(TClient); - return CreateClientConfig(clientType); - } + return CreateClientConfig(clientType); + } - public ClientConfig CreateClientConfig(Type clientType) - { - ConstructorInfo clientConstructorInfo = FindConstructorWithCredentialsAndClientConfig(clientType); - ParameterInfo clientConfigParam = clientConstructorInfo.GetParameters()[1]; + public ClientConfig CreateClientConfig(Type clientType) + { + ConstructorInfo clientConstructorInfo = FindConstructorWithCredentialsAndClientConfig(clientType); + ParameterInfo clientConfigParam = clientConstructorInfo.GetParameters()[1]; + + return (ClientConfig) Activator.CreateInstance(clientConfigParam.ParameterType); + } - return (ClientConfig) Activator.CreateInstance(clientConfigParam.ParameterType); - } + public bool SetForcePathStyle(ClientConfig clientConfig, bool value = true) + { + PropertyInfo forcePathStyleProperty = clientConfig.GetType().GetProperty("ForcePathStyle", BindingFlags.Public | BindingFlags.Instance); - public bool SetForcePathStyle(ClientConfig clientConfig, bool value = true) + if (forcePathStyleProperty == null) { - PropertyInfo forcePathStyleProperty = clientConfig.GetType().GetProperty("ForcePathStyle", BindingFlags.Public | BindingFlags.Instance); + return false; + } - if (forcePathStyleProperty == null) - { - return false; - } + forcePathStyleProperty.SetValue(clientConfig, value); - forcePathStyleProperty.SetValue(clientConfig, value); + return true; + } - return true; - } + private static ConstructorInfo FindConstructorWithCredentialsAndClientConfig(Type clientType) + { + return clientType.GetConstructors(BindingFlags.Instance | BindingFlags.Public) + .Where(info => + { + ParameterInfo[] parameterInfos = info.GetParameters(); - private static ConstructorInfo FindConstructorWithCredentialsAndClientConfig(Type clientType) - { - return clientType.GetConstructors(BindingFlags.Instance | BindingFlags.Public) - .Where(info => + if (parameterInfos.Length != 2) { - ParameterInfo[] parameterInfos = info.GetParameters(); - - if (parameterInfos.Length != 2) - { - return false; - } - - ParameterInfo credentialsParameter = parameterInfos[0]; - ParameterInfo clientConfigParameter = parameterInfos[1]; - - return credentialsParameter.Name == "credentials" && - credentialsParameter.ParameterType == typeof(AWSCredentials) && - clientConfigParameter.Name == "clientConfig" && - clientConfigParameter.ParameterType.IsSubclassOf(typeof(ClientConfig)); - }) - .Single(); - } + return false; + } + + ParameterInfo credentialsParameter = parameterInfos[0]; + ParameterInfo clientConfigParameter = parameterInfos[1]; + + return credentialsParameter.Name == "credentials" && + credentialsParameter.ParameterType == typeof(AWSCredentials) && + clientConfigParameter.Name == "clientConfig" && + clientConfigParameter.ParameterType.IsSubclassOf(typeof(ClientConfig)); + }) + .Single(); } } \ No newline at end of file diff --git a/tests/LocalStack.Client.Tests/Mocks/MockServiceClients/MockAwsServiceEndpoint.cs b/tests/LocalStack.Client.Tests/Mocks/MockServiceClients/MockAwsServiceEndpoint.cs index fc7afa0..b60b328 100644 --- a/tests/LocalStack.Client.Tests/Mocks/MockServiceClients/MockAwsServiceEndpoint.cs +++ b/tests/LocalStack.Client.Tests/Mocks/MockServiceClients/MockAwsServiceEndpoint.cs @@ -1,8 +1,5 @@ namespace LocalStack.Client.Tests.Mocks.MockServiceClients; -public class MockAwsServiceEndpoint : AwsServiceEndpoint -{ - public MockAwsServiceEndpoint() : base(MockServiceMetadata.MockServiceId, "mockService", AwsServiceEnum.ApiGateway, 1234, "localhost", "http://localhost:1234") - { - } -} +public record MockAwsServiceEndpoint() + : AwsServiceEndpoint(MockServiceMetadata.MockServiceId, "mockService", AwsServiceEnum.ApiGateway, 1234, "localhost", "http://localhost:1234"); + From e7f1351b8f4176c2d45aa5cb51916829d540ada2 Mon Sep 17 00:00:00 2001 From: "deniz_irgin@yahoo.com" Date: Thu, 18 Nov 2021 19:28:26 +0300 Subject: [PATCH 08/24] fix SessionOptions bug --- src/LocalStack.Client/Options/SessionOptions.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/LocalStack.Client/Options/SessionOptions.cs b/src/LocalStack.Client/Options/SessionOptions.cs index af09fc5..d3b9637 100644 --- a/src/LocalStack.Client/Options/SessionOptions.cs +++ b/src/LocalStack.Client/Options/SessionOptions.cs @@ -17,11 +17,11 @@ public SessionOptions(string awsAccessKeyId = Constants.AwsAccessKeyId, RegionName = regionName; } - public string AwsAccessKeyId { get; } = Constants.AwsAccessKeyId; + public string AwsAccessKeyId { get; private set; } = Constants.AwsAccessKeyId; - public string AwsAccessKey { get; } = Constants.AwsAccessKey; + public string AwsAccessKey { get; private set; } = Constants.AwsAccessKey; - public string AwsSessionToken { get; } = Constants.AwsSessionToken; + public string AwsSessionToken { get; private set; } = Constants.AwsSessionToken; - public string RegionName { get; } = Constants.RegionName; + public string RegionName { get; private set; } = Constants.RegionName; } \ No newline at end of file From be15a060d09190370edc1f935bb18776b5431a42 Mon Sep 17 00:00:00 2001 From: "deniz_irgin@yahoo.com" Date: Thu, 18 Nov 2021 21:58:01 +0300 Subject: [PATCH 09/24] test testspace --- .github/workflows/build-ubuntu.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 5b2f06b..fd36cb9 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -28,6 +28,11 @@ jobs: - name: Install NuGet uses: NuGet/setup-nuget@v1.0.5 + - name: + uses: testspace-com/setup-testspace@v1 + with: + domain: ${{github.repository_owner}} + - name: Install .NET Core 3.1 uses: actions/setup-dotnet@v1 with: @@ -55,3 +60,8 @@ jobs: with: name: test-results-ubuntu path: "**/*.trx" + + - name: Push result to Testspace server + run: | + testspace **/*.trx + if: always() From 3d672a3aeed95fc633b51367ed6afa520a35efe1 Mon Sep 17 00:00:00 2001 From: "deniz_irgin@yahoo.com" Date: Thu, 18 Nov 2021 22:00:36 +0300 Subject: [PATCH 10/24] fix yml syntax error --- .github/workflows/build-ubuntu.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index fd36cb9..4105353 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -28,7 +28,7 @@ jobs: - name: Install NuGet uses: NuGet/setup-nuget@v1.0.5 - - name: + - name: Setup Testspace uses: testspace-com/setup-testspace@v1 with: domain: ${{github.repository_owner}} From 113ea349337848b0875d6f765d2d8bbbb2352e4b Mon Sep 17 00:00:00 2001 From: "deniz_irgin@yahoo.com" Date: Thu, 18 Nov 2021 22:02:08 +0300 Subject: [PATCH 11/24] fix yml syntax error again --- .github/workflows/build-ubuntu.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 4105353..2200e87 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -30,8 +30,8 @@ jobs: - name: Setup Testspace uses: testspace-com/setup-testspace@v1 - with: - domain: ${{github.repository_owner}} + with: + domain: ${{github.repository_owner}} - name: Install .NET Core 3.1 uses: actions/setup-dotnet@v1 From 70749324fb268c350c1277d124de1a0bb99ac078 Mon Sep 17 00:00:00 2001 From: "deniz_irgin@yahoo.com" Date: Fri, 19 Nov 2021 11:37:07 +0300 Subject: [PATCH 12/24] add testreport to pipelines --- .github/workflows/build-macos.yml | 15 +- .github/workflows/build-ubuntu.yml | 9 +- .github/workflows/build-windows.yml | 15 +- .github/workflows/test-report.yml | 16 -- build/LocalStack.Build/BuildContext.cs | 2 - build/LocalStack.Build/Program.cs | 6 +- ...LocalStack.Client.Integration.Tests.csproj | 222 +++++++++--------- .../LocalStack.Client.Tests.csproj | 51 ++-- 8 files changed, 164 insertions(+), 172 deletions(-) delete mode 100644 .github/workflows/test-report.yml diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index bb820e1..4516dda 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -28,6 +28,11 @@ jobs: - 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 Core 3.1 uses: actions/setup-dotnet@v1 with: @@ -49,9 +54,7 @@ jobs: - name: Run Tests run: ./build.sh --target tests --exclusive - - name: Upload Test Results - uses: actions/upload-artifact@v2 - if: success() || failure() - with: - name: test-results-macos - path: "**/*.trx" + - name: Push result to Testspace server + run: | + testspace [macos]**/*.trx + if: always() diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 2200e87..09ba32a 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -54,14 +54,7 @@ jobs: - name: Run Tests run: ./build.sh --target tests --skipFunctionalTest false --exclusive - - name: Upload Test Results - uses: actions/upload-artifact@v2 - if: success() || failure() - with: - name: test-results-ubuntu - path: "**/*.trx" - - name: Push result to Testspace server run: | - testspace **/*.trx + testspace [linux]**/*.trx if: always() diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 582bda8..494bc1a 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -22,6 +22,11 @@ jobs: - name: Checkout uses: actions/checkout@v2 + - name: Setup Testspace + uses: testspace-com/setup-testspace@v1 + with: + domain: ${{github.repository_owner}} + - name: Install .NET Core 3.1 uses: actions/setup-dotnet@v1 with: @@ -43,9 +48,7 @@ jobs: - name: Run Tests run: .\build.ps1 --target tests --exclusive - - name: Upload Test Results - uses: actions/upload-artifact@v2 - if: success() || failure() - with: - name: test-results-windows - path: "**/*.trx" + - name: Push result to Testspace server + run: | + testspace [windows]**/*.trx + if: always() diff --git a/.github/workflows/test-report.yml b/.github/workflows/test-report.yml deleted file mode 100644 index 550cfb9..0000000 --- a/.github/workflows/test-report.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: "test-report" -on: - workflow_run: - workflows: ["build-ubuntu"] - types: - - completed -jobs: - report: - runs-on: ubuntu-20.04 - steps: - - uses: dorny/test-reporter@v1 - with: - artifact: test-results-ubuntu - name: .NET Tests - path: "**/*.trx" - reporter: dotnet-trx diff --git a/build/LocalStack.Build/BuildContext.cs b/build/LocalStack.Build/BuildContext.cs index 1239d00..157d62e 100644 --- a/build/LocalStack.Build/BuildContext.cs +++ b/build/LocalStack.Build/BuildContext.cs @@ -73,8 +73,6 @@ public BuildContext(ICakeContext context) : base(context) public ConvertableDirectoryPath ArtifactOutput { get; } - public ConvertableDirectoryPath ArtifactExtensionsOutput { get; } - public ConvertableDirectoryPath LocalStackClientFolder { get; } public ConvertableDirectoryPath LocalStackClientExtFolder { get; } diff --git a/build/LocalStack.Build/Program.cs b/build/LocalStack.Build/Program.cs index 1a5ccae..b2239b7 100644 --- a/build/LocalStack.Build/Program.cs +++ b/build/LocalStack.Build/Program.cs @@ -60,7 +60,8 @@ public override void Run(BuildContext context) { NoRestore = !context.ForceRestore, NoBuild = !context.ForceBuild, - Configuration = context.BuildConfiguration + Configuration = context.BuildConfiguration, + Blame = true }; IEnumerable projMetadata = context.GetProjMetadata(); @@ -80,7 +81,7 @@ public override void Run(BuildContext context) continue; } - context.Warning($"Running {targetFramework.ToUpper()} tests for {testProj.AssemblyName}"); + context.Warning($"=============Running {targetFramework.ToUpper()} tests for {testProj.AssemblyName}============="); settings.Framework = targetFramework; if (context.IsRunningOnUnix() && targetFramework == "net461") @@ -93,6 +94,7 @@ public override void Run(BuildContext context) settings.ArgumentCustomization = args => args.Append($" --logger \"trx;LogFileName={testFilePrefix}_{testResults}\""); context.DotNetCoreTest(testProjectPath, settings); } + context.Warning("=============================================================="); } } } 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 ea9a3c7..09a0b96 100644 --- a/tests/LocalStack.Client.Integration.Tests/LocalStack.Client.Integration.Tests.csproj +++ b/tests/LocalStack.Client.Integration.Tests/LocalStack.Client.Integration.Tests.csproj @@ -1,115 +1,119 @@  - - net461;netcoreapp3.1;net5.0;net6.0 - true - latest - + + net461;netcoreapp3.1;net5.0;net6.0 + true + latest + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers - - - all - runtime; build; native; contentfiles; analyzers - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers + + - - - + + + + + + + \ No newline at end of file diff --git a/tests/LocalStack.Client.Tests/LocalStack.Client.Tests.csproj b/tests/LocalStack.Client.Tests/LocalStack.Client.Tests.csproj index 6477459..db5edab 100644 --- a/tests/LocalStack.Client.Tests/LocalStack.Client.Tests.csproj +++ b/tests/LocalStack.Client.Tests/LocalStack.Client.Tests.csproj @@ -1,30 +1,35 @@  - - net461;netcoreapp3.1;net5.0;net6.0 - true - latest - + + net461;netcoreapp3.1;net5.0;net6.0 + true + latest + - - - - - - - all - runtime; build; native; contentfiles; analyzers - - - all - runtime; build; native; contentfiles; analyzers - + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers + - - + + - - - + + + + + + + + \ No newline at end of file From cdd91c70aa87369eeeddf2e9b05968985ee28792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deniz=20=C4=B0rgin?= Date: Fri, 19 Nov 2021 11:53:43 +0300 Subject: [PATCH 13/24] Update README.md add test result badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a5d942b..183f12a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![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/) +![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/) [![Testspace tests (compact)](https://img.shields.io/testspace/tests/localstack-dotnet/localstack-dotnet:localstack-dotnet-client/master?compact_message)](https://localstack-dotnet.testspace.com/spaces/155695/result_sets) # LocalStack .Net Core and .Net Framework Client From 19f699bf2b60be24197b753a7f67569aaa3cf8da Mon Sep 17 00:00:00 2001 From: "deniz_irgin@yahoo.com" Date: Fri, 26 Nov 2021 09:01:51 +0300 Subject: [PATCH 14/24] fix region problem --- README.md | 14 +- .../GlobalUsings.cs | 4 + .../ServiceCollectionExtensions.cs | 2 - .../Contracts/ISessionReflection.cs | 2 + .../Exceptions/LocalStackClientException.cs | 43 +++++ src/LocalStack.Client/GlobalUsings.cs | 2 + .../Options/SessionOptions.cs | 4 +- src/LocalStack.Client/Session.cs | 26 ++- .../Utils/SessionReflection.cs | 5 + .../ServiceCollectionExtensionsTests.cs | 68 +++++++- .../AssertAmazonClient.cs | 17 +- .../CreateClientByImplementationTests.cs | 5 +- .../GlobalUsings.cs | 3 + tests/LocalStack.Client.Tests/GlobalUsings.cs | 2 + .../Mocks/MockSession.cs | 3 +- .../SessionTests/SessionOptionsTests.cs | 10 +- .../SessionTests/SessionReflectionTests.cs | 21 +++ .../SessionTests/SessionTests.cs | 157 +++++++++++++++++- 18 files changed, 352 insertions(+), 36 deletions(-) create mode 100644 src/LocalStack.Client/Exceptions/LocalStackClientException.cs diff --git a/README.md b/README.md index 183f12a..5bc8095 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ You can configure `LocalStack.Client` by using entries in the `appsettings.json` "AwsAccessKeyId": "my-AwsAccessKeyId", "AwsAccessKey": "my-AwsAccessKey", "AwsSessionToken": "my-AwsSessionToken", - "RegionName": "eu-central-1" + "RegionName": null // can be values like "eu-central-1", "us-east-1", "us-west-1" etc. }, "Config": { "LocalStackHost": "localhost", @@ -143,7 +143,11 @@ You can configure `LocalStack.Client` by using entries in the `appsettings.json` All the entries above are has shown with default values (except `UseLocalStack`, it's `false` by default). So the above entries do not need to be specified. -What is entered for the aws credential values ​​in the `Session` section does not matter for LocalStack. `RegionName` is important since LocalStack creates resources by spesified region. +What is entered for the aws credential values ​​in the `Session` section does not matter for LocalStack. + +`RegionName` is important since LocalStack creates resources by spesified region (LocalStack has full multi-region support after `v0.12.17`). By default `RegionName` is `null`. If no value is entered in the `RegionName` entry, the [AWSSDK.NET](https://aws.amazon.com/sdk-for-net/) will use the `us-east-1` region by default. + +Internally depends on whether you set `RegionName` or not, the values of [ServiceUrl](https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Core/Amazon.Runtime/ClientConfig.cs#L202) ve [RegionEndpoint](https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Core/Amazon.Runtime/ClientConfig.cs#L144) properties of the [ClientConfig](https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Core/Amazon.Runtime/ClientConfig.cs) will change. [RegionEndpoint](https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Core/Amazon.Runtime/ClientConfig.cs#L144) and [ServiceUrl](https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Core/Amazon.Runtime/ClientConfig.cs#L202) are mutually exclusive properties. Whichever property is set last will cause the other to automatically be reset to null. If a value set to `RegionName` entry, the LocalStack.NET AWS will set RegionEndpoint property of the ClientConfig. Because of LocalStack.NET sets the RegionEndpoint property after the ServiceUrl property, ServiceUrl will be set to null. `Config` section contains important entries for local development. Starting with LocalStack releases after `v0.11.5`, all services are now exposed via the edge service (port 4566) only! If you are using a version of LocalStack lower than v0.11.5, you should set `UseLegacyPorts` to `true`. Edge port can be set to any available port ([see LocalStack configuration section](https://github.com/localstack/localstack#configurations)). If you have made such a change in LocalStack's configuration, be sure to set the same port value to `EdgePort` in the `Config` section. For `LocalStackHost` and `UseSsl` entries, ​​corresponding to the [LocalStack configuration](https://github.com/localstack/localstack#configurations) should be used. @@ -196,7 +200,7 @@ If you do not want to use any DI library, you have to instantiate `SessionStanda * AwsAccessKeyId: accessKey (It doesn't matter to LocalStack) * AwsAccessKey: secretKey (It doesn't matter to LocalStack) * AwsSessionToken: token (It doesn't matter to LocalStack) -* RegionName: us-east-1 +* RegionName: null // can be values like "eu-central-1", "us-east-1", "us-west-1" etc. * ==== Custom Values ==== * var sessionOptions = new SessionOptions("someAwsAccessKeyId", "someAwsAccessKey", "someAwsSessionToken", "eu-central-"); */ @@ -244,7 +248,7 @@ var collection = new ServiceCollection(); * AwsAccessKeyId: accessKey (It doesn't matter to LocalStack) * AwsAccessKey: secretKey (It doesn't matter to LocalStack) * AwsSessionToken: token (It doesn't matter to LocalStack) -* RegionName: us-east-1 +* RegionName: null // can be values like "eu-central-1", "us-east-1", "us-west-1" etc. * ==== Custom Values ==== * var sessionOptions = new SessionOptions("someAwsAccessKeyId", "someAwsAccessKey", "someAwsSessionToken", "eu-central-"); */ @@ -299,7 +303,7 @@ collection.Configure(options => configuration.GetSection("Loc * AwsAccessKeyId: accessKey (It doesn't matter to LocalStack) * AwsAccessKey: secretKey (It doesn't matter to LocalStack) * AwsSessionToken: token (It doesn't matter to LocalStack) -* RegionName: us-east-1 +* RegionName: null // can be values like "eu-central-1", "us-east-1", "us-west-1" etc. */ collection.Configure(options => configuration.GetSection("LocalStack") .GetSection(nameof(LocalStackOptions.Session)) diff --git a/src/LocalStack.Client.Extensions/GlobalUsings.cs b/src/LocalStack.Client.Extensions/GlobalUsings.cs index 95a0629..5076202 100644 --- a/src/LocalStack.Client.Extensions/GlobalUsings.cs +++ b/src/LocalStack.Client.Extensions/GlobalUsings.cs @@ -1,11 +1,15 @@ global using System; global using System.Reflection; + global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.DependencyInjection.Extensions; global using Microsoft.Extensions.Options; + +global using Amazon; global using Amazon.Extensions.NETCore.Setup; global using Amazon.Runtime; + global using LocalStack.Client.Contracts; global using LocalStack.Client.Extensions.Contracts; global using LocalStack.Client.Options; diff --git a/src/LocalStack.Client.Extensions/ServiceCollectionExtensions.cs b/src/LocalStack.Client.Extensions/ServiceCollectionExtensions.cs index bdf28aa..55482d4 100644 --- a/src/LocalStack.Client.Extensions/ServiceCollectionExtensions.cs +++ b/src/LocalStack.Client.Extensions/ServiceCollectionExtensions.cs @@ -197,13 +197,11 @@ private static ServiceDescriptor GetServiceFactoryDescriptor(AWSOption if (localStackOptions.UseLocalStack) { var session = provider.GetRequiredService(); - serviceInstance = session.CreateClientByInterface(); } else { var clientFactory = provider.GetRequiredService(); - serviceInstance = clientFactory.CreateServiceClient(provider, options); } diff --git a/src/LocalStack.Client/Contracts/ISessionReflection.cs b/src/LocalStack.Client/Contracts/ISessionReflection.cs index 89fd0c1..64ab21b 100644 --- a/src/LocalStack.Client/Contracts/ISessionReflection.cs +++ b/src/LocalStack.Client/Contracts/ISessionReflection.cs @@ -11,4 +11,6 @@ public interface ISessionReflection ClientConfig CreateClientConfig(Type clientType); bool SetForcePathStyle(ClientConfig clientConfig, bool value = true); + + void SetClientRegion(AmazonServiceClient amazonServiceClient, string systemName); } \ No newline at end of file diff --git a/src/LocalStack.Client/Exceptions/LocalStackClientException.cs b/src/LocalStack.Client/Exceptions/LocalStackClientException.cs new file mode 100644 index 0000000..dd7b724 --- /dev/null +++ b/src/LocalStack.Client/Exceptions/LocalStackClientException.cs @@ -0,0 +1,43 @@ +namespace LocalStack.Client.Exceptions; + +public abstract class LocalStackClientException : Exception +{ + /// + /// Construct instance of ConfigurationException + /// + /// The error message. + protected LocalStackClientException(string message) : base(message) { } + + /// + /// Construct instance of ConfigurationException + /// + /// The error message. + /// Original exception. + protected LocalStackClientException(string message, Exception exception) : base(message, exception) { } +} + +public class NotSupportedClientException : LocalStackClientException +{ + public NotSupportedClientException(string message) + : base(message) + { + } + + public NotSupportedClientException(string message, Exception exception) + : base(message, exception) + { + } +} + +public class MisconfiguredClientException : LocalStackClientException +{ + public MisconfiguredClientException(string message) + : base(message) + { + } + + public MisconfiguredClientException(string message, Exception exception) + : base(message, exception) + { + } +} \ No newline at end of file diff --git a/src/LocalStack.Client/GlobalUsings.cs b/src/LocalStack.Client/GlobalUsings.cs index 1f98f27..3c8c50a 100644 --- a/src/LocalStack.Client/GlobalUsings.cs +++ b/src/LocalStack.Client/GlobalUsings.cs @@ -4,11 +4,13 @@ global using System.Linq; global using System.Reflection; +global using Amazon; global using Amazon.Runtime; global using Amazon.Runtime.Internal; global using LocalStack.Client.Contracts; global using LocalStack.Client.Enums; +global using LocalStack.Client.Exceptions; global using LocalStack.Client.Models; global using LocalStack.Client.Options; global using LocalStack.Client.Utils; diff --git a/src/LocalStack.Client/Options/SessionOptions.cs b/src/LocalStack.Client/Options/SessionOptions.cs index d3b9637..571646c 100644 --- a/src/LocalStack.Client/Options/SessionOptions.cs +++ b/src/LocalStack.Client/Options/SessionOptions.cs @@ -9,7 +9,7 @@ public SessionOptions() public SessionOptions(string awsAccessKeyId = Constants.AwsAccessKeyId, string awsAccessKey = Constants.AwsAccessKey, string awsSessionToken = Constants.AwsSessionToken, - string regionName = Constants.RegionName) + string regionName = default) { AwsAccessKeyId = awsAccessKeyId; AwsAccessKey = awsAccessKey; @@ -23,5 +23,5 @@ public SessionOptions(string awsAccessKeyId = Constants.AwsAccessKeyId, public string AwsSessionToken { get; private set; } = Constants.AwsSessionToken; - public string RegionName { get; private set; } = Constants.RegionName; + public string RegionName { get; private set; } = default; } \ No newline at end of file diff --git a/src/LocalStack.Client/Session.cs b/src/LocalStack.Client/Session.cs index 63432bf..4e87e9c 100644 --- a/src/LocalStack.Client/Session.cs +++ b/src/LocalStack.Client/Session.cs @@ -17,14 +17,14 @@ public TClient CreateClientByImplementation() where TClient : AmazonSer { Type clientType = typeof(TClient); - return (TClient) CreateClientByImplementation(clientType); + return (TClient)CreateClientByImplementation(clientType); } public AmazonServiceClient CreateClientByImplementation(Type implType) { IServiceMetadata serviceMetadata = _sessionReflection.ExtractServiceMetadata(implType); AwsServiceEndpoint awsServiceEndpoint = _config.GetAwsServiceEndpoint(serviceMetadata.ServiceId) ?? - throw new InvalidOperationException($"{serviceMetadata.ServiceId} is not supported by this mock session."); + throw new NotSupportedClientException($"{serviceMetadata.ServiceId} is not supported by this mock session."); AWSCredentials awsCredentials = new SessionAWSCredentials(_sessionOptions.AwsAccessKeyId, _sessionOptions.AwsAccessKey, _sessionOptions.AwsSessionToken); ClientConfig clientConfig = _sessionReflection.CreateClientConfig(implType); @@ -35,23 +35,28 @@ public AmazonServiceClient CreateClientByImplementation(Type implType) clientConfig.ProxyHost = awsServiceEndpoint.Host; clientConfig.ProxyPort = awsServiceEndpoint.Port; - var clientInstance = (AmazonServiceClient) Activator.CreateInstance(implType, awsCredentials, clientConfig); + if (!string.IsNullOrWhiteSpace(_sessionOptions.RegionName)) + { + clientConfig.RegionEndpoint = RegionEndpoint.GetBySystemName(_sessionOptions.RegionName); + } + + var clientInstance = (AmazonServiceClient)Activator.CreateInstance(implType, awsCredentials, clientConfig); return clientInstance; } - public AmazonServiceClient CreateClientByInterface() where TClient: IAmazonService + public AmazonServiceClient CreateClientByInterface() where TClient : IAmazonService { Type serviceInterfaceType = typeof(TClient); - return (AmazonServiceClient) CreateClientByInterface(serviceInterfaceType); + return (AmazonServiceClient)CreateClientByInterface(serviceInterfaceType); } public AmazonServiceClient CreateClientByInterface(Type serviceInterfaceType) { var clientTypeName = $"{serviceInterfaceType.Namespace}.{serviceInterfaceType.Name.Substring(1)}Client"; Type clientType = serviceInterfaceType.GetTypeInfo().Assembly.GetType(clientTypeName); - + if (clientType == null) { throw new AmazonClientException($"Failed to find service client {clientTypeName} which implements {serviceInterfaceType.FullName}."); @@ -72,14 +77,19 @@ public AmazonServiceClient CreateClientByInterface(Type serviceInterfaceType) clientConfig.ProxyHost = awsServiceEndpoint.Host; clientConfig.ProxyPort = awsServiceEndpoint.Port; - ConstructorInfo constructor = clientType.GetConstructor(new Type[] { typeof(AWSCredentials), clientConfig.GetType() }); + ConstructorInfo constructor = clientType.GetConstructor(new[] { typeof(AWSCredentials), clientConfig.GetType() }); + + if (!string.IsNullOrWhiteSpace(_sessionOptions.RegionName)) + { + clientConfig.RegionEndpoint = RegionEndpoint.GetBySystemName(_sessionOptions.RegionName); + } if (constructor == null) { throw new AmazonClientException($"Service client {clientTypeName} missing a constructor with parameters AWSCredentials and {clientConfig.GetType().FullName}."); } - var client = (AmazonServiceClient) constructor.Invoke(new object[] { awsCredentials, clientConfig }); + var client = (AmazonServiceClient)constructor.Invoke(new object[] { awsCredentials, clientConfig }); return client; } diff --git a/src/LocalStack.Client/Utils/SessionReflection.cs b/src/LocalStack.Client/Utils/SessionReflection.cs index dae3865..be4f81d 100644 --- a/src/LocalStack.Client/Utils/SessionReflection.cs +++ b/src/LocalStack.Client/Utils/SessionReflection.cs @@ -34,6 +34,11 @@ public ClientConfig CreateClientConfig(Type clientType) return (ClientConfig) Activator.CreateInstance(clientConfigParam.ParameterType); } + public void SetClientRegion(AmazonServiceClient amazonServiceClient, string systemName) + { + PropertyInfo regionEndpointProperty = amazonServiceClient.Config.GetType().GetProperty(nameof(amazonServiceClient.Config.RegionEndpoint), BindingFlags.Public | BindingFlags.Instance); + regionEndpointProperty?.SetValue(amazonServiceClient.Config, RegionEndpoint.GetBySystemName(systemName)); + } public bool SetForcePathStyle(ClientConfig clientConfig, bool value = true) { diff --git a/tests/LocalStack.Client.Extensions.Tests/ServiceCollectionExtensionsTests.cs b/tests/LocalStack.Client.Extensions.Tests/ServiceCollectionExtensionsTests.cs index 119c92b..127e338 100644 --- a/tests/LocalStack.Client.Extensions.Tests/ServiceCollectionExtensionsTests.cs +++ b/tests/LocalStack.Client.Extensions.Tests/ServiceCollectionExtensionsTests.cs @@ -1,4 +1,6 @@ -namespace LocalStack.Client.Extensions.Tests; +using Amazon; + +namespace LocalStack.Client.Extensions.Tests; public class ServiceCollectionExtensionsTests { @@ -192,8 +194,64 @@ public void AddLocalStackServices_Should_Add_ISessionReflection_To_Container_As_ Assert.IsType(sessionReflection); } - [Theory, InlineData(false), InlineData(true)] - public void GetRequiredService_Should_Return_AmazonService_That_Configured_For_LocalStack_If_UseLocalStack_Is_True(bool useAlternateNameAddServiceMethod) + [Theory, + InlineData(true, "eu-central-1"), + InlineData(true, "us-west-1"), + InlineData(true, "af-south-1"), + InlineData(true, "ap-southeast-1"), + InlineData(true, "ca-central-1"), + InlineData(true, "eu-west-2"), + InlineData(true, "sa-east-1"), + InlineData(false, "eu-central-1"), + InlineData(false, "us-west-1"), + InlineData(false, "af-south-1"), + InlineData(false, "ap-southeast-1"), + InlineData(false, "ca-central-1"), + InlineData(false, "eu-west-2"), + InlineData(false, "sa-east-1")] + public void GetRequiredService_Should_Return_AmazonService_That_Configured_For_LocalStack_If_UseLocalStack_Is_True(bool useAlternateNameAddServiceMethod, string systemName) + { + var configurationValue = new Dictionary { { "LocalStack:UseLocalStack", "true" }, {"LocalStack:Session:RegionName", systemName} }; + IConfiguration configuration = new ConfigurationBuilder().AddInMemoryCollection(configurationValue).Build(); + + var mockServiceMetadata = new MockServiceMetadata(); + var mockAwsServiceEndpoint = new MockAwsServiceEndpoint(); + + var mockConfig = new Mock(MockBehavior.Strict); + IConfig mockConfigObject = mockConfig.Object; + + mockConfig.Setup(config => config.GetAwsServiceEndpoint(It.Is(s => s == mockServiceMetadata.ServiceId))).Returns(() => mockAwsServiceEndpoint); + + IServiceCollection serviceCollection = new ServiceCollection(); + serviceCollection = serviceCollection + .AddLocalStack(configuration) + .Replace(ServiceDescriptor.Singleton(_ => mockConfigObject)); + + if (!useAlternateNameAddServiceMethod) + { + serviceCollection.AddAwsService(); + } + else + { + serviceCollection.AddAWSServiceLocalStack(); + } + + ServiceProvider provider = serviceCollection.BuildServiceProvider(); + + var mockAmazonService = provider.GetRequiredService(); + + IClientConfig clientConfig = mockAmazonService.Config; + + Assert.True(clientConfig.UseHttp); + Assert.Equal(mockAwsServiceEndpoint.Host, clientConfig.ProxyHost); + Assert.Equal(mockAwsServiceEndpoint.Port, clientConfig.ProxyPort); + Assert.Equal(RegionEndpoint.GetBySystemName(systemName), clientConfig.RegionEndpoint); + } + + [Theory, + InlineData(true), + InlineData(false)] + public void GetRequiredService_Should_Return_AmazonService_With_Service_Url_That_Configured_For_LocalStack_If_UseLocalStack_Is_True_And_RegionName_Is_Null(bool useAlternateNameAddServiceMethod) { var configurationValue = new Dictionary { { "LocalStack:UseLocalStack", "true" } }; IConfiguration configuration = new ConfigurationBuilder().AddInMemoryCollection(configurationValue).Build(); @@ -226,10 +284,12 @@ public void GetRequiredService_Should_Return_AmazonService_That_Configured_For_L IClientConfig clientConfig = mockAmazonService.Config; - Assert.Equal(mockAwsServiceEndpoint.ServiceUrl, clientConfig.ServiceURL); Assert.True(clientConfig.UseHttp); Assert.Equal(mockAwsServiceEndpoint.Host, clientConfig.ProxyHost); Assert.Equal(mockAwsServiceEndpoint.Port, clientConfig.ProxyPort); + Assert.Null(clientConfig.RegionEndpoint); + Assert.NotNull(clientConfig.ServiceURL); + Assert.Equal(mockAwsServiceEndpoint.ServiceUrl, clientConfig.ServiceURL); } [Theory, diff --git a/tests/LocalStack.Client.Integration.Tests/AssertAmazonClient.cs b/tests/LocalStack.Client.Integration.Tests/AssertAmazonClient.cs index 40c2a0c..8b8c495 100644 --- a/tests/LocalStack.Client.Integration.Tests/AssertAmazonClient.cs +++ b/tests/LocalStack.Client.Integration.Tests/AssertAmazonClient.cs @@ -2,11 +2,26 @@ public static class AssertAmazonClient { + public const string TestAwsRegion = "eu-central-1"; + public static void AssertClientConfiguration(AmazonServiceClient amazonServiceClient) { IClientConfig clientConfig = amazonServiceClient.Config; - Assert.Equal($"http://{Constants.LocalStackHost}:{Constants.EdgePort}", clientConfig.ServiceURL); + if (clientConfig.ServiceURL != null && amazonServiceClient.Config.RegionEndpoint == null) + { + Assert.Equal($"http://{Constants.LocalStackHost}:{Constants.EdgePort}", clientConfig.ServiceURL); + } + else if(clientConfig.ServiceURL == null && amazonServiceClient.Config.RegionEndpoint != null) + { + Assert.Equal(RegionEndpoint.GetBySystemName(TestAwsRegion), amazonServiceClient.Config.RegionEndpoint); + } + else + { + throw new MisconfiguredClientException( + "Both ServiceURL and RegionEndpoint properties are null. Under normal conditions, one of these two properties must be a set. This means either something has changed in the new version of the Amazon Client library or there is a bug in the LocalStack.NET Client that we could not detect before. Please open an issue on the subject."); + } + Assert.True(clientConfig.UseHttp); PropertyInfo forcePathStyleProperty = clientConfig.GetType().GetProperty("ForcePathStyle", BindingFlags.Public | BindingFlags.Instance); diff --git a/tests/LocalStack.Client.Integration.Tests/CreateClientByImplementationTests.cs b/tests/LocalStack.Client.Integration.Tests/CreateClientByImplementationTests.cs index 9126c79..36126ca 100644 --- a/tests/LocalStack.Client.Integration.Tests/CreateClientByImplementationTests.cs +++ b/tests/LocalStack.Client.Integration.Tests/CreateClientByImplementationTests.cs @@ -6,7 +6,10 @@ public class CreateClientByImplementationTests static CreateClientByImplementationTests() { - Session = SessionStandalone.Init().Create(); + Session = SessionStandalone.Init() + + .WithSessionOptions(new SessionOptions(regionName: AssertAmazonClient.TestAwsRegion)) + .Create(); } [Fact] diff --git a/tests/LocalStack.Client.Integration.Tests/GlobalUsings.cs b/tests/LocalStack.Client.Integration.Tests/GlobalUsings.cs index 030a9a4..4bde9c0 100644 --- a/tests/LocalStack.Client.Integration.Tests/GlobalUsings.cs +++ b/tests/LocalStack.Client.Integration.Tests/GlobalUsings.cs @@ -1,5 +1,6 @@ global using System.Reflection; +global using Amazon; global using Amazon.Amplify; global using Amazon.APIGateway; global using Amazon.ApiGatewayManagementApi; @@ -87,7 +88,9 @@ global using Amazon.WAFV2; global using Amazon.XRay; +global using LocalStack.Client.Options; global using LocalStack.Client.Contracts; +global using LocalStack.Client.Exceptions; global using LocalStack.Client.Models; global using Xunit; diff --git a/tests/LocalStack.Client.Tests/GlobalUsings.cs b/tests/LocalStack.Client.Tests/GlobalUsings.cs index 6e912d1..676657f 100644 --- a/tests/LocalStack.Client.Tests/GlobalUsings.cs +++ b/tests/LocalStack.Client.Tests/GlobalUsings.cs @@ -3,6 +3,7 @@ global using System.Reflection; global using System.Linq; +global using Amazon; global using Amazon.Runtime; global using Amazon.Runtime.Internal; global using Amazon.Runtime.Internal.Auth; @@ -10,6 +11,7 @@ global using LocalStack.Client.Contracts; global using LocalStack.Client.Enums; +global using LocalStack.Client.Exceptions; global using LocalStack.Client.Models; global using LocalStack.Client.Options; global using LocalStack.Client.Tests.Mocks; diff --git a/tests/LocalStack.Client.Tests/Mocks/MockSession.cs b/tests/LocalStack.Client.Tests/Mocks/MockSession.cs index 3a6f996..5954719 100644 --- a/tests/LocalStack.Client.Tests/Mocks/MockSession.cs +++ b/tests/LocalStack.Client.Tests/Mocks/MockSession.cs @@ -18,7 +18,8 @@ private MockSession(Mock sessionOptionsMock, Mock conf public static MockSession Create() { - return new MockSession(new Mock(MockBehavior.Strict), new Mock(MockBehavior.Strict), + return new MockSession(new Mock(MockBehavior.Strict), + new Mock(MockBehavior.Strict), new Mock(MockBehavior.Strict)); } } diff --git a/tests/LocalStack.Client.Tests/SessionTests/SessionOptionsTests.cs b/tests/LocalStack.Client.Tests/SessionTests/SessionOptionsTests.cs index 0c1a010..8d5fe7b 100644 --- a/tests/LocalStack.Client.Tests/SessionTests/SessionOptionsTests.cs +++ b/tests/LocalStack.Client.Tests/SessionTests/SessionOptionsTests.cs @@ -10,7 +10,7 @@ public void SessionOptions_Should_Created_With_Default_Parameters_If_It_Created_ Assert.Equal(Constants.AwsAccessKeyId, sessionOptions.AwsAccessKeyId); Assert.Equal(Constants.AwsAccessKey, sessionOptions.AwsAccessKey); Assert.Equal(Constants.AwsSessionToken, sessionOptions.AwsSessionToken); - Assert.Equal(Constants.RegionName, sessionOptions.RegionName); + Assert.Equal(default, sessionOptions.RegionName); } [Fact] @@ -25,7 +25,7 @@ public void SessionOptions_Should_Created_With_Default_Parameters_If_It_Created_ Assert.Equal(Constants.AwsAccessKeyId, sessionOptions.AwsAccessKeyId); Assert.Equal(Constants.AwsAccessKey, sessionOptions.AwsAccessKey); Assert.Equal(Constants.AwsSessionToken, sessionOptions.AwsSessionToken); - Assert.Equal(Constants.RegionName, sessionOptions.RegionName); + Assert.Equal(default, sessionOptions.RegionName); } [Fact] @@ -38,7 +38,7 @@ public void AwsAccessKeyId_Property_Of_ConfigOptions_Should_Equal_To_Given_AwsAc Assert.Equal(awsAccessKeyId, sessionOptions.AwsAccessKeyId); Assert.Equal(Constants.AwsAccessKey, sessionOptions.AwsAccessKey); Assert.Equal(Constants.AwsSessionToken, sessionOptions.AwsSessionToken); - Assert.Equal(Constants.RegionName, sessionOptions.RegionName); + Assert.Equal(default, sessionOptions.RegionName); } [Fact] @@ -51,7 +51,7 @@ public void AwsAccessKey_Property_Of_ConfigOptions_Should_Equal_To_Given_AwsAcce Assert.Equal(Constants.AwsAccessKeyId, sessionOptions.AwsAccessKeyId); Assert.Equal(awsAccessKey, sessionOptions.AwsAccessKey); Assert.Equal(Constants.AwsSessionToken, sessionOptions.AwsSessionToken); - Assert.Equal(Constants.RegionName, sessionOptions.RegionName); + Assert.Equal(default, sessionOptions.RegionName); } [Fact] @@ -64,7 +64,7 @@ public void AwsSessionToken_Property_Of_ConfigOptions_Should_Equal_To_Given_AwsS Assert.Equal(Constants.AwsAccessKeyId, sessionOptions.AwsAccessKeyId); Assert.Equal(Constants.AwsAccessKey, sessionOptions.AwsAccessKey); Assert.Equal(awsSessionToken, sessionOptions.AwsSessionToken); - Assert.Equal(Constants.RegionName, sessionOptions.RegionName); + Assert.Equal(default, sessionOptions.RegionName); } [Fact] diff --git a/tests/LocalStack.Client.Tests/SessionTests/SessionReflectionTests.cs b/tests/LocalStack.Client.Tests/SessionTests/SessionReflectionTests.cs index 117a92d..dc8881d 100644 --- a/tests/LocalStack.Client.Tests/SessionTests/SessionReflectionTests.cs +++ b/tests/LocalStack.Client.Tests/SessionTests/SessionReflectionTests.cs @@ -56,4 +56,25 @@ public void SetForcePathStyle_Should_Set_ForcePathStyle_Of_ClientConfig_If_It_Ex Assert.True(set); Assert.True(clientConfig.ForcePathStyle); } + + [Theory, + InlineData("eu-central-1"), + InlineData("us-west-1"), + InlineData("af-south-1"), + InlineData("ap-southeast-1"), + InlineData("ca-central-1"), + InlineData("eu-west-2"), + InlineData("sa-east-1")] + public void SetClientRegion_Should_Set_RegionEndpoint_Of_The_Given_Client_By_System_Name(string systemName) + { + var sessionReflection = new SessionReflection(); + var mockAmazonServiceClient = new MockAmazonServiceClient(); + + Assert.Null(mockAmazonServiceClient.Config.RegionEndpoint); + + sessionReflection.SetClientRegion(mockAmazonServiceClient, systemName); + + Assert.NotNull(mockAmazonServiceClient.Config.RegionEndpoint); + Assert.Equal(RegionEndpoint.GetBySystemName(systemName), mockAmazonServiceClient.Config.RegionEndpoint); + } } diff --git a/tests/LocalStack.Client.Tests/SessionTests/SessionTests.cs b/tests/LocalStack.Client.Tests/SessionTests/SessionTests.cs index f9e86e5..63a2d39 100644 --- a/tests/LocalStack.Client.Tests/SessionTests/SessionTests.cs +++ b/tests/LocalStack.Client.Tests/SessionTests/SessionTests.cs @@ -11,7 +11,7 @@ public void CreateClientByImplementation_Should_Throw_InvalidOperationException_ mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => null); - Assert.Throws(() => mockSession.CreateClientByImplementation()); + Assert.Throws(() => mockSession.CreateClientByImplementation()); mockSession.ConfigMock.Verify(config => config.GetAwsServiceEndpoint(It.Is(serviceId => serviceId == mockServiceMetadata.ServiceId)), Times.Once); } @@ -30,9 +30,12 @@ public void CreateClientByImplementation_Should_Create_SessionAWSCredentials_Wit mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns(awsAccessKeyId); mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns(awsAccessKey); mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns(awsSessionToken); + mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(default(string)); + mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true); + mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint); var mockAmazonServiceClient = mockSession.CreateClientByImplementation(); @@ -60,21 +63,88 @@ public void CreateClientByImplementation_Should_Create_ClientConfig_With_Service mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); + mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(default(string)); + mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true); + mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint); var mockAmazonServiceClient = mockSession.CreateClientByImplementation(); Assert.NotNull(mockAmazonServiceClient); IClientConfig clientConfig = mockAmazonServiceClient.Config; + Assert.Equal(mockAwsServiceEndpoint.ServiceUrl, clientConfig.ServiceURL); Assert.True(clientConfig.UseHttp); Assert.Equal(mockAwsServiceEndpoint.Host, clientConfig.ProxyHost); Assert.Equal(mockAwsServiceEndpoint.Port, clientConfig.ProxyPort); } + [Theory, + InlineData("eu-central-1"), + InlineData("us-west-1"), + InlineData("af-south-1"), + InlineData("ap-southeast-1"), + InlineData("ca-central-1"), + InlineData("eu-west-2"), + InlineData("sa-east-1")] + public void CreateClientByImplementation_Should_Set_RegionEndpoint_By_RegionName_Property_Of_SessionOptions_And_ServiceUrl_To_Null_If_RegionName_IsNotNull_Or_Empty(string systemName) + { + var mockSession = MockSession.Create(); + + IServiceMetadata mockServiceMetadata = new MockServiceMetadata(); + var mockAwsServiceEndpoint = new MockAwsServiceEndpoint(); + var mockClientConfig = new MockClientConfig(); + + mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); + mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); + mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); + mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(systemName); + + mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); + mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); + mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true); + + mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint); + + var mockAmazonServiceClient = mockSession.CreateClientByImplementation(); + + Assert.NotNull(mockAmazonServiceClient); + Assert.Null(mockAmazonServiceClient.Config.ServiceURL); + Assert.Equal(RegionEndpoint.GetBySystemName(systemName), mockAmazonServiceClient.Config.RegionEndpoint); + } + + [Theory, + InlineData(""), + InlineData(null)] + public void CreateClientByImplementation_Should_Set_ServiceUrl_And_RegionEndpoint_To_Null_If_RegionName_Property_Of_SessionOptions_And_ServiceUrl_IsNull_Or_Empty(string systemName) + { + var mockSession = MockSession.Create(); + + IServiceMetadata mockServiceMetadata = new MockServiceMetadata(); + var mockAwsServiceEndpoint = new MockAwsServiceEndpoint(); + var mockClientConfig = new MockClientConfig(); + + mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); + mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); + mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); + mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(systemName); + + mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); + mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); + mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true); + + mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint); + + var mockAmazonServiceClient = mockSession.CreateClientByImplementation(); + + Assert.NotNull(mockAmazonServiceClient); + Assert.Null(mockAmazonServiceClient.Config.RegionEndpoint); + Assert.Equal(mockAwsServiceEndpoint.ServiceUrl, mockAmazonServiceClient.Config.ServiceURL); + } + [Fact] public void CreateClientByImplementation_Should_Pass_The_ClientConfig_To_SetForcePathStyle() { @@ -86,9 +156,12 @@ public void CreateClientByImplementation_Should_Pass_The_ClientConfig_To_SetForc mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); + mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(default(string)); + mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true); + mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint); mockSession.CreateClientByImplementation(); @@ -107,13 +180,12 @@ public void CreateClientByImplementation_Should_Create_AmazonServiceClient_By_Gi mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); + mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(default(string)); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true); + mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint); var mockAmazonServiceClient = mockSession.CreateClientByImplementation(); @@ -161,9 +233,12 @@ public void CreateClientByInterface_Should_Create_SessionAWSCredentials_With_Aws mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns(awsAccessKeyId); mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns(awsAccessKey); mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns(awsSessionToken); + mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(default(string)); + mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true); + mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint); var mockAmazonServiceClient = mockSession.CreateClientByInterface() as MockAmazonServiceClient; @@ -191,9 +266,12 @@ public void CreateClientByInterface_Should_Create_ClientConfig_With_ServiceURL_U mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); + mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(default(string)); + mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true); + mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint); var mockAmazonServiceClient = mockSession.CreateClientByInterface() as MockAmazonServiceClient; @@ -206,6 +284,69 @@ public void CreateClientByInterface_Should_Create_ClientConfig_With_ServiceURL_U Assert.Equal(mockAwsServiceEndpoint.Port, clientConfig.ProxyPort); } + [Theory, + InlineData("eu-central-1"), + InlineData("us-west-1"), + InlineData("af-south-1"), + InlineData("ap-southeast-1"), + InlineData("ca-central-1"), + InlineData("eu-west-2"), + InlineData("sa-east-1")] + public void CreateClientByInterface_Should_Set_RegionEndpoint_By_RegionName_Property_Of_SessionOptions_And_ServiceUrl_To_Null_If_RegionName_IsNotNull_Or_Empty(string systemName) + { + var mockSession = MockSession.Create(); + + IServiceMetadata mockServiceMetadata = new MockServiceMetadata(); + var mockAwsServiceEndpoint = new MockAwsServiceEndpoint(); + var mockClientConfig = new MockClientConfig(); + + mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); + mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); + mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); + mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(systemName); + + mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); + mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); + mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true); + + mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint); + + var mockAmazonServiceClient = mockSession.CreateClientByInterface() as MockAmazonServiceClient; + + Assert.NotNull(mockAmazonServiceClient); + Assert.Null(mockAmazonServiceClient.Config.ServiceURL); + Assert.Equal(RegionEndpoint.GetBySystemName(systemName), mockAmazonServiceClient.Config.RegionEndpoint); + } + + [Theory, + InlineData(""), + InlineData(null)] + public void CreateClientByInterface_Should_Set_ServiceUrl_And_RegionEndpoint_To_Null_If_RegionName_Property_Of_SessionOptions_And_ServiceUrl_IsNull_Or_Empty(string systemName) + { + var mockSession = MockSession.Create(); + + IServiceMetadata mockServiceMetadata = new MockServiceMetadata(); + var mockAwsServiceEndpoint = new MockAwsServiceEndpoint(); + var mockClientConfig = new MockClientConfig(); + + mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); + mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); + mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); + mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(systemName); + + mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); + mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); + mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true); + + mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint); + + var mockAmazonServiceClient = mockSession.CreateClientByInterface() as MockAmazonServiceClient; + + Assert.NotNull(mockAmazonServiceClient); + Assert.Null(mockAmazonServiceClient.Config.RegionEndpoint); + Assert.Equal(mockAwsServiceEndpoint.ServiceUrl, mockAmazonServiceClient.Config.ServiceURL); + } + [Fact] public void CreateClientByInterface_Should_Pass_The_ClientConfig_To_SetForcePathStyle() { @@ -217,9 +358,12 @@ public void CreateClientByInterface_Should_Pass_The_ClientConfig_To_SetForcePath mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); + mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(default(string)); + mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true); + mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint); mockSession.CreateClientByInterface(); @@ -238,13 +382,12 @@ public void CreateClientByInterface_Should_Create_AmazonServiceClient_By_Given_G mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); + mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(default(string)); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true); + mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint); var mockAmazonServiceClient = mockSession.CreateClientByInterface() as MockAmazonServiceClient; From e62e9edc332b99e1894c53a60caeb49622775392 Mon Sep 17 00:00:00 2001 From: "deniz_irgin@yahoo.com" Date: Sun, 28 Nov 2021 17:09:14 +0300 Subject: [PATCH 15/24] fixes #15 --- src/LocalStack.Client/Contracts/ISession.cs | 8 +- .../Options/SessionOptions.cs | 4 +- src/LocalStack.Client/Session.cs | 42 +++-- .../AssertAmazonClient.cs | 4 +- .../CreateClientByImplementationTests.cs | 87 ++++++++- .../CreateClientByInterfaceTests.cs | 92 +++++++++- .../GlobalUsings.cs | 3 +- .../Mocks/Extensions.cs | 20 +++ .../SessionTests/SessionOptionsTests.cs | 10 +- .../SessionTests/SessionTests.cs | 168 ++++++++++-------- 10 files changed, 323 insertions(+), 115 deletions(-) create mode 100644 tests/LocalStack.Client.Tests/Mocks/Extensions.cs diff --git a/src/LocalStack.Client/Contracts/ISession.cs b/src/LocalStack.Client/Contracts/ISession.cs index 6b89703..543ddf4 100644 --- a/src/LocalStack.Client/Contracts/ISession.cs +++ b/src/LocalStack.Client/Contracts/ISession.cs @@ -2,11 +2,11 @@ public interface ISession { - TClient CreateClientByImplementation() where TClient : AmazonServiceClient; + TClient CreateClientByImplementation(bool useServiceUrl = false) where TClient : AmazonServiceClient; - AmazonServiceClient CreateClientByImplementation(Type implType); + AmazonServiceClient CreateClientByImplementation(Type implType, bool useServiceUrl = false); - AmazonServiceClient CreateClientByInterface() where TClient: IAmazonService; + AmazonServiceClient CreateClientByInterface(bool useServiceUrl = false) where TClient: IAmazonService; - AmazonServiceClient CreateClientByInterface(Type serviceInterfaceType); + AmazonServiceClient CreateClientByInterface(Type serviceInterfaceType, bool useServiceUrl = false); } \ No newline at end of file diff --git a/src/LocalStack.Client/Options/SessionOptions.cs b/src/LocalStack.Client/Options/SessionOptions.cs index 571646c..d3b9637 100644 --- a/src/LocalStack.Client/Options/SessionOptions.cs +++ b/src/LocalStack.Client/Options/SessionOptions.cs @@ -9,7 +9,7 @@ public SessionOptions() public SessionOptions(string awsAccessKeyId = Constants.AwsAccessKeyId, string awsAccessKey = Constants.AwsAccessKey, string awsSessionToken = Constants.AwsSessionToken, - string regionName = default) + string regionName = Constants.RegionName) { AwsAccessKeyId = awsAccessKeyId; AwsAccessKey = awsAccessKey; @@ -23,5 +23,5 @@ public SessionOptions(string awsAccessKeyId = Constants.AwsAccessKeyId, public string AwsSessionToken { get; private set; } = Constants.AwsSessionToken; - public string RegionName { get; private set; } = default; + public string RegionName { get; private set; } = Constants.RegionName; } \ No newline at end of file diff --git a/src/LocalStack.Client/Session.cs b/src/LocalStack.Client/Session.cs index 4e87e9c..3572d5b 100644 --- a/src/LocalStack.Client/Session.cs +++ b/src/LocalStack.Client/Session.cs @@ -13,15 +13,20 @@ public Session(ISessionOptions sessionOptions, IConfig config, ISessionReflectio _sessionReflection = sessionReflection; } - public TClient CreateClientByImplementation() where TClient : AmazonServiceClient + public TClient CreateClientByImplementation(bool useServiceUrl = false) where TClient : AmazonServiceClient { Type clientType = typeof(TClient); - return (TClient)CreateClientByImplementation(clientType); + return (TClient)CreateClientByImplementation(clientType, useServiceUrl); } - public AmazonServiceClient CreateClientByImplementation(Type implType) + public AmazonServiceClient CreateClientByImplementation(Type implType, bool useServiceUrl = false) { + if (!useServiceUrl && string.IsNullOrWhiteSpace(_sessionOptions.RegionName)) + { + throw new MisconfiguredClientException($"{nameof(_sessionOptions.RegionName)} must be set if {nameof(useServiceUrl)} is false."); + } + IServiceMetadata serviceMetadata = _sessionReflection.ExtractServiceMetadata(implType); AwsServiceEndpoint awsServiceEndpoint = _config.GetAwsServiceEndpoint(serviceMetadata.ServiceId) ?? throw new NotSupportedClientException($"{serviceMetadata.ServiceId} is not supported by this mock session."); @@ -29,13 +34,16 @@ public AmazonServiceClient CreateClientByImplementation(Type implType) AWSCredentials awsCredentials = new SessionAWSCredentials(_sessionOptions.AwsAccessKeyId, _sessionOptions.AwsAccessKey, _sessionOptions.AwsSessionToken); ClientConfig clientConfig = _sessionReflection.CreateClientConfig(implType); - clientConfig.ServiceURL = awsServiceEndpoint.ServiceUrl; clientConfig.UseHttp = true; _sessionReflection.SetForcePathStyle(clientConfig); clientConfig.ProxyHost = awsServiceEndpoint.Host; clientConfig.ProxyPort = awsServiceEndpoint.Port; - if (!string.IsNullOrWhiteSpace(_sessionOptions.RegionName)) + if (useServiceUrl) + { + clientConfig.ServiceURL = awsServiceEndpoint.ServiceUrl; + } + else if (!string.IsNullOrWhiteSpace(_sessionOptions.RegionName)) { clientConfig.RegionEndpoint = RegionEndpoint.GetBySystemName(_sessionOptions.RegionName); } @@ -45,14 +53,14 @@ public AmazonServiceClient CreateClientByImplementation(Type implType) return clientInstance; } - public AmazonServiceClient CreateClientByInterface() where TClient : IAmazonService + public AmazonServiceClient CreateClientByInterface(bool useServiceUrl = false) where TClient : IAmazonService { Type serviceInterfaceType = typeof(TClient); - return (AmazonServiceClient)CreateClientByInterface(serviceInterfaceType); + return (AmazonServiceClient)CreateClientByInterface(serviceInterfaceType, useServiceUrl); } - public AmazonServiceClient CreateClientByInterface(Type serviceInterfaceType) + public AmazonServiceClient CreateClientByInterface(Type serviceInterfaceType, bool useServiceUrl = false) { var clientTypeName = $"{serviceInterfaceType.Namespace}.{serviceInterfaceType.Name.Substring(1)}Client"; Type clientType = serviceInterfaceType.GetTypeInfo().Assembly.GetType(clientTypeName); @@ -62,28 +70,36 @@ public AmazonServiceClient CreateClientByInterface(Type serviceInterfaceType) throw new AmazonClientException($"Failed to find service client {clientTypeName} which implements {serviceInterfaceType.FullName}."); } + if (!useServiceUrl && string.IsNullOrWhiteSpace(_sessionOptions.RegionName)) + { + throw new MisconfiguredClientException($"{nameof(_sessionOptions.RegionName)} must be set if {nameof(useServiceUrl)} is false."); + } + IServiceMetadata serviceMetadata = _sessionReflection.ExtractServiceMetadata(clientType); AwsServiceEndpoint awsServiceEndpoint = _config.GetAwsServiceEndpoint(serviceMetadata.ServiceId) ?? - throw new InvalidOperationException($"{serviceMetadata.ServiceId} is not supported by this mock session."); + throw new NotSupportedClientException($"{serviceMetadata.ServiceId} is not supported by this mock session."); AWSCredentials awsCredentials = new SessionAWSCredentials(_sessionOptions.AwsAccessKeyId, _sessionOptions.AwsAccessKey, _sessionOptions.AwsSessionToken); ClientConfig clientConfig = _sessionReflection.CreateClientConfig(clientType); - clientConfig.ServiceURL = awsServiceEndpoint.ServiceUrl; clientConfig.UseHttp = true; _sessionReflection.SetForcePathStyle(clientConfig); clientConfig.ProxyHost = awsServiceEndpoint.Host; clientConfig.ProxyPort = awsServiceEndpoint.Port; - ConstructorInfo constructor = clientType.GetConstructor(new[] { typeof(AWSCredentials), clientConfig.GetType() }); - - if (!string.IsNullOrWhiteSpace(_sessionOptions.RegionName)) + if (useServiceUrl) + { + clientConfig.ServiceURL = awsServiceEndpoint.ServiceUrl; + } + else if (!string.IsNullOrWhiteSpace(_sessionOptions.RegionName)) { clientConfig.RegionEndpoint = RegionEndpoint.GetBySystemName(_sessionOptions.RegionName); } + ConstructorInfo constructor = clientType.GetConstructor(new[] { typeof(AWSCredentials), clientConfig.GetType() }); + if (constructor == null) { throw new AmazonClientException($"Service client {clientTypeName} missing a constructor with parameters AWSCredentials and {clientConfig.GetType().FullName}."); diff --git a/tests/LocalStack.Client.Integration.Tests/AssertAmazonClient.cs b/tests/LocalStack.Client.Integration.Tests/AssertAmazonClient.cs index 8b8c495..b5c338a 100644 --- a/tests/LocalStack.Client.Integration.Tests/AssertAmazonClient.cs +++ b/tests/LocalStack.Client.Integration.Tests/AssertAmazonClient.cs @@ -8,11 +8,11 @@ public static void AssertClientConfiguration(AmazonServiceClient amazonServiceCl { IClientConfig clientConfig = amazonServiceClient.Config; - if (clientConfig.ServiceURL != null && amazonServiceClient.Config.RegionEndpoint == null) + if (clientConfig.ServiceURL != null) { Assert.Equal($"http://{Constants.LocalStackHost}:{Constants.EdgePort}", clientConfig.ServiceURL); } - else if(clientConfig.ServiceURL == null && amazonServiceClient.Config.RegionEndpoint != null) + else if(clientConfig.ServiceURL == null) { Assert.Equal(RegionEndpoint.GetBySystemName(TestAwsRegion), amazonServiceClient.Config.RegionEndpoint); } diff --git a/tests/LocalStack.Client.Integration.Tests/CreateClientByImplementationTests.cs b/tests/LocalStack.Client.Integration.Tests/CreateClientByImplementationTests.cs index 36126ca..597787d 100644 --- a/tests/LocalStack.Client.Integration.Tests/CreateClientByImplementationTests.cs +++ b/tests/LocalStack.Client.Integration.Tests/CreateClientByImplementationTests.cs @@ -238,23 +238,73 @@ public void Should_Able_To_Create_AmazonIoTWirelessClient() } [Fact] - public void Should_Able_To_Create_AmazonIoTDataClient() + public void Should_Able_To_Create_AmazonIoTDataClient_With_ServiceUr() { - var amazonIoTDataClient = Session.CreateClientByImplementation(); + var amazonIoTDataClient = Session.CreateClientByImplementation(useServiceUrl:true); Assert.NotNull(amazonIoTDataClient); AssertAmazonClient.AssertClientConfiguration(amazonIoTDataClient); } [Fact] - public void Should_Able_To_Create_AmazonIoTJobsDataPlaneClient() + public void Should_Throw_AmazonClientException_When_Creating_AmazonIoTDataClient_If_RegionEndpoint_Used() { - var amazonIoTJobsDataPlaneClient = Session.CreateClientByImplementation(); + try + { + Session.CreateClientByImplementation(); + } + catch (Exception e) + { + Exception ex = e; + + while (ex != null) + { + if (ex is AmazonClientException) + { + return; + } + + ex = ex.InnerException; + } + + throw; + } + } + + [Fact] + public void Should_Able_To_Create_AmazonIoTJobsDataPlaneClient_With_ServiceUr() + { + var amazonIoTJobsDataPlaneClient = Session.CreateClientByImplementation(useServiceUrl:true); Assert.NotNull(amazonIoTJobsDataPlaneClient); AssertAmazonClient.AssertClientConfiguration(amazonIoTJobsDataPlaneClient); } + [Fact] + public void Should_Throw_AmazonClientException_When_Creating_AmazonIoTJobsDataPlaneClient_If_RegionEndpoint_Used() + { + try + { + Session.CreateClientByImplementation(); + } + catch (Exception e) + { + Exception ex = e; + + while (ex != null) + { + if (ex is AmazonClientException) + { + return; + } + + ex = ex.InnerException; + } + + throw; + } + } + [Fact] public void Should_Able_To_Create_AmazonCognitoIdentityProviderClient() { @@ -526,14 +576,39 @@ public void Should_Able_To_Create_AmazonMediaStoreClient() } [Fact] - public void Should_Able_To_Create_AmazonMediaStoreDataClient() + public void Should_Able_To_Create_AmazonMediaStoreDataClient_With_ServiceUrl() { - var amazonMediaStoreDataClient = Session.CreateClientByImplementation(); + var amazonMediaStoreDataClient = Session.CreateClientByImplementation(useServiceUrl:true); Assert.NotNull(amazonMediaStoreDataClient); AssertAmazonClient.AssertClientConfiguration(amazonMediaStoreDataClient); } + [Fact] + public void Should_Throw_AmazonClientException_When_Creating_AmazonMediaStoreDataClient_If_RegionEndpoint_Used() + { + try + { + Session.CreateClientByImplementation(); + } + catch (Exception e) + { + Exception ex = e; + + while (ex != null) + { + if (ex is AmazonClientException) + { + return; + } + + ex = ex.InnerException; + } + + throw; + } + } + [Fact] public void Should_Able_To_Create_AmazonTransferClient() { diff --git a/tests/LocalStack.Client.Integration.Tests/CreateClientByInterfaceTests.cs b/tests/LocalStack.Client.Integration.Tests/CreateClientByInterfaceTests.cs index 1fa5213..619cd9b 100644 --- a/tests/LocalStack.Client.Integration.Tests/CreateClientByInterfaceTests.cs +++ b/tests/LocalStack.Client.Integration.Tests/CreateClientByInterfaceTests.cs @@ -6,7 +6,10 @@ public class CreateClientByInterfaceTests static CreateClientByInterfaceTests() { - Session = SessionStandalone.Init().Create(); + Session = SessionStandalone.Init() + + .WithSessionOptions(new SessionOptions(regionName: AssertAmazonClient.TestAwsRegion)) + .Create(); } [Fact] @@ -235,23 +238,73 @@ public void Should_Able_To_Create_AmazonIoTWirelessClient() } [Fact] - public void Should_Able_To_Create_AmazonIoTDataClient() + public void Should_Able_To_Create_AmazonIoTDataClient_With_ServiceUrl() { - AmazonServiceClient amazonIoTDataClient = Session.CreateClientByInterface(); + AmazonServiceClient amazonIoTDataClient = Session.CreateClientByInterface(useServiceUrl:true); Assert.NotNull(amazonIoTDataClient); AssertAmazonClient.AssertClientConfiguration(amazonIoTDataClient); } [Fact] - public void Should_Able_To_Create_AmazonIoTJobsDataPlaneClient() + public void Should_Throw_AmazonClientException_When_Creating_AmazonIoTDataClient_If_RegionEndpoint_Used() { - AmazonServiceClient amazonIoTJobsDataPlaneClient = Session.CreateClientByInterface(); + try + { + Session.CreateClientByInterface(); + } + catch (Exception e) + { + Exception ex = e; + + while (ex != null) + { + if (ex is AmazonClientException) + { + return; + } + + ex = ex.InnerException; + } + + throw; + } + } + + [Fact] + public void Should_Able_To_Create_AmazonIoTJobsDataPlaneClient_With_ServiceUrl() + { + AmazonServiceClient amazonIoTJobsDataPlaneClient = Session.CreateClientByInterface(useServiceUrl:true); Assert.NotNull(amazonIoTJobsDataPlaneClient); AssertAmazonClient.AssertClientConfiguration(amazonIoTJobsDataPlaneClient); } + [Fact] + public void Should_Throw_AmazonClientException_When_Creating_AmazonIoTJobsDataPlaneClient_If_RegionEndpoint_Used() + { + try + { + Session.CreateClientByInterface(); + } + catch (Exception e) + { + Exception ex = e; + + while (ex != null) + { + if (ex is AmazonClientException) + { + return; + } + + ex = ex.InnerException; + } + + throw; + } + } + [Fact] public void Should_Able_To_Create_AmazonCognitoIdentityProviderClient() { @@ -523,14 +576,39 @@ public void Should_Able_To_Create_AmazonMediaStoreClient() } [Fact] - public void Should_Able_To_Create_AmazonMediaStoreDataClient() + public void Should_Able_To_Create_AmazonMediaStoreDataClient_With_ServiceUrl() { - AmazonServiceClient amazonMediaStoreDataClient = Session.CreateClientByInterface(); + AmazonServiceClient amazonMediaStoreDataClient = Session.CreateClientByInterface(useServiceUrl:true); Assert.NotNull(amazonMediaStoreDataClient); AssertAmazonClient.AssertClientConfiguration(amazonMediaStoreDataClient); } + [Fact] + public void Should_Throw_AmazonClientException_When_Creating_AmazonMediaStoreDataClient_If_RegionEndpoint_Used() + { + try + { + Session.CreateClientByInterface(); + } + catch (Exception e) + { + Exception ex = e; + + while (ex != null) + { + if (ex is AmazonClientException) + { + return; + } + + ex = ex.InnerException; + } + + throw; + } + } + [Fact] public void Should_Able_To_Create_AmazonTransferClient() { diff --git a/tests/LocalStack.Client.Integration.Tests/GlobalUsings.cs b/tests/LocalStack.Client.Integration.Tests/GlobalUsings.cs index 4bde9c0..1490c90 100644 --- a/tests/LocalStack.Client.Integration.Tests/GlobalUsings.cs +++ b/tests/LocalStack.Client.Integration.Tests/GlobalUsings.cs @@ -1,4 +1,5 @@ -global using System.Reflection; +global using System; +global using System.Reflection; global using Amazon; global using Amazon.Amplify; diff --git a/tests/LocalStack.Client.Tests/Mocks/Extensions.cs b/tests/LocalStack.Client.Tests/Mocks/Extensions.cs new file mode 100644 index 0000000..48598ba --- /dev/null +++ b/tests/LocalStack.Client.Tests/Mocks/Extensions.cs @@ -0,0 +1,20 @@ +namespace LocalStack.Client.Tests.Mocks; + +internal static class Extensions +{ + public static (string awsAccessKeyId, string awsAccessKey, string awsSessionToken, string regionName) + SetupDefault(this Mock mock, + string awsAccessKeyId = "AwsAccessKeyId", + string awsAccessKey = "AwsAccessKey", + string awsSessionToken = "AwsSessionToken", + string regionName = "eu-central-1") + { + + mock.SetupGet(options => options.AwsAccessKeyId).Returns(awsAccessKeyId); + mock.SetupGet(options => options.AwsAccessKey).Returns(awsAccessKey); + mock.SetupGet(options => options.AwsSessionToken).Returns(awsSessionToken); + mock.SetupGet(options => options.RegionName).Returns(regionName); + + return (awsAccessKeyId, awsAccessKey, awsSessionToken, regionName); + } +} \ No newline at end of file diff --git a/tests/LocalStack.Client.Tests/SessionTests/SessionOptionsTests.cs b/tests/LocalStack.Client.Tests/SessionTests/SessionOptionsTests.cs index 8d5fe7b..0c1a010 100644 --- a/tests/LocalStack.Client.Tests/SessionTests/SessionOptionsTests.cs +++ b/tests/LocalStack.Client.Tests/SessionTests/SessionOptionsTests.cs @@ -10,7 +10,7 @@ public void SessionOptions_Should_Created_With_Default_Parameters_If_It_Created_ Assert.Equal(Constants.AwsAccessKeyId, sessionOptions.AwsAccessKeyId); Assert.Equal(Constants.AwsAccessKey, sessionOptions.AwsAccessKey); Assert.Equal(Constants.AwsSessionToken, sessionOptions.AwsSessionToken); - Assert.Equal(default, sessionOptions.RegionName); + Assert.Equal(Constants.RegionName, sessionOptions.RegionName); } [Fact] @@ -25,7 +25,7 @@ public void SessionOptions_Should_Created_With_Default_Parameters_If_It_Created_ Assert.Equal(Constants.AwsAccessKeyId, sessionOptions.AwsAccessKeyId); Assert.Equal(Constants.AwsAccessKey, sessionOptions.AwsAccessKey); Assert.Equal(Constants.AwsSessionToken, sessionOptions.AwsSessionToken); - Assert.Equal(default, sessionOptions.RegionName); + Assert.Equal(Constants.RegionName, sessionOptions.RegionName); } [Fact] @@ -38,7 +38,7 @@ public void AwsAccessKeyId_Property_Of_ConfigOptions_Should_Equal_To_Given_AwsAc Assert.Equal(awsAccessKeyId, sessionOptions.AwsAccessKeyId); Assert.Equal(Constants.AwsAccessKey, sessionOptions.AwsAccessKey); Assert.Equal(Constants.AwsSessionToken, sessionOptions.AwsSessionToken); - Assert.Equal(default, sessionOptions.RegionName); + Assert.Equal(Constants.RegionName, sessionOptions.RegionName); } [Fact] @@ -51,7 +51,7 @@ public void AwsAccessKey_Property_Of_ConfigOptions_Should_Equal_To_Given_AwsAcce Assert.Equal(Constants.AwsAccessKeyId, sessionOptions.AwsAccessKeyId); Assert.Equal(awsAccessKey, sessionOptions.AwsAccessKey); Assert.Equal(Constants.AwsSessionToken, sessionOptions.AwsSessionToken); - Assert.Equal(default, sessionOptions.RegionName); + Assert.Equal(Constants.RegionName, sessionOptions.RegionName); } [Fact] @@ -64,7 +64,7 @@ public void AwsSessionToken_Property_Of_ConfigOptions_Should_Equal_To_Given_AwsS Assert.Equal(Constants.AwsAccessKeyId, sessionOptions.AwsAccessKeyId); Assert.Equal(Constants.AwsAccessKey, sessionOptions.AwsAccessKey); Assert.Equal(awsSessionToken, sessionOptions.AwsSessionToken); - Assert.Equal(default, sessionOptions.RegionName); + Assert.Equal(Constants.RegionName, sessionOptions.RegionName); } [Fact] diff --git a/tests/LocalStack.Client.Tests/SessionTests/SessionTests.cs b/tests/LocalStack.Client.Tests/SessionTests/SessionTests.cs index 63a2d39..cf3fb38 100644 --- a/tests/LocalStack.Client.Tests/SessionTests/SessionTests.cs +++ b/tests/LocalStack.Client.Tests/SessionTests/SessionTests.cs @@ -3,11 +3,12 @@ public class SessionTests { [Fact] - public void CreateClientByImplementation_Should_Throw_InvalidOperationException_If_Given_ServiceId_Is_Not_Supported() + public void CreateClientByImplementation_Should_Throw_NotSupportedClientException_If_Given_ServiceId_Is_Not_Supported() { var mockSession = MockSession.Create(); IServiceMetadata mockServiceMetadata = new MockServiceMetadata(); + mockSession.SessionOptionsMock.SetupDefault(); mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => null); @@ -16,6 +17,18 @@ public void CreateClientByImplementation_Should_Throw_InvalidOperationException_ mockSession.ConfigMock.Verify(config => config.GetAwsServiceEndpoint(It.Is(serviceId => serviceId == mockServiceMetadata.ServiceId)), Times.Once); } + [Fact] + public void CreateClientByImplementation_Should_Throw_MisconfiguredClientException_If_Given_RegionName_Property_Of_SessionOptions_IsNullOrEmpty_And_Given_UseServiceUrl_Is_False() + { + var mockSession = MockSession.Create(); + + mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(default(string)); + Assert.Throws(() => mockSession.CreateClientByImplementation(false)); + + mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(string.Empty); + Assert.Throws(() => mockSession.CreateClientByImplementation(false)); + } + [Fact] public void CreateClientByImplementation_Should_Create_SessionAWSCredentials_With_AwsAccessKeyId_And_AwsAccessKey_And_AwsSessionToken() { @@ -23,14 +36,8 @@ public void CreateClientByImplementation_Should_Create_SessionAWSCredentials_Wit IServiceMetadata mockServiceMetadata = new MockServiceMetadata(); var mockAwsServiceEndpoint = new MockAwsServiceEndpoint(); var mockClientConfig = new MockClientConfig(); - const string awsAccessKeyId = "AwsAccessKeyId"; - const string awsAccessKey = "AwsAccessKey"; - const string awsSessionToken = "AwsSessionToken"; - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns(awsAccessKeyId); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns(awsAccessKey); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns(awsSessionToken); - mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(default(string)); + (string awsAccessKeyId, string awsAccessKey, string awsSessionToken, _) = mockSession.SessionOptionsMock.SetupDefault(); mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); @@ -47,23 +54,21 @@ public void CreateClientByImplementation_Should_Create_SessionAWSCredentials_Wit var sessionAwsCredentials = (SessionAWSCredentials)awsCredentials; ImmutableCredentials immutableCredentials = sessionAwsCredentials.GetCredentials(); + Assert.Equal(awsAccessKeyId, immutableCredentials.AccessKey); Assert.Equal(awsAccessKey, immutableCredentials.SecretKey); Assert.Equal(awsSessionToken, immutableCredentials.Token); } [Fact] - public void CreateClientByImplementation_Should_Create_ClientConfig_With_ServiceURL_UseHttp_ProxyHost_ProxyPort() + public void CreateClientByImplementation_Should_Create_ClientConfig_With_UseHttp_True_And_ProxyHost_And_ProxyPort_By_ServiceEndpoint_Configuration() { var mockSession = MockSession.Create(); IServiceMetadata mockServiceMetadata = new MockServiceMetadata(); var mockAwsServiceEndpoint = new MockAwsServiceEndpoint(); var mockClientConfig = new MockClientConfig(); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); - mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(default(string)); + mockSession.SessionOptionsMock.SetupDefault(); mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); @@ -76,7 +81,6 @@ public void CreateClientByImplementation_Should_Create_ClientConfig_With_Service IClientConfig clientConfig = mockAmazonServiceClient.Config; - Assert.Equal(mockAwsServiceEndpoint.ServiceUrl, clientConfig.ServiceURL); Assert.True(clientConfig.UseHttp); Assert.Equal(mockAwsServiceEndpoint.Host, clientConfig.ProxyHost); Assert.Equal(mockAwsServiceEndpoint.Port, clientConfig.ProxyPort); @@ -98,10 +102,7 @@ public void CreateClientByImplementation_Should_Set_RegionEndpoint_By_RegionName var mockAwsServiceEndpoint = new MockAwsServiceEndpoint(); var mockClientConfig = new MockClientConfig(); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); - mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(systemName); + (_, _, _, string regionName) = mockSession.SessionOptionsMock.SetupDefault(regionName: systemName); mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); @@ -113,13 +114,13 @@ public void CreateClientByImplementation_Should_Set_RegionEndpoint_By_RegionName Assert.NotNull(mockAmazonServiceClient); Assert.Null(mockAmazonServiceClient.Config.ServiceURL); - Assert.Equal(RegionEndpoint.GetBySystemName(systemName), mockAmazonServiceClient.Config.RegionEndpoint); + Assert.Equal(RegionEndpoint.GetBySystemName(regionName), mockAmazonServiceClient.Config.RegionEndpoint); } [Theory, - InlineData(""), + InlineData("sa-east-1"), InlineData(null)] - public void CreateClientByImplementation_Should_Set_ServiceUrl_And_RegionEndpoint_To_Null_If_RegionName_Property_Of_SessionOptions_And_ServiceUrl_IsNull_Or_Empty(string systemName) + public void CreateClientByImplementation_Should_Set_ServiceUrl_By_ServiceEndpoint_Configuration_And_RegionEndpoint_To_Null_If_Given_UseServiceUrl_Parameter_Is_True_Regardless_Of_Use_RegionName_Property_Of_SessionOptions_Has_Value_Or_Not(string systemName) { var mockSession = MockSession.Create(); @@ -127,10 +128,7 @@ public void CreateClientByImplementation_Should_Set_ServiceUrl_And_RegionEndpoin var mockAwsServiceEndpoint = new MockAwsServiceEndpoint(); var mockClientConfig = new MockClientConfig(); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); - mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(systemName); + mockSession.SessionOptionsMock.SetupDefault(regionName: systemName); mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); @@ -138,7 +136,7 @@ public void CreateClientByImplementation_Should_Set_ServiceUrl_And_RegionEndpoin mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint); - var mockAmazonServiceClient = mockSession.CreateClientByImplementation(); + var mockAmazonServiceClient = mockSession.CreateClientByImplementation(useServiceUrl: true); Assert.NotNull(mockAmazonServiceClient); Assert.Null(mockAmazonServiceClient.Config.RegionEndpoint); @@ -153,10 +151,7 @@ public void CreateClientByImplementation_Should_Pass_The_ClientConfig_To_SetForc var mockAwsServiceEndpoint = new MockAwsServiceEndpoint(); var mockClientConfig = new MockClientConfig(); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); - mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(default(string)); + mockSession.SessionOptionsMock.SetupDefault(); mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); @@ -169,18 +164,17 @@ public void CreateClientByImplementation_Should_Pass_The_ClientConfig_To_SetForc mockSession.SessionReflectionMock.Verify(reflection => reflection.SetForcePathStyle(It.Is(config => config == mockClientConfig), true), Times.Once); } - [Fact] - public void CreateClientByImplementation_Should_Create_AmazonServiceClient_By_Given_Generic_Type() + [Theory, + InlineData(false), + InlineData(true)] + public void CreateClientByImplementation_Should_Create_AmazonServiceClient_By_Given_Generic_Type_And_Configure_ServiceUrl_Or_RegionEndpoint_By_Given_UseServiceUrl_Parameter(bool useServiceUrl) { var mockSession = MockSession.Create(); IServiceMetadata mockServiceMetadata = new MockServiceMetadata(); var mockAwsServiceEndpoint = new MockAwsServiceEndpoint(); var mockClientConfig = new MockClientConfig(); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); - mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(default(string)); + (_, _, _, string regionName) = mockSession.SessionOptionsMock.SetupDefault(); mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); @@ -188,9 +182,23 @@ public void CreateClientByImplementation_Should_Create_AmazonServiceClient_By_Gi mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint); - var mockAmazonServiceClient = mockSession.CreateClientByImplementation(); + var mockAmazonServiceClient = mockSession.CreateClientByImplementation(useServiceUrl); + Assert.NotNull(mockAmazonServiceClient); + if (useServiceUrl) + { + Assert.Null(mockAmazonServiceClient.Config.RegionEndpoint); + Assert.NotNull(mockAmazonServiceClient.Config.ServiceURL); + Assert.Equal(mockAwsServiceEndpoint.ServiceUrl, mockAmazonServiceClient.Config.ServiceURL); + } + else + { + Assert.Null(mockAmazonServiceClient.Config.ServiceURL); + Assert.NotNull(mockAmazonServiceClient.Config.RegionEndpoint); + Assert.Equal(RegionEndpoint.GetBySystemName(regionName), mockAmazonServiceClient.Config.RegionEndpoint); + } + mockSession.ConfigMock.Verify(config => config.GetAwsServiceEndpoint(It.Is(serviceId => serviceId == mockServiceMetadata.ServiceId)), Times.Once); mockSession.SessionReflectionMock.Verify(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient))), Times.Once); mockSession.SessionReflectionMock.Verify(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient))), Times.Once); @@ -206,19 +214,32 @@ public void CreateClientByInterface_Should_Throw_AmazonClientException_If_Given_ } [Fact] - public void CreateClientByInterface_Should_Throw_InvalidOperationException_If_Given_ServiceId_Is_Not_Supported() + public void CreateClientByInterface_Should_Throw_NotSupportedClientException_If_Given_ServiceId_Is_Not_Supported() { var mockSession = MockSession.Create(); IServiceMetadata mockServiceMetadata = new MockServiceMetadata(); + mockSession.SessionOptionsMock.SetupDefault(); mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => null); - Assert.Throws(() => mockSession.CreateClientByInterface()); + Assert.Throws(() => mockSession.CreateClientByInterface()); mockSession.ConfigMock.Verify(config => config.GetAwsServiceEndpoint(It.Is(serviceId => serviceId == mockServiceMetadata.ServiceId)), Times.Once); } + [Fact] + public void CreateClientByInterface_Should_Throw_MisconfiguredClientException_If_Given_RegionName_Property_Of_SessionOptions_IsNullOrEmpty_And_Given_UseServiceUrl_Is_False() + { + var mockSession = MockSession.Create(); + + mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(default(string)); + Assert.Throws(() => mockSession.CreateClientByInterface(false)); + + mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(string.Empty); + Assert.Throws(() => mockSession.CreateClientByInterface(false)); + } + [Fact] public void CreateClientByInterface_Should_Create_SessionAWSCredentials_With_AwsAccessKeyId_And_AwsAccessKey_And_AwsSessionToken() { @@ -226,14 +247,8 @@ public void CreateClientByInterface_Should_Create_SessionAWSCredentials_With_Aws IServiceMetadata mockServiceMetadata = new MockServiceMetadata(); var mockAwsServiceEndpoint = new MockAwsServiceEndpoint(); var mockClientConfig = new MockClientConfig(); - const string awsAccessKeyId = "AwsAccessKeyId"; - const string awsAccessKey = "AwsAccessKey"; - const string awsSessionToken = "AwsSessionToken"; - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns(awsAccessKeyId); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns(awsAccessKey); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns(awsSessionToken); - mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(default(string)); + (string awsAccessKeyId, string awsAccessKey, string awsSessionToken, _) = mockSession.SessionOptionsMock.SetupDefault(); mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); @@ -250,23 +265,21 @@ public void CreateClientByInterface_Should_Create_SessionAWSCredentials_With_Aws var sessionAwsCredentials = (SessionAWSCredentials)awsCredentials; ImmutableCredentials immutableCredentials = sessionAwsCredentials.GetCredentials(); + Assert.Equal(awsAccessKeyId, immutableCredentials.AccessKey); Assert.Equal(awsAccessKey, immutableCredentials.SecretKey); Assert.Equal(awsSessionToken, immutableCredentials.Token); } [Fact] - public void CreateClientByInterface_Should_Create_ClientConfig_With_ServiceURL_UseHttp_ProxyHost_ProxyPort() + public void CreateClientByInterface_Should_Create_ClientConfig_With_UseHttp_True_And_ProxyHost_And_ProxyPort_By_ServiceEndpoint_Configuration() { var mockSession = MockSession.Create(); IServiceMetadata mockServiceMetadata = new MockServiceMetadata(); var mockAwsServiceEndpoint = new MockAwsServiceEndpoint(); var mockClientConfig = new MockClientConfig(); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); - mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(default(string)); + mockSession.SessionOptionsMock.SetupDefault(); mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); @@ -278,7 +291,7 @@ public void CreateClientByInterface_Should_Create_ClientConfig_With_ServiceURL_U Assert.NotNull(mockAmazonServiceClient); IClientConfig clientConfig = mockAmazonServiceClient.Config; - Assert.Equal(mockAwsServiceEndpoint.ServiceUrl, clientConfig.ServiceURL); + Assert.True(clientConfig.UseHttp); Assert.Equal(mockAwsServiceEndpoint.Host, clientConfig.ProxyHost); Assert.Equal(mockAwsServiceEndpoint.Port, clientConfig.ProxyPort); @@ -300,10 +313,7 @@ public void CreateClientByInterface_Should_Set_RegionEndpoint_By_RegionName_Prop var mockAwsServiceEndpoint = new MockAwsServiceEndpoint(); var mockClientConfig = new MockClientConfig(); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); - mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(systemName); + (_, _, _, string regionName) = mockSession.SessionOptionsMock.SetupDefault(regionName: systemName); mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); @@ -315,13 +325,14 @@ public void CreateClientByInterface_Should_Set_RegionEndpoint_By_RegionName_Prop Assert.NotNull(mockAmazonServiceClient); Assert.Null(mockAmazonServiceClient.Config.ServiceURL); - Assert.Equal(RegionEndpoint.GetBySystemName(systemName), mockAmazonServiceClient.Config.RegionEndpoint); + Assert.Equal(RegionEndpoint.GetBySystemName(regionName), mockAmazonServiceClient.Config.RegionEndpoint); } + [Theory, - InlineData(""), + InlineData("sa-east-1"), InlineData(null)] - public void CreateClientByInterface_Should_Set_ServiceUrl_And_RegionEndpoint_To_Null_If_RegionName_Property_Of_SessionOptions_And_ServiceUrl_IsNull_Or_Empty(string systemName) + public void CreateClientByInterface_Should_Set_ServiceUrl_By_ServiceEndpoint_Configuration_And_RegionEndpoint_To_Null_If_Given_UseServiceUrl_Parameter_Is_True_Regardless_Of_Use_RegionName_Property_Of_SessionOptions_Has_Value_Or_Not(string systemName) { var mockSession = MockSession.Create(); @@ -329,10 +340,7 @@ public void CreateClientByInterface_Should_Set_ServiceUrl_And_RegionEndpoint_To_ var mockAwsServiceEndpoint = new MockAwsServiceEndpoint(); var mockClientConfig = new MockClientConfig(); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); - mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(systemName); + mockSession.SessionOptionsMock.SetupDefault(regionName: systemName); mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); @@ -340,7 +348,7 @@ public void CreateClientByInterface_Should_Set_ServiceUrl_And_RegionEndpoint_To_ mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint); - var mockAmazonServiceClient = mockSession.CreateClientByInterface() as MockAmazonServiceClient; + var mockAmazonServiceClient = mockSession.CreateClientByInterface(useServiceUrl: true) as MockAmazonServiceClient; Assert.NotNull(mockAmazonServiceClient); Assert.Null(mockAmazonServiceClient.Config.RegionEndpoint); @@ -355,10 +363,7 @@ public void CreateClientByInterface_Should_Pass_The_ClientConfig_To_SetForcePath var mockAwsServiceEndpoint = new MockAwsServiceEndpoint(); var mockClientConfig = new MockClientConfig(); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); - mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(default(string)); + mockSession.SessionOptionsMock.SetupDefault(); mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); @@ -371,18 +376,17 @@ public void CreateClientByInterface_Should_Pass_The_ClientConfig_To_SetForcePath mockSession.SessionReflectionMock.Verify(reflection => reflection.SetForcePathStyle(It.Is(config => config == mockClientConfig), true), Times.Once); } - [Fact] - public void CreateClientByInterface_Should_Create_AmazonServiceClient_By_Given_Generic_Type() + [Theory, + InlineData(false), + InlineData(true)] + public void CreateClientByInterface_Should_Create_AmazonServiceClient_By_Given_Generic_Type_And_Configure_ServiceUrl_Or_RegionEndpoint_By_Given_UseServiceUrl_Parameter(bool useServiceUrl) { var mockSession = MockSession.Create(); IServiceMetadata mockServiceMetadata = new MockServiceMetadata(); var mockAwsServiceEndpoint = new MockAwsServiceEndpoint(); var mockClientConfig = new MockClientConfig(); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKeyId).Returns("AwsAccessKeyId"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsAccessKey).Returns("AwsAccessKey"); - mockSession.SessionOptionsMock.SetupGet(options => options.AwsSessionToken).Returns("AwsSessionToken"); - mockSession.SessionOptionsMock.SetupGet(options => options.RegionName).Returns(default(string)); + (_, _, _, string regionName) = mockSession.SessionOptionsMock.SetupDefault(); mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata); mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig); @@ -390,9 +394,23 @@ public void CreateClientByInterface_Should_Create_AmazonServiceClient_By_Given_G mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint); - var mockAmazonServiceClient = mockSession.CreateClientByInterface() as MockAmazonServiceClient; + var mockAmazonServiceClient = mockSession.CreateClientByInterface(useServiceUrl) as MockAmazonServiceClient; + Assert.NotNull(mockAmazonServiceClient); + if (useServiceUrl) + { + Assert.Null(mockAmazonServiceClient.Config.RegionEndpoint); + Assert.NotNull(mockAmazonServiceClient.Config.ServiceURL); + Assert.Equal(mockAwsServiceEndpoint.ServiceUrl, mockAmazonServiceClient.Config.ServiceURL); + } + else + { + Assert.Null(mockAmazonServiceClient.Config.ServiceURL); + Assert.NotNull(mockAmazonServiceClient.Config.RegionEndpoint); + Assert.Equal(RegionEndpoint.GetBySystemName(regionName), mockAmazonServiceClient.Config.RegionEndpoint); + } + mockSession.ConfigMock.Verify(config => config.GetAwsServiceEndpoint(It.Is(serviceId => serviceId == mockServiceMetadata.ServiceId)), Times.Once); mockSession.SessionReflectionMock.Verify(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient))), Times.Once); mockSession.SessionReflectionMock.Verify(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient))), Times.Once); From c7df292bc95d6b4ce80f4a7b7731b9b95317826c Mon Sep 17 00:00:00 2001 From: "deniz_irgin@yahoo.com" Date: Sun, 28 Nov 2021 17:11:09 +0300 Subject: [PATCH 16/24] add force service url support for the services that not support region endpoint --- LocalStack.sln | 1 - src/Directory.Build.props | 2 +- .../LocalStack.Client.Extensions.csproj | 30 +------- .../ServiceCollectionExtensions.cs | 69 +++++++++++++------ .../GlobalUsings.cs | 4 +- .../ServiceCollectionExtensionsTests.cs | 67 +++++++++++------- 6 files changed, 94 insertions(+), 79 deletions(-) diff --git a/LocalStack.sln b/LocalStack.sln index 3f33526..5d987d8 100644 --- a/LocalStack.sln +++ b/LocalStack.sln @@ -29,7 +29,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Solution Items", "_Solutio build.sh = build.sh src\Directory.Build.props = src\Directory.Build.props .github\workflows\publish-nuget.yml = .github\workflows\publish-nuget.yml - .github\workflows\test-report.yml = .github\workflows\test-report.yml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LocalStack.Client.Extensions", "src\LocalStack.Client.Extensions\LocalStack.Client.Extensions.csproj", "{74035094-A726-44E2-9B88-42D6425D8548}" diff --git a/src/Directory.Build.props b/src/Directory.Build.props index a2a7457..53b7949 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,6 +5,6 @@ LocalStack.NET https://github.com/localstack-dotnet/localstack-dotnet-client localstack-dotnet-square.png - 1.2 + 1.3 \ No newline at end of file diff --git a/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj b/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj index c82b5a1..b60431c 100644 --- a/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj +++ b/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj @@ -13,7 +13,7 @@ aws-sdk, localstack, client-library, dotnet, dotnet-core LICENSE.txt - 1.1.0 + 1.1.2 @@ -27,30 +27,6 @@ - - - - @@ -69,8 +45,4 @@ - - - - diff --git a/src/LocalStack.Client.Extensions/ServiceCollectionExtensions.cs b/src/LocalStack.Client.Extensions/ServiceCollectionExtensions.cs index 55482d4..9df2c60 100644 --- a/src/LocalStack.Client.Extensions/ServiceCollectionExtensions.cs +++ b/src/LocalStack.Client.Extensions/ServiceCollectionExtensions.cs @@ -7,9 +7,11 @@ public static class ServiceCollectionExtensions public static IServiceCollection AddLocalStack(this IServiceCollection collection, IConfiguration configuration) { collection.Configure(options => configuration.GetSection(LocalStackSectionName).Bind(options, c => c.BindNonPublicProperties = true)); + collection.Configure(options => configuration.GetSection(LocalStackSectionName) .GetSection(nameof(LocalStackOptions.Session)) .Bind(options, c => c.BindNonPublicProperties = true)); + collection.Configure(options => configuration.GetSection(LocalStackSectionName) .GetSection(nameof(LocalStackOptions.Config)) .Bind(options, c => c.BindNonPublicProperties = true)); @@ -39,11 +41,12 @@ public static IServiceCollection AddDefaultAwsOptions(this IServiceCollection co /// The AWS service interface, like IAmazonS3. /// /// The lifetime of the service client created. The default is Singleton. + /// (LocalStack) Determines whether the service client will use RegionEndpoint or ServiceUrl. The default is false. /// Returns back the IServiceCollection to continue the fluent system of IServiceCollection. - public static IServiceCollection AddAwsService(this IServiceCollection collection, ServiceLifetime lifetime = ServiceLifetime.Singleton) + public static IServiceCollection AddAwsService(this IServiceCollection collection, ServiceLifetime lifetime = ServiceLifetime.Singleton, bool useServiceUrl = false) where TService : IAmazonService { - return AddAwsService(collection, null, lifetime); + return AddAwsService(collection, null, lifetime, useServiceUrl); } /// @@ -55,12 +58,15 @@ public static IServiceCollection AddAwsService(this IServiceCollection /// /// The AWS options used to create the service client overriding the default AWS options added using AddDefaultAWSOptions. /// The lifetime of the service client created. The default is Singleton. + /// (LocalStack) Determines whether the service client will use RegionEndpoint or ServiceUrl. The default is false. /// Returns back the IServiceCollection to continue the fluent system of IServiceCollection. - public static IServiceCollection AddAwsService(this IServiceCollection collection, + public static IServiceCollection AddAwsService(this IServiceCollection collection, AWSOptions options, - ServiceLifetime lifetime = ServiceLifetime.Singleton) where TService : IAmazonService + ServiceLifetime lifetime = ServiceLifetime.Singleton, + bool useServiceUrl = false) + where TService : IAmazonService { - ServiceDescriptor descriptor = GetServiceFactoryDescriptor(options, lifetime); + ServiceDescriptor descriptor = GetServiceFactoryDescriptor(options, lifetime, useServiceUrl); collection.Add(descriptor); @@ -75,11 +81,14 @@ public static IServiceCollection AddAwsService(this IServiceCollection /// The AWS service interface, like IAmazonS3. /// /// The lifetime of the service client created. The default is Singleton. + /// (LocalStack) Determines whether the service client will use RegionEndpoint or ServiceUrl. The default is false. /// Returns back the IServiceCollection to continue the fluent system of IServiceCollection. - public static IServiceCollection AddAWSServiceLocalStack(this IServiceCollection collection, ServiceLifetime lifetime = ServiceLifetime.Singleton) + public static IServiceCollection AddAWSServiceLocalStack(this IServiceCollection collection, + ServiceLifetime lifetime = ServiceLifetime.Singleton, + bool useServiceUrl = false) where TService : IAmazonService { - return AddAWSServiceLocalStack(collection, null, lifetime); + return AddAWSServiceLocalStack(collection, null, lifetime, useServiceUrl); } /// @@ -91,12 +100,15 @@ public static IServiceCollection AddAWSServiceLocalStack(this IService /// /// The AWS options used to create the service client overriding the default AWS options added using AddDefaultAWSOptions. /// The lifetime of the service client created. The default is Singleton. + /// (LocalStack) Determines whether the service client will use RegionEndpoint or ServiceUrl. The default is false. /// Returns back the IServiceCollection to continue the fluent system of IServiceCollection. - public static IServiceCollection AddAWSServiceLocalStack(this IServiceCollection collection, + public static IServiceCollection AddAWSServiceLocalStack(this IServiceCollection collection, AWSOptions options, - ServiceLifetime lifetime = ServiceLifetime.Singleton) where TService : IAmazonService + ServiceLifetime lifetime = ServiceLifetime.Singleton, + bool useServiceUrl = false) + where TService : IAmazonService { - return AddAwsService(collection, options, lifetime); + return AddAwsService(collection, options, lifetime, useServiceUrl); } /// @@ -107,8 +119,11 @@ public static IServiceCollection AddAWSServiceLocalStack(this IService /// The AWS service interface, like IAmazonS3. /// /// The lifetime of the service client created. The default is Singleton. + /// (LocalStack) Determines whether the service client will use RegionEndpoint or ServiceUrl. The default is false. /// Returns back the IServiceCollection to continue the fluent system of IServiceCollection. - public static IServiceCollection TryAddAwsService(this IServiceCollection collection, ServiceLifetime lifetime = ServiceLifetime.Singleton) + public static IServiceCollection TryAddAwsService(this IServiceCollection collection, + ServiceLifetime lifetime = ServiceLifetime.Singleton, + bool useServiceUrl = false) where TService : IAmazonService { return TryAddAwsService(collection, null, lifetime); @@ -123,11 +138,15 @@ public static IServiceCollection TryAddAwsService(this IServiceCollect /// /// The AWS options used to create the service client overriding the default AWS options added using AddDefaultAWSOptions. /// The lifetime of the service client created. The default is Singleton. + /// (LocalStack) Determines whether the service client will use RegionEndpoint or ServiceUrl. The default is false. /// Returns back the IServiceCollection to continue the fluent system of IServiceCollection. - public static IServiceCollection TryAddAwsService(this IServiceCollection collection, AWSOptions options, - ServiceLifetime lifetime = ServiceLifetime.Singleton) where TService : IAmazonService + public static IServiceCollection TryAddAwsService(this IServiceCollection collection, + AWSOptions options, + ServiceLifetime lifetime = ServiceLifetime.Singleton, + bool useServiceUrl = false) + where TService : IAmazonService { - ServiceDescriptor descriptor = GetServiceFactoryDescriptor(options, lifetime); + ServiceDescriptor descriptor = GetServiceFactoryDescriptor(options, lifetime, useServiceUrl); collection.TryAdd(descriptor); return collection; @@ -141,11 +160,14 @@ public static IServiceCollection TryAddAwsService(this IServiceCollect /// The AWS service interface, like IAmazonS3. /// /// The lifetime of the service client created. The default is Singleton. + /// (LocalStack) Determines whether the service client will use RegionEndpoint or ServiceUrl. The default is false. /// Returns back the IServiceCollection to continue the fluent system of IServiceCollection. - public static IServiceCollection TryAddAWSServiceLocalStack(this IServiceCollection collection, ServiceLifetime lifetime = ServiceLifetime.Singleton) + public static IServiceCollection TryAddAWSServiceLocalStack(this IServiceCollection collection, + ServiceLifetime lifetime = ServiceLifetime.Singleton, + bool useServiceUrl = false) where TService : IAmazonService { - return TryAddAWSServiceLocalStack(collection, null, lifetime); + return TryAddAWSServiceLocalStack(collection, null, lifetime, useServiceUrl); } /// @@ -157,11 +179,15 @@ public static IServiceCollection TryAddAWSServiceLocalStack(this IServ /// /// The AWS options used to create the service client overriding the default AWS options added using AddDefaultAWSOptions. /// The lifetime of the service client created. The default is Singleton. + /// (LocalStack) Determines whether the service client will use RegionEndpoint or ServiceUrl. The default is false. /// Returns back the IServiceCollection to continue the fluent system of IServiceCollection. - public static IServiceCollection TryAddAWSServiceLocalStack(this IServiceCollection collection, AWSOptions options, - ServiceLifetime lifetime = ServiceLifetime.Singleton) where TService : IAmazonService + public static IServiceCollection TryAddAWSServiceLocalStack(this IServiceCollection collection, + AWSOptions options, + ServiceLifetime lifetime = ServiceLifetime.Singleton, + bool useServiceUrl = false) + where TService : IAmazonService { - return TryAddAwsService(collection, options, lifetime); + return TryAddAwsService(collection, options, lifetime, useServiceUrl); } private static IServiceCollection AddLocalStackServices(this IServiceCollection services) @@ -186,7 +212,8 @@ private static IServiceCollection AddLocalStackServices(this IServiceCollection return services; } - private static ServiceDescriptor GetServiceFactoryDescriptor(AWSOptions options, ServiceLifetime lifetime) where TService : IAmazonService + private static ServiceDescriptor GetServiceFactoryDescriptor(AWSOptions options, ServiceLifetime lifetime, bool useServiceUrl = false) + where TService : IAmazonService { var descriptor = new ServiceDescriptor(typeof(TService), provider => { @@ -197,7 +224,7 @@ private static ServiceDescriptor GetServiceFactoryDescriptor(AWSOption if (localStackOptions.UseLocalStack) { var session = provider.GetRequiredService(); - serviceInstance = session.CreateClientByInterface(); + serviceInstance = session.CreateClientByInterface(useServiceUrl); } else { diff --git a/tests/LocalStack.Client.Extensions.Tests/GlobalUsings.cs b/tests/LocalStack.Client.Extensions.Tests/GlobalUsings.cs index aa29b51..5dc6d26 100644 --- a/tests/LocalStack.Client.Extensions.Tests/GlobalUsings.cs +++ b/tests/LocalStack.Client.Extensions.Tests/GlobalUsings.cs @@ -7,6 +7,7 @@ global using Microsoft.Extensions.DependencyInjection.Extensions; global using Microsoft.Extensions.Options; +global using Amazon; global using Amazon.Extensions.NETCore.Setup; global using Amazon.Runtime; @@ -16,6 +17,7 @@ global using LocalStack.Client.Options; global using LocalStack.Client.Tests.Mocks.MockServiceClients; global using LocalStack.Client.Utils; +global using LocalStack.Client.Models; global using Moq; -global using Xunit; \ No newline at end of file +global using Xunit; diff --git a/tests/LocalStack.Client.Extensions.Tests/ServiceCollectionExtensionsTests.cs b/tests/LocalStack.Client.Extensions.Tests/ServiceCollectionExtensionsTests.cs index 127e338..6d56171 100644 --- a/tests/LocalStack.Client.Extensions.Tests/ServiceCollectionExtensionsTests.cs +++ b/tests/LocalStack.Client.Extensions.Tests/ServiceCollectionExtensionsTests.cs @@ -1,6 +1,4 @@ -using Amazon; - -namespace LocalStack.Client.Extensions.Tests; +namespace LocalStack.Client.Extensions.Tests; public class ServiceCollectionExtensionsTests { @@ -211,7 +209,7 @@ public void AddLocalStackServices_Should_Add_ISessionReflection_To_Container_As_ InlineData(false, "sa-east-1")] public void GetRequiredService_Should_Return_AmazonService_That_Configured_For_LocalStack_If_UseLocalStack_Is_True(bool useAlternateNameAddServiceMethod, string systemName) { - var configurationValue = new Dictionary { { "LocalStack:UseLocalStack", "true" }, {"LocalStack:Session:RegionName", systemName} }; + var configurationValue = new Dictionary { { "LocalStack:UseLocalStack", "true" }, { "LocalStack:Session:RegionName", systemName } }; IConfiguration configuration = new ConfigurationBuilder().AddInMemoryCollection(configurationValue).Build(); var mockServiceMetadata = new MockServiceMetadata(); @@ -248,10 +246,12 @@ public void GetRequiredService_Should_Return_AmazonService_That_Configured_For_L Assert.Equal(RegionEndpoint.GetBySystemName(systemName), clientConfig.RegionEndpoint); } - [Theory, - InlineData(true), - InlineData(false)] - public void GetRequiredService_Should_Return_AmazonService_With_Service_Url_That_Configured_For_LocalStack_If_UseLocalStack_Is_True_And_RegionName_Is_Null(bool useAlternateNameAddServiceMethod) + [Theory, + InlineData(false, false), + InlineData(false, true), + InlineData(true, true), + InlineData(true, false)] + public void GetRequiredService_Should_Return_AmazonService_That_Configured_For_LocalStack_If_UseLocalStack_Is_True_And_Should_Configure_ServiceUrl_Or_RegionEndpoint_By_Given_UseServiceUrl_Parameter(bool useAlternateNameAddServiceMethod, bool useServiceUrl) { var configurationValue = new Dictionary { { "LocalStack:UseLocalStack", "true" } }; IConfiguration configuration = new ConfigurationBuilder().AddInMemoryCollection(configurationValue).Build(); @@ -271,11 +271,11 @@ public void GetRequiredService_Should_Return_AmazonService_With_Service_Url_That if (!useAlternateNameAddServiceMethod) { - serviceCollection.AddAwsService(); + serviceCollection.AddAwsService(useServiceUrl: useServiceUrl); } else { - serviceCollection.AddAWSServiceLocalStack(); + serviceCollection.AddAWSServiceLocalStack(useServiceUrl: useServiceUrl); } ServiceProvider provider = serviceCollection.BuildServiceProvider(); @@ -287,19 +287,35 @@ public void GetRequiredService_Should_Return_AmazonService_With_Service_Url_That Assert.True(clientConfig.UseHttp); Assert.Equal(mockAwsServiceEndpoint.Host, clientConfig.ProxyHost); Assert.Equal(mockAwsServiceEndpoint.Port, clientConfig.ProxyPort); - Assert.Null(clientConfig.RegionEndpoint); - Assert.NotNull(clientConfig.ServiceURL); - Assert.Equal(mockAwsServiceEndpoint.ServiceUrl, clientConfig.ServiceURL); + + if (useServiceUrl) + { + Assert.Null(clientConfig.RegionEndpoint); + Assert.NotNull(clientConfig.ServiceURL); + Assert.Equal(mockAwsServiceEndpoint.ServiceUrl, clientConfig.ServiceURL); + } + else + { + Assert.Null(clientConfig.ServiceURL); + Assert.NotNull(clientConfig.RegionEndpoint); + Assert.Equal(RegionEndpoint.GetBySystemName(Constants.RegionName), clientConfig.RegionEndpoint); + } } - [Theory, - InlineData(false, 1, 0, false), - InlineData(true, 0, 1, false), - InlineData(false, 1, 0, true), - InlineData(true, 0, 1, true)] - public void GetRequiredService_Should_Use_Suitable_ClientFactory_To_Create_AwsService_By_UseLocalStack_Value( - bool useLocalStack, int awsClientFactoryInvolved, int sessionInvolved, bool useAlternateNameAddServiceMethod) + [Theory, + InlineData(false, false, false), + InlineData(false, false, true), + InlineData(true, false, false), + InlineData(true, false, true), + InlineData(false, true, false), + InlineData(false, true, true), + InlineData(true, true, false), + InlineData(true, true,true)] + public void GetRequiredService_Should_Use_Suitable_ClientFactory_To_Create_AwsService_By_UseLocalStack_Value(bool useLocalStack, bool useAlternateNameAddServiceMethod, bool useServiceUrl) { + int sessionInvolved = useLocalStack ? 1 : 0; + int awsClientFactoryInvolved = useLocalStack ? 0 : 1; + var configurationValue = new Dictionary { { "LocalStack:UseLocalStack", useLocalStack.ToString() } }; IConfiguration configuration = new ConfigurationBuilder().AddInMemoryCollection(configurationValue).Build(); @@ -316,24 +332,23 @@ public void GetRequiredService_Should_Use_Suitable_ClientFactory_To_Create_AwsSe if (!useAlternateNameAddServiceMethod) { - serviceCollection.AddAwsService(); + serviceCollection.AddAwsService(useServiceUrl:useServiceUrl); } else { - serviceCollection.AddAWSServiceLocalStack(); + serviceCollection.AddAWSServiceLocalStack(useServiceUrl:useServiceUrl); } ServiceProvider provider = serviceCollection.BuildServiceProvider(); - mockSession.Setup(session => session.CreateClientByInterface()).Returns(() => new MockAmazonServiceClient("tsada", "sadasdas", "sadasda", new MockClientConfig())); + mockSession.Setup(session => session.CreateClientByInterface(It.IsAny())).Returns(() => new MockAmazonServiceClient("tsada", "sadasdas", "sadasda", new MockClientConfig())); mockClientFactory.Setup(wrapper => wrapper.CreateServiceClient(It.IsAny(), It.IsAny())).Returns(() => new MockAmazonServiceClient("tsada", "sadasdas", "sadasda", new MockClientConfig())); var mockAmazonService = provider.GetRequiredService(); Assert.NotNull(mockAmazonService); - mockClientFactory.Verify(wrapper => wrapper.CreateServiceClient(It.IsAny(), It.IsAny()), - Times.Exactly(awsClientFactoryInvolved)); - mockSession.Verify(session => session.CreateClientByInterface(), Times.Exactly(sessionInvolved)); + mockClientFactory.Verify(wrapper => wrapper.CreateServiceClient(It.IsAny(), It.IsAny()), Times.Exactly(awsClientFactoryInvolved)); + mockSession.Verify(session => session.CreateClientByInterface(It.Is(b => b == useServiceUrl)), Times.Exactly(sessionInvolved)); } } From 18aa3d15315e4619b141e14d2b20e57ea6858859 Mon Sep 17 00:00:00 2001 From: "deniz_irgin@yahoo.com" Date: Sun, 28 Nov 2021 17:12:04 +0300 Subject: [PATCH 17/24] start to add new real life scenarios for functional tests --- .../Fixtures/LocalStackFixture.cs | 6 +- .../Fixtures/LocalStackLegacyFixture.cs | 26 ++-- .../GlobalUsings.cs | 26 ++++ .../LocalStack.Client.Functional.Tests.csproj | 46 ++++---- .../Scenarios/BaseScenario.cs | 16 +-- .../DynamoDb/BaseDynamoDbScenario.cs | 4 +- .../DynamoDb/DynamoDbLegacyScenario.cs | 2 +- .../Scenarios/DynamoDb/DynamoDbScenario.cs | 8 +- .../RealLife/SnsToSqsLegacyScenario.cs | 15 +++ .../Scenarios/RealLife/SnsToSqsScenario.cs | 85 ++++++++++++++ .../Scenarios/S3/BaseS3Scenario.cs | 4 +- .../Scenarios/S3/S3LegacyScenario.cs | 2 +- .../Scenarios/S3/S3Scenario.cs | 4 +- .../Scenarios/SNS/BaseSnsScenario.cs | 30 +++++ .../Scenarios/SNS/Models/JobCreatedEvent.cs | 3 + .../Scenarios/SNS/SnsLegacyScenario.cs | 15 +++ .../Scenarios/SNS/SnsScenario.cs | 111 ++++++++++++++++++ .../Scenarios/SQS/BaseSqsScenario.cs | 6 +- ...SqsLegacyTests.cs => SqsLegacyScenario.cs} | 6 +- .../Scenarios/SQS/SqsScenario.cs | 8 +- 20 files changed, 363 insertions(+), 60 deletions(-) create mode 100644 tests/LocalStack.Client.Functional.Tests/Scenarios/RealLife/SnsToSqsLegacyScenario.cs create mode 100644 tests/LocalStack.Client.Functional.Tests/Scenarios/RealLife/SnsToSqsScenario.cs create mode 100644 tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/BaseSnsScenario.cs create mode 100644 tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/Models/JobCreatedEvent.cs create mode 100644 tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/SnsLegacyScenario.cs create mode 100644 tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/SnsScenario.cs rename tests/LocalStack.Client.Functional.Tests/Scenarios/SQS/{SqsLegacyTests.cs => SqsLegacyScenario.cs} (64%) diff --git a/tests/LocalStack.Client.Functional.Tests/Fixtures/LocalStackFixture.cs b/tests/LocalStack.Client.Functional.Tests/Fixtures/LocalStackFixture.cs index c393f9f..d0c0e12 100644 --- a/tests/LocalStack.Client.Functional.Tests/Fixtures/LocalStackFixture.cs +++ b/tests/LocalStack.Client.Functional.Tests/Fixtures/LocalStackFixture.cs @@ -10,10 +10,10 @@ public LocalStackFixture() .WithName($"LocalStack-0.13.0-{DateTime.Now.Ticks}") .WithImage("localstack/localstack:0.13.0") .WithCleanUp(true) - .WithEnvironment("DEFAULT_REGION", "eu-central-1") - .WithEnvironment("SERVICES", "s3,dynamodb,sqs") + .WithEnvironment("SERVICES", "s3,dynamodb,sqs,sns") .WithEnvironment("DOCKER_HOST", "unix:///var/run/docker.sock") - .WithEnvironment("LS_LOG", "info") + .WithEnvironment("DEBUG", "1") + .WithEnvironment("LS_LOG", "trace") .WithPortBinding(4566, 4566); _localStackContainer = localStackBuilder.Build(); diff --git a/tests/LocalStack.Client.Functional.Tests/Fixtures/LocalStackLegacyFixture.cs b/tests/LocalStack.Client.Functional.Tests/Fixtures/LocalStackLegacyFixture.cs index c2b7a40..c68829e 100644 --- a/tests/LocalStack.Client.Functional.Tests/Fixtures/LocalStackLegacyFixture.cs +++ b/tests/LocalStack.Client.Functional.Tests/Fixtures/LocalStackLegacyFixture.cs @@ -6,17 +6,23 @@ public class LocalStackLegacyFixture : IAsyncLifetime public LocalStackLegacyFixture() { + int dynamoDbPort = AwsServiceEndpointMetadata.DynamoDb.Port; + int containerPort = AwsServiceEndpointMetadata.Sqs.Port; + int hostPort = AwsServiceEndpointMetadata.S3.Port; + int snsPort = AwsServiceEndpointMetadata.Sns.Port; + ITestcontainersBuilder localStackBuilder = new TestcontainersBuilder() - .WithName($"LocalStackLegacy-0.11.4-{DateTime.Now.Ticks}") - .WithImage("localstack/localstack:0.11.4") - .WithCleanUp(true) - .WithEnvironment("DEFAULT_REGION", "eu-central-1") - .WithEnvironment("SERVICES", "s3,dynamodb,sqs") - .WithEnvironment("DOCKER_HOST", "unix:///var/run/docker.sock") - .WithEnvironment("DEBUG", "1") - .WithPortBinding(4569, 4569) // dynamo - .WithPortBinding(4576, 4576) // sqs - .WithPortBinding(4572, 4572); // s3 + .WithName($"LocalStackLegacy-0.11.4-{DateTime.Now.Ticks}") + .WithImage("localstack/localstack:0.11.4") + .WithCleanUp(true) + .WithEnvironment("DEFAULT_REGION", "eu-central-1") + .WithEnvironment("SERVICES", "s3,dynamodb,sqs,sns") + .WithEnvironment("DOCKER_HOST", "unix:///var/run/docker.sock") + .WithEnvironment("DEBUG", "1") + .WithPortBinding(dynamoDbPort, dynamoDbPort) + .WithPortBinding(containerPort, containerPort) + .WithPortBinding(hostPort, hostPort) + .WithPortBinding(snsPort, snsPort); _localStackContainer = localStackBuilder.Build(); } diff --git a/tests/LocalStack.Client.Functional.Tests/GlobalUsings.cs b/tests/LocalStack.Client.Functional.Tests/GlobalUsings.cs index 1464afc..d91362d 100644 --- a/tests/LocalStack.Client.Functional.Tests/GlobalUsings.cs +++ b/tests/LocalStack.Client.Functional.Tests/GlobalUsings.cs @@ -1,5 +1,6 @@ global using System; global using System.Collections.Generic; +global using System.Dynamic; global using System.IO; global using System.Linq; global using System.Net; @@ -9,6 +10,7 @@ global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; +global using Amazon; global using Amazon.DynamoDBv2; global using Amazon.DynamoDBv2.DataModel; global using Amazon.DynamoDBv2.DocumentModel; @@ -18,16 +20,40 @@ global using Amazon.S3.Transfer; global using Amazon.SQS; global using Amazon.SQS.Model; +global using Amazon.SimpleNotificationService; +global using Amazon.SimpleNotificationService.Model; global using AutoFixture; +global using Newtonsoft.Json; +global using Newtonsoft.Json.Converters; + global using DotNet.Testcontainers.Containers.Builders; global using DotNet.Testcontainers.Containers.Modules; global using LocalStack.Client.Extensions; +global using LocalStack.Client.Enums; +global using LocalStack.Client.Contracts; global using LocalStack.Client.Extensions.Tests.Extensions; global using LocalStack.Client.Functional.Tests.Fixtures; global using LocalStack.Client.Functional.Tests.Scenarios.DynamoDb.Entities; global using LocalStack.Client.Functional.Tests.Scenarios.SQS.Models; +global using LocalStack.Client.Functional.Tests.Scenarios.SNS.Models; global using Xunit; + + +#if NETCOREAPP +namespace System.Runtime.CompilerServices +{ + using System.ComponentModel; + /// + /// Reserved to be used by the compiler for tracking metadata. + /// This class should not be used by developers in source code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class IsExternalInit + { + } +} +#endif \ No newline at end of file diff --git a/tests/LocalStack.Client.Functional.Tests/LocalStack.Client.Functional.Tests.csproj b/tests/LocalStack.Client.Functional.Tests/LocalStack.Client.Functional.Tests.csproj index a04a857..3554b12 100644 --- a/tests/LocalStack.Client.Functional.Tests/LocalStack.Client.Functional.Tests.csproj +++ b/tests/LocalStack.Client.Functional.Tests/LocalStack.Client.Functional.Tests.csproj @@ -1,38 +1,39 @@  - + netcoreapp3.1;net5.0;net6.0 latest - + - + - PreserveNewest + PreserveNewest - PreserveNewest - appsettings.json + PreserveNewest + appsettings.json - + - + + - all - runtime; build; native; contentfiles; analyzers + all + runtime; build; native; contentfiles; analyzers - all - runtime; build; native; contentfiles; analyzers + all + runtime; build; native; contentfiles; analyzers - all - runtime; build; native; contentfiles; analyzers; buildtransitive + all + runtime; build; native; contentfiles; analyzers; buildtransitive @@ -40,18 +41,23 @@ - + + - + - + - + - Always + Always - + + + + NETCOREAPP + diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/BaseScenario.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/BaseScenario.cs index e179cd7..5a58eee 100644 --- a/tests/LocalStack.Client.Functional.Tests/Scenarios/BaseScenario.cs +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/BaseScenario.cs @@ -2,18 +2,18 @@ public abstract class BaseScenario { - protected BaseScenario(TestFixture testFixture, string configFile) + protected BaseScenario(TestFixture testFixture, string configFile, bool useServiceUrl = false) { - TestFixture testFixture1 = testFixture; - - ConfigurationBuilder configurationBuilder = testFixture1.CreateConfigureAppConfiguration(configFile); + ConfigurationBuilder configurationBuilder = testFixture.CreateConfigureAppConfiguration(configFile); Configuration = configurationBuilder.Build(); - IServiceCollection serviceCollection = testFixture1.CreateServiceCollection(Configuration); + IServiceCollection serviceCollection = testFixture.CreateServiceCollection(Configuration); + serviceCollection - .AddAwsService() - .AddAwsService() - .AddAwsService(); + .AddAwsService(useServiceUrl: useServiceUrl) + .AddAwsService(useServiceUrl: useServiceUrl) + .AddAwsService(useServiceUrl: useServiceUrl) + .AddAwsService(useServiceUrl: useServiceUrl); ServiceProvider = serviceCollection.BuildServiceProvider(); } diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/BaseDynamoDbScenario.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/BaseDynamoDbScenario.cs index f950ade..e3b85e3 100644 --- a/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/BaseDynamoDbScenario.cs +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/BaseDynamoDbScenario.cs @@ -4,8 +4,8 @@ public abstract class BaseDynamoDbScenario : BaseScenario { protected const string TestTableName = "Movies"; - protected BaseDynamoDbScenario(TestFixture testFixture, string configFile) - : base(testFixture, configFile) + protected BaseDynamoDbScenario(TestFixture testFixture, string configFile, bool useServiceUrl = false) + : base(testFixture, configFile, useServiceUrl) { DynamoDb = ServiceProvider.GetRequiredService(); DynamoDbContext = new DynamoDBContext(DynamoDb); diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/DynamoDbLegacyScenario.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/DynamoDbLegacyScenario.cs index fc6359c..cd2487e 100644 --- a/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/DynamoDbLegacyScenario.cs +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/DynamoDbLegacyScenario.cs @@ -4,7 +4,7 @@ public class DynamoDbLegacyScenario : DynamoDbScenario { public DynamoDbLegacyScenario(TestFixture testFixture) - : base(testFixture, TestConstants.LegacyLocalStackConfig) + : base(testFixture, TestConstants.LegacyLocalStackConfig, true) { } } diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/DynamoDbScenario.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/DynamoDbScenario.cs index fe6ece2..6fd18aa 100644 --- a/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/DynamoDbScenario.cs +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/DynamoDb/DynamoDbScenario.cs @@ -1,10 +1,12 @@ -namespace LocalStack.Client.Functional.Tests.Scenarios.DynamoDb; +using JsonSerializer = System.Text.Json.JsonSerializer; + +namespace LocalStack.Client.Functional.Tests.Scenarios.DynamoDb; [Collection(nameof(LocalStackCollection))] public class DynamoDbScenario : BaseDynamoDbScenario { - public DynamoDbScenario(TestFixture testFixture, string configFile = TestConstants.LocalStackConfig) - : base(testFixture, configFile) + public DynamoDbScenario(TestFixture testFixture, string configFile = TestConstants.LocalStackConfig, bool useServiceUrl = false) + : base(testFixture, configFile, useServiceUrl) { } diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/RealLife/SnsToSqsLegacyScenario.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/RealLife/SnsToSqsLegacyScenario.cs new file mode 100644 index 0000000..4084e75 --- /dev/null +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/RealLife/SnsToSqsLegacyScenario.cs @@ -0,0 +1,15 @@ +namespace LocalStack.Client.Functional.Tests.Scenarios.RealLife; + +[Collection(nameof(LocalStackLegacyCollection))] +public class SnsToSqsLegacyScenario : SnsToSqsScenario +{ + public SnsToSqsLegacyScenario(TestFixture testFixture) + : base(testFixture, TestConstants.LegacyLocalStackConfig, true) + { + } + + public override Task Should_Create_A_SNS_Topic_And_SQS_Queue_Then_Subscribe_To_The_Topic_Using_SQS_Then_Publish_A_Message_To_Topic_And_Read_It_From_The_Queue() + { + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/RealLife/SnsToSqsScenario.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/RealLife/SnsToSqsScenario.cs new file mode 100644 index 0000000..d5bfee0 --- /dev/null +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/RealLife/SnsToSqsScenario.cs @@ -0,0 +1,85 @@ +using MessageAttributeValue = Amazon.SimpleNotificationService.Model.MessageAttributeValue; + +namespace LocalStack.Client.Functional.Tests.Scenarios.RealLife; + +[Collection(nameof(LocalStackCollection))] +public class SnsToSqsScenario : BaseScenario +{ + public SnsToSqsScenario(TestFixture testFixture, string configFile = TestConstants.LocalStackConfig, bool useServiceUrl = false) + : base(testFixture, configFile, useServiceUrl) + { + AmazonSimpleNotificationService = ServiceProvider.GetRequiredService(); + AmazonSqs = ServiceProvider.GetRequiredService(); + } + + protected IAmazonSimpleNotificationService AmazonSimpleNotificationService { get; set; } + + protected IAmazonSQS AmazonSqs { get; set; } + + [Fact] + public virtual async Task Should_Create_A_SNS_Topic_And_SQS_Queue_Then_Subscribe_To_The_Topic_Using_SQS_Then_Publish_A_Message_To_Topic_And_Read_It_From_The_Queue() + { + var topicName = Guid.NewGuid().ToString(); + var queueName = Guid.NewGuid().ToString(); + var jobCreatedEvent = new JobCreatedEvent(423565221, 191, 125522, "Painting Service"); + + var createTopicRequest = new CreateTopicRequest(topicName); + CreateTopicResponse createTopicResponse = await AmazonSimpleNotificationService.CreateTopicAsync(createTopicRequest); + + Assert.Equal(HttpStatusCode.OK, createTopicResponse.HttpStatusCode); + + var createQueueRequest = new CreateQueueRequest(queueName); + CreateQueueResponse createQueueResponse = await AmazonSqs.CreateQueueAsync(createQueueRequest); + + Assert.Equal(HttpStatusCode.OK, createQueueResponse.HttpStatusCode); + + var subscribeRequest = new SubscribeRequest(createTopicResponse.TopicArn, "sqs", createQueueResponse.QueueUrl); + SubscribeResponse subscribeResponse = await AmazonSimpleNotificationService.SubscribeAsync(subscribeRequest); + + Assert.Equal(HttpStatusCode.OK, subscribeResponse.HttpStatusCode); + + string serializedObject = JsonConvert.SerializeObject(jobCreatedEvent); + var messageAttributes = new Dictionary + { + { + nameof(jobCreatedEvent.EventName), + new MessageAttributeValue {DataType = "String", StringValue = jobCreatedEvent.EventName} + } + }; + + var publishRequest = new PublishRequest + { + Message = serializedObject, + TopicArn = createTopicResponse.TopicArn, + Subject = jobCreatedEvent.EventName, + MessageAttributes = messageAttributes + }; + + PublishResponse publishResponse = await AmazonSimpleNotificationService.PublishAsync(publishRequest); + + Assert.Equal(HttpStatusCode.OK, publishResponse.HttpStatusCode); + + var receiveMessageRequest = new ReceiveMessageRequest(createQueueResponse.QueueUrl); + ReceiveMessageResponse receiveMessageResponse = await AmazonSqs.ReceiveMessageAsync(receiveMessageRequest); + + Assert.Equal(HttpStatusCode.OK, receiveMessageResponse.HttpStatusCode); + + Assert.NotNull(receiveMessageResponse.Messages); + Assert.NotEmpty(receiveMessageResponse.Messages); + Assert.Single(receiveMessageResponse.Messages); + + dynamic deserializedMessage = JsonConvert.DeserializeObject(receiveMessageResponse.Messages[0].Body, new ExpandoObjectConverter()); + + Assert.NotNull(deserializedMessage); + Assert.Equal(publishResponse.MessageId, (string)deserializedMessage.MessageId); + + JobCreatedEvent sqsJobCreatedEvent = JsonConvert.DeserializeObject(deserializedMessage.Message); + + Assert.NotNull(sqsJobCreatedEvent); + Assert.Equal(jobCreatedEvent.EventName, sqsJobCreatedEvent.EventName); + Assert.Equal(jobCreatedEvent.Description, sqsJobCreatedEvent.Description); + Assert.Equal(jobCreatedEvent.JobId, sqsJobCreatedEvent.JobId); + Assert.Equal(jobCreatedEvent.ServiceId, sqsJobCreatedEvent.ServiceId); + Assert.Equal(jobCreatedEvent.UserId, sqsJobCreatedEvent.UserId); + } +} \ No newline at end of file diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/S3/BaseS3Scenario.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/S3/BaseS3Scenario.cs index 320c8ca..d91e84e 100644 --- a/tests/LocalStack.Client.Functional.Tests/Scenarios/S3/BaseS3Scenario.cs +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/S3/BaseS3Scenario.cs @@ -6,8 +6,8 @@ public abstract class BaseS3Scenario : BaseScenario private const string FilePath = "SampleData.txt"; protected const string Key = "SampleData.txt"; - protected BaseS3Scenario(TestFixture testFixture, string configFile) - : base(testFixture, configFile) + protected BaseS3Scenario(TestFixture testFixture, string configFile, bool useServiceUrl = false) + : base(testFixture, configFile, useServiceUrl) { AmazonS3 = ServiceProvider.GetRequiredService(); } diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/S3/S3LegacyScenario.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/S3/S3LegacyScenario.cs index 01af9e9..3e8c112 100644 --- a/tests/LocalStack.Client.Functional.Tests/Scenarios/S3/S3LegacyScenario.cs +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/S3/S3LegacyScenario.cs @@ -4,7 +4,7 @@ public class S3LegacyScenario : S3Scenario { public S3LegacyScenario(TestFixture testFixture) - : base(testFixture, TestConstants.LegacyLocalStackConfig) + : base(testFixture, TestConstants.LegacyLocalStackConfig, true) { } } diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/S3/S3Scenario.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/S3/S3Scenario.cs index f75e981..fc34e70 100644 --- a/tests/LocalStack.Client.Functional.Tests/Scenarios/S3/S3Scenario.cs +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/S3/S3Scenario.cs @@ -3,8 +3,8 @@ [Collection(nameof(LocalStackCollection))] public class S3Scenario : BaseS3Scenario { - public S3Scenario(TestFixture testFixture, string configFile = TestConstants.LocalStackConfig) - : base(testFixture, configFile) + public S3Scenario(TestFixture testFixture, string configFile = TestConstants.LocalStackConfig, bool useServiceUrl = false) + : base(testFixture, configFile, useServiceUrl) { } diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/BaseSnsScenario.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/BaseSnsScenario.cs new file mode 100644 index 0000000..7fb5841 --- /dev/null +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/BaseSnsScenario.cs @@ -0,0 +1,30 @@ +namespace LocalStack.Client.Functional.Tests.Scenarios.SNS; + +public abstract class BaseSnsScenario : BaseScenario +{ + protected BaseSnsScenario(TestFixture testFixture, string configFile, bool useServiceUrl = false) + : base(testFixture, configFile, useServiceUrl) + { + AmazonSimpleNotificationService = ServiceProvider.GetRequiredService(); + } + + protected IAmazonSimpleNotificationService AmazonSimpleNotificationService { get; } + + protected async Task CreateSnsTopic(string topic) + { + var createTopicRequest = new CreateTopicRequest(topic); + + CreateTopicResponse createTopicResponse = await AmazonSimpleNotificationService.CreateTopicAsync(createTopicRequest); + + return createTopicResponse; + } + + protected async Task DeleteSnsTopic(string topic) + { + var deleteTopicRequest = new DeleteTopicRequest(topic); + + DeleteTopicResponse deleteTopicResponse = await AmazonSimpleNotificationService.DeleteTopicAsync(deleteTopicRequest); + + return deleteTopicResponse; + } +} \ No newline at end of file diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/Models/JobCreatedEvent.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/Models/JobCreatedEvent.cs new file mode 100644 index 0000000..6b83dc4 --- /dev/null +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/Models/JobCreatedEvent.cs @@ -0,0 +1,3 @@ +namespace LocalStack.Client.Functional.Tests.Scenarios.SNS.Models; + +internal record JobCreatedEvent(long JobId, int ServiceId, int UserId, string Description, string EventName = nameof(JobCreatedEvent)); \ No newline at end of file diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/SnsLegacyScenario.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/SnsLegacyScenario.cs new file mode 100644 index 0000000..a0a2c5f --- /dev/null +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/SnsLegacyScenario.cs @@ -0,0 +1,15 @@ +namespace LocalStack.Client.Functional.Tests.Scenarios.SNS; + +[Collection(nameof(LocalStackLegacyCollection))] +public class SnsLegacyScenario : SnsScenario +{ + public SnsLegacyScenario(TestFixture testFixture) + : base(testFixture, TestConstants.LegacyLocalStackConfig, true) + { + } + + public override Task Multi_Region_Tests(string systemName) + { + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/SnsScenario.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/SnsScenario.cs new file mode 100644 index 0000000..bfaf502 --- /dev/null +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/SnsScenario.cs @@ -0,0 +1,111 @@ +using JsonSerializer = System.Text.Json.JsonSerializer; +using MessageAttributeValue = Amazon.SimpleNotificationService.Model.MessageAttributeValue; + +namespace LocalStack.Client.Functional.Tests.Scenarios.SNS; + +[Collection(nameof(LocalStackCollection))] +public class SnsScenario : BaseSnsScenario +{ + public SnsScenario(TestFixture testFixture, string configFile = TestConstants.LocalStackConfig, bool useServiceUrl = false) + : base(testFixture, configFile, useServiceUrl) + { + } + + [Fact] + public async Task SnsService_Should_Create_A_Sns_Topic() + { + var topicName = Guid.NewGuid().ToString(); + CreateTopicResponse createTopicResponse = await CreateSnsTopic(topicName); + + Assert.Equal(HttpStatusCode.OK, createTopicResponse.HttpStatusCode); + + ListTopicsResponse listTopicsResponse = await AmazonSimpleNotificationService.ListTopicsAsync(); + Topic snsTopic = listTopicsResponse.Topics.SingleOrDefault(topic => topic.TopicArn == createTopicResponse.TopicArn); + + Assert.NotNull(snsTopic); + Assert.EndsWith(topicName, snsTopic.TopicArn); + + await DeleteSnsTopic(createTopicResponse.TopicArn); //Cleanup + } + + [Fact] + public async Task SnsService_Should_Delete_A_Sns_Topic() + { + var topicName = Guid.NewGuid().ToString(); + + CreateTopicResponse createTopicResponse = await CreateSnsTopic(topicName); + DeleteTopicResponse deleteTopicResponse = await DeleteSnsTopic(createTopicResponse.TopicArn); + + Assert.Equal(HttpStatusCode.OK, deleteTopicResponse.HttpStatusCode); + + ListTopicsResponse listTopicsResponse = await AmazonSimpleNotificationService.ListTopicsAsync(); + bool hasAny = listTopicsResponse.Topics.Any(topic => topic.TopicArn == createTopicResponse.TopicArn); + + Assert.False(hasAny); + } + + [Fact] + public async Task SnsService_Should_Send_Publish_A_Message() + { + var topicName = Guid.NewGuid().ToString(); + CreateTopicResponse createTopicResponse = await CreateSnsTopic(topicName); + + var jobCreatedEvent = new JobCreatedEvent(423565221, 191, 125522, "Painting Service"); + string serializedObject = JsonSerializer.Serialize(jobCreatedEvent); + + var messageAttributes = new Dictionary + { + { + nameof(jobCreatedEvent.EventName), + new MessageAttributeValue {DataType = "String", StringValue = jobCreatedEvent.EventName} + } + }; + + var publishRequest = new PublishRequest + { + Message = serializedObject, + TopicArn = createTopicResponse.TopicArn, + Subject = jobCreatedEvent.EventName, + MessageAttributes = messageAttributes + }; + + PublishResponse publishResponse = await AmazonSimpleNotificationService.PublishAsync(publishRequest); + + Assert.Equal(HttpStatusCode.OK, publishResponse.HttpStatusCode); + + await DeleteSnsTopic(createTopicResponse.TopicArn); //Cleanup + } + + [Theory, + InlineData("eu-central-1"), + InlineData("us-west-1"), + InlineData("af-south-1"), + InlineData("ap-southeast-1"), + InlineData("ca-central-1"), + InlineData("eu-west-2"), + InlineData("sa-east-1")] + public virtual async Task Multi_Region_Tests(string systemName) + { + var sessionReflection = ServiceProvider.GetRequiredService(); + var amazonSimpleNotificationService = ServiceProvider.GetRequiredService(); + + sessionReflection.SetClientRegion((AmazonSimpleNotificationServiceClient)amazonSimpleNotificationService, systemName); + + Assert.Equal(RegionEndpoint.GetBySystemName(systemName), amazonSimpleNotificationService.Config.RegionEndpoint); + + var topicName = Guid.NewGuid().ToString(); + CreateTopicResponse createTopicResponse = await CreateSnsTopic(topicName); + + Assert.Equal(HttpStatusCode.OK, createTopicResponse.HttpStatusCode); + + var topicArn = $"arn:aws:sns:{systemName}:000000000000:{topicName}"; + + ListTopicsResponse listTopicsResponse = await AmazonSimpleNotificationService.ListTopicsAsync(); + Topic snsTopic = listTopicsResponse.Topics.SingleOrDefault(topic => topic.TopicArn == topicArn); + + Assert.NotNull(snsTopic); + Assert.Single(listTopicsResponse.Topics); + + await DeleteSnsTopic(topicArn); //Cleanup + } +} \ No newline at end of file diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/SQS/BaseSqsScenario.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/SQS/BaseSqsScenario.cs index 57692ea..e32d696 100644 --- a/tests/LocalStack.Client.Functional.Tests/Scenarios/SQS/BaseSqsScenario.cs +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/SQS/BaseSqsScenario.cs @@ -1,11 +1,13 @@ -namespace LocalStack.Client.Functional.Tests.Scenarios.SQS; +using JsonSerializer = System.Text.Json.JsonSerializer; + +namespace LocalStack.Client.Functional.Tests.Scenarios.SQS; public abstract class BaseSqsScenario : BaseScenario { protected const string TestDlQueueName = "ArmutLocalStack-Test-DLQ.fifo"; protected const string TestQueueName = "ArmutLocalStack-Test.fifo"; - protected BaseSqsScenario(TestFixture testFixture, string configFile) : base(testFixture, configFile) + protected BaseSqsScenario(TestFixture testFixture, string configFile, bool useServiceUrl = false) : base(testFixture, configFile, useServiceUrl) { AmazonSqs = ServiceProvider.GetRequiredService(); } diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/SQS/SqsLegacyTests.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/SQS/SqsLegacyScenario.cs similarity index 64% rename from tests/LocalStack.Client.Functional.Tests/Scenarios/SQS/SqsLegacyTests.cs rename to tests/LocalStack.Client.Functional.Tests/Scenarios/SQS/SqsLegacyScenario.cs index 596e17b..a76af5c 100644 --- a/tests/LocalStack.Client.Functional.Tests/Scenarios/SQS/SqsLegacyTests.cs +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/SQS/SqsLegacyScenario.cs @@ -1,10 +1,10 @@ namespace LocalStack.Client.Functional.Tests.Scenarios.SQS; [Collection(nameof(LocalStackLegacyCollection))] -public class SqsLegacyTests : SqsScenario +public class SqsLegacyScenario : SqsScenario { - public SqsLegacyTests(TestFixture testFixture) - : base(testFixture, TestConstants.LegacyLocalStackConfig) + public SqsLegacyScenario(TestFixture testFixture) + : base(testFixture, TestConstants.LegacyLocalStackConfig, true) { } } diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/SQS/SqsScenario.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/SQS/SqsScenario.cs index 3813f92..8ad173b 100644 --- a/tests/LocalStack.Client.Functional.Tests/Scenarios/SQS/SqsScenario.cs +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/SQS/SqsScenario.cs @@ -1,10 +1,12 @@ -namespace LocalStack.Client.Functional.Tests.Scenarios.SQS; +using JsonSerializer = System.Text.Json.JsonSerializer; + +namespace LocalStack.Client.Functional.Tests.Scenarios.SQS; [Collection(nameof(LocalStackCollection))] public class SqsScenario : BaseSqsScenario { - public SqsScenario(TestFixture testFixture, string configFile = TestConstants.LocalStackConfig) - : base(testFixture, configFile) + public SqsScenario(TestFixture testFixture, string configFile = TestConstants.LocalStackConfig, bool useServiceUrl = false) + : base(testFixture, configFile, useServiceUrl) { } From 78e4ba94442b9740adf6faa6e90915b12c52f37c Mon Sep 17 00:00:00 2001 From: "deniz_irgin@yahoo.com" Date: Sun, 28 Nov 2021 21:06:01 +0300 Subject: [PATCH 18/24] add SESv2 support --- .../Enums/AwsServiceEndpointMetadata.cs | 3 ++- src/LocalStack.Client/Enums/AwsServiceEnum.cs | 1 + .../CreateClientByImplementationTests.cs | 9 +++++++++ .../CreateClientByInterfaceTests.cs | 9 +++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/LocalStack.Client/Enums/AwsServiceEndpointMetadata.cs b/src/LocalStack.Client/Enums/AwsServiceEndpointMetadata.cs index 7d6c4c7..20031eb 100644 --- a/src/LocalStack.Client/Enums/AwsServiceEndpointMetadata.cs +++ b/src/LocalStack.Client/Enums/AwsServiceEndpointMetadata.cs @@ -20,6 +20,7 @@ public class AwsServiceEndpointMetadata public static readonly AwsServiceEndpointMetadata RedshiftData = new("Redshift Data", "redshift-data", CommonEndpointPattern, 4577, AwsServiceEnum.RedshiftData); public static readonly AwsServiceEndpointMetadata Es = new("ES", "es", CommonEndpointPattern, 4578, AwsServiceEnum.Es); public static readonly AwsServiceEndpointMetadata Ses = new("SES", "ses", CommonEndpointPattern, 4579, AwsServiceEnum.Ses); + public static readonly AwsServiceEndpointMetadata Sesv2 = new("SESv2", "sesv2", CommonEndpointPattern, 4579, AwsServiceEnum.Sesv2); public static readonly AwsServiceEndpointMetadata Route53 = new("Route 53", "route53", CommonEndpointPattern, 4580, AwsServiceEnum.Route53); public static readonly AwsServiceEndpointMetadata CloudFormation = new("CloudFormation", "cloudformation", CommonEndpointPattern, 4581, AwsServiceEnum.CloudFormation); public static readonly AwsServiceEndpointMetadata CloudWatch = new("CloudWatch", "cloudwatch", CommonEndpointPattern, 4582, AwsServiceEnum.CloudWatch); @@ -100,7 +101,7 @@ public class AwsServiceEndpointMetadata public static readonly AwsServiceEndpointMetadata[] All = { - ApiGateway, ApiGatewayV2, Kinesis, DynamoDb, DynamoDbStreams, ElasticSearch, S3, Firehose, Lambda, Sns, Sqs, Redshift, RedshiftData, Es, Ses, Route53, CloudFormation, CloudWatch, + ApiGateway, ApiGatewayV2, Kinesis, DynamoDb, DynamoDbStreams, ElasticSearch, S3, Firehose, Lambda, Sns, Sqs, Redshift, RedshiftData, Es, Ses, Sesv2, Route53, CloudFormation, CloudWatch, Ssm, SecretsManager, StepFunctions, Logs, Events, Elb, Iot, IoTAnalytics, IoTEvents, IoTEventsData, IoTWireless, IoTDataPlane, IoTJobsDataPlane, CognitoIdp, CognitoIdentity, Sts, Iam, Rds, RdsData, CloudSearch, Swf, Ec2, ElastiCache, Kms, Emr, Ecs, Eks, XRay, ElasticBeanstalk, AppSync, CloudFront, Athena, Glue, SageMaker, SageMakerRuntime, Ecr, Qldb, QldbSession, CloudTrail, Glacier, Batch, Organizations, AutoScaling, MediaStore, MediaStoreData, Transfer, Acm, CodeCommit, KinesisAnalytics, Amplify, ApplicationAutoscaling, Kafka, ApiGatewayManagementApi, diff --git a/src/LocalStack.Client/Enums/AwsServiceEnum.cs b/src/LocalStack.Client/Enums/AwsServiceEnum.cs index 2984c7d..7512b20 100644 --- a/src/LocalStack.Client/Enums/AwsServiceEnum.cs +++ b/src/LocalStack.Client/Enums/AwsServiceEnum.cs @@ -17,6 +17,7 @@ public enum AwsServiceEnum RedshiftData, Es, Ses, + Sesv2, Route53, CloudFormation, CloudWatch, diff --git a/tests/LocalStack.Client.Integration.Tests/CreateClientByImplementationTests.cs b/tests/LocalStack.Client.Integration.Tests/CreateClientByImplementationTests.cs index 597787d..fcaba8c 100644 --- a/tests/LocalStack.Client.Integration.Tests/CreateClientByImplementationTests.cs +++ b/tests/LocalStack.Client.Integration.Tests/CreateClientByImplementationTests.cs @@ -111,6 +111,15 @@ public void Should_Able_To_Create_AmazonSimpleEmailServiceClient() AssertAmazonClient.AssertClientConfiguration(amazonSimpleEmailServiceClient); } + [Fact] + public void Should_Able_To_Create_AmazonSimpleEmailServiceV2Client() + { + var simpleEmailServiceV2Client = Session.CreateClientByImplementation(); + + Assert.NotNull(simpleEmailServiceV2Client); + AssertAmazonClient.AssertClientConfiguration(simpleEmailServiceV2Client); + } + [Fact] public void Should_Able_To_Create_AmazonRoute53Client() { diff --git a/tests/LocalStack.Client.Integration.Tests/CreateClientByInterfaceTests.cs b/tests/LocalStack.Client.Integration.Tests/CreateClientByInterfaceTests.cs index 619cd9b..15db537 100644 --- a/tests/LocalStack.Client.Integration.Tests/CreateClientByInterfaceTests.cs +++ b/tests/LocalStack.Client.Integration.Tests/CreateClientByInterfaceTests.cs @@ -111,6 +111,15 @@ public void Should_Able_To_Create_AmazonSimpleEmailServiceClient() AssertAmazonClient.AssertClientConfiguration(amazonSimpleEmailServiceClient); } + [Fact] + public void Should_Able_To_Create_AmazonSimpleEmailServiceV2Client() + { + AmazonServiceClient simpleEmailServiceV2Client = Session.CreateClientByInterface(); + + Assert.NotNull(simpleEmailServiceV2Client); + AssertAmazonClient.AssertClientConfiguration(simpleEmailServiceV2Client); + } + [Fact] public void Should_Able_To_Create_AmazonRoute53Client() { From 163e0161e009c6f36cc057cd8fa0884681e7dd75 Mon Sep 17 00:00:00 2001 From: "deniz_irgin@yahoo.com" Date: Sun, 28 Nov 2021 21:07:53 +0300 Subject: [PATCH 19/24] update packages --- .../LocalStack.Client.Extensions.csproj | 2 +- .../LocalStack.Client.csproj | 2 +- .../LocalStack.Client.Extensions.Tests.csproj | 2 +- .../LocalStack.Client.Functional.Tests.csproj | 10 +- .../GlobalUsings.cs | 1 + ...LocalStack.Client.Integration.Tests.csproj | 175 +++++++++--------- ....Client.Sandbox.DependencyInjection.csproj | 4 +- ...tack.Client.Sandbox.WithGenericHost.csproj | 30 +-- .../LocalStack.Client.Sandbox.csproj | 2 +- 9 files changed, 115 insertions(+), 113 deletions(-) diff --git a/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj b/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj index b60431c..f8633ae 100644 --- a/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj +++ b/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/LocalStack.Client/LocalStack.Client.csproj b/src/LocalStack.Client/LocalStack.Client.csproj index 0011265..58f810d 100644 --- a/src/LocalStack.Client/LocalStack.Client.csproj +++ b/src/LocalStack.Client/LocalStack.Client.csproj @@ -18,7 +18,7 @@ - + diff --git a/tests/LocalStack.Client.Extensions.Tests/LocalStack.Client.Extensions.Tests.csproj b/tests/LocalStack.Client.Extensions.Tests/LocalStack.Client.Extensions.Tests.csproj index ca3bde5..557993d 100644 --- a/tests/LocalStack.Client.Extensions.Tests/LocalStack.Client.Extensions.Tests.csproj +++ b/tests/LocalStack.Client.Extensions.Tests/LocalStack.Client.Extensions.Tests.csproj @@ -9,7 +9,7 @@ - + diff --git a/tests/LocalStack.Client.Functional.Tests/LocalStack.Client.Functional.Tests.csproj b/tests/LocalStack.Client.Functional.Tests/LocalStack.Client.Functional.Tests.csproj index 3554b12..af3e900 100644 --- a/tests/LocalStack.Client.Functional.Tests/LocalStack.Client.Functional.Tests.csproj +++ b/tests/LocalStack.Client.Functional.Tests/LocalStack.Client.Functional.Tests.csproj @@ -20,7 +20,7 @@ - + @@ -38,10 +38,10 @@ - - - - + + + + diff --git a/tests/LocalStack.Client.Integration.Tests/GlobalUsings.cs b/tests/LocalStack.Client.Integration.Tests/GlobalUsings.cs index 1490c90..dbc5159 100644 --- a/tests/LocalStack.Client.Integration.Tests/GlobalUsings.cs +++ b/tests/LocalStack.Client.Integration.Tests/GlobalUsings.cs @@ -77,6 +77,7 @@ global using Amazon.ServerlessApplicationRepository; global using Amazon.ServiceDiscovery; global using Amazon.SimpleEmail; +global using Amazon.SimpleEmailV2; global using Amazon.SimpleNotificationService; global using Amazon.SimpleSystemsManagement; global using Amazon.SimpleWorkflow; 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 09a0b96..931296e 100644 --- a/tests/LocalStack.Client.Integration.Tests/LocalStack.Client.Integration.Tests.csproj +++ b/tests/LocalStack.Client.Integration.Tests/LocalStack.Client.Integration.Tests.csproj @@ -7,93 +7,94 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/sandboxes/LocalStack.Client.Sandbox.DependencyInjection/LocalStack.Client.Sandbox.DependencyInjection.csproj b/tests/sandboxes/LocalStack.Client.Sandbox.DependencyInjection/LocalStack.Client.Sandbox.DependencyInjection.csproj index 7bcb34b..13492a8 100644 --- a/tests/sandboxes/LocalStack.Client.Sandbox.DependencyInjection/LocalStack.Client.Sandbox.DependencyInjection.csproj +++ b/tests/sandboxes/LocalStack.Client.Sandbox.DependencyInjection/LocalStack.Client.Sandbox.DependencyInjection.csproj @@ -23,13 +23,13 @@ - + - + diff --git a/tests/sandboxes/LocalStack.Client.Sandbox.WithGenericHost/LocalStack.Client.Sandbox.WithGenericHost.csproj b/tests/sandboxes/LocalStack.Client.Sandbox.WithGenericHost/LocalStack.Client.Sandbox.WithGenericHost.csproj index 8d3321e..2dc1a34 100644 --- a/tests/sandboxes/LocalStack.Client.Sandbox.WithGenericHost/LocalStack.Client.Sandbox.WithGenericHost.csproj +++ b/tests/sandboxes/LocalStack.Client.Sandbox.WithGenericHost/LocalStack.Client.Sandbox.WithGenericHost.csproj @@ -1,39 +1,39 @@  - + Exe net6.0;net5.0;netcoreapp3.1 latest - + - + - PreserveNewest + PreserveNewest - PreserveNewest - appsettings.json + PreserveNewest + appsettings.json - + - - + + - + - + - + - + - Always + Always - + diff --git a/tests/sandboxes/LocalStack.Client.Sandbox/LocalStack.Client.Sandbox.csproj b/tests/sandboxes/LocalStack.Client.Sandbox/LocalStack.Client.Sandbox.csproj index 46bad46..5268f49 100644 --- a/tests/sandboxes/LocalStack.Client.Sandbox/LocalStack.Client.Sandbox.csproj +++ b/tests/sandboxes/LocalStack.Client.Sandbox/LocalStack.Client.Sandbox.csproj @@ -8,7 +8,7 @@ - + From ef171756afb30f9ecf192ea3a147633bea723f48 Mon Sep 17 00:00:00 2001 From: "deniz_irgin@yahoo.com" Date: Sun, 28 Nov 2021 21:09:58 +0300 Subject: [PATCH 20/24] update README for v1.3.0 --- README.md | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5bc8095..a3e73ea 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ application development. - [Installation](#extensions-installation) - [Usage](#extensions-usage) - [About AddAwsService](#extensions-usage-about-addawsservice) + - [useServiceUrl Parameter](#useserviceurl) - [Standalone Initialization](#standalone-initialization) - [Microsoft.Extensions.DependencyInjection Initialization](#di) 5. [Developing](#developing) @@ -129,7 +130,7 @@ You can configure `LocalStack.Client` by using entries in the `appsettings.json` "AwsAccessKeyId": "my-AwsAccessKeyId", "AwsAccessKey": "my-AwsAccessKey", "AwsSessionToken": "my-AwsSessionToken", - "RegionName": null // can be values like "eu-central-1", "us-east-1", "us-west-1" etc. + "RegionName": "eu-central-1" }, "Config": { "LocalStackHost": "localhost", @@ -145,9 +146,7 @@ So the above entries do not need to be specified. What is entered for the aws credential values ​​in the `Session` section does not matter for LocalStack. -`RegionName` is important since LocalStack creates resources by spesified region (LocalStack has full multi-region support after `v0.12.17`). By default `RegionName` is `null`. If no value is entered in the `RegionName` entry, the [AWSSDK.NET](https://aws.amazon.com/sdk-for-net/) will use the `us-east-1` region by default. - -Internally depends on whether you set `RegionName` or not, the values of [ServiceUrl](https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Core/Amazon.Runtime/ClientConfig.cs#L202) ve [RegionEndpoint](https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Core/Amazon.Runtime/ClientConfig.cs#L144) properties of the [ClientConfig](https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Core/Amazon.Runtime/ClientConfig.cs) will change. [RegionEndpoint](https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Core/Amazon.Runtime/ClientConfig.cs#L144) and [ServiceUrl](https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Core/Amazon.Runtime/ClientConfig.cs#L202) are mutually exclusive properties. Whichever property is set last will cause the other to automatically be reset to null. If a value set to `RegionName` entry, the LocalStack.NET AWS will set RegionEndpoint property of the ClientConfig. Because of LocalStack.NET sets the RegionEndpoint property after the ServiceUrl property, ServiceUrl will be set to null. +`RegionName` is important since LocalStack creates resources by spesified region. `Config` section contains important entries for local development. Starting with LocalStack releases after `v0.11.5`, all services are now exposed via the edge service (port 4566) only! If you are using a version of LocalStack lower than v0.11.5, you should set `UseLegacyPorts` to `true`. Edge port can be set to any available port ([see LocalStack configuration section](https://github.com/localstack/localstack#configurations)). If you have made such a change in LocalStack's configuration, be sure to set the same port value to `EdgePort` in the `Config` section. For `LocalStackHost` and `UseSsl` entries, ​​corresponding to the [LocalStack configuration](https://github.com/localstack/localstack#configurations) should be used. @@ -190,6 +189,28 @@ It is named as `AddAwsService` to avoid name conflict with `AddAWSService`. (Alternatively, `AddAWSServiceLocalStack` method can be used to prevent mix-up with `AddAWSService`.) +#### useServiceUrl Parameter + +LocalStack.NET uses [ClientConfig](https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Core/Amazon.Runtime/ClientConfig.cs) to configure AWS clients to connect LocalStack. `ClientConfig` has two properties called `ServiceUrl` and `RegionEndpoint`, these are mutually exclusive properties. Whichever property is set last will cause the other to automatically be reset to null. LocalStack.NET has given priority to the RegionEndpoint property and the `us-east-1` region is used as the default value (Different regions can be set by using appsettings.json, see [RegionName](#session-regioname) entry. Because of it sets the RegionEndpoint property after the ServiceUrl property, ServiceUrl will be set to null. + +To override this behavior, the `useServiceUrl` optional parameter can be set to `true` as below. + +```csharp +public void ConfigureServices(IServiceCollection services) +{ + // Add framework services. + services.AddMvc(); + + services.AddLocalStack(Configuration) + services.AddDefaultAWSOptions(Configuration.GetAWSOptions()); + services.AddAwsService(); + services.AddAwsService(useServiceUrl: true) + services.AddAwsService(useServiceUrl: true) +} +``` + +The `RegionEndpoint` is not applicable for services such as AWS MediaStore, Iot. The optional parameter `useServiceUrl` can be useful for use in such scenarios. + ### Standalone Initialization If you do not want to use any DI library, you have to instantiate `SessionStandalone` as follows. @@ -200,7 +221,7 @@ If you do not want to use any DI library, you have to instantiate `SessionStanda * AwsAccessKeyId: accessKey (It doesn't matter to LocalStack) * AwsAccessKey: secretKey (It doesn't matter to LocalStack) * AwsSessionToken: token (It doesn't matter to LocalStack) -* RegionName: null // can be values like "eu-central-1", "us-east-1", "us-west-1" etc. +* RegionName: us-east-1 * ==== Custom Values ==== * var sessionOptions = new SessionOptions("someAwsAccessKeyId", "someAwsAccessKey", "someAwsSessionToken", "eu-central-"); */ @@ -230,6 +251,32 @@ var amazonS3Client = session.CreateClientByImplementation(); var amazonS3Client = session.CreateClientByInterface(); ``` +### useServiceUrl Parameter + +LocalStack.NET uses [ClientConfig](https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Core/Amazon.Runtime/ClientConfig.cs) to configure AWS clients to connect LocalStack. `ClientConfig` has two properties called `ServiceUrl` and `RegionEndpoint`, these are mutually exclusive properties. Whichever property is set last will cause the other to automatically be reset to null. LocalStack.NET has given priority to the RegionEndpoint property and the `us-east-1` region is used as the default value (Different regions can be set by using appsettings.json, see [RegionName](#session-regioname) entry. Because of it sets the RegionEndpoint property after the ServiceUrl property, ServiceUrl will be set to null. + +To override this behavior, the `useServiceUrl` optional parameter can be set to `true` as below. + +```csharp +var sessionOptions = new SessionOptions(); +var configOptions = new ConfigOptions(); + +ISession session = SessionStandalone.Init() + .WithSessionOptions(sessionOptions) + .WithConfigurationOptions(configOptions).Create(); + +var amazonS3Client = session.CreateClientByImplementation(true); +var amazonS3Client = session.CreateClientByImplementation(); +``` + +The `RegionEndpoint` is not applicable for services such as AWS MediaStore, Iot. The optional parameter `useServiceUrl` can be useful for use in such scenarios. + +`CreateClientByInterface` method can also be used to create AWS service, as follows + +```csharp +var amazonS3Client = session.CreateClientByInterface(true); +``` + ### Microsoft.Extensions.DependencyInjection Initialization First, you need to install `Microsoft.Extensions.DependencyInjection` nuget package as follows @@ -248,7 +295,7 @@ var collection = new ServiceCollection(); * AwsAccessKeyId: accessKey (It doesn't matter to LocalStack) * AwsAccessKey: secretKey (It doesn't matter to LocalStack) * AwsSessionToken: token (It doesn't matter to LocalStack) -* RegionName: null // can be values like "eu-central-1", "us-east-1", "us-west-1" etc. +* RegionName: us-east-1 * ==== Custom Values ==== * var sessionOptions = new SessionOptions("someAwsAccessKeyId", "someAwsAccessKey", "someAwsSessionToken", "eu-central-"); */ @@ -303,7 +350,7 @@ collection.Configure(options => configuration.GetSection("Loc * AwsAccessKeyId: accessKey (It doesn't matter to LocalStack) * AwsAccessKey: secretKey (It doesn't matter to LocalStack) * AwsSessionToken: token (It doesn't matter to LocalStack) -* RegionName: null // can be values like "eu-central-1", "us-east-1", "us-west-1" etc. +* RegionName: us-east-1 */ collection.Configure(options => configuration.GetSection("LocalStack") .GetSection(nameof(LocalStackOptions.Session)) @@ -347,6 +394,8 @@ ServiceProvider serviceProvider = collection.BuildServiceProvider(); var amazonS3Client = serviceProvider.GetRequiredService(); ``` +See [useServiceUrl] parameter usage (#standalone-useserviceurl) + ## Developing We welcome feedback, bug reports, and pull requests! @@ -391,6 +440,21 @@ Linux ## Changelog +### [v1.3.0](https://github.com/localstack-dotnet/localstack-dotnet-client/releases/tag/v1.3.0) + +#### 1. New Features +- New endpoints in the official [Localstack Python Client](https://github.com/localstack/localstack-python-client) v1.27 have been added. + - SESv2 + - EventBridge ([#14](https://github.com/localstack-dotnet/localstack-dotnet-client/pull/14)) +- Tested against LocalStack v0.13.0 container. +#### 2. Enhancements +- `useServiceUrl` parameter added to change client connection behavior. See [useServiceUrl Parameter](#useserviceurl) +- Readme and SourceLink added to Nuget packages +#### 3. Bug Fixes +- Session::RegionName configuration does not honor while creating AWS client ([#15](https://github.com/localstack-dotnet/localstack-dotnet-client/issues/15)) + +Thanks to [petertownsend](https://github.com/petertownsend) for his contribution + ### [v1.2.3](https://github.com/localstack-dotnet/localstack-dotnet-client/releases/tag/v1.2.3) - New endpoints in the official [Localstack Python Client](https://github.com/localstack/localstack-python-client) v1.25 have been added. From ae614bfd20eb4e74d1f11a7889bf41f414637ed4 Mon Sep 17 00:00:00 2001 From: Blind-Striker Date: Sun, 28 Nov 2021 23:21:54 +0300 Subject: [PATCH 21/24] add README file to packages --- .../LocalStack.Client.Extensions.csproj | 8 + src/LocalStack.Client.Extensions/README.md | 504 ++++++++++++++++++ .../LocalStack.Client.csproj | 9 +- src/LocalStack.Client/README.md | 504 ++++++++++++++++++ 4 files changed, 1024 insertions(+), 1 deletion(-) create mode 100644 src/LocalStack.Client.Extensions/README.md create mode 100644 src/LocalStack.Client/README.md diff --git a/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj b/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj index f8633ae..5e48c05 100644 --- a/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj +++ b/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj @@ -13,6 +13,7 @@ aws-sdk, localstack, client-library, dotnet, dotnet-core LICENSE.txt + README.md 1.1.2 @@ -31,13 +32,20 @@ + + + + Always + + Always + diff --git a/src/LocalStack.Client.Extensions/README.md b/src/LocalStack.Client.Extensions/README.md new file mode 100644 index 0000000..a3e73ea --- /dev/null +++ b/src/LocalStack.Client.Extensions/README.md @@ -0,0 +1,504 @@ +![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/) [![Testspace tests (compact)](https://img.shields.io/testspace/tests/localstack-dotnet/localstack-dotnet:localstack-dotnet-client/master?compact_message)](https://localstack-dotnet.testspace.com/spaces/155695/result_sets) + +# LocalStack .Net Core and .Net Framework Client + +![LocalStack](https://github.com/localstack-dotnet/localstack-dotnet-client/blob/master/assets/localstack-dotnet.png?raw=true) + +This is an easy-to-use .NET client for [LocalStack](https://github.com/localstack/localstack). +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. + +## Continuous integration + +| 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 | macO | [![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) | + +## Table of Contents + +1. [Supported Platforms](#supported-platforms) +2. [Prerequisites](#prerequisites) +3. [Installation](#installation) +4. [Usage](#usage) + - [LocalStack.Client.Extensions (Recommended)](#localstack-client-extensions) + - [Installation](#extensions-installation) + - [Usage](#extensions-usage) + - [About AddAwsService](#extensions-usage-about-addawsservice) + - [useServiceUrl Parameter](#useserviceurl) + - [Standalone Initialization](#standalone-initialization) + - [Microsoft.Extensions.DependencyInjection Initialization](#di) +5. [Developing](#developing) + - [About Sandbox Applications](#about-sandboxes) + - [Running Tests](#running-tests) +6. [Changelog](#changelog) +7. [License](#license) + +## Supported Platforms + +- [.NET 6.0](https://dotnet.microsoft.com/download/dotnet/6.0) +- [.NET 5.0](https://dotnet.microsoft.com/download/dotnet/5.0) +- [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) +- [.NET 4.6.1 and Above](https://dotnet.microsoft.com/download/dotnet-framework) + +## Prerequisites + +To make use of this library, you need to have [LocalStack](https://github.com/localstack/localstack) +installed on your local machine. In particular, the `localstack` command needs to be available. + +## Installation + +The easiest way to install *LocalStack .NET Client* is via `nuget`: + +``` +Install-Package LocalStack.Client +``` + +Or use `dotnet cli` + +``` +dotnet add package LocalStack.Client +``` + +| 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) | + +## Usage + +This library provides a thin wrapper around [aws-sdk-net](https://github.com/aws/aws-sdk-net). +Therefore the usage of this library is same as using `AWS SDK for .NET`. + +See [Getting Started with the AWS SDK for .NET](https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-setup.html) + +This library can be used with any DI library, [AWSSDK.Extensions.NETCore.Setup](https://docs.aws.amazon.com/sdk-for-net/latest/developer-guide/net-dg-config-netcore.html) or it can be used as standalone. + +### LocalStack.Client.Extensions (Recommended) + +[LocalStack.Client.Extensions](https://www.nuget.org/packages/LocalStack.Client/) is extensions for the LocalStack.NET Client to integrate with .NET Core configuration and dependency injection frameworks. The extensions also provides wrapper around [AWSSDK.Extensions.NETCore.Setup](https://docs.aws.amazon.com/sdk-for-net/latest/developer-guide/net-dg-config-netcore.html) to use both LocalStack and AWS side-by-side. + +This approach is recommended since `AWSSDK.Extensions.NETCore.Setup` is very popular and also it is best practice for using [AWSSDK.NET](https://aws.amazon.com/sdk-for-net/) with .NET Core, .NET 5 or .NET 6 + +#### Installation + +The easiest way to install *LocalStack .NET Client Extensions* is via `nuget`: + +``` +Install-Package LocalStack.Client.Extensions +``` + +Or use `dotnet cli` + +``` +dotnet add package LocalStack.Client.Extensions +``` + +#### Usage + +The usage is very similar to `AWSSDK.Extensions.NETCore.Setup` with some differences. + +```csharp +public void ConfigureServices(IServiceCollection services) +{ + // Add framework services. + services.AddMvc(); + + services.AddLocalStack(Configuration) + services.AddDefaultAWSOptions(Configuration.GetAWSOptions()); + services.AddAwsService(); + services.AddAwsService(); +} +``` + +The most important difference is that `AddAwsService` extensions method is used instead of `AddAWSService` used in `AWSSDK.Extensions.NETCore.Setup`. The reason for this will be explained later in this section. + +In addition, the `AddLocalStack` extension method is also used. + +(Alternatively, `AddAWSServiceLocalStack` method can be used to prevent mix-up with `AddAWSService`.) + +`AddLocalStack` extension method is responsible for both configurations and adding of `LocalStack.Client` dependencies to service collection. + +You can configure `LocalStack.Client` by using entries in the `appsettings.json` files, as shown in the following example. + +```json +"LocalStack": { + "UseLocalStack": true, + "Session": { + "AwsAccessKeyId": "my-AwsAccessKeyId", + "AwsAccessKey": "my-AwsAccessKey", + "AwsSessionToken": "my-AwsSessionToken", + "RegionName": "eu-central-1" + }, + "Config": { + "LocalStackHost": "localhost", + "UseSsl": false, + "UseLegacyPorts": false, + "EdgePort": 4566 + } +} +``` + +All the entries above are has shown with default values (except `UseLocalStack`, it's `false` by default). +So the above entries do not need to be specified. + +What is entered for the aws credential values ​​in the `Session` section does not matter for LocalStack. + +`RegionName` is important since LocalStack creates resources by spesified region. + +`Config` section contains important entries for local development. Starting with LocalStack releases after `v0.11.5`, all services are now exposed via the edge service (port 4566) only! If you are using a version of LocalStack lower than v0.11.5, you should set `UseLegacyPorts` to `true`. Edge port can be set to any available port ([see LocalStack configuration section](https://github.com/localstack/localstack#configurations)). If you have made such a change in LocalStack's configuration, be sure to set the same port value to `EdgePort` in the `Config` section. For `LocalStackHost` and `UseSsl` entries, ​​corresponding to the [LocalStack configuration](https://github.com/localstack/localstack#configurations) should be used. + +The following sample setting files can be used to use both `LocalStack.Client` and`AWSSDK.Extensions.NETCore.Setup` in different environments. + +`appsettings.Development.json` + +```json +"LocalStack": { + "UseLocalStack": true, + "Session": { + ... + }, + "Config": { + ... + } +} +``` + +`appsettings.Production.json` + +```json +"LocalStack": { + "UseLocalStack": false +}, +"AWS": { + "Profile": "", + "Region": "eu-central-1" +} +``` + +See project [LocalStack.Client.Sandbox.WithGenericHost](https://github.com/localstack-dotnet/localstack-dotnet-client/tree/master/tests/sandboxes/LocalStack.Client.Sandbox.WithGenericHost) for a use case. + +#### About AddAwsService + +`AddAwsService` is equivalent of `AddAWSService` used in `AWSSDK.Extensions.NETCore.Setup`. It decides which factory to use when resolving any AWS Service. To decide this, it checks the `UseLocalStack` entry. +If the `UseLocalStack` entry is `true`, it uses the [Session](https://github.com/localstack-dotnet/localstack-dotnet-client/blob/master/src/LocalStack.Client/Session.cs) class of `LocalStack.Client` to create AWS Service. If the `UseLocalStack` entry is `false`, it uses the [ClientFactory](https://github.com/aws/aws-sdk-net/blob/master/extensions/src/AWSSDK.Extensions.NETCore.Setup/ClientFactory.cs) class of `AWSSDK.Extensions.NETCore.Setup` which is also used by original `AddAWSService`. + +It is named as `AddAwsService` to avoid name conflict with `AddAWSService`. + +(Alternatively, `AddAWSServiceLocalStack` method can be used to prevent mix-up with `AddAWSService`.) + +#### useServiceUrl Parameter + +LocalStack.NET uses [ClientConfig](https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Core/Amazon.Runtime/ClientConfig.cs) to configure AWS clients to connect LocalStack. `ClientConfig` has two properties called `ServiceUrl` and `RegionEndpoint`, these are mutually exclusive properties. Whichever property is set last will cause the other to automatically be reset to null. LocalStack.NET has given priority to the RegionEndpoint property and the `us-east-1` region is used as the default value (Different regions can be set by using appsettings.json, see [RegionName](#session-regioname) entry. Because of it sets the RegionEndpoint property after the ServiceUrl property, ServiceUrl will be set to null. + +To override this behavior, the `useServiceUrl` optional parameter can be set to `true` as below. + +```csharp +public void ConfigureServices(IServiceCollection services) +{ + // Add framework services. + services.AddMvc(); + + services.AddLocalStack(Configuration) + services.AddDefaultAWSOptions(Configuration.GetAWSOptions()); + services.AddAwsService(); + services.AddAwsService(useServiceUrl: true) + services.AddAwsService(useServiceUrl: true) +} +``` + +The `RegionEndpoint` is not applicable for services such as AWS MediaStore, Iot. The optional parameter `useServiceUrl` can be useful for use in such scenarios. + +### Standalone Initialization + +If you do not want to use any DI library, you have to instantiate `SessionStandalone` as follows. + +```csharp +/* +* ==== Default Values ==== +* AwsAccessKeyId: accessKey (It doesn't matter to LocalStack) +* AwsAccessKey: secretKey (It doesn't matter to LocalStack) +* AwsSessionToken: token (It doesn't matter to LocalStack) +* RegionName: us-east-1 +* ==== Custom Values ==== +* var sessionOptions = new SessionOptions("someAwsAccessKeyId", "someAwsAccessKey", "someAwsSessionToken", "eu-central-"); +*/ +var sessionOptions = new SessionOptions(); + +/* +* ==== Default Values ==== +* LocalStackHost: localhost +* UseSsl: false +* UseLegacyPorts: false (Set true if your LocalStack version is 0.11.5 or above) +* EdgePort: 4566 (It doesn't matter if use legacy ports) +* ==== Custom Values ==== +* var configOptions = new ConfigOptions("mylocalhost", false, false, 4566); +*/ +var configOptions = new ConfigOptions(); + +ISession session = SessionStandalone.Init() + .WithSessionOptions(sessionOptions) + .WithConfigurationOptions(configOptions).Create(); + +var amazonS3Client = session.CreateClientByImplementation(); +``` + +`CreateClientByInterface` method can also be used to create AWS service, as follows + +```csharp +var amazonS3Client = session.CreateClientByInterface(); +``` + +### useServiceUrl Parameter + +LocalStack.NET uses [ClientConfig](https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Core/Amazon.Runtime/ClientConfig.cs) to configure AWS clients to connect LocalStack. `ClientConfig` has two properties called `ServiceUrl` and `RegionEndpoint`, these are mutually exclusive properties. Whichever property is set last will cause the other to automatically be reset to null. LocalStack.NET has given priority to the RegionEndpoint property and the `us-east-1` region is used as the default value (Different regions can be set by using appsettings.json, see [RegionName](#session-regioname) entry. Because of it sets the RegionEndpoint property after the ServiceUrl property, ServiceUrl will be set to null. + +To override this behavior, the `useServiceUrl` optional parameter can be set to `true` as below. + +```csharp +var sessionOptions = new SessionOptions(); +var configOptions = new ConfigOptions(); + +ISession session = SessionStandalone.Init() + .WithSessionOptions(sessionOptions) + .WithConfigurationOptions(configOptions).Create(); + +var amazonS3Client = session.CreateClientByImplementation(true); +var amazonS3Client = session.CreateClientByImplementation(); +``` + +The `RegionEndpoint` is not applicable for services such as AWS MediaStore, Iot. The optional parameter `useServiceUrl` can be useful for use in such scenarios. + +`CreateClientByInterface` method can also be used to create AWS service, as follows + +```csharp +var amazonS3Client = session.CreateClientByInterface(true); +``` + +### Microsoft.Extensions.DependencyInjection Initialization + +First, you need to install `Microsoft.Extensions.DependencyInjection` nuget package as follows + +``` +dotnet add package Microsoft.Extensions.DependencyInjection +``` + +Register necessary dependencies to `ServiceCollection` as follows + +```csharp +var collection = new ServiceCollection(); + +/* +* ==== Default Values ==== +* AwsAccessKeyId: accessKey (It doesn't matter to LocalStack) +* AwsAccessKey: secretKey (It doesn't matter to LocalStack) +* AwsSessionToken: token (It doesn't matter to LocalStack) +* RegionName: us-east-1 +* ==== Custom Values ==== +* var sessionOptions = new SessionOptions("someAwsAccessKeyId", "someAwsAccessKey", "someAwsSessionToken", "eu-central-"); +*/ +var sessionOptions = new SessionOptions(); + +/* +* ==== Default Values ==== +* LocalStackHost: localhost +* UseSsl: false +* UseLegacyPorts: false (Set true if your LocalStack version is 0.11.4 or below) +* EdgePort: 4566 (It doesn't matter if use legacy ports) +* ==== Custom Values ==== +* var configOptions = new ConfigOptions("mylocalhost", false, false, 4566); +*/ +var configOptions = new ConfigOptions(); + +collection + .AddScoped(provider => sessionOptions) + .AddScoped(provider => configOptions)) + .AddScoped() + .AddSingleton() + .AddSingleton() + .AddTransient(provider => + { + var session = provider.GetRequiredService(); + + return (IAmazonS3) session.CreateClientByInterface(); + }); + +ServiceProvider serviceProvider = collection.BuildServiceProvider(); + +var amazonS3Client = serviceProvider.GetRequiredService(); +``` + +If you want to use it with `ConfigurationBuilder`, you can also choose a usage as below. + +```csharp +var collection = new ServiceCollection(); +var builder = new ConfigurationBuilder(); + +builder.SetBasePath(Directory.GetCurrentDirectory()); +builder.AddJsonFile("appsettings.json", true); +builder.AddJsonFile("appsettings.Development.json", true); +builder.AddEnvironmentVariables(); +builder.AddCommandLine(args); + +IConfiguration configuration = builder.Build(); + +collection.Configure(options => configuration.GetSection("LocalStack").Bind(options, c => c.BindNonPublicProperties = true)); +/* +* ==== Default Values ==== +* AwsAccessKeyId: accessKey (It doesn't matter to LocalStack) +* AwsAccessKey: secretKey (It doesn't matter to LocalStack) +* AwsSessionToken: token (It doesn't matter to LocalStack) +* RegionName: us-east-1 + */ +collection.Configure(options => configuration.GetSection("LocalStack") + .GetSection(nameof(LocalStackOptions.Session)) + .Bind(options, c => c.BindNonPublicProperties = true)); +/* + * ==== Default Values ==== + * LocalStackHost: localhost + * UseSsl: false + * UseLegacyPorts: false (Set true if your LocalStack version is 0.11.5 or above) + * EdgePort: 4566 (It doesn't matter if use legacy ports) + */ +collection.Configure(options => configuration.GetSection("LocalStack") + .GetSection(nameof(LocalStackOptions.Config)) + .Bind(options, c => c.BindNonPublicProperties = true)); + + +collection.AddTransient(provider => +{ + ConfigOptions options = provider.GetRequiredService>().Value; + + return new Config(options); +}) +.AddSingleton() +.AddSingleton(provider => +{ + SessionOptions sessionOptions = provider.GetRequiredService>().Value; + var config = provider.GetRequiredService(); + var sessionReflection = provider.GetRequiredService(); + + return new Session(sessionOptions, config, sessionReflection); +}) +.AddTransient(provider => +{ + var session = provider.GetRequiredService(); + + return (IAmazonS3) session.CreateClientByInterface(); +}); + +ServiceProvider serviceProvider = collection.BuildServiceProvider(); + +var amazonS3Client = serviceProvider.GetRequiredService(); +``` + +See [useServiceUrl] parameter usage (#standalone-useserviceurl) + +## Developing + +We welcome feedback, bug reports, and pull requests! + +Use commands below to get you started and test your code: + +Windows + +``` +build.ps1 +``` + +Linux + +``` +./build.sh +``` + +### About Sandbox Applications + +In addition to Unit Tests and Functional Test, LocalStack .Net Repository has various sandbox console applications for both testing and example purposes under [tests/sandboxes](https://github.com/localstack-dotnet/localstack-dotnet-client/tree/master/tests/sandboxes) + +Sandbox applications include various examples of initialization methods of `LocalStack.Client` (see [Usage](#usage) section) and common AWS applications. They provide a convenient and safe environment for those who want to make developments in the library. + +To run sandbox applications with LocalStack container, console application called [LocalStack.Container](https://github.com/localstack-dotnet/localstack-dotnet-client/tree/master/tests/sandboxes/LocalStack.Container) has been developed. It uses [Dotnet Testcontainer](https://github.com/HofmeisterAn/dotnet-testcontainers) to bootstrap LocalStack. Experiments can be made by running LocalStack.Container application first and then any sandbox application. + +### Running Tests + +Use commands below to run tests + +Windows + +``` +build.ps1 --target=tests +``` + +Linux + +``` +./build.sh --target=tests +``` + +## Changelog + +### [v1.3.0](https://github.com/localstack-dotnet/localstack-dotnet-client/releases/tag/v1.3.0) + +#### 1. New Features +- New endpoints in the official [Localstack Python Client](https://github.com/localstack/localstack-python-client) v1.27 have been added. + - SESv2 + - EventBridge ([#14](https://github.com/localstack-dotnet/localstack-dotnet-client/pull/14)) +- Tested against LocalStack v0.13.0 container. +#### 2. Enhancements +- `useServiceUrl` parameter added to change client connection behavior. See [useServiceUrl Parameter](#useserviceurl) +- Readme and SourceLink added to Nuget packages +#### 3. Bug Fixes +- Session::RegionName configuration does not honor while creating AWS client ([#15](https://github.com/localstack-dotnet/localstack-dotnet-client/issues/15)) + +Thanks to [petertownsend](https://github.com/petertownsend) for his contribution + +### [v1.2.3](https://github.com/localstack-dotnet/localstack-dotnet-client/releases/tag/v1.2.3) + +- New endpoints in the official [Localstack Python Client](https://github.com/localstack/localstack-python-client) v1.25 have been added. + - Config Service +- .NET 6.0 support added. +- AWSSDK.Core set to 3.7.3.15 as the minimum version. +- AWSSDK.Extensions.NETCore.Setup set to 3.7.1 as the minimum version. +- Tested against LocalStack v0.13.0 container. + +### [v1.2.2](https://github.com/localstack-dotnet/localstack-dotnet-client/releases/tag/v1.2.2) + +- New endpoints in the official [Localstack Python Client](https://github.com/localstack/localstack-python-client) v1.22 have been added. + - EFS, Backup, LakeFormation, WAF, WAF V2 and QLDB Session +- AWSSDK.Core set to 3.7.1 as the minimum version. +- Tested against LocalStack v0.12.16 container. + +### [v1.2](https://github.com/localstack-dotnet/localstack-dotnet-client/releases/tag/v1.2.0) + +- New endpoints in the official [Localstack Python Client](https://github.com/localstack/localstack-python-client) v1.20 have been added. + - IoTAnalytics, IoT Events, IoT Events Data, IoT Wireless, IoT Data Plane, IoT Jobs Data Plane, Support, Neptune, DocDB, ServiceDiscovery, ServerlessApplicationRepository, AppConfig, Cost Explorer, MediaConvert, Resource Groups Tagging API, Resource Groups +- AWSSDK.Core set to 3.7.0 as the minimum version. +- Obsolete methods removed. +- New alternate AddAWSServiceLocalStack method added to prevent mix up with AddAWSService (for LocalStack.Client.Extension v1.1.0). +- Tested against LocalStack v0.12.10 container. + +### [v1.1](https://github.com/localstack-dotnet/localstack-dotnet-client/releases/tag/v1.1.0) + +- New endpoints in the official [Localstack Python Client](https://github.com/localstack/localstack-python-client) v1.10 have been added. + - Transfer, ACM, CodeCommit, Kinesis Analytics, Amplify, Application Auto Scaling, Kafka, Timestream Query, Timestream Write, Timestream Write, S3 Control, Elastic Load Balancing v2, Redshift Data +- .NET 5.0 support added. +- AWSSDK.Core set to 3.5.0 as the minimum version. +- Tested against LocalStack v0.12.07 container. + +### [v1.0](https://github.com/localstack-dotnet/localstack-dotnet-client/releases/tag/v1.0.0) + +- New endpoints in the official [Localstack Python Client](https://github.com/localstack/localstack-python-client) v0.23 have been added. + - ElastiCache, Kms, Emr, Ecs, Eks, XRay, ElasticBeanstalk, AppSync, CloudFront, Athena, Glue, Api Gateway V2, RdsData, SageMaker, SageMakerRuntime, Ecr, Qldb +- .netcore2.2 support removed since Microsoft depracated it. .netcore3.1 support added. +- AWSSDK.Core set to 3.3.106.5 as the minimum version. + +### [v0.8.0.163](https://github.com/localstack-dotnet/localstack-dotnet-client/releases/tag/v0.8.0.163) + +- First release. + +## License + +Licensed under MIT, see [LICENSE](LICENSE) for the full text. diff --git a/src/LocalStack.Client/LocalStack.Client.csproj b/src/LocalStack.Client/LocalStack.Client.csproj index 58f810d..b8aac07 100644 --- a/src/LocalStack.Client/LocalStack.Client.csproj +++ b/src/LocalStack.Client/LocalStack.Client.csproj @@ -13,7 +13,7 @@ aws-sdk, localstack, client-library, dotnet, dotnet-core LICENSE.txt - + README.md @@ -26,13 +26,20 @@ + + + + Always + + Always + diff --git a/src/LocalStack.Client/README.md b/src/LocalStack.Client/README.md new file mode 100644 index 0000000..a3e73ea --- /dev/null +++ b/src/LocalStack.Client/README.md @@ -0,0 +1,504 @@ +![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/) [![Testspace tests (compact)](https://img.shields.io/testspace/tests/localstack-dotnet/localstack-dotnet:localstack-dotnet-client/master?compact_message)](https://localstack-dotnet.testspace.com/spaces/155695/result_sets) + +# LocalStack .Net Core and .Net Framework Client + +![LocalStack](https://github.com/localstack-dotnet/localstack-dotnet-client/blob/master/assets/localstack-dotnet.png?raw=true) + +This is an easy-to-use .NET client for [LocalStack](https://github.com/localstack/localstack). +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. + +## Continuous integration + +| 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 | macO | [![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) | + +## Table of Contents + +1. [Supported Platforms](#supported-platforms) +2. [Prerequisites](#prerequisites) +3. [Installation](#installation) +4. [Usage](#usage) + - [LocalStack.Client.Extensions (Recommended)](#localstack-client-extensions) + - [Installation](#extensions-installation) + - [Usage](#extensions-usage) + - [About AddAwsService](#extensions-usage-about-addawsservice) + - [useServiceUrl Parameter](#useserviceurl) + - [Standalone Initialization](#standalone-initialization) + - [Microsoft.Extensions.DependencyInjection Initialization](#di) +5. [Developing](#developing) + - [About Sandbox Applications](#about-sandboxes) + - [Running Tests](#running-tests) +6. [Changelog](#changelog) +7. [License](#license) + +## Supported Platforms + +- [.NET 6.0](https://dotnet.microsoft.com/download/dotnet/6.0) +- [.NET 5.0](https://dotnet.microsoft.com/download/dotnet/5.0) +- [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) +- [.NET 4.6.1 and Above](https://dotnet.microsoft.com/download/dotnet-framework) + +## Prerequisites + +To make use of this library, you need to have [LocalStack](https://github.com/localstack/localstack) +installed on your local machine. In particular, the `localstack` command needs to be available. + +## Installation + +The easiest way to install *LocalStack .NET Client* is via `nuget`: + +``` +Install-Package LocalStack.Client +``` + +Or use `dotnet cli` + +``` +dotnet add package LocalStack.Client +``` + +| 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) | + +## Usage + +This library provides a thin wrapper around [aws-sdk-net](https://github.com/aws/aws-sdk-net). +Therefore the usage of this library is same as using `AWS SDK for .NET`. + +See [Getting Started with the AWS SDK for .NET](https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-setup.html) + +This library can be used with any DI library, [AWSSDK.Extensions.NETCore.Setup](https://docs.aws.amazon.com/sdk-for-net/latest/developer-guide/net-dg-config-netcore.html) or it can be used as standalone. + +### LocalStack.Client.Extensions (Recommended) + +[LocalStack.Client.Extensions](https://www.nuget.org/packages/LocalStack.Client/) is extensions for the LocalStack.NET Client to integrate with .NET Core configuration and dependency injection frameworks. The extensions also provides wrapper around [AWSSDK.Extensions.NETCore.Setup](https://docs.aws.amazon.com/sdk-for-net/latest/developer-guide/net-dg-config-netcore.html) to use both LocalStack and AWS side-by-side. + +This approach is recommended since `AWSSDK.Extensions.NETCore.Setup` is very popular and also it is best practice for using [AWSSDK.NET](https://aws.amazon.com/sdk-for-net/) with .NET Core, .NET 5 or .NET 6 + +#### Installation + +The easiest way to install *LocalStack .NET Client Extensions* is via `nuget`: + +``` +Install-Package LocalStack.Client.Extensions +``` + +Or use `dotnet cli` + +``` +dotnet add package LocalStack.Client.Extensions +``` + +#### Usage + +The usage is very similar to `AWSSDK.Extensions.NETCore.Setup` with some differences. + +```csharp +public void ConfigureServices(IServiceCollection services) +{ + // Add framework services. + services.AddMvc(); + + services.AddLocalStack(Configuration) + services.AddDefaultAWSOptions(Configuration.GetAWSOptions()); + services.AddAwsService(); + services.AddAwsService(); +} +``` + +The most important difference is that `AddAwsService` extensions method is used instead of `AddAWSService` used in `AWSSDK.Extensions.NETCore.Setup`. The reason for this will be explained later in this section. + +In addition, the `AddLocalStack` extension method is also used. + +(Alternatively, `AddAWSServiceLocalStack` method can be used to prevent mix-up with `AddAWSService`.) + +`AddLocalStack` extension method is responsible for both configurations and adding of `LocalStack.Client` dependencies to service collection. + +You can configure `LocalStack.Client` by using entries in the `appsettings.json` files, as shown in the following example. + +```json +"LocalStack": { + "UseLocalStack": true, + "Session": { + "AwsAccessKeyId": "my-AwsAccessKeyId", + "AwsAccessKey": "my-AwsAccessKey", + "AwsSessionToken": "my-AwsSessionToken", + "RegionName": "eu-central-1" + }, + "Config": { + "LocalStackHost": "localhost", + "UseSsl": false, + "UseLegacyPorts": false, + "EdgePort": 4566 + } +} +``` + +All the entries above are has shown with default values (except `UseLocalStack`, it's `false` by default). +So the above entries do not need to be specified. + +What is entered for the aws credential values ​​in the `Session` section does not matter for LocalStack. + +`RegionName` is important since LocalStack creates resources by spesified region. + +`Config` section contains important entries for local development. Starting with LocalStack releases after `v0.11.5`, all services are now exposed via the edge service (port 4566) only! If you are using a version of LocalStack lower than v0.11.5, you should set `UseLegacyPorts` to `true`. Edge port can be set to any available port ([see LocalStack configuration section](https://github.com/localstack/localstack#configurations)). If you have made such a change in LocalStack's configuration, be sure to set the same port value to `EdgePort` in the `Config` section. For `LocalStackHost` and `UseSsl` entries, ​​corresponding to the [LocalStack configuration](https://github.com/localstack/localstack#configurations) should be used. + +The following sample setting files can be used to use both `LocalStack.Client` and`AWSSDK.Extensions.NETCore.Setup` in different environments. + +`appsettings.Development.json` + +```json +"LocalStack": { + "UseLocalStack": true, + "Session": { + ... + }, + "Config": { + ... + } +} +``` + +`appsettings.Production.json` + +```json +"LocalStack": { + "UseLocalStack": false +}, +"AWS": { + "Profile": "", + "Region": "eu-central-1" +} +``` + +See project [LocalStack.Client.Sandbox.WithGenericHost](https://github.com/localstack-dotnet/localstack-dotnet-client/tree/master/tests/sandboxes/LocalStack.Client.Sandbox.WithGenericHost) for a use case. + +#### About AddAwsService + +`AddAwsService` is equivalent of `AddAWSService` used in `AWSSDK.Extensions.NETCore.Setup`. It decides which factory to use when resolving any AWS Service. To decide this, it checks the `UseLocalStack` entry. +If the `UseLocalStack` entry is `true`, it uses the [Session](https://github.com/localstack-dotnet/localstack-dotnet-client/blob/master/src/LocalStack.Client/Session.cs) class of `LocalStack.Client` to create AWS Service. If the `UseLocalStack` entry is `false`, it uses the [ClientFactory](https://github.com/aws/aws-sdk-net/blob/master/extensions/src/AWSSDK.Extensions.NETCore.Setup/ClientFactory.cs) class of `AWSSDK.Extensions.NETCore.Setup` which is also used by original `AddAWSService`. + +It is named as `AddAwsService` to avoid name conflict with `AddAWSService`. + +(Alternatively, `AddAWSServiceLocalStack` method can be used to prevent mix-up with `AddAWSService`.) + +#### useServiceUrl Parameter + +LocalStack.NET uses [ClientConfig](https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Core/Amazon.Runtime/ClientConfig.cs) to configure AWS clients to connect LocalStack. `ClientConfig` has two properties called `ServiceUrl` and `RegionEndpoint`, these are mutually exclusive properties. Whichever property is set last will cause the other to automatically be reset to null. LocalStack.NET has given priority to the RegionEndpoint property and the `us-east-1` region is used as the default value (Different regions can be set by using appsettings.json, see [RegionName](#session-regioname) entry. Because of it sets the RegionEndpoint property after the ServiceUrl property, ServiceUrl will be set to null. + +To override this behavior, the `useServiceUrl` optional parameter can be set to `true` as below. + +```csharp +public void ConfigureServices(IServiceCollection services) +{ + // Add framework services. + services.AddMvc(); + + services.AddLocalStack(Configuration) + services.AddDefaultAWSOptions(Configuration.GetAWSOptions()); + services.AddAwsService(); + services.AddAwsService(useServiceUrl: true) + services.AddAwsService(useServiceUrl: true) +} +``` + +The `RegionEndpoint` is not applicable for services such as AWS MediaStore, Iot. The optional parameter `useServiceUrl` can be useful for use in such scenarios. + +### Standalone Initialization + +If you do not want to use any DI library, you have to instantiate `SessionStandalone` as follows. + +```csharp +/* +* ==== Default Values ==== +* AwsAccessKeyId: accessKey (It doesn't matter to LocalStack) +* AwsAccessKey: secretKey (It doesn't matter to LocalStack) +* AwsSessionToken: token (It doesn't matter to LocalStack) +* RegionName: us-east-1 +* ==== Custom Values ==== +* var sessionOptions = new SessionOptions("someAwsAccessKeyId", "someAwsAccessKey", "someAwsSessionToken", "eu-central-"); +*/ +var sessionOptions = new SessionOptions(); + +/* +* ==== Default Values ==== +* LocalStackHost: localhost +* UseSsl: false +* UseLegacyPorts: false (Set true if your LocalStack version is 0.11.5 or above) +* EdgePort: 4566 (It doesn't matter if use legacy ports) +* ==== Custom Values ==== +* var configOptions = new ConfigOptions("mylocalhost", false, false, 4566); +*/ +var configOptions = new ConfigOptions(); + +ISession session = SessionStandalone.Init() + .WithSessionOptions(sessionOptions) + .WithConfigurationOptions(configOptions).Create(); + +var amazonS3Client = session.CreateClientByImplementation(); +``` + +`CreateClientByInterface` method can also be used to create AWS service, as follows + +```csharp +var amazonS3Client = session.CreateClientByInterface(); +``` + +### useServiceUrl Parameter + +LocalStack.NET uses [ClientConfig](https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Core/Amazon.Runtime/ClientConfig.cs) to configure AWS clients to connect LocalStack. `ClientConfig` has two properties called `ServiceUrl` and `RegionEndpoint`, these are mutually exclusive properties. Whichever property is set last will cause the other to automatically be reset to null. LocalStack.NET has given priority to the RegionEndpoint property and the `us-east-1` region is used as the default value (Different regions can be set by using appsettings.json, see [RegionName](#session-regioname) entry. Because of it sets the RegionEndpoint property after the ServiceUrl property, ServiceUrl will be set to null. + +To override this behavior, the `useServiceUrl` optional parameter can be set to `true` as below. + +```csharp +var sessionOptions = new SessionOptions(); +var configOptions = new ConfigOptions(); + +ISession session = SessionStandalone.Init() + .WithSessionOptions(sessionOptions) + .WithConfigurationOptions(configOptions).Create(); + +var amazonS3Client = session.CreateClientByImplementation(true); +var amazonS3Client = session.CreateClientByImplementation(); +``` + +The `RegionEndpoint` is not applicable for services such as AWS MediaStore, Iot. The optional parameter `useServiceUrl` can be useful for use in such scenarios. + +`CreateClientByInterface` method can also be used to create AWS service, as follows + +```csharp +var amazonS3Client = session.CreateClientByInterface(true); +``` + +### Microsoft.Extensions.DependencyInjection Initialization + +First, you need to install `Microsoft.Extensions.DependencyInjection` nuget package as follows + +``` +dotnet add package Microsoft.Extensions.DependencyInjection +``` + +Register necessary dependencies to `ServiceCollection` as follows + +```csharp +var collection = new ServiceCollection(); + +/* +* ==== Default Values ==== +* AwsAccessKeyId: accessKey (It doesn't matter to LocalStack) +* AwsAccessKey: secretKey (It doesn't matter to LocalStack) +* AwsSessionToken: token (It doesn't matter to LocalStack) +* RegionName: us-east-1 +* ==== Custom Values ==== +* var sessionOptions = new SessionOptions("someAwsAccessKeyId", "someAwsAccessKey", "someAwsSessionToken", "eu-central-"); +*/ +var sessionOptions = new SessionOptions(); + +/* +* ==== Default Values ==== +* LocalStackHost: localhost +* UseSsl: false +* UseLegacyPorts: false (Set true if your LocalStack version is 0.11.4 or below) +* EdgePort: 4566 (It doesn't matter if use legacy ports) +* ==== Custom Values ==== +* var configOptions = new ConfigOptions("mylocalhost", false, false, 4566); +*/ +var configOptions = new ConfigOptions(); + +collection + .AddScoped(provider => sessionOptions) + .AddScoped(provider => configOptions)) + .AddScoped() + .AddSingleton() + .AddSingleton() + .AddTransient(provider => + { + var session = provider.GetRequiredService(); + + return (IAmazonS3) session.CreateClientByInterface(); + }); + +ServiceProvider serviceProvider = collection.BuildServiceProvider(); + +var amazonS3Client = serviceProvider.GetRequiredService(); +``` + +If you want to use it with `ConfigurationBuilder`, you can also choose a usage as below. + +```csharp +var collection = new ServiceCollection(); +var builder = new ConfigurationBuilder(); + +builder.SetBasePath(Directory.GetCurrentDirectory()); +builder.AddJsonFile("appsettings.json", true); +builder.AddJsonFile("appsettings.Development.json", true); +builder.AddEnvironmentVariables(); +builder.AddCommandLine(args); + +IConfiguration configuration = builder.Build(); + +collection.Configure(options => configuration.GetSection("LocalStack").Bind(options, c => c.BindNonPublicProperties = true)); +/* +* ==== Default Values ==== +* AwsAccessKeyId: accessKey (It doesn't matter to LocalStack) +* AwsAccessKey: secretKey (It doesn't matter to LocalStack) +* AwsSessionToken: token (It doesn't matter to LocalStack) +* RegionName: us-east-1 + */ +collection.Configure(options => configuration.GetSection("LocalStack") + .GetSection(nameof(LocalStackOptions.Session)) + .Bind(options, c => c.BindNonPublicProperties = true)); +/* + * ==== Default Values ==== + * LocalStackHost: localhost + * UseSsl: false + * UseLegacyPorts: false (Set true if your LocalStack version is 0.11.5 or above) + * EdgePort: 4566 (It doesn't matter if use legacy ports) + */ +collection.Configure(options => configuration.GetSection("LocalStack") + .GetSection(nameof(LocalStackOptions.Config)) + .Bind(options, c => c.BindNonPublicProperties = true)); + + +collection.AddTransient(provider => +{ + ConfigOptions options = provider.GetRequiredService>().Value; + + return new Config(options); +}) +.AddSingleton() +.AddSingleton(provider => +{ + SessionOptions sessionOptions = provider.GetRequiredService>().Value; + var config = provider.GetRequiredService(); + var sessionReflection = provider.GetRequiredService(); + + return new Session(sessionOptions, config, sessionReflection); +}) +.AddTransient(provider => +{ + var session = provider.GetRequiredService(); + + return (IAmazonS3) session.CreateClientByInterface(); +}); + +ServiceProvider serviceProvider = collection.BuildServiceProvider(); + +var amazonS3Client = serviceProvider.GetRequiredService(); +``` + +See [useServiceUrl] parameter usage (#standalone-useserviceurl) + +## Developing + +We welcome feedback, bug reports, and pull requests! + +Use commands below to get you started and test your code: + +Windows + +``` +build.ps1 +``` + +Linux + +``` +./build.sh +``` + +### About Sandbox Applications + +In addition to Unit Tests and Functional Test, LocalStack .Net Repository has various sandbox console applications for both testing and example purposes under [tests/sandboxes](https://github.com/localstack-dotnet/localstack-dotnet-client/tree/master/tests/sandboxes) + +Sandbox applications include various examples of initialization methods of `LocalStack.Client` (see [Usage](#usage) section) and common AWS applications. They provide a convenient and safe environment for those who want to make developments in the library. + +To run sandbox applications with LocalStack container, console application called [LocalStack.Container](https://github.com/localstack-dotnet/localstack-dotnet-client/tree/master/tests/sandboxes/LocalStack.Container) has been developed. It uses [Dotnet Testcontainer](https://github.com/HofmeisterAn/dotnet-testcontainers) to bootstrap LocalStack. Experiments can be made by running LocalStack.Container application first and then any sandbox application. + +### Running Tests + +Use commands below to run tests + +Windows + +``` +build.ps1 --target=tests +``` + +Linux + +``` +./build.sh --target=tests +``` + +## Changelog + +### [v1.3.0](https://github.com/localstack-dotnet/localstack-dotnet-client/releases/tag/v1.3.0) + +#### 1. New Features +- New endpoints in the official [Localstack Python Client](https://github.com/localstack/localstack-python-client) v1.27 have been added. + - SESv2 + - EventBridge ([#14](https://github.com/localstack-dotnet/localstack-dotnet-client/pull/14)) +- Tested against LocalStack v0.13.0 container. +#### 2. Enhancements +- `useServiceUrl` parameter added to change client connection behavior. See [useServiceUrl Parameter](#useserviceurl) +- Readme and SourceLink added to Nuget packages +#### 3. Bug Fixes +- Session::RegionName configuration does not honor while creating AWS client ([#15](https://github.com/localstack-dotnet/localstack-dotnet-client/issues/15)) + +Thanks to [petertownsend](https://github.com/petertownsend) for his contribution + +### [v1.2.3](https://github.com/localstack-dotnet/localstack-dotnet-client/releases/tag/v1.2.3) + +- New endpoints in the official [Localstack Python Client](https://github.com/localstack/localstack-python-client) v1.25 have been added. + - Config Service +- .NET 6.0 support added. +- AWSSDK.Core set to 3.7.3.15 as the minimum version. +- AWSSDK.Extensions.NETCore.Setup set to 3.7.1 as the minimum version. +- Tested against LocalStack v0.13.0 container. + +### [v1.2.2](https://github.com/localstack-dotnet/localstack-dotnet-client/releases/tag/v1.2.2) + +- New endpoints in the official [Localstack Python Client](https://github.com/localstack/localstack-python-client) v1.22 have been added. + - EFS, Backup, LakeFormation, WAF, WAF V2 and QLDB Session +- AWSSDK.Core set to 3.7.1 as the minimum version. +- Tested against LocalStack v0.12.16 container. + +### [v1.2](https://github.com/localstack-dotnet/localstack-dotnet-client/releases/tag/v1.2.0) + +- New endpoints in the official [Localstack Python Client](https://github.com/localstack/localstack-python-client) v1.20 have been added. + - IoTAnalytics, IoT Events, IoT Events Data, IoT Wireless, IoT Data Plane, IoT Jobs Data Plane, Support, Neptune, DocDB, ServiceDiscovery, ServerlessApplicationRepository, AppConfig, Cost Explorer, MediaConvert, Resource Groups Tagging API, Resource Groups +- AWSSDK.Core set to 3.7.0 as the minimum version. +- Obsolete methods removed. +- New alternate AddAWSServiceLocalStack method added to prevent mix up with AddAWSService (for LocalStack.Client.Extension v1.1.0). +- Tested against LocalStack v0.12.10 container. + +### [v1.1](https://github.com/localstack-dotnet/localstack-dotnet-client/releases/tag/v1.1.0) + +- New endpoints in the official [Localstack Python Client](https://github.com/localstack/localstack-python-client) v1.10 have been added. + - Transfer, ACM, CodeCommit, Kinesis Analytics, Amplify, Application Auto Scaling, Kafka, Timestream Query, Timestream Write, Timestream Write, S3 Control, Elastic Load Balancing v2, Redshift Data +- .NET 5.0 support added. +- AWSSDK.Core set to 3.5.0 as the minimum version. +- Tested against LocalStack v0.12.07 container. + +### [v1.0](https://github.com/localstack-dotnet/localstack-dotnet-client/releases/tag/v1.0.0) + +- New endpoints in the official [Localstack Python Client](https://github.com/localstack/localstack-python-client) v0.23 have been added. + - ElastiCache, Kms, Emr, Ecs, Eks, XRay, ElasticBeanstalk, AppSync, CloudFront, Athena, Glue, Api Gateway V2, RdsData, SageMaker, SageMakerRuntime, Ecr, Qldb +- .netcore2.2 support removed since Microsoft depracated it. .netcore3.1 support added. +- AWSSDK.Core set to 3.3.106.5 as the minimum version. + +### [v0.8.0.163](https://github.com/localstack-dotnet/localstack-dotnet-client/releases/tag/v0.8.0.163) + +- First release. + +## License + +Licensed under MIT, see [LICENSE](LICENSE) for the full text. From 1e7ffc5db16759dfc7eda5051c2147fb078633a9 Mon Sep 17 00:00:00 2001 From: Blind-Striker Date: Mon, 29 Nov 2021 00:05:45 +0300 Subject: [PATCH 22/24] fixes #13 --- .../LocalStack.Client.Extensions.csproj | 9 ++++++++- src/LocalStack.Client/LocalStack.Client.csproj | 9 +++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj b/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj index 5e48c05..f2e01ac 100644 --- a/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj +++ b/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj @@ -6,6 +6,7 @@ LocalStack.Client.Extensions LocalStack.Client.Extensions latest + 1.1.2 LocalStack.NET Client @@ -14,7 +15,13 @@ aws-sdk, localstack, client-library, dotnet, dotnet-core LICENSE.txt README.md - 1.1.2 + true + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + + + true diff --git a/src/LocalStack.Client/LocalStack.Client.csproj b/src/LocalStack.Client/LocalStack.Client.csproj index b8aac07..0f00c9c 100644 --- a/src/LocalStack.Client/LocalStack.Client.csproj +++ b/src/LocalStack.Client/LocalStack.Client.csproj @@ -14,6 +14,13 @@ aws-sdk, localstack, client-library, dotnet, dotnet-core LICENSE.txt README.md + true + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + + + true @@ -21,7 +28,6 @@ - @@ -43,7 +49,6 @@ - NET461 From 1b63945f013e48eb5adb78c8c6bc8380aad0249e Mon Sep 17 00:00:00 2001 From: Blind-Striker Date: Mon, 29 Nov 2021 00:47:08 +0300 Subject: [PATCH 23/24] add delete container logic before every functional tests --- .../LocalStack.Build/LocalStack.Build.csproj | 1 + build/LocalStack.Build/Program.cs | 27 ++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/build/LocalStack.Build/LocalStack.Build.csproj b/build/LocalStack.Build/LocalStack.Build.csproj index 36da102..e0c8120 100644 --- a/build/LocalStack.Build/LocalStack.Build.csproj +++ b/build/LocalStack.Build/LocalStack.Build.csproj @@ -7,6 +7,7 @@ CA1303 + diff --git a/build/LocalStack.Build/Program.cs b/build/LocalStack.Build/Program.cs index b2239b7..f3a4122 100644 --- a/build/LocalStack.Build/Program.cs +++ b/build/LocalStack.Build/Program.cs @@ -1,4 +1,6 @@ -return new CakeHost() +using Cake.Docker; + +return new CakeHost() .UseContext() .Run(args); @@ -84,6 +86,29 @@ public override void Run(BuildContext context) context.Warning($"=============Running {targetFramework.ToUpper()} 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.IsRunningOnUnix() && targetFramework == "net461") { context.RunXUnitUsingMono(targetFramework, $"{testProj.DirectoryPath}/bin/{context.BuildConfiguration}/{targetFramework}/{testProj.AssemblyName}.dll"); From 229c1d28fd04f0d0fb24730183889bd9db919684 Mon Sep 17 00:00:00 2001 From: Blind-Striker Date: Mon, 29 Nov 2021 00:55:00 +0300 Subject: [PATCH 24/24] add wait logic for real life sns-sqs test --- .../Scenarios/RealLife/SnsToSqsScenario.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/RealLife/SnsToSqsScenario.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/RealLife/SnsToSqsScenario.cs index d5bfee0..58db736 100644 --- a/tests/LocalStack.Client.Functional.Tests/Scenarios/RealLife/SnsToSqsScenario.cs +++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/RealLife/SnsToSqsScenario.cs @@ -1,4 +1,6 @@ -using MessageAttributeValue = Amazon.SimpleNotificationService.Model.MessageAttributeValue; +using System.Threading; + +using MessageAttributeValue = Amazon.SimpleNotificationService.Model.MessageAttributeValue; namespace LocalStack.Client.Functional.Tests.Scenarios.RealLife; @@ -64,6 +66,14 @@ public virtual async Task Should_Create_A_SNS_Topic_And_SQS_Queue_Then_Subscribe Assert.Equal(HttpStatusCode.OK, receiveMessageResponse.HttpStatusCode); + if (receiveMessageResponse.Messages.Count == 0) + { + await Task.Delay(2000); + receiveMessageResponse = await AmazonSqs.ReceiveMessageAsync(receiveMessageRequest); + + Assert.Equal(HttpStatusCode.OK, receiveMessageResponse.HttpStatusCode); + } + Assert.NotNull(receiveMessageResponse.Messages); Assert.NotEmpty(receiveMessageResponse.Messages); Assert.Single(receiveMessageResponse.Messages);