diff --git a/README.CICD.k8s.md b/README.CICD.k8s.md new file mode 100644 index 000000000..182880fc5 --- /dev/null +++ b/README.CICD.k8s.md @@ -0,0 +1,48 @@ +# Kubernetes CI/CD VSTS +For k8s CI/CD pipeline delivery a series of tasks must be created in VSTS to deploy k8s in Azure + +## Prerequisites +* A Kubernetes cluster. Follow Azure Container Service's [walkthrough](https://docs.microsoft.com/en-us/azure/container-service/container-service-kubernetes-walkthrough) to create one. +* A private Docker registry. Follow Azure Container Registry's [guide](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-portal) to create one. +* Optionally, previous steps can be skipped if you run gen-k8s-env.ps1 script to automatically create the azure environment needed for kubernetes deployment. Azure cli 2.0 must be previously installed [installation guide](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli). For example: +>``` +>./gen-k8s-env -resourceGroupName k8sGroup -location westeurope -registryName k8sregistry -orchestratorName k8s-cluster -dnsName k8s-dns +>``` +* An `Azure Blob storage`. It is needed for storing the kubernetes config file used by the hosted agent to access to Kubernetes cluster. Example: + + + +* Upload the `kubernetes config file` to the blob storage previously created. Execute the following command which will download the config file into the directory `c:\Users\\.kube\` and then, upload it to your blob storage: +>``` +>https://eshopk8s.blob.core.windows.net/k8s-config/config +>``` +## Create the VSTS tasks +1. Create a `Download File` task to download the kubernetes binary `kubectl` to the hosted agent. For example: +>``` +>https://storage.googleapis.com/kubernetes-release/release/v0.0.1.7.0-alpha.0/bin/windows/386/kubectl.exe +>``` + + +2. Create a Download File task to download the kubernetes config file to the hosted agent. For example: +>``` +>https://eshopk8s.blob.core.windows.net/k8s-config/config +>``` + + +3. Create a powershell task to execute the k8s deployment script. For example: + +* Deployment script path +>``` +>$(System.DefaultWorkingDirectory)/All Microservices/docker-compose/deploy.ps1 +>``` + +* Deployment script path arguments. Where: + - userDockerHub: indicates if Docker Hub is used instead of ACR + - deployCI: indicates that it is a CI/CD deployment + - execPath: path where the k8s binary is stored + - kubeconfigPath: path where the k8s config file is stored +>``` +>-deployCI $true -useDockerHub $true -execPath '$(System.DefaultWorkingDirectory)/' -kubeconfigPath '$(System.DefaultWorkingDirectory)/' +>``` + + diff --git a/_docker/rabbitmq/Dockerfile.nanowin b/_docker/rabbitmq/Dockerfile.nanowin deleted file mode 100644 index 26474c235..000000000 --- a/_docker/rabbitmq/Dockerfile.nanowin +++ /dev/null @@ -1,29 +0,0 @@ -#https://github.com/spring2/dockerfiles/tree/master/rabbitmq - -FROM microsoft/windowsservercore - -SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] - -ENV chocolateyUseWindowsCompression false - -RUN iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1')); \ - choco install -y curl; - -RUN choco install -y erlang -ENV ERLANG_SERVICE_MANAGER_PATH="C:\Program Files\erl8.2\erts-8.2\bin" -RUN choco install -y rabbitmq -ENV RABBITMQ_SERVER="C:\Program Files\RabbitMQ Server\rabbitmq_server-3.6.5" - -ENV RABBITMQ_CONFIG_FILE="c:\rabbitmq" -COPY rabbitmq.config C:/ -COPY rabbitmq.config C:/Users/ContainerAdministrator/AppData/Roaming/RabbitMQ/ -COPY enabled_plugins C:/Users/ContainerAdministrator/AppData/Roaming/RabbitMQ/ - - -EXPOSE 4369 -EXPOSE 5672 -EXPOSE 5671 -EXPOSE 15672 - -WORKDIR C:/Program\ Files/RabbitMQ\ Server/rabbitmq_server-3.6.5/sbin -CMD .\rabbitmq-server.bat \ No newline at end of file diff --git a/_docker/rabbitmq/enabled_plugins b/_docker/rabbitmq/enabled_plugins deleted file mode 100644 index 9eafc419b..000000000 --- a/_docker/rabbitmq/enabled_plugins +++ /dev/null @@ -1 +0,0 @@ -[rabbitmq_amqp1_0,rabbitmq_management]. diff --git a/_docker/rabbitmq/rabbitmq.config b/_docker/rabbitmq/rabbitmq.config deleted file mode 100644 index f7837f213..000000000 --- a/_docker/rabbitmq/rabbitmq.config +++ /dev/null @@ -1 +0,0 @@ -[{rabbit, [{loopback_users, []}]}]. \ No newline at end of file diff --git a/_docker/redis/Dockerfile.nanowin b/_docker/redis/Dockerfile.nanowin deleted file mode 100644 index a08bcb0b6..000000000 --- a/_docker/redis/Dockerfile.nanowin +++ /dev/null @@ -1,30 +0,0 @@ -# The MSI installs a service which is hard to override, so let's use a zip file. - -FROM microsoft/windowsservercore -MAINTAINER alexellis2@gmail.com - -SHELL ["powershell"] -RUN $ErrorActionPreference = 'Stop'; \ - wget https://github.com/MSOpenTech/redis/releases/download/win-3.2.100/Redis-x64-3.2.100.zip -OutFile Redis-x64-3.2.100.zip ; \ - Expand-Archive Redis-x64-3.2.100.zip -dest 'C:\\Program Files\\Redis\\' ; \ - Remove-Item Redis-x64-3.2.100.zip -Force - -RUN setx PATH '%PATH%;C:\\Program Files\\Redis\\' -WORKDIR 'C:\\Program Files\\Redis\\' - - - -RUN Get-Content redis.windows.conf | Where { $_ -notmatch 'bind 127.0.0.1' } | Set-Content redis.openport.conf ; \ - Get-Content redis.openport.conf | Where { $_ -notmatch 'protected-mode yes' } | Set-Content redis.unprotected.conf ; \ - Add-Content redis.unprotected.conf 'protected-mode no' ; \ - Add-Content redis.unprotected.conf 'bind 0.0.0.0' ; \ - Get-Content redis.unprotected.conf - -EXPOSE 6379 - -RUN set-itemproperty -path 'HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters' -Name ServerPriorityTimeLimit -Value 0 -Type DWord - -# Define our command to be run when launching the container -CMD .\\redis-server.exe .\\redis.unprotected.conf --port 6379 ; \ - Write-Host Redis Started... ; \ - while ($true) { Start-Sleep -Seconds 3600 } diff --git a/cli-mac/build-bits.sh b/cli-mac/build-bits.sh old mode 100644 new mode 100755 index f8ad2e2f9..681c3605d --- a/cli-mac/build-bits.sh +++ b/cli-mac/build-bits.sh @@ -10,6 +10,12 @@ projectList=( "../src/Web/WebStatus" ) + +pushd $(pwd)/../src/Web/WebSPA +npm install +npm rebuild node-sass +popd + for project in "${projectList[@]}" do echo -e "\e[33mWorking on $(pwd)/$project" diff --git a/docker-compose-windows.override.yml b/docker-compose-windows.override.yml index c85864d72..c3733c164 100644 --- a/docker-compose-windows.override.yml +++ b/docker-compose-windows.override.yml @@ -11,65 +11,69 @@ services: basket.api: environment: - ASPNETCORE_ENVIRONMENT=Development - - ASPNETCORE_URLS=http://0.0.0.0:5103 + - ASPNETCORE_URLS=http://0.0.0.0:80 - ConnectionString=basket.data - - identityUrl=http://identity.api:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. + - identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. - EventBusConnection=rabbitmq ports: - - "5103:5103" + - "5103:80" catalog.api: environment: - ASPNETCORE_ENVIRONMENT=Development - - ASPNETCORE_URLS=http://0.0.0.0:5101 + - ASPNETCORE_URLS=http://0.0.0.0:80 - ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word - ExternalCatalogBaseUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5101 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. - EventBusConnection=rabbitmq ports: - - "5101:5101" + - "5101:80" identity.api: environment: - ASPNETCORE_ENVIRONMENT=Development - - ASPNETCORE_URLS=http://0.0.0.0:5105 + - ASPNETCORE_URLS=http://0.0.0.0:80 - SpaClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5104 - ConnectionStrings__DefaultConnection=Server=sql.data;Database=Microsoft.eShopOnContainers.Service.IdentityDb;User Id=sa;Password=Pass@word - MvcClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5100 #Local: You need to open your local dev-machine firewall at range 5100-5105. ports: - - "5105:5105" + - "5105:80" ordering.api: environment: - ASPNETCORE_ENVIRONMENT=Development - - ASPNETCORE_URLS=http://0.0.0.0:5102 + - ASPNETCORE_URLS=http://0.0.0.0:80 - ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word - - identityUrl=http://identity.api:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. + - identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. - EventBusConnection=rabbitmq ports: - - "5102:5102" + - "5102:80" webspa: environment: - ASPNETCORE_ENVIRONMENT=Development - - ASPNETCORE_URLS=http://0.0.0.0:5104 + - ASPNETCORE_URLS=http://0.0.0.0:80 - CatalogUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5101 - OrderingUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5102 - IdentityUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. - BasketUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5103 + - CatalogUrlHC=http://catalog.api/hc + - OrderingUrlHC=http://ordering.api/hc + - IdentityUrlHC=http://identity.api/hc #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser. + - BasketUrlHC=http://basket.api/hc ports: - - "5104:5104" + - "5104:80" webmvc: environment: - ASPNETCORE_ENVIRONMENT=Development - - ASPNETCORE_URLS=http://0.0.0.0:5100 - - CatalogUrl=http://catalog.api:5101 - - OrderingUrl=http://ordering.api:5102 - - BasketUrl=http://basket.api:5103 + - ASPNETCORE_URLS=http://0.0.0.0:80 + - CatalogUrl=http://catalog.api + - OrderingUrl=http://ordering.api + - BasketUrl=http://basket.api - IdentityUrl=http://10.0.75.1:5105 #Local: Use 10.0.75.1 in a "Docker for Windows" environment, if using "localhost" from browser. #Remote: Use ${ESHOP_EXTERNAL_DNS_NAME_OR_IP} if using external IP or DNS name from browser. ports: - - "5100:5100" + - "5100:80" sql.data: environment: diff --git a/docker-compose-windows.prod.yml b/docker-compose-windows.prod.yml index 8d00df4ca..7b9c9ab22 100644 --- a/docker-compose-windows.prod.yml +++ b/docker-compose-windows.prod.yml @@ -16,61 +16,65 @@ services: basket.api: environment: - ASPNETCORE_ENVIRONMENT=Production - - ASPNETCORE_URLS=http://0.0.0.0:5103 + - ASPNETCORE_URLS=http://0.0.0.0:80 - ConnectionString=basket.data - - identityUrl=http://identity.api:5105 #Local: You need to open your host's firewall at range 5100-5105. at range 5100-5105. + - identityUrl=http://identity.api #Local: You need to open your host's firewall at range 5100-5105. at range 5100-5105. ports: - "5103:5103" catalog.api: environment: - ASPNETCORE_ENVIRONMENT=Production - - ASPNETCORE_URLS=http://0.0.0.0:5101 + - ASPNETCORE_URLS=http://0.0.0.0:80 - ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word - ExternalCatalogBaseUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5101 #Local: You need to open your host's firewall at range 5100-5105. at range 5100-5105. ports: - - "5101:5101" + - "5101:80" identity.api: environment: - ASPNETCORE_ENVIRONMENT=Production - - ASPNETCORE_URLS=http://0.0.0.0:5105 + - ASPNETCORE_URLS=http://0.0.0.0:80 - SpaClient=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5104 - ConnectionStrings__DefaultConnection=Server=sql.data;Database=Microsoft.eShopOnContainers.Service.IdentityDb;User Id=sa;Password=Pass@word - MvcClient=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5100 #Local: You need to open your host's firewall at range 5100-5105. ports: - - "5105:5105" + - "5105:80" ordering.api: environment: - ASPNETCORE_ENVIRONMENT=Production - - ASPNETCORE_URLS=http://0.0.0.0:5102 + - ASPNETCORE_URLS=http://0.0.0.0:80 - ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word - identityUrl=http://identity.api:5105 #Local: You need to open your host's firewall at range 5100-5105. at range 5100-5105. ports: - - "5102:5102" + - "5102:80" webspa: environment: - ASPNETCORE_ENVIRONMENT=Production - - ASPNETCORE_URLS=http://0.0.0.0:5104 + - ASPNETCORE_URLS=http://0.0.0.0 - CatalogUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5101 - OrderingUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5102 - IdentityUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5105 #Local: You need to open your host's firewall at range 5100-5105. at range 5100-5105. - BasketUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5103 + - CatalogUrlHC=http://catalog.api/hc + - OrderingUrlHC=http://ordering.api/hc + - IdentityUrlHC=http://identity.api/hc #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser. + - BasketUrlHC=http://basket.api/hc ports: - - "5104:5104" + - "5104:80" webmvc: environment: - ASPNETCORE_ENVIRONMENT=Production - - ASPNETCORE_URLS=http://0.0.0.0:5100 - - CatalogUrl=http://catalog.api:5101 - - OrderingUrl=http://ordering.api:5102 + - ASPNETCORE_URLS=http://0.0.0.0:80 + - CatalogUrl=http://catalog.api + - OrderingUrl=http://ordering.api - IdentityUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5105 #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser. - - BasketUrl=http://basket.api:5103 + - BasketUrl=http://basket.api ports: - - "5100:5100" + - "5100:80" sql.data: environment: diff --git a/docker-compose-windows.yml b/docker-compose-windows.yml index 5a08a9302..48f7ef7b4 100644 --- a/docker-compose-windows.yml +++ b/docker-compose-windows.yml @@ -58,7 +58,7 @@ services: image: microsoft/mssql-server-windows basket.data: - image: eshop/redis-win + image: redis:nanoserver # build: # context: ./_docker/redis # dockerfile: Dockerfile.nanowin @@ -66,7 +66,7 @@ services: - "6379:6379" rabbitmq: - image: eshop/rabbitmq-win + image: spring2/rabbitmq # build: # context: ./_docker/rabbitmq # dockerfile: Dockerfile.nanowin diff --git a/docker-compose.ci.build.yml b/docker-compose.ci.build.yml index 546b7690f..1b3f3bea3 100644 --- a/docker-compose.ci.build.yml +++ b/docker-compose.ci.build.yml @@ -6,5 +6,5 @@ services: volumes: - .:/src working_dir: /src - command: /bin/bash -c "dotnet restore ./eShopOnContainers-ServicesAndWebApps.sln && dotnet publish ./eShopOnContainers-ServicesAndWebApps.sln -c Release -o ./obj/Docker/publish" - + command: /bin/bash -c "pushd ./src/Web/WebSPA && npm rebuild node-sass && popd && dotnet restore ./eShopOnContainers-ServicesAndWebApps.sln && dotnet publish ./eShopOnContainers-ServicesAndWebApps.sln -c Release -o ./obj/Docker/publish" + \ No newline at end of file diff --git a/docker-compose.override.yml b/docker-compose.override.yml index f96a8d177..04d1c4d9c 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -11,66 +11,70 @@ services: basket.api: environment: - ASPNETCORE_ENVIRONMENT=Development - - ASPNETCORE_URLS=http://0.0.0.0:5103 + - ASPNETCORE_URLS=http://0.0.0.0:80 - ConnectionString=basket.data - - identityUrl=http://identity.api:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. + - identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. - EventBusConnection=rabbitmq ports: - - "5103:5103" + - "5103:80" catalog.api: environment: - ASPNETCORE_ENVIRONMENT=Development - - ASPNETCORE_URLS=http://0.0.0.0:5101 + - ASPNETCORE_URLS=http://0.0.0.0:80 - ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word - ExternalCatalogBaseUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5101 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. - EventBusConnection=rabbitmq ports: - - "5101:5101" + - "5101:80" identity.api: environment: - ASPNETCORE_ENVIRONMENT=Development - - ASPNETCORE_URLS=http://0.0.0.0:5105 + - ASPNETCORE_URLS=http://0.0.0.0:80 - SpaClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5104 - XamarinCallback=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5105/xamarincallback #localhost do not work for UWP login, so we have to use "external" IP always - ConnectionStrings__DefaultConnection=Server=sql.data;Database=Microsoft.eShopOnContainers.Service.IdentityDb;User Id=sa;Password=Pass@word - MvcClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5100 #Local: You need to open your local dev-machine firewall at range 5100-5105. ports: - - "5105:5105" + - "5105:80" ordering.api: environment: - ASPNETCORE_ENVIRONMENT=Development - - ASPNETCORE_URLS=http://0.0.0.0:5102 + - ASPNETCORE_URLS=http://0.0.0.0:80 - ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word - - identityUrl=http://identity.api:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. + - identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. - EventBusConnection=rabbitmq ports: - - "5102:5102" + - "5102:80" webspa: environment: - ASPNETCORE_ENVIRONMENT=Development - - ASPNETCORE_URLS=http://0.0.0.0:5104 + - ASPNETCORE_URLS=http://0.0.0.0:80 - CatalogUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5101 - OrderingUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5102 - IdentityUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. - - BasketUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5103 + - BasketUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5103 + - CatalogUrlHC=http://catalog.api/hc + - OrderingUrlHC=http://ordering.api/hc + - IdentityUrlHC=http://identity.api/hc #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser. + - BasketUrlHC=http://basket.api/hc ports: - - "5104:5104" + - "5104:80" webmvc: environment: - ASPNETCORE_ENVIRONMENT=Development - - ASPNETCORE_URLS=http://0.0.0.0:5100 - - CatalogUrl=http://catalog.api:5101 - - OrderingUrl=http://ordering.api:5102 - - BasketUrl=http://basket.api:5103 + - ASPNETCORE_URLS=http://0.0.0.0:80 + - CatalogUrl=http://catalog.api + - OrderingUrl=http://ordering.api + - BasketUrl=http://basket.api - IdentityUrl=http://10.0.75.1:5105 #Local: Use 10.0.75.1 in a "Docker for Windows" environment, if using "localhost" from browser. #Remote: Use ${ESHOP_EXTERNAL_DNS_NAME_OR_IP} if using external IP or DNS name from browser. ports: - - "5100:5100" + - "5100:80" sql.data: environment: @@ -82,12 +86,12 @@ services: webstatus: environment: - ASPNETCORE_ENVIRONMENT=Development - - ASPNETCORE_URLS=http://0.0.0.0:5107 - - CatalogUrl=http://catalog.api:5101/hc - - OrderingUrl=http://ordering.api:5102/hc - - BasketUrl=http://basket.api:5103/hc - - IdentityUrl=http://identity.api:5105/hc - - mvc=http://webmvc:5100/hc - - spa=http://webspa:5104/hc + - ASPNETCORE_URLS=http://0.0.0.0:80 + - CatalogUrl=http://catalog.api/hc + - OrderingUrl=http://ordering.api/hc + - BasketUrl=http://basket.api/hc + - IdentityUrl=http://identity.api/hc + - mvc=http://webmvc/hc + - spa=http://webspa/hc ports: - - "5107:5107" + - "5107:80" diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 58bfe5f82..c5d8839ea 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -16,65 +16,69 @@ services: basket.api: environment: - ASPNETCORE_ENVIRONMENT=Production - - ASPNETCORE_URLS=http://0.0.0.0:5103 + - ASPNETCORE_URLS=http://0.0.0.0:80 - ConnectionString=basket.data - - identityUrl=http://identity.api:5105 #Local: You need to open your host's firewall at range 5100-5105. at range 5100-5105. + - identityUrl=http://identity.api #Local: You need to open your host's firewall at range 5100-5105. at range 5100-5105. - EventBusConnection=rabbitmq ports: - - "5103:5103" + - "5103:80" catalog.api: environment: - ASPNETCORE_ENVIRONMENT=Production - - ASPNETCORE_URLS=http://0.0.0.0:5101 + - ASPNETCORE_URLS=http://0.0.0.0:80 - ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word - ExternalCatalogBaseUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5101 #Local: You need to open your host's firewall at range 5100-5105. at range 5100-5105. - EventBusConnection=rabbitmq ports: - - "5101:5101" + - "5101:80" identity.api: environment: - ASPNETCORE_ENVIRONMENT=Production - - ASPNETCORE_URLS=http://0.0.0.0:5105 + - ASPNETCORE_URLS=http://0.0.0.0:80 - SpaClient=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5104 - ConnectionStrings__DefaultConnection=Server=sql.data;Database=Microsoft.eShopOnContainers.Service.IdentityDb;User Id=sa;Password=Pass@word - MvcClient=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5100 #Local: You need to open your host's firewall at range 5100-5105. - XamarinCallback=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5105/xamarincallback ports: - - "5105:5105" + - "5105:80" ordering.api: environment: - ASPNETCORE_ENVIRONMENT=Production - - ASPNETCORE_URLS=http://0.0.0.0:5102 + - ASPNETCORE_URLS=http://0.0.0.0:80 - ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word - - identityUrl=http://identity.api:5105 #Local: You need to open your host's firewall at range 5100-5105. at range 5100-5105. + - identityUrl=http://identity.api #Local: You need to open your host's firewall at range 5100-5105. at range 5100-5105. - EventBusConnection=rabbitmq ports: - - "5102:5102" + - "5102:80" webspa: environment: - ASPNETCORE_ENVIRONMENT=Production - - ASPNETCORE_URLS=http://0.0.0.0:5104 + - ASPNETCORE_URLS=http://0.0.0.0:80 - CatalogUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5101 - OrderingUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5102 - IdentityUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5105 #Local: You need to open your host's firewall at range 5100-5105. at range 5100-5105. - - BasketUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5103 + - BasketUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5103 + - CatalogUrlHC=http://catalog.api/hc + - OrderingUrlHC=http://ordering.api/hc + - IdentityUrlHC=http://identity.api/hc #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser. + - BasketUrlHC=http://basket.api/hc ports: - - "5104:5104" + - "5104:80" webmvc: environment: - ASPNETCORE_ENVIRONMENT=Production - - ASPNETCORE_URLS=http://0.0.0.0:5100 - - CatalogUrl=http://catalog.api:5101 - - OrderingUrl=http://ordering.api:5102 + - ASPNETCORE_URLS=http://0.0.0.0:80 + - CatalogUrl=http://catalog.api + - OrderingUrl=http://ordering.api - IdentityUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5105 #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser. - - BasketUrl=http://basket.api:5103 + - BasketUrl=http://basket.api ports: - - "5100:5100" + - "5100:80" sql.data: environment: @@ -86,12 +90,13 @@ services: webstatus: environment: - ASPNETCORE_ENVIRONMENT=Production - - ASPNETCORE_URLS=http://0.0.0.0:5107 - - CatalogUrl=http://catalog.api:5101/hc - - OrderingUrl=http://ordering.api:5102/hc - - BasketUrl=http://basket.api:5103/hc - - mvc=http://webmvc:5100/hc - - spa=http://webspa:5104/hc - - IdentityUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5105 #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser. + - ASPNETCORE_URLS=http://0.0.0.0:80 + - CatalogUrl=http://catalog.api/hc + - OrderingUrl=http://ordering.api/hc + - BasketUrl=http://basket.api/hc + - IdentityUrl=http://identity.api/hc + - mvc=http://webmvc/hc + - spa=http://webspa/hc + ports: - - "5107:5107" \ No newline at end of file + - "5107:80" \ No newline at end of file diff --git a/eShopOnContainers-iOS.sln b/eShopOnContainers-iOS.sln new file mode 100644 index 000000000..a6e889786 --- /dev/null +++ b/eShopOnContainers-iOS.sln @@ -0,0 +1,237 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26430.6 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3AF739CD-81D8-428D-A08A-0A58372DEBF6}" + ProjectSection(SolutionItems) = preProject + docker-compose.yml = docker-compose.yml + global.json = global.json + NuGet.config = NuGet.config + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mobile Apps", "Mobile Apps", "{F61357CE-1CC2-410E-8776-B16EEBC98EB8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{A857AD10-40FF-4303-BEC2-FF1C58D5735E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eShopOnContainers.Core", "src\Mobile\eShopOnContainers\eShopOnContainers.Core\eShopOnContainers.Core.csproj", "{67F9D3A8-F71E-4428-913F-C37AE82CDB24}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eShopOnContainers.iOS", "src\Mobile\eShopOnContainers\eShopOnContainers.iOS\eShopOnContainers.iOS.csproj", "{6EEB23DC-7063-4444-9AF8-90DF24F549C0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared Code", "Shared Code", "{778289CA-31F7-4464-8C2A-612EE846F8A7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{9CC7814B-72A6-465B-A61C-57B512DEE303}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mobile Apps", "Mobile Apps", "{B7B1D395-4E06-4036-BE86-C216756B9367}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eShopOnContainers.UnitTests", "src\Mobile\eShopOnContainers\eShopOnContainers.UnitTests\eShopOnContainers.UnitTests.csproj", "{F7B6A162-BC4D-4924-B16A-713F9B0344E7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eShopOnContainers.TestRunner.iOS", "src\Mobile\eShopOnContainers\eShopOnContainers.TestRunner.iOS\eShopOnContainers.TestRunner.iOS.csproj", "{B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Ad-Hoc|Any CPU = Ad-Hoc|Any CPU + Ad-Hoc|ARM = Ad-Hoc|ARM + Ad-Hoc|iPhone = Ad-Hoc|iPhone + Ad-Hoc|iPhoneSimulator = Ad-Hoc|iPhoneSimulator + Ad-Hoc|x64 = Ad-Hoc|x64 + Ad-Hoc|x86 = Ad-Hoc|x86 + AppStore|Any CPU = AppStore|Any CPU + AppStore|ARM = AppStore|ARM + AppStore|iPhone = AppStore|iPhone + AppStore|iPhoneSimulator = AppStore|iPhoneSimulator + AppStore|x64 = AppStore|x64 + AppStore|x86 = AppStore|x86 + Debug|Any CPU = Debug|Any CPU + Debug|ARM = Debug|ARM + Debug|iPhone = Debug|iPhone + Debug|iPhoneSimulator = Debug|iPhoneSimulator + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|ARM = Release|ARM + Release|iPhone = Release|iPhone + Release|iPhoneSimulator = Release|iPhoneSimulator + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Ad-Hoc|ARM.Build.0 = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Ad-Hoc|x64.Build.0 = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Ad-Hoc|x86.Build.0 = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.AppStore|Any CPU.Build.0 = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.AppStore|ARM.ActiveCfg = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.AppStore|ARM.Build.0 = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.AppStore|iPhone.Build.0 = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.AppStore|x64.ActiveCfg = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.AppStore|x64.Build.0 = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.AppStore|x86.ActiveCfg = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.AppStore|x86.Build.0 = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Debug|ARM.ActiveCfg = Debug|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Debug|ARM.Build.0 = Debug|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Debug|iPhone.Build.0 = Debug|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Debug|x64.ActiveCfg = Debug|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Debug|x64.Build.0 = Debug|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Debug|x86.ActiveCfg = Debug|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Debug|x86.Build.0 = Debug|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Release|Any CPU.Build.0 = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Release|ARM.ActiveCfg = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Release|ARM.Build.0 = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Release|iPhone.ActiveCfg = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Release|iPhone.Build.0 = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Release|x64.ActiveCfg = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Release|x64.Build.0 = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Release|x86.ActiveCfg = Release|Any CPU + {67F9D3A8-F71E-4428-913F-C37AE82CDB24}.Release|x86.Build.0 = Release|Any CPU + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Ad-Hoc|ARM.ActiveCfg = Ad-Hoc|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Ad-Hoc|x64.ActiveCfg = Ad-Hoc|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.AppStore|ARM.ActiveCfg = AppStore|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.AppStore|iPhone.ActiveCfg = AppStore|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.AppStore|iPhone.Build.0 = AppStore|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.AppStore|x64.ActiveCfg = AppStore|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.AppStore|x86.ActiveCfg = AppStore|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Debug|Any CPU.ActiveCfg = Debug|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Debug|Any CPU.Build.0 = Debug|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Debug|ARM.ActiveCfg = Debug|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Debug|iPhone.ActiveCfg = Debug|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Debug|iPhone.Build.0 = Debug|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Debug|x64.ActiveCfg = Debug|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Debug|x86.ActiveCfg = Debug|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Release|Any CPU.ActiveCfg = Release|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Release|ARM.ActiveCfg = Release|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Release|iPhone.ActiveCfg = Release|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Release|iPhone.Build.0 = Release|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Release|x64.ActiveCfg = Release|iPhone + {6EEB23DC-7063-4444-9AF8-90DF24F549C0}.Release|x86.ActiveCfg = Release|iPhone + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Ad-Hoc|ARM.Build.0 = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Ad-Hoc|x64.Build.0 = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Ad-Hoc|x86.Build.0 = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.AppStore|Any CPU.Build.0 = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.AppStore|ARM.ActiveCfg = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.AppStore|ARM.Build.0 = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.AppStore|iPhone.Build.0 = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.AppStore|x64.ActiveCfg = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.AppStore|x64.Build.0 = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.AppStore|x86.ActiveCfg = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.AppStore|x86.Build.0 = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Debug|ARM.ActiveCfg = Debug|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Debug|ARM.Build.0 = Debug|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Debug|iPhone.Build.0 = Debug|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Debug|x64.ActiveCfg = Debug|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Debug|x64.Build.0 = Debug|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Debug|x86.ActiveCfg = Debug|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Debug|x86.Build.0 = Debug|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Release|Any CPU.Build.0 = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Release|ARM.ActiveCfg = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Release|ARM.Build.0 = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Release|iPhone.ActiveCfg = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Release|iPhone.Build.0 = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Release|x64.ActiveCfg = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Release|x64.Build.0 = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Release|x86.ActiveCfg = Release|Any CPU + {F7B6A162-BC4D-4924-B16A-713F9B0344E7}.Release|x86.Build.0 = Release|Any CPU + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Ad-Hoc|ARM.ActiveCfg = Ad-Hoc|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Ad-Hoc|x64.ActiveCfg = Ad-Hoc|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.AppStore|ARM.ActiveCfg = AppStore|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.AppStore|iPhone.ActiveCfg = AppStore|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.AppStore|iPhone.Build.0 = AppStore|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.AppStore|x64.ActiveCfg = AppStore|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.AppStore|x86.ActiveCfg = AppStore|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Debug|Any CPU.ActiveCfg = Debug|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Debug|Any CPU.Build.0 = Debug|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Debug|ARM.ActiveCfg = Debug|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Debug|iPhone.ActiveCfg = Debug|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Debug|iPhone.Build.0 = Debug|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Debug|x64.ActiveCfg = Debug|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Debug|x86.ActiveCfg = Debug|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Release|Any CPU.ActiveCfg = Release|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Release|ARM.ActiveCfg = Release|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Release|iPhone.ActiveCfg = Release|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Release|iPhone.Build.0 = Release|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Release|x64.ActiveCfg = Release|iPhone + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3}.Release|x86.ActiveCfg = Release|iPhone + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {F61357CE-1CC2-410E-8776-B16EEBC98EB8} = {932D8224-11F6-4D07-B109-DA28AD288A63} + {67F9D3A8-F71E-4428-913F-C37AE82CDB24} = {778289CA-31F7-4464-8C2A-612EE846F8A7} + {6EEB23DC-7063-4444-9AF8-90DF24F549C0} = {9CC7814B-72A6-465B-A61C-57B512DEE303} + {778289CA-31F7-4464-8C2A-612EE846F8A7} = {F61357CE-1CC2-410E-8776-B16EEBC98EB8} + {9CC7814B-72A6-465B-A61C-57B512DEE303} = {F61357CE-1CC2-410E-8776-B16EEBC98EB8} + {B7B1D395-4E06-4036-BE86-C216756B9367} = {A857AD10-40FF-4303-BEC2-FF1C58D5735E} + {F7B6A162-BC4D-4924-B16A-713F9B0344E7} = {B7B1D395-4E06-4036-BE86-C216756B9367} + {B68C2B56-7581-46AE-B55D-D25DDFD3BFE3} = {B7B1D395-4E06-4036-BE86-C216756B9367} + EndGlobalSection +EndGlobal diff --git a/img/k8s/blob_creation.png b/img/k8s/blob_creation.png new file mode 100644 index 000000000..a9e386ead Binary files /dev/null and b/img/k8s/blob_creation.png differ diff --git a/img/k8s/deploy_script_task.png b/img/k8s/deploy_script_task.png new file mode 100644 index 000000000..917625f3e Binary files /dev/null and b/img/k8s/deploy_script_task.png differ diff --git a/img/k8s/get_kubectlbin_task.png b/img/k8s/get_kubectlbin_task.png new file mode 100644 index 000000000..423aceca8 Binary files /dev/null and b/img/k8s/get_kubectlbin_task.png differ diff --git a/img/k8s/get_kubectlconfig_task.png b/img/k8s/get_kubectlconfig_task.png new file mode 100644 index 000000000..594e68ba7 Binary files /dev/null and b/img/k8s/get_kubectlconfig_task.png differ diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/HealthCheckBuilderSqlServerExtensions.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/HealthCheckBuilderSqlServerExtensions.cs index 4998c91ed..1837d9638 100644 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/HealthCheckBuilderSqlServerExtensions.cs +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/HealthCheckBuilderSqlServerExtensions.cs @@ -7,9 +7,18 @@ using System.Data.SqlClient; namespace Microsoft.Extensions.HealthChecks { + // REVIEW: What are the appropriate guards for these functions? + public static class HealthCheckBuilderSqlServerExtensions { public static HealthCheckBuilder AddSqlCheck(this HealthCheckBuilder builder, string name, string connectionString) + { + Guard.ArgumentNotNull(nameof(builder), builder); + + return AddSqlCheck(builder, name, connectionString, builder.DefaultCacheDuration); + } + + public static HealthCheckBuilder AddSqlCheck(this HealthCheckBuilder builder, string name, string connectionString, TimeSpan cacheDuration) { builder.AddCheck($"SqlCheck({name})", async () => { @@ -37,7 +46,7 @@ namespace Microsoft.Extensions.HealthChecks { return HealthCheckResult.Unhealthy($"SqlCheck({name}): Exception during check: {ex.GetType().FullName}"); } - }); + }, cacheDuration); return builder; } diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/NumericChecks.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/NumericChecks.cs index f3c795629..4c958234e 100644 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/NumericChecks.cs +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/NumericChecks.cs @@ -10,7 +10,14 @@ namespace Microsoft.Extensions.HealthChecks { // Numeric checks - public static HealthCheckBuilder AddMinValueCheck(this HealthCheckBuilder builder, string name, T minValue, Func currentValueFunc) + public static HealthCheckBuilder AddMinValueCheck(this HealthCheckBuilder builder, string name, T minValue, Func currentValueFunc) where T : IComparable + { + Guard.ArgumentNotNull(nameof(builder), builder); + + return AddMinValueCheck(builder, name, minValue, currentValueFunc, builder.DefaultCacheDuration); + } + + public static HealthCheckBuilder AddMinValueCheck(this HealthCheckBuilder builder, string name, T minValue, Func currentValueFunc, TimeSpan cacheDuration) where T : IComparable { Guard.ArgumentNotNull(nameof(builder), builder); @@ -26,12 +33,19 @@ namespace Microsoft.Extensions.HealthChecks $"min={minValue}, current={currentValue}", new Dictionary { { "min", minValue }, { "current", currentValue } } ); - }); + }, cacheDuration); return builder; } - public static HealthCheckBuilder AddMaxValueCheck(this HealthCheckBuilder builder, string name, T maxValue, Func currentValueFunc) + public static HealthCheckBuilder AddMaxValueCheck(this HealthCheckBuilder builder, string name, T maxValue, Func currentValueFunc) where T : IComparable + { + Guard.ArgumentNotNull(nameof(builder), builder); + + return AddMaxValueCheck(builder, name, maxValue, currentValueFunc, builder.DefaultCacheDuration); + } + + public static HealthCheckBuilder AddMaxValueCheck(this HealthCheckBuilder builder, string name, T maxValue, Func currentValueFunc, TimeSpan cacheDuration) where T : IComparable { Guard.ArgumentNotNull(nameof(builder), builder); @@ -47,7 +61,7 @@ namespace Microsoft.Extensions.HealthChecks $"max={maxValue}, current={currentValue}", new Dictionary { { "max", maxValue }, { "current", currentValue } } ); - }); + }, cacheDuration); return builder; } diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/SystemChecks.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/SystemChecks.cs index d4491fda4..dbd9feff2 100644 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/SystemChecks.cs +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/SystemChecks.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Diagnostics; namespace Microsoft.Extensions.HealthChecks @@ -12,10 +13,19 @@ namespace Microsoft.Extensions.HealthChecks public static HealthCheckBuilder AddPrivateMemorySizeCheck(this HealthCheckBuilder builder, long maxSize) => AddMaxValueCheck(builder, $"PrivateMemorySize({maxSize})", maxSize, () => Process.GetCurrentProcess().PrivateMemorySize64); + public static HealthCheckBuilder AddPrivateMemorySizeCheck(this HealthCheckBuilder builder, long maxSize, TimeSpan cacheDuration) + => AddMaxValueCheck(builder, $"PrivateMemorySize({maxSize})", maxSize, () => Process.GetCurrentProcess().PrivateMemorySize64, cacheDuration); + public static HealthCheckBuilder AddVirtualMemorySizeCheck(this HealthCheckBuilder builder, long maxSize) => AddMaxValueCheck(builder, $"VirtualMemorySize({maxSize})", maxSize, () => Process.GetCurrentProcess().VirtualMemorySize64); + public static HealthCheckBuilder AddVirtualMemorySizeCheck(this HealthCheckBuilder builder, long maxSize, TimeSpan cacheDuration) + => AddMaxValueCheck(builder, $"VirtualMemorySize({maxSize})", maxSize, () => Process.GetCurrentProcess().VirtualMemorySize64, cacheDuration); + public static HealthCheckBuilder AddWorkingSetCheck(this HealthCheckBuilder builder, long maxSize) => AddMaxValueCheck(builder, $"WorkingSet({maxSize})", maxSize, () => Process.GetCurrentProcess().WorkingSet64); + + public static HealthCheckBuilder AddWorkingSetCheck(this HealthCheckBuilder builder, long maxSize, TimeSpan cacheDuration) + => AddMaxValueCheck(builder, $"WorkingSet({maxSize})", maxSize, () => Process.GetCurrentProcess().WorkingSet64, cacheDuration); } } diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/UrlChecks.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/UrlChecks.cs index d7df58def..2a6cfe908 100644 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/UrlChecks.cs +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/Checks/UrlChecks.cs @@ -10,36 +10,73 @@ namespace Microsoft.Extensions.HealthChecks { public static partial class HealthCheckBuilderExtensions { - // URL checks + // Default URL check public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url) - => AddUrlCheck(builder, url, response => UrlChecker.DefaultUrlCheck(response)); + { + Guard.ArgumentNotNull(nameof(builder), builder); + + return AddUrlCheck(builder, url, builder.DefaultCacheDuration); + } + + public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, TimeSpan cacheDuration) + => AddUrlCheck(builder, url, response => UrlChecker.DefaultUrlCheck(response), cacheDuration); + + // Func returning IHealthCheckResult + + public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, Func checkFunc) + { + Guard.ArgumentNotNull(nameof(builder), builder); + + return AddUrlCheck(builder, url, checkFunc, builder.DefaultCacheDuration); + } public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, - Func checkFunc) + Func checkFunc, + TimeSpan cacheDuration) { Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); - return AddUrlCheck(builder, url, response => new ValueTask(checkFunc(response))); + return AddUrlCheck(builder, url, response => new ValueTask(checkFunc(response)), cacheDuration); + } + + // Func returning Task + + public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, Func> checkFunc) + { + Guard.ArgumentNotNull(nameof(builder), builder); + + return AddUrlCheck(builder, url, checkFunc, builder.DefaultCacheDuration); } public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, - Func> checkFunc) + Func> checkFunc, + TimeSpan cacheDuration) { Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); - return AddUrlCheck(builder, url, response => new ValueTask(checkFunc(response))); + return AddUrlCheck(builder, url, response => new ValueTask(checkFunc(response)), cacheDuration); + } + + // Func returning ValueTask + + public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, Func> checkFunc) + { + Guard.ArgumentNotNull(nameof(builder), builder); + + return AddUrlCheck(builder, url, checkFunc, builder.DefaultCacheDuration); } public static HealthCheckBuilder AddUrlCheck(this HealthCheckBuilder builder, string url, - Func> checkFunc) + Func> checkFunc, + TimeSpan cacheDuration) { Guard.ArgumentNotNull(nameof(builder), builder); Guard.ArgumentNotNullOrEmpty(nameof(url), url); Guard.ArgumentNotNull(nameof(checkFunc), checkFunc); var urlCheck = new UrlChecker(checkFunc, url); - builder.AddCheck($"UrlCheck({url})", () => urlCheck.CheckAsync()); + builder.AddCheck($"UrlCheck({url})", () => urlCheck.CheckAsync(), cacheDuration); return builder; } } diff --git a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckBuilder.cs b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckBuilder.cs index 4e1c6e4c9..006e4a6ef 100644 --- a/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckBuilder.cs +++ b/src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks/HealthCheckBuilder.cs @@ -21,7 +21,7 @@ namespace Microsoft.Extensions.HealthChecks [string.Empty] = _currentGroup }; - DefaultCacheDuration = TimeSpan.FromMinutes(1); + DefaultCacheDuration = TimeSpan.FromMinutes(5); } /// diff --git a/src/BuildingBlocks/Resilience/Resilience.Http/IHttpClient.cs b/src/BuildingBlocks/Resilience/Resilience.Http/IHttpClient.cs index 0e56a66da..5ea3003ed 100644 --- a/src/BuildingBlocks/Resilience/Resilience.Http/IHttpClient.cs +++ b/src/BuildingBlocks/Resilience/Resilience.Http/IHttpClient.cs @@ -1,16 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; +using System.Net.Http; using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http { public interface IHttpClient { - HttpClient Inst { get; } - Task GetStringAsync(string uri); - Task PostAsync(string uri, T item); - Task DeleteAsync(string uri); + Task GetStringAsync(string uri, string authorizationToken = null, string authorizationMethod = "Bearer"); + + Task PostAsync(string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer"); + + Task DeleteAsync(string uri, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer"); + + Task PutAsync(string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer"); } } diff --git a/src/BuildingBlocks/Resilience/Resilience.Http/ResiliencePolicy.cs b/src/BuildingBlocks/Resilience/Resilience.Http/ResiliencePolicy.cs deleted file mode 100644 index 63eadc857..000000000 --- a/src/BuildingBlocks/Resilience/Resilience.Http/ResiliencePolicy.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http -{ - public class ResiliencePolicy - { - } -} diff --git a/src/BuildingBlocks/Resilience/Resilience.Http/ResilientHttpClient.cs b/src/BuildingBlocks/Resilience/Resilience.Http/ResilientHttpClient.cs index 2ccc84aaa..d80352862 100644 --- a/src/BuildingBlocks/Resilience/Resilience.Http/ResilientHttpClient.cs +++ b/src/BuildingBlocks/Resilience/Resilience.Http/ResilientHttpClient.cs @@ -3,9 +3,12 @@ using Newtonsoft.Json; using Polly; using Polly.Wrap; using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Http.Headers; using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http @@ -17,48 +20,140 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http /// public class ResilientHttpClient : IHttpClient { - private HttpClient _client; - private PolicyWrap _policyWrapper; - private ILogger _logger; - public HttpClient Inst => _client; + private readonly HttpClient _client; + private readonly ILogger _logger; + private readonly Func> _policyCreator; + private ConcurrentDictionary _policyWrappers; - public ResilientHttpClient(Policy[] policies, ILogger logger) + public ResilientHttpClient(Func> policyCreator, ILogger logger) { _client = new HttpClient(); _logger = logger; + _policyCreator = policyCreator; + _policyWrappers = new ConcurrentDictionary(); + } - // Add Policies to be applied - _policyWrapper = Policy.WrapAsync(policies); - } - public Task GetStringAsync(string uri) => - HttpInvoker(() => - _client.GetStringAsync(uri)); + public Task PostAsync(string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer") + { + return DoPostPutAsync(HttpMethod.Post, uri, item, authorizationToken, requestId, authorizationMethod); + } - public Task PostAsync(string uri, T item) => - // a new StringContent must be created for each retry - // as it is disposed after each call - HttpInvoker(() => + public Task PutAsync(string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer") + { + return DoPostPutAsync(HttpMethod.Put, uri, item, authorizationToken, requestId, authorizationMethod); + } + + public Task DeleteAsync(string uri, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer") + { + var origin = GetOriginFromUri(uri); + + return HttpInvoker(origin, async () => { - var response = _client.PostAsync(uri, new StringContent(JsonConvert.SerializeObject(item), System.Text.Encoding.UTF8, "application/json")); - // raise exception if HttpResponseCode 500 - // needed for circuit breaker to track fails - if (response.Result.StatusCode == HttpStatusCode.InternalServerError) + var requestMessage = new HttpRequestMessage(HttpMethod.Delete, uri); + + if (authorizationToken != null) + { + requestMessage.Headers.Authorization = new AuthenticationHeaderValue(authorizationMethod, authorizationToken); + } + + if (requestId != null) { - throw new HttpRequestException(); + requestMessage.Headers.Add("x-requestid", requestId); } - return response; + return await _client.SendAsync(requestMessage); }); + } - public Task DeleteAsync(string uri) => - HttpInvoker(() => _client.DeleteAsync(uri)); + public Task GetStringAsync(string uri, string authorizationToken = null, string authorizationMethod = "Bearer") + { + var origin = GetOriginFromUri(uri); + + return HttpInvoker(origin, async () => + { + var requestMessage = new HttpRequestMessage(HttpMethod.Get, uri); + + if (authorizationToken != null) + { + requestMessage.Headers.Authorization = new AuthenticationHeaderValue(authorizationMethod, authorizationToken); + } + var response = await _client.SendAsync(requestMessage); + + return await response.Content.ReadAsStringAsync(); + }); + } + + private Task DoPostPutAsync(HttpMethod method, string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer") + { + if (method != HttpMethod.Post && method != HttpMethod.Put) + { + throw new ArgumentException("Value must be either post or put.", nameof(method)); + } + + // a new StringContent must be created for each retry + // as it is disposed after each call + var origin = GetOriginFromUri(uri); + + return HttpInvoker(origin, () => + { + var requestMessage = new HttpRequestMessage(method, uri); + + requestMessage.Content = new StringContent(JsonConvert.SerializeObject(item), System.Text.Encoding.UTF8, "application/json"); + + if (authorizationToken != null) + { + requestMessage.Headers.Authorization = new AuthenticationHeaderValue(authorizationMethod, authorizationToken); + } + + if (requestId != null) + { + requestMessage.Headers.Add("x-requestid", requestId); + } + + var response = _client.SendAsync(requestMessage).Result; + + // raise exception if HttpResponseCode 500 + // needed for circuit breaker to track fails + + if (response.StatusCode == HttpStatusCode.InternalServerError) + { + throw new HttpRequestException(); + } + + return Task.FromResult(response); + }); + } + + private async Task HttpInvoker(string origin, Func> action) + { + var normalizedOrigin = NormalizeOrigin(origin); + + if (!_policyWrappers.TryGetValue(normalizedOrigin, out PolicyWrap policyWrap)) + { + policyWrap = Policy.Wrap(_policyCreator(normalizedOrigin).ToArray()); + _policyWrappers.TryAdd(normalizedOrigin, policyWrap); + } - private Task HttpInvoker(Func> action) => // Executes the action applying all // the policies defined in the wrapper - _policyWrapper.ExecuteAsync(() => action()); - } + return await policyWrap.Execute(action, new Context(normalizedOrigin)); + } + + + private static string NormalizeOrigin(string origin) + { + return origin?.Trim()?.ToLower(); + } + private static string GetOriginFromUri(string uri) + { + var url = new Uri(uri); + + var origin = $"{url.Scheme}://{url.DnsSafeHost}:{url.Port}"; + + return origin; + } + } } diff --git a/src/BuildingBlocks/Resilience/Resilience.Http/StandardHttpClient.cs b/src/BuildingBlocks/Resilience/Resilience.Http/StandardHttpClient.cs index 4f400caf5..3d5217064 100644 --- a/src/BuildingBlocks/Resilience/Resilience.Http/StandardHttpClient.cs +++ b/src/BuildingBlocks/Resilience/Resilience.Http/StandardHttpClient.cs @@ -1,7 +1,9 @@ using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System; +using System.Net; using System.Net.Http; +using System.Net.Http.Headers; using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http @@ -10,24 +12,90 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http { private HttpClient _client; private ILogger _logger; - public HttpClient Inst => _client; + public StandardHttpClient(ILogger logger) { _client = new HttpClient(); _logger = logger; } - - public Task GetStringAsync(string uri) => - _client.GetStringAsync(uri); - public Task PostAsync(string uri, T item) + public async Task GetStringAsync(string uri, string authorizationToken = null, string authorizationMethod = "Bearer") { - var contentString = new StringContent(JsonConvert.SerializeObject(item), System.Text.Encoding.UTF8, "application/json"); - return _client.PostAsync(uri, contentString); + var requestMessage = new HttpRequestMessage(HttpMethod.Get, uri); + + if (authorizationToken != null) + { + requestMessage.Headers.Authorization = new AuthenticationHeaderValue(authorizationMethod, authorizationToken); + } + + var response = await _client.SendAsync(requestMessage); + + return await response.Content.ReadAsStringAsync(); } - public Task DeleteAsync(string uri) => - _client.DeleteAsync(uri); + private async Task DoPostPutAsync(HttpMethod method, string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer") + { + if (method != HttpMethod.Post && method != HttpMethod.Put) + { + throw new ArgumentException("Value must be either post or put.", nameof(method)); + } + + // a new StringContent must be created for each retry + // as it is disposed after each call + + var requestMessage = new HttpRequestMessage(HttpMethod.Post, uri); + + requestMessage.Content = new StringContent(JsonConvert.SerializeObject(item), System.Text.Encoding.UTF8, "application/json"); + + if (authorizationToken != null) + { + requestMessage.Headers.Authorization = new AuthenticationHeaderValue(authorizationMethod, authorizationToken); + } + + if (requestId != null) + { + requestMessage.Headers.Add("x-requestid", requestId); + } + + var response = await _client.SendAsync(requestMessage); + + // raise exception if HttpResponseCode 500 + // needed for circuit breaker to track fails + + if (response.StatusCode == HttpStatusCode.InternalServerError) + { + throw new HttpRequestException(); + } + + return response; + } + + + public async Task PostAsync(string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer") + { + return await DoPostPutAsync(HttpMethod.Post, uri, item, authorizationToken, requestId, authorizationToken); + } + + public async Task PutAsync(string uri, T item, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer") + { + return await DoPostPutAsync(HttpMethod.Put, uri, item, authorizationToken, requestId, authorizationToken); + } + public async Task DeleteAsync(string uri, string authorizationToken = null, string requestId = null, string authorizationMethod = "Bearer") + { + var requestMessage = new HttpRequestMessage(HttpMethod.Delete, uri); + + if (authorizationToken != null) + { + requestMessage.Headers.Authorization = new AuthenticationHeaderValue(authorizationMethod, authorizationToken); + } + + if (requestId != null) + { + requestMessage.Headers.Add("x-requestid", requestId); + } + + return await _client.SendAsync(requestMessage); + } } } diff --git a/src/Services/Catalog/Catalog.API/Startup.cs b/src/Services/Catalog/Catalog.API/Startup.cs index 9eb195674..65fb26515 100644 --- a/src/Services/Catalog/Catalog.API/Startup.cs +++ b/src/Services/Catalog/Catalog.API/Startup.cs @@ -50,7 +50,12 @@ services.AddHealthChecks(checks => { - checks.AddSqlCheck("CatalogDb", Configuration["ConnectionString"]); + var minutes = 1; + if (int.TryParse(Configuration["HealthCheck:Timeout"], out var minutesParsed)) + { + minutes = minutesParsed; + } + checks.AddSqlCheck("CatalogDb", Configuration["ConnectionString"], TimeSpan.FromMinutes(minutes)); }); services.AddMvc(options => diff --git a/src/Services/Identity/Identity.API/Configuration/Config.cs b/src/Services/Identity/Identity.API/Configuration/Config.cs index c26674591..744d0a0ce 100644 --- a/src/Services/Identity/Identity.API/Configuration/Config.cs +++ b/src/Services/Identity/Identity.API/Configuration/Config.cs @@ -60,7 +60,7 @@ namespace Identity.API.Configuration AllowAccessTokensViaBrowser = true, RedirectUris = { clientsUrl["Xamarin"] }, RequireConsent = false, - PostLogoutRedirectUris = { "http://13.88.8.119:5105/Account/Redirecting", "http://10.6.1.234:5105/Account/Redirecting" }, + PostLogoutRedirectUris = { $"{clientsUrl["Xamarin"]}/Account/Redirecting" }, AllowedCorsOrigins = { "http://eshopxamarin" }, AllowedScopes = { @@ -84,15 +84,11 @@ namespace Identity.API.Configuration AllowOfflineAccess = true, RedirectUris = new List { - $"{clientsUrl["Mvc"]}/signin-oidc", - "http://104.40.62.65:5100/signin-oidc", - "http://localhost:5100/signin-oidc", - "http://13.88.8.119:5100/signin-oidc" + $"{clientsUrl["Mvc"]}/signin-oidc" }, PostLogoutRedirectUris = new List { - $"{clientsUrl["Mvc"]}/signout-callback-oidc", - "http://localhost:5100/signout-callback-oidc" + $"{clientsUrl["Mvc"]}/signout-callback-oidc" }, AllowedScopes = new List { diff --git a/src/Services/Identity/Identity.API/Startup.cs b/src/Services/Identity/Identity.API/Startup.cs index b47f0535d..981e305c8 100644 --- a/src/Services/Identity/Identity.API/Startup.cs +++ b/src/Services/Identity/Identity.API/Startup.cs @@ -66,7 +66,12 @@ namespace eShopOnContainers.Identity services.AddHealthChecks(checks => { - checks.AddSqlCheck("Identity_Db", Configuration.GetConnectionString("DefaultConnection")); + var minutes = 1; + if (int.TryParse(Configuration["HealthCheck:Timeout"], out var minutesParsed)) + { + minutes = minutesParsed; + } + checks.AddSqlCheck("Identity_Db", Configuration.GetConnectionString("DefaultConnection"), TimeSpan.FromMinutes(minutes)); }); services.AddTransient(); diff --git a/src/Services/Ordering/Ordering.API/Application/Queries/IOrderQueries.cs b/src/Services/Ordering/Ordering.API/Application/Queries/IOrderQueries.cs index 8d78524ea..253b01e9c 100644 --- a/src/Services/Ordering/Ordering.API/Application/Queries/IOrderQueries.cs +++ b/src/Services/Ordering/Ordering.API/Application/Queries/IOrderQueries.cs @@ -1,13 +1,14 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries { + using System.Collections.Generic; using System.Threading.Tasks; public interface IOrderQueries { Task GetOrderAsync(int id); - Task GetOrdersAsync(); + Task> GetOrdersAsync(); - Task GetCardTypesAsync(); + Task> GetCardTypesAsync(); } } diff --git a/src/Services/Ordering/Ordering.API/Application/Queries/OrderQueries.cs b/src/Services/Ordering/Ordering.API/Application/Queries/OrderQueries.cs index 9d909e254..e51cf04ce 100644 --- a/src/Services/Ordering/Ordering.API/Application/Queries/OrderQueries.cs +++ b/src/Services/Ordering/Ordering.API/Application/Queries/OrderQueries.cs @@ -44,7 +44,7 @@ } } - public async Task GetOrdersAsync() + public async Task> GetOrdersAsync() { using (var connection = new SqlConnection(_connectionString)) { @@ -58,7 +58,7 @@ } } - public async Task GetCardTypesAsync() + public async Task> GetCardTypesAsync() { using (var connection = new SqlConnection(_connectionString)) { diff --git a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs index c5b21aeef..902eb007b 100644 --- a/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs +++ b/src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs @@ -68,8 +68,9 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers [HttpGet] public async Task GetOrders() { - var orders = await _orderQueries - .GetOrdersAsync(); + var orderTask = _orderQueries.GetOrdersAsync(); + + var orders = await orderTask; return Ok(orders); } diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingStartupFilter.cs b/src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingStartupFilter.cs new file mode 100644 index 000000000..028239f2d --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingStartupFilter.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ordering.API.Infrastructure.Middlewares +{ + public class FailingStartupFilter : IStartupFilter + { + public FailingStartupFilter() + { + } + + public Action Configure(Action next) + { + return app => + { + app.UseFailingMiddleware(); + next(app); + }; + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingWebHostBuilderExtensions.cs b/src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingWebHostBuilderExtensions.cs new file mode 100644 index 000000000..1c4979fae --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/FailingWebHostBuilderExtensions.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Ordering.API.Infrastructure.Middlewares; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Hosting +{ + public static class WebHostBuildertExtensions + { + public static IWebHostBuilder UseFailing(this IWebHostBuilder builder, string path) + { + builder.ConfigureServices(services => + { + services.AddSingleton(new FailingStartupFilter()); + }); + return builder; + } + + } +} diff --git a/src/Services/Ordering/Ordering.API/Program.cs b/src/Services/Ordering/Ordering.API/Program.cs index ba92a2da9..752c15e80 100644 --- a/src/Services/Ordering/Ordering.API/Program.cs +++ b/src/Services/Ordering/Ordering.API/Program.cs @@ -10,6 +10,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API { var host = new WebHostBuilder() .UseKestrel() + .UseFailing("/Failing") .UseHealthChecks("/hc") .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() diff --git a/src/Services/Ordering/Ordering.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs index 58d8f1cbe..0cc8dbc0a 100644 --- a/src/Services/Ordering/Ordering.API/Startup.cs +++ b/src/Services/Ordering/Ordering.API/Startup.cs @@ -61,7 +61,12 @@ services.AddHealthChecks(checks => { - checks.AddSqlCheck("OrderingDb", Configuration["ConnectionString"]); + var minutes = 1; + if (int.TryParse(Configuration["HealthCheck:Timeout"], out var minutesParsed)) + { + minutes = minutesParsed; + } + checks.AddSqlCheck("OrderingDb", Configuration["ConnectionString"], TimeSpan.FromMinutes(minutes)); }); services.AddEntityFrameworkSqlServer() @@ -143,8 +148,6 @@ app.UseCors("CorsPolicy"); - app.UseFailingMiddleware(); - ConfigureAuth(app); app.UseMvcWithDefaultRoute(); diff --git a/src/Web/WebMVC/Controllers/OrderController.cs b/src/Web/WebMVC/Controllers/OrderController.cs index 90be194dc..83152d697 100644 --- a/src/Web/WebMVC/Controllers/OrderController.cs +++ b/src/Web/WebMVC/Controllers/OrderController.cs @@ -51,7 +51,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers } catch(BrokenCircuitException) { - ModelState.AddModelError("Error", "It was not possible to create a new order, please try later on"); + ModelState.AddModelError("Error", "It was not possible to create a new order, please try later on. (Business Msg Due to Circuit-Breaker)"); } return View(model); } diff --git a/src/Web/WebMVC/Infrastructure/API.cs b/src/Web/WebMVC/Infrastructure/API.cs new file mode 100644 index 000000000..c837b8067 --- /dev/null +++ b/src/Web/WebMVC/Infrastructure/API.cs @@ -0,0 +1,68 @@ +namespace WebMVC.Infrastructure +{ + public static class API + { + public static class Basket + { + public static string GetBasket(string baseUri, string basketId) + { + return $"{baseUri}/{basketId}"; + } + + public static string UpdateBasket(string baseUri) + { + return baseUri; + } + + public static string CleanBasket(string baseUri, string basketId) + { + return $"{baseUri}/{basketId}"; + } + } + + public static class Order + { + public static string GetOrder(string baseUri, string orderId) + { + return $"{baseUri}/{orderId}"; + } + + public static string GetAllMyOrders(string baseUri) + { + return baseUri; + } + + public static string AddNewOrder(string baseUri) + { + return $"{baseUri}/new"; + } + } + + public static class Catalog + { + public static string GetAllCatalogItems(string baseUri, int page, int take, int? brand, int? type) + { + var filterQs = ""; + + if (brand.HasValue || type.HasValue) + { + var brandQs = (brand.HasValue) ? brand.Value.ToString() : "null"; + var typeQs = (type.HasValue) ? type.Value.ToString() : "null"; + filterQs = $"/type/{typeQs}/brand/{brandQs}"; + } + + return $"{baseUri}items{filterQs}?pageIndex={page}&pageSize={take}"; + } + + public static string GetAllBrands(string baseUri) + { + return $"{baseUri}catalogBrands"; + } + + public static string GetAllTypes(string baseUri) + { + return $"{baseUri}catalogTypes"; + } + } + } +} diff --git a/src/Web/WebMVC/Infrastructure/ResilientHttpClientFactory.cs b/src/Web/WebMVC/Infrastructure/ResilientHttpClientFactory.cs index 8efadf366..6322869ff 100644 --- a/src/Web/WebMVC/Infrastructure/ResilientHttpClientFactory.cs +++ b/src/Web/WebMVC/Infrastructure/ResilientHttpClientFactory.cs @@ -1,10 +1,7 @@ using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http; using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Polly; +using System; using System.Net.Http; namespace Microsoft.eShopOnContainers.WebMVC.Infrastructure @@ -17,14 +14,13 @@ namespace Microsoft.eShopOnContainers.WebMVC.Infrastructure =>_logger = logger; public ResilientHttpClient CreateResilientHttpClient() - => new ResilientHttpClient(CreatePolicies(), _logger); - + => new ResilientHttpClient((origin) => CreatePolicies(), _logger); private Policy[] CreatePolicies() => new Policy[] { - Policy.Handle() - .WaitAndRetryAsync( + Policy.Handle() + .WaitAndRetry( // number of retries 6, // exponential backofff @@ -40,7 +36,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Infrastructure _logger.LogDebug(msg); }), Policy.Handle() - .CircuitBreakerAsync( + .CircuitBreaker( // number of exceptions before breaking circuit 5, // time circuit opened before retry @@ -54,6 +50,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Infrastructure { // on circuit closed _logger.LogTrace("Circuit breaker reset"); - })}; + }) + }; } } diff --git a/src/Web/WebMVC/Services/BasketService.cs b/src/Web/WebMVC/Services/BasketService.cs index 7d82a0fc6..bd418ea26 100644 --- a/src/Web/WebMVC/Services/BasketService.cs +++ b/src/Web/WebMVC/Services/BasketService.cs @@ -5,9 +5,8 @@ using Microsoft.eShopOnContainers.WebMVC.ViewModels; using Microsoft.Extensions.Options; using Newtonsoft.Json; using System.Collections.Generic; -using System.Linq; -using System.Net.Http; using System.Threading.Tasks; +using WebMVC.Infrastructure; namespace Microsoft.eShopOnContainers.WebMVC.Services { @@ -28,15 +27,13 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services public async Task GetBasket(ApplicationUser user) { - var context = _httpContextAccesor.HttpContext; - var token = await context.Authentication.GetTokenAsync("access_token"); + var token = await GetUserTokenAsync(); + var getBasketUri = API.Basket.GetBasket(_remoteServiceBaseUrl, user.Id); - _apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); + var dataString = await _apiClient.GetStringAsync(getBasketUri, token); - var basketUrl = $"{_remoteServiceBaseUrl}/{user.Id}"; - var dataString = await _apiClient.GetStringAsync(basketUrl); // Use the ?? Null conditional operator to simplify the initialization of response - var response = JsonConvert.DeserializeObject(dataString) ?? + var response = JsonConvert.DeserializeObject(dataString) ?? new Basket() { BuyerId = user.Id @@ -47,14 +44,10 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services public async Task UpdateBasket(Basket basket) { - var context = _httpContextAccesor.HttpContext; - var token = await context.Authentication.GetTokenAsync("access_token"); - - _apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); + var token = await GetUserTokenAsync(); + var updateBasketUri = API.Basket.UpdateBasket(_remoteServiceBaseUrl); - var basketUrl = _remoteServiceBaseUrl; - - var response = await _apiClient.PostAsync(basketUrl, basket); + var response = await _apiClient.PostAsync(updateBasketUri, basket, token); response.EnsureSuccessStatusCode(); @@ -88,7 +81,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services order.OrderItems.Add(new OrderItem() { ProductId = int.Parse(x.ProductId), - + PictureUrl = x.PictureUrl, ProductName = x.ProductName, Units = x.Quantity, @@ -102,7 +95,8 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services public async Task AddItemToBasket(ApplicationUser user, BasketItem product) { - Basket basket = await GetBasket(user); + var basket = await GetBasket(user); + if (basket == null) { basket = new Basket() @@ -113,20 +107,25 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services } basket.Items.Add(product); + await UpdateBasket(basket); } public async Task CleanBasket(ApplicationUser user) { - var context = _httpContextAccesor.HttpContext; - var token = await context.Authentication.GetTokenAsync("access_token"); + var token = await GetUserTokenAsync(); + var cleanBasketUri = API.Basket.CleanBasket(_remoteServiceBaseUrl, user.Id); + + var response = await _apiClient.DeleteAsync(cleanBasketUri, token); - _apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); - var basketUrl = $"{_remoteServiceBaseUrl}/{user.Id}"; - var response = await _apiClient.DeleteAsync(basketUrl); - //CCE: response status code... } + + async Task GetUserTokenAsync() + { + var context = _httpContextAccesor.HttpContext; + return await context.Authentication.GetTokenAsync("access_token"); + } } } diff --git a/src/Web/WebMVC/Services/CatalogService.cs b/src/Web/WebMVC/Services/CatalogService.cs index f7225ff0b..2af428e2e 100644 --- a/src/Web/WebMVC/Services/CatalogService.cs +++ b/src/Web/WebMVC/Services/CatalogService.cs @@ -7,43 +7,32 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Collections.Generic; using System.Threading.Tasks; +using WebMVC.Infrastructure; namespace Microsoft.eShopOnContainers.WebMVC.Services { public class CatalogService : ICatalogService { private readonly IOptionsSnapshot _settings; - private IHttpClient _apiClient; + private readonly IHttpClient _apiClient; + private readonly ILogger _logger; + private readonly string _remoteServiceBaseUrl; - - public CatalogService(IOptionsSnapshot settings, ILoggerFactory loggerFactory, IHttpClient httpClient) { + + public CatalogService(IOptionsSnapshot settings, IHttpClient httpClient, ILogger logger) + { _settings = settings; - _remoteServiceBaseUrl = $"{_settings.Value.CatalogUrl}/api/v1/catalog/"; _apiClient = httpClient; - var log = loggerFactory.CreateLogger("catalog service"); - log.LogDebug(settings.Value.CatalogUrl); - } - - public async Task GetCatalogItems(int page,int take, int? brand, int? type) - { - var itemsQs = $"items?pageIndex={page}&pageSize={take}"; - var filterQs = ""; + _logger = logger; - if (brand.HasValue || type.HasValue) - { - var brandQs = (brand.HasValue) ? brand.Value.ToString() : "null"; - var typeQs = (type.HasValue) ? type.Value.ToString() : "null"; - filterQs = $"/type/{typeQs}/brand/{brandQs}"; - } - - var catalogUrl = $"{_remoteServiceBaseUrl}items{filterQs}?pageIndex={page}&pageSize={take}"; + _remoteServiceBaseUrl = $"{_settings.Value.CatalogUrl}/api/v1/catalog/"; + } - var dataString = ""; + public async Task GetCatalogItems(int page, int take, int? brand, int? type) + { + var allcatalogItemsUri = API.Catalog.GetAllCatalogItems(_remoteServiceBaseUrl, page, take, brand, type); - // - // Using a HttpClient wrapper with Retry and Exponential Backoff - // - dataString = await _apiClient.GetStringAsync(catalogUrl); + var dataString = await _apiClient.GetStringAsync(allcatalogItemsUri); var response = JsonConvert.DeserializeObject(dataString); @@ -52,14 +41,16 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services public async Task> GetBrands() { - var url = $"{_remoteServiceBaseUrl}catalogBrands"; - var dataString = await _apiClient.GetStringAsync(url); + var getBrandsUri = API.Catalog.GetAllBrands(_remoteServiceBaseUrl); + + var dataString = await _apiClient.GetStringAsync(getBrandsUri); var items = new List(); items.Add(new SelectListItem() { Value = null, Text = "All", Selected = true }); - JArray brands = JArray.Parse(dataString); - foreach (JObject brand in brands.Children()) + var brands = JArray.Parse(dataString); + + foreach (var brand in brands.Children()) { items.Add(new SelectListItem() { @@ -73,14 +64,15 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services public async Task> GetTypes() { - var url = $"{_remoteServiceBaseUrl}catalogTypes"; - var dataString = await _apiClient.GetStringAsync(url); + var getTypesUri = API.Catalog.GetAllTypes(_remoteServiceBaseUrl); + + var dataString = await _apiClient.GetStringAsync(getTypesUri); var items = new List(); items.Add(new SelectListItem() { Value = null, Text = "All", Selected = true }); - JArray brands = JArray.Parse(dataString); - foreach (JObject brand in brands.Children()) + var brands = JArray.Parse(dataString); + foreach (var brand in brands.Children()) { items.Add(new SelectListItem() { diff --git a/src/Web/WebMVC/Services/OrderingService.cs b/src/Web/WebMVC/Services/OrderingService.cs index 570a48361..8f198fbed 100644 --- a/src/Web/WebMVC/Services/OrderingService.cs +++ b/src/Web/WebMVC/Services/OrderingService.cs @@ -1,14 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.eShopOnContainers.WebMVC.ViewModels; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; +using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http; +using Microsoft.eShopOnContainers.WebMVC.ViewModels; using Microsoft.Extensions.Options; -using System.Net.Http; using Newtonsoft.Json; -using Microsoft.AspNetCore.Authentication; -using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using WebMVC.Infrastructure; namespace Microsoft.eShopOnContainers.WebMVC.Services { @@ -27,15 +26,13 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services _apiClient = httpClient; } - async public Task GetOrder(ApplicationUser user, string Id) + async public Task GetOrder(ApplicationUser user, string id) { - var context = _httpContextAccesor.HttpContext; - var token = await context.Authentication.GetTokenAsync("access_token"); - _apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); + var token = await GetUserTokenAsync(); + var getOrderUri = API.Order.GetOrder(_remoteServiceBaseUrl, id); + + var dataString = await _apiClient.GetStringAsync(getOrderUri, token); - var ordersUrl = $"{_remoteServiceBaseUrl}/{Id}"; - var dataString = await _apiClient.GetStringAsync(ordersUrl); - var response = JsonConvert.DeserializeObject(dataString); return response; @@ -43,16 +40,13 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services async public Task> GetMyOrders(ApplicationUser user) { - var context = _httpContextAccesor.HttpContext; - var token = await context.Authentication.GetTokenAsync("access_token"); + var token = await GetUserTokenAsync(); + var allMyOrdersUri = API.Order.GetAllMyOrders(_remoteServiceBaseUrl); - _apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); - - var ordersUrl = _remoteServiceBaseUrl; - var dataString = await _apiClient.GetStringAsync(ordersUrl); + var dataString = await _apiClient.GetStringAsync(allMyOrdersUri, token); var response = JsonConvert.DeserializeObject>(dataString); - return response; + return response; } public Order MapUserInfoIntoOrder(ApplicationUser user, Order order) @@ -62,10 +56,10 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services order.State = user.State; order.Country = user.Country; order.ZipCode = user.ZipCode; - + order.CardNumber = user.CardNumber; order.CardHolderName = user.CardHolderName; - order.CardExpiration = new DateTime(int.Parse("20" + user.Expiration.Split('/')[1]),int.Parse(user.Expiration.Split('/')[0]), 1); + order.CardExpiration = new DateTime(int.Parse("20" + user.Expiration.Split('/')[1]), int.Parse(user.Expiration.Split('/')[0]), 1); order.CardSecurityNumber = user.SecurityNumber; return order; @@ -73,21 +67,21 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services async public Task CreateOrder(Order order) { - var context = _httpContextAccesor.HttpContext; - var token = await context.Authentication.GetTokenAsync("access_token"); - - _apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); - _apiClient.Inst.DefaultRequestHeaders.Add("x-requestid", order.RequestId.ToString()); + var token = await GetUserTokenAsync(); + var requestId = order.RequestId.ToString(); + var addNewOrderUri = API.Order.AddNewOrder(_remoteServiceBaseUrl); - var ordersUrl = $"{_remoteServiceBaseUrl}/new"; order.CardTypeId = 1; order.CardExpirationApiFormat(); + SetFakeIdToProducts(order); - var response = await _apiClient.PostAsync(ordersUrl, order); + var response = await _apiClient.PostAsync(addNewOrderUri, order, token, requestId); if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError) - throw new Exception("Error creating order, try later"); + { + throw new Exception("Error creating order, try later."); + } response.EnsureSuccessStatusCode(); } @@ -106,10 +100,17 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services destination.CardSecurityNumber = original.CardSecurityNumber; } - private void SetFakeIdToProducts(Order order) + void SetFakeIdToProducts(Order order) { var id = 1; order.OrderItems.ForEach(x => { x.ProductId = id; id++; }); } + + async Task GetUserTokenAsync() + { + var context = _httpContextAccesor.HttpContext; + + return await context.Authentication.GetTokenAsync("access_token"); + } } } diff --git a/src/Web/WebMVC/Startup.cs b/src/Web/WebMVC/Startup.cs index e86c88c04..ba0b1244f 100644 --- a/src/Web/WebMVC/Startup.cs +++ b/src/Web/WebMVC/Startup.cs @@ -54,29 +54,34 @@ namespace Microsoft.eShopOnContainers.WebMVC services.AddHealthChecks(checks => { - checks.AddUrlCheck(Configuration["CatalogUrl"]); - checks.AddUrlCheck(Configuration["OrderingUrl"]); - checks.AddUrlCheck(Configuration["BasketUrl"]); - checks.AddUrlCheck(Configuration["IdentityUrl"]); + var minutes = 1; + if (int.TryParse(Configuration["HealthCheck:Timeout"], out var minutesParsed)) + { + minutes = minutesParsed; + } + checks.AddUrlCheck(Configuration["CatalogUrl"] + "/hc", TimeSpan.FromMinutes(minutes)); + checks.AddUrlCheck(Configuration["OrderingUrl"] + "/hc", TimeSpan.FromMinutes(minutes)); + checks.AddUrlCheck(Configuration["BasketUrl"] + "/hc", TimeSpan.FromMinutes(minutes)); + checks.AddUrlCheck(Configuration["IdentityUrl"] + "/hc", TimeSpan.FromMinutes(minutes)); }); // Add application services. - services.AddSingleton(); - services.AddTransient(); - services.AddTransient(); + services.AddSingleton(); + services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient, IdentityParser>(); if (Configuration.GetValue("UseResilientHttp") == bool.TrueString) { - services.AddTransient(); - services.AddTransient(sp => sp.GetService().CreateResilientHttpClient()); + services.AddSingleton(); + services.AddSingleton(sp => sp.GetService().CreateResilientHttpClient()); } else { - services.AddTransient(); + services.AddSingleton(); } - } + } // 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) @@ -113,10 +118,10 @@ namespace Microsoft.eShopOnContainers.WebMVC AuthenticationScheme = "oidc", SignInScheme = "Cookies", Authority = identityUrl.ToString(), - PostLogoutRedirectUri = callBackUrl.ToString(), + PostLogoutRedirectUri = callBackUrl.ToString(), ClientId = "mvc", ClientSecret = "secret", - ResponseType = "code id_token", + ResponseType = "code id_token", SaveTokens = true, GetClaimsFromUserInfoEndpoint = true, RequireHttpsMetadata = false, diff --git a/src/Web/WebMVC/Views/Shared/_Layout.cshtml b/src/Web/WebMVC/Views/Shared/_Layout.cshtml index 456248b94..990018b09 100644 --- a/src/Web/WebMVC/Views/Shared/_Layout.cshtml +++ b/src/Web/WebMVC/Views/Shared/_Layout.cshtml @@ -55,7 +55,7 @@
- +
diff --git a/src/Web/WebMVC/WebMVC.csproj b/src/Web/WebMVC/WebMVC.csproj index 3f0f6b84b..f109548e6 100644 --- a/src/Web/WebMVC/WebMVC.csproj +++ b/src/Web/WebMVC/WebMVC.csproj @@ -13,12 +13,12 @@ ..\..\..\docker-compose.dcproj - + diff --git a/src/Web/WebMVC/wwwroot/css/site.min.css b/src/Web/WebMVC/wwwroot/css/site.min.css index f5fc90999..2a4a53b47 100644 --- a/src/Web/WebMVC/wwwroot/css/site.min.css +++ b/src/Web/WebMVC/wwwroot/css/site.min.css @@ -1 +1 @@ -.esh-app-footer{background-color:#000;border-top:1px solid #eee;margin-top:2.5rem;padding-bottom:2.5rem;padding-top:2.5rem;width:100%}.esh-app-footer-brand{height:50px;width:230px}.esh-app-footer-text{color:#83d01b;line-height:50px;text-align:right;width:100%}@font-face{font-family:Montserrat;font-weight:400;src:url("../fonts/Montserrat-Regular.eot?") format("eot"),url("../fonts/Montserrat-Regular.woff") format("woff"),url("../fonts/Montserrat-Regular.ttf") format("truetype"),url("../fonts/Montserrat-Regular.svg#Montserrat") format("svg")}@font-face{font-family:Montserrat;font-weight:700;src:url("../fonts/Montserrat-Bold.eot?") format("eot"),url("../fonts/Montserrat-Bold.woff") format("woff"),url("../fonts/Montserrat-Bold.ttf") format("truetype"),url("../fonts/Montserrat-Bold.svg#Montserrat") format("svg")}html,body{font-family:Montserrat,sans-serif;font-size:16px;font-weight:400;z-index:10}*,*::after,*::before{box-sizing:border-box}.preloading{color:#00a69c;display:block;font-size:1.5rem;left:50%;position:fixed;top:50%;transform:translate(-50%,-50%)}select::-ms-expand{display:none}@media screen and (min-width:992px){.form-input{max-width:360px;width:360px}}.form-input{border-radius:0;height:45px;padding:10px}.form-input-small{max-width:100px !important}.form-input-medium{width:150px !important}.alert{padding-left:0}.alert-danger{background-color:transparent;border:0;color:#fb0d0d;font-size:12px}a,a:active,a:hover,a:visited{color:#000;text-decoration:none;transition:color .35s}a:hover,a:active{color:#75b918;transition:color .35s}.esh-basket{min-height:80vh}.esh-basket-titles{padding-bottom:1rem;padding-top:2rem}.esh-basket-titles--clean{padding-bottom:0;padding-top:0}.esh-basket-title{text-transform:uppercase}.esh-basket-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-basket-items--border:last-of-type{border-color:transparent}.esh-basket-item{font-size:1rem;font-weight:300}.esh-basket-item--middle{line-height:8rem}@media screen and (max-width:1024px){.esh-basket-item--middle{line-height:1rem}}.esh-basket-item--mark{color:#00a69c}.esh-basket-image{height:8rem}.esh-basket-input{line-height:1rem;width:100%}.esh-basket-checkout{border:none;border-radius:0;background-color:#83d01b;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-basket-checkout:hover{background-color:#4a760f;transition:all .35s}.esh-basket-margin12{margin-left:12px}.esh-basketstatus{cursor:pointer;display:inline-block;float:right;position:relative;transition:all .35s}.esh-basketstatus.is-disabled{opacity:.5;pointer-events:none}.esh-basketstatus-image{height:36px;margin-top:.5rem}.esh-basketstatus-badge{background-color:#83d01b;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem}.esh-basketstatus:hover .esh-basketstatus-badge{background-color:transparent;color:#75b918;transition:all .35s}.esh-catalog-hero{background-image:url("../images/main_banner.png");background-size:cover;height:260px;width:100%}.esh-catalog-title{position:relative;top:74.28571px}.esh-catalog-filters{background-color:#00a69c;height:65px}.esh-catalog-filter{background-color:transparent;border-color:#00d9cc;color:#fff;cursor:pointer;margin-right:1rem;margin-top:.5rem;outline-color:#83d01b;padding-bottom:0;padding-left:.5rem;padding-right:.5rem;padding-top:1.5rem;min-width:140px;-webkit-appearance:none}.esh-catalog-filter option{background-color:#00a69c}.esh-catalog-label{display:inline-block;position:relative;z-index:0}.esh-catalog-label::before{color:rgba(255,255,255,.5);content:attr(data-title);font-size:.65rem;margin-top:.65rem;margin-left:.5rem;position:absolute;text-transform:uppercase;z-index:1}.esh-catalog-label::after{background-image:url("../images/arrow-down.png");height:7px;content:'';position:absolute;right:1.5rem;top:2.5rem;width:10px;z-index:1}.esh-catalog-send{background-color:#83d01b;color:#fff;cursor:pointer;font-size:1rem;transform:translateY(.5rem);padding:.5rem;transition:all .35s}.esh-catalog-send:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-items{margin-top:1rem}.esh-catalog-item{text-align:center;margin-bottom:1.5rem;width:33%;display:inline-block;float:none !important}@media screen and (max-width:1024px){.esh-catalog-item{width:50%}}@media screen and (max-width:768px){.esh-catalog-item{width:100%}}.esh-catalog-thumbnail{max-width:370px;width:100%}.esh-catalog-button{background-color:#83d01b;border:none;color:#fff;cursor:pointer;font-size:1rem;height:3rem;margin-top:1rem;transition:all .35s;width:80%}.esh-catalog-button.is-disabled{opacity:.5;pointer-events:none}.esh-catalog-button:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-name{font-size:1rem;font-weight:300;margin-top:.5rem;text-align:center;text-transform:uppercase}.esh-catalog-price{text-align:center;font-weight:900;font-size:28px}.esh-catalog-price::before{content:'$'}.esh-orders{min-height:80vh;overflow-x:hidden}.esh-orders-header{background-color:#00a69c;height:4rem}.esh-orders-back{color:rgba(255,255,255,.4);line-height:4rem;text-transform:uppercase;text-decoration:none;transition:color .35s}.esh-orders-back:hover{color:#fff;transition:color .35s}.esh-orders-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders-title{text-transform:uppercase}.esh-orders-items{height:2rem;line-height:2rem;position:relative}.esh-orders-items:nth-of-type(2n+1):before{background-color:#eef;content:'';height:100%;left:0;margin-left:-100vw;position:absolute;top:0;width:200vw;z-index:-1}.esh-orders-item{font-weight:300}.esh-orders-item--hover{opacity:0;pointer-events:none}.esh-orders-items:hover .esh-orders-item--hover{opacity:1;pointer-events:all}.esh-orders-link{color:#83d01b;text-decoration:none;transition:color .35s}.esh-orders-link:hover{color:#75b918;transition:color .35s}.esh-orders_new{min-height:80vh}.esh-orders_new-header{background-color:#00a69c;height:4rem}.esh-orders_new-back{color:rgba(255,255,255,.4);line-height:4rem;text-decoration:none;text-transform:uppercase;transition:color .35s}.esh-orders_new-back:hover{color:#fff;transition:color .35s}.esh-orders_new-section{padding:1rem 0}.esh-orders_new-section--right{text-align:right}.esh-orders_new-placeOrder{background-color:#83d01b;border:0;border-radius:0;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-orders_new-placeOrder:hover{background-color:#4a760f;transition:all .35s}.esh-orders_new-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders_new-title{font-size:1.25rem;text-transform:uppercase}.esh-orders_new-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-orders_new-items--border:last-of-type{border-color:transparent}.esh-orders_new-item{font-size:1rem;font-weight:300}.esh-orders_new-item--middle{line-height:8rem}@media screen and (max-width:768px){.esh-orders_new-item--middle{line-height:1rem}}.esh-orders_new-item--mark{color:#83d01b}.esh-orders_new-image{height:8rem}.esh-orders_detail{min-height:80vh}.esh-orders_detail-section{padding:1rem 0}.esh-orders_detail-section--right{text-align:right}.esh-orders_detail-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders_detail-title{text-transform:uppercase}.esh-orders_detail-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-orders_detail-items--border:last-of-type{border-color:transparent}.esh-orders_detail-item{font-size:1rem;font-weight:300}.esh-orders_detail-item--middle{line-height:8rem}@media screen and (max-width:768px){.esh-orders_detail-item--middle{line-height:1rem}}.esh-orders_detail-item--mark{color:#83d01b}.esh-orders_detail-image{height:8rem}.esh-identity{line-height:3rem;position:relative;text-align:right}.esh-identity-section{display:inline-block;width:100%}.esh-identity-name{display:inline-block}.esh-identity-name--upper{text-transform:uppercase}@media screen and (max-width:768px){.esh-identity-name{font-size:.85rem}}.esh-identity-image{display:inline-block}.esh-identity-drop{background:#fff;height:0;min-width:14rem;right:0;overflow:hidden;padding:.5rem;position:absolute;top:2.5rem;transition:height .35s}.esh-identity:hover .esh-identity-drop{border:1px solid #eee;height:7rem;transition:height .35s}.esh-identity-item{cursor:pointer;display:block;transition:color .35s}.esh-identity-item:hover{color:#75b918;transition:color .35s}.esh-header{background-color:#00a69c;height:4rem}.esh-header-back{color:rgba(255,255,255,.5) !important;line-height:4rem;text-transform:uppercase;text-decoration:none;transition:color .35s}.esh-header-back:hover{color:#fff !important;transition:color .35s}.esh-pager-wrapper{padding-top:1rem;text-align:center}.esh-pager-item{margin:0 5vw}.esh-pager-item--navigable{display:inline-block;cursor:pointer}.esh-pager-item--navigable.is-disabled{opacity:0;pointer-events:none}.esh-pager-item--navigable:hover{color:#83d01b}@media screen and (max-width:1280px){.esh-pager-item{font-size:.85rem}}@media screen and (max-width:1024px){.esh-pager-item{margin:0 4vw}} \ No newline at end of file +.esh-app-footer{background-color:#000;border-top:1px solid #eee;margin-top:2.5rem;padding-bottom:2.5rem;padding-top:2.5rem;width:100%}.esh-app-footer-brand{height:50px;width:230px}.esh-app-footer-text{color:#83d01b;line-height:50px;text-align:right;width:100%}@font-face{font-family:Montserrat;font-weight:400;src:url("../fonts/Montserrat-Regular.eot?") format("eot"),url("../fonts/Montserrat-Regular.woff") format("woff"),url("../fonts/Montserrat-Regular.ttf") format("truetype"),url("../fonts/Montserrat-Regular.svg#Montserrat") format("svg")}@font-face{font-family:Montserrat;font-weight:700;src:url("../fonts/Montserrat-Bold.eot?") format("eot"),url("../fonts/Montserrat-Bold.woff") format("woff"),url("../fonts/Montserrat-Bold.ttf") format("truetype"),url("../fonts/Montserrat-Bold.svg#Montserrat") format("svg")}html,body{font-family:Montserrat,sans-serif;font-size:16px;font-weight:400;z-index:10}*,*::after,*::before{box-sizing:border-box}.preloading{color:#00a69c;display:block;font-size:1.5rem;left:50%;position:fixed;top:50%;transform:translate(-50%,-50%)}select::-ms-expand{display:none}@media screen and (min-width:992px){.form-input{max-width:360px;width:360px}}.form-input{border-radius:0;height:45px;padding:10px}.form-input-small{max-width:100px !important}.form-input-medium{width:150px !important}.alert{padding-left:0}.alert-danger{background-color:transparent;border:0;color:#fb0d0d;font-size:12px}a,a:active,a:hover,a:visited{color:#000;text-decoration:none;transition:color .35s}a:hover,a:active{color:#75b918;transition:color .35s}.esh-pager-wrapper{padding-top:1rem;text-align:center}.esh-pager-item{margin:0 5vw}.esh-pager-item--navigable{display:inline-block;cursor:pointer}.esh-pager-item--navigable.is-disabled{opacity:0;pointer-events:none}.esh-pager-item--navigable:hover{color:#83d01b}@media screen and (max-width:1280px){.esh-pager-item{font-size:.85rem}}@media screen and (max-width:1024px){.esh-pager-item{margin:0 4vw}}.esh-identity{line-height:3rem;position:relative;text-align:right}.esh-identity-section{display:inline-block;width:100%}.esh-identity-name{display:inline-block}.esh-identity-name--upper{text-transform:uppercase}@media screen and (max-width:768px){.esh-identity-name{font-size:.85rem}}.esh-identity-image{display:inline-block}.esh-identity-drop{background:#fff;height:0;min-width:14rem;right:0;overflow:hidden;padding:.5rem;position:absolute;top:2.5rem;transition:height .35s}.esh-identity:hover .esh-identity-drop{border:1px solid #eee;height:7rem;transition:height .35s}.esh-identity-item{cursor:pointer;display:block;transition:color .35s}.esh-identity-item:hover{color:#75b918;transition:color .35s}.esh-header{background-color:#00a69c;height:4rem}.esh-header-back{color:rgba(255,255,255,.5) !important;line-height:4rem;text-transform:uppercase;text-decoration:none;transition:color .35s}.esh-header-back:hover{color:#fff !important;transition:color .35s}.esh-orders{min-height:80vh;overflow-x:hidden}.esh-orders-header{background-color:#00a69c;height:4rem}.esh-orders-back{color:rgba(255,255,255,.4);line-height:4rem;text-transform:uppercase;text-decoration:none;transition:color .35s}.esh-orders-back:hover{color:#fff;transition:color .35s}.esh-orders-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders-title{text-transform:uppercase}.esh-orders-items{height:2rem;line-height:2rem;position:relative}.esh-orders-items:nth-of-type(2n+1):before{background-color:#eef;content:'';height:100%;left:0;margin-left:-100vw;position:absolute;top:0;width:200vw;z-index:-1}.esh-orders-item{font-weight:300}.esh-orders-item--hover{opacity:0;pointer-events:none}.esh-orders-items:hover .esh-orders-item--hover{opacity:1;pointer-events:all}.esh-orders-link{color:#83d01b;text-decoration:none;transition:color .35s}.esh-orders-link:hover{color:#75b918;transition:color .35s}.esh-orders_new{min-height:80vh}.esh-orders_new-header{background-color:#00a69c;height:4rem}.esh-orders_new-back{color:rgba(255,255,255,.4);line-height:4rem;text-decoration:none;text-transform:uppercase;transition:color .35s}.esh-orders_new-back:hover{color:#fff;transition:color .35s}.esh-orders_new-section{padding:1rem 0}.esh-orders_new-section--right{text-align:right}.esh-orders_new-placeOrder{background-color:#83d01b;border:0;border-radius:0;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-orders_new-placeOrder:hover{background-color:#4a760f;transition:all .35s}.esh-orders_new-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders_new-title{font-size:1.25rem;text-transform:uppercase}.esh-orders_new-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-orders_new-items--border:last-of-type{border-color:transparent}.esh-orders_new-item{font-size:1rem;font-weight:300}.esh-orders_new-item--middle{line-height:8rem}@media screen and (max-width:768px){.esh-orders_new-item--middle{line-height:1rem}}.esh-orders_new-item--mark{color:#83d01b}.esh-orders_new-image{height:8rem}.esh-orders_detail{min-height:80vh}.esh-orders_detail-section{padding:1rem 0}.esh-orders_detail-section--right{text-align:right}.esh-orders_detail-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders_detail-title{text-transform:uppercase}.esh-orders_detail-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-orders_detail-items--border:last-of-type{border-color:transparent}.esh-orders_detail-item{font-size:1rem;font-weight:300}.esh-orders_detail-item--middle{line-height:8rem}@media screen and (max-width:768px){.esh-orders_detail-item--middle{line-height:1rem}}.esh-orders_detail-item--mark{color:#83d01b}.esh-orders_detail-image{height:8rem}.esh-catalog-hero{background-image:url("../images/main_banner.png");background-size:cover;height:260px;width:100%}.esh-catalog-title{position:relative;top:74.28571px}.esh-catalog-filters{background-color:#00a69c;height:65px}.esh-catalog-filter{background-color:transparent;border-color:#00d9cc;color:#fff;cursor:pointer;margin-right:1rem;margin-top:.5rem;outline-color:#83d01b;padding-bottom:0;padding-left:.5rem;padding-right:.5rem;padding-top:1.5rem;min-width:140px;-webkit-appearance:none}.esh-catalog-filter option{background-color:#00a69c}.esh-catalog-label{display:inline-block;position:relative;z-index:0}.esh-catalog-label::before{color:rgba(255,255,255,.5);content:attr(data-title);font-size:.65rem;margin-top:.65rem;margin-left:.5rem;position:absolute;text-transform:uppercase;z-index:1}.esh-catalog-label::after{background-image:url("../images/arrow-down.png");height:7px;content:'';position:absolute;right:1.5rem;top:2.5rem;width:10px;z-index:1}.esh-catalog-send{background-color:#83d01b;color:#fff;cursor:pointer;font-size:1rem;transform:translateY(.5rem);padding:.5rem;transition:all .35s}.esh-catalog-send:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-items{margin-top:1rem}.esh-catalog-item{text-align:center;margin-bottom:1.5rem;width:33%;display:inline-block;float:none !important}@media screen and (max-width:1024px){.esh-catalog-item{width:50%}}@media screen and (max-width:768px){.esh-catalog-item{width:100%}}.esh-catalog-thumbnail{max-width:370px;width:100%}.esh-catalog-button{background-color:#83d01b;border:none;color:#fff;cursor:pointer;font-size:1rem;height:3rem;margin-top:1rem;transition:all .35s;width:80%}.esh-catalog-button.is-disabled{opacity:.5;pointer-events:none}.esh-catalog-button:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-name{font-size:1rem;font-weight:300;margin-top:.5rem;text-align:center;text-transform:uppercase}.esh-catalog-price{text-align:center;font-weight:900;font-size:28px}.esh-catalog-price::before{content:'$'}.esh-basket{min-height:80vh}.esh-basket-titles{padding-bottom:1rem;padding-top:2rem}.esh-basket-titles--clean{padding-bottom:0;padding-top:0}.esh-basket-title{text-transform:uppercase}.esh-basket-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-basket-items--border:last-of-type{border-color:transparent}.esh-basket-item{font-size:1rem;font-weight:300}.esh-basket-item--middle{line-height:8rem}@media screen and (max-width:1024px){.esh-basket-item--middle{line-height:1rem}}.esh-basket-item--mark{color:#00a69c}.esh-basket-image{height:8rem}.esh-basket-input{line-height:1rem;width:100%}.esh-basket-checkout{border:none;border-radius:0;background-color:#83d01b;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-basket-checkout:hover{background-color:#4a760f;transition:all .35s}.esh-basket-margin12{margin-left:12px}.esh-basketstatus{cursor:pointer;display:inline-block;float:right;position:relative;transition:all .35s}.esh-basketstatus.is-disabled{opacity:.5;pointer-events:none}.esh-basketstatus-image{height:36px;margin-top:.5rem}.esh-basketstatus-badge{background-color:#83d01b;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem}.esh-basketstatus:hover .esh-basketstatus-badge{background-color:transparent;color:#75b918;transition:all .35s} \ No newline at end of file diff --git a/src/Web/WebSPA/Startup.cs b/src/Web/WebSPA/Startup.cs index ee932f476..53476266a 100644 --- a/src/Web/WebSPA/Startup.cs +++ b/src/Web/WebSPA/Startup.cs @@ -45,10 +45,16 @@ namespace eShopConContainers.WebSPA { services.AddHealthChecks(checks => { - checks.AddUrlCheck(Configuration["CatalogUrl"]); - checks.AddUrlCheck(Configuration["OrderingUrl"]); - checks.AddUrlCheck(Configuration["BasketUrl"]); - checks.AddUrlCheck(Configuration["IdentityUrl"]); + var minutes = 1; + if (int.TryParse(Configuration["HealthCheck:Timeout"], out var minutesParsed)) + { + minutes = minutesParsed; + } + + checks.AddUrlCheck(Configuration["CatalogUrlHC"], TimeSpan.FromMinutes(minutes)); + checks.AddUrlCheck(Configuration["OrderingUrlHC"], TimeSpan.FromMinutes(minutes)); + checks.AddUrlCheck(Configuration["BasketUrlHC"], TimeSpan.FromMinutes(minutes)); + checks.AddUrlCheck(Configuration["IdentityUrlHC"], TimeSpan.FromMinutes(minutes)); }); services.Configure(Configuration); diff --git a/src/Web/WebSPA/WebSPA.csproj b/src/Web/WebSPA/WebSPA.csproj index 03c3cd442..bf126f271 100644 --- a/src/Web/WebSPA/WebSPA.csproj +++ b/src/Web/WebSPA/WebSPA.csproj @@ -73,12 +73,14 @@ + + diff --git a/src/Web/WebStatus/Controllers/HomeController.cs b/src/Web/WebStatus/Controllers/HomeController.cs index 34a31f1f5..cd59c44d2 100644 --- a/src/Web/WebStatus/Controllers/HomeController.cs +++ b/src/Web/WebStatus/Controllers/HomeController.cs @@ -28,6 +28,7 @@ namespace WebStatus.Controllers data.AddResult(checkResult.Key, checkResult.Value); } + ViewBag.RefreshSeconds = 60; return View(data); } diff --git a/src/Web/WebStatus/Extensions/HealthCheckBuilderExtensions.cs b/src/Web/WebStatus/Extensions/HealthCheckBuilderExtensions.cs index 369f59722..c0655b753 100644 --- a/src/Web/WebStatus/Extensions/HealthCheckBuilderExtensions.cs +++ b/src/Web/WebStatus/Extensions/HealthCheckBuilderExtensions.cs @@ -8,11 +8,11 @@ namespace WebStatus.Extensions { public static class HealthCheckBuilderExtensions { - public static HealthCheckBuilder AddUrlCheckIfNotNull(this HealthCheckBuilder builder, string url) + public static HealthCheckBuilder AddUrlCheckIfNotNull(this HealthCheckBuilder builder, string url, TimeSpan cacheDuration) { if (!string.IsNullOrEmpty(url)) { - builder.AddUrlCheck(url); + builder.AddUrlCheck(url, cacheDuration); } return builder; diff --git a/src/Web/WebStatus/Startup.cs b/src/Web/WebStatus/Startup.cs index 06416940c..0b9ecb937 100644 --- a/src/Web/WebStatus/Startup.cs +++ b/src/Web/WebStatus/Startup.cs @@ -32,12 +32,18 @@ namespace WebStatus // Add framework services. services.AddHealthChecks(checks => { - checks.AddUrlCheckIfNotNull(Configuration["OrderingUrl"]); - checks.AddUrlCheckIfNotNull(Configuration["BasketUrl"]); - checks.AddUrlCheckIfNotNull(Configuration["CatalogUrl"]); - checks.AddUrlCheckIfNotNull(Configuration["IdentityUrl"]); - checks.AddUrlCheckIfNotNull(Configuration["mvc"]); - checks.AddUrlCheckIfNotNull(Configuration["spa"]); + var minutes = 1; + if (int.TryParse(Configuration["HealthCheck:Timeout"], out var minutesParsed)) + { + minutes = minutesParsed; + } + + checks.AddUrlCheckIfNotNull(Configuration["OrderingUrl"], TimeSpan.FromMinutes(minutes)); + checks.AddUrlCheckIfNotNull(Configuration["BasketUrl"], TimeSpan.FromMinutes(minutes)); + checks.AddUrlCheckIfNotNull(Configuration["CatalogUrl"], TimeSpan.FromMinutes(minutes)); + checks.AddUrlCheckIfNotNull(Configuration["IdentityUrl"], TimeSpan.FromMinutes(minutes)); + checks.AddUrlCheckIfNotNull(Configuration["mvc"], TimeSpan.FromMinutes(minutes)); + checks.AddUrlCheckIfNotNull(Configuration["spa"], TimeSpan.FromMinutes(minutes)); }); services.AddMvc(); } diff --git a/src/Web/WebStatus/Views/Shared/_Layout.cshtml b/src/Web/WebStatus/Views/Shared/_Layout.cshtml index a7eb2e3b6..7429afcaf 100644 --- a/src/Web/WebStatus/Views/Shared/_Layout.cshtml +++ b/src/Web/WebStatus/Views/Shared/_Layout.cshtml @@ -16,6 +16,12 @@ asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" /> + + @if (ViewBag.RefreshSeconds != null && ViewBag.RefreshSeconds > 0) + { + + } + @Html.Raw(JavaScriptSnippet.FullScript) @@ -42,9 +48,10 @@
@RenderBody()
-
-

© 2017 - WebStatus

-
+
+ diff --git a/test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs b/test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs index 2c8c1280b..afc43c2eb 100644 --- a/test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs +++ b/test/Services/FunctionalTests/Services/Catalog/CatalogScenariosBase.cs @@ -32,9 +32,9 @@ namespace FunctionalTests.Services.Catalog } } - public static class Post + public static class Put { - public static string UpdateCatalogProduct = "api/v1/catalog/update"; + public static string UpdateCatalogProduct = "api/v1/catalog/items"; } } } diff --git a/test/Services/FunctionalTests/Services/IntegrationEventsScenarios.cs b/test/Services/FunctionalTests/Services/IntegrationEventsScenarios.cs index 8ed11be3a..fd270d159 100644 --- a/test/Services/FunctionalTests/Services/IntegrationEventsScenarios.cs +++ b/test/Services/FunctionalTests/Services/IntegrationEventsScenarios.cs @@ -43,7 +43,7 @@ namespace FunctionalTests.Services var itemToModify = basket.Items[2]; var oldPrice = itemToModify.UnitPrice; var newPrice = oldPrice + priceModification; - var pRes = await catalogClient.PostAsync(CatalogScenariosBase.Post.UpdateCatalogProduct, new StringContent(ChangePrice(itemToModify, newPrice, originalCatalogProducts), UTF8Encoding.UTF8, "application/json")); + var pRes = await catalogClient.PutAsync(CatalogScenariosBase.Put.UpdateCatalogProduct, new StringContent(ChangePrice(itemToModify, newPrice, originalCatalogProducts), UTF8Encoding.UTF8, "application/json")); var modifiedCatalogProducts = await GetCatalogAsync(catalogClient); diff --git a/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs b/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs index 49f04fa3b..52119997d 100644 --- a/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs +++ b/test/Services/IntegrationTests/Services/Ordering/OrderingScenarios.cs @@ -93,7 +93,7 @@ string BuildOrderWithInvalidExperationTime() { var order = new CreateOrderCommand( - null, + new List(), cardExpiration: DateTime.UtcNow.AddYears(-1), cardNumber: "5145-555-5555", cardHolderName: "Jhon Senna", diff --git a/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs b/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs index 9a4a70bf8..1615bcd0b 100644 --- a/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs +++ b/test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs @@ -14,6 +14,8 @@ namespace UnitTest.Ordering.Application using System.Collections; using System.Collections.Generic; using Xunit; + using static Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands.CreateOrderCommand; + public class NewOrderRequestHandlerTest { private readonly Mock _orderRepositoryMock; @@ -72,7 +74,7 @@ namespace UnitTest.Ordering.Application private CreateOrderCommand FakeOrderRequestWithBuyer(Dictionary args = null) { return new CreateOrderCommand( - null, + new List(), city: args != null && args.ContainsKey("city") ? (string)args["city"] : null, street: args != null && args.ContainsKey("street") ? (string)args["street"] : null, state: args != null && args.ContainsKey("state") ? (string)args["state"] : null, diff --git a/test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs b/test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs index 8c7659862..c0656f050 100644 --- a/test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs +++ b/test/Services/UnitTest/Ordering/Application/OrdersWebApiTest.cs @@ -6,6 +6,7 @@ using Microsoft.eShopOnContainers.Services.Ordering.API.Controllers; using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; using Moq; using System; +using System.Linq; using System.Threading.Tasks; using Xunit; @@ -59,7 +60,7 @@ namespace UnitTest.Ordering.Application public async Task Get_orders_success() { //Arrange - var fakeDynamicResult = new Object(); + var fakeDynamicResult = Enumerable.Empty(); _orderQueriesMock.Setup(x => x.GetOrdersAsync()) .Returns(Task.FromResult(fakeDynamicResult)); @@ -92,7 +93,7 @@ namespace UnitTest.Ordering.Application public async Task Get_cardTypes_success() { //Arrange - var fakeDynamicResult = new Object(); + var fakeDynamicResult = Enumerable.Empty(); _orderQueriesMock.Setup(x => x.GetCardTypesAsync()) .Returns(Task.FromResult(fakeDynamicResult));