diff --git a/.dockerignore b/.dockerignore index dd3d41423..5fbf98f1e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,7 +7,6 @@ docker-compose*.yml docker-compose.dcproj *.sln -!eShopOnContainers-ServicesAndWebApps.sln *.md hosts LICENSE @@ -31,4 +30,13 @@ cli-linux **/wwwroot/lib/* global.json **/appsettings.localhost.json -src/Web/WebSPA/wwwroot/ \ No newline at end of file +src/Web/WebSPA/wwwroot/ +packages/ +csproj-files/ +test-results/ +TestResults/ +src/Mobile/ +src/Web/Catalog.WebForms/ +src/Web/WebMonolithic/ +src/BuildingBlocks/CommandBus/ +src/Services/Marketing/Infrastructure/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6bc27551d..f3fdf2b8f 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,13 @@ bld/ # Visual Studio 2015 cache/options directory .vs/ + +# Dockerfile projects folder for restore-packages script +csproj-files/ + +# .js files created on build: +src/Web/WebMVC/wwwroot/js/site* + # Uncomment if you have tasks that create the project's static files in wwwroot **/wwwroot/lib/ !/wwwroot/lib/signalr @@ -38,6 +45,8 @@ bld/ *.VisualState.xml TestResult.xml +tests-results/ + # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ @@ -265,4 +274,9 @@ pub/ .mfractor # Ignore HealthCheckdb -*healthchecksdb* \ No newline at end of file +*healthchecksdb* + +# Ignores all extra inf.yaml and app.yaml that are copied by prepare-devspaces.ps1 +src/**/app.yaml +src/**/inf.yaml + diff --git a/README.md b/README.md index ffdf6450a..b7f92ffa0 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,25 @@ -# eShopOnContainers - Microservices Architecture and Containers based Reference Application (**BETA state** - Visual Studio 2017 and CLI environments compatible) +# eShopOnContainers - Microservices Architecture and Containers based Reference Application (**BETA state** - Visual Studio and CLI environments compatible) Sample .NET Core reference application, powered by Microsoft, based on a simplified microservices architecture and Docker containers. -[![Build status (Linux images)](https://msftdevtools.visualstudio.com/eShopOnContainers/_apis/build/status/All%20Microservices%20Linux)](https://msftdevtools.visualstudio.com/eShopOnContainers/_build/latest?definitionId=184) +## Linux Build Status for 'dev' branch +Dev branch contains the latest "stable" code, and their images are tagged with `:dev` in our [Docker Hub](https://cloud.docker.com/u/eshop/repository/list): + +| Basket API | Catalog API | Identity API | Location API | +| ------------- | ------------- | ------------- | ------------- | +| [![Basket API](https://msftdevtools.visualstudio.com/eShopOnContainers/_apis/build/status/microservices/basket?branchName=dev)](https://msftdevtools.visualstudio.com/eShopOnContainers/_build/latest?definitionId=199&branchName=dev) | [![Catalog API](https://msftdevtools.visualstudio.com/eShopOnContainers/_apis/build/status/microservices/catalog?branchName=dev)](https://msftdevtools.visualstudio.com/eShopOnContainers/_build/latest?definitionId=197&branchName=dev) | [![Identity API](https://msftdevtools.visualstudio.com/eShopOnContainers/_apis/build/status/microservices/identity?branchName=dev)](https://msftdevtools.visualstudio.com/eShopOnContainers/_build/latest?definitionId=200&branchName=dev) | [![Location API](https://msftdevtools.visualstudio.com/eShopOnContainers/_apis/build/status/microservices/location?branchName=dev)](https://msftdevtools.visualstudio.com/eShopOnContainers/_build/latest?definitionId=202&branchName=dev) | + +| Marketing API | Ordering API | Payment API | Api Gateways base image | +| ------------- | ------------- | ------------- | ------------- | +| [![Marketing API](https://msftdevtools.visualstudio.com/eShopOnContainers/_apis/build/status/microservices/marketing?branchName=dev)](https://msftdevtools.visualstudio.com/eShopOnContainers/_build/latest?definitionId=203&branchName=dev) | [![Ordering API](https://msftdevtools.visualstudio.com/eShopOnContainers/_apis/build/status/microservices/ordering?branchName=dev)](https://msftdevtools.visualstudio.com/eShopOnContainers/_build/latest?definitionId=198&branchName=dev) | [![Payment API](https://msftdevtools.visualstudio.com/eShopOnContainers/_apis/build/status/microservices/payment?branchName=dev)](https://msftdevtools.visualstudio.com/eShopOnContainers/_build/latest?definitionId=205&branchName=dev) | [![Api Gateways base image](https://msftdevtools.visualstudio.com/eShopOnContainers/_apis/build/status/microservices/apigws?branchName=dev)](https://msftdevtools.visualstudio.com/eShopOnContainers/_build/latest?definitionId=201&branchName=dev) + +| Web Shopping Aggregator | Mobile Shopping Aggregator | WebMVC Client | WebSPA Client | +| ------------- | ------------- | ------------- | ------------- | +| [![Web Shopping Aggregator](https://msftdevtools.visualstudio.com/eShopOnContainers/_apis/build/status/microservices/web-shopping-agg?branchName=dev)](https://msftdevtools.visualstudio.com/eShopOnContainers/_build/latest?definitionId=206&branchName=dev) | [![Mobile Shopping Aggregator](https://msftdevtools.visualstudio.com/eShopOnContainers/_apis/build/status/microservices/mobile-shopping-agg?branchName=dev)](https://msftdevtools.visualstudio.com/eShopOnContainers/_build/latest?definitionId=204&branchName=dev) | [![WebMVC Client](https://msftdevtools.visualstudio.com/eShopOnContainers/_apis/build/status/microservices/webmvc?branchName=dev)](https://msftdevtools.visualstudio.com/eShopOnContainers/_build/latest?definitionId=209&branchName=dev) | [![WebSPA Client](https://msftdevtools.visualstudio.com/eShopOnContainers/_apis/build/status/microservices/webspa?branchName=dev)](https://msftdevtools.visualstudio.com/eShopOnContainers/_build/latest?definitionId=210&branchName=dev) | + +| Web Status | Webhooks API | Webbhooks demo client | +| ------------- | ------------- | ------------- | + [![Web Status](https://msftdevtools.visualstudio.com/eShopOnContainers/_apis/build/status/microservices/webstatus?branchName=dev)](https://msftdevtools.visualstudio.com/eShopOnContainers/_build/latest?definitionId=211&branchName=dev) | [![Webhooks API](https://msftdevtools.visualstudio.com/eShopOnContainers/_apis/build/status/microservices/webhooks?branchName=dev)](https://msftdevtools.visualstudio.com/eShopOnContainers/_build/latest?definitionId=207&branchName=dev) | [![Webhooks demo client](https://msftdevtools.visualstudio.com/eShopOnContainers/_apis/build/status/microservices/webhooks-client?branchName=dev)](https://msftdevtools.visualstudio.com/eShopOnContainers/_build/latest?definitionId=208&branchName=dev) | ## IMPORTANT NOTES! **You can use either the latest version of Visual Studio or simply Docker CLI and .NET CLI for Windows, Mac and Linux**. @@ -63,6 +80,12 @@ The architecture proposes a microservice oriented architecture implementation wi >

A similar case is defined in regard to Redis cache running as a container for the development environment. Or a No-SQL database (MongoDB) running as a container. >

However, in a real production environment it is recommended to have your databases (SQL Server, Redis, and the NO-SQL database, in this case) in HA (High Available) services like Azure SQL Database, Redis as a service and Azure CosmosDB instead the MongoDB container (as both systems share the same access protocol). If you want to change to a production configuration, you'll just need to change the connection strings once you have set up the servers in an HA cloud or on-premises. +> ### Important Note on EventBus +> In this solution's current EventBus is a simplified implementation, mainly used for learning purposes (development and testing), so it doesn't handle all production scenarios, most notably on error handling.

+> The following forks provide production environment level implementation examples with eShopOnContainers : +> * Implementation with [CAP](https://github.com/dotnetcore/CAP) : https://github.com/yang-xiaodong/eShopOnContainers +> * Implementation with [NServiceBus](https://github.com/Particular/NServiceBus) : https://github.com/Particular/eShopOnContainers + ## Related documentation and guidance While developing this reference application, we've been creating a reference Guide/eBook focusing on architecting and developing containerized and microservice based .NET Applications (download link available below) which explains in detail how to develop this kind of architectural style (microservices, Docker containers, Domain-Driven Design for certain microservices) plus other simpler architectural styles, like monolithic apps that can also live as Docker containers.

@@ -106,7 +129,7 @@ Finally, those microservices are consumed by multiple client web and mobile apps ## Setting up your development environment for eShopOnContainers -### Visual Studio 2017 and Windows based +### Visual Studio 2017 (or above) and Windows based This is the more straightforward way to get started: https://github.com/dotnet-architecture/eShopOnContainers/wiki/02.-Setting-eShopOnContainers-in-a-Visual-Studio-2017-environment @@ -126,5 +149,5 @@ As mentioned, we'd appreciate your feedback, improvements and ideas. You can create new issues at the issues section, do pull requests and/or send emails to **eshop_feedback@service.microsoft.com** ## Questions -[QUESTION] Answer +1 if the solution is working for you (Through VS2017 or CLI environment): +[QUESTION] Answer +1 if the solution is working for you (Through VS or CLI environment): https://github.com/dotnet/eShopOnContainers/issues/107 diff --git a/build/azure-devops/apigws/azure-pipelines.yml b/build/azure-devops/apigws/azure-pipelines.yml new file mode 100644 index 000000000..6fedf33a6 --- /dev/null +++ b/build/azure-devops/apigws/azure-pipelines.yml @@ -0,0 +1,52 @@ +variables: + registryEndpoint: eshop-registry +trigger: + branches: + include: + - master + - dev + paths: + include: + - src/ApiGateways/* + - k8s/helm/apigwmm/* + - k8s/helm/apigwms/* + - k8s/helm/apigwwm/* + - k8s/helm/apigwws/* + exclude: + - src/ApiGateways/Mobile.Bff.Shopping/aggregator/* + - src/ApiGateways/Web.Bff.Shopping/aggregator/* +jobs: +- job: BuildLinux + pool: + vmImage: 'ubuntu-16.04' + steps: + - task: DockerCompose@0 + displayName: Compose build apigws + inputs: + dockerComposeCommand: 'build mobileshoppingapigw mobilemarketingapigw webshoppingapigw webmarketingapigw' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) + - task: DockerCompose@0 + displayName: Compose push apigws + inputs: + dockerComposeCommand: 'push mobileshoppingapigw mobilemarketingapigw webshoppingapigw webmarketingapigw' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) + - task: CopyFiles@2 + inputs: + sourceFolder: $(Build.SourcesDirectory)/k8s/helm + targetFolder: $(Build.ArtifactStagingDirectory)/k8s/helm + - task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: $(Build.ArtifactStagingDirectory)/k8s/helm + artifactName: helm diff --git a/build/azure-devops/basket-api/azure-pipelines.yml b/build/azure-devops/basket-api/azure-pipelines.yml new file mode 100644 index 000000000..6d94c5918 --- /dev/null +++ b/build/azure-devops/basket-api/azure-pipelines.yml @@ -0,0 +1,45 @@ +pool: + vmImage: 'ubuntu-16.04' +variables: + registryEndpoint: eshop-registry +trigger: + branches: + include: + - master + - dev + paths: + include: + - src/BuildingBlocks/* + - src/Services/Basket/* + - k8s/helm/basket-api/* +steps: +- task: DockerCompose@0 + displayName: Compose build basket + inputs: + dockerComposeCommand: 'build basket.api' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: DockerCompose@0 + displayName: Compose push basket + inputs: + dockerComposeCommand: 'push basket.api' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: CopyFiles@2 + inputs: + sourceFolder: $(Build.SourcesDirectory)/k8s/helm + targetFolder: $(Build.ArtifactStagingDirectory)/k8s/helm +- task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: $(Build.ArtifactStagingDirectory)/k8s/helm + artifactName: helm diff --git a/build/azure-devops/catalog-api/azure-pipelines.yml b/build/azure-devops/catalog-api/azure-pipelines.yml new file mode 100644 index 000000000..d277d4777 --- /dev/null +++ b/build/azure-devops/catalog-api/azure-pipelines.yml @@ -0,0 +1,45 @@ +pool: + vmImage: 'ubuntu-16.04' +variables: + registryEndpoint: eshop-registry +trigger: + branches: + include: + - master + - dev + paths: + include: + - src/BuildingBlocks/* + - src/Services/Catalog/* + - k8s/helm/catalog-api/* +steps: +- task: DockerCompose@0 + displayName: Compose build catalog + inputs: + dockerComposeCommand: 'build catalog.api' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: DockerCompose@0 + displayName: Compose push catalog + inputs: + dockerComposeCommand: 'push catalog.api' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: CopyFiles@2 + inputs: + sourceFolder: $(Build.SourcesDirectory)/k8s/helm + targetFolder: $(Build.ArtifactStagingDirectory)/k8s/helm +- task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: $(Build.ArtifactStagingDirectory)/k8s/helm + artifactName: helm diff --git a/build/azure-devops/identity-api/azure-pipelines.yml b/build/azure-devops/identity-api/azure-pipelines.yml new file mode 100644 index 000000000..454c03d16 --- /dev/null +++ b/build/azure-devops/identity-api/azure-pipelines.yml @@ -0,0 +1,45 @@ +pool: + vmImage: 'ubuntu-16.04' +variables: + registryEndpoint: eshop-registry +trigger: + branches: + include: + - master + - dev + paths: + include: + - src/BuildingBlocks/* + - src/Services/Identity/* + - k8s/helm/identity-api/* +steps: +- task: DockerCompose@0 + displayName: Compose build identity + inputs: + dockerComposeCommand: 'build identity.api' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: DockerCompose@0 + displayName: Compose push identity + inputs: + dockerComposeCommand: 'push identity.api' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: CopyFiles@2 + inputs: + sourceFolder: $(Build.SourcesDirectory)/k8s/helm + targetFolder: $(Build.ArtifactStagingDirectory)/k8s/helm +- task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: $(Build.ArtifactStagingDirectory)/k8s/helm + artifactName: helm diff --git a/build/azure-devops/infrastructure/azure-pipelines.yml b/build/azure-devops/infrastructure/azure-pipelines.yml new file mode 100644 index 000000000..50296d457 --- /dev/null +++ b/build/azure-devops/infrastructure/azure-pipelines.yml @@ -0,0 +1,25 @@ +pool: + vmImage: 'ubuntu-16.04' +variables: + registryEndpoint: eshop-registry +trigger: + branches: + include: + - master + - dev + paths: + include: + - k8s/helm/basket-data/* + - k8s/helm/keystore-data/* + - k8s/helm/nosql-data/* + - k8s/helm/rabbitmq/* + - k8s/helm/sql-data/* +steps: +- task: CopyFiles@2 + inputs: + sourceFolder: $(Build.SourcesDirectory)/k8s/helm + targetFolder: $(Build.ArtifactStagingDirectory)/k8s/helm +- task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: $(Build.ArtifactStagingDirectory)/k8s/helm + artifactName: helm diff --git a/build/azure-devops/location-api/azure-pipelines.yml b/build/azure-devops/location-api/azure-pipelines.yml new file mode 100644 index 000000000..ab3d31b3f --- /dev/null +++ b/build/azure-devops/location-api/azure-pipelines.yml @@ -0,0 +1,45 @@ +pool: + vmImage: 'ubuntu-16.04' +variables: + registryEndpoint: eshop-registry +trigger: + branches: + include: + - master + - dev + paths: + include: + - src/BuildingBlocks/* + - src/Services/Location/* + - k8s/helm/locations-api/* +steps: +- task: DockerCompose@0 + displayName: Compose build locations + inputs: + dockerComposeCommand: 'build locations.api' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: DockerCompose@0 + displayName: Compose push locations + inputs: + dockerComposeCommand: 'push locations.api' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: CopyFiles@2 + inputs: + sourceFolder: $(Build.SourcesDirectory)/k8s/helm + targetFolder: $(Build.ArtifactStagingDirectory)/k8s/helm +- task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: $(Build.ArtifactStagingDirectory)/k8s/helm + artifactName: helm diff --git a/build/azure-devops/marketing-api/azure-pipelines.yml b/build/azure-devops/marketing-api/azure-pipelines.yml new file mode 100644 index 000000000..193a0cccf --- /dev/null +++ b/build/azure-devops/marketing-api/azure-pipelines.yml @@ -0,0 +1,45 @@ +pool: + vmImage: 'ubuntu-16.04' +variables: + registryEndpoint: eshop-registry +trigger: + branches: + include: + - master + - dev + paths: + include: + - src/BuildingBlocks/* + - src/Services/Marketing/* + - k8s/helm/marketing-api/* +steps: +- task: DockerCompose@0 + displayName: Compose build marketing + inputs: + dockerComposeCommand: 'build marketing.api' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: DockerCompose@0 + displayName: Compose push marketing + inputs: + dockerComposeCommand: 'push marketing.api' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: CopyFiles@2 + inputs: + sourceFolder: $(Build.SourcesDirectory)/k8s/helm + targetFolder: $(Build.ArtifactStagingDirectory)/k8s/helm +- task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: $(Build.ArtifactStagingDirectory)/k8s/helm + artifactName: helm diff --git a/build/azure-devops/mobile-shopping-agg/azure-pipelines.yml b/build/azure-devops/mobile-shopping-agg/azure-pipelines.yml new file mode 100644 index 000000000..6fb0018a1 --- /dev/null +++ b/build/azure-devops/mobile-shopping-agg/azure-pipelines.yml @@ -0,0 +1,44 @@ +pool: + vmImage: 'ubuntu-16.04' +variables: + registryEndpoint: eshop-registry +trigger: + branches: + include: + - master + - dev + paths: + include: + - src/ApiGateways/Mobile.Bff.Shopping/aggregator/* + - k8s/helm/mobileshoppingagg/* +steps: +- task: DockerCompose@0 + displayName: Compose build mobileshoppingagg + inputs: + dockerComposeCommand: 'build mobileshoppingagg' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: DockerCompose@0 + displayName: Compose push mobileshoppingagg + inputs: + dockerComposeCommand: 'push mobileshoppingagg' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: CopyFiles@2 + inputs: + sourceFolder: $(Build.SourcesDirectory)/k8s/helm + targetFolder: $(Build.ArtifactStagingDirectory)/k8s/helm +- task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: $(Build.ArtifactStagingDirectory)/k8s/helm + artifactName: helm diff --git a/build/azure-devops/ordering-api/azure-pipelines.yml b/build/azure-devops/ordering-api/azure-pipelines.yml new file mode 100644 index 000000000..ef0b17a32 --- /dev/null +++ b/build/azure-devops/ordering-api/azure-pipelines.yml @@ -0,0 +1,47 @@ +pool: + vmImage: 'ubuntu-16.04' +variables: + registryEndpoint: eshop-registry +trigger: + branches: + include: + - master + - dev + paths: + include: + - src/BuildingBlocks/* + - src/Services/Ordering/* + - k8s/helm/ordering-api/* + - k8s/helm/ordering-backgroundtasks/* + - k8s/helm/ordering-signalrhub/* +steps: +- task: DockerCompose@0 + displayName: Compose build ordering + inputs: + dockerComposeCommand: 'build ordering.api ordering.backgroundtasks ordering.signalrhub' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: DockerCompose@0 + displayName: Compose push ordering + inputs: + dockerComposeCommand: 'push ordering.api ordering.backgroundtasks ordering.signalrhub' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: CopyFiles@2 + inputs: + sourceFolder: $(Build.SourcesDirectory)/k8s/helm + targetFolder: $(Build.ArtifactStagingDirectory)/k8s/helm +- task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: $(Build.ArtifactStagingDirectory)/k8s/helm + artifactName: helm diff --git a/build/azure-devops/payment-api/azure-pipelines.yml b/build/azure-devops/payment-api/azure-pipelines.yml new file mode 100644 index 000000000..8518c8841 --- /dev/null +++ b/build/azure-devops/payment-api/azure-pipelines.yml @@ -0,0 +1,45 @@ +pool: + vmImage: 'ubuntu-16.04' +variables: + registryEndpoint: eshop-registry +trigger: + branches: + include: + - master + - dev + paths: + include: + - src/BuildingBlocks/* + - src/Services/Payment/* + - k8s/helm/payment-api/* +steps: +- task: DockerCompose@0 + displayName: Compose build payment + inputs: + dockerComposeCommand: 'build payment.api' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: DockerCompose@0 + displayName: Compose push payment + inputs: + dockerComposeCommand: 'push payment.api' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: CopyFiles@2 + inputs: + sourceFolder: $(Build.SourcesDirectory)/k8s/helm + targetFolder: $(Build.ArtifactStagingDirectory)/k8s/helm +- task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: $(Build.ArtifactStagingDirectory)/k8s/helm + artifactName: helm diff --git a/build/azure-devops/readme.md b/build/azure-devops/readme.md new file mode 100644 index 000000000..b7216d4de --- /dev/null +++ b/build/azure-devops/readme.md @@ -0,0 +1,5 @@ +# Azure Devops build definitions + +This folder contains the Azure Devops build definitions in YAML format. Each folder contains one `azure-pipelines.yml` that contains the build definition for one microservice (usually a Docker image, but some microservices generates more than one Docker image). + +For more information about YAML builds read the [Azure DevOps documentation](https://docs.microsoft.com/en-us/azure/devops/pipelines/get-started-yaml?view=azure-devops). \ No newline at end of file diff --git a/build/azure-devops/web-shopping-agg/azure-pipelines.yml b/build/azure-devops/web-shopping-agg/azure-pipelines.yml new file mode 100644 index 000000000..70a4df950 --- /dev/null +++ b/build/azure-devops/web-shopping-agg/azure-pipelines.yml @@ -0,0 +1,44 @@ +pool: + vmImage: 'ubuntu-16.04' +variables: + registryEndpoint: eshop-registry +trigger: + branches: + include: + - master + - dev + paths: + include: + - src/ApiGateways/Web.Bff.Shopping/aggregator/* + - k8s/helm/webshoppingagg/* +steps: +- task: DockerCompose@0 + displayName: Compose build webshoppingagg + inputs: + dockerComposeCommand: 'build webshoppingagg' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: DockerCompose@0 + displayName: Compose push webshoppingagg + inputs: + dockerComposeCommand: 'push webshoppingagg' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: CopyFiles@2 + inputs: + sourceFolder: $(Build.SourcesDirectory)/k8s/helm + targetFolder: $(Build.ArtifactStagingDirectory)/k8s/helm +- task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: $(Build.ArtifactStagingDirectory)/k8s/helm + artifactName: helm diff --git a/build/azure-devops/webhooks-api/azure-pipelines.yml b/build/azure-devops/webhooks-api/azure-pipelines.yml new file mode 100644 index 000000000..b48948111 --- /dev/null +++ b/build/azure-devops/webhooks-api/azure-pipelines.yml @@ -0,0 +1,45 @@ +pool: + vmImage: 'ubuntu-16.04' +variables: + registryEndpoint: eshop-registry +trigger: + branches: + include: + - master + - dev + paths: + include: + - src/BuildingBlocks/* + - src/Services/Webhooks/* + - k8s/helm/webhooks-api/* +steps: +- task: DockerCompose@0 + displayName: Compose build webhooks + inputs: + dockerComposeCommand: 'build webhooks.api' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: DockerCompose@0 + displayName: Compose push webhooks + inputs: + dockerComposeCommand: 'push webhooks.api' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: CopyFiles@2 + inputs: + sourceFolder: $(Build.SourcesDirectory)/k8s/helm + targetFolder: $(Build.ArtifactStagingDirectory)/k8s/helm +- task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: $(Build.ArtifactStagingDirectory)/k8s/helm + artifactName: helm diff --git a/build/azure-devops/webhooks-client/azure-pipelines.yml b/build/azure-devops/webhooks-client/azure-pipelines.yml new file mode 100644 index 000000000..71e3bbe16 --- /dev/null +++ b/build/azure-devops/webhooks-client/azure-pipelines.yml @@ -0,0 +1,45 @@ +pool: + vmImage: 'ubuntu-16.04' +variables: + registryEndpoint: eshop-registry +trigger: + branches: + include: + - master + - dev + paths: + include: + - src/BuildingBlocks/* + - src/Web/WebhookClient/* + - k8s/helm/webhooks-web/* +steps: +- task: DockerCompose@0 + displayName: Compose build webhooks.client + inputs: + dockerComposeCommand: 'build webhooks.client' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: DockerCompose@0 + displayName: Compose push webhooks.client + inputs: + dockerComposeCommand: 'push webhooks.client' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: CopyFiles@2 + inputs: + sourceFolder: $(Build.SourcesDirectory)/k8s/helm + targetFolder: $(Build.ArtifactStagingDirectory)/k8s/helm +- task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: $(Build.ArtifactStagingDirectory)/k8s/helm + artifactName: helm diff --git a/build/azure-devops/webmvc/azure-pipelines.yml b/build/azure-devops/webmvc/azure-pipelines.yml new file mode 100644 index 000000000..66b8518fa --- /dev/null +++ b/build/azure-devops/webmvc/azure-pipelines.yml @@ -0,0 +1,45 @@ +pool: + vmImage: 'ubuntu-16.04' +variables: + registryEndpoint: eshop-registry +trigger: + branches: + include: + - master + - dev + paths: + include: + - src/BuildingBlocks/* + - src/Web/WebMVC/* + - k8s/helm/webmvc/* +steps: +- task: DockerCompose@0 + displayName: Compose build webmvc + inputs: + dockerComposeCommand: 'build webmvc' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: DockerCompose@0 + displayName: Compose push webmvc + inputs: + dockerComposeCommand: 'push webmvc' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: CopyFiles@2 + inputs: + sourceFolder: $(Build.SourcesDirectory)/k8s/helm + targetFolder: $(Build.ArtifactStagingDirectory)/k8s/helm +- task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: $(Build.ArtifactStagingDirectory)/k8s/helm + artifactName: helm diff --git a/build/azure-devops/webspa/azure-pipelines.yml b/build/azure-devops/webspa/azure-pipelines.yml new file mode 100644 index 000000000..eee8cd25d --- /dev/null +++ b/build/azure-devops/webspa/azure-pipelines.yml @@ -0,0 +1,45 @@ +pool: + vmImage: 'ubuntu-16.04' +variables: + registryEndpoint: eshop-registry +trigger: + branches: + include: + - master + - dev + paths: + include: + - src/BuildingBlocks/* + - src/Web/WebSPA/* + - k8s/helm/webspa/* +steps: +- task: DockerCompose@0 + displayName: Compose build webspa + inputs: + dockerComposeCommand: 'build webspa' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: DockerCompose@0 + displayName: Compose push webspa + inputs: + dockerComposeCommand: 'push webspa' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: CopyFiles@2 + inputs: + sourceFolder: $(Build.SourcesDirectory)/k8s/helm + targetFolder: $(Build.ArtifactStagingDirectory)/k8s/helm +- task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: $(Build.ArtifactStagingDirectory)/k8s/helm + artifactName: helm diff --git a/build/azure-devops/webstatus/azure-pipelines.yml b/build/azure-devops/webstatus/azure-pipelines.yml new file mode 100644 index 000000000..311c457e2 --- /dev/null +++ b/build/azure-devops/webstatus/azure-pipelines.yml @@ -0,0 +1,45 @@ +pool: + vmImage: 'ubuntu-16.04' +variables: + registryEndpoint: eshop-registry +trigger: + branches: + include: + - master + - dev + paths: + include: + - src/BuildingBlocks/* + - src/Web/WebStatus/* + - k8s/helm/webstatus/* +steps: +- task: DockerCompose@0 + displayName: Compose build webstatus + inputs: + dockerComposeCommand: 'build webstatus' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: DockerCompose@0 + displayName: Compose push webstatus + inputs: + dockerComposeCommand: 'push webstatus' + containerregistrytype: Container Registry + dockerRegistryEndpoint: $(registryEndpoint) + dockerComposeFile: docker-compose.yml + qualifyImageNames: true + projectName: "" + dockerComposeFileArgs: | + TAG=$(Build.SourceBranchName) +- task: CopyFiles@2 + inputs: + sourceFolder: $(Build.SourcesDirectory)/k8s/helm + targetFolder: $(Build.ArtifactStagingDirectory)/k8s/helm +- task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: $(Build.ArtifactStagingDirectory)/k8s/helm + artifactName: helm diff --git a/build/multiarch-manifests/create-manifests.ps1 b/build/multiarch-manifests/create-manifests.ps1 new file mode 100644 index 000000000..cc7f05194 --- /dev/null +++ b/build/multiarch-manifests/create-manifests.ps1 @@ -0,0 +1,26 @@ +Param( + [parameter(Mandatory=$true)][string]$registry +) + +if ([String]::IsNullOrEmpty($registry)) { + Write-Host "Registry must be set to docker registry to use" -ForegroundColor Red + exit 1 +} + +Write-Host "This script creates the local manifests, for pushing the multi-arch manifests" -ForegroundColor Yellow +Write-Host "Tags used are linux-master, win-master, linux-dev, win-dev, linux-latest, win-latest" -ForegroundColor Yellow +Write-Host "Multiarch images tags will be master, dev, latest" -ForegroundColor Yellow + + +$services = "identity.api", "basket.api", "catalog.api", "ordering.api", "ordering.backgroundtasks", "marketing.api", "payment.api", "locations.api", "webhooks.api", "ocelotapigw", "mobileshoppingagg", "webshoppingagg", "ordering.signalrhub", "webstatus", "webspa", "webmvc", "webhooks.client" + +foreach ($svc in $services) { + Write-Host "Creating manifest for $svc and tags :latest, :master, and :dev" + docker manifest create $registry/${svc}:master $registry/${svc}:linux-master $registry/${svc}:win-master + docker manifest create $registry/${svc}:dev $registry/${svc}:linux-dev $registry/${svc}:win-dev + docker manifest create $registry/${svc}:latest $registry/${svc}:linux-latest $registry/${svc}:win-latest + Write-Host "Pushing manifest for $svc and tags :latest, :master, and :dev" + docker manifest push $registry/${svc}:latest + docker manifest push $registry/${svc}:dev + docker manifest push $registry/${svc}:master +} \ No newline at end of file diff --git a/cli-windows/set-dockernat-networkategory-to-private.ps1 b/cli-windows/set-dockernat-networkategory-to-private.ps1 new file mode 100644 index 000000000..6614c8e8a --- /dev/null +++ b/cli-windows/set-dockernat-networkategory-to-private.ps1 @@ -0,0 +1,2 @@ + #Requires -RunAsAdministrator + Get-NetConnectionProfile | Where-Object { $_.InterfaceAlias -match "(DockerNAT)" } | ForEach-Object { Set-NetConnectionProfile -InterfaceIndex $_.InterfaceIndex -NetworkCategory Private } diff --git a/deploy/az/servicebus/readme.md b/deploy/az/servicebus/readme.md index 16da4c7b2..886b3ec60 100644 --- a/deploy/az/servicebus/readme.md +++ b/deploy/az/servicebus/readme.md @@ -8,7 +8,7 @@ The ARM template `sbusdeploy.json` and its parameter file (`sbusdeploy.parameter ## Editing sbusdeploy.parameters.json file -You can edit the `sbusdeploy.parameters.parameters.json` file to set your values, but is not needed. The only parameter than can +You can edit the `sbusdeploy.parameters.json` file to set your values, but is not needed. The only parameter than can be set is: 1. `namespaceprefix` is a string that is used to create the namespace. ARM script creates unique values by appending a unique string to this parameter value, so you can leave the default value. @@ -21,4 +21,4 @@ i. e. if you are in windows, to deploy servicebus in a new resourcegroup located ``` create-resources.cmd servicebus\sbusdeploy newResourceGroup -c westus -``` \ No newline at end of file +``` diff --git a/docker-compose.elk.yml b/docker-compose.elk.yml new file mode 100644 index 000000000..04e6c8a05 --- /dev/null +++ b/docker-compose.elk.yml @@ -0,0 +1,37 @@ +version: '3.4' + +services: + + elasticsearch: + build: + context: elk/elasticsearch/ + volumes: + - ./elk/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml:ro + ports: + - "9200:9200" + - "9300:9300" + environment: + ES_JAVA_OPTS: "-Xmx256m -Xms256m" + + logstash: + build: + context: elk/logstash/ + volumes: + - ./elk/logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml:ro + - ./elk/logstash/pipeline:/usr/share/logstash/pipeline:ro + ports: + - "8080:8080" + environment: + LS_JAVA_OPTS: "-Xmx256m -Xms256m" + depends_on: + - elasticsearch + + kibana: + build: + context: elk/kibana/ + volumes: + - ./elk/kibana/config/:/usr/share/kibana/config:ro + ports: + - "5601:5601" + depends_on: + - elasticsearch \ No newline at end of file diff --git a/docker-compose.override.yml b/docker-compose.override.yml index f8d608a27..e59a9a2e6 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -7,6 +7,12 @@ version: '3.4' # An external IP or DNS name has to be used (instead localhost and the 10.0.75.1 IP) when testing the Web apps and the Xamarin apps from remote machines/devices using the same WiFi, for instance. services: + seq: + environment: + - ACCEPT_EULA=Y + ports: + - "5340:80" + sql.data: environment: - SA_PASSWORD=Pass@word @@ -318,7 +324,7 @@ services: webstatus: environment: - - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_ENVIRONMENT=Production - ASPNETCORE_URLS=http://0.0.0.0:80 - HealthChecks-UI__HealthChecks__1__Name=WebMVC HTTP Check - HealthChecks-UI__HealthChecks__1__Uri=http://webmvc/hc diff --git a/docker-compose.yml b/docker-compose.yml index 194d7e06c..8c523e122 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,10 @@ version: '3.4' services: + + seq: + image: datalust/seq:latest + sql.data: image: microsoft/mssql-server-linux:2017-latest @@ -14,7 +18,7 @@ services: image: rabbitmq:3-management-alpine identity.api: - image: ${REGISTRY:-eshop}/identity.api:${TAG:-latest} + image: ${REGISTRY:-eshop}/identity.api:${PLATFORM:-linux}-${TAG:-latest} build: context: . dockerfile: src/Services/Identity/Identity.API/Dockerfile @@ -22,7 +26,7 @@ services: - sql.data basket.api: - image: ${REGISTRY:-eshop}/basket.api:${TAG:-latest} + image: ${REGISTRY:-eshop}/basket.api:${PLATFORM:-linux}-${TAG:-latest} build: context: . dockerfile: src/Services/Basket/Basket.API/Dockerfile @@ -32,7 +36,7 @@ services: - rabbitmq catalog.api: - image: ${REGISTRY:-eshop}/catalog.api:${TAG:-latest} + image: ${REGISTRY:-eshop}/catalog.api:${PLATFORM:-linux}-${TAG:-latest} build: context: . dockerfile: src/Services/Catalog/Catalog.API/Dockerfile @@ -41,7 +45,7 @@ services: - rabbitmq ordering.api: - image: ${REGISTRY:-eshop}/ordering.api:${TAG:-latest} + image: ${REGISTRY:-eshop}/ordering.api:${PLATFORM:-linux}-${TAG:-latest} build: context: . dockerfile: src/Services/Ordering/Ordering.API/Dockerfile @@ -50,7 +54,7 @@ services: - rabbitmq ordering.backgroundtasks: - image: ${REGISTRY:-eshop}/ordering.backgroundtasks:${TAG:-latest} + image: ${REGISTRY:-eshop}/ordering.backgroundtasks:${PLATFORM:-linux}-${TAG:-latest} build: context: . dockerfile: src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile @@ -59,7 +63,7 @@ services: - rabbitmq marketing.api: - image: ${REGISTRY:-eshop}/marketing.api:${TAG:-latest} + image: ${REGISTRY:-eshop}/marketing.api:${PLATFORM:-linux}-${TAG:-latest} build: context: . dockerfile: src/Services/Marketing/Marketing.API/Dockerfile @@ -70,7 +74,7 @@ services: - rabbitmq payment.api: - image: ${REGISTRY:-eshop}/payment.api:${TAG:-latest} + image: ${REGISTRY:-eshop}/payment.api:${PLATFORM:-linux}-${TAG:-latest} build: context: . dockerfile: src/Services/Payment/Payment.API/Dockerfile @@ -78,7 +82,7 @@ services: - rabbitmq locations.api: - image: ${REGISTRY:-eshop}/locations.api:${TAG:-latest} + image: ${REGISTRY:-eshop}/locations.api:${PLATFORM:-linux}-${TAG:-latest} build: context: . dockerfile: src/Services/Location/Locations.API/Dockerfile @@ -87,7 +91,7 @@ services: - rabbitmq webhooks.api: - image: ${REGISTRY:-eshop}/webhooks.api:${TAG:-latest} + image: ${REGISTRY:-eshop}/webhooks.api:${PLATFORM:-linux}-${TAG:-latest} build: context: . dockerfile: src/Services/Webhooks/Webhooks.API/Dockerfile @@ -95,7 +99,7 @@ services: - sql.data mobileshoppingapigw: - image: ${REGISTRY:-eshop}/ocelotapigw:${TAG:-latest} + image: ${REGISTRY:-eshop}/ocelotapigw:${PLATFORM:-linux}-${TAG:-latest} build: context: . dockerfile: src/ApiGateways/ApiGw-Base/Dockerfile @@ -110,7 +114,7 @@ services: - basket.api mobilemarketingapigw: - image: ${REGISTRY:-eshop}/ocelotapigw:${TAG:-latest} + image: ${REGISTRY:-eshop}/ocelotapigw:${PLATFORM:-linux}-${TAG:-latest} build: context: . dockerfile: src/ApiGateways/ApiGw-Base/Dockerfile @@ -125,7 +129,7 @@ services: - basket.api webshoppingapigw: - image: ${REGISTRY:-eshop}/ocelotapigw:${TAG:-latest} + image: ${REGISTRY:-eshop}/ocelotapigw:${PLATFORM:-linux}-${TAG:-latest} build: context: . dockerfile: src/ApiGateways/ApiGw-Base/Dockerfile @@ -140,7 +144,7 @@ services: - basket.api webmarketingapigw: - image: ${REGISTRY:-eshop}/ocelotapigw:${TAG:-latest} + image: ${REGISTRY:-eshop}/ocelotapigw:${PLATFORM:-linux}-${TAG:-latest} build: context: . dockerfile: src/ApiGateways/ApiGw-Base/Dockerfile @@ -155,7 +159,7 @@ services: - basket.api mobileshoppingagg: - image: ${REGISTRY:-eshop}/mobileshoppingagg:${TAG:-latest} + image: ${REGISTRY:-eshop}/mobileshoppingagg:${PLATFORM:-linux}-${TAG:-latest} build: context: . dockerfile: src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile @@ -170,7 +174,7 @@ services: - basket.api webshoppingagg: - image: ${REGISTRY:-eshop}/webshoppingagg:${TAG:-latest} + image: ${REGISTRY:-eshop}/webshoppingagg:${PLATFORM:-linux}-${TAG:-latest} build: context: . dockerfile: src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile @@ -185,7 +189,7 @@ services: - basket.api ordering.signalrhub: - image: ${REGISTRY:-eshop}/ordering.signalrhub:${TAG:-latest} + image: ${REGISTRY:-eshop}/ordering.signalrhub:${PLATFORM:-linux}-${TAG:-latest} build: context: . dockerfile: src/Services/Ordering/Ordering.SignalrHub/Dockerfile @@ -200,13 +204,13 @@ services: - basket.api webstatus: - image: ${REGISTRY:-eshop}/webstatus:${TAG:-latest} + image: ${REGISTRY:-eshop}/webstatus:${PLATFORM:-linux}-${TAG:-latest} build: context: . dockerfile: src/Web/WebStatus/Dockerfile webspa: - image: ${REGISTRY:-eshop}/webspa:${TAG:-latest} + image: ${REGISTRY:-eshop}/webspa:${PLATFORM:-linux}-${TAG:-latest} build: context: . dockerfile: src/Web/WebSPA/Dockerfile @@ -216,7 +220,7 @@ services: # - webmarketingapigw webmvc: - image: ${REGISTRY:-eshop}/webmvc:${TAG:-latest} + image: ${REGISTRY:-eshop}/webmvc:${PLATFORM:-linux}-${TAG:-latest} build: context: . dockerfile: src/Web/WebMVC/Dockerfile @@ -226,7 +230,7 @@ services: - webmarketingapigw webhooks.client: - image: ${REGISTRY:-eshop}/webhooks.client:${TAG:-latest} + image: ${REGISTRY:-eshop}/webhooks.client:${PLATFORM:-linux}-${TAG:-latest} build: context: . dockerfile: src/Web/WebhookClient/Dockerfile diff --git a/docker-scripts/linux/install-node.sh b/docker-scripts/linux/install-node.sh deleted file mode 100644 index 73407aae9..000000000 --- a/docker-scripts/linux/install-node.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -export NODE_DOWNLOAD_SHA 0e20787e2eda4cc31336d8327556ebc7417e8ee0a6ba0de96a09b0ec2b841f60 -curl -SL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz" --output nodejs.tar.gz \ - && echo "$NODE_DOWNLOAD_SHA nodejs.tar.gz" | sha256sum -c - \ - && tar -xzf "nodejs.tar.gz" -C /usr/local --strip-components=1 \ - && rm nodejs.tar.gz \ - && ln -s /usr/local/bin/node /usr/local/bin/nodejs diff --git a/docker-scripts/win/install-node.cmd b/docker-scripts/win/install-node.cmd deleted file mode 100644 index eaa4e356c..000000000 --- a/docker-scripts/win/install-node.cmd +++ /dev/null @@ -1,4 +0,0 @@ -set NODE_VERSION=8.11.1 -curl -SL "https://nodejs.org/dist/v%NODE_VERSION%/node-v%NODE_VERSION%-win-x64.zip" --output nodejs.zip -tar -xf nodejs.zip -C c:\ -setx PATH "%PATH%;c:\node-v%NODE_VERSION%-win-x64" \ No newline at end of file diff --git a/eShopOnContainers-ServicesAndWebApps.sln b/eShopOnContainers-ServicesAndWebApps.sln index 447b73073..882bca09f 100644 --- a/eShopOnContainers-ServicesAndWebApps.sln +++ b/eShopOnContainers-ServicesAndWebApps.sln @@ -142,9 +142,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Payment.API", "src\Services EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webhooks", "Webhooks", "{E0AA11C4-2873-461D-8F82-53392530FB7A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Webhooks.API", "src\Services\Webhooks\Webhooks.API\Webhooks.API.csproj", "{84E2016E-0435-44C6-8020-3D288AA38B2C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Webhooks.API", "src\Services\Webhooks\Webhooks.API\Webhooks.API.csproj", "{84E2016E-0435-44C6-8020-3D288AA38B2C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebhookClient", "src\Web\WebhookClient\WebhookClient.csproj", "{766D7E92-6AF0-476C-ADD5-282BF4D8C576}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebhookClient", "src\Web\WebhookClient\WebhookClient.csproj", "{766D7E92-6AF0-476C-ADD5-282BF4D8C576}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Devspace.Support", "Devspace.Support", "{68F5041D-51F2-4630-94B6-B49789F5E51A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Devspaces.Support", "src\BuildingBlocks\Devspaces.Support\Devspaces.Support.csproj", "{56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -1744,6 +1748,54 @@ Global {766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|x64.Build.0 = Release|Any CPU {766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|x86.ActiveCfg = Release|Any CPU {766D7E92-6AF0-476C-ADD5-282BF4D8C576}.Release|x86.Build.0 = Release|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.AppStore|ARM.Build.0 = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.AppStore|iPhone.Build.0 = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.AppStore|x64.ActiveCfg = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.AppStore|x64.Build.0 = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.AppStore|x86.ActiveCfg = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.AppStore|x86.Build.0 = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Debug|ARM.ActiveCfg = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Debug|ARM.Build.0 = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Debug|iPhone.Build.0 = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Debug|x64.ActiveCfg = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Debug|x64.Build.0 = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Debug|x86.ActiveCfg = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Debug|x86.Build.0 = Debug|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Release|Any CPU.ActiveCfg = Release|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Release|Any CPU.Build.0 = Release|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Release|ARM.ActiveCfg = Release|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Release|ARM.Build.0 = Release|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Release|iPhone.ActiveCfg = Release|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Release|iPhone.Build.0 = Release|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Release|x64.ActiveCfg = Release|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Release|x64.Build.0 = Release|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Release|x86.ActiveCfg = Release|Any CPU + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1808,6 +1860,8 @@ Global {E0AA11C4-2873-461D-8F82-53392530FB7A} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8} {84E2016E-0435-44C6-8020-3D288AA38B2C} = {E0AA11C4-2873-461D-8F82-53392530FB7A} {766D7E92-6AF0-476C-ADD5-282BF4D8C576} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04} + {68F5041D-51F2-4630-94B6-B49789F5E51A} = {DB0EFB20-B024-4E5E-A75C-52143C131D25} + {56C2EF0B-6BF2-41D9-BE07-6E6D08D06B35} = {68F5041D-51F2-4630-94B6-B49789F5E51A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {25728519-5F0F-4973-8A64-0A81EB4EA8D9} diff --git a/eShopOnContainers.sln b/eShopOnContainers.sln index 999203557..8e08a05fa 100644 --- a/eShopOnContainers.sln +++ b/eShopOnContainers.sln @@ -55,20 +55,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBusRabbitMQ", "src\Bui EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationEventLogEF", "src\BuildingBlocks\EventBus\IntegrationEventLogEF\IntegrationEventLogEF.csproj", "{9EE28E45-1533-472B-8267-56C48855BA0E}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HealthChecks", "HealthChecks", "{A81ECBC2-6B00-4DCD-8388-469174033379}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj", "{942ED6E8-0050-495F-A0EA-01E97F63760C}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebStatus", "src\Web\WebStatus\WebStatus.csproj", "{C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Payment", "Payment", "{022E145D-1593-47EE-9608-8E323D3C63F5}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Payment.API", "src\Services\Payment\Payment.API\Payment.API.csproj", "{1A01AF82-6FCB-464C-B39C-F127AEBD315D}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HealthChecks", "src\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj", "{22A0F9C1-2D4A-4107-95B7-8459E6688BC5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks.SqlServer", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.SqlServer\Microsoft.Extensions.HealthChecks.SqlServer.csproj", "{4BD76717-3102-4969-8C2C-BAAA3F0263B6}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Location", "Location", "{41139F64-4046-4F16-96B7-D941D96FA9C6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Locations.API", "src\Services\Location\Locations.API\Locations.API.csproj", "{E7581357-FC34-474C-B8F5-307EE3CE05EF}" @@ -79,8 +71,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Marketing.API", "src\Servic EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBusServiceBus", "src\BuildingBlocks\EventBus\EventBusServiceBus\EventBusServiceBus.csproj", "{69AF10D3-AA76-4FF7-B187-EC7E8CC5F5B8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.HealthChecks.AzureStorage", "src\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.AzureStorage\Microsoft.Extensions.HealthChecks.AzureStorage.csproj", "{768C887F-C229-4B94-ACD8-0C7F65686524}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebHost", "WebHost", "{1815B651-941C-466B-AE33-D1D7EEB8F77F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebHost.Customization", "src\BuildingBlocks\WebHostCustomization\WebHost.Customization\WebHost.Customization.csproj", "{15F4B3AA-89B6-4A0D-9051-414305974781}" @@ -141,6 +131,36 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{DA1786E4 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{30308DE0-8128-4613-BCAD-B0BEFFB20E38}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ApiGateways", "ApiGateways", "{79C64C7A-ED74-4F01-921F-92F4F9FC1E1D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ApiGw-Base", "ApiGw-Base", "{56AD1FCA-6E16-4798-BF29-941C5B3277D2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mobile.Bff.Marketing", "Mobile.Bff.Marketing", "{34ED3311-2B30-4C8B-823B-312B50FFC32A}" + ProjectSection(SolutionItems) = preProject + src\ApiGateways\Mobile.Bff.Marketing\apigw\configuration.json = src\ApiGateways\Mobile.Bff.Marketing\apigw\configuration.json + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mobile.Bff.Shopping", "Mobile.Bff.Shopping", "{A32A5254-BA36-46FC-8C75-F7B8FFE8FCD0}" + ProjectSection(SolutionItems) = preProject + src\ApiGateways\Mobile.Bff.Shopping\apigw\configuration.json = src\ApiGateways\Mobile.Bff.Shopping\apigw\configuration.json + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web.Bff.Marketing", "Web.Bff.Marketing", "{696D2B7E-6B75-401D-964A-BFE6F4D7AF73}" + ProjectSection(SolutionItems) = preProject + src\ApiGateways\Web.Bff.Marketing\apigw\configuration.json = src\ApiGateways\Web.Bff.Marketing\apigw\configuration.json + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web.Bff.Shopping", "Web.Bff.Shopping", "{424BC53E-17EA-4E12-BC07-64BAA927ABCB}" + ProjectSection(SolutionItems) = preProject + src\ApiGateways\Web.Bff.Shopping\apigw\configuration.json = src\ApiGateways\Web.Bff.Shopping\apigw\configuration.json + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OcelotApiGw", "src\ApiGateways\ApiGw-Base\OcelotApiGw.csproj", "{0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mobile.Shopping.HttpAggregator", "src\ApiGateways\Mobile.Bff.Shopping\aggregator\Mobile.Shopping.HttpAggregator.csproj", "{98E0B3BA-6601-4C59-A9AA-24A00A17D835}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web.Shopping.HttpAggregator", "src\ApiGateways\Web.Bff.Shopping\aggregator\Web.Shopping.HttpAggregator.csproj", "{E39BD762-BC86-459D-B818-B6BF2D9F1352}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU @@ -635,54 +655,6 @@ Global {9EE28E45-1533-472B-8267-56C48855BA0E}.Release|x64.Build.0 = Release|Any CPU {9EE28E45-1533-472B-8267-56C48855BA0E}.Release|x86.ActiveCfg = Release|Any CPU {9EE28E45-1533-472B-8267-56C48855BA0E}.Release|x86.Build.0 = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|x64.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Ad-Hoc|x86.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|ARM.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|ARM.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|iPhone.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|iPhone.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|x64.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|x64.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|x86.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.AppStore|x86.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|ARM.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|ARM.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|iPhone.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|x64.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|x64.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|x86.ActiveCfg = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Debug|x86.Build.0 = Debug|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|Any CPU.Build.0 = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|ARM.ActiveCfg = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|ARM.Build.0 = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|iPhone.ActiveCfg = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|iPhone.Build.0 = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|x64.ActiveCfg = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|x64.Build.0 = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|x86.ActiveCfg = Release|Any CPU - {942ED6E8-0050-495F-A0EA-01E97F63760C}.Release|x86.Build.0 = Release|Any CPU {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU @@ -779,102 +751,6 @@ Global {1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|x64.Build.0 = Release|Any CPU {1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|x86.ActiveCfg = Release|Any CPU {1A01AF82-6FCB-464C-B39C-F127AEBD315D}.Release|x86.Build.0 = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|x64.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Ad-Hoc|x86.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|ARM.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|ARM.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|iPhone.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|iPhone.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|x64.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|x64.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|x86.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.AppStore|x86.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|ARM.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|ARM.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|iPhone.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|x64.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|x64.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|x86.ActiveCfg = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Debug|x86.Build.0 = Debug|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|Any CPU.Build.0 = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|ARM.ActiveCfg = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|ARM.Build.0 = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|iPhone.ActiveCfg = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|iPhone.Build.0 = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|x64.ActiveCfg = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|x64.Build.0 = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|x86.ActiveCfg = Release|Any CPU - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5}.Release|x86.Build.0 = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|x64.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Ad-Hoc|x86.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|ARM.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|ARM.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|iPhone.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|iPhone.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|x64.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|x64.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|x86.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.AppStore|x86.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|ARM.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|ARM.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|iPhone.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|x64.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|x64.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|x86.ActiveCfg = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Debug|x86.Build.0 = Debug|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|Any CPU.Build.0 = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|ARM.ActiveCfg = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|ARM.Build.0 = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|iPhone.ActiveCfg = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|iPhone.Build.0 = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x64.ActiveCfg = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x64.Build.0 = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x86.ActiveCfg = Release|Any CPU - {4BD76717-3102-4969-8C2C-BAAA3F0263B6}.Release|x86.Build.0 = Release|Any CPU {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {E7581357-FC34-474C-B8F5-307EE3CE05EF}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU @@ -1019,54 +895,6 @@ Global {69AF10D3-AA76-4FF7-B187-EC7E8CC5F5B8}.Release|x64.Build.0 = Release|Any CPU {69AF10D3-AA76-4FF7-B187-EC7E8CC5F5B8}.Release|x86.ActiveCfg = Release|Any CPU {69AF10D3-AA76-4FF7-B187-EC7E8CC5F5B8}.Release|x86.Build.0 = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|x64.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Ad-Hoc|x86.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|ARM.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|ARM.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|iPhone.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|iPhone.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|x64.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|x64.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|x86.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.AppStore|x86.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|Any CPU.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|ARM.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|ARM.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|iPhone.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|x64.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|x64.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|x86.ActiveCfg = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Debug|x86.Build.0 = Debug|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|Any CPU.ActiveCfg = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|Any CPU.Build.0 = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|ARM.ActiveCfg = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|ARM.Build.0 = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|iPhone.ActiveCfg = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|iPhone.Build.0 = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|x64.ActiveCfg = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|x64.Build.0 = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|x86.ActiveCfg = Release|Any CPU - {768C887F-C229-4B94-ACD8-0C7F65686524}.Release|x86.Build.0 = Release|Any CPU {15F4B3AA-89B6-4A0D-9051-414305974781}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {15F4B3AA-89B6-4A0D-9051-414305974781}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {15F4B3AA-89B6-4A0D-9051-414305974781}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU @@ -2067,6 +1895,150 @@ Global {3572B4E2-4399-4797-B5C2-3720D870E0C3}.Release|x64.Build.0 = Release|Any CPU {3572B4E2-4399-4797-B5C2-3720D870E0C3}.Release|x86.ActiveCfg = Release|Any CPU {3572B4E2-4399-4797-B5C2-3720D870E0C3}.Release|x86.Build.0 = Release|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.AppStore|ARM.Build.0 = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.AppStore|iPhone.Build.0 = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.AppStore|x64.ActiveCfg = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.AppStore|x64.Build.0 = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.AppStore|x86.ActiveCfg = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.AppStore|x86.Build.0 = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Debug|ARM.ActiveCfg = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Debug|ARM.Build.0 = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Debug|iPhone.Build.0 = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Debug|x64.ActiveCfg = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Debug|x64.Build.0 = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Debug|x86.ActiveCfg = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Debug|x86.Build.0 = Debug|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Release|Any CPU.Build.0 = Release|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Release|ARM.ActiveCfg = Release|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Release|ARM.Build.0 = Release|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Release|iPhone.ActiveCfg = Release|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Release|iPhone.Build.0 = Release|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Release|x64.ActiveCfg = Release|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Release|x64.Build.0 = Release|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Release|x86.ActiveCfg = Release|Any CPU + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC}.Release|x86.Build.0 = Release|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.AppStore|ARM.Build.0 = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.AppStore|iPhone.Build.0 = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.AppStore|x64.ActiveCfg = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.AppStore|x64.Build.0 = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.AppStore|x86.ActiveCfg = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.AppStore|x86.Build.0 = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Debug|Any CPU.Build.0 = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Debug|ARM.ActiveCfg = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Debug|ARM.Build.0 = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Debug|iPhone.Build.0 = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Debug|x64.ActiveCfg = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Debug|x64.Build.0 = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Debug|x86.ActiveCfg = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Debug|x86.Build.0 = Debug|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Release|Any CPU.ActiveCfg = Release|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Release|Any CPU.Build.0 = Release|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Release|ARM.ActiveCfg = Release|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Release|ARM.Build.0 = Release|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Release|iPhone.ActiveCfg = Release|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Release|iPhone.Build.0 = Release|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Release|x64.ActiveCfg = Release|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Release|x64.Build.0 = Release|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Release|x86.ActiveCfg = Release|Any CPU + {98E0B3BA-6601-4C59-A9AA-24A00A17D835}.Release|x86.Build.0 = Release|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.AppStore|ARM.Build.0 = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.AppStore|iPhone.Build.0 = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.AppStore|x64.ActiveCfg = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.AppStore|x64.Build.0 = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.AppStore|x86.ActiveCfg = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.AppStore|x86.Build.0 = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Debug|ARM.ActiveCfg = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Debug|ARM.Build.0 = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Debug|iPhone.Build.0 = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Debug|x64.ActiveCfg = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Debug|x64.Build.0 = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Debug|x86.ActiveCfg = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Debug|x86.Build.0 = Debug|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Release|Any CPU.Build.0 = Release|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Release|ARM.ActiveCfg = Release|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Release|ARM.Build.0 = Release|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Release|iPhone.ActiveCfg = Release|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Release|iPhone.Build.0 = Release|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Release|x64.ActiveCfg = Release|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Release|x64.Build.0 = Release|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Release|x86.ActiveCfg = Release|Any CPU + {E39BD762-BC86-459D-B818-B6BF2D9F1352}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2092,19 +2064,14 @@ Global {0044B293-1DCC-4224-B948-00CF6DC7F510} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} {8088F3FC-6787-45FA-A924-816EC81CBFAC} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} {9EE28E45-1533-472B-8267-56C48855BA0E} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} - {A81ECBC2-6B00-4DCD-8388-469174033379} = {DB0EFB20-B024-4E5E-A75C-52143C131D25} - {942ED6E8-0050-495F-A0EA-01E97F63760C} = {A81ECBC2-6B00-4DCD-8388-469174033379} {C0A7918D-B4F2-4E7F-8DE2-1E5279EF079F} = {E279BF0F-7F66-4F3A-A3AB-2CDA66C1CD04} {022E145D-1593-47EE-9608-8E323D3C63F5} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8} {1A01AF82-6FCB-464C-B39C-F127AEBD315D} = {022E145D-1593-47EE-9608-8E323D3C63F5} - {22A0F9C1-2D4A-4107-95B7-8459E6688BC5} = {A81ECBC2-6B00-4DCD-8388-469174033379} - {4BD76717-3102-4969-8C2C-BAAA3F0263B6} = {A81ECBC2-6B00-4DCD-8388-469174033379} {41139F64-4046-4F16-96B7-D941D96FA9C6} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8} {E7581357-FC34-474C-B8F5-307EE3CE05EF} = {41139F64-4046-4F16-96B7-D941D96FA9C6} {A5260DE0-1FDD-467E-9CC1-A028AB081CEE} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8} {DF395F85-B010-465D-857A-7EBCC512C0C2} = {A5260DE0-1FDD-467E-9CC1-A028AB081CEE} {69AF10D3-AA76-4FF7-B187-EC7E8CC5F5B8} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F} - {768C887F-C229-4B94-ACD8-0C7F65686524} = {A81ECBC2-6B00-4DCD-8388-469174033379} {1815B651-941C-466B-AE33-D1D7EEB8F77F} = {DB0EFB20-B024-4E5E-A75C-52143C131D25} {15F4B3AA-89B6-4A0D-9051-414305974781} = {1815B651-941C-466B-AE33-D1D7EEB8F77F} {EF3EDC78-E864-43FF-8E80-CF33DD9508A3} = {932D8224-11F6-4D07-B109-DA28AD288A63} @@ -2135,6 +2102,15 @@ Global {2B26A7AA-6D61-42FA-8AB7-C0F05AAE7F1C} = {41139F64-4046-4F16-96B7-D941D96FA9C6} {DA1786E4-30AB-434E-A827-92896390B79D} = {A5260DE0-1FDD-467E-9CC1-A028AB081CEE} {30308DE0-8128-4613-BCAD-B0BEFFB20E38} = {0BD0DB92-2D98-44D9-9AC0-C59186D59B0B} + {79C64C7A-ED74-4F01-921F-92F4F9FC1E1D} = {932D8224-11F6-4D07-B109-DA28AD288A63} + {56AD1FCA-6E16-4798-BF29-941C5B3277D2} = {79C64C7A-ED74-4F01-921F-92F4F9FC1E1D} + {34ED3311-2B30-4C8B-823B-312B50FFC32A} = {79C64C7A-ED74-4F01-921F-92F4F9FC1E1D} + {A32A5254-BA36-46FC-8C75-F7B8FFE8FCD0} = {79C64C7A-ED74-4F01-921F-92F4F9FC1E1D} + {696D2B7E-6B75-401D-964A-BFE6F4D7AF73} = {79C64C7A-ED74-4F01-921F-92F4F9FC1E1D} + {424BC53E-17EA-4E12-BC07-64BAA927ABCB} = {79C64C7A-ED74-4F01-921F-92F4F9FC1E1D} + {0A328C44-4C4E-49BE-9FB4-9D851CEC28AC} = {56AD1FCA-6E16-4798-BF29-941C5B3277D2} + {98E0B3BA-6601-4C59-A9AA-24A00A17D835} = {A32A5254-BA36-46FC-8C75-F7B8FFE8FCD0} + {E39BD762-BC86-459D-B818-B6BF2D9F1352} = {424BC53E-17EA-4E12-BC07-64BAA927ABCB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {25728519-5F0F-4973-8A64-0A81EB4EA8D9} diff --git a/elk/Readme.md b/elk/Readme.md new file mode 100644 index 000000000..dddeab3fd --- /dev/null +++ b/elk/Readme.md @@ -0,0 +1,88 @@ +This article contains a brief introduction to centralized structured logging with [Serilog](https://serilog.net/) and event viewing with [ELK](https://www.elastic.co/elk-stack) in eShopOnContainers. ELK is an acronym of ElasticSearch, LogStash and Kibana. This is one of the most used tools in the industry standards. + +![](img/elk/kibana-working.png) + +## Wiring eshopOnContainers with ELK in Localhost + +eshopOnContainers is ready for work with ELK, you only need to setup the configuration parameter **LogstashUrl**, in **Serilog** Section, for achieve this, you can do it modifing this parameter in every appsettings.json of every service, or via Environment Variable **Serilog:LogstashUrl**. + +There is another option, a zero-configuration environment for testing the integration launching via ```docker-compose``` command, on the root directory of eshopOnContainers: + +```sh +docker-compose -f docker-compose.yml -f docker-compose.override.yml -f docker-compose.elk.yml build + +docker-compose -f docker-compose.yml -f docker-compose.override.yml -f docker-compose.elk.yml up +``` + +### Configuring Logstash index on Kibana + +Once time you have started and configured your application, you only need to configure the logstash index on kibana. +You can address to Kibana, with docker-compose setup is at [http://localhost:5601](http://localhost:5601) + +If you have accessed to kibana too early, you can see this error. It's normal, depending of your machine the kibana stack needs a bit of time to startup. +![](img/elk/kibana_startup.png) + +You can wait a bit and refresh the page, the first time you enter, you need to configure and index pattern, in the ```docker-compose``` configuration, the index pattern name is **eshops-\***. +![](img/elk/kibana_eshops_index.png) + +With the index pattern configured, you can enter in the discover section and start viewing how the tool is recollecting the logging information. + +![](img/elk/kibana_result.png) + +## Configuring ELK on Azure VM +Another option is to use a preconfigured virtual machine with Logstash, ElasticSearch and Kibana and point the configuration parameter **LogstashUrl**. For doing this you can address to Microsoft Azure, and start searching a Certified ELK Virtual Machine + +![](img/elk/create-vm-elk-azure.png) + +This options it have a certified preconfigured options (Network, VirtualMachine type, OS, RAM, Disks) for having a good starting point of ELK with good performance. + +![](img/elk/create-vm-elk-azure-summary.png) + +When you have configured the main aspects of your virtual machine, you will have a "review & create" last step like this: +![](img/elk/create-vm-elk-azure-last-step.png) + +### Configuring the bitnami environment + + This virtual machine has a lot of configuration pipeing done. If you want to change something of the default configuration you can address this documentation: + [https://docs.bitnami.com/virtual-machine/apps/elk/get-started/](https://docs.bitnami.com/virtual-machine/apps/elk/get-started/) + + The only thing you have to change is the logstash configuration inside the machine. This configuration is at the file ```/opt/bitnami/logstash/conf/logstash.conf``` + You must edit the file and overwrite with this configuration: + ```conf + input { + http { + #default host 0.0.0.0:8080 + codec => json + } +} + +## Add your filters / logstash plugins configuration here +filter { + split { + field => "events" + target => "e" + remove_field => "events" + } +} + +output { + elasticsearch { + hosts => "elasticsearch:9200" + index=>"eshops-%{+xxxx.ww}" + } +} +``` + +For doing this you can connect via ssh to the vm and edit the file using the vi editor for example. +When the file will be edited, check there are Inbound Port Rules created for the logstash service. You can do it going to Networking Menu on your ELK Virtual Machine Resource in Azure. + +![](img/elk/azure-nsg-inboundportsConfig.png) + +The only thing that remains is to connect to your vm vía browser. And check the bitnami splash page is showing. + +![](img/elk/bitnami_splash.png) + +You can get the password for accessing going to your virtual machine in azure and check the boot diagnostics, theres a message that shows to you which is your password. + +When you have the user and password you can access to the kibana tool, and create the ```eshops-*``` index pattern that is well documented at the beggining of this documentation and then start to discover. +![](img/elk/) \ No newline at end of file diff --git a/elk/elasticsearch/Dockerfile b/elk/elasticsearch/Dockerfile new file mode 100644 index 000000000..0ad46d887 --- /dev/null +++ b/elk/elasticsearch/Dockerfile @@ -0,0 +1,5 @@ +# https://github.com/elastic/elasticsearch-docker +FROM docker.elastic.co/elasticsearch/elasticsearch-oss:6.0.0 + +# Add your elasticsearch plugins setup here +# Example: RUN elasticsearch-plugin install analysis-icu diff --git a/elk/elasticsearch/config/elasticsearch.yml b/elk/elasticsearch/config/elasticsearch.yml new file mode 100644 index 000000000..e97577084 --- /dev/null +++ b/elk/elasticsearch/config/elasticsearch.yml @@ -0,0 +1,16 @@ +--- +## Default Elasticsearch configuration from elasticsearch-docker. +## from https://github.com/elastic/elasticsearch-docker/blob/master/build/elasticsearch/elasticsearch.yml +# +cluster.name: "docker-cluster" +network.host: 0.0.0.0 + +# minimum_master_nodes need to be explicitly set when bound on a public IP +# set to 1 to allow single node clusters +# Details: https://github.com/elastic/elasticsearch/pull/17288 +discovery.zen.minimum_master_nodes: 1 + +## Use single node discovery in order to disable production mode and avoid bootstrap checks +## see https://www.elastic.co/guide/en/elasticsearch/reference/current/bootstrap-checks.html +# +discovery.type: single-node diff --git a/elk/kibana/Dockerfile b/elk/kibana/Dockerfile new file mode 100644 index 000000000..1785734aa --- /dev/null +++ b/elk/kibana/Dockerfile @@ -0,0 +1,5 @@ +# https://github.com/elastic/kibana-docker +FROM docker.elastic.co/kibana/kibana-oss:6.0.0 + +# Add your kibana plugins setup here +# Example: RUN kibana-plugin install diff --git a/elk/kibana/config/kibana.yml b/elk/kibana/config/kibana.yml new file mode 100644 index 000000000..4b34a1606 --- /dev/null +++ b/elk/kibana/config/kibana.yml @@ -0,0 +1,7 @@ +--- +## Default Kibana configuration from kibana-docker. +## from https://github.com/elastic/kibana-docker/blob/master/build/kibana/config/kibana.yml +# +server.name: kibana +server.host: "0" +elasticsearch.url: http://elasticsearch:9200 diff --git a/elk/logstash/Dockerfile b/elk/logstash/Dockerfile new file mode 100644 index 000000000..d8bb475a5 --- /dev/null +++ b/elk/logstash/Dockerfile @@ -0,0 +1,6 @@ +# https://github.com/elastic/logstash-docker +FROM docker.elastic.co/logstash/logstash-oss:6.0.0 + +# Add your logstash plugins setup here +# Example: RUN logstash-plugin install logstash-filter-json +RUN logstash-plugin install logstash-input-http \ No newline at end of file diff --git a/elk/logstash/config/logstash.yml b/elk/logstash/config/logstash.yml new file mode 100644 index 000000000..d4f28f778 --- /dev/null +++ b/elk/logstash/config/logstash.yml @@ -0,0 +1,6 @@ +--- +## Default Logstash configuration from logstash-docker. +## from https://github.com/elastic/logstash-docker/blob/master/build/logstash/config/logstash-oss.yml +# +http.host: "0.0.0.0" +path.config: /usr/share/logstash/pipeline \ No newline at end of file diff --git a/elk/logstash/pipeline/logstash.conf b/elk/logstash/pipeline/logstash.conf new file mode 100644 index 000000000..b38a681d7 --- /dev/null +++ b/elk/logstash/pipeline/logstash.conf @@ -0,0 +1,22 @@ +input { + http { + #default host 0.0.0.0:8080 + codec => json + } +} + +## Add your filters / logstash plugins configuration here +filter { + split { + field => "events" + target => "e" + remove_field => "events" + } +} + +output { + elasticsearch { + hosts => "elasticsearch:9200" + index=>"eshops-%{+xxxx.ww}" + } +} diff --git a/hosts b/hosts deleted file mode 100644 index 19e812a15..000000000 --- a/hosts +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) 1993-2009 Microsoft Corp. -# -# This is a sample HOSTS file used by Microsoft TCP/IP for Windows. -# -# This file contains the mappings of IP addresses to host names. Each -# entry should be kept on an individual line. The IP address should -# be placed in the first column followed by the corresponding host name. -# The IP address and the host name should be separated by at least one -# space. -# -# Additionally, comments (such as these) may be inserted on individual -# lines or following the machine name denoted by a '#' symbol. -# -# For example: -# -# 102.54.94.97 rhino.acme.com # source server -# 38.25.63.10 x.acme.com # x client host - -# localhost name resolution is handled within DNS itself. -# 127.0.0.1 localhost -# ::1 localhost - - 10.0.75.1 identity.service \ No newline at end of file diff --git a/img/elk/azure-nsg-inboundportsConfig.png b/img/elk/azure-nsg-inboundportsConfig.png new file mode 100644 index 000000000..5a0371e66 Binary files /dev/null and b/img/elk/azure-nsg-inboundportsConfig.png differ diff --git a/img/elk/bitnami_splash.png b/img/elk/bitnami_splash.png new file mode 100644 index 000000000..a34643864 Binary files /dev/null and b/img/elk/bitnami_splash.png differ diff --git a/img/elk/create-vm-elk-azure-last-step.png b/img/elk/create-vm-elk-azure-last-step.png new file mode 100644 index 000000000..348f8843c Binary files /dev/null and b/img/elk/create-vm-elk-azure-last-step.png differ diff --git a/img/elk/create-vm-elk-azure-summary.png b/img/elk/create-vm-elk-azure-summary.png new file mode 100644 index 000000000..b56ae5af3 Binary files /dev/null and b/img/elk/create-vm-elk-azure-summary.png differ diff --git a/img/elk/create-vm-elk-azure.png b/img/elk/create-vm-elk-azure.png new file mode 100644 index 000000000..b7d98bb95 Binary files /dev/null and b/img/elk/create-vm-elk-azure.png differ diff --git a/img/elk/discover-kibana.png b/img/elk/discover-kibana.png new file mode 100644 index 000000000..3977042c7 Binary files /dev/null and b/img/elk/discover-kibana.png differ diff --git a/img/elk/kibana_eshops_index.png b/img/elk/kibana_eshops_index.png new file mode 100644 index 000000000..5a4be8ec3 Binary files /dev/null and b/img/elk/kibana_eshops_index.png differ diff --git a/img/elk/kibana_result.png b/img/elk/kibana_result.png new file mode 100644 index 000000000..55b7fd3ff Binary files /dev/null and b/img/elk/kibana_result.png differ diff --git a/img/elk/kibana_startup.png b/img/elk/kibana_startup.png new file mode 100644 index 000000000..e283d7de8 Binary files /dev/null and b/img/elk/kibana_startup.png differ diff --git a/img/elk/kibana_working.png b/img/elk/kibana_working.png new file mode 100644 index 000000000..e310e2710 Binary files /dev/null and b/img/elk/kibana_working.png differ diff --git a/k8s/README.k8s.md b/k8s/README.k8s.md index 62841aba1..84d9a72f0 100644 --- a/k8s/README.k8s.md +++ b/k8s/README.k8s.md @@ -20,7 +20,7 @@ For AKS: For ACS: >``` ->./gen-k8s-env -resourceGroupName k8sGroup -location westeurope -registryName k8sregistry -createAcr true -orchestratorName k8s-cluster -dnsName k8s-dns +>./gen-k8s-env-aks -resourceGroupName k8sGroup -location westeurope -registryName k8sregistry -serviceName k8s-cluster -createAcr true -nodeCount 3 -nodeVMSize Standard_D2_v2 >``` * A Docker development environment with `docker` and `docker-compose`. diff --git a/k8s/helm/apigwmm/templates/_names.tpl b/k8s/helm/apigwmm/templates/_names.tpl index a6eeb9965..d44859fea 100644 --- a/k8s/helm/apigwmm/templates/_names.tpl +++ b/k8s/helm/apigwmm/templates/_names.tpl @@ -33,14 +33,13 @@ {{- end -}} + {{- define "pathBase" -}} -{{- $name := first .}} -{{- $ctx := last .}} -{{- if $ctx.Values.inf.k8s.suffix -}} -{{- $suffix := include "suffix-name" $ctx -}} -{{- printf "/%s-%s" $name $suffix -}} +{{- if .Values.inf.k8s.suffix -}} +{{- $suffix := include "suffix-name" . -}} +{{- printf "%s-%s" .Values.pathBase $suffix -}} {{- else -}} -{{- printf "/%s" $name -}} +{{- .Values.pathBase -}} {{- end -}} {{- end -}} diff --git a/k8s/helm/apigwmm/templates/deployment.yaml b/k8s/helm/apigwmm/templates/deployment.yaml index 6dc58bf50..c9abb0b62 100644 --- a/k8s/helm/apigwmm/templates/deployment.yaml +++ b/k8s/helm/apigwmm/templates/deployment.yaml @@ -61,10 +61,10 @@ spec: imagePullPolicy: {{ .Values.image.pullPolicy }} volumeMounts: - name: config - mountPath: /app/configuration + mountPath: {{ .Values.ocelot.configPath }} env: - name: PATH_BASE - value: {{ include "pathBase" (list .Values.app.ingress.entries.mobilemarketingapigw .) }} + value: {{ include "pathBase" . }} - name: k8sname value: {{ .Values.clusterName }} {{- if .Values.env.values -}} diff --git a/k8s/helm/apigwmm/templates/ingress.yaml b/k8s/helm/apigwmm/templates/ingress.yaml index 28e2aa84d..290aac0b3 100644 --- a/k8s/helm/apigwmm/templates/ingress.yaml +++ b/k8s/helm/apigwmm/templates/ingress.yaml @@ -1,5 +1,6 @@ {{- if .Values.ingress.enabled -}} -{{- $ingressPath := include "pathBase" (list .Values.app.ingress.entries.mobilemarketingapigw .) -}} +{{- $ingressPath := include "pathBase" . -}} +{{- $serviceName := .Values.app.svc.mobilemarketingapigw -}} apiVersion: extensions/v1beta1 kind: Ingress metadata: @@ -23,11 +24,13 @@ spec: {{- end }} {{- end }} rules: - - host: {{ .Values.inf.k8s.dns }} + {{- range .Values.ingress.hosts }} + - host: {{ . }} http: paths: - path: {{ $ingressPath }} backend: - serviceName: {{ .Values.app.svc.mobilemarketingapigw }} + serviceName: {{ $serviceName }} servicePort: http + {{- end }} {{- end }} diff --git a/k8s/helm/apigwmm/values.yaml b/k8s/helm/apigwmm/values.yaml index 501266780..ea87a6c05 100644 --- a/k8s/helm/apigwmm/values.yaml +++ b/k8s/helm/apigwmm/values.yaml @@ -62,3 +62,5 @@ probes: initialDelaySeconds: 90 periodSeconds: 60 port: 80 +ocelot: + configPath: /app/configuration diff --git a/k8s/helm/apigwms/templates/_names.tpl b/k8s/helm/apigwms/templates/_names.tpl index 1e840c56c..d44859fea 100644 --- a/k8s/helm/apigwms/templates/_names.tpl +++ b/k8s/helm/apigwms/templates/_names.tpl @@ -35,13 +35,11 @@ {{- define "pathBase" -}} -{{- $name := first .}} -{{- $ctx := last .}} -{{- if $ctx.Values.inf.k8s.suffix -}} -{{- $suffix := include "suffix-name" $ctx -}} -{{- printf "/%s-%s" $name $suffix -}} +{{- if .Values.inf.k8s.suffix -}} +{{- $suffix := include "suffix-name" . -}} +{{- printf "%s-%s" .Values.pathBase $suffix -}} {{- else -}} -{{- printf "/%s" $name -}} +{{- .Values.pathBase -}} {{- end -}} {{- end -}} diff --git a/k8s/helm/apigwms/templates/deployment.yaml b/k8s/helm/apigwms/templates/deployment.yaml index 8a4fd8942..b22922f89 100644 --- a/k8s/helm/apigwms/templates/deployment.yaml +++ b/k8s/helm/apigwms/templates/deployment.yaml @@ -61,10 +61,10 @@ spec: imagePullPolicy: {{ .Values.image.pullPolicy }} volumeMounts: - name: config - mountPath: /app/configuration + mountPath: {{ .Values.ocelot.configPath }} env: - name: PATH_BASE - value: {{ include "pathBase" (list .Values.app.ingress.entries.mobileshoppingapigw .) }} + value: {{ include "pathBase" . }} - name: k8sname value: {{ .Values.clusterName }} {{- if .Values.env.values -}} diff --git a/k8s/helm/apigwms/templates/ingress.yaml b/k8s/helm/apigwms/templates/ingress.yaml index 7dd50d8dd..e93ddc2c6 100644 --- a/k8s/helm/apigwms/templates/ingress.yaml +++ b/k8s/helm/apigwms/templates/ingress.yaml @@ -1,5 +1,7 @@ {{- if .Values.ingress.enabled -}} -{{- $ingressPath := include "pathBase" (list .Values.app.ingress.entries.mobileshoppingapigw .) -}} +{{- $ingressPath := include "pathBase" . -}} +{{- $serviceName := .Values.app.svc.mobileshoppingapigw -}} + apiVersion: extensions/v1beta1 kind: Ingress metadata: @@ -23,11 +25,13 @@ spec: {{- end }} {{- end }} rules: - - host: {{ .Values.inf.k8s.dns }} + {{- range .Values.ingress.hosts }} + - host: {{ . }} http: paths: - path: {{ $ingressPath }} backend: - serviceName: {{ .Values.app.svc.mobileshoppingapigw }} + serviceName: {{ $serviceName }} servicePort: http + {{- end }} {{- end }} diff --git a/k8s/helm/apigwms/values.yaml b/k8s/helm/apigwms/values.yaml index 58377ec5b..650ab6449 100644 --- a/k8s/helm/apigwms/values.yaml +++ b/k8s/helm/apigwms/values.yaml @@ -61,4 +61,6 @@ probes: timeoutSeconds: 5 initialDelaySeconds: 90 periodSeconds: 60 - port: 80 \ No newline at end of file + port: 80 +ocelot: + configPath: /app/configuration \ No newline at end of file diff --git a/k8s/helm/apigwwm/templates/_names.tpl b/k8s/helm/apigwwm/templates/_names.tpl index 1e840c56c..d44859fea 100644 --- a/k8s/helm/apigwwm/templates/_names.tpl +++ b/k8s/helm/apigwwm/templates/_names.tpl @@ -35,13 +35,11 @@ {{- define "pathBase" -}} -{{- $name := first .}} -{{- $ctx := last .}} -{{- if $ctx.Values.inf.k8s.suffix -}} -{{- $suffix := include "suffix-name" $ctx -}} -{{- printf "/%s-%s" $name $suffix -}} +{{- if .Values.inf.k8s.suffix -}} +{{- $suffix := include "suffix-name" . -}} +{{- printf "%s-%s" .Values.pathBase $suffix -}} {{- else -}} -{{- printf "/%s" $name -}} +{{- .Values.pathBase -}} {{- end -}} {{- end -}} diff --git a/k8s/helm/apigwwm/templates/deployment.yaml b/k8s/helm/apigwwm/templates/deployment.yaml index 5cbce9f22..d1f39ab6c 100644 --- a/k8s/helm/apigwwm/templates/deployment.yaml +++ b/k8s/helm/apigwwm/templates/deployment.yaml @@ -61,10 +61,10 @@ spec: imagePullPolicy: {{ .Values.image.pullPolicy }} volumeMounts: - name: config - mountPath: /app/configuration + mountPath: {{ .Values.ocelot.configPath }} env: - name: PATH_BASE - value: {{ include "pathBase" (list .Values.app.ingress.entries.webmarketingapigw .) }} + value: {{ include "pathBase" . }} - name: k8sname value: {{ .Values.clusterName }} {{- if .Values.env.values -}} diff --git a/k8s/helm/apigwwm/templates/ingress.yaml b/k8s/helm/apigwwm/templates/ingress.yaml index 0a79c4660..297fea52b 100644 --- a/k8s/helm/apigwwm/templates/ingress.yaml +++ b/k8s/helm/apigwwm/templates/ingress.yaml @@ -1,5 +1,7 @@ {{- if .Values.ingress.enabled -}} -{{- $ingressPath := include "pathBase" (list .Values.app.ingress.entries.webmarketingapigw .) -}} +{{- $ingressPath := include "pathBase" . -}} +{{- $serviceName := .Values.app.svc.webmarketingapigw -}} + apiVersion: extensions/v1beta1 kind: Ingress metadata: @@ -23,11 +25,13 @@ spec: {{- end }} {{- end }} rules: - - host: {{ .Values.inf.k8s.dns }} + {{- range .Values.ingress.hosts }} + - host: {{ . }} http: paths: - path: {{ $ingressPath }} backend: - serviceName: {{ .Values.app.svc.webmarketingapigw }} + serviceName: {{ $serviceName }} servicePort: http + {{- end }} {{- end }} diff --git a/k8s/helm/apigwwm/values.yaml b/k8s/helm/apigwwm/values.yaml index 68cbb89c4..63deb5832 100644 --- a/k8s/helm/apigwwm/values.yaml +++ b/k8s/helm/apigwwm/values.yaml @@ -61,4 +61,6 @@ probes: timeoutSeconds: 5 initialDelaySeconds: 90 periodSeconds: 60 - port: 80 \ No newline at end of file + port: 80 +ocelot: + configPath: /app/configuration \ No newline at end of file diff --git a/k8s/helm/apigwws/templates/_names.tpl b/k8s/helm/apigwws/templates/_names.tpl index a6eeb9965..d44859fea 100644 --- a/k8s/helm/apigwws/templates/_names.tpl +++ b/k8s/helm/apigwws/templates/_names.tpl @@ -33,14 +33,13 @@ {{- end -}} + {{- define "pathBase" -}} -{{- $name := first .}} -{{- $ctx := last .}} -{{- if $ctx.Values.inf.k8s.suffix -}} -{{- $suffix := include "suffix-name" $ctx -}} -{{- printf "/%s-%s" $name $suffix -}} +{{- if .Values.inf.k8s.suffix -}} +{{- $suffix := include "suffix-name" . -}} +{{- printf "%s-%s" .Values.pathBase $suffix -}} {{- else -}} -{{- printf "/%s" $name -}} +{{- .Values.pathBase -}} {{- end -}} {{- end -}} diff --git a/k8s/helm/apigwws/templates/deployment.yaml b/k8s/helm/apigwws/templates/deployment.yaml index 4912d12bb..327eb50b7 100644 --- a/k8s/helm/apigwws/templates/deployment.yaml +++ b/k8s/helm/apigwws/templates/deployment.yaml @@ -60,10 +60,10 @@ spec: imagePullPolicy: {{ .Values.image.pullPolicy }} volumeMounts: - name: config - mountPath: /app/configuration + mountPath: {{ .Values.ocelot.configPath }} env: - name: PATH_BASE - value: {{ include "pathBase" (list .Values.app.ingress.entries.webshoppingapigw .) }} + value: {{ include "pathBase" . }} - name: k8sname value: {{ .Values.clusterName }} {{- if .Values.env.values -}} diff --git a/k8s/helm/apigwws/templates/ingress.yaml b/k8s/helm/apigwws/templates/ingress.yaml index ee1f681ad..8bd2cfc6d 100644 --- a/k8s/helm/apigwws/templates/ingress.yaml +++ b/k8s/helm/apigwws/templates/ingress.yaml @@ -1,5 +1,6 @@ {{- if .Values.ingress.enabled -}} -{{- $ingressPath := include "pathBase" (list .Values.app.ingress.entries.webshoppingapigw .) -}} +{{- $ingressPath := include "pathBase" . -}} +{{- $serviceName := .Values.app.svc.webshoppingapigw -}} apiVersion: extensions/v1beta1 kind: Ingress metadata: @@ -23,11 +24,13 @@ spec: {{- end }} {{- end }} rules: - - host: {{ .Values.inf.k8s.dns }} + {{- range .Values.ingress.hosts }} + - host: {{ . }} http: paths: - path: {{ $ingressPath }} backend: - serviceName: {{ .Values.app.svc.webshoppingapigw }} + serviceName: {{ $serviceName }} servicePort: http + {{- end }} {{- end }} diff --git a/k8s/helm/apigwws/values.yaml b/k8s/helm/apigwws/values.yaml index 94b8a203f..2b047de99 100644 --- a/k8s/helm/apigwws/values.yaml +++ b/k8s/helm/apigwws/values.yaml @@ -62,3 +62,5 @@ probes: initialDelaySeconds: 90 periodSeconds: 60 port: 80 +ocelot: + configPath: /app/configuration \ No newline at end of file diff --git a/k8s/helm/basket-api/templates/ingress.yaml b/k8s/helm/basket-api/templates/ingress.yaml new file mode 100644 index 000000000..f99bd55a6 --- /dev/null +++ b/k8s/helm/basket-api/templates/ingress.yaml @@ -0,0 +1,37 @@ +{{- if .Values.ingress.enabled -}} +{{- $ingressPath := include "pathBase" . -}} +{{- $serviceName := .Values.app.svc.basket -}} + +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: {{ template "basket-api.fullname" . }} + labels: + app: {{ template "basket-api.name" . }} + chart: {{ template "basket-api.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- with .Values.ingress.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: +{{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + - {{ .Values.inf.k8s.dns }} + secretName: {{ .secretName }} + {{- end }} +{{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ . }} + http: + paths: + - path: {{ $ingressPath }} + backend: + serviceName: {{ $serviceName }} + servicePort: http + {{- end }} +{{- end }} \ No newline at end of file diff --git a/k8s/helm/basket-api/values.yaml b/k8s/helm/basket-api/values.yaml index 6c264afba..a773700fe 100644 --- a/k8s/helm/basket-api/values.yaml +++ b/k8s/helm/basket-api/values.yaml @@ -53,3 +53,6 @@ probes: initialDelaySeconds: 90 periodSeconds: 60 port: 80 + +ingress: + enabled: false \ No newline at end of file diff --git a/k8s/helm/catalog-api/templates/ingress.yaml b/k8s/helm/catalog-api/templates/ingress.yaml new file mode 100644 index 000000000..238d7b07e --- /dev/null +++ b/k8s/helm/catalog-api/templates/ingress.yaml @@ -0,0 +1,37 @@ +{{- if .Values.ingress.enabled -}} +{{- $ingressPath := include "pathBase" . -}} +{{- $serviceName := .Values.app.svc.catalog -}} + +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: {{ template "catalog-api.fullname" . }} + labels: + app: {{ template "catalog-api.name" . }} + chart: {{ template "catalog-api.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- with .Values.ingress.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: +{{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + - {{ .Values.inf.k8s.dns }} + secretName: {{ .secretName }} + {{- end }} +{{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ . }} + http: + paths: + - path: {{ $ingressPath }} + backend: + serviceName: {{ $serviceName }} + servicePort: http + {{- end }} +{{- end }} \ No newline at end of file diff --git a/k8s/helm/deploy-all-istio.ps1 b/k8s/helm/deploy-all-istio.ps1 new file mode 100644 index 000000000..5e05c6dbb --- /dev/null +++ b/k8s/helm/deploy-all-istio.ps1 @@ -0,0 +1,116 @@ +Param( + [parameter(Mandatory=$false)][string]$registry, + [parameter(Mandatory=$false)][bool]$installIstioOnSystem=$false, + [parameter(Mandatory=$false)][string]$dockerUser, + [parameter(Mandatory=$false)][string]$dockerPassword, + [parameter(Mandatory=$false)][string]$externalDns="aks", + [parameter(Mandatory=$false)][string]$dnsname="eshoptestistio", + [parameter(Mandatory=$false)][string]$appName="eshop", + [parameter(Mandatory=$false)][bool]$deployInfrastructure=$true, + [parameter(Mandatory=$false)][string]$kialiuser="admin", + [parameter(Mandatory=$false)][string]$kialipasswrd="admin", + [parameter(Mandatory=$false)][bool]$clean=$true, + [parameter(Mandatory=$false)][string]$aksName="", + [parameter(Mandatory=$false)][string]$aksRg="", + [parameter(Mandatory=$false)][string]$imageTag="latest", + [parameter(Mandatory=$false)][bool]$useLocalk8s=$false + ) + +$dns = $externalDns + +# Instalamos Istio +# Specify the Istio version that will be leveraged throughout these instructions +$ISTIO_VERSION="1.0.6" + +# Windows +$ProgressPreference = 'SilentlyContinue'; +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +Invoke-WebRequest -URI "https://github.com/istio/istio/releases/download/$ISTIO_VERSION/istio-$ISTIO_VERSION-win.zip" -OutFile "istio-$ISTIO_VERSION.zip" +Remove-Item istio-$ISTIO_VERSION -Recurse -ErrorAction Ignore +Expand-Archive -Path "istio-$ISTIO_VERSION.zip" -DestinationPath . + +if($installIstioOnSystem -eq $true) { + New-Item -ItemType Directory -Force -Path "C:\Program Files\Istio" + mv ./istio-$ISTIO_VERSION/bin/istioctl.exe "C:\Program Files/Istio/" + $PATH = [environment]::GetEnvironmentVariable("PATH", "User") + [environment]::SetEnvironmentVariable("PATH", $PATH + "; C:\Program Files\Istio", "User") +} +# Primero Desinstalamos cualquier cosa que haya en el cluster +if ($clean -eq $true) { + Write-Host "Cleaning previous helm releases..." -ForegroundColor Green + helm delete --purge $(helm ls -q) + kubectl delete -f istio-$ISTIO_VERSION/install/kubernetes/helm/istio/templates/crds.yaml -n istio-system + Write-Host "Previous releases deleted" -ForegroundColor Green +} + +Write-Host "Generating Kiali Credentials" -ForegroundColor Green +#generamos la credenciales para que kiali arranque sin problemas +kubectl -n istio-system create secret generic kiali --from-literal=username=$kialiuser --from-literal=passphrase=$kialipasswrd + + +Write-Host "Deploying Istio in the cluster" -ForegroundColor Green +helm install istio-$ISTIO_VERSION/install/kubernetes/helm/istio --wait --name istio --namespace istio-system --set global.controlPlaneSecurityEnabled=true --set grafana.enabled=true --set tracing.enabled=true --set kiali.enabled=true + +Write-Host "Setting Up Gateway" +kubectl delete gateway istio-autogenerated-k8s-ingress -n istio-system +kubectl apply -f ./istio/gateway.yml + +if ($useLocalk8s -eq $true) { + $dns="localhost" + $externalDns="localhost" +} +else { + Write-Host "Resolving DNS to Gateway public IP" -ForegroundColor Green + $ipaddress = $(kubectl get service istio-ingressgateway -n istio-system)[1] | %{ $_.Split(' ')[9];} + $query = "[?ipAddress!=null]|[?contains([ipAddress], '$ipaddress')].[id]" + $resid = az network public-ip list --query $query --output tsv + $jsonresponse = az network public-ip update --ids $resid --dns-name $dnsname + $externalDns = ($jsonresponse | ConvertFrom-Json).dnsSettings.fqdn + Write-Host "$externalDns is pointing to Cluster public ip $ipaddress" +} + +$useCustomRegistry=$false +if (-not [string]::IsNullOrEmpty($registry)) { + $useCustomRegistry=$true + if ([string]::IsNullOrEmpty($dockerUser) -or [string]::IsNullOrEmpty($dockerPassword)) { + Write-Host "Error: Must use -dockerUser AND -dockerPassword if specifying custom registry" -ForegroundColor Red + exit 1 + } +} +Write-Host "Begin eShopOnContainers installation using Helm" -ForegroundColor Green + +$infras = ("sql-data", "nosql-data", "rabbitmq", "keystore-data", "basket-data") +$charts = ("eshop-common", "apigwmm", "apigwms", "apigwwm", "apigwws", "basket-api","catalog-api", "identity-api", "locations-api", "marketing-api", "mobileshoppingagg","ordering-api","ordering-backgroundtasks","ordering-signalrhub", "payment-api", "webmvc", "webshoppingagg", "webspa", "webstatus", "webhooks-api", "webhooks-web") + +if ($deployInfrastructure) { + foreach ($infra in $infras) { + Write-Host "Installing infrastructure: $infra" -ForegroundColor Green + helm install --values app.yaml --values inf.yaml --set app.name=$appName --set inf.k8s.dns=$externalDns --name="$appName-$infra" $infra + } +} + +foreach ($chart in $charts) { + Write-Host "Installing: $chart" -ForegroundColor Green + if ($useCustomRegistry) { + helm install --set inf.registry.server=$registry --set inf.registry.login=$dockerUser --set inf.registry.pwd=$dockerPassword --set inf.registry.secretName=eshop-docker-scret --values app.yaml --values inf.yaml --values $ingressValuesFile --set app.name=$appName --set inf.k8s.dns=$dns --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart + } + else { + if ($chart -ne "eshop-common") { # eshop-common is ignored when no secret must be deployed + helm install --values app.yaml --values inf.yaml --set app.name=$appName --set inf.k8s.dns=$externalDns --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart + } + } +} + +Write-Host "helm charts installed." -ForegroundColor Green +Write-Host "Appling Virtual Services for routing." -ForegroundColor Green +kubectl apply -f ./istio/virtualservices.yml + +Remove-Item istio-$ISTIO_VERSION -Recurse -ErrorAction Ignore +Remove-Item istio-$ISTIO_VERSION.zip -Recurse -ErrorAction Ignore + + + + + + + diff --git a/k8s/helm/deploy-all.ps1 b/k8s/helm/deploy-all.ps1 index 1819c9a67..a30fc1ee6 100644 --- a/k8s/helm/deploy-all.ps1 +++ b/k8s/helm/deploy-all.ps1 @@ -5,6 +5,7 @@ Param( [parameter(Mandatory=$false)][string]$externalDns, [parameter(Mandatory=$false)][string]$appName="eshop", [parameter(Mandatory=$false)][bool]$deployInfrastructure=$true, + [parameter(Mandatory=$false)][bool]$deployCharts=$true, [parameter(Mandatory=$false)][bool]$clean=$true, [parameter(Mandatory=$false)][string]$aksName="", [parameter(Mandatory=$false)][string]$aksRg="", @@ -44,7 +45,7 @@ if ([string]::IsNullOrEmpty($dns)) { if ($clean) { Write-Host "Cleaning previous helm releases..." -ForegroundColor Green - helm delete --purge $(helm ls -q) + helm delete --purge $(helm ls -q eshop) Write-Host "Previous releases deleted" -ForegroundColor Green } @@ -66,24 +67,28 @@ $charts = ("eshop-common", "apigwmm", "apigwms", "apigwwm", "apigwws", "basket-a if ($deployInfrastructure) { foreach ($infra in $infras) { Write-Host "Installing infrastructure: $infra" -ForegroundColor Green - helm install --values app.yaml --values inf.yaml --values $ingressValuesFile --set app.name=$appName --set inf.k8s.dns=$dns --name="$appName-$infra" $infra + helm install --values app.yaml --values inf.yaml --values $ingressValuesFile --set app.name=$appName --set inf.k8s.dns=$dns --set ingress.hosts={$dns} --name="$appName-$infra" $infra } } +else { + Write-Host "eShopOnContainers infrastructure (bbdd, redis, ...) charts aren't installed (-deployCharts is false)" -ForegroundColor Yellow +} -foreach ($chart in $charts) { - Write-Host "Installing: $chart" -ForegroundColor Green - if ($useCustomRegistry) { - helm install --set inf.registry.server=$registry --set inf.registry.login=$dockerUser --set inf.registry.pwd=$dockerPassword --set inf.registry.secretName=eshop-docker-scret --values app.yaml --values inf.yaml --values $ingressValuesFile --set app.name=$appName --set inf.k8s.dns=$dns --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart - } - else { - if ($chart -ne "eshop-common") { # eshop-common is ignored when no secret must be deployed - helm install --values app.yaml --values inf.yaml --values $ingressValuesFile --set app.name=$appName --set inf.k8s.dns=$dns --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart +if ($deployCharts) { + foreach ($chart in $charts) { + Write-Host "Installing: $chart" -ForegroundColor Green + if ($useCustomRegistry) { + helm install --set inf.registry.server=$registry --set inf.registry.login=$dockerUser --set inf.registry.pwd=$dockerPassword --set inf.registry.secretName=eshop-docker-scret --values app.yaml --values inf.yaml --values $ingressValuesFile --set app.name=$appName --set inf.k8s.dns=$dns --set ingress.hosts={$dns} --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart + } + else { + if ($chart -ne "eshop-common") { # eshop-common is ignored when no secret must be deployed + helm install --values app.yaml --values inf.yaml --values $ingressValuesFile --set app.name=$appName --set inf.k8s.dns=$dns --set ingress.hosts={$dns} --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart + } } } } +else { + Write-Host "eShopOnContainers non-infrastructure charts aren't installed (-deployCharts is false)" -ForegroundColor Yellow +} Write-Host "helm charts installed." -ForegroundColor Green - - - - diff --git a/k8s/helm/deploy-all.sh b/k8s/helm/deploy-all.sh new file mode 100755 index 000000000..705b172f5 --- /dev/null +++ b/k8s/helm/deploy-all.sh @@ -0,0 +1,208 @@ +#!/usr/bin/env bash + +# http://redsymbol.net/articles/unofficial-bash-strict-mode +set -euo pipefail + +usage() +{ + cat < + The name of the AKS cluster. Required when the registry (using the -r parameter) is set to "aks". + --aks-rg + The resource group for the AKS cluster. Required when the registry (using the -r parameter) is set to "aks". + -b | --build-solution + Force a solution build before deployment (default: false). + -d | --dns + Specifies the external DNS/ IP address of the Kubernetes cluster. + When --use-local-k8s is specified the external DNS is automatically set to localhost. + -h | --help + Displays this help text and exits the script. + -n | --app-name + Specifies the name of the application (default: eshop). + -p | --docker-password + The Docker password used to logon to the custom registry, supplied using the -r parameter. + -r | --registry + Specifies the container registry to use (required), e.g. myregistry.azurecr.io. + --skip-clean + Do not clean the Kubernetes cluster (default is to clean the cluster). + --skip-image-build + Do not build images (default is to build all images). + --skip-image-push + Do not upload images to the container registry (just run the Kubernetes deployment portion). + Default is to push the images to the container registry. + --skip-infrastructure + Do not deploy infrastructure resources (like sql-data, no-sql or redis). + This is useful for production environments where infrastructure is hosted outside the Kubernetes cluster. + -t | --tag + The tag used for the newly created docker images. Default: newly created, date-based timestamp, with 1-minute resolution. + -u | --docker-user + The Docker username used to logon to the custom registry, supplied using the -r parameter. + --use-local-k8s + Deploy to a locally installed Kubernetes (default: false). + +It is assumed that the Kubernetes cluster has been granted access to the container registry. +If using AKS and ACR see link for more info: +https://docs.microsoft.com/en-us/azure/container-registry/container-registry-auth-aks + +WARNING! THE SCRIPT WILL COMPLETELY DESTROY ALL DEPLOYMENTS AND SERVICES VISIBLE +FROM THE CURRENT CONFIGURATION CONTEXT. +It is recommended that you create a separate namespace and confguration context +for the $app_name application, to isolate it from other applications on the cluster. +For more information see https://kubernetes.io/docs/tasks/administer-cluster/namespaces/ +You can use namespace.yaml file (in the same directory) to create the namespace. + +END +} + +app_name='eshop' +aks_name='' +aks_rg='' +build_images='yes' +clean='yes' +build_solution='' +container_registry='' +docker_password='' +docker_username='' +dns='' +image_tag=$(date '+%Y%m%d%H%M') +push_images='yes' +skip_infrastructure='' +use_local_k8s='' + +while [[ $# -gt 0 ]]; do + case "$1" in + --aks-name ) + aks_name="$2"; shift 2;; + --aks-rg ) + aks_rg="$2"; shift 2;; + -b | --build-solution ) + build_solution='yes'; shift ;; + -d | --dns ) + dns="$2"; shift 2;; + -h | --help ) + usage; exit 1 ;; + -n | --app-name ) + app_name="$2"; shift 2;; + -p | --docker-password ) + docker_password="$2"; shift;; + -r | --registry ) + container_registry="$2"; shift 2;; + --skip-clean ) + clean=''; shift ;; + --skip-image-build ) + build_images=''; shift ;; + --skip-image-push ) + push_images=''; shift ;; + --skip-infrastructure ) + skip_infrastructure='yes'; shift ;; + -t | --tag ) + image_tag="$2"; shift 2;; + -u | --docker-username ) + docker_username="$2"; shift 2;; + --use-local-k8s ) + use_local_k8s='yes'; shift ;; + *) + echo "Unknown option $1" + usage; exit 2 ;; + esac +done + +if [[ $build_solution ]]; then + echo "#################### Building $app_name solution ####################" + dotnet publish -o obj/Docker/publish ../../eShopOnContainers-ServicesAndWebApps.sln +fi + +export TAG=$image_tag + +if [[ $build_images ]]; then + echo "#################### Building the $app_name Docker images ####################" + docker-compose -p ../.. -f ../../docker-compose.yml build + + # Remove temporary images + docker rmi $(docker images -qf "dangling=true") +fi + +if [[ $push_images ]]; then + echo "#################### Pushing images to the container registry ####################" + services=(basket.api catalog.api identity.api ordering.api marketing.api payment.api locations.api webmvc webspa webstatus) + + for service in "${services[@]}" + do + echo "Pushing image for service $service..." + docker tag "eshop/$service:$image_tag" "$container_registry/$service:$image_tag" + docker push "$container_registry/$service:$image_tag" + done +fi + +ingress_values_file="ingress_values.yaml" + +if [[ $use_local_k8s ]]; then + ingress_values_file="ingress_values_dockerk8s.yaml" + dns="localhost" +fi + +if [[ $dns == "aks" ]]; then + echo "#################### Begin AKS discovery based on the --dns aks setting. ####################" + if [[ -z $aks_name ]] || [[ -z $aks_rg ]]; then + echo "Error: When using -dns aks, MUST set -aksName and -aksRg too." + echo '' + usage + exit 1 + fi + + echo "Getting DNS of AKS of AKS $aks_name (in resource group $aks_rg)" + dns="$(az aks show -n $aks_name -g $aks_rg --query addonProfiles.httpApplicationRouting.config.HTTPApplicationRoutingZoneName)" + if [[ -z dns ]]; then + echo "Error: when getting DNS of AKS $aks_name (in resource group $aks_rg). Please ensure AKS has httpRouting enabled AND Azure CLI is logged in and is of version 2.0.37 or higher." + exit 1 + fi + $dns=${dns//[\"]/""} + echo "DNS base found is $dns. Will use $aks_name.$dns for the app!" +fi + +# Initialization & check commands +if [[ -z $dns ]]; then + echo "No DNS specified. Ingress resources will be bound to public IP." +fi + +if [[ $clean ]]; then + echo "Cleaning previous helm releases..." + helm delete --purge $(helm ls -q) + echo "Previous releases deleted" +fi + +use_custom_registry='' + +if [[ -n $container_registry ]]; then + use_custom_registry='yes' + if [[ -z $docker_user ]] || [[ -z $docker_password ]]; then + echo "Error: Must use -u (--docker-username) AND -p (--docker-password) if specifying custom registry" + exit 1 + fi +fi + +echo "#################### Begin $app_name installation using Helm ####################" +infras=(sql-data nosql-data rabbitmq keystore-data basket-data) +charts=(eshop-common apigwmm apigwms apigwwm apigwws basket-api catalog-api identity-api locations-api marketing-api mobileshoppingagg ordering-api ordering-backgroundtasks ordering-signalrhub payment-api webmvc webshoppingagg webspa webstatus webhooks-api webhooks-web) + +if [[ !$skip_infrastructure ]]; then + for infra in "${infras[@]}" + do + echo "Installing infrastructure: $infra" + helm install --values app.yaml --values inf.yaml --values $ingress_values_file --set app.name=$app_name --set inf.k8s.dns=$dns --name="$app_name-$infra" $infra + done +fi + +for chart in "${charts[@]}" +do + echo "Installing: $chart" + if [[ $use_custom_registry ]]; then + helm install --set inf.registry.server=$container_registry --set inf.registry.login=$docker_username --set inf.registry.pwd=$docker_password --set inf.registry.secretName=eshop-docker-scret --values app.yaml --values inf.yaml --values $ingress_values_file --set app.name=$app_name --set inf.k8s.dns=$dns --set image.tag=$image_tag --set image.pullPolicy=Always --name="$app_name-$chart" $chart + elif [[ $chart != "eshop-common" ]]; then # eshop-common is ignored when no secret must be deployed + helm install --values app.yaml --values inf.yaml --values $ingress_values_file --set app.name=$app_name --set inf.k8s.dns=$dns --set image.tag=$image_tag --set image.pullPolicy=Always --name="$app_name-$chart" $chart + fi +done + +echo "FINISHED: Helm charts installed." diff --git a/k8s/helm/identity-api/templates/configmap.yaml b/k8s/helm/identity-api/templates/configmap.yaml index 24d71b699..67c0f21cb 100644 --- a/k8s/helm/identity-api/templates/configmap.yaml +++ b/k8s/helm/identity-api/templates/configmap.yaml @@ -36,5 +36,4 @@ data: xamarin_callback_e: http://{{ $xamarincallback }} webhooksapi_e: http://{{ $webhooks_url }} webhooksweb_e: http://{{ $webhooksweb_url }} - - + enableDevspaces: "{{ .Values.enableDevspaces }}" \ No newline at end of file diff --git a/k8s/helm/identity-api/templates/ingress.yaml b/k8s/helm/identity-api/templates/ingress.yaml index 5824f91e2..1d2d3d5d5 100644 --- a/k8s/helm/identity-api/templates/ingress.yaml +++ b/k8s/helm/identity-api/templates/ingress.yaml @@ -1,5 +1,6 @@ {{- if .Values.ingress.enabled -}} {{- $ingressPath := include "pathBase" . -}} +{{- $serviceName := .Values.app.svc.identity }} apiVersion: extensions/v1beta1 kind: Ingress metadata: @@ -23,11 +24,13 @@ spec: {{- end }} {{- end }} rules: - - host: {{ .Values.inf.k8s.dns }} + {{- range .Values.ingress.hosts }} + - host: {{ . }} http: paths: - path: {{ $ingressPath }} backend: - serviceName: {{ .Values.app.svc.identity }} + serviceName: {{ $serviceName }} servicePort: http + {{- end }} {{- end }} diff --git a/k8s/helm/identity-api/values.yaml b/k8s/helm/identity-api/values.yaml index c0fd38192..7b57dfde1 100644 --- a/k8s/helm/identity-api/values.yaml +++ b/k8s/helm/identity-api/values.yaml @@ -58,6 +58,8 @@ env: key: webhooksapi_e - name: WebhooksWebClient key: webhooksweb_e + - name: EnableDevspaces + key: enableDevspaces values: - name: ASPNETCORE_ENVIRONMENT value: Development @@ -65,6 +67,7 @@ env: value: 'K8S' - name: IsClusterEnv value: 'True' + probes: liveness: path: /liveness @@ -76,4 +79,6 @@ probes: timeoutSeconds: 5 initialDelaySeconds: 90 periodSeconds: 60 - port: 80 \ No newline at end of file + port: 80 + +enableDevspaces: "false" \ No newline at end of file diff --git a/k8s/helm/istio/doc.md b/k8s/helm/istio/doc.md new file mode 100644 index 000000000..b8c0a0257 --- /dev/null +++ b/k8s/helm/istio/doc.md @@ -0,0 +1,325 @@ +# Using Helm Charts to deploy eShopOnContainers to AKS with ISTIO + +It is possible to deploy eShopOnContainers on a AKS using [Helm](https://helm.sh/) instead of custom scripts (that will be deprecated soon). + +## Create Kubernetes cluster in AKS +You can create the AKS cluster by using two ways: + +- A. Use Azure CLI: Follow a procedure suing [Azure CLI like here](https://docs.microsoft.com/en-us/azure/aks/kubernetes-walkthrough), but make sure you **enable RBAC** with `--enable-rbac` in `az aks create` command. + +- B. Use Azure's portal + +The following steps are using the Azure portal to create the AKS cluster: + +- Start the process by providing the general data, like in the following screenshot: + +![image](https://user-images.githubusercontent.com/1712635/45787360-c59ecd80-bc29-11e8-9565-c989ad6ad57b.png) + +- Then, very important, in the next step, enable RBAC: + +![image](https://user-images.githubusercontent.com/1712635/45780917-8bc2cc80-bc13-11e8-87ac-2942b3c7496d.png) + + You can use **basic network** settings since for a test you don't need integration into any existing VNET. + +![image](https://user-images.githubusercontent.com/1712635/45780991-b745b700-bc13-11e8-926b-afac57229d0a.png) + +- You can also enable monitoring: + +![image](https://user-images.githubusercontent.com/1712635/45781148-1277a980-bc14-11e8-8614-f7a239731bec.png) + +- Finally, create the cluster. It'll take a few minutes for it to be ready. + +### Configure RBAC security for K8s dashboard service-account + +In order NOT to get errors in the Kubernetes dashboard, you'll need to set the following service-account steps. + +Here you can see the errors you might see: +![image](https://user-images.githubusercontent.com/1712635/45784384-5622e100-bc1d-11e8-8d33-e22fd955150a.png) + +Now, just run the Azure CLI command to browse the Kubernetes Dashboard: + +`az aks browse --resource-group pro-eshop-aks-helm-linux-resgrp --name pro-eshop-aks-helm-linux` + +![image](https://user-images.githubusercontent.com/1712635/45786406-2d9ee500-bc25-11e8-83e9-bdfc302e80f1.png) + + +## Additional pre-requisites + +In addition to having an AKS cluster created in Azure and having kubectl and Azure CLI installed in your local machine and configured to use your Azure subscription, you also need the following pre-requisites: + +### Install Helm + +You need to have helm installed on your machine, and Tiller must be installed on the AKS. Follow these instructions on how to ['Install applications with Helm in Azure Kubernetes Service (AKS)'](https://docs.microsoft.com/en-us/azure/aks/kubernetes-helm) to setup Helm and Tiller for AKS. + +**Note**: If your ASK cluster is not RBAC-enabled (default option in portal) you may receive following error when running a helm command: + +``` +Error: Get http://localhost:8080/api/v1/namespaces/kube-system/configmaps?labelSelector=OWNER%!D(MISSING)TILLER: dial tcp [::1]:8080: connect: connection refused +``` + +If so, type: + +``` +kubectl --namespace=kube-system edit deployment/tiller-deploy +``` + +Your default text editor will popup with the YAML definition of the tiller deploy. Search for: + +``` +automountServiceAccountToken: false +``` + +And change it to: + +``` +automountServiceAccountToken: true +``` + +Save the file and close the editor. This should reapply the deployment in the cluster. Now Helm commands should work. + +## Install eShopOnContainers with Istio using Helm + +All steps need to be performed on `/k8s/helm` folder. The easiest way is to use the `deploy-all-istio.ps1` script from a Powershell window: + +``` +.\deploy-all-istio.ps1 -dnsname eshoptestistio -externalDns aks -aksName eshoptest -aksRg eshoptest -imageTag dev +``` + +This will install all the [eShopOnContainers public images](https://hub.docker.com/u/eshop/) with tag `dev` on the AKS named `eshoptest` in the resource group `eshoptest` and with the dns url: http://**eshoptestistio**.westus.cloudapp.azure.com/ . By default all infrastructure (sql, mongo, rabbit and redis) is installed also in the cluster. + +Once the script is run, you should see following output when using `kubectl get deployment`: + +``` +NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE +eshop-apigwmm 1 1 1 1 4d +eshop-apigwms 1 1 1 1 4d +eshop-apigwwm 1 1 1 1 4d +eshop-apigwws 1 1 1 1 4d +eshop-basket-api 1 1 1 1 4d +eshop-basket-data 1 1 1 1 4d +eshop-catalog-api 1 1 1 1 4d +eshop-identity-api 1 1 1 1 4d +eshop-keystore-data 1 1 1 1 4d +eshop-locations-api 1 1 1 1 4d +eshop-marketing-api 1 1 1 1 4d +eshop-mobileshoppingagg 1 1 1 1 4d +eshop-nosql-data 1 1 1 1 4d +eshop-ordering-api 1 1 1 1 4d +eshop-ordering-backgroundtasks 1 1 1 1 4d +eshop-ordering-signalrhub 1 1 1 1 4d +eshop-payment-api 1 1 1 1 4d +eshop-rabbitmq 1 1 1 1 4d +eshop-sql-data 1 1 1 1 4d +eshop-webmvc 1 1 1 1 4d +eshop-webshoppingagg 1 1 1 1 4d +eshop-webspa 1 1 1 1 4d +eshop-webstatus 1 1 1 1 4d +``` + +Every public service is exposed through the istio ingress gateway. +Yo can see the ingress gateway public ip doing `kubectl get services -n istio-system` +``` +grafana ClusterIP 10.0.204.87 3000/TCP 1h +istio-citadel ClusterIP 10.0.23.86 8060/TCP,9093/TCP 1h +istio-egressgateway ClusterIP 10.0.136.169 80/TCP,443/TCP 1h +istio-galley ClusterIP 10.0.113.51 443/TCP,9093/TCP 1h +istio-ingressgateway LoadBalancer 10.0.76.80 40.118.189.161 80:31380/TCP,443:31390/TCP,31400:31400/TCP,15011:31276/TCP,8060:30519/TCP,853:31698/TCP,15030:31453/TCP,15031:32362/TCP 1h +istio-pilot ClusterIP 10.0.164.253 15010/TCP,15011/TCP,8080/TCP,9093/TCP 1h +istio-policy ClusterIP 10.0.170.49 9091/TCP,15004/TCP,9093/TCP 1h +istio-sidecar-injector ClusterIP 10.0.251.12 443/TCP 1h +istio-telemetry ClusterIP 10.0.195.112 9091/TCP,15004/TCP,9093/TCP,42422/TCP 1h +jaeger-agent ClusterIP None 5775/UDP,6831/UDP,6832/UDP 1h +jaeger-collector ClusterIP 10.0.123.98 14267/TCP,14268/TCP 1h +jaeger-query ClusterIP 10.0.244.146 16686/TCP 1h +kiali ClusterIP 10.0.182.12 20001/TCP 1h +prometheus ClusterIP 10.0.136.223 9090/TCP 1h +tracing ClusterIP 10.0.57.236 80/TCP 1h +zipkin ClusterIP 10.0.30.57 9411/TCP 1h +``` + +You can view the MVC client at http://[dns]/ + +## Customizing the deployment + +### Using your own images + +To use your own images instead of the public ones, you have to pass following additional parameters to the `deploy-all-istio.ps1` script: + +* `registry`: Login server for the Docker registry +* `dockerUser`: User login for the Docker registry +* `dockerPassword`: User password for the Docker registry + +This will deploy a secret on the cluster to connect to the specified server, and all image names deployed will be prepended with `registry/` value. + +### Not deploying infrastructure containers + +If you want to use external resources, use `-deployInfrastructure $false` to not deploy infrastructure containers. However **you still have to manually update the scripts to provide your own configuration** (see next section). + +### Providing your own configuration + +The file `inf.yaml` contains the description of the infrastructure used. File is docummented so take a look on it to understand all of its entries. If using external resources you need to edit this file according to your needs. You'll need to edit: + +* `inf.sql.host` with the host name of the SQL Server +* `inf.sql.common` entries to provide your SQL user, password. `Pid` is not used when using external resources (it is used to set specific product id for the SQL Server container). +* `inf.sql.catalog`, `inf.sql.ordering`, `inf.sql.identity`: To provide the database names for catalog, ordering and identity services +* `mongo.host`: With the host name of the Mongo DB +* `mongo.locations`, `mongo.marketing` with the database names for locations and marketing services +* `redis.basket.constr` with the connection string to Redis for Basket Service. Note that `redis.basket.svc` is not used when using external services +* `redis.keystore.constr` with the connection string to Redis for Keystore Service. Note that `redis.keystore.svc` is not used when using external services +* `eventbus.constr` with the connection string to Azure Service Bus and `eventbus.useAzure` to `true` to use Azure service bus. Note that `eventbus.svc` is not used when using external services + +### Using Azure storage for Catalog Photos + +Using Azure storage for catalog (and marketing) photos is not directly supported, but you can accomplish it by editing the file `k8s/helm/catalog-api/templates/configmap.yaml`. Search for lines: + +``` +catalog__PicBaseUrl: http://{{ $webshoppingapigw }}/api/v1/c/catalog/items/[0]/pic/ +``` + +And replace it for: + +``` +catalog__PicBaseUrl: http:/// +``` + +In the same way, to use Azure storage for the marketing service, have to edit the file `k8s/helm/marketing-api/templates/configmap.yaml` and replacing the line: + +``` +marketing__PicBaseUrl: http://{{ $webshoppingapigw }}/api/v1/c/catalog/items/[0]/pic/ +``` + +by: + +``` +marketing__PicBaseUrl: http:/// +``` + +# Using Helm Charts to deploy eShopOnContainers to a local Kubernetes in Windows with 'Docker for Windows' + +## Additional pre-requisites + +In addition to having Docker for Windows/Mac with Kubernetes enabled and having kubectl ayou also need the following pre-requisites: + +### Install Helm + +You need to have helm installed on your machine, and Tiller must be installed on the local Docker Kubernetes cluster. Once you have [Helm downloaded](https://helm.sh/) and installed on your machine you must: + +1. Create the tiller service account, by running `kubectl apply -f helm-rbac.yaml` from `/k8s` folder +2. Install tiller and configure it to use the tiller service account by typing `helm init --service-account tiller` + +## Install eShopOnContainers with Istio using Helm + +All steps need to be performed on `/k8s/helm` folder. The easiest way is to use the `deploy-all-istio.ps1` script from a Powershell window: + +``` +.\deploy-all-istio.ps1 -imageTag dev -useLocalk8s $true +``` + +The parameter `useLocalk8s` to $true, forces the script to use `localhost` as the DNS for all Helm charts. + +This will install all the [eShopOnContainers public images](https://hub.docker.com/u/eshop/) with tag `dev` on the Docker local Kubernetes cluster. By default all infrastructure (sql, mongo, rabbit and redis) is installed also in the cluster. + +Once the script is run, you should see following output when using `kubectl get deployment`: + +``` +NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE +eshop-apigwmm 1 1 1 1 2h +eshop-apigwms 1 1 1 1 2h +eshop-apigwwm 1 1 1 1 2h +eshop-apigwws 1 1 1 1 2h +eshop-basket-api 1 1 1 1 2h +eshop-basket-data 1 1 1 1 2h +eshop-catalog-api 1 1 1 1 2h +eshop-identity-api 1 1 1 1 2h +eshop-keystore-data 1 1 1 1 2h +eshop-locations-api 1 1 1 1 2h +eshop-marketing-api 1 1 1 1 2h +eshop-mobileshoppingagg 1 1 1 1 2h +eshop-nosql-data 1 1 1 1 2h +eshop-ordering-api 1 1 1 1 2h +eshop-ordering-backgroundtasks 1 1 1 1 2h +eshop-ordering-signalrhub 1 1 1 1 2h +eshop-payment-api 1 1 1 1 2h +eshop-rabbitmq 1 1 1 1 2h +eshop-sql-data 1 1 1 1 2h +eshop-webmvc 1 1 1 1 2h +eshop-webshoppingagg 1 1 1 1 2h +eshop-webspa 1 1 1 1 2h +eshop-webstatus 1 1 1 1 2h +``` + +Note that istio ingress gateway is bound to DNS localhost and the host is also "localhost". So, you can access the webspa by typing `http://localhost` and the MVC by typing `http://localhost/` + +As this is the Docker local K8s cluster, you can see also the containers running on your machine. If you type `docker ps` you'll see all them: + +``` +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +fec1e3499416 a3f21ec4bd11 "/entrypoint.sh /ngi…" 9 minutes ago Up 9 minutes k8s_nginx-ingress-controller_nginx-ingress-controller-f88c75bc6-5xs2n_ingress-nginx_f1cc7094-e68f-11e8-b4b6-00155d016146_0 +76485867f032 eshop/payment.api "dotnet Payment.API.…" 2 hours ago Up 2 hours k8s_payment-api_eshop-payment-api-75d5f9bdf6-6zx2v_default_4a3cdab4-e67f-11e8-b4b6-00155d016146_1 +c2c4640ed610 eshop/marketing.api "dotnet Marketing.AP…" 2 hours ago Up 2 hours k8s_marketing-api_eshop-marketing-api-6b8c5989fd-jpxqv_default_45780626-e67f-11e8-b4b6-00155d016146_1 +85301d538574 eshop/ordering.signalrhub "dotnet Ordering.Sig…" 2 hours ago Up 2 hours k8s_ordering-signalrhub_eshop-ordering-signalrhub-58cf5ff6-cnlm8_default_4932c344-e67f-11e8-b4b6-00155d016146_1 +7a408a98000e eshop/ordering.backgroundtasks "dotnet Ordering.Bac…" 2 hours ago Up 2 hours k8s_ordering-backgroundtasks_eshop-ordering-backgroundtasks-cc8f6d4d8-ztfk7_default_47f9cf10-e67f-11e8-b4b6-00155d016146_1 +12c64b3a13e0 eshop/basket.api "dotnet Basket.API.d…" 2 hours ago Up 2 hours k8s_basket-api_eshop-basket-api-658546684d-6hlvd_default_4262d022-e67f-11e8-b4b6-00155d016146_1 +133fccfeeff3 eshop/webstatus "dotnet WebStatus.dll" 2 hours ago Up 2 hours k8s_webstatus_eshop-webstatus-7f46479dc4-bqnq7_default_4dc13eb2-e67f-11e8-b4b6-00155d016146_0 +00c6e4c52135 eshop/webspa "dotnet WebSPA.dll" 2 hours ago Up 2 hours k8s_webspa_eshop-webspa-64cb8df9cb-dcbwg_default_4cd47376-e67f-11e8-b4b6-00155d016146_0 +d4507f1f6b1a eshop/webshoppingagg "dotnet Web.Shopping…" 2 hours ago Up 2 hours k8s_webshoppingagg_eshop-webshoppingagg-cc94fc86-sxd2v_default_4be6cdb9-e67f-11e8-b4b6-00155d016146_0 +9178e26703da eshop/webmvc "dotnet WebMVC.dll" 2 hours ago Up 2 hours k8s_webmvc_eshop-webmvc-985779684-4br5z_default_4addd4d6-e67f-11e8-b4b6-00155d016146_0 +1088c281c710 eshop/ordering.api "dotnet Ordering.API…" 2 hours ago Up 2 hours k8s_ordering-api_eshop-ordering-api-fb8c548cb-k68x9_default_4740958a-e67f-11e8-b4b6-00155d016146_0 +12424156d5c9 eshop/mobileshoppingagg "dotnet Mobile.Shopp…" 2 hours ago Up 2 hours k8s_mobileshoppingagg_eshop-mobileshoppingagg-b54645d7b-rlrgh_default_46c00017-e67f-11e8-b4b6-00155d016146_0 +65463ffd437d eshop/locations.api "dotnet Locations.AP…" 2 hours ago Up 2 hours k8s_locations-api_eshop-locations-api-577fc94696-dfhq8_default_44929c4b-e67f-11e8-b4b6-00155d016146_0 +5b3431873763 eshop/identity.api "dotnet Identity.API…" 2 hours ago Up 2 hours k8s_identity-api_eshop-identity-api-85d9b79f4-s5ks7_default_43d6eb7c-e67f-11e8-b4b6-00155d016146_0 +7c8e77252459 eshop/catalog.api "dotnet Catalog.API.…" 2 hours ago Up 2 hours k8s_catalog-api_eshop-catalog-api-59fd444fb-ztvhz_default_4356705a-e67f-11e8-b4b6-00155d016146_0 +94d95d0d3653 eshop/ocelotapigw "dotnet OcelotApiGw.…" 2 hours ago Up 2 hours k8s_apigwws_eshop-apigwws-65474b979d-n99jw_default_41395473-e67f-11e8-b4b6-00155d016146_0 +bc4bbce71d5f eshop/ocelotapigw "dotnet OcelotApiGw.…" 2 hours ago Up 2 hours k8s_apigwwm_eshop-apigwwm-857c549dd8-8w5gv_default_4098d770-e67f-11e8-b4b6-00155d016146_0 +840aabcceaa9 eshop/ocelotapigw "dotnet OcelotApiGw.…" 2 hours ago Up 2 hours k8s_apigwms_eshop-apigwms-5b94dfb54b-dnmr9_default_401fc611-e67f-11e8-b4b6-00155d016146_0 +aabed7646f5b eshop/ocelotapigw "dotnet OcelotApiGw.…" 2 hours ago Up 2 hours k8s_apigwmm_eshop-apigwmm-85f96cbdb4-dhfwr_default_3ed7967a-e67f-11e8-b4b6-00155d016146_0 +49c5700def5a f06a5773f01e "docker-entrypoint.s…" 2 hours ago Up 2 hours k8s_basket-data_eshop-basket-data-66fbc788cc-csnlw_default_3e0c45fe-e67f-11e8-b4b6-00155d016146_0 +a5db4c521807 f06a5773f01e "docker-entrypoint.s…" 2 hours ago Up 2 hours k8s_keystore-data_eshop-keystore-data-5c9c85cb99-8k56s_default_3ce1a273-e67f-11e8-b4b6-00155d016146_0 +aae88fd2d810 d69a5113ceae "docker-entrypoint.s…" 2 hours ago Up 2 hours k8s_rabbitmq_eshop-rabbitmq-6b68647bc4-gr565_default_3c37ee6a-e67f-11e8-b4b6-00155d016146_0 +65d49ca9589d bbed8d0e01c1 "docker-entrypoint.s…" 2 hours ago Up 2 hours k8s_nosql-data_eshop-nosql-data-579c9d89f8-mtt95_default_3b9c1f89-e67f-11e8-b4b6-00155d016146_0 +090e0dde2ec4 bbe2822dfe38 "/opt/mssql/bin/sqls…" 2 hours ago Up 2 hours k8s_sql-data_eshop-sql-data-5c4fdcccf4-bscdb_default_3afd29b8-e67f-11e8-b4b6-00155d016146_0 +``` + +## Known issues + +Login from the webmvc results in following error: HttpRequestException: Response status code does not indicate success: 404 (Not Found). + +The reason is because MVC needs to access the Identity Server from both outside the container (browser) and inside the container (C# code). Thus, the configuration uses always the *external url* of the Identity Server, which in this case is just `http://localhost/identity-api`. But this external url is incorrect when used from C# code, and the web mvc can't access the identity api. This is the only case when this issue happens (and is the reason why we use 10.0.75.1 for local address in web mvc in local development mode) + +Solving this requires some manual steps: + +Update the configmap of Web MVC by typing (**line breaks are mandatory**) and your cluster dns name has to be the same of your environment: + +``` +kubectl patch cm cfg-eshop-webmvc --type strategic --patch @' +data: + urls__IdentityUrl: http://**eshoptest**.westus.cloudapp.azure.com/identity + urls__mvc: http://**eshoptest**.westus.cloudapp.azure.com/webmvc +'@ +``` + +Update the configmap of Identity API by typing (**line breaks are mandatory**): + +``` +kubectl patch cm cfg-eshop-identity-api --type strategic --patch @' +data: + mvc_e: http://**eshoptest**.westus.cloudapp.azure.com/webmvc +'@ +``` + +Restart the SQL Server pod to ensure the database is recreated again: + +``` +kubectl delete pod --selector app=sql-data +``` + +Wait until SQL Server pod is ready to accept connections and then restart all other pods: + +``` +kubectl delete pod --selector="app!=sql-data" +``` + +**Note:** Pods are deleted to ensure the databases are recreated again, as identity api stores its client names and urls in the database. + +Now, you can access the MVC app using: `http://**eshoptest**.westus.cloudapp.azure.com/`. + diff --git a/k8s/helm/istio/gateway.yml b/k8s/helm/istio/gateway.yml new file mode 100644 index 000000000..fbc61dadd --- /dev/null +++ b/k8s/helm/istio/gateway.yml @@ -0,0 +1,15 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: Gateway +metadata: + name: istio-ingressgateway + #namespace: istio-system +spec: + selector: + istio: ingressgateway # use Istio default gateway implementation + servers: + - port: + number: 80 + name: http + protocol: HTTP + hosts: + - "*" \ No newline at end of file diff --git a/k8s/helm/istio/virtualservices.yml b/k8s/helm/istio/virtualservices.yml new file mode 100644 index 000000000..e01f4678f --- /dev/null +++ b/k8s/helm/istio/virtualservices.yml @@ -0,0 +1,59 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: webmvcvs + namespace: default +spec: + hosts: + - "*" + gateways: + - istio-ingressgateway + http: + - match: + - uri: + prefix: / + route: + - destination: + port: + number: 80 + host: webmvc +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: webshoppingapigwvs + namespace: default +spec: + hosts: + - "*" + gateways: + - istio-ingressgateway + http: + - match: + - uri: + prefix: /webshoppingapigw + route: + - destination: + port: + number: 80 + host: webshoppingapigw +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: identityvs + namespace: default +spec: + hosts: + - "*" + gateways: + - istio-ingressgateway + http: + - match: + - uri: + prefix: /identity + route: + - destination: + port: + number: 80 + host: identity \ No newline at end of file diff --git a/k8s/helm/locations-api/templates/ingress.yaml b/k8s/helm/locations-api/templates/ingress.yaml new file mode 100644 index 000000000..8c846944e --- /dev/null +++ b/k8s/helm/locations-api/templates/ingress.yaml @@ -0,0 +1,36 @@ +{{- if .Values.ingress.enabled -}} +{{- $ingressPath := include "pathBase" . -}} +{{- $serviceName := .Values.app.svc.locations }} +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: {{ template "locations-api.fullname" . }} + labels: + app: {{ template "locations-api.name" . }} + chart: {{ template "locations-api.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- with .Values.ingress.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: +{{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + - {{ .Values.inf.k8s.dns }} + secretName: {{ .secretName }} + {{- end }} +{{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ . }} + http: + paths: + - path: {{ $ingressPath }} + backend: + serviceName: {{ $serviceName }} + servicePort: http + {{- end }} +{{- end }} diff --git a/k8s/helm/marketing-api/templates/ingress.yaml b/k8s/helm/marketing-api/templates/ingress.yaml new file mode 100644 index 000000000..7a5b29b12 --- /dev/null +++ b/k8s/helm/marketing-api/templates/ingress.yaml @@ -0,0 +1,36 @@ +{{- if .Values.ingress.enabled -}} +{{- $ingressPath := include "pathBase" . -}} +{{- $serviceName := .Values.app.svc.marketing }} +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: {{ template "marketing-api.fullname" . }} + labels: + app: {{ template "marketing-api.name" . }} + chart: {{ template "marketing-api.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- with .Values.ingress.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: +{{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + - {{ .Values.inf.k8s.dns }} + secretName: {{ .secretName }} + {{- end }} +{{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ . }} + http: + paths: + - path: {{ $ingressPath }} + backend: + serviceName: {{ $serviceName }} + servicePort: http + {{- end }} +{{- end }} diff --git a/k8s/helm/mobileshoppingagg/templates/ingress.yaml b/k8s/helm/mobileshoppingagg/templates/ingress.yaml new file mode 100644 index 000000000..6c50a574e --- /dev/null +++ b/k8s/helm/mobileshoppingagg/templates/ingress.yaml @@ -0,0 +1,36 @@ +{{- if .Values.ingress.enabled -}} +{{- $ingressPath := include "pathBase" . -}} +{{- $serviceName := .Values.app.svc.mobileshoppingagg }} +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: {{ template "mobileshoppingagg.fullname" . }} + labels: + app: {{ template "mobileshoppingagg.name" . }} + chart: {{ template "mobileshoppingagg.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- with .Values.ingress.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: +{{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + - {{ .Values.inf.k8s.dns }} + secretName: {{ .secretName }} + {{- end }} +{{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ . }} + http: + paths: + - path: {{ $ingressPath }} + backend: + serviceName: {{ $serviceName }} + servicePort: http + {{- end }} +{{- end }} diff --git a/k8s/helm/ordering-backgroundtasks/templates/configmap.yaml b/k8s/helm/ordering-backgroundtasks/templates/configmap.yaml index 54fec785b..23041d4ac 100644 --- a/k8s/helm/ordering-backgroundtasks/templates/configmap.yaml +++ b/k8s/helm/ordering-backgroundtasks/templates/configmap.yaml @@ -1,10 +1,11 @@ {{- $name := include "ordering-backgroundtasks.fullname" . -}} {{- $sqlsrv := include "sql-name" . -}} +{{- $cfgname := printf "cfg-%s" $name | trunc 63 }} apiVersion: v1 kind: ConfigMap metadata: - name: "cfg-{{ $name }}" + name: "{{ $cfgname }}" labels: app: {{ template "ordering-backgroundtasks.name" . }} chart: {{ template "ordering-backgroundtasks.chart" .}} diff --git a/k8s/helm/ordering-backgroundtasks/templates/deployment.yaml b/k8s/helm/ordering-backgroundtasks/templates/deployment.yaml index 017f9f3dd..d93c7f764 100644 --- a/k8s/helm/ordering-backgroundtasks/templates/deployment.yaml +++ b/k8s/helm/ordering-backgroundtasks/templates/deployment.yaml @@ -1,11 +1,11 @@ {{- $name := include "ordering-backgroundtasks.fullname" . -}} -{{- $cfgname := printf "%s-%s" "cfg" $name -}} +{{- $cfgname := printf "cfg-%s" $name | trunc 63 }} + apiVersion: apps/v1beta2 kind: Deployment metadata: name: {{ template "ordering-backgroundtasks.fullname" . }} labels: - ufo: {{ $cfgname}} app: {{ template "ordering-backgroundtasks.name" . }} chart: {{ template "ordering-backgroundtasks.chart" . }} release: {{ .Release.Name }} diff --git a/k8s/helm/webhooks-api/templates/ingress.yaml b/k8s/helm/webhooks-api/templates/ingress.yaml index 293f8e47e..debf0f84a 100644 --- a/k8s/helm/webhooks-api/templates/ingress.yaml +++ b/k8s/helm/webhooks-api/templates/ingress.yaml @@ -1,5 +1,6 @@ {{- if .Values.ingress.enabled -}} {{- $ingressPath := include "pathBase" . -}} +{{- $serviceName := .Values.app.svc.webhooks }} apiVersion: extensions/v1beta1 kind: Ingress metadata: @@ -23,11 +24,13 @@ spec: {{- end }} {{- end }} rules: - - host: {{ .Values.inf.k8s.dns }} + {{- range .Values.ingress.hosts }} + - host: {{ . }} http: paths: - path: {{ $ingressPath }} backend: - serviceName: {{ .Values.app.svc.webhooks }} + serviceName: {{ $serviceName }} servicePort: http + {{- end }} {{- end }} diff --git a/k8s/helm/webmvc/templates/configmap.yaml b/k8s/helm/webmvc/templates/configmap.yaml index 60dacdadd..55c4e6b33 100644 --- a/k8s/helm/webmvc/templates/configmap.yaml +++ b/k8s/helm/webmvc/templates/configmap.yaml @@ -2,7 +2,6 @@ {{- $identity := include "url-of" (list .Values.app.ingress.entries.identity .) -}} {{- $webshoppingapigw := include "url-of" (list .Values.app.ingress.entries.webshoppingapigw .) -}} {{- $mvc := include "url-of" (list .Values.app.ingress.entries.mvc .) -}} -{{- $mongo := include "mongo-name" . -}} apiVersion: v1 diff --git a/k8s/helm/webmvc/templates/ingress.yaml b/k8s/helm/webmvc/templates/ingress.yaml index abfb62b2f..892fa4273 100644 --- a/k8s/helm/webmvc/templates/ingress.yaml +++ b/k8s/helm/webmvc/templates/ingress.yaml @@ -1,5 +1,6 @@ {{- if .Values.ingress.enabled -}} {{- $ingressPath := include "pathBase" . -}} +{{- $serviceName := .Values.app.svc.mvc -}} apiVersion: extensions/v1beta1 kind: Ingress metadata: @@ -23,11 +24,13 @@ spec: {{- end }} {{- end }} rules: - - host: {{ .Values.inf.k8s.dns }} + {{- range .Values.ingress.hosts }} + - host: {{ . }} http: paths: - path: {{ $ingressPath }} backend: - serviceName: {{ .Values.app.svc.mvc }} + serviceName: {{ $serviceName }} servicePort: http + {{- end }} {{- end }} diff --git a/k8s/helm/webshoppingagg/templates/ingress.yaml b/k8s/helm/webshoppingagg/templates/ingress.yaml new file mode 100644 index 000000000..742db4d30 --- /dev/null +++ b/k8s/helm/webshoppingagg/templates/ingress.yaml @@ -0,0 +1,36 @@ +{{- if .Values.ingress.enabled -}} +{{- $ingressPath := include "pathBase" . -}} +{{- $serviceName := .Values.app.svc.webshoppingagg }} +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: {{ template "webshoppingagg.fullname" . }} + labels: + app: {{ template "webshoppingagg.name" . }} + chart: {{ template "webshoppingagg.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- with .Values.ingress.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: +{{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + - {{ .Values.inf.k8s.dns }} + secretName: {{ .secretName }} + {{- end }} +{{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ . }} + http: + paths: + - path: {{ $ingressPath }} + backend: + serviceName: {{ $serviceName }} + servicePort: http + {{- end }} +{{- end }} diff --git a/k8s/nginx-ingress/mandatory-istio.yaml b/k8s/nginx-ingress/mandatory-istio.yaml new file mode 100644 index 000000000..56b1cc3b5 --- /dev/null +++ b/k8s/nginx-ingress/mandatory-istio.yaml @@ -0,0 +1,238 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: ingress-nginx + +--- + +kind: ConfigMap +apiVersion: v1 +metadata: + name: nginx-configuration + namespace: ingress-nginx + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: nginx-ingress-serviceaccount + namespace: ingress-nginx + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRole +metadata: + name: nginx-ingress-clusterrole + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx +rules: + - apiGroups: + - "" + resources: + - configmaps + - endpoints + - nodes + - pods + - secrets + verbs: + - list + - watch + - apiGroups: + - "" + resources: + - nodes + verbs: + - get + - apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch + - apiGroups: + - "extensions" + resources: + - ingresses + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - apiGroups: + - "extensions" + resources: + - ingresses/status + verbs: + - update + +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: Role +metadata: + name: nginx-ingress-role + namespace: ingress-nginx + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx +rules: + - apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - namespaces + verbs: + - get + - apiGroups: + - "" + resources: + - configmaps + resourceNames: + # Defaults to "-" + # Here: "-" + # This has to be adapted if you change either parameter + # when launching the nginx-ingress-controller. + - "ingress-controller-leader-nginx" + verbs: + - get + - update + - apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - apiGroups: + - "" + resources: + - endpoints + verbs: + - get + +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: RoleBinding +metadata: + name: nginx-ingress-role-nisa-binding + namespace: ingress-nginx + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: nginx-ingress-role +subjects: + - kind: ServiceAccount + name: nginx-ingress-serviceaccount + namespace: ingress-nginx + +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRoleBinding +metadata: + name: nginx-ingress-clusterrole-nisa-binding + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: nginx-ingress-clusterrole +subjects: + - kind: ServiceAccount + name: nginx-ingress-serviceaccount + namespace: ingress-nginx + +--- + +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: nginx-ingress-controller + namespace: ingress-nginx + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + template: + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + annotations: + prometheus.io/port: "10254" + prometheus.io/scrape: "true" + spec: + serviceAccountName: nginx-ingress-serviceaccount + containers: + - name: nginx-ingress-controller + image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.20.0 + args: + - /nginx-ingress-controller + - --configmap=$(POD_NAMESPACE)/nginx-configuration + - --publish-service=$(POD_NAMESPACE)/ingress-nginx + - --annotations-prefix=nginx.ingress.kubernetes.io + securityContext: + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + # www-data -> 33 + runAsUser: 33 + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + ports: + - name: http + containerPort: 80 + - name: https + containerPort: 443 + livenessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 diff --git a/restore-packages.cmd b/restore-packages.cmd new file mode 100644 index 000000000..4e99614d6 --- /dev/null +++ b/restore-packages.cmd @@ -0,0 +1 @@ +for /R %%f in (*.csproj) do dotnet restore --no-dependencies %%f diff --git a/run-docker-compose-build.ps1 b/run-docker-compose-build.ps1 index 7d99ee0e7..ed4a14541 100644 --- a/run-docker-compose-build.ps1 +++ b/run-docker-compose-build.ps1 @@ -1,6 +1,6 @@ $startTime = $(Get-Date) -docker-compose build +docker-compose build --build-arg RUN=scripts/restore-packages $elapsedTime = $(Get-Date) - $startTime diff --git a/scripts/restore-packages b/scripts/restore-packages new file mode 100644 index 000000000..5ad4abf62 --- /dev/null +++ b/scripts/restore-packages @@ -0,0 +1 @@ +echo RESTORING ALL PACKAGES...; for f in /src/csproj-files/*.csproj; do dotnet restore $f; done diff --git a/scripts/restore-packages.cmd b/scripts/restore-packages.cmd new file mode 100644 index 000000000..9f3a8434e --- /dev/null +++ b/scripts/restore-packages.cmd @@ -0,0 +1 @@ +for %%p in (csproj-files\*.csproj) do dotnet restore %%p diff --git a/src/ApiGateways/ApiGw-Base/Dockerfile b/src/ApiGateways/ApiGw-Base/Dockerfile index eb94675b9..f4751f2d1 100644 --- a/src/ApiGateways/ApiGw-Base/Dockerfile +++ b/src/ApiGateways/ApiGw-Base/Dockerfile @@ -1,16 +1,26 @@ -FROM microsoft/dotnet:2.2.0-aspnetcore-runtime AS base +FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.2.100-sdk AS build +FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build WORKDIR /src + +COPY scripts scripts/ + +COPY src/ApiGateways/*/*.csproj /src/csproj-files/ +COPY src/ApiGateways/*/*/*.csproj /src/csproj-files/ +COPY src/BuildingBlocks/*/*/*.csproj /src/csproj-files/ +COPY src/Services/*/*/*.csproj /src/csproj-files/ +COPY src/Web/*/*.csproj /src/csproj-files/ + +ARG RUN=pwd +RUN ${RUN} + COPY . . WORKDIR /src/src/ApiGateways/ApiGw-Base/ -RUN dotnet restore -nowarn:msb3202,nu1503 -RUN dotnet build -c Release -o /app +RUN dotnet publish -c Release -o /app FROM build AS publish -RUN dotnet publish -c Release -o /app FROM base AS final WORKDIR /app diff --git a/src/ApiGateways/ApiGw-Base/Dockerfile.develop b/src/ApiGateways/ApiGw-Base/Dockerfile.develop new file mode 100644 index 000000000..4da230d68 --- /dev/null +++ b/src/ApiGateways/ApiGw-Base/Dockerfile.develop @@ -0,0 +1,14 @@ +FROM microsoft/dotnet:2.2-sdk +ARG BUILD_CONFIGURATION=Debug +ENV ASPNETCORE_ENVIRONMENT=Development +ENV DOTNET_USE_POLLING_FILE_WATCHER=true +EXPOSE 80 + +WORKDIR /src +COPY ["src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj", "src/ApiGateways/ApiGw-Base/"] +RUN dotnet restore "src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj" +COPY . . +WORKDIR "/src/src/ApiGateways/ApiGw-Base" +RUN dotnet build --no-restore "OcelotApiGw.csproj" -c $BUILD_CONFIGURATION + +ENTRYPOINT ["dotnet", "run", "--no-build", "--no-launch-profile", "-c", "$BUILD_CONFIGURATION", "--"] \ No newline at end of file diff --git a/src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj b/src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj index cf963debc..b1b6b1db6 100644 --- a/src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj +++ b/src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj @@ -5,12 +5,8 @@ - - - - - - + + diff --git a/src/ApiGateways/ApiGw-Base/Program.cs b/src/ApiGateways/ApiGw-Base/Program.cs index c8f8d7993..bcf1c8d60 100644 --- a/src/ApiGateways/ApiGw-Base/Program.cs +++ b/src/ApiGateways/ApiGw-Base/Program.cs @@ -25,6 +25,12 @@ namespace OcelotApiGw builder.ConfigureServices(s => s.AddSingleton(builder)) .ConfigureAppConfiguration(ic => ic.AddJsonFile(Path.Combine("configuration", "configuration.json"))) .UseStartup() + .ConfigureLogging((hostingContext, loggingbuilder) => + { + loggingbuilder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + loggingbuilder.AddConsole(); + loggingbuilder.AddDebug(); + }) .UseSerilog((builderContext, config) => { config diff --git a/src/ApiGateways/ApiGw-Base/Startup.cs b/src/ApiGateways/ApiGw-Base/Startup.cs index cd90bd7ca..585c26471 100644 --- a/src/ApiGateways/ApiGw-Base/Startup.cs +++ b/src/ApiGateways/ApiGw-Base/Startup.cs @@ -76,7 +76,7 @@ namespace OcelotApiGw services.AddOcelot(_cfg); } - public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + public void Configure(IApplicationBuilder app, IHostingEnvironment env) { var pathBase = _cfg["PATH_BASE"]; @@ -101,8 +101,6 @@ namespace OcelotApiGw Predicate = r => r.Name.Contains("self") }); - loggerFactory.AddConsole(_cfg.GetSection("Logging")); - app.UseCors("CorsPolicy"); app.UseOcelot().Wait(); diff --git a/src/ApiGateways/Mobile.Bff.Marketing/apigw/azds.yaml b/src/ApiGateways/Mobile.Bff.Marketing/apigw/azds.yaml new file mode 100644 index 000000000..46375343b --- /dev/null +++ b/src/ApiGateways/Mobile.Bff.Marketing/apigw/azds.yaml @@ -0,0 +1,44 @@ +kind: helm-release +apiVersion: 1.1 +build: + context: ..\..\..\..\ + dockerfile: ..\..\..\ApiGateways\ApiGw-Base\Dockerfile +install: + chart: ../../../../k8s/helm/apigwmm + set: + replicaCount: 1 + image: + tag: $(tag) + pullPolicy: Never + ingress: + annotations: + kubernetes.io/ingress.class: traefik-azds + hosts: + # This expands to [space.s.]webmvc...aksapp.io + - $(spacePrefix)eshop$(hostSuffix) + inf: + k8s: + dns: $(spacePrefix)eshop$(hostSuffix) + values: + - values.dev.yaml? + - secrets.dev.yaml? + - inf.yaml + - app.yaml +configurations: + develop: + build: + useGitIgnore: true + dockerfile: ..\..\..\ApiGateways\ApiGw-Base\Dockerfile.develop + args: + BUILD_CONFIGURATION: ${BUILD_CONFIGURATION:-Debug} + container: + sync: + - '**/Pages/**' + - '**/Views/**' + - '**/wwwroot/**' + - '!**/*.{sln,csproj}' + command: [dotnet, run, --no-restore, --no-build, --no-launch-profile, -c, "${BUILD_CONFIGURATION:-Debug}"] + iterate: + processesToKill: [dotnet, vsdbg] + buildCommands: + - [dotnet, build, --no-restore, -c, "${BUILD_CONFIGURATION:-Debug}"] diff --git a/src/ApiGateways/Mobile.Bff.Marketing/apigw/values.dev.yaml b/src/ApiGateways/Mobile.Bff.Marketing/apigw/values.dev.yaml new file mode 100644 index 000000000..fb59d98d5 --- /dev/null +++ b/src/ApiGateways/Mobile.Bff.Marketing/apigw/values.dev.yaml @@ -0,0 +1,2 @@ +ocelot: + configPath: /src/src/ApiGateways/ApiGw-Base/configuration \ No newline at end of file diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile index be89c315f..d29c73b3e 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile @@ -1,16 +1,26 @@ -FROM microsoft/dotnet:2.2.0-aspnetcore-runtime AS base +FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.2.100-sdk AS build +FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build WORKDIR /src + +COPY scripts scripts/ + +COPY src/ApiGateways/*/*.csproj /src/csproj-files/ +COPY src/ApiGateways/*/*/*.csproj /src/csproj-files/ +COPY src/BuildingBlocks/*/*/*.csproj /src/csproj-files/ +COPY src/Services/*/*/*.csproj /src/csproj-files/ +COPY src/Web/*/*.csproj /src/csproj-files/ + +ARG RUN=pwd +RUN ${RUN} + COPY . . WORKDIR /src/src/ApiGateways/Mobile.Bff.Shopping/aggregator -RUN dotnet restore -nowarn:msb3202,nu1503 -RUN dotnet build --no-restore -c Release -o /app +RUN dotnet publish -c Release -o /app FROM build AS publish -RUN dotnet publish --no-restore -c Release -o /app FROM base AS final WORKDIR /app diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile.develop b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile.develop new file mode 100644 index 000000000..c4330c370 --- /dev/null +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile.develop @@ -0,0 +1,16 @@ +FROM microsoft/dotnet:2.2-sdk +ARG BUILD_CONFIGURATION=Debug +ENV ASPNETCORE_ENVIRONMENT=Development +ENV DOTNET_USE_POLLING_FILE_WATCHER=true +EXPOSE 80 + +WORKDIR /src +COPY ["src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj", "src/ApiGateways/Mobile.Bff.Shopping/aggregator/"] +COPY ["src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj", "src/BuildingBlocks/Devspaces.Support/"] + +RUN dotnet restore src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj -nowarn:msb3202,nu1503 +COPY . . +WORKDIR "/src/src/ApiGateways/Mobile.Bff.Shopping/aggregator" +RUN dotnet build --no-restore -c $BUILD_CONFIGURATION + +CMD ["dotnet", "run", "--no-build", "--no-launch-profile", "-c", "$BUILD_CONFIGURATION", "--"]] \ No newline at end of file diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj index 6300d6711..1dd1ba1b6 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj @@ -12,15 +12,19 @@ - - + + - + + + + + diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Properties/launchSettings.json b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Properties/launchSettings.json index 925e70b0d..c259d5094 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Properties/launchSettings.json +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Properties/launchSettings.json @@ -24,6 +24,13 @@ "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "http://localhost:61632/" + }, + "Azure Dev Spaces": { + "commandName": "AzureDevSpaces", + "launchBrowser": true, + "resourceGroup": "eshoptestedu", + "aksName": "eshoptestedu", + "subscriptionId": "e3035ac1-c06c-4daf-8939-57b3c5f1f759" } } } \ No newline at end of file diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs index eeb58ac3b..eee0d9c6e 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs @@ -20,6 +20,7 @@ using Swashbuckle.AspNetCore.Swagger; using HealthChecks.UI.Client; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.Extensions.Diagnostics.HealthChecks; +using Devspaces.Support; namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator { @@ -47,6 +48,7 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator services.AddCustomMvc(Configuration) .AddCustomAuthentication(Configuration) + .AddDevspaces() .AddHttpServices(); } @@ -57,7 +59,7 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator if (!string.IsNullOrEmpty(pathBase)) { - loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'"); + loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase); app.UsePathBase(pathBase); } @@ -188,15 +190,18 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator services.AddHttpClient() .AddHttpMessageHandler() .AddPolicyHandler(GetRetryPolicy()) - .AddPolicyHandler(GetCircuitBreakerPolicy()); + .AddPolicyHandler(GetCircuitBreakerPolicy()) + .AddDevspacesSupport(); services.AddHttpClient() .AddPolicyHandler(GetRetryPolicy()) - .AddPolicyHandler(GetCircuitBreakerPolicy()); + .AddPolicyHandler(GetCircuitBreakerPolicy()) + .AddDevspacesSupport(); services.AddHttpClient() .AddPolicyHandler(GetRetryPolicy()) - .AddPolicyHandler(GetCircuitBreakerPolicy()); + .AddPolicyHandler(GetCircuitBreakerPolicy()) + .AddDevspacesSupport(); return services; } diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/azds.yaml b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/azds.yaml new file mode 100644 index 000000000..8dbac7128 --- /dev/null +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/azds.yaml @@ -0,0 +1,55 @@ +kind: helm-release +apiVersion: 1.1 +build: + context: ..\..\..\.. + dockerfile: Dockerfile +install: + chart: ../../../../k8s/helm/mobileshoppingagg + set: + image: + tag: $(tag) + pullPolicy: Never + ingress: + annotations: + kubernetes.io/ingress.class: traefik-azds + hosts: + # This expands to [space.s.]apigwms...aksapp.io + - $(spacePrefix)eshop$(hostSuffix) + inf: + k8s: + dns: $(spacePrefix)eshop$(hostSuffix) + values: + - values.dev.yaml? + - secrets.dev.yaml? + - app.yaml + - inf.yaml +configurations: + develop: + build: + useGitIgnore: true + dockerfile: Dockerfile.develop + container: + syncTarget: /src + sync: + - '**/Pages/**' + - '**/Views/**' + - '**/wwwroot/**' + - '!**/*.{sln,csproj}' + command: + - dotnet + - run + - --no-restore + - --no-build + - --no-launch-profile + - -c + - ${Configuration:-Debug} + iterate: + processesToKill: + - dotnet + - vsdbg + buildCommands: + - - dotnet + - build + - --no-restore + - -c + - ${Configuration:-Debug} diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/values.dev.yaml b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/values.dev.yaml new file mode 100644 index 000000000..eaed45149 --- /dev/null +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/values.dev.yaml @@ -0,0 +1,3 @@ +ingress: + enabled: true + tls: [] \ No newline at end of file diff --git a/src/ApiGateways/Mobile.Bff.Shopping/apigw/azds.yaml b/src/ApiGateways/Mobile.Bff.Shopping/apigw/azds.yaml new file mode 100644 index 000000000..9e2d09e42 --- /dev/null +++ b/src/ApiGateways/Mobile.Bff.Shopping/apigw/azds.yaml @@ -0,0 +1,44 @@ +kind: helm-release +apiVersion: 1.1 +build: + context: ..\..\..\..\ + dockerfile: ..\..\..\ApiGateways\ApiGw-Base\Dockerfile +install: + chart: ../../../../k8s/helm/apigwms + set: + replicaCount: 1 + image: + tag: $(tag) + pullPolicy: Never + ingress: + annotations: + kubernetes.io/ingress.class: traefik-azds + hosts: + # This expands to [space.s.]webmvc...aksapp.io + - $(spacePrefix)eshop$(hostSuffix) + inf: + k8s: + dns: $(spacePrefix)eshop$(hostSuffix) + values: + - values.dev.yaml? + - secrets.dev.yaml? + - inf.yaml + - app.yaml +configurations: + develop: + build: + useGitIgnore: true + dockerfile: ..\..\..\ApiGateways\ApiGw-Base\Dockerfile.develop + args: + BUILD_CONFIGURATION: ${BUILD_CONFIGURATION:-Debug} + container: + sync: + - '**/Pages/**' + - '**/Views/**' + - '**/wwwroot/**' + - '!**/*.{sln,csproj}' + command: [dotnet, run, --no-restore, --no-build, --no-launch-profile, -c, "${BUILD_CONFIGURATION:-Debug}"] + iterate: + processesToKill: [dotnet, vsdbg] + buildCommands: + - [dotnet, build, --no-restore, -c, "${BUILD_CONFIGURATION:-Debug}"] diff --git a/src/ApiGateways/Mobile.Bff.Shopping/apigw/values.dev.yaml b/src/ApiGateways/Mobile.Bff.Shopping/apigw/values.dev.yaml new file mode 100644 index 000000000..fb59d98d5 --- /dev/null +++ b/src/ApiGateways/Mobile.Bff.Shopping/apigw/values.dev.yaml @@ -0,0 +1,2 @@ +ocelot: + configPath: /src/src/ApiGateways/ApiGw-Base/configuration \ No newline at end of file diff --git a/src/ApiGateways/Web.Bff.Marketing/apigw/azds.yaml b/src/ApiGateways/Web.Bff.Marketing/apigw/azds.yaml new file mode 100644 index 000000000..8f60cbcc7 --- /dev/null +++ b/src/ApiGateways/Web.Bff.Marketing/apigw/azds.yaml @@ -0,0 +1,44 @@ +kind: helm-release +apiVersion: 1.1 +build: + context: ..\..\..\..\ + dockerfile: ..\..\..\ApiGateways\ApiGw-Base\Dockerfile +install: + chart: ../../../../k8s/helm/apigwwm + set: + replicaCount: 1 + image: + tag: $(tag) + pullPolicy: Never + ingress: + annotations: + kubernetes.io/ingress.class: traefik-azds + hosts: + # This expands to [space.s.]webmvc...aksapp.io + - $(spacePrefix)eshop$(hostSuffix) + inf: + k8s: + dns: $(spacePrefix)eshop$(hostSuffix) + values: + - values.dev.yaml? + - secrets.dev.yaml? + - inf.yaml + - app.yaml +configurations: + develop: + build: + useGitIgnore: true + dockerfile: ..\..\..\ApiGateways\ApiGw-Base\Dockerfile.develop + args: + BUILD_CONFIGURATION: ${BUILD_CONFIGURATION:-Debug} + container: + sync: + - '**/Pages/**' + - '**/Views/**' + - '**/wwwroot/**' + - '!**/*.{sln,csproj}' + command: [dotnet, run, --no-restore, --no-build, --no-launch-profile, -c, "${BUILD_CONFIGURATION:-Debug}"] + iterate: + processesToKill: [dotnet, vsdbg] + buildCommands: + - [dotnet, build, --no-restore, -c, "${BUILD_CONFIGURATION:-Debug}"] diff --git a/src/ApiGateways/Web.Bff.Marketing/apigw/values.dev.yaml b/src/ApiGateways/Web.Bff.Marketing/apigw/values.dev.yaml new file mode 100644 index 000000000..fb59d98d5 --- /dev/null +++ b/src/ApiGateways/Web.Bff.Marketing/apigw/values.dev.yaml @@ -0,0 +1,2 @@ +ocelot: + configPath: /src/src/ApiGateways/ApiGw-Base/configuration \ No newline at end of file diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile b/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile index 236d3705a..8ac850778 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile @@ -1,16 +1,26 @@ -FROM microsoft/dotnet:2.2.0-aspnetcore-runtime AS base +FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.2.100-sdk AS build +FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build WORKDIR /src + +COPY scripts scripts/ + +COPY src/ApiGateways/*/*.csproj /src/csproj-files/ +COPY src/ApiGateways/*/*/*.csproj /src/csproj-files/ +COPY src/BuildingBlocks/*/*/*.csproj /src/csproj-files/ +COPY src/Services/*/*/*.csproj /src/csproj-files/ +COPY src/Web/*/*.csproj /src/csproj-files/ + +ARG RUN=pwd +RUN ${RUN} + COPY . . WORKDIR /src/src/ApiGateways/Web.Bff.Shopping/aggregator -RUN dotnet restore -nowarn:msb3202,nu1503 -RUN dotnet build --no-restore -c Release -o /app +RUN dotnet publish -c Release -o /app FROM build AS publish -RUN dotnet publish --no-restore -c Release -o /app FROM base AS final WORKDIR /app diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile.develop b/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile.develop new file mode 100644 index 000000000..3d0d16dd3 --- /dev/null +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile.develop @@ -0,0 +1,16 @@ +FROM microsoft/dotnet:2.2-sdk +ARG BUILD_CONFIGURATION=Debug +ENV ASPNETCORE_ENVIRONMENT=Development +ENV DOTNET_USE_POLLING_FILE_WATCHER=true +EXPOSE 80 + +WORKDIR /src +COPY ["src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj", "src/ApiGateways/Web.Bff.Shopping/aggregator/"] +COPY ["src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj", "src/BuildingBlocks/Devspaces.Support/"] + +RUN dotnet restore src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj -nowarn:msb3202,nu1503 +COPY . . +WORKDIR "/src/src/ApiGateways/Web.Bff.Shopping/aggregator" +RUN dotnet build --no-restore -c $BUILD_CONFIGURATION + +CMD ["dotnet", "run", "--no-build", "--no-launch-profile", "-c", "$BUILD_CONFIGURATION", "--"]] \ No newline at end of file diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs b/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs index 0fcd13a41..dd5e1cc8c 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs @@ -21,6 +21,7 @@ using System.Net.Http; using HealthChecks.UI.Client; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.Extensions.Diagnostics.HealthChecks; +using Devspaces.Support; namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator { @@ -48,6 +49,7 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator services.AddCustomMvc(Configuration) .AddCustomAuthentication(Configuration) + .AddDevspaces() .AddApplicationServices(); } @@ -57,7 +59,7 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator var pathBase = Configuration["PATH_BASE"]; if (!string.IsNullOrEmpty(pathBase)) { - loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'"); + loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase); app.UsePathBase(pathBase); } @@ -182,21 +184,23 @@ namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator services.AddSingleton(); //register http services - + services.AddHttpClient() .AddHttpMessageHandler() .AddPolicyHandler(GetRetryPolicy()) - .AddPolicyHandler(GetCircuitBreakerPolicy()); + .AddPolicyHandler(GetCircuitBreakerPolicy()) + .AddDevspacesSupport(); services.AddHttpClient() .AddPolicyHandler(GetRetryPolicy()) - .AddPolicyHandler(GetCircuitBreakerPolicy()); + .AddPolicyHandler(GetCircuitBreakerPolicy()) + .AddDevspacesSupport(); services.AddHttpClient() .AddHttpMessageHandler() .AddPolicyHandler(GetRetryPolicy()) - .AddPolicyHandler(GetCircuitBreakerPolicy()); - + .AddPolicyHandler(GetCircuitBreakerPolicy()) + .AddDevspacesSupport(); return services; } diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj b/src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj index de2f55ab8..d438e4602 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj @@ -12,14 +12,18 @@ - - + + - + + + + + diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/azds.yaml b/src/ApiGateways/Web.Bff.Shopping/aggregator/azds.yaml new file mode 100644 index 000000000..189d2261d --- /dev/null +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/azds.yaml @@ -0,0 +1,55 @@ +kind: helm-release +apiVersion: 1.1 +build: + context: ..\..\..\.. + dockerfile: Dockerfile +install: + chart: ../../../../k8s/helm/webshoppingagg + set: + image: + tag: $(tag) + pullPolicy: Never + ingress: + annotations: + kubernetes.io/ingress.class: traefik-azds + hosts: + # This expands to [space.s.]apigwms...aksapp.io + - $(spacePrefix)eshop$(hostSuffix) + inf: + k8s: + dns: $(spacePrefix)eshop$(hostSuffix) + values: + - values.dev.yaml? + - secrets.dev.yaml? + - app.yaml + - inf.yaml +configurations: + develop: + build: + useGitIgnore: true + dockerfile: Dockerfile.develop + container: + syncTarget: /src + sync: + - '**/Pages/**' + - '**/Views/**' + - '**/wwwroot/**' + - '!**/*.{sln,csproj}' + command: + - dotnet + - run + - --no-restore + - --no-build + - --no-launch-profile + - -c + - ${Configuration:-Debug} + iterate: + processesToKill: + - dotnet + - vsdbg + buildCommands: + - - dotnet + - build + - --no-restore + - -c + - ${Configuration:-Debug} diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/values.dev.yaml b/src/ApiGateways/Web.Bff.Shopping/aggregator/values.dev.yaml new file mode 100644 index 000000000..45f664d6a --- /dev/null +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/values.dev.yaml @@ -0,0 +1,2 @@ +ocelot: + configPath: /app/configuration \ No newline at end of file diff --git a/src/ApiGateways/Web.Bff.Shopping/apigw/azds.yaml b/src/ApiGateways/Web.Bff.Shopping/apigw/azds.yaml new file mode 100644 index 000000000..69f26cdca --- /dev/null +++ b/src/ApiGateways/Web.Bff.Shopping/apigw/azds.yaml @@ -0,0 +1,43 @@ +kind: helm-release +apiVersion: 1.1 +build: + context: ..\..\..\..\ + dockerfile: ..\..\..\..\ApiGateways\ApiGw-Base\Dockerfile +install: + chart: ../../../../k8s/helm/apigwws + set: + replicaCount: 1 + image: + tag: $(tag) + pullPolicy: Never + ingress: + annotations: + kubernetes.io/ingress.class: traefik-azds + hosts: + - $(spacePrefix)eshop$(hostSuffix) + inf: + k8s: + dns: $(spacePrefix)eshop$(hostSuffix) + values: + - values.dev.yaml? + - secrets.dev.yaml? + - inf.yaml + - app.yaml +configurations: + develop: + build: + useGitIgnore: true + dockerfile: ..\..\..\ApiGateways\ApiGw-Base\Dockerfile.develop + args: + BUILD_CONFIGURATION: ${BUILD_CONFIGURATION:-Debug} + container: + sync: + - '**/Pages/**' + - '**/Views/**' + - '**/wwwroot/**' + - '!**/*.{sln,csproj}' + command: [dotnet, run, --no-restore, --no-build, --no-launch-profile, -c, "${BUILD_CONFIGURATION:-Debug}"] + iterate: + processesToKill: [dotnet, vsdbg] + buildCommands: + - [dotnet, build, --no-restore, -c, "${BUILD_CONFIGURATION:-Debug}"] diff --git a/src/ApiGateways/Web.Bff.Shopping/apigw/values.dev.yaml b/src/ApiGateways/Web.Bff.Shopping/apigw/values.dev.yaml new file mode 100644 index 000000000..fb59d98d5 --- /dev/null +++ b/src/ApiGateways/Web.Bff.Shopping/apigw/values.dev.yaml @@ -0,0 +1,2 @@ +ocelot: + configPath: /src/src/ApiGateways/ApiGw-Base/configuration \ No newline at end of file diff --git a/src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj b/src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj new file mode 100644 index 000000000..81a897bad --- /dev/null +++ b/src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/src/BuildingBlocks/Devspaces.Support/DevspacesMessageHandler.cs b/src/BuildingBlocks/Devspaces.Support/DevspacesMessageHandler.cs new file mode 100644 index 000000000..0895b752a --- /dev/null +++ b/src/BuildingBlocks/Devspaces.Support/DevspacesMessageHandler.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Devspaces.Support +{ + public class DevspacesMessageHandler : DelegatingHandler + { + private const string DevspacesHeaderName = "azds-route-as"; + private readonly IHttpContextAccessor _httpContextAccessor; + public DevspacesMessageHandler(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var req = _httpContextAccessor.HttpContext.Request; + + if (req.Headers.ContainsKey(DevspacesHeaderName)) + { + request.Headers.Add(DevspacesHeaderName, req.Headers[DevspacesHeaderName] as IEnumerable); + } + return base.SendAsync(request, cancellationToken); + } + } +} diff --git a/src/BuildingBlocks/Devspaces.Support/HttpClientBuilderDevspacesExtensions.cs b/src/BuildingBlocks/Devspaces.Support/HttpClientBuilderDevspacesExtensions.cs new file mode 100644 index 000000000..60108301d --- /dev/null +++ b/src/BuildingBlocks/Devspaces.Support/HttpClientBuilderDevspacesExtensions.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Devspaces.Support +{ + public static class HttpClientBuilderDevspacesExtensions + { + public static IHttpClientBuilder AddDevspacesSupport(this IHttpClientBuilder builder) + { + builder.AddHttpMessageHandler(); + return builder; + } + } +} diff --git a/src/BuildingBlocks/Devspaces.Support/ServiceCollectionDevspacesExtensions.cs b/src/BuildingBlocks/Devspaces.Support/ServiceCollectionDevspacesExtensions.cs new file mode 100644 index 000000000..0ac5c9e1b --- /dev/null +++ b/src/BuildingBlocks/Devspaces.Support/ServiceCollectionDevspacesExtensions.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Devspaces.Support +{ + public static class ServiceCollectionDevspacesExtensions + { + public static IServiceCollection AddDevspaces(this IServiceCollection services) + { + services.AddTransient(); + return services; + } + } +} diff --git a/src/BuildingBlocks/EventBus/EventBus/Extensions/GenericTypeExtensions.cs b/src/BuildingBlocks/EventBus/EventBus/Extensions/GenericTypeExtensions.cs new file mode 100644 index 000000000..de5a2cb79 --- /dev/null +++ b/src/BuildingBlocks/EventBus/EventBus/Extensions/GenericTypeExtensions.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Extensions +{ + public static class GenericTypeExtensions + { + public static string GetGenericTypeName(this Type type) + { + var typeName = string.Empty; + + if (type.IsGenericType) + { + var genericTypes = string.Join(",", type.GetGenericArguments().Select(t => t.Name).ToArray()); + typeName = $"{type.Name.Remove(type.Name.IndexOf('`'))}<{genericTypes}>"; + } + else + { + typeName = type.Name; + } + + return typeName; + } + + public static string GetGenericTypeName(this object @object) + { + return @object.GetType().GetGenericTypeName(); + } + } +} diff --git a/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs b/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs index e789081f3..ea1aca61e 100644 --- a/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs +++ b/src/BuildingBlocks/EventBus/EventBus/InMemoryEventBusSubscriptionsManager.cs @@ -115,10 +115,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus private void RaiseOnEventRemoved(string eventName) { var handler = OnEventRemoved; - if (handler != null) - { - OnEventRemoved(this, eventName); - } + handler?.Invoke(this, eventName); } diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersisterConnection.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersistentConnection.cs similarity index 91% rename from src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersisterConnection.cs rename to src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersistentConnection.cs index 2e0555e61..93e5b2917 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersisterConnection.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersistentConnection.cs @@ -72,7 +72,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ .Or() .WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) => { - _logger.LogWarning(ex.ToString()); + _logger.LogWarning(ex, "RabbitMQ Client could not connect after {TimeOut}s ({ExceptionMessage})", $"{time.TotalSeconds:n1}", ex.Message); } ); @@ -88,7 +88,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ _connection.CallbackException += OnCallbackException; _connection.ConnectionBlocked += OnConnectionBlocked; - _logger.LogInformation($"RabbitMQ persistent connection acquired a connection {_connection.Endpoint.HostName} and is subscribed to failure events"); + _logger.LogInformation("RabbitMQ Client acquired a persistent connection to '{HostName}' and is subscribed to failure events", _connection.Endpoint.HostName); return true; } diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs index a3b6437ef..397b75017 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs @@ -2,6 +2,7 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Extensions; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -76,7 +77,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ .Or() .WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) => { - _logger.LogWarning(ex.ToString()); + _logger.LogWarning(ex, "Could not publish event: {EventId} after {Timeout}s ({ExceptionMessage})", @event.Id, $"{time.TotalSeconds:n1}", ex.Message); }); using (var channel = _persistentConnection.CreateModel()) @@ -97,7 +98,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ channel.BasicPublish(exchange: BROKER_NAME, routingKey: eventName, - mandatory:true, + mandatory: true, basicProperties: properties, body: body); }); @@ -107,8 +108,11 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ public void SubscribeDynamic(string eventName) where TH : IDynamicIntegrationEventHandler { + _logger.LogInformation("Subscribing to dynamic event {EventName} with {EventHandler}", eventName, typeof(TH).GetGenericTypeName()); + DoInternalSubscription(eventName); _subsManager.AddDynamicSubscription(eventName); + StartBasicConsume(); } public void Subscribe() @@ -117,7 +121,11 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ { var eventName = _subsManager.GetEventKey(); DoInternalSubscription(eventName); + + _logger.LogInformation("Subscribing to event {EventName} with {EventHandler}", eventName, typeof(TH).GetGenericTypeName()); + _subsManager.AddSubscription(); + StartBasicConsume(); } private void DoInternalSubscription(string eventName) @@ -140,9 +148,13 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ } public void Unsubscribe() - where TH : IIntegrationEventHandler where T : IntegrationEvent + where TH : IIntegrationEventHandler { + var eventName = _subsManager.GetEventKey(); + + _logger.LogInformation("Unsubscribing from event {EventName}", eventName); + _subsManager.RemoveSubscription(); } @@ -162,6 +174,50 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ _subsManager.Clear(); } + private void StartBasicConsume() + { + if (_consumerChannel != null) + { + var consumer = new AsyncEventingBasicConsumer(_consumerChannel); + + consumer.Received += Consumer_Received; + + _consumerChannel.BasicConsume( + queue: _queueName, + autoAck: false, + consumer: consumer); + } + else + { + _logger.LogError("StartBasicConsume can't call on _consumerChannel == null"); + } + } + + private async Task Consumer_Received(object sender, BasicDeliverEventArgs eventArgs) + { + var eventName = eventArgs.RoutingKey; + var message = Encoding.UTF8.GetString(eventArgs.Body); + + try + { + if (message.ToLowerInvariant().Contains("throw-fake-exception")) + { + throw new InvalidOperationException($"Fake exception requested: \"{message}\""); + } + + await ProcessEvent(eventName, message); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "----- ERROR Processing message \"{Message}\"", message); + } + + // Even on exception we take the message off the queue. + // in a REAL WORLD app this should be handled with a Dead Letter Exchange (DLX). + // For more information see: https://www.rabbitmq.com/dlx.html + _consumerChannel.BasicAck(eventArgs.DeliveryTag, multiple: false); + } + private IModel CreateConsumerChannel() { if (!_persistentConnection.IsConnected) @@ -172,7 +228,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ var channel = _persistentConnection.CreateModel(); channel.ExchangeDeclare(exchange: BROKER_NAME, - type: "direct"); + type: "direct"); channel.QueueDeclare(queue: _queueName, durable: true, @@ -180,26 +236,11 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ autoDelete: false, arguments: null); - - var consumer = new EventingBasicConsumer(channel); - consumer.Received += async (model, ea) => - { - var eventName = ea.RoutingKey; - var message = Encoding.UTF8.GetString(ea.Body); - - await ProcessEvent(eventName, message); - - channel.BasicAck(ea.DeliveryTag,multiple:false); - }; - - channel.BasicConsume(queue: _queueName, - autoAck: false, - consumer: consumer); - channel.CallbackException += (sender, ea) => { _consumerChannel.Dispose(); _consumerChannel = CreateConsumerChannel(); + StartBasicConsume(); }; return channel; @@ -215,7 +256,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ foreach (var subscription in subscriptions) { if (subscription.IsDynamic) - { + { var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler; if (handler == null) continue; dynamic eventData = JObject.Parse(message); diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj index 6e0215c29..62373d1b3 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/IRabbitMQPersisterConnection.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/IRabbitMQPersistentConnection.cs similarity index 100% rename from src/BuildingBlocks/EventBus/EventBusRabbitMQ/IRabbitMQPersisterConnection.cs rename to src/BuildingBlocks/EventBus/EventBusRabbitMQ/IRabbitMQPersistentConnection.cs diff --git a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs index d16eb4625..cd2dc557f 100644 --- a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs +++ b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs @@ -27,7 +27,7 @@ ILifetimeScope autofac) { _serviceBusPersisterConnection = serviceBusPersisterConnection; - _logger = logger; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager(); _subscriptionClient = new SubscriptionClient(serviceBusPersisterConnection.ServiceBusConnectionStringBuilder, @@ -61,6 +61,8 @@ public void SubscribeDynamic(string eventName) where TH : IDynamicIntegrationEventHandler { + _logger.LogInformation("Subscribing to dynamic event {EventName} with {EventHandler}", eventName, nameof(TH)); + _subsManager.AddDynamicSubscription(eventName); } @@ -83,10 +85,12 @@ } catch (ServiceBusException) { - _logger.LogInformation($"The messaging entity {eventName} already exists."); + _logger.LogWarning("The messaging entity {eventName} already exists.", eventName); } } + _logger.LogInformation("Subscribing to event {EventName} with {EventHandler}", eventName, nameof(TH)); + _subsManager.AddSubscription(); } @@ -105,15 +109,19 @@ } catch (MessagingEntityNotFoundException) { - _logger.LogInformation($"The messaging entity {eventName} Could not be found."); + _logger.LogWarning("The messaging entity {eventName} Could not be found.", eventName); } + _logger.LogInformation("Unsubscribing from event {EventName}", eventName); + _subsManager.RemoveSubscription(); } public void UnsubscribeDynamic(string eventName) where TH : IDynamicIntegrationEventHandler { + _logger.LogInformation("Unsubscribing from dynamic event {EventName}", eventName); + _subsManager.RemoveDynamicSubscription(eventName); } @@ -136,17 +144,16 @@ await _subscriptionClient.CompleteAsync(message.SystemProperties.LockToken); } }, - new MessageHandlerOptions(ExceptionReceivedHandler) { MaxConcurrentCalls = 10, AutoComplete = false }); + new MessageHandlerOptions(ExceptionReceivedHandler) { MaxConcurrentCalls = 10, AutoComplete = false }); } private Task ExceptionReceivedHandler(ExceptionReceivedEventArgs exceptionReceivedEventArgs) { - Console.WriteLine($"Message handler encountered an exception {exceptionReceivedEventArgs.Exception}."); + var ex = exceptionReceivedEventArgs.Exception; var context = exceptionReceivedEventArgs.ExceptionReceivedContext; - Console.WriteLine("Exception context for troubleshooting:"); - Console.WriteLine($"- Endpoint: {context.Endpoint}"); - Console.WriteLine($"- Entity Path: {context.EntityPath}"); - Console.WriteLine($"- Executing Action: {context.Action}"); + + _logger.LogError(ex, "ERROR handling message: {ExceptionMessage} - Context: {@ExceptionContext}", ex.Message, context); + return Task.CompletedTask; } @@ -172,7 +179,7 @@ var handler = scope.ResolveOptional(subscription.HandlerType); if (handler == null) continue; var eventType = _subsManager.GetEventTypeByName(eventName); - var integrationEvent = JsonConvert.DeserializeObject(message, eventType); + var integrationEvent = JsonConvert.DeserializeObject(message, eventType); var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType); await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent }); } @@ -194,7 +201,7 @@ } catch (MessagingEntityNotFoundException) { - _logger.LogInformation($"The messaging entity { RuleDescription.DefaultRuleName } Could not be found."); + _logger.LogWarning("The messaging entity {DefaultRuleName} Could not be found.", RuleDescription.DefaultRuleName); } } } diff --git a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj index 288c4256a..3de07e329 100644 --- a/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj +++ b/src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj index 731953225..ef3463cca 100644 --- a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj +++ b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj @@ -6,10 +6,10 @@ - - - - + + + + diff --git a/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs b/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs index 0da18f581..fa06e0a0b 100644 --- a/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs +++ b/src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs @@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Hosting try { - logger.LogInformation($"Migrating database associated with context {typeof(TContext).Name}"); + logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name); if (underK8s) { @@ -55,11 +55,11 @@ namespace Microsoft.AspNetCore.Hosting retry.Execute(() => InvokeSeeder(seeder, context, services)); } - logger.LogInformation($"Migrated database associated with context {typeof(TContext).Name}"); + logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name); } catch (Exception ex) { - logger.LogError(ex, $"An error occurred while migrating the database used on context {typeof(TContext).Name}"); + logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name); if (underK8s) { throw; // Rethrow under k8s because we rely on k8s to re-run the pod diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.iOS/eShopOnContainers.TestRunner.iOS.csproj.bak b/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.iOS/eShopOnContainers.TestRunner.iOS.csproj.bak deleted file mode 100644 index 0d45fd280..000000000 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.TestRunner.iOS/eShopOnContainers.TestRunner.iOS.csproj.bak +++ /dev/null @@ -1,175 +0,0 @@ - - - - Debug - iPhoneSimulator - {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3} - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Exe - eShopOnContainers.TestRunner.iOS - Resources - eShopOnContainers.TestRunner.iOS - - - - - true - full - false - bin\iPhoneSimulator\Debug - DEBUG - prompt - 4 - false - x86_64 - SdkOnly - True - 10.1 - False - False - False - False - False - False - False - True - Default - HttpClientHandler - False - - - none - true - bin\iPhoneSimulator\Release - prompt - 4 - None - x86_64 - false - - - true - full - false - bin\iPhone\Debug - DEBUG - prompt - 4 - false - ARMv7, ARM64 - Entitlements.plist - iPhone Developer - true - - - none - true - bin\iPhone\Release - prompt - 4 - Entitlements.plist - ARMv7, ARM64 - false - iPhone Developer - - - none - True - bin\iPhone\Ad-Hoc - prompt - 4 - False - ARMv7, ARM64 - Entitlements.plist - True - Automatic:AdHoc - iPhone Distribution - - - none - True - bin\iPhone\AppStore - prompt - 4 - False - ARMv7, ARM64 - Entitlements.plist - Automatic:AppStore - iPhone Distribution - - - - - - - - - - - - - - - - ..\..\packages\Xamarin.Forms.2.3.3.166-pre4\lib\Xamarin.iOS10\Xamarin.Forms.Core.dll - True - - - ..\..\packages\Xamarin.Forms.2.3.3.166-pre4\lib\Xamarin.iOS10\Xamarin.Forms.Platform.dll - True - - - ..\..\packages\Xamarin.Forms.2.3.3.166-pre4\lib\Xamarin.iOS10\Xamarin.Forms.Platform.iOS.dll - True - - - ..\..\packages\Xamarin.Forms.2.3.3.166-pre4\lib\Xamarin.iOS10\Xamarin.Forms.Xaml.dll - True - - - - ..\..\packages\xunit.abstractions.2.0.1\lib\netstandard1.0\xunit.abstractions.dll - True - - - ..\..\packages\xunit.assert.2.2.0-beta4-build3444\lib\netstandard1.0\xunit.assert.dll - True - - - ..\..\packages\xunit.extensibility.core.2.2.0-beta4-build3444\lib\netstandard1.0\xunit.core.dll - True - - - ..\..\packages\xunit.extensibility.execution.2.2.0-beta4-build3444\lib\netstandard1.0\xunit.execution.dotnet.dll - True - - - ..\..\packages\xunit.runner.devices.2.1.0\lib\Xamarin.iOS\xunit.runner.devices.dll - True - - - ..\..\packages\xunit.runner.utility.2.2.0-beta4-build3444\lib\netstandard1.1\xunit.runner.utility.dotnet.dll - True - - - - - - - - - {f7b6a162-bc4d-4924-b16a-713f9b0344e7} - eShopOnContainers.UnitTests - - - - - - - Este proyecto hace referencia a los paquetes NuGet que faltan en este equipo. Use la restauración de paquetes NuGet para descargarlos. Para obtener más información, consulte http://go.microsoft.com/fwlink/?LinkID=322105. El archivo que falta es {0}. - - - - - - \ No newline at end of file diff --git a/src/Services/Basket/Basket.API/.dockerignore b/src/Services/Basket/Basket.API/.dockerignore new file mode 100644 index 000000000..04f7b133d --- /dev/null +++ b/src/Services/Basket/Basket.API/.dockerignore @@ -0,0 +1,14 @@ +.dockerignore +.git +.gitignore +.vs +.vscode +**/*.*proj.user +**/azds.yaml +**/bin +**/charts +**/Dockerfile +**/Dockerfile.develop +**/obj +**/secrets.dev.yaml +**/values.dev.yaml \ No newline at end of file diff --git a/src/Services/Basket/Basket.API/Basket.API.csproj b/src/Services/Basket/Basket.API/Basket.API.csproj index 4add803b6..d5fc0a301 100644 --- a/src/Services/Basket/Basket.API/Basket.API.csproj +++ b/src/Services/Basket/Basket.API/Basket.API.csproj @@ -13,25 +13,29 @@ - - + + + - - + + + + + - - - - + + + + + - diff --git a/src/Services/Basket/Basket.API/Controllers/BasketController.cs b/src/Services/Basket/Basket.API/Controllers/BasketController.cs index 0e15e65dd..69f601579 100644 --- a/src/Services/Basket/Basket.API/Controllers/BasketController.cs +++ b/src/Services/Basket/Basket.API/Controllers/BasketController.cs @@ -5,6 +5,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.Services.Basket.API.Model; using Microsoft.eShopOnContainers.Services.Basket.API.Services; +using Microsoft.Extensions.Logging; +using Serilog.Context; using System; using System.Net; using System.Threading.Tasks; @@ -19,9 +21,15 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers private readonly IBasketRepository _repository; private readonly IIdentityService _identityService; private readonly IEventBus _eventBus; + private readonly ILogger _logger; - public BasketController(IBasketRepository repository, IIdentityService identityService, IEventBus eventBus) + public BasketController( + ILogger logger, + IBasketRepository repository, + IIdentityService identityService, + IEventBus eventBus) { + _logger = logger; _repository = repository; _identityService = identityService; _eventBus = eventBus; @@ -33,14 +41,14 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers { var basket = await _repository.GetBasketAsync(id); - return basket ?? new CustomerBasket(id); + return Ok(basket ?? new CustomerBasket(id)); } [HttpPost] [ProducesResponseType(typeof(CustomerBasket), (int)HttpStatusCode.OK)] public async Task> UpdateBasketAsync([FromBody]CustomerBasket value) { - return await _repository.UpdateBasketAsync(value); + return Ok(await _repository.UpdateBasketAsync(value)); } [Route("checkout")] @@ -50,7 +58,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers public async Task CheckoutAsync([FromBody]BasketCheckout basketCheckout, [FromHeader(Name = "x-requestid")] string requestId) { var userId = _identityService.GetUserIdentity(); - + basketCheckout.RequestId = (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) ? guid : basketCheckout.RequestId; @@ -70,7 +78,18 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers // Once basket is checkout, sends an integration event to // ordering.api to convert basket to order and proceeds with // order creation process - _eventBus.Publish(eventMessage); + try + { + _logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", eventMessage.Id, Program.AppName, eventMessage); + + _eventBus.Publish(eventMessage); + } + catch (Exception ex) + { + _logger.LogError(ex, "ERROR Publishing integration event: {IntegrationEventId} from {AppName}", eventMessage.Id, Program.AppName); + + throw; + } return Accepted(); } diff --git a/src/Services/Basket/Basket.API/Dockerfile b/src/Services/Basket/Basket.API/Dockerfile index a5ccd56f5..81955e178 100644 --- a/src/Services/Basket/Basket.API/Dockerfile +++ b/src/Services/Basket/Basket.API/Dockerfile @@ -1,22 +1,32 @@ -FROM microsoft/dotnet:2.2.0-aspnetcore-runtime AS base +FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.2.100-sdk AS build +FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build WORKDIR /src + +COPY scripts scripts/ + +COPY src/ApiGateways/*/*.csproj /src/csproj-files/ +COPY src/ApiGateways/*/*/*.csproj /src/csproj-files/ +COPY src/BuildingBlocks/*/*/*.csproj /src/csproj-files/ +COPY src/Services/*/*/*.csproj /src/csproj-files/ +COPY src/Web/*/*.csproj /src/csproj-files/ + +ARG RUN=pwd +RUN ${RUN} + COPY . . WORKDIR /src/src/Services/Basket/Basket.API -RUN dotnet restore -nowarn:msb3202,nu1503 -RUN dotnet build --no-restore -c Release -o /app - -FROM build as functionaltest -WORKDIR /src/src/Services/Basket/Basket.FunctionalTests +RUN dotnet publish -c Release -o /app FROM build as unittest WORKDIR /src/src/Services/Basket/Basket.UnitTests +FROM build as functionaltest +WORKDIR /src/src/Services/Basket/Basket.FunctionalTests + FROM build AS publish -RUN dotnet publish --no-restore -c Release -o /app FROM base AS final WORKDIR /app diff --git a/src/Services/Basket/Basket.API/Dockerfile.develop b/src/Services/Basket/Basket.API/Dockerfile.develop new file mode 100644 index 000000000..5ae5cc235 --- /dev/null +++ b/src/Services/Basket/Basket.API/Dockerfile.develop @@ -0,0 +1,19 @@ +FROM microsoft/dotnet:2.2-sdk +ARG BUILD_CONFIGURATION=Debug +ENV ASPNETCORE_ENVIRONMENT=Development +ENV DOTNET_USE_POLLING_FILE_WATCHER=true +EXPOSE 80 + +WORKDIR /src + +COPY ["src/BuildingBlocks/EventBus/EventBus/EventBus.csproj", "src/BuildingBlocks/EventBus/EventBus/"] +COPY ["src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj", "src/BuildingBlocks/EventBus/EventBusRabbitMQ/"] +COPY ["src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj", "src/BuildingBlocks/EventBus/EventBusServiceBus/"] +COPY ["src/Services/Basket/Basket.API/Basket.API.csproj", "src/Services/Basket/Basket.API/"] + +RUN dotnet restore src/Services/Basket/Basket.API/Basket.API.csproj -nowarn:msb3202,nu1503 +COPY . . +WORKDIR /src/src/Services/Basket/Basket.API +RUN dotnet build --no-restore -c $BUILD_CONFIGURATION + +ENTRYPOINT ["dotnet", "run", "--no-build", "--no-launch-profile", "-c", "$BUILD_CONFIGURATION", "--"] \ No newline at end of file diff --git a/src/Services/Basket/Basket.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs b/src/Services/Basket/Basket.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs index 7caa9740d..f1c7b08d0 100644 --- a/src/Services/Basket/Basket.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs +++ b/src/Services/Basket/Basket.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs @@ -19,11 +19,13 @@ namespace Basket.API.Infrastructure.Filters operation.Responses.Add("401", new Response { Description = "Unauthorized" }); operation.Responses.Add("403", new Response { Description = "Forbidden" }); - operation.Security = new List>>(); - operation.Security.Add(new Dictionary> + operation.Security = new List>> { - { "oauth2", new [] { "basketapi" } } - }); + new Dictionary> + { + { "oauth2", new [] { "basketapi" } } + } + }; } } } diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs index 19ae1b594..cb7b6a2d6 100644 --- a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs +++ b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs @@ -1,6 +1,9 @@ using Basket.API.IntegrationEvents.Events; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.Services.Basket.API; using Microsoft.eShopOnContainers.Services.Basket.API.Model; +using Microsoft.Extensions.Logging; +using Serilog.Context; using System; using System.Threading.Tasks; @@ -9,15 +12,24 @@ namespace Basket.API.IntegrationEvents.EventHandling public class OrderStartedIntegrationEventHandler : IIntegrationEventHandler { private readonly IBasketRepository _repository; + private readonly ILogger _logger; - public OrderStartedIntegrationEventHandler(IBasketRepository repository) + public OrderStartedIntegrationEventHandler( + IBasketRepository repository, + ILogger logger) { _repository = repository ?? throw new ArgumentNullException(nameof(repository)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task Handle(OrderStartedIntegrationEvent @event) { - await _repository.DeleteBasketAsync(@event.UserId.ToString()); + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) + { + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); + + await _repository.DeleteBasketAsync(@event.UserId.ToString()); + } } } } diff --git a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs index 546483b40..c27200e6f 100644 --- a/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs +++ b/src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs @@ -1,6 +1,8 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events; using Microsoft.eShopOnContainers.Services.Basket.API.Model; +using Microsoft.Extensions.Logging; +using Serilog.Context; using System; using System.Linq; using System.Threading.Tasks; @@ -9,22 +11,31 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Even { public class ProductPriceChangedIntegrationEventHandler : IIntegrationEventHandler { + private readonly ILogger _logger; private readonly IBasketRepository _repository; - public ProductPriceChangedIntegrationEventHandler(IBasketRepository repository) + public ProductPriceChangedIntegrationEventHandler( + ILogger logger, + IBasketRepository repository) { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _repository = repository ?? throw new ArgumentNullException(nameof(repository)); } public async Task Handle(ProductPriceChangedIntegrationEvent @event) { - var userIds = _repository.GetUsers(); - - foreach (var id in userIds) + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) { - var basket = await _repository.GetBasketAsync(id); + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); - await UpdatePriceInBasketItems(@event.ProductId, @event.NewPrice, @event.OldPrice, basket); + var userIds = _repository.GetUsers(); + + foreach (var id in userIds) + { + var basket = await _repository.GetBasketAsync(id); + + await UpdatePriceInBasketItems(@event.ProductId, @event.NewPrice, @event.OldPrice, basket); + } } } @@ -35,17 +46,19 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Even if (itemsToUpdate != null) { + _logger.LogInformation("----- ProductPriceChangedIntegrationEventHandler - Updating items in basket for user: {BuyerId} ({@Items})", basket.BuyerId, itemsToUpdate); + foreach (var item in itemsToUpdate) { - if(item.UnitPrice == oldPrice) - { + if (item.UnitPrice == oldPrice) + { var originalPrice = item.UnitPrice; item.UnitPrice = newPrice; item.OldUnitPrice = originalPrice; } } await _repository.UpdateBasketAsync(basket); - } + } } } } diff --git a/src/Services/Basket/Basket.API/Program.cs b/src/Services/Basket/Basket.API/Program.cs index e4328645e..40cc0eebc 100644 --- a/src/Services/Basket/Basket.API/Program.cs +++ b/src/Services/Basket/Basket.API/Program.cs @@ -12,51 +12,81 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API { public class Program { - public static void Main(string[] args) + public static readonly string Namespace = typeof(Program).Namespace; + public static readonly string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); + + public static int Main(string[] args) { - BuildWebHost(args).Run(); + var configuration = GetConfiguration(); + + Log.Logger = CreateSerilogLogger(configuration); + + try + { + Log.Information("Configuring web host ({ApplicationContext})...", AppName); + var host = BuildWebHost(configuration, args); + + Log.Information("Starting web host ({ApplicationContext})...", AppName); + host.Run(); + + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName); + return 1; + } + finally + { + Log.CloseAndFlush(); + } } - public static IWebHost BuildWebHost(string[] args) => + private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) => WebHost.CreateDefaultBuilder(args) + .CaptureStartupErrors(false) .UseFailing(options => - { - options.ConfigPath = "/Failing"; - }) - .UseContentRoot(Directory.GetCurrentDirectory()) + options.ConfigPath = "/Failing") .UseStartup() - .ConfigureAppConfiguration((builderContext, config) => - { - var builtConfig = config.Build(); - - var configurationBuilder = new ConfigurationBuilder(); - - if (Convert.ToBoolean(builtConfig["UseVault"])) - { - configurationBuilder.AddAzureKeyVault( - $"https://{builtConfig["Vault:Name"]}.vault.azure.net/", - builtConfig["Vault:ClientId"], - builtConfig["Vault:ClientSecret"]); - } - - configurationBuilder.AddEnvironmentVariables(); - - config.AddConfiguration(configurationBuilder.Build()); - }) - .ConfigureLogging((hostingContext, builder) => - { - builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - builder.AddConsole(); - builder.AddDebug(); - }) - .UseSerilog((builderContext, config) => - { - config - .MinimumLevel.Information() - .Enrich.FromLogContext() - .WriteTo.Console(); - }) .UseApplicationInsights() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseConfiguration(configuration) + .UseSerilog() .Build(); + + private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) + { + var seqServerUrl = configuration["Serilog:SeqServerUrl"]; + var logstashUrl = configuration["Serilog:LogstashgUrl"]; + return new LoggerConfiguration() + .MinimumLevel.Verbose() + .Enrich.WithProperty("ApplicationContext", AppName) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) + .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl) + .ReadFrom.Configuration(configuration) + .CreateLogger(); + } + + private static IConfiguration GetConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddEnvironmentVariables(); + + var config = builder.Build(); + + if (config.GetValue("UseVault", false)) + { + builder.AddAzureKeyVault( + $"https://{config["Vault:Name"]}.vault.azure.net/", + config["Vault:ClientId"], + config["Vault:ClientSecret"]); + } + + return builder.Build(); + } } } diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index eecc8bdd4..97ede1879 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -50,7 +50,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API // This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) { - RegisterAppInsights(services); + RegisterAppInsights(services); // Add framework services. services.AddMvc(options => @@ -66,7 +66,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API services.AddCustomHealthCheck(Configuration); - services.Configure(Configuration); + services.Configure(Configuration); //By connecting here we are making sure that our service //cannot start until redis is ready. This might slow down startup, @@ -105,7 +105,8 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API var factory = new ConnectionFactory() { - HostName = Configuration["EventBusConnection"] + HostName = Configuration["EventBusConnection"], + DispatchConsumersAsync = true }; if (!string.IsNullOrEmpty(Configuration["EventBusUserName"])) @@ -181,8 +182,8 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { - loggerFactory.AddAzureWebAppDiagnostics(); - loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); + //loggerFactory.AddAzureWebAppDiagnostics(); + //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); var pathBase = Configuration["PATH_BASE"]; if (!string.IsNullOrEmpty(pathBase)) @@ -228,7 +229,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API if (orchestratorType?.ToUpper() == "K8S") { // Enable K8s telemetry initializer - services.EnableKubernetes(); + services.AddApplicationInsightsKubernetesEnricher(); } if (orchestratorType?.ToUpper() == "SF") { diff --git a/src/Services/Basket/Basket.API/appsettings.json b/src/Services/Basket/Basket.API/appsettings.json index 4bff4d70d..fc8fc544a 100644 --- a/src/Services/Basket/Basket.API/appsettings.json +++ b/src/Services/Basket/Basket.API/appsettings.json @@ -1,10 +1,14 @@ { - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" + "Serilog": { + "SeqServerUrl": null, + "LogstashgUrl": null, + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.eShopOnContainers": "Information", + "System": "Warning" + } } }, "IdentityUrl": "http://localhost:5105", diff --git a/src/Services/Basket/Basket.API/azds.yaml b/src/Services/Basket/Basket.API/azds.yaml new file mode 100644 index 000000000..4fbbb7be4 --- /dev/null +++ b/src/Services/Basket/Basket.API/azds.yaml @@ -0,0 +1,56 @@ +kind: helm-release +apiVersion: 1.1 +build: + context: ..\..\..\.. + dockerfile: Dockerfile +install: + chart: ../../../../k8s/helm/basket-api + set: + replicaCount: 1 + image: + tag: $(tag) + pullPolicy: Never + ingress: + annotations: + kubernetes.io/ingress.class: traefik-azds + hosts: + - $(spacePrefix)eshop$(hostSuffix) + inf: + k8s: + dns: $(spacePrefix)eshop$(hostSuffix) + values: + - values.dev.yaml? + - secrets.dev.yaml? + - inf.yaml + - app.yaml +configurations: + develop: + build: + useGitIgnore: true + dockerfile: Dockerfile.develop + args: + BUILD_CONFIGURATION: ${BUILD_CONFIGURATION:-Debug} + container: + sync: + - '**/Pages/**' + - '**/Views/**' + - '**/wwwroot/**' + - '!**/*.{sln,csproj}' + command: + - dotnet + - run + - --no-restore + - --no-build + - --no-launch-profile + - -c + - ${BUILD_CONFIGURATION:-Debug} + iterate: + processesToKill: + - dotnet + - vsdbg + buildCommands: + - - dotnet + - build + - --no-restore + - -c + - ${BUILD_CONFIGURATION:-Debug} diff --git a/src/Services/Basket/Basket.API/values.dev.yaml b/src/Services/Basket/Basket.API/values.dev.yaml new file mode 100644 index 000000000..eaed45149 --- /dev/null +++ b/src/Services/Basket/Basket.API/values.dev.yaml @@ -0,0 +1,3 @@ +ingress: + enabled: true + tls: [] \ No newline at end of file diff --git a/src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj b/src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj index 2a2635354..4fd8cb9ec 100644 --- a/src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj +++ b/src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj @@ -17,12 +17,17 @@ - + + + - - + + + all + runtime; build; native; contentfiles; analyzers + - + diff --git a/src/Services/Basket/Basket.FunctionalTests/BasketScenarios.cs b/src/Services/Basket/Basket.FunctionalTests/BasketScenarios.cs index 5c7ecdef5..83d507ade 100644 --- a/src/Services/Basket/Basket.FunctionalTests/BasketScenarios.cs +++ b/src/Services/Basket/Basket.FunctionalTests/BasketScenarios.cs @@ -5,7 +5,7 @@ using System; using System.Net.Http; using System.Text; using System.Threading.Tasks; -using WebMVC.Models; +using WebMVC.Services.ModelDTOs; using Xunit; namespace Basket.FunctionalTests diff --git a/src/Services/Basket/Basket.UnitTests/Application/BasketWebApiTest.cs b/src/Services/Basket/Basket.UnitTests/Application/BasketWebApiTest.cs index a2e382b92..2864b8b9f 100644 --- a/src/Services/Basket/Basket.UnitTests/Application/BasketWebApiTest.cs +++ b/src/Services/Basket/Basket.UnitTests/Application/BasketWebApiTest.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.Services.Basket.API.Controllers; using Microsoft.eShopOnContainers.Services.Basket.API.Model; +using Microsoft.Extensions.Logging; using Moq; using System; using System.Collections.Generic; @@ -20,12 +21,14 @@ namespace UnitTest.Basket.Application private readonly Mock _basketRepositoryMock; private readonly Mock _identityServiceMock; private readonly Mock _serviceBusMock; + private readonly Mock> _loggerMock; public BasketWebApiTest() { _basketRepositoryMock = new Mock(); _identityServiceMock = new Mock(); _serviceBusMock = new Mock(); + _loggerMock = new Mock>(); } [Fact] @@ -43,12 +46,16 @@ namespace UnitTest.Basket.Application //Act var basketController = new BasketController( - _basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object); + _loggerMock.Object, + _basketRepositoryMock.Object, + _identityServiceMock.Object, + _serviceBusMock.Object); + var actionResult = await basketController.GetBasketByIdAsync(fakeCustomerId); //Assert Assert.Equal((actionResult.Result as OkObjectResult).StatusCode, (int)System.Net.HttpStatusCode.OK); - Assert.Equal(((CustomerBasket)actionResult.Value).BuyerId, fakeCustomerId); + Assert.Equal((((ObjectResult)actionResult.Result).Value as CustomerBasket).BuyerId, fakeCustomerId); } [Fact] @@ -65,14 +72,17 @@ namespace UnitTest.Basket.Application //Act var basketController = new BasketController( - _basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object); + _loggerMock.Object, + _basketRepositoryMock.Object, + _identityServiceMock.Object, + _serviceBusMock.Object); var actionResult = await basketController.UpdateBasketAsync(fakeCustomerBasket); //Assert Assert.Equal((actionResult.Result as OkObjectResult).StatusCode, (int)System.Net.HttpStatusCode.OK); - Assert.Equal(((CustomerBasket)actionResult.Value).BuyerId, fakeCustomerId); - } + Assert.Equal((((ObjectResult)actionResult.Result).Value as CustomerBasket).BuyerId, fakeCustomerId); + } [Fact] public async Task Doing_Checkout_Without_Basket_Should_Return_Bad_Request() @@ -84,7 +94,10 @@ namespace UnitTest.Basket.Application //Act var basketController = new BasketController( - _basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object); + _loggerMock.Object, + _basketRepositoryMock.Object, + _identityServiceMock.Object, + _serviceBusMock.Object); var result = await basketController.CheckoutAsync(new BasketCheckout(), Guid.NewGuid().ToString()) as BadRequestResult; Assert.NotNull(result); @@ -102,7 +115,10 @@ namespace UnitTest.Basket.Application _identityServiceMock.Setup(x => x.GetUserIdentity()).Returns(fakeCustomerId); var basketController = new BasketController( - _basketRepositoryMock.Object, _identityServiceMock.Object, _serviceBusMock.Object); + _loggerMock.Object, + _basketRepositoryMock.Object, + _identityServiceMock.Object, + _serviceBusMock.Object); basketController.ControllerContext = new ControllerContext() { @@ -122,7 +138,7 @@ namespace UnitTest.Basket.Application } private CustomerBasket GetCustomerBasketFake(string fakeCustomerId) - { + { return new CustomerBasket(fakeCustomerId) { Items = new List() diff --git a/src/Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj b/src/Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj index 071bbf52b..27a851767 100644 --- a/src/Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj +++ b/src/Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj @@ -7,12 +7,17 @@ - - - + + + + + + all + runtime; build; native; contentfiles; analyzers + - + diff --git a/src/Services/Catalog/Catalog.API/Catalog.API.csproj b/src/Services/Catalog/Catalog.API/Catalog.API.csproj index a2be6b522..6150179ee 100644 --- a/src/Services/Catalog/Catalog.API/Catalog.API.csproj +++ b/src/Services/Catalog/Catalog.API/Catalog.API.csproj @@ -34,24 +34,26 @@ - + - - + + + - - - + + + + diff --git a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs index d9fa4002e..5dfb10a96 100644 --- a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs +++ b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs @@ -60,6 +60,15 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers .Take(pageSize) .ToListAsync(); + /* The "awesome" fix for testing Devspaces */ + + /* + foreach (var pr in itemsOnPage) { + pr.Name = "Awesome " + pr.Name; + } + + */ + itemsOnPage = ChangeUriPlaceholder(itemsOnPage); var model = new PaginatedItemsViewModel(pageIndex, pageSize, totalItems, itemsOnPage); diff --git a/src/Services/Catalog/Catalog.API/Dockerfile b/src/Services/Catalog/Catalog.API/Dockerfile index a9fefb765..70a97da27 100644 --- a/src/Services/Catalog/Catalog.API/Dockerfile +++ b/src/Services/Catalog/Catalog.API/Dockerfile @@ -1,13 +1,24 @@ -FROM microsoft/dotnet:2.2.0-aspnetcore-runtime AS base +FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.2.100-sdk AS build +FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build WORKDIR /src + +COPY scripts scripts/ + +COPY src/ApiGateways/*/*.csproj /src/csproj-files/ +COPY src/ApiGateways/*/*/*.csproj /src/csproj-files/ +COPY src/BuildingBlocks/*/*/*.csproj /src/csproj-files/ +COPY src/Services/*/*/*.csproj /src/csproj-files/ +COPY src/Web/*/*.csproj /src/csproj-files/ + +ARG RUN=pwd +RUN ${RUN} + COPY . . WORKDIR /src/src/Services/Catalog/Catalog.API -RUN dotnet restore -nowarn:msb3202,nu1503 -RUN dotnet build --no-restore -c Release -o /app +RUN dotnet publish -c Release -o /app FROM build as unittest WORKDIR /src/src/Services/Catalog/Catalog.UnitTests @@ -16,7 +27,6 @@ FROM build as functionaltest WORKDIR /src/src/Services/Catalog/Catalog.FunctionalTests FROM build AS publish -RUN dotnet publish --no-restore -c Release -o /app FROM base AS final WORKDIR /app diff --git a/src/Services/Catalog/Catalog.API/Dockerfile.develop b/src/Services/Catalog/Catalog.API/Dockerfile.develop new file mode 100644 index 000000000..11034a2bc --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Dockerfile.develop @@ -0,0 +1,21 @@ +FROM microsoft/dotnet:2.2-sdk +ARG BUILD_CONFIGURATION=Debug +ENV ASPNETCORE_ENVIRONMENT=Development +ENV DOTNET_USE_POLLING_FILE_WATCHER=true +EXPOSE 80 + +WORKDIR /src + +COPY ["src/BuildingBlocks/EventBus/EventBus/EventBus.csproj", "src/BuildingBlocks/EventBus/EventBus/"] +COPY ["src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj", "src/BuildingBlocks/EventBus/EventBusRabbitMQ/"] +COPY ["src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj", "src/BuildingBlocks/EventBus/EventBusServiceBus/"] +COPY ["src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj", "src/BuildingBlocks/EventBus/IntegrationEventLogEF/"] +COPY ["src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj", "src/BuildingBlocks/WebHostCustomization/WebHost.Customization/"] +COPY ["src/Services/Catalog/Catalog.API/Catalog.API.csproj", "src/Services/Catalog/Catalog.API/"] + +RUN dotnet restore src/Services/Catalog/Catalog.API/Catalog.API.csproj -nowarn:msb3202,nu1503 +COPY . . +WORKDIR "/src/src/Services/Catalog/Catalog.API" +RUN dotnet build --no-restore -c $BUILD_CONFIGURATION + +ENTRYPOINT ["dotnet", "run", "--no-build", "--no-launch-profile", "-c", "$BUILD_CONFIGURATION", "--"] \ No newline at end of file diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs index e7ea1e09d..8bdd2a401 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContextSeed.cs @@ -76,14 +76,14 @@ } catch (Exception ex) { - logger.LogError(ex.Message); + logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return GetPreconfiguredCatalogBrands(); } return File.ReadAllLines(csvFileCatalogBrands) .Skip(1) // skip header row .SelectTry(x => CreateCatalogBrand(x)) - .OnCaughtException(ex => { logger.LogError(ex.Message); return null; }) + .OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) .Where(x => x != null); } @@ -131,14 +131,14 @@ } catch (Exception ex) { - logger.LogError(ex.Message); + logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return GetPreconfiguredCatalogTypes(); } return File.ReadAllLines(csvFileCatalogTypes) .Skip(1) // skip header row .SelectTry(x => CreateCatalogType(x)) - .OnCaughtException(ex => { logger.LogError(ex.Message); return null; }) + .OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) .Where(x => x != null); } @@ -186,7 +186,7 @@ } catch (Exception ex) { - logger.LogError(ex.Message); + logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return GetPreconfiguredItems(); } @@ -197,7 +197,7 @@ .Skip(1) // skip header row .Select(row => Regex.Split(row, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)") ) .SelectTry(column => CreateCatalogItem(column, csvheaders, catalogTypeIdLookup, catalogBrandIdLookup)) - .OnCaughtException(ex => { logger.LogError(ex.Message); return null; }) + .OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) .Where(x => x != null); } @@ -377,7 +377,7 @@ sleepDurationProvider: retry => TimeSpan.FromSeconds(5), onRetry: (exception, timeSpan, retry, ctx) => { - logger.LogTrace($"[{prefix}] Exception {exception.GetType().Name} with message ${exception.Message} detected on attempt {retry} of {retries}"); + logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", prefix, exception.GetType().Name, exception.Message, retry, retries); } ); } diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs b/src/Services/Catalog/Catalog.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs index 4c4c746a6..1c1dfd45f 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs @@ -49,7 +49,7 @@ namespace Catalog.API.Infrastructure.Filters if (env.IsDevelopment()) { - json.DeveloperMeesage = context.Exception; + json.DeveloperMessage = context.Exception; } context.Result = new InternalServerErrorObjectResult(json); @@ -62,7 +62,7 @@ namespace Catalog.API.Infrastructure.Filters { public string[] Messages { get; set; } - public object DeveloperMeesage { get; set; } + public object DeveloperMessage { get; set; } } } } diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs b/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs index 8c550bf27..3b9476b9f 100644 --- a/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs @@ -4,7 +4,10 @@ using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities; +using Microsoft.eShopOnContainers.Services.Catalog.API; using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; +using Microsoft.Extensions.Logging; +using Serilog.Context; using System; using System.Data.Common; using System.Threading.Tasks; @@ -17,10 +20,15 @@ namespace Catalog.API.IntegrationEvents private readonly IEventBus _eventBus; private readonly CatalogContext _catalogContext; private readonly IIntegrationEventLogService _eventLogService; + private readonly ILogger _logger; - public CatalogIntegrationEventService(IEventBus eventBus, CatalogContext catalogContext, - Func integrationEventLogServiceFactory) + public CatalogIntegrationEventService( + ILogger logger, + IEventBus eventBus, + CatalogContext catalogContext, + Func integrationEventLogServiceFactory) { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _catalogContext = catalogContext ?? throw new ArgumentNullException(nameof(catalogContext)); _integrationEventLogServiceFactory = integrationEventLogServiceFactory ?? throw new ArgumentNullException(nameof(integrationEventLogServiceFactory)); _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); @@ -31,26 +39,31 @@ namespace Catalog.API.IntegrationEvents { try { + _logger.LogInformation("----- Publishing integration event: {IntegrationEventId_published} from {AppName} - ({@IntegrationEvent})", evt.Id, Program.AppName, evt); + await _eventLogService.MarkEventAsInProgressAsync(evt.Id); _eventBus.Publish(evt); await _eventLogService.MarkEventAsPublishedAsync(evt.Id); } - catch (Exception) + catch (Exception ex) { + _logger.LogError(ex, "ERROR Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", evt.Id, Program.AppName, evt); await _eventLogService.MarkEventAsFailedAsync(evt.Id); - } + } } public async Task SaveEventAndCatalogContextChangesAsync(IntegrationEvent evt) { + _logger.LogInformation("----- CatalogIntegrationEventService - Saving changes and integrationEvent: {IntegrationEventId}", evt.Id); + //Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction(): //See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency - await ResilientTransaction.New(_catalogContext) - .ExecuteAsync(async () => { - // Achieving atomicity between original catalog database operation and the IntegrationEventLog thanks to a local transaction - await _catalogContext.SaveChangesAsync(); - await _eventLogService.SaveEventAsync(evt, _catalogContext.Database.CurrentTransaction.GetDbTransaction()); - }); + await ResilientTransaction.New(_catalogContext).ExecuteAsync(async () => + { + // Achieving atomicity between original catalog database operation and the IntegrationEventLog thanks to a local transaction + await _catalogContext.SaveChangesAsync(); + await _eventLogService.SaveEventAsync(evt, _catalogContext.Database.CurrentTransaction.GetDbTransaction()); + }); } } } diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs b/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs index 0f30a3e0a..493a271cc 100644 --- a/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToAwaitingValidationIntegrationEventHandler.cs @@ -8,39 +8,51 @@ using System.Linq; using global::Catalog.API.IntegrationEvents; using IntegrationEvents.Events; + using Serilog.Context; + using Microsoft.Extensions.Logging; public class OrderStatusChangedToAwaitingValidationIntegrationEventHandler : IIntegrationEventHandler { private readonly CatalogContext _catalogContext; private readonly ICatalogIntegrationEventService _catalogIntegrationEventService; + private readonly ILogger _logger; - public OrderStatusChangedToAwaitingValidationIntegrationEventHandler(CatalogContext catalogContext, - ICatalogIntegrationEventService catalogIntegrationEventService) + public OrderStatusChangedToAwaitingValidationIntegrationEventHandler( + CatalogContext catalogContext, + ICatalogIntegrationEventService catalogIntegrationEventService, + ILogger logger) { _catalogContext = catalogContext; _catalogIntegrationEventService = catalogIntegrationEventService; + _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); } - public async Task Handle(OrderStatusChangedToAwaitingValidationIntegrationEvent command) + public async Task Handle(OrderStatusChangedToAwaitingValidationIntegrationEvent @event) { - var confirmedOrderStockItems = new List(); - - foreach (var orderStockItem in command.OrderStockItems) + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) { - var catalogItem = _catalogContext.CatalogItems.Find(orderStockItem.ProductId); - var hasStock = catalogItem.AvailableStock >= orderStockItem.Units; - var confirmedOrderStockItem = new ConfirmedOrderStockItem(catalogItem.Id, hasStock); + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); - confirmedOrderStockItems.Add(confirmedOrderStockItem); - } + var confirmedOrderStockItems = new List(); + + foreach (var orderStockItem in @event.OrderStockItems) + { + var catalogItem = _catalogContext.CatalogItems.Find(orderStockItem.ProductId); + var hasStock = catalogItem.AvailableStock >= orderStockItem.Units; + var confirmedOrderStockItem = new ConfirmedOrderStockItem(catalogItem.Id, hasStock); - var confirmedIntegrationEvent = confirmedOrderStockItems.Any(c => !c.HasStock) - ? (IntegrationEvent) new OrderStockRejectedIntegrationEvent(command.OrderId, confirmedOrderStockItems) - : new OrderStockConfirmedIntegrationEvent(command.OrderId); + confirmedOrderStockItems.Add(confirmedOrderStockItem); + } - await _catalogIntegrationEventService.SaveEventAndCatalogContextChangesAsync(confirmedIntegrationEvent); - await _catalogIntegrationEventService.PublishThroughEventBusAsync(confirmedIntegrationEvent); + var confirmedIntegrationEvent = confirmedOrderStockItems.Any(c => !c.HasStock) + ? (IntegrationEvent)new OrderStockRejectedIntegrationEvent(@event.OrderId, confirmedOrderStockItems) + : new OrderStockConfirmedIntegrationEvent(@event.OrderId); + + await _catalogIntegrationEventService.SaveEventAndCatalogContextChangesAsync(confirmedIntegrationEvent); + await _catalogIntegrationEventService.PublishThroughEventBusAsync(confirmedIntegrationEvent); + + } } } } \ No newline at end of file diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs b/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs index 0a45547f9..7d383254f 100644 --- a/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/OrderStatusChangedToPaidIntegrationEventHandler.cs @@ -4,28 +4,40 @@ using System.Threading.Tasks; using Infrastructure; using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events; + using Microsoft.Extensions.Logging; + using Serilog.Context; public class OrderStatusChangedToPaidIntegrationEventHandler : IIntegrationEventHandler { private readonly CatalogContext _catalogContext; + private readonly ILogger _logger; - public OrderStatusChangedToPaidIntegrationEventHandler(CatalogContext catalogContext) + public OrderStatusChangedToPaidIntegrationEventHandler( + CatalogContext catalogContext, + ILogger logger) { _catalogContext = catalogContext; + _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); } - public async Task Handle(OrderStatusChangedToPaidIntegrationEvent command) + public async Task Handle(OrderStatusChangedToPaidIntegrationEvent @event) { - //we're not blocking stock/inventory - foreach (var orderStockItem in command.OrderStockItems) + using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) { - var catalogItem = _catalogContext.CatalogItems.Find(orderStockItem.ProductId); + _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); - catalogItem.RemoveStock(orderStockItem.Units); - } + //we're not blocking stock/inventory + foreach (var orderStockItem in @event.OrderStockItems) + { + var catalogItem = _catalogContext.CatalogItems.Find(orderStockItem.ProductId); + + catalogItem.RemoveStock(orderStockItem.Units); + } - await _catalogContext.SaveChangesAsync(); + await _catalogContext.SaveChangesAsync(); + + } } } } \ No newline at end of file diff --git a/src/Services/Catalog/Catalog.API/Model/CatalogItem.cs b/src/Services/Catalog/Catalog.API/Model/CatalogItem.cs index 459c60098..2e4579f7f 100644 --- a/src/Services/Catalog/Catalog.API/Model/CatalogItem.cs +++ b/src/Services/Catalog/Catalog.API/Model/CatalogItem.cs @@ -64,7 +64,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Model if (quantityDesired <= 0) { - throw new CatalogDomainException($"Item units desired should be greater than cero"); + throw new CatalogDomainException($"Item units desired should be greater than zero"); } int removed = Math.Min(quantityDesired, this.AvailableStock); diff --git a/src/Services/Catalog/Catalog.API/Program.cs b/src/Services/Catalog/Catalog.API/Program.cs index 3f4c19955..39b071c46 100644 --- a/src/Services/Catalog/Catalog.API/Program.cs +++ b/src/Services/Catalog/Catalog.API/Program.cs @@ -9,65 +9,98 @@ using Microsoft.Extensions.Options; using Serilog; using System; using System.IO; + namespace Microsoft.eShopOnContainers.Services.Catalog.API { public class Program { - public static void Main(string[] args) + public static readonly string Namespace = typeof(Program).Namespace; + public static readonly string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); + + public static int Main(string[] args) { - BuildWebHost(args) - .MigrateDbContext((context,services)=> + var configuration = GetConfiguration(); + + Log.Logger = CreateSerilogLogger(configuration); + + try + { + Log.Information("Configuring web host ({ApplicationContext})...", AppName); + var host = BuildWebHost(configuration, args); + + Log.Information("Applying migrations ({ApplicationContext})...", AppName); + host.MigrateDbContext((context, services) => { var env = services.GetService(); var settings = services.GetService>(); var logger = services.GetService>(); new CatalogContextSeed() - .SeedAsync(context,env,settings,logger) - .Wait(); - + .SeedAsync(context, env, settings, logger) + .Wait(); }) - .MigrateDbContext((_,__)=> { }) - .Run(); + .MigrateDbContext((_, __) => { }); + + Log.Information("Starting web host ({ApplicationContext})...", AppName); + host.Run(); + + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName); + return 1; + } + finally + { + Log.CloseAndFlush(); + } } - public static IWebHost BuildWebHost(string[] args) => + private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) => WebHost.CreateDefaultBuilder(args) - .UseStartup() + .CaptureStartupErrors(false) + .UseStartup() .UseApplicationInsights() .UseContentRoot(Directory.GetCurrentDirectory()) .UseWebRoot("Pics") - .ConfigureAppConfiguration((builderContext, config) => - { - var builtConfig = config.Build(); + .UseConfiguration(configuration) + .UseSerilog() + .Build(); - var configurationBuilder = new ConfigurationBuilder(); + private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) + { + var seqServerUrl = configuration["Serilog:SeqServerUrl"]; + var logstashUrl = configuration["Serilog:LogstashgUrl"]; + return new LoggerConfiguration() + .MinimumLevel.Verbose() + .Enrich.WithProperty("ApplicationContext", AppName) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) + .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl) + .ReadFrom.Configuration(configuration) + .CreateLogger(); + } - if (Convert.ToBoolean(builtConfig["UseVault"])) - { - configurationBuilder.AddAzureKeyVault( - $"https://{builtConfig["Vault:Name"]}.vault.azure.net/", - builtConfig["Vault:ClientId"], - builtConfig["Vault:ClientSecret"]); - } + private static IConfiguration GetConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddEnvironmentVariables(); - configurationBuilder.AddEnvironmentVariables(); + var config = builder.Build(); - config.AddConfiguration(configurationBuilder.Build()); - }) - .ConfigureLogging((hostingContext, builder) => - { - builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - builder.AddConsole(); - builder.AddDebug(); - }) - .UseSerilog((builderContext, config) => - { - config - .MinimumLevel.Information() - .Enrich.FromLogContext() - .WriteTo.Console(); - }) - .Build(); + if (config.GetValue("UseVault", false)) + { + builder.AddAzureKeyVault( + $"https://{config["Vault:Name"]}.vault.azure.net/", + config["Vault:ClientId"], + config["Vault:ClientSecret"]); + } + + return builder.Build(); + } } } \ No newline at end of file diff --git a/src/Services/Catalog/Catalog.API/Properties/launchSettings.json b/src/Services/Catalog/Catalog.API/Properties/launchSettings.json index 2b21ca280..f842f80d0 100644 --- a/src/Services/Catalog/Catalog.API/Properties/launchSettings.json +++ b/src/Services/Catalog/Catalog.API/Properties/launchSettings.json @@ -13,7 +13,11 @@ "launchBrowser": true, "launchUrl": "/swagger", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ConnectionString": "server=localhost,5433;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word", + "ASPNETCORE_ENVIRONMENT": "Development", + "EventBusConnection": "localhost", + "Serilog:SeqServerUrl": "http://locahost:5340", + "Serilog:LogstashgUrl":"http://locahost:8080", } }, "Microsoft.eShopOnContainers.Services.Catalog.API": { @@ -23,6 +27,13 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } + }, + "Azure Dev Spaces": { + "commandName": "AzureDevSpaces", + "launchBrowser": true, + "resourceGroup": "edu-devspaces3", + "aksName": "edu-devspaces3", + "subscriptionId": "e3035ac1-c06c-4daf-8939-57b3c5f1f759" } } } \ No newline at end of file diff --git a/src/Services/Catalog/Catalog.API/Startup.cs b/src/Services/Catalog/Catalog.API/Startup.cs index 5c77d37c8..1a51a86fb 100644 --- a/src/Services/Catalog/Catalog.API/Startup.cs +++ b/src/Services/Catalog/Catalog.API/Startup.cs @@ -64,14 +64,14 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API { //Configure logs - loggerFactory.AddAzureWebAppDiagnostics(); - loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); + //loggerFactory.AddAzureWebAppDiagnostics(); + //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); var pathBase = Configuration["PATH_BASE"]; if (!string.IsNullOrEmpty(pathBase)) { - loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'"); + loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase); app.UsePathBase(pathBase); } @@ -117,7 +117,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API if (orchestratorType?.ToUpper() == "K8S") { // Enable K8s telemetry initializer - services.EnableKubernetes(); + services.AddApplicationInsightsKubernetesEnricher(); } if (orchestratorType?.ToUpper() == "SF") { @@ -297,7 +297,8 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API var factory = new ConnectionFactory() { - HostName = configuration["EventBusConnection"] + HostName = configuration["EventBusConnection"], + DispatchConsumersAsync = true }; if (!string.IsNullOrEmpty(configuration["EventBusUserName"])) diff --git a/src/Services/Catalog/Catalog.API/appsettings.json b/src/Services/Catalog/Catalog.API/appsettings.json index 0bf489699..cc7b1b1fb 100644 --- a/src/Services/Catalog/Catalog.API/appsettings.json +++ b/src/Services/Catalog/Catalog.API/appsettings.json @@ -2,12 +2,16 @@ "ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word", "PicBaseUrl": "http://localhost:5101/api/v1/catalog/items/[0]/pic/", "UseCustomizationData": false, - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Trace", - "System": "Information", - "Microsoft": "Information" + "Serilog": { + "SeqServerUrl": null, + "LogstashgUrl": null, + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.eShopOnContainers": "Information", + "System": "Warning" + } } }, "AzureServiceBusEnabled": false, diff --git a/src/Services/Catalog/Catalog.API/azds.yaml b/src/Services/Catalog/Catalog.API/azds.yaml new file mode 100644 index 000000000..9f98a3793 --- /dev/null +++ b/src/Services/Catalog/Catalog.API/azds.yaml @@ -0,0 +1,54 @@ +kind: helm-release +apiVersion: 1.1 +build: + context: ..\..\..\.. + dockerfile: Dockerfile +install: + chart: ../../../../k8s/helm/catalog-api + set: + image: + tag: $(tag) + pullPolicy: Never + ingress: + annotations: + kubernetes.io/ingress.class: traefik-azds + hosts: + - $(spacePrefix)eshop$(hostSuffix) + inf: + k8s: + dns: $(spacePrefix)eshop$(hostSuffix) + values: + - values.dev.yaml? + - secrets.dev.yaml? + - inf.yaml + - app.yaml +configurations: + develop: + build: + useGitIgnore: true + dockerfile: Dockerfile.develop + container: + syncTarget: /src + sync: + - '**/Pages/**' + - '**/Views/**' + - '**/wwwroot/**' + - '!**/*.{sln,csproj}' + command: + - dotnet + - run + - --no-restore + - --no-build + - --no-launch-profile + - -c + - ${Configuration:-Debug} + iterate: + processesToKill: + - dotnet + - vsdbg + buildCommands: + - - dotnet + - build + - --no-restore + - -c + - ${Configuration:-Debug} diff --git a/src/Services/Catalog/Catalog.API/values.dev.yaml b/src/Services/Catalog/Catalog.API/values.dev.yaml new file mode 100644 index 000000000..eaed45149 --- /dev/null +++ b/src/Services/Catalog/Catalog.API/values.dev.yaml @@ -0,0 +1,3 @@ +ingress: + enabled: true + tls: [] \ No newline at end of file diff --git a/src/Services/Catalog/Catalog.API/web.config b/src/Services/Catalog/Catalog.API/web.config index e04a0397b..2157aef31 100644 --- a/src/Services/Catalog/Catalog.API/web.config +++ b/src/Services/Catalog/Catalog.API/web.config @@ -2,8 +2,10 @@ - + - + + + \ No newline at end of file diff --git a/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj b/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj index b38d20ff1..455dfb0bc 100644 --- a/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj +++ b/src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj @@ -33,10 +33,15 @@ - + + + - - + + + all + runtime; build; native; contentfiles; analyzers + diff --git a/src/Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj b/src/Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj index 9e6a4a4ba..efa9d56a4 100644 --- a/src/Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj +++ b/src/Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj @@ -7,11 +7,16 @@ - - - + + + + + + all + runtime; build; native; contentfiles; analyzers + - + diff --git a/src/Services/Identity/Identity.API/.dockerignore b/src/Services/Identity/Identity.API/.dockerignore new file mode 100644 index 000000000..04f7b133d --- /dev/null +++ b/src/Services/Identity/Identity.API/.dockerignore @@ -0,0 +1,14 @@ +.dockerignore +.git +.gitignore +.vs +.vscode +**/*.*proj.user +**/azds.yaml +**/bin +**/charts +**/Dockerfile +**/Dockerfile.develop +**/obj +**/secrets.dev.yaml +**/values.dev.yaml \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Controllers/AccountController.cs b/src/Services/Identity/Identity.API/Controllers/AccountController.cs index 7a1fea312..6e9bbce16 100644 --- a/src/Services/Identity/Identity.API/Controllers/AccountController.cs +++ b/src/Services/Identity/Identity.API/Controllers/AccountController.cs @@ -20,7 +20,7 @@ using Microsoft.Extensions.Logging; namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers { ///

- /// This sample controller implements a typical login/logout/provision workflow for local and external accounts. + /// This sample controller implements a typical login/logout/provision workflow for local accounts. /// The login service encapsulates the interactions with the user data store. This data store is in-memory only and cannot be used for production! /// The interaction service provides a way for the UI to communicate with identityserver for validation and context retrieval /// @@ -58,8 +58,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers var context = await _interaction.GetAuthorizationContextAsync(returnUrl); if (context?.IdP != null) { - // if IdP is passed, then bypass showing the login screen - return ExternalLogin(context.IdP, returnUrl); + throw new NotImplementedException("External login is not implemented!"); } var vm = await BuildLoginViewModelAsync(returnUrl, context); @@ -209,7 +208,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers } catch (Exception ex) { - _logger.LogCritical(ex.Message); + _logger.LogError(ex, "LOGOUT ERROR: {ExceptionMessage}", ex.Message); } } @@ -238,28 +237,6 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers return Redirect(redirectUrl); } - /// - /// initiate roundtrip to external authentication provider - /// - [HttpGet] - public IActionResult ExternalLogin(string provider, string returnUrl) - { - if (returnUrl != null) - { - returnUrl = UrlEncoder.Default.Encode(returnUrl); - } - returnUrl = "/account/externallogincallback?returnUrl=" + returnUrl; - - // start challenge and roundtrip the return URL - var props = new AuthenticationProperties - { - RedirectUri = returnUrl, - Items = { { "scheme", provider } } - }; - return new ChallengeResult(provider, props); - } - - // GET: /Account/Register [HttpGet] [AllowAnonymous] diff --git a/src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs b/src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs index 3dbf28171..a1fda6455 100644 --- a/src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs +++ b/src/Services/Identity/Identity.API/Data/ApplicationDbContextSeed.cs @@ -51,7 +51,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data { retryForAvaiability++; - logger.LogError(ex.Message,$"There is an error migrating data for ApplicationDbContext"); + logger.LogError(ex, "EXCEPTION ERROR while migrating {DbContextName}", nameof(ApplicationDbContext)); await SeedAsync(context,env,logger,settings, retryForAvaiability); } @@ -80,7 +80,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data } catch (Exception ex) { - logger.LogError(ex.Message); + logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return GetDefaultUser(); } @@ -89,7 +89,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data .Skip(1) // skip header column .Select(row => Regex.Split(row, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)") ) .SelectTry(column => CreateApplicationUser(column, csvheaders)) - .OnCaughtException(ex => { logger.LogError(ex.Message); return null; }) + .OnCaughtException(ex => { logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); return null; }) .Where(x => x != null) .ToList(); @@ -199,7 +199,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data string imagesZipFile = Path.Combine(contentRootPath, "Setup", "images.zip"); if (!File.Exists(imagesZipFile)) { - logger.LogError($" zip file '{imagesZipFile}' does not exists."); + logger.LogError("Zip file '{ZipFileName}' does not exists.", imagesZipFile); return; } @@ -221,14 +221,14 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Data } else { - logger.LogWarning($"Skip file '{entry.Name}' in zipfile '{imagesZipFile}'"); + logger.LogWarning("Skipped file '{FileName}' in zipfile '{ZipFileName}'", entry.Name, imagesZipFile); } } } } catch (Exception ex) { - logger.LogError($"Exception in method GetPreconfiguredImages WebMVC. Exception Message={ex.Message}"); + logger.LogError(ex, "EXCEPTION ERROR: {Message}", ex.Message); ; } } } diff --git a/src/Services/Identity/Identity.API/Devspaces/DevspacesRedirectUriValidator.cs b/src/Services/Identity/Identity.API/Devspaces/DevspacesRedirectUriValidator.cs new file mode 100644 index 000000000..53e497b59 --- /dev/null +++ b/src/Services/Identity/Identity.API/Devspaces/DevspacesRedirectUriValidator.cs @@ -0,0 +1,31 @@ +using IdentityServer4.Models; +using IdentityServer4.Validation; +using Microsoft.Extensions.Logging; +using System.Threading.Tasks; + + +namespace Microsoft.eShopOnContainers.Services.Identity.API.Devspaces +{ + public class DevspacesRedirectUriValidator : IRedirectUriValidator + { + private readonly ILogger _logger; + public DevspacesRedirectUriValidator(ILogger logger) + { + _logger = logger; + } + + public Task IsPostLogoutRedirectUriValidAsync(string requestedUri, Client client) + { + + _logger.LogInformation($"Client {client.ClientName} used post logout uri {requestedUri}."); + return Task.FromResult(true); + } + + public Task IsRedirectUriValidAsync(string requestedUri, Client client) + { + _logger.LogInformation($"Client {client.ClientName} used redirect uri {requestedUri}."); + return Task.FromResult(true); + } + + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Devspaces/IdentityDevspacesBuilderExtensions.cs b/src/Services/Identity/Identity.API/Devspaces/IdentityDevspacesBuilderExtensions.cs new file mode 100644 index 000000000..4dadb0e54 --- /dev/null +++ b/src/Services/Identity/Identity.API/Devspaces/IdentityDevspacesBuilderExtensions.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.Services.Identity.API.Devspaces +{ + static class IdentityDevspacesBuilderExtensions + { + public static IIdentityServerBuilder AddDevspacesIfNeeded(this IIdentityServerBuilder builder, bool useDevspaces) + { + if (useDevspaces) + { + builder.AddRedirectUriValidator(); + } + return builder; + } + } +} diff --git a/src/Services/Identity/Identity.API/Dockerfile b/src/Services/Identity/Identity.API/Dockerfile index f3d18f113..ed1502208 100644 --- a/src/Services/Identity/Identity.API/Dockerfile +++ b/src/Services/Identity/Identity.API/Dockerfile @@ -1,16 +1,26 @@ -FROM microsoft/dotnet:2.2.0-aspnetcore-runtime AS base +FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS base WORKDIR /app EXPOSE 80 -FROM microsoft/dotnet:2.2.100-sdk AS build +FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build WORKDIR /src + +COPY scripts scripts/ + +COPY src/ApiGateways/*/*.csproj /src/csproj-files/ +COPY src/ApiGateways/*/*/*.csproj /src/csproj-files/ +COPY src/BuildingBlocks/*/*/*.csproj /src/csproj-files/ +COPY src/Services/*/*/*.csproj /src/csproj-files/ +COPY src/Web/*/*.csproj /src/csproj-files/ + +ARG RUN=pwd +RUN ${RUN} + COPY . . WORKDIR /src/src/Services/Identity/Identity.API -RUN dotnet restore -nowarn:msb3202,nu1503 -RUN dotnet build --no-restore -c Release -o /app +RUN dotnet publish -c Release -o /app FROM build AS publish -RUN dotnet publish --no-restore -c Release -o /app FROM base AS final WORKDIR /app diff --git a/src/Services/Identity/Identity.API/Dockerfile.develop b/src/Services/Identity/Identity.API/Dockerfile.develop new file mode 100644 index 000000000..3ab1fbe7c --- /dev/null +++ b/src/Services/Identity/Identity.API/Dockerfile.develop @@ -0,0 +1,15 @@ +FROM microsoft/dotnet:2.2-sdk +ARG BUILD_CONFIGURATION=Debug +ENV ASPNETCORE_ENVIRONMENT=Development +ENV DOTNET_USE_POLLING_FILE_WATCHER=true +EXPOSE 80 + +WORKDIR /src +COPY ["src/Services/Identity/Identity.API/Identity.API.csproj", "src/Services/Identity/Identity.API/"] +COPY ["src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj", "src/BuildingBlocks/WebHostCustomization/WebHost.Customization/"] +RUN dotnet restore src/Services/Identity/Identity.API/Identity.API.csproj -nowarn:msb3202,nu1503 +COPY . . +WORKDIR "/src/src/Services/Identity/Identity.API" +RUN dotnet build --no-restore -c $BUILD_CONFIGURATION + +ENTRYPOINT ["dotnet", "run", "--no-build", "--no-launch-profile", "-c", "$BUILD_CONFIGURATION", "--"] diff --git a/src/Services/Identity/Identity.API/Identity.API.csproj b/src/Services/Identity/Identity.API/Identity.API.csproj index 077d6c624..728843f71 100644 --- a/src/Services/Identity/Identity.API/Identity.API.csproj +++ b/src/Services/Identity/Identity.API/Identity.API.csproj @@ -13,25 +13,29 @@ - - - + + + - - - + + + + + + - - - + + + + - + + - diff --git a/src/Services/Identity/Identity.API/Models/AccountViewModels/ExternalLoginConfirmationViewModel.cs b/src/Services/Identity/Identity.API/Models/AccountViewModels/ExternalLoginConfirmationViewModel.cs deleted file mode 100644 index 5d4f597a2..000000000 --- a/src/Services/Identity/Identity.API/Models/AccountViewModels/ExternalLoginConfirmationViewModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Microsoft.eShopOnContainers.Services.Identity.API.Models.AccountViewModels -{ - public class ExternalLoginConfirmationViewModel - { - [Required] - [EmailAddress] - public string Email { get; set; } - } -} diff --git a/src/Services/Identity/Identity.API/Models/ManageViewModels/ManageLoginsViewModel.cs b/src/Services/Identity/Identity.API/Models/ManageViewModels/ManageLoginsViewModel.cs deleted file mode 100644 index 0b24c3b7c..000000000 --- a/src/Services/Identity/Identity.API/Models/ManageViewModels/ManageLoginsViewModel.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.AspNetCore.Http.Authentication; -using Microsoft.AspNetCore.Identity; -using System.Collections.Generic; - -namespace Microsoft.eShopOnContainers.Services.Identity.API.Models.ManageViewModels -{ - public class ManageLoginsViewModel - { - public IList CurrentLogins { get; set; } - - public IList OtherLogins { get; set; } - } -} diff --git a/src/Services/Identity/Identity.API/Models/ManageViewModels/RemoveLoginViewModel.cs b/src/Services/Identity/Identity.API/Models/ManageViewModels/RemoveLoginViewModel.cs deleted file mode 100644 index c9171fcf3..000000000 --- a/src/Services/Identity/Identity.API/Models/ManageViewModels/RemoveLoginViewModel.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Microsoft.eShopOnContainers.Services.Identity.API.Models.ManageViewModels -{ - public class RemoveLoginViewModel - { - public string LoginProvider { get; set; } - public string ProviderKey { get; set; } - } -} diff --git a/src/Services/Identity/Identity.API/Program.cs b/src/Services/Identity/Identity.API/Program.cs index d06d49492..784f63a1e 100644 --- a/src/Services/Identity/Identity.API/Program.cs +++ b/src/Services/Identity/Identity.API/Program.cs @@ -14,69 +14,100 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API { public class Program { - public static void Main(string[] args) + public static readonly string Namespace = typeof(Program).Namespace; + public static readonly string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); + + public static int Main(string[] args) { - BuildWebHost(args) - .MigrateDbContext((_, __) => { }) - .MigrateDbContext((context, services) => - { - var env = services.GetService(); - var logger = services.GetService>(); - var settings = services.GetService>(); - - new ApplicationDbContextSeed() - .SeedAsync(context, env, logger, settings) - .Wait(); - }) - .MigrateDbContext((context,services)=> - { - var configuration = services.GetService(); - - new ConfigurationDbContextSeed() - .SeedAsync(context, configuration) - .Wait(); - }).Run(); - } + var configuration = GetConfiguration(); - public static IWebHost BuildWebHost(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseStartup() - .ConfigureAppConfiguration((builderContext, config) => - { - var builtConfig = config.Build(); + Log.Logger = CreateSerilogLogger(configuration); + + try + { + Log.Information("Configuring web host ({ApplicationContext})...", AppName); + var host = BuildWebHost(configuration, args); - var configurationBuilder = new ConfigurationBuilder(); + Log.Information("Applying migrations ({ApplicationContext})...", AppName); + host.MigrateDbContext((_, __) => { }) + .MigrateDbContext((context, services) => + { + var env = services.GetService(); + var logger = services.GetService>(); + var settings = services.GetService>(); - if (Convert.ToBoolean(builtConfig["UseVault"])) + new ApplicationDbContextSeed() + .SeedAsync(context, env, logger, settings) + .Wait(); + }) + .MigrateDbContext((context, services) => { - configurationBuilder.AddAzureKeyVault( - $"https://{builtConfig["Vault:Name"]}.vault.azure.net/", - builtConfig["Vault:ClientId"], - builtConfig["Vault:ClientSecret"]); - } - - configurationBuilder.AddEnvironmentVariables(); - - config.AddConfiguration(configurationBuilder.Build()); - }) - .ConfigureLogging((hostingContext, builder) => - { - builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - builder.AddConsole(); - builder.AddDebug(); - }) + new ConfigurationDbContextSeed() + .SeedAsync(context, configuration) + .Wait(); + }); + + Log.Information("Starting web host ({ApplicationContext})...", AppName); + host.Run(); + + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName); + return 1; + } + finally + { + Log.CloseAndFlush(); + } + } + + private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) => + WebHost.CreateDefaultBuilder(args) + .CaptureStartupErrors(false) + .UseStartup() .UseApplicationInsights() - .UseSerilog((builderContext, config) => - { - config - .MinimumLevel.Information() - .Enrich.FromLogContext() - .WriteTo.Console(); - }) + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseConfiguration(configuration) + .UseSerilog() .Build(); + + private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) + { + var seqServerUrl = configuration["Serilog:SeqServerUrl"]; + var logstashUrl = configuration["Serilog:LogstashgUrl"]; + return new LoggerConfiguration() + .MinimumLevel.Verbose() + .Enrich.WithProperty("ApplicationContext", AppName) + .Enrich.FromLogContext() + .WriteTo.Console() + .WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) + .WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://localhost:8080" : logstashUrl) + .ReadFrom.Configuration(configuration) + .CreateLogger(); + } + + private static IConfiguration GetConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddEnvironmentVariables(); + + var config = builder.Build(); + + if (config.GetValue("UseVault", false)) + { + builder.AddAzureKeyVault( + $"https://{config["Vault:Name"]}.vault.azure.net/", + config["Vault:ClientId"], + config["Vault:ClientSecret"]); + } + + return builder.Build(); + } + } } diff --git a/src/Services/Identity/Identity.API/README.md b/src/Services/Identity/Identity.API/README.md index 76e599dea..ffaaed531 100644 --- a/src/Services/Identity/Identity.API/README.md +++ b/src/Services/Identity/Identity.API/README.md @@ -2,10 +2,10 @@ Sample reference containerized application, cross-platform and microservices architecture. Powered by Microsoft -#Overview +## Overview This sample runs a microservices oriented application and a .net core Mvc application that consumes this services. You can find more information about how to set up docker in your machine in the global directory solution. -#Setup +## Setup This service is a identity provider or STS (Security Token Service) currently implemented with IdentityServer 4 wrapping ASP.NET Identity underneath. Check procedures on how to get the sample app started at the Wiki: diff --git a/src/Services/Identity/Identity.API/Startup.cs b/src/Services/Identity/Identity.API/Startup.cs index aa390779e..6ea7e3888 100644 --- a/src/Services/Identity/Identity.API/Startup.cs +++ b/src/Services/Identity/Identity.API/Startup.cs @@ -13,6 +13,7 @@ using Microsoft.eShopOnContainers.Services.Identity.API.Certificates; using Microsoft.eShopOnContainers.Services.Identity.API.Data; using Microsoft.eShopOnContainers.Services.Identity.API.Models; using Microsoft.eShopOnContainers.Services.Identity.API.Services; +using Microsoft.eShopOnContainers.Services.Identity.API.Devspaces; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -42,12 +43,12 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API // Add framework services. services.AddDbContext(options => options.UseSqlServer(Configuration["ConnectionString"], - sqlServerOptionsAction: sqlOptions => - { - sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); - //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency - sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); - })); + sqlServerOptionsAction: sqlOptions => + { + sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); + //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency + sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); + })); services.AddIdentity() .AddEntityFrameworkStores() @@ -85,27 +86,28 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API x.IssuerUri = "null"; x.Authentication.CookieLifetime = TimeSpan.FromHours(2); }) + .AddDevspacesIfNeeded(Configuration.GetValue("EnableDevspaces", false)) .AddSigningCredential(Certificate.Get()) .AddAspNetIdentity() .AddConfigurationStore(options => { options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, - sqlServerOptionsAction: sqlOptions => - { - sqlOptions.MigrationsAssembly(migrationsAssembly); - //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency - sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); - }); + sqlServerOptionsAction: sqlOptions => + { + sqlOptions.MigrationsAssembly(migrationsAssembly); + //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency + sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); + }); }) .AddOperationalStore(options => { options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, - sqlServerOptionsAction: sqlOptions => - { - sqlOptions.MigrationsAssembly(migrationsAssembly); - //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency - sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); - }); + sqlServerOptionsAction: sqlOptions => + { + sqlOptions.MigrationsAssembly(migrationsAssembly); + //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency + sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); + }); }) .Services.AddTransient(); @@ -118,10 +120,10 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { - loggerFactory.AddConsole(Configuration.GetSection("Logging")); - loggerFactory.AddDebug(); - loggerFactory.AddAzureWebAppDiagnostics(); - loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); + //loggerFactory.AddConsole(Configuration.GetSection("Logging")); + //loggerFactory.AddDebug(); + //loggerFactory.AddAzureWebAppDiagnostics(); + //loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); if (env.IsDevelopment()) { @@ -136,7 +138,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API var pathBase = Configuration["PATH_BASE"]; if (!string.IsNullOrEmpty(pathBase)) { - loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'"); + loggerFactory.CreateLogger().LogDebug("Using PATH BASE '{pathBase}'", pathBase); app.UsePathBase(pathBase); } @@ -182,7 +184,7 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API if (orchestratorType?.ToUpper() == "K8S") { // Enable K8s telemetry initializer - services.EnableKubernetes(); + services.AddApplicationInsightsKubernetesEnricher(); } if (orchestratorType?.ToUpper() == "SF") { diff --git a/src/Services/Identity/Identity.API/Views/Consent/Index.cshtml b/src/Services/Identity/Identity.API/Views/Consent/Index.cshtml index 03f026bda..08c316553 100644 --- a/src/Services/Identity/Identity.API/Views/Consent/Index.cshtml +++ b/src/Services/Identity/Identity.API/Views/Consent/Index.cshtml @@ -16,7 +16,7 @@
- @Html.Partial("_ValidationSummary") +
@@ -49,7 +49,7 @@
    @foreach (var scope in Model.ResourceScopes) { - @Html.Partial("_ScopeListItem", scope) + }
diff --git a/src/Services/Identity/Identity.API/Views/Shared/_LoginPartial.cshtml b/src/Services/Identity/Identity.API/Views/Shared/_LoginPartial.cshtml index 3d0a461f5..e110e459c 100644 --- a/src/Services/Identity/Identity.API/Views/Shared/_LoginPartial.cshtml +++ b/src/Services/Identity/Identity.API/Views/Shared/_LoginPartial.cshtml @@ -7,7 +7,7 @@ @if (SignInManager.IsSignedIn(User)) { - +