├── .gitignore ├── LICENSE ├── README.md └── src ├── Dockerfile ├── helm └── samples-microservice │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── deployment.yaml │ ├── ingress.yaml │ └── service.yaml │ └── values.yaml ├── samples.microservice.api ├── Controllers │ ├── ErrorController.cs │ └── VehicleController.cs ├── ErrorHandlingMiddleware.cs ├── Program.cs ├── Startup.cs ├── samples.microservice.api.csproj └── secrets.template-LEGACY.json ├── samples.microservice.core ├── BaseRepository.cs ├── Entity.cs ├── IRepository.cs ├── RepositoryExtensions.cs └── samples.microservice.core.csproj ├── samples.microservice.entities ├── VehicleData.cs └── samples.microservice.entities.csproj ├── samples.microservice.repository ├── CosmosRepository.cs └── samples.microservice.repository.csproj ├── samples.microservice.sln ├── vehicle-api-deployment.yaml └── vehicle-api-service.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff: 7 | .idea/**/workspace.xml 8 | .idea/**/tasks.xml 9 | .idea/dictionaries 10 | 11 | # Sensitive or high-churn files: 12 | .idea/**/dataSources/ 13 | .idea/**/dataSources.ids 14 | .idea/**/dataSources.xml 15 | .idea/**/dataSources.local.xml 16 | .idea/**/sqlDataSources.xml 17 | .idea/**/dynamic.xml 18 | .idea/**/uiDesigner.xml 19 | 20 | # Gradle: 21 | .idea/**/gradle.xml 22 | .idea/**/libraries 23 | 24 | # CMake 25 | cmake-build-debug/ 26 | cmake-build-release/ 27 | 28 | # Mongo Explorer plugin: 29 | .idea/**/mongoSettings.xml 30 | 31 | ## File-based project format: 32 | *.iws 33 | 34 | ## Plugin-specific files: 35 | 36 | # IntelliJ 37 | out/ 38 | 39 | # mpeltonen/sbt-idea plugin 40 | .idea_modules/ 41 | 42 | # JIRA plugin 43 | atlassian-ide-plugin.xml 44 | 45 | # Cursive Clojure plugin 46 | .idea/replstate.xml 47 | 48 | # Crashlytics plugin (for Android Studio and IntelliJ) 49 | com_crashlytics_export_strings.xml 50 | crashlytics.properties 51 | crashlytics-build.properties 52 | fabric.properties 53 | 54 | .DS_Store 55 | .idea/.idea.samples.microservice/.idea/workspace.xml 56 | Would skip repository samples.microservice.api/ 57 | .idea/.idea.samples.microservice/.idea/deployment.xml 58 | samples.microservice.api/wwwroot/ 59 | 60 | # Build results 61 | [Dd]ebug/ 62 | [Dd]ebugPublic/ 63 | [Rr]elease/ 64 | [Rr]eleases/ 65 | x64/ 66 | x86/ 67 | build/ 68 | bld/ 69 | [Bb]in/ 70 | [Oo]bj/ 71 | 72 | # MSTest test Results 73 | [Tt]est[Rr]esult*/ 74 | [Bb]uild[Ll]og.* 75 | 76 | # Publish Web Output 77 | *.[Pp]ublish.xml 78 | *.azurePubxml 79 | # TODO: Comment the next line if you want to checkin your web deploy settings 80 | # but database connection strings (with potential passwords) will be unencrypted 81 | *.pubxml 82 | *.publishproj 83 | 84 | # NuGet Packages 85 | *.nupkg 86 | # The packages folder can be ignored because of Package Restore 87 | **/packages/* 88 | # except build/, which is used as an MSBuild target. 89 | !**/packages/build/ 90 | # Uncomment if necessary however generally it will be regenerated when needed 91 | #!**/packages/repositories.config 92 | 93 | #Mac OSX 94 | .DS_Store 95 | 96 | # User-specific files 97 | *.suo 98 | *.user 99 | *.userosscache 100 | *.sln.docstates 101 | 102 | # User-specific files (MonoDevelop/Xamarin Studio) 103 | *.userprefs -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Nik Sachdeva 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Zero Config Microservice: using Kubernetes and Azure KeyVault 2 | >For more details on the architecture and the concepts behind this sample, check out my blog here: https://connectedstuff.io/2018/02/06/the-zero-config-microservice-using-kubernetes-and-azure-keyvault-2/ 3 | 4 | ### A sample microservice project to demonstrate the following: 5 | - use of Azure keyvault and Kubernetes ConfigMaps for Configuration, it stresses on the importance of separating DevOps functions from Developer functions by have no appSettings, Secrets, blah blah .json files in code. All data is injected through Kubernetes or asked specifically from Azure Key Vault. 6 | 7 | ### Other features in the sample: 8 | - use of Serilog for strucutered logging, 9 | - use of repository for cosmosdb, a generic repository that can be used and extended for CosmosDB Document operations. 10 | - deployment of asp.net core Containers to Kubernetes 11 | 12 | ### Project Structure 13 | - samples.microservice.api - a ASP.NET Core 2.0 MicroService that allows for operations on Vehicle Records 14 | - sample.microservice.core - core interfaces and patterns used in the microservice. 15 | - samples.microservice.entities - a class library to hold custom entities that will be passed as messages to the microservice. 16 | - samples.microservice.repository - the CosmosDB repository that can be used in a generic way to interact with CosmosDB documents. The repository is independent of the micro-service and can be used with minimal changes for any application. 17 | 18 | ### What do you need to run the sample: 19 | All the configuration files for ASP.NET Core have been moved out so you will not see any appSettings.json rather you need to configure Kubernetes and Azure KeyVault with the right configurations: 20 | - Configure Azure KeyVault and add configuration values to it, here is a sample to do it: https://medium.com/@patoncrispy/securing-settings-in-dot-net-core-apps-using-azure-key-vault-d4ec82de00b8. DONOT put the configuration in the appSettings file as mentioned in the blog post, we will instead use Kubernetes config Maps for that. You need the following values in your KeyVault for the service to work, you can add additional values and pull them in the application as well: 21 | 22 | ```` 23 | Azure KeyVault Key-Pairs required for Cosmos DB repository: 24 | 1. cosmos-connstr - database connection string 25 | 2. cosmos-db-CollectionName - cosmos collection name 26 | 3. cosmos-dbname - name of the database for the Document API 27 | 4. cosmos-key - your primary key 28 | 5. cosmos-uri - e.g. https://.documents.azure.com:443/ 29 | ```` 30 | If you dont have a CosmosDB database, you can create one using the steps here: https://docs.microsoft.com/en-us/azure/cosmos-db/. You dont need to create the API using this tutorial just the Azure portal database account steps. 31 | 32 | - Configure Kubernetes with ConfigMaps and Secrets 33 | Instead of putting your Azure Key Vault details in a appSettings or secrets.json file, we will use ConfigMaps and Secrets to automatically inject these values during pod deployment. This is where you get clear separation between DevOps teams who manage the config data and Development teams who are only interested in the key to fetch the value. 34 | You can use kubectl to create ConfigMaps and Secrets, you will need the below configurations: 35 | ```` 36 | kubectl create configmap vault --from-literal=vault=<> 37 | kubectl create configmap kvuri --from-literal=kvuri=https://{vault-name}.vault.azure.net/ #DONT change this, the value gets injected automatically for this config. 38 | kubectl create configmap clientid --from-literal=clientId=<> 39 | kubectl create secret generic clientsecret --from-literal=clientSecret=<> 40 | ```` 41 | - Configure your docker hub image name and registry settings: 42 | Once you have the config settings in, you need to push the container in your docker hub or private registry. 43 | 44 | ```` 45 | docker push </<>:< 46 | ```` 47 | 48 | - Next, provide the docker container details to the vehicle-api-deployment.yaml 49 | 50 | ```` 51 | containers: 52 | - .... 53 | image: /: #CHANGE THIS: to the tag for your container 54 | ```` 55 | 56 | - Finally, deploy the container in your kubernetes cluster and you have a ready to use Vehicle Microservice that can do all basic CRUD operations. Enjoy!! 57 | 58 | ```` 59 | kubectl create -f vehicle-api-deployment.yaml 60 | kubectl create -f vehicle-api-service.yaml 61 | 62 | ```` 63 | 64 | *Note that in my Dockerfile I am using a private registry secret alias, if you want to deploy in a private registry you will have to provide a docker-secret to kubernetes. If you dont need and want to try a public docker registry instead, simply comment the following in the vehicle-api-deployment.yaml 65 | 66 | ```` 67 | imagePullSecrets: #secret to get details of private repo, disable this if using public docker repo 68 | - name: regsecret 69 | ```` 70 | -------------------------------------------------------------------------------- /src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnetcore-build:2.0 AS build-env 2 | WORKDIR /app 3 | 4 | # Copy csproj and restore as distinct layers 5 | COPY *.sln ./ 6 | 7 | RUN dotnet restore 8 | 9 | # Copy everything else and build 10 | COPY . ./ 11 | RUN dotnet publish -c Release -o out 12 | 13 | # Build runtime image 14 | FROM microsoft/aspnetcore:2.0 15 | ##Add ENV variables 16 | #ENV vault "niksac-kv" 17 | #ENV clientId "4dee3790-5990-454d-b875-6d82456cba84" 18 | #ENV clientSecret "fYn4vc836sNayvr8fQn4vYzS6qwmXmnFF/ovDvIyfHc=" 19 | #ENV kvuri "https://{vault-name}.vault.azure.net/" 20 | 21 | WORKDIR /app 22 | # NOTE: Need to find a way to automate the below copy, currently if you create a sln file build 23 | # it creates a per project folders so there is a need to have a way to aggregate all files before the copy happens 24 | # the below presents a challenge of scale where the ops engineer will have add projects every time a dev add one 25 | # not be used in production 26 | COPY --from=build-env /app/samples.microservice.core/out ./ 27 | COPY --from=build-env /app/samples.microservice.entities/out ./ 28 | COPY --from=build-env /app/samples.microservice.repository/out ./ 29 | COPY --from=build-env /app/samples.microservice.api/out ./ 30 | 31 | ENTRYPOINT ["dotnet", "samples.microservice.api.dll"] -------------------------------------------------------------------------------- /src/helm/samples-microservice/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /src/helm/samples-microservice/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: A Helm chart for samples.microservice 3 | name: samples-microservice 4 | version: 0.1.0 5 | -------------------------------------------------------------------------------- /src/helm/samples-microservice/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range .Values.ingress.hosts }} 4 | http://{{ . }} 5 | {{- end }} 6 | {{- else if contains "NodePort" .Values.service.type }} 7 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "samples.microservice.fullname" . }}) 8 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 9 | echo http://$NODE_IP:$NODE_PORT 10 | {{- else if contains "LoadBalancer" .Values.service.type }} 11 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 12 | You can watch the status of by running 'kubectl get svc -w {{ template "samples.microservice.fullname" . }}' 13 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "samples.microservice.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 14 | echo http://$SERVICE_IP:{{ .Values.service.externalPort }} 15 | {{- else if contains "ClusterIP" .Values.service.type }} 16 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "samples.microservice.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 17 | echo "Visit http://127.0.0.1:8080 to use your application" 18 | kubectl port-forward $POD_NAME 8080:{{ .Values.service.internalPort }} 19 | {{- end }} 20 | -------------------------------------------------------------------------------- /src/helm/samples-microservice/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "samples.microservice.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | */}} 13 | {{- define "samples.microservice.fullname" -}} 14 | {{- $name := default .Chart.Name .Values.nameOverride -}} 15 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 16 | {{- end -}} 17 | -------------------------------------------------------------------------------- /src/helm/samples-microservice/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: {{ template "samples.microservice.fullname" . }} 5 | labels: 6 | app: {{ template "samples.microservice.name" . }} 7 | chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | spec: 11 | replicas: {{ .Values.replicaCount }} 12 | template: 13 | metadata: 14 | labels: 15 | app: {{ template "samples.microservice.name" . }} 16 | release: {{ .Release.Name }} 17 | spec: 18 | imagePullSecrets: #secret to get details of private repo, disable this if using public docker repo 19 | - name: regsecret 20 | containers: 21 | - name: {{ .Chart.Name }} 22 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 23 | imagePullPolicy: {{ .Values.image.pullPolicy }} 24 | ports: 25 | - containerPort: {{ .Values.service.internalPort }} 26 | env: #set environment variables for the docker container using configMaps and Secret Keys 27 | - name: clientId 28 | valueFrom: 29 | configMapKeyRef: 30 | name: clientid 31 | key: clientId 32 | - name: kvuri 33 | valueFrom: 34 | configMapKeyRef: 35 | name: kvuri 36 | key: kvuri 37 | - name: vault 38 | valueFrom: 39 | configMapKeyRef: 40 | name: vault 41 | key: vault 42 | - name: clientsecret 43 | valueFrom: 44 | secretKeyRef: 45 | name: clientsecret 46 | key: clientSecret 47 | resources: 48 | {{ toYaml .Values.resources | indent 12 }} 49 | {{- if .Values.nodeSelector }} 50 | nodeSelector: 51 | {{ toYaml .Values.nodeSelector | indent 8 }} 52 | {{- end }} 53 | 54 | -------------------------------------------------------------------------------- /src/helm/samples-microservice/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $serviceName := include "samples.microservice.fullname" . -}} 3 | {{- $servicePort := .Values.service.externalPort -}} 4 | apiVersion: extensions/v1beta1 5 | kind: Ingress 6 | metadata: 7 | name: {{ template "samples.microservice.fullname" . }} 8 | labels: 9 | app: {{ template "samples.microservice.name" . }} 10 | chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} 11 | release: {{ .Release.Name }} 12 | heritage: {{ .Release.Service }} 13 | annotations: 14 | {{- range $key, $value := .Values.ingress.annotations }} 15 | {{ $key }}: {{ $value | quote }} 16 | {{- end }} 17 | spec: 18 | rules: 19 | {{- range $host := .Values.ingress.hosts }} 20 | - host: {{ $host }} 21 | http: 22 | paths: 23 | - path: / 24 | backend: 25 | serviceName: {{ $serviceName }} 26 | servicePort: {{ $servicePort }} 27 | {{- end -}} 28 | {{- if .Values.ingress.tls }} 29 | tls: 30 | {{ toYaml .Values.ingress.tls | indent 4 }} 31 | {{- end -}} 32 | {{- end -}} 33 | -------------------------------------------------------------------------------- /src/helm/samples-microservice/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ template "samples.microservice.fullname" . }} 5 | labels: 6 | app: {{ template "samples.microservice.name" . }} 7 | chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | spec: 11 | type: {{ .Values.service.type }} 12 | ports: 13 | - port: {{ .Values.service.externalPort }} 14 | targetPort: {{ .Values.service.internalPort }} 15 | protocol: TCP 16 | name: {{ .Values.service.name }} 17 | selector: 18 | app: {{ template "samples.microservice.name" . }} 19 | release: {{ .Release.Name }} 20 | -------------------------------------------------------------------------------- /src/helm/samples-microservice/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for samples.microservice. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | replicaCount: 1 5 | image: 6 | repository: / 7 | tag: < 8 | pullPolicy: Always 9 | service: 10 | name: vehicle-api 11 | type: LoadBalancer 12 | externalPort: 80 13 | internalPort: 80 14 | ingress: 15 | enabled: false 16 | # Used to create an Ingress record. 17 | hosts: 18 | - chart-example.local 19 | annotations: 20 | # kubernetes.io/ingress.class: nginx 21 | # kubernetes.io/tls-acme: "true" 22 | tls: 23 | # Secrets must be manually created in the namespace. 24 | # - secretName: chart-example-tls 25 | # hosts: 26 | # - chart-example.local 27 | resources: {} 28 | # We usually recommend not to specify default resources and to leave this as a conscious 29 | # choice for the user. This also increases chances charts run on environments with little 30 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 31 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 32 | # limits: 33 | # cpu: 100m 34 | # memory: 128Mi 35 | # requests: 36 | # cpu: 100m 37 | # memory: 128Mi 38 | nameOverride: vehicle-api 39 | -------------------------------------------------------------------------------- /src/samples.microservice.api/Controllers/ErrorController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace samples.microservice.api.Controllers 6 | { 7 | public class ErrorController : Controller 8 | { 9 | /// 10 | /// Captures all errors on HTTP 11 | /// 12 | /// 13 | /// 14 | [Route("error/{code}")] 15 | public async Task Error(int code) 16 | { 17 | await HttpContext.Response.WriteAsync($"An error occurred, Error code: {code}"); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/samples.microservice.api/Controllers/VehicleController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.Logging; 7 | using samples.microservice.core; 8 | using samples.microservice.entities; 9 | 10 | namespace samples.microservice.api.Controllers 11 | { 12 | public class VehicleController : Controller 13 | { 14 | private readonly ILogger _logger; 15 | private readonly IRepository _repository; 16 | private IConfiguration _configuration; 17 | 18 | /// 19 | /// Controller representing communication with Cosmos Db 20 | /// 21 | /// 22 | /// 23 | /// 24 | public VehicleController(IConfiguration configuration, IRepository repository, ILoggerFactory loggerFactory) 25 | { 26 | _configuration = configuration; 27 | _repository = repository; 28 | _logger = loggerFactory.CreateLogger(); 29 | _logger.LogInformation($"Entering Vehicle Controller"); 30 | } 31 | 32 | /// 33 | /// API to get multiple documents, default count is 10 34 | /// 35 | /// 36 | [HttpGet("/vehicle")] 37 | public async Task> GetAsync(int maxcount = 10) 38 | { 39 | try 40 | { 41 | return await _repository.ReadAsync(); 42 | } 43 | catch (Exception e) 44 | { 45 | _logger.LogError(e, $"Error in Get {e.Message} details: {e}"); 46 | throw; 47 | } 48 | } 49 | 50 | /// 51 | /// API to get a specific document 52 | /// 53 | /// 54 | /// 55 | [HttpGet("/vehicle/{id}")] 56 | public async Task GetAsync(string id) 57 | { 58 | try 59 | { 60 | return await _repository.ReadSingularAsync(id); 61 | } 62 | catch (Exception e) 63 | { 64 | _logger.LogError(e, $"Error in Get {e.Message} details: {e}"); 65 | throw; 66 | } 67 | } 68 | 69 | /// 70 | /// Upsert API for handling Insert and Update operations 71 | /// 72 | /// 73 | /// 74 | [HttpPost("/vehicle")] 75 | [HttpPut("/vehicle")] 76 | public async Task Save([FromBody] VehicleData document) 77 | { 78 | try 79 | { 80 | if (document == null) return BadRequest(); 81 | var result = await _repository.SaveAsync(document); 82 | return Json(result); 83 | } 84 | catch (Exception e) 85 | { 86 | _logger.LogError(e, $"Error in Save {e.Message} details: {e}"); 87 | throw; 88 | } 89 | } 90 | 91 | /// 92 | /// API to delete a document using its identifier 93 | /// 94 | /// 95 | /// 96 | [HttpDelete("/vehicle/{id}")] 97 | public async Task Remove([FromRoute] string id) 98 | { 99 | try 100 | { 101 | if (id == null) return BadRequest(); 102 | var result = await _repository.DeleteAsync(id); 103 | return Json(result); 104 | } 105 | catch (Exception e) 106 | { 107 | _logger.LogError(e, $"Error in Remove {e.Message} details: {e}"); 108 | throw; 109 | } 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /src/samples.microservice.api/ErrorHandlingMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Http; 5 | using Newtonsoft.Json; 6 | 7 | namespace samples.microservice.api 8 | { 9 | public class ErrorHandlingMiddleware 10 | { 11 | private readonly RequestDelegate _next; 12 | 13 | public ErrorHandlingMiddleware(RequestDelegate next) 14 | { 15 | _next = next; 16 | } 17 | 18 | public async Task Invoke(HttpContext context /* other scoped dependencies */) 19 | { 20 | try 21 | { 22 | await _next(context); 23 | } 24 | catch (Exception ex) 25 | { 26 | await HandleExceptionAsync(context, ex); 27 | } 28 | } 29 | 30 | private static Task HandleExceptionAsync(HttpContext context, Exception exception) 31 | { 32 | var code = HttpStatusCode.InternalServerError; // 500 if unexpected 33 | 34 | switch (exception) 35 | { 36 | case NotImplementedException _: 37 | code = HttpStatusCode.NotFound; 38 | break; 39 | case UnauthorizedAccessException _: 40 | code = HttpStatusCode.Unauthorized; 41 | break; 42 | default: 43 | code = HttpStatusCode.BadRequest; 44 | break; 45 | } 46 | 47 | var result = JsonConvert.SerializeObject(new {error = exception.Message}); 48 | context.Response.ContentType = "application/json"; 49 | context.Response.StatusCode = (int) code; 50 | return context.Response.WriteAsync(result); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/samples.microservice.api/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Microsoft.AspNetCore; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.Logging; 6 | using Serilog; 7 | 8 | namespace samples.microservice.api 9 | { 10 | public class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | BuildWebHost(args).Run(); 15 | } 16 | 17 | public static IWebHost BuildWebHost(string[] args) 18 | { 19 | return WebHost.CreateDefaultBuilder(args) 20 | .UseContentRoot(Directory.GetCurrentDirectory()) 21 | .ConfigureAppConfiguration((context, config) => 22 | { 23 | /*** CONVENTIONAL WAY: See below for conventional way of getting secrets from a config file, this requires a configuration file with 24 | the following details 25 | { 26 | "vault": "", 27 | "clientId": "", 28 | "clientSecret": "", 29 | "kvuri":"https://{vault-name}.vault.azure.net/" 30 | } 31 | 32 | and then the below code to get the data. 33 | 34 | // add the configuration file for getting keyvault configuration data 35 | config.SetBasePath(Directory.GetCurrentDirectory()) 36 | .AddEnvironmentVariables() 37 | .AddJsonFile($"secrets.{env.EnvironmentName}.json", optional: false, reloadOnChange: true); 38 | 39 | // add azure key vault configuration 40 | var buildConfig = config.Build(); 41 | 42 | // get the key vault uri 43 | var vaultUri = buildConfig["kvuri"].Replace("{vault-name}", buildConfig["vault"]); 44 | 45 | In this sample we have elimnated the need to get the above file from a secret file and rather use 46 | Kubernetes ConfigMaps and Secrets, the main configuration is still kept within KeyVault. 47 | ****/ 48 | 49 | // ***** CONTAINER WAY: Instead of getting KV configuration details from a settings file, Kubernetes will push those details as 50 | // Environment variables for the container, use the Enviroment variables directly to access information 51 | 52 | // add the environment variables to config 53 | config.AddEnvironmentVariables(); 54 | 55 | // add azure key vault configuration using environment variables 56 | var buildConfig = config.Build(); 57 | 58 | // get the key vault uri 59 | var vaultUri = buildConfig["kvuri"].Replace("{vault-name}", buildConfig["vault"]); 60 | 61 | // setup KeyVault store for getting configuration values 62 | config.AddAzureKeyVault(vaultUri, buildConfig["clientId"], buildConfig["clientSecret"]); 63 | }) 64 | .UseStartup() 65 | .Build(); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /src/samples.microservice.api/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Logging; 6 | using samples.microservice.core; 7 | using samples.microservice.repository; 8 | using Serilog; 9 | using ILogger = Microsoft.Extensions.Logging.ILogger; 10 | 11 | namespace samples.microservice.api 12 | { 13 | public class Startup 14 | { 15 | private readonly ILoggerFactory _loggerFactory; 16 | 17 | /// 18 | /// 19 | /// 20 | /// 21 | public Startup(IConfiguration configuration, ILoggerFactory loggerFactory) 22 | { 23 | Configuration = configuration; 24 | _loggerFactory = loggerFactory; 25 | } 26 | 27 | public IConfiguration Configuration { get; } 28 | 29 | // This method gets called by the runtime. Use this method to add services to the container. 30 | public void ConfigureServices(IServiceCollection services) 31 | { 32 | // add MVC as a service 33 | services.AddMvc(); 34 | 35 | // Add SeriLog as provider for logging 36 | // Configure Serilog 37 | //TODO: configure logger per environment, this can be done in config as well 38 | Log.Logger = new LoggerConfiguration() 39 | .MinimumLevel.Verbose() 40 | .WriteTo.ColoredConsole().CreateLogger(); 41 | _loggerFactory.AddSerilog(dispose: true); 42 | 43 | //add the repositories to the configuration 44 | services.AddRepository(Configuration, _loggerFactory); 45 | } 46 | 47 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 48 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 49 | { 50 | // show custom error pages 51 | if (env.IsDevelopment()) 52 | { 53 | app.UseDeveloperExceptionPage(); 54 | _loggerFactory.AddDebug(); 55 | } 56 | else 57 | { 58 | app.UseExceptionHandler("/error/500"); 59 | } 60 | 61 | _loggerFactory.AddConsole(LogLevel.Debug); 62 | 63 | // app.UseStatusCodePagesWithReExecute("/error/{0}"); 64 | // add support for custom exception handling middleware, 65 | // this will allow for all exception messages to be handled by a common middleware 66 | // app.UseMiddleware(); 67 | 68 | // use MVC framework and map routes to controllers 69 | app.UseMvc(); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /src/samples.microservice.api/samples.microservice.api.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {2447D6F5-0AD0-4490-91DD-20768226F09E} 27 | samples.microservice.core 28 | 29 | 30 | {7BECEABC-51B3-4BB7-8E1D-9F0C220FCDD4} 31 | samples.microservice.entities 32 | 33 | 34 | {BA21605E-74BC-4CF3-BEE0-D10DE45931CB} 35 | samples.microservice.repository 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/samples.microservice.api/secrets.template-LEGACY.json: -------------------------------------------------------------------------------- 1 | { 2 | "vault": "", 3 | "clientId": "", 4 | "clientSecret": "", 5 | "kvuri": "https://{vault-name}.vault.azure.net/" 6 | } -------------------------------------------------------------------------------- /src/samples.microservice.core/BaseRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace samples.microservice.core 8 | { 9 | /// 10 | /// Template pattern for repository building 11 | /// 12 | public abstract class BaseRepository : IRepository 13 | { 14 | protected readonly IConfiguration Configuration; 15 | protected readonly ILogger Logger; 16 | 17 | protected BaseRepository(IConfiguration configuration, ILoggerFactory loggerFactory) 18 | { 19 | Configuration = configuration; 20 | Logger = loggerFactory.CreateLogger(); 21 | } 22 | 23 | public abstract Task SaveAsync(TEntity entity, string modifiedby = null, 24 | CancellationToken token = default(CancellationToken)) where TEntity : Entity; 25 | 26 | public abstract Task DeleteAsync(string id, CancellationToken token) where TEntity : Entity; 27 | 28 | public abstract Task ReadSingularAsync(string id, 29 | CancellationToken token = default(CancellationToken)) where TEntity : Entity; 30 | 31 | public abstract Task> ReadAsync(string partitionKey, int maxItemCount, 32 | CancellationToken token) where TEntity : Entity; 33 | } 34 | } -------------------------------------------------------------------------------- /src/samples.microservice.core/Entity.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | 4 | namespace samples.microservice.core 5 | { 6 | public abstract class Entity 7 | { 8 | [JsonProperty(PropertyName = "id")] 9 | public string Id { get; set; } 10 | 11 | [JsonProperty(PropertyName = "name")] 12 | public string Name { get; set; } 13 | 14 | [JsonProperty(PropertyName = "description")] 15 | public string Description { get; set; } 16 | 17 | [JsonProperty(PropertyName = "custom")] 18 | public Dictionary CustomProperties { get; set; } 19 | 20 | /// 21 | /// serialize JSON into string 22 | /// 23 | /// 24 | public override string ToString() 25 | { 26 | return JsonConvert.SerializeObject(this); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/samples.microservice.core/IRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace samples.microservice.core 6 | { 7 | public interface IRepository 8 | { 9 | /// 10 | /// Upsert to create or update an entity 11 | /// 12 | /// 13 | /// 14 | /// 15 | /// 16 | /// 17 | Task SaveAsync(TEntity entity, string modifiedby = null, 18 | CancellationToken token = default(CancellationToken)) where TEntity : Entity; 19 | 20 | /// 21 | /// Delete an entity 22 | /// 23 | /// 24 | /// 25 | /// 26 | /// 27 | Task DeleteAsync(string id, CancellationToken token = default(CancellationToken)) 28 | where TEntity : Entity; 29 | 30 | /// 31 | /// Read specific record from the data store 32 | /// 33 | /// i 34 | /// 35 | /// 36 | /// 37 | Task ReadSingularAsync(string id, CancellationToken token = default(CancellationToken)) 38 | where TEntity : Entity; 39 | 40 | /// 41 | /// Read all records from the data store, default resultset is 10 42 | /// 43 | /// 44 | /// 45 | /// 46 | /// 47 | /// 48 | Task> ReadAsync(string partitionKey = null, int maxItemCount = 10, 49 | CancellationToken token = default(CancellationToken)) where TEntity : Entity; 50 | } 51 | } -------------------------------------------------------------------------------- /src/samples.microservice.core/RepositoryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace samples.microservice.core 7 | { 8 | public static class RepositoryExtensions 9 | { 10 | public static IServiceCollection AddRepository(this IServiceCollection services, 11 | IConfiguration configuration, 12 | ILoggerFactory loggerFactory) where T : BaseRepository 13 | { 14 | return services.AddSingleton(_ => 15 | Activator.CreateInstance(typeof(T), configuration, loggerFactory) as T); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/samples.microservice.core/samples.microservice.core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/samples.microservice.entities/VehicleData.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using samples.microservice.core; 3 | 4 | namespace samples.microservice.entities 5 | { 6 | public class VehicleData : Entity 7 | { 8 | [JsonProperty(PropertyName = "vin")] 9 | public string Vin { get; set; } 10 | 11 | [JsonProperty(PropertyName = "esn")] 12 | public string Esn { get; set; } 13 | 14 | [JsonProperty(PropertyName = "fuel")] 15 | public int FuelGauge { get; set; } 16 | 17 | [JsonProperty(PropertyName = "pressure")] 18 | public int PressureGauge { get; set; } 19 | 20 | [JsonProperty(PropertyName = "indicatorlight")] 21 | public bool IndicatorLights { get; set; } 22 | 23 | [JsonProperty(PropertyName = "headlight")] 24 | public bool HeadLights { get; set; } 25 | } 26 | } -------------------------------------------------------------------------------- /src/samples.microservice.entities/samples.microservice.entities.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0 4 | 5 | 6 | 7 | {2447D6F5-0AD0-4490-91DD-20768226F09E} 8 | samples.microservice.core 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/samples.microservice.repository/CosmosRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Azure.Documents; 8 | using Microsoft.Azure.Documents.Client; 9 | using Microsoft.Azure.Documents.Linq; 10 | using Microsoft.Extensions.Configuration; 11 | using Microsoft.Extensions.Logging; 12 | using samples.microservice.core; 13 | 14 | namespace samples.microservice.repository 15 | { 16 | /// 17 | /// Repository for common Document operations 18 | /// 19 | public class CosmosRepository : BaseRepository 20 | { 21 | private static DocumentClient _client; 22 | private readonly string _cosmosEndPoint; 23 | private readonly string _cosmosKey; 24 | private readonly string _databaseCollectionName; 25 | private readonly string _databaseName; 26 | 27 | /// 28 | /// Constructor for CosmosRepository 29 | /// 30 | /// 31 | /// 32 | public CosmosRepository(IConfiguration configuration, ILoggerFactory loggerFactory) : base(configuration, loggerFactory) 33 | { 34 | try 35 | { 36 | // get the cosmos values from configuration 37 | _cosmosEndPoint = configuration["cosmos-uri"]; 38 | _cosmosKey = configuration["cosmos-key"]; 39 | _databaseName = configuration["cosmos-dbName"]; 40 | _databaseCollectionName = configuration["cosmos-db-CollectionName"]; 41 | } 42 | catch (Exception e) 43 | { 44 | Logger.LogError($"Error occurred: {e.Message}", e); 45 | throw; 46 | } 47 | } 48 | 49 | /// 50 | /// Initialized the database if not already 51 | /// 52 | /// 53 | private async Task EnsureClientConnected(CancellationToken token) 54 | { 55 | try 56 | { 57 | // connecting to the Cosmos Document db store 58 | //TODO: make client singleton and lazy initialize 59 | if (_client != null) return true; 60 | _client = new DocumentClient(new Uri(_cosmosEndPoint), _cosmosKey) 61 | { 62 | ConnectionPolicy = 63 | { 64 | ConnectionMode = ConnectionMode.Direct, 65 | ConnectionProtocol = 66 | Protocol.Https // TODO: move to TCP, firewall configuration blocking at the moment 67 | } 68 | }; 69 | 70 | // ensure client is loaded to optimize performance 71 | await _client.OpenAsync(token); 72 | return true; 73 | } 74 | catch (DocumentClientException de) 75 | { 76 | var baseException = de.GetBaseException(); 77 | Logger.LogError($"Error occurred: {de.Message} Base exception was: {baseException.Message}", de); 78 | throw; 79 | } 80 | catch (Exception e) 81 | { 82 | Logger.LogError($"Error occurred: {e.Message}", e); 83 | throw; 84 | } 85 | } 86 | 87 | /// 88 | /// saves (Upsert) into the database 89 | /// 90 | /// 91 | /// 92 | /// 93 | /// 94 | /// 95 | public override async Task SaveAsync(TEntity entity, string modifiedby = null, 96 | CancellationToken token = default(CancellationToken)) 97 | { 98 | //TODO: Add Polly retry logic to ensure to connect back 99 | if (entity == null) throw new ArgumentNullException(nameof(entity)); 100 | var id = entity.Id.Trim(); 101 | 102 | try 103 | { 104 | await EnsureClientConnected(token); 105 | 106 | // first check if the records already exists, if it does then update 107 | var options = new RequestOptions {PartitionKey = new PartitionKey(id)}; 108 | var response = await _client.ReadDocumentAsync(UriFactory.CreateDocumentUri(_databaseName, 109 | _databaseCollectionName, 110 | id), options); 111 | Logger.LogInformation("Found {0} during upsert", id); 112 | 113 | // Update the document instead of adding a new one 114 | await _client.ReplaceDocumentAsync( 115 | UriFactory.CreateDocumentUri(_databaseName, _databaseCollectionName, entity.Id), 116 | entity); 117 | Logger.LogInformation("Entity {0} Updated", id); 118 | } 119 | catch (DocumentClientException de) 120 | { 121 | // if the document does not exist then attempt to insert 122 | if (de.StatusCode == HttpStatusCode.NotFound) 123 | { 124 | var options = new RequestOptions {PartitionKey = new PartitionKey(entity.Id)}; 125 | await _client.CreateDocumentAsync( 126 | UriFactory.CreateDocumentCollectionUri(_databaseName, _databaseCollectionName), entity, 127 | options); 128 | Logger.LogInformation("Created new entity {0}", id); 129 | } 130 | else 131 | { 132 | throw; 133 | } 134 | } 135 | 136 | return true; 137 | } 138 | 139 | /// 140 | /// delete an entity from the database 141 | /// 142 | /// 143 | /// 144 | /// 145 | /// 146 | /// 147 | public override async Task DeleteAsync(string id, CancellationToken token) 148 | { 149 | try 150 | { 151 | await EnsureClientConnected(token); 152 | var options = new RequestOptions {PartitionKey = new PartitionKey(id.Trim())}; 153 | await _client.DeleteDocumentAsync( 154 | UriFactory.CreateDocumentUri(_databaseName, _databaseCollectionName, id.Trim()), options); 155 | Logger.LogInformation($"Deletion of {id} was successfull."); 156 | return true; 157 | } 158 | catch (Exception e) 159 | { 160 | Logger.LogInformation($"Error occurred {e.Message} details: {e}"); 161 | throw; 162 | } 163 | } 164 | 165 | /// 166 | /// reads the entity based on Id 167 | /// 168 | /// 169 | /// 170 | /// 171 | /// 172 | public override async Task ReadSingularAsync(string id, 173 | CancellationToken token = default(CancellationToken)) 174 | { 175 | try 176 | { 177 | await EnsureClientConnected(token); 178 | id = id.Trim(); 179 | var queryOptions = new FeedOptions {MaxItemCount = -1, EnableCrossPartitionQuery = true, PartitionKey = new PartitionKey(id)}; 180 | var query = _client 181 | .CreateDocumentQuery(UriFactory 182 | .CreateDocumentCollectionUri(_databaseName, _databaseCollectionName), queryOptions) 183 | //.Where(c => c.Id == id) // filter only on partition key 184 | .AsDocumentQuery(); 185 | 186 | var response = await query.ExecuteNextAsync(token); 187 | Logger.LogInformation($"Records returned for singular query {response.Count}"); 188 | return response.FirstOrDefault(); 189 | } 190 | catch (Exception e) 191 | { 192 | Logger.LogInformation($"Error occurred {e.Message} details: {e}"); 193 | throw; 194 | } 195 | } 196 | 197 | /// 198 | /// Reads the entity from the database and returns the top 10 documents 199 | /// 200 | /// 201 | /// 202 | public override async Task> ReadAsync(string partitionKey, int maxItemCount, 203 | CancellationToken token) 204 | { 205 | try 206 | { 207 | await EnsureClientConnected(token); 208 | var entities = new List(); 209 | var queryOptions = new FeedOptions {MaxItemCount = maxItemCount, EnableCrossPartitionQuery = true}; 210 | var documentQuery = _client.CreateDocumentQuery(UriFactory 211 | .CreateDocumentCollectionUri(_databaseName, _databaseCollectionName), queryOptions); 212 | entities.AddRange(documentQuery); 213 | Logger.LogInformation($"Records returned for multiple query {entities.Count}"); 214 | return entities; 215 | } 216 | catch (Exception e) 217 | { 218 | Logger.LogInformation($"Error occurred {e.Message} details: {e}"); 219 | throw; 220 | } 221 | } 222 | } 223 | } -------------------------------------------------------------------------------- /src/samples.microservice.repository/samples.microservice.repository.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0 4 | 5 | 6 | 7 | {2447D6F5-0AD0-4490-91DD-20768226F09E} 8 | samples.microservice.core 9 | 10 | 11 | {7BECEABC-51B3-4BB7-8E1D-9F0C220FCDD4} 12 | samples.microservice.entities 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/samples.microservice.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "samples.microservice.api", "samples.microservice.api\samples.microservice.api.csproj", "{01B1128A-0316-4FFF-9D91-3C3A71F9A368}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "samples.microservice.core", "samples.microservice.core\samples.microservice.core.csproj", "{2447D6F5-0AD0-4490-91DD-20768226F09E}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "samples.microservice.repository", "samples.microservice.repository\samples.microservice.repository.csproj", "{BA21605E-74BC-4CF3-BEE0-D10DE45931CB}" 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "samples.microservice.entities", "samples.microservice.entities\samples.microservice.entities.csproj", "{7BECEABC-51B3-4BB7-8E1D-9F0C220FCDD4}" 10 | EndProject 11 | Global 12 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 13 | Debug|Any CPU = Debug|Any CPU 14 | Release|Any CPU = Release|Any CPU 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {01B1128A-0316-4FFF-9D91-3C3A71F9A368}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {01B1128A-0316-4FFF-9D91-3C3A71F9A368}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {01B1128A-0316-4FFF-9D91-3C3A71F9A368}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {01B1128A-0316-4FFF-9D91-3C3A71F9A368}.Release|Any CPU.Build.0 = Release|Any CPU 21 | {2447D6F5-0AD0-4490-91DD-20768226F09E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {2447D6F5-0AD0-4490-91DD-20768226F09E}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {2447D6F5-0AD0-4490-91DD-20768226F09E}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {2447D6F5-0AD0-4490-91DD-20768226F09E}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {BA21605E-74BC-4CF3-BEE0-D10DE45931CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {BA21605E-74BC-4CF3-BEE0-D10DE45931CB}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {BA21605E-74BC-4CF3-BEE0-D10DE45931CB}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {BA21605E-74BC-4CF3-BEE0-D10DE45931CB}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {7BECEABC-51B3-4BB7-8E1D-9F0C220FCDD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {7BECEABC-51B3-4BB7-8E1D-9F0C220FCDD4}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {7BECEABC-51B3-4BB7-8E1D-9F0C220FCDD4}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {7BECEABC-51B3-4BB7-8E1D-9F0C220FCDD4}.Release|Any CPU.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /src/vehicle-api-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: vehicle-api-deploy #name for the deployment 5 | labels: 6 | app: vehicle-api #label that will be used to map the service, this tag is very important 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: vehicle-api #label that will be used to map the service, this tag is very important 12 | template: 13 | metadata: 14 | labels: 15 | app: vehicle-api #label that will be used to map the service, this tag is very important 16 | spec: 17 | containers: 18 | - name: vehicleapi #name for the container configuration 19 | image: /: # **CHANGE THIS: the tag for the container to be deployed 20 | imagePullPolicy: Always #getting latest image on each deployment 21 | ports: 22 | - containerPort: 80 #map to port 80 in the docker container 23 | env: #set environment variables for the docker container using configMaps and Secret Keys 24 | - name: clientId 25 | valueFrom: 26 | configMapKeyRef: 27 | name: clientid 28 | key: clientId 29 | - name: kvuri 30 | valueFrom: 31 | configMapKeyRef: 32 | name: kvuri 33 | key: kvuri 34 | - name: vault 35 | valueFrom: 36 | configMapKeyRef: 37 | name: vault 38 | key: vault 39 | - name: clientsecret 40 | valueFrom: 41 | secretKeyRef: 42 | name: clientsecret 43 | key: clientSecret 44 | imagePullSecrets: #secret to get details of private repo, disable this if using public docker repo 45 | - name: regsecret -------------------------------------------------------------------------------- /src/vehicle-api-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: vehicle-api-svc #name for the service 5 | spec: 6 | type: LoadBalancer #specify that the service type will be load balancer so public endpoint 7 | ports: 8 | - name: http #port 9 | port: 80 10 | targetPort: 80 11 | selector: 12 | app: vehicle-api # label to match the underlying pod deployments, this much match the deployment label 13 | 14 | 15 | --------------------------------------------------------------------------------