diff --git a/README.md b/README.md index 8a08259f..93c82d7a 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,29 @@ Microsoft patterns & practices This reference implementation shows a set of best practices for building and running a microservices architecture on Microsoft Azure, using Kubernetes. +| | [Basic](https://github.com/mspnp/microservices-reference-implementation/tree/basic) | [Advanced](https://github.com/mspnp/microservices-reference-implementation/) | +|-----------------------------------------|-------|----------| +| Distributed Monitoring | ✅ | ✅ | +| Ingress Controller | ✅ | ✅ | +| Azure Active Directory Pod Identity | ✅ | ✅ | +| CI/CD using Azure Pipelines | ✅ | ✅ | +| Helm charts | ✅ | ✅ | +| Resource Limits | ✅ | ✅ | +| Readiness/Liveness Probes | ✅ | ✅ | +| Horizontal Pod Autoscaling | ❌ | ✅ | +| Cluster Autoscaling | ❌ | ✅ | +| Advanced Networking | ❌ | ✅ | +| Service Endpoints | ❌ | ✅ | +| Network Policies | ❌ | ✅ | +| Egress restriction using Azure Firewall | ❌ | ✅ | + ## Guidance This project has a companion set of articles that describe challenges, design patterns, and best practices for building microservices architecture. You can find these articles on the Azure Architecture Center: - [Designing, building, and operating microservices on Azure with Kubernetes](https://docs.microsoft.com/azure/architecture/microservices) +- [Microservices architecture on Azure Kubernetes Service (AKS)](https://docs.microsoft.com/azure/architecture/reference-architectures/microservices/aks) +- [Building a CI/CD pipeline for microservices on Kubernetes](https://docs.microsoft.com/azure/architecture/microservices/ci-cd-kubernetes) ## Scenario diff --git a/architecture.png b/architecture.png index 4d00150e..58b58509 100644 Binary files a/architecture.png and b/architecture.png differ diff --git a/azuredeploy-firewall.json b/azuredeploy-firewall.json new file mode 100644 index 00000000..754152df --- /dev/null +++ b/azuredeploy-firewall.json @@ -0,0 +1,464 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "aksVnetName": { + "type": "string" + }, + "aksClusterSubnetName": { + "type": "string" + }, + "aksClusterSubnetPrefix": { + "type": "string" + }, + "firewallPublicIpName": { + "type": "string" + }, + "firewallSubnetName": { + "type": "string", + "defaultValue": "AzureFirewallSubnet" + }, + "serviceTagsLocation": { + "type": "string" + }, + "aksFqdns": { + "type": "array" + }, + "acrServers": { + "type": "array" + }, + "deliveryRedisHostNames": { + "type": "array" + } + }, + "variables": { + "rulePrefix": "[concat(parameters('aksVnetName'),'-',parameters('aksClusterSubnetName'),'.')]", + "regionalRulePrefix": "[concat(variables('rulePrefix'),parameters('serviceTagsLocation'),'.')]", + "egressFirewallPrefix": "egressFw", + "egressFirewallName": "[concat(variables('egressFirewallPrefix'),'-',uniqueString(resourceGroup().id))]", + "clusterEgressRouteTableName": "[concat('egressRouteTable-',uniqueString(resourceGroup().id))]", + "clusterEgressRouteName": "[concat('egressRoute-',uniqueString(resourceGroup().id))]" + }, + "resources": [ + { + "type": "Microsoft.Network/routeTables", + "apiVersion": "2019-09-01", + "name": "[variables('clusterEgressRouteTableName')]", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[resourceId('Microsoft.Network/azureFirewalls', variables('egressFirewallName'))]" + ], + "properties": { + "routes": [ + { + "properties": { + "addressPrefix": "0.0.0.0/0", + "nextHopType": "VirtualAppliance", + "nextHopIpAddress": "[reference(concat('Microsoft.Network/azureFirewalls/', variables('egressFirewallName')),'2019-09-01','Full').properties.ipConfigurations[0].properties.privateIPAddress]" + }, + "name": "[variables('clusterEgressRouteName')]" + } + ] + } + }, + { + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2019-09-01", + "name": "[concat(parameters('aksVnetName'),'/',parameters('aksClusterSubnetName'))]", + "dependsOn": [ + "[resourceId('Microsoft.Network/routeTables', variables('clusterEgressRouteTableName'))]" + ], + "properties": { + "addressPrefix": "[parameters('aksClusterSubnetPrefix')]", + "routeTable": { + "id": "[resourceId('Microsoft.Network/routeTables', variables('clusterEgressRouteTableName'))]" + } + } + }, + { + "type": "Microsoft.Network/azureFirewalls", + "name": "[variables('egressFirewallName')]", + "dependsOn": [], + "apiVersion": "2019-09-01", + "location": "[resourceGroup().location]", + "properties": { + "threatIntelMode": "Alert", + "ipConfigurations": [ + { + "name": "clusterIpConfig", + "properties": { + "publicIPAddress": { + "id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('firewallPublicIpName'))]" + }, + "subnet": { + "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('aksVnetName'),parameters('firewallSubnetName'))]" + } + } + } + ], + "networkRuleCollections": [ + { + "name": "egressNetRegionalAks", + "properties": { + "priority": 100, + "action": { + "type": "Allow" + }, + "rules": [ + { + "name": "[concat(variables('regionalRulePrefix'),'TunnelFrontCommToK8sApiServer')]", + "protocols": [ + "TCP" + ], + "sourceAddresses": [ + "[parameters('aksClusterSubnetPrefix')]" + ], + "destinationAddresses": [ + "[concat('AzureCloud.',parameters('serviceTagsLocation'))]" + ], + "sourceIpGroups": [], + "destinationIpGroups": [], + "destinationFqdns": [], + "destinationPorts": [ + "9000", + "22" + ] + } + ] + } + }, + { + "name": "egressNetAks", + "properties": { + "priority": 200, + "action": { + "type": "Allow" + }, + "rules": [ + { + "name": "[concat(variables('rulePrefix'),'DnsResolution')]", + "protocols": [ + "TCP", + "UDP" + ], + "sourceAddresses": [ + "[parameters('aksClusterSubnetPrefix')]" + ], + "destinationAddresses": [ + "*" + ], + "sourceIpGroups": [], + "destinationIpGroups": [], + "destinationFqdns": [], + "destinationPorts": [ + "53" + ] + }, + { + "name": "[concat(variables('rulePrefix'),'LinuxNodesTimeSync')]", + "protocols": [ + "UDP" + ], + "sourceAddresses": [ + "[parameters('aksClusterSubnetPrefix')]" + ], + "destinationAddresses": [ + "*" + ], + "sourceIpGroups": [], + "destinationIpGroups": [], + "destinationFqdns": [], + "destinationPorts": [ + "123" + ] + } + ] + } + }, + { + "name": "egressNetRegionalFabrikamDroneDelivery", + "properties": { + "priority": 300, + "action": { + "type": "Allow" + }, + "rules": [ + { + "name": "[concat(variables('regionalRulePrefix'),'ServiceBus')]", + "protocols": [ + "TCP" + ], + "sourceAddresses": [ + "[parameters('aksClusterSubnetPrefix')]" + ], + "destinationAddresses": [ + "[concat('ServiceBus.',parameters('serviceTagsLocation'))]" + ], + "sourceIpGroups": [], + "destinationIpGroups": [], + "destinationFqdns": [], + "destinationPorts": [ + "5671" + ] + }, + { + "name": "[concat(variables('regionalRulePrefix'),'AzureCosmosDb')]", + "protocols": [ + "TCP" + ], + "sourceAddresses": [ + "[parameters('aksClusterSubnetPrefix')]" + ], + "destinationAddresses": [ + "[concat('AzureCosmosDB.',parameters('serviceTagsLocation'))]" + ], + "sourceIpGroups": [], + "destinationIpGroups": [], + "destinationFqdns": [], + "destinationPorts": [ + "443" + ] + }, + { + "name": "[concat(variables('regionalRulePrefix'),'AzureMongoDb')]", + "protocols": [ + "TCP" + ], + "sourceAddresses": [ + "[parameters('aksClusterSubnetPrefix')]" + ], + "destinationAddresses": [ + "[concat('AzureCosmosDB.',parameters('serviceTagsLocation'))]" + ], + "sourceIpGroups": [], + "destinationIpGroups": [], + "destinationFqdns": [], + "destinationPorts": [ + "10255" + ] + }, + { + "name": "[concat(variables('regionalRulePrefix'),'AzureKeyVault')]", + "protocols": [ + "TCP" + ], + "sourceAddresses": [ + "[parameters('aksClusterSubnetPrefix')]" + ], + "destinationAddresses": [ + "[concat('AzureKeyVault.',parameters('serviceTagsLocation'))]" + ], + "sourceIpGroups": [], + "destinationIpGroups": [], + "destinationFqdns": [], + "destinationPorts": [ + "443" + ] + } + ] + } + }, + { + "name": "egressNetFabrikamDroneDelivery", + "properties": { + "priority": 400, + "action": { + "type": "Allow" + }, + "rules": [ + { + "name": "[concat(variables('rulePrefix'),'AzureMonitor')]", + "protocols": [ + "TCP" + ], + "sourceAddresses": [ + "[parameters('aksClusterSubnetPrefix')]" + ], + "destinationAddresses": [ + "AzureMonitor" + ], + "sourceIpGroups": [], + "destinationIpGroups": [], + "destinationFqdns": [], + "destinationPorts": [ + "443" + ] + } + ] + } + }, + { + "name": "egressNetRegionalTCP443AzureCloud", + "properties": { + "priority": 65000, + "action": { + "type": "Allow" + }, + "rules": [ + { + "name": "[concat(variables('regionalRulePrefix'),'AADPodIdentityWorkaround')]", + "protocols": [ + "TCP" + ], + "sourceAddresses": [ + "[parameters('aksClusterSubnetPrefix')]" + ], + "destinationAddresses": [ + "[concat('AzureCloud.',parameters('serviceTagsLocation'))]" + ], + "sourceIpGroups": [], + "destinationIpGroups": [], + "destinationFqdns": [], + "destinationPorts": [ + "443" + ] + } + ] + } + } + ], + "applicationRuleCollections": [ + { + "name": "egressAppAks", + "properties": { + "priority": 100, + "action": { + "type": "Allow" + }, + "rules": [ + { + "name": "[concat(variables('rulePrefix'),'ContainerRegistries')]", + "protocols": [ + { + "protocolType": "https", + "port": "443" + } + ], + "sourceAddresses": [ + "[parameters('aksClusterSubnetPrefix')]" + ], + "targetFqdns": [ + "gcr.io", + "storage.googleapis.com", + "aksrepos.azurecr.io", + "*mcr.microsoft.com", + "*.cdn.mscr.io", + "*.blob.core.windows.net" + ] + }, + { + "name": "[concat(variables('rulePrefix'),'K8sGETPUTOperations')]", + "protocols": [ + { + "protocolType": "https", + "port": "443" + } + ], + "sourceAddresses": [ + "[parameters('aksClusterSubnetPrefix')]" + ], + "targetFqdns": [ + "management.azure.com" + ] + }, + { + "name": "[concat(variables('rulePrefix'),'Ubuntu')]", + "protocols": [ + { + "protocolType": "http", + "port": "80" + }, + { + "protocolType": "https", + "port": "443" + } + ], + "sourceAddresses": [ + "[parameters('aksClusterSubnetPrefix')]" + ], + "targetFqdns": [ + "*.ubuntu.com" + ] + }, + { + "name": "[concat(variables('rulePrefix'),'AzureCNIBinaries')]", + "protocols": [ + { + "protocolType": "https", + "port": "443" + } + ], + "sourceAddresses": [ + "[parameters('aksClusterSubnetPrefix')]" + ], + "targetFqdns": [ + "acs-mirror.azureedge.net" + ] + }, + { + "name": "[concat(variables('rulePrefix'),'AzureActiveDirectoryAuthN')]", + "protocols": [ + { + "protocolType": "https", + "port": "443" + } + ], + "sourceAddresses": [ + "[parameters('aksClusterSubnetPrefix')]" + ], + "targetFqdns": [ + "login.microsoftonline.com" + ] + } + ] + } + }, + { + "name": "egressAppFabrikamDroneDelivery", + "properties": { + "priority": 200, + "action": { + "type": "Allow" + }, + "rules": [ + { + "name": "[concat(variables('rulePrefix'),'AzureContainerRegistries')]", + "protocols": [ + { + "protocolType": "https", + "port": "443" + } + ], + "sourceAddresses": [ + "[parameters('aksClusterSubnetPrefix')]" + ], + "targetFqdns": "[parameters('acrServers')]" + }, + { + "name": "[concat(variables('rulePrefix'),'AzureCacheRedis')]", + "protocols": [ + { + "protocolType": "https", + "port": "6380" + } + ], + "sourceAddresses": [ + "[parameters('aksClusterSubnetPrefix')]" + ], + "targetFqdns": "[parameters('deliveryRedisHostNames')]" + } + ] + } + } + ], + "natRuleCollections": [] + } + } + ], + "outputs": { + "azureFirewallName": { + "type": "string", + "value": "[variables('egressFirewallName')]" + } + } +} diff --git a/azuredeploy-prereqs.json b/azuredeploy-prereqs.json new file mode 100644 index 00000000..960d4cf6 --- /dev/null +++ b/azuredeploy-prereqs.json @@ -0,0 +1,190 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "environmentName": { + "type": "string", + "defaultValue": "dev", + "allowedValues": [ + "dev", + "qa", + "staging", + "prod" + ] + }, + "resourceGroupName": { + "type": "string" + }, + "resourceGroupLocation": { + "type": "string", + "defaultValue": "eastus" + } + }, + "variables": { + "acrResourceGroupNamePrefix": "[concat(parameters('resourceGroupName'),'-acr')]", + "nestedIdDeploymentName": "[concat(deployment().name,'-identities')]", + "environmentSettings": { + "dev": { + "deliveryIdName": "dev-d", + "workflowIdName": "dev-wf", + "droneSchedulerIdName": "dev-ds", + "appGatewayControllerIdName": "dev-ag", + "acrResourceGroupName": "[variables('acrResourceGroupNamePrefix')]" + }, + "qa": { + "deliveryIdName": "qa-d", + "workflowIdName": "qa-wf", + "droneSchedulerIdName": "qa-ds", + "appGatewayControllerIdName": "qa-ag", + "acrResourceGroupName": "[variables('acrResourceGroupNamePrefix')]" + }, + "staging": { + "deliveryIdName": "staging-d", + "workflowIdName": "staging-wf", + "droneSchedulerIdName": "staging-ds", + "appGatewayControllerIdName": "staging-ag", + "acrResourceGroupName": "[variables('acrResourceGroupNamePrefix')]" + }, + "prod": { + "deliveryIdName": "prod-d", + "workflowIdName": "prod-wf", + "droneSchedulerIdName": "prod-ds", + "appGatewayControllerIdName": "prod-ag", + "acrResourceGroupName": "[concat(variables('acrResourceGroupNamePrefix'),'-',parameters('environmentName'))]" + } + } + }, + "resources": [ + { + "name": "[parameters('resourceGroupName')]", + "location": "[parameters('resourceGroupLocation')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "tags": { + "displayName": "Resource Group for general purpose" + } + }, + { + "name": "[variables('environmentSettings')[parameters('environmentName')].acrResourceGroupName]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('resourceGroupLocation')]", + "tags": { + "displayName": "Container Registry Resource Group" + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "[variables('nestedIdDeploymentName')]", + "resourceGroup": "[parameters('resourceGroupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('resourceGroupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [ + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "name": "[variables('environmentSettings')[parameters('environmentName')].workflowIdName]", + "apiVersion": "2015-08-31-preview", + "location": "[parameters('resourceGroupLocation')]", + "tags": { + "displayName": "workflow managed identity", + "what": "rbac", + "reason": "aad-pod-identity", + "app": "fabrikam-workflow", + "[parameters('environmentName')]": true + } + }, + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "name": "[variables('environmentSettings')[parameters('environmentName')].deliveryIdName]", + "apiVersion": "2015-08-31-preview", + "location": "[parameters('resourceGroupLocation')]", + "tags": { + "displayName": "delivery managed identity", + "what": "rbac", + "reason": "aad-pod-identity", + "app": "fabrikam-delivery", + "[parameters('environmentName')]": true + } + }, + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "name": "[variables('environmentSettings')[parameters('environmentName')].droneSchedulerIdName]", + "apiVersion": "2015-08-31-preview", + "location": "[parameters('resourceGroupLocation')]", + "tags": { + "displayName": "dronescheduler managed identity", + "what": "rbac", + "reason": "aad-pod-identity", + "app": "fabrikam-dronescheduler", + "[parameters('environmentName')]": true + } + }, + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "name": "[variables('environmentSettings')[parameters('environmentName')].appGatewayControllerIdName]", + "apiVersion": "2015-08-31-preview", + "location": "[parameters('resourceGroupLocation')]", + "tags": { + "displayName": "app gateway controller managed identity", + "what": "rbac", + "reason": "aad-pod-identity", + "[parameters('environmentName')]": true + } + } + ], + "outputs": { + "deliveryIdName": { + "value": "[variables('environmentSettings')[parameters('environmentName')].deliveryIdName]", + "type": "string" + }, + "deliveryPrincipalResourceId": { + "value": "[concat(subscription().id, '/resourceGroups/',parameters('resourceGroupName'),'/providers/Microsoft.ManagedIdentity/userAssignedIdentities/',variables('environmentSettings')[parameters('environmentName')].deliveryIdName)]", + "type": "string" + }, + "droneSchedulerIdName": { + "value": "[variables('environmentSettings')[parameters('environmentName')].droneSchedulerIdName]", + "type": "string" + }, + "droneSchedulerPrincipalResourceId": { + "value": "[concat(subscription().id, '/resourceGroups/',parameters('resourceGroupName'),'/providers/Microsoft.ManagedIdentity/userAssignedIdentities/',variables('environmentSettings')[parameters('environmentName')].droneSchedulerIdName)]", + "type": "string" + }, + "workflowIdName": { + "value": "[variables('environmentSettings')[parameters('environmentName')].workflowIdName]", + "type": "string" + }, + "workflowPrincipalResourceId": { + "value": "[concat(subscription().id, '/resourceGroups/',parameters('resourceGroupName'),'/providers/Microsoft.ManagedIdentity/userAssignedIdentities/',variables('environmentSettings')[parameters('environmentName')].workflowIdName)]", + "type": "string" + }, + "appGatewayControllerIdName": { + "value": "[variables('environmentSettings')[parameters('environmentName')].appGatewayControllerIdName]", + "type": "string" + }, + "appGatewayControllerPrincipalResourceId": { + "value": "[concat(subscription().id, '/resourceGroups/',parameters('resourceGroupName'),'/providers/Microsoft.ManagedIdentity/userAssignedIdentities/',variables('environmentSettings')[parameters('environmentName')].appGatewayControllerIdName)]", + "type": "string" + }, + "acrResourceGroupName": { + "value": "[variables('environmentSettings')[parameters('environmentName')].acrResourceGroupName]", + "type": "string" + } + } + } + } + } + ], + "outputs": { + "identitiesDeploymentName": { + "value": "[variables('nestedIdDeploymentName')]", + "type": "string" + } + } +} diff --git a/azuredeploy.json b/azuredeploy.json new file mode 100644 index 00000000..78fb8766 --- /dev/null +++ b/azuredeploy.json @@ -0,0 +1,1622 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "environmentName": { + "type": "string", + "defaultValue": "dev", + "allowedValues": [ + "dev", + "qa", + "staging", + "prod" + ] + }, + "acrResourceGroupName": { + "type": "string" + }, + "acrResourceGroupLocation": { + "type": "string" + }, + "deliveryIdName": { + "metadata": { + "description": "Name of the delivery managed identity" + }, + "type": "string" + }, + "deliveryPrincipalId": { + "metadata": { + "description": "Principal id for the delivery managed identity" + }, + "type": "string" + }, + "droneSchedulerIdName": { + "metadata": { + "description": "Name of the drone scheduler managed identity" + }, + "type": "string" + }, + "droneSchedulerPrincipalId": { + "metadata": { + "description": "Principal id for the drone scheduler managed identity" + }, + "type": "string" + }, + "workflowIdName": { + "metadata": { + "description": "Name of the workflow managed identity" + }, + "type": "string" + }, + "workflowPrincipalId": { + "metadata": { + "description": "Principal id for the workflow managed identity" + }, + "type": "string" + }, + "appGatewayControllerIdName": { + "metadata": { + "description": "Name of the app gateway controller managed identity" + }, + "type": "string" + }, + "appGatewayControllerPrincipalId": { + "metadata": { + "description": "Principal id for the app gateway controller managed identity" + }, + "type": "string" + }, + "sshRSAPublicKey": { + "type": "string", + "metadata": { + "description": "Configure all linux machines with the SSH RSA public key string. Your key should include three parts, for example 'ssh-rsa AAAAB...snip...UcyupgH azureuser@linuxvm'" + } + }, + "servicePrincipalClientId": { + "metadata": { + "description": "Client ID (used by cloudprovider)" + }, + "type": "string" + }, + "servicePrincipalClientSecret": { + "metadata": { + "description": "The Service Principal Client Secret." + }, + "type": "securestring" + }, + "servicePrincipalId": { + "metadata": { + "description": "Principal ID (used by cloudprovider)" + }, + "type": "string" + }, + "osType": { + "type": "string", + "defaultValue": "Linux", + "allowedValues": [ + "Linux" + ], + "metadata": { + "description": "The type of operating system." + } + }, + "osDiskSizeGB": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Disk size (in GB) to provision for each of the agent pool nodes. This value ranges from 0 to 1023. Specifying 0 will apply the default disk size for that agentVMSize." + }, + "minValue": 0, + "maxValue": 1023 + }, + "adminUsername": { + "type": "string", + "metadata": { + "description": "User name for the Linux Virtual Machines." + }, + "defaultValue": "azureuser" + }, + "kubernetesVersion": { + "type": "string", + "metadata": { + "description": "The version of Kubernetes. It must be supported in the target location." + } + }, + "deliveryRedisStorageType": { + "type": "string", + "defaultValue": "Standard_LRS", + "allowedValues": [ + "Standard_LRS", + "Standard_ZRS", + "Standard_GRS" + ], + "metadata": { + "description": "Type of the storage account that will store Redis Cache." + } + }, + "deliveryRedisDiagnosticsEnabled": { + "type": "bool", + "allowedValues": [ + false, + true + ], + "defaultValue": false, + "metadata": { + "description": "A value that indicates whether diagnostics should be saved to the specified storage account." + } + }, + "networkPolicy": { + "type": "string", + "defaultValue": "azure", + "allowedValues": [ + "azure", + "calico" + ], + "metadata": { + "description": "Network policy used for configuring Kubernetes network profile." + } + }, + "applicationGatewaySize": { + "type": "string", + "allowedValues": [ + "WAF_v2", + "Standard_v2" + ], + "defaultValue": "WAF_v2", + "metadata": { + "description": "Application gateway size" + } + }, + "applicationGatewayTier": { + "type": "string", + "allowedValues": [ + "WAF_v2", + "Standard_v2" + ], + "defaultValue": "WAF_v2", + "metadata": { + "description": "Application gateway tier" + } + }, + "applicationGatewayWafEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "WAF Enabled" + } + }, + "applicationGatewayWafMode": { + "type": "string", + "allowedValues": [ + "Detection", + "Prevention" + ], + "defaultValue": "Detection", + "metadata": { + "description": "WAF Mode" + } + }, + "applicationGatewayWafRuleSetType": { + "type": "string", + "allowedValues": [ + "OWASP" + ], + "defaultValue": "OWASP", + "metadata": { + "description": "WAF Rule Set Type" + } + }, + "applicationGatewayWafRuleSetVersion": { + "type": "string", + "allowedValues": [ + "2.2.9", + "3.0" + ], + "defaultValue": "3.0", + "metadata": { + "description": "WAF Rule Set Version" + } + } + }, + "variables": { + "clusterNamePrefix": "aks", + "acrNamePrefix": "acr", + "aiNamePrefix": "ai", + "acrName": "[uniqueString(variables('acrNamePrefix'),resourceGroup().id)]", + "appGatewayNamePrefix": "appg", + "aksVnetAddressPrefix": "10.10.0.0/16", + "aksClusterSubnetPrefix": "10.10.0.0/21", + "appGatewaySubnetPrefixes": [ + "10.10.8.0/24", + "10.10.9.0/24", + "10.10.10.0/24", + "10.10.11.0/24" + ], + "firewallSubnetPrefix": "10.10.12.0/24", + "firewallSubnetName": "AzureFirewallSubnet", + "aksVnetNamePrefix": "vnet", + "aksClusterSubnetNamePrefix": "subnet", + "readerRoleObjectId": "acdd72a7-3385-48ef-bd42-f606fba81ae7", + "managedIdentityOperatorRoleObjectId": "f1a07417-d97a-45cb-824c-7a7467783830", + "contributorRoleObjectId": "b24988ac-6180-42a0-ab88-20f7382dd24c", + "readerRoleId": "[concat(subscription().Id, '/providers/Microsoft.Authorization/roleDefinitions/', variables('readerRoleObjectId'))]", + "managedIdentityOperatorRoleId": "[concat(subscription().Id, '/providers/Microsoft.Authorization/roleDefinitions/', variables('managedIdentityOperatorRoleObjectId'))]", + "contributorRoleId": "[concat(subscription().Id, '/providers/Microsoft.Authorization/roleDefinitions/', variables('contributorRoleObjectId'))]", + "deliveryRedisStorageName": "[concat(parameters('environmentName'),'rsto',uniqueString(resourceGroup().id))]", + "nestedACRDeploymentName": "[concat('azuredeploy-acr-',parameters('acrResourceGroupName'),parameters('environmentName'))]", + "aksLogAnalyticsNamePrefix": "logsAnalytics", + "egressFirewallPrefix": "egressFw", + "firewallPublicIpName": "[concat('firewallPip-',uniqueString(variables('egressFirewallPrefix'), resourceGroup().id))]", + "environmentSettings": { + "dev": { + "aksClusterName": "[uniqueString(variables('clusterNamePrefix'), resourceGroup().id)]", + "aksMinCount": null, + "aksMaxCount": null, + "aksEnableAutoScaling": false, + "acrName": "[variables('acrName')]", + "appGatewayName": "[concat(parameters('environmentName'),'-ag-', uniqueString(variables('appGatewayNamePrefix'), resourceGroup().id))]", + "applicationGatewayMinCapacity": 1, + "aksVnetName": "[uniqueString(variables('aksVnetNamePrefix'), resourceGroup().id)]", + "aksClusterSubnetName": "[uniqueString(variables('aksClusterSubnetNamePrefix'), resourceGroup().id)]", + "appGatewaySubnetIndex": 0, + "appGatewayPublicIpName": "[concat(parameters('environmentName'),'-agip-', uniqueString(variables('appGatewayNamePrefix'), resourceGroup().id))]", + "appGatewayPublicDnsName": "[concat(parameters('environmentName'),'-ingest-', uniqueString(variables('appGatewayNamePrefix'), resourceGroup().id))]", + "appInsightsName": "[concat(parameters('environmentName'),uniqueString(variables('aiNamePrefix'),resourceGroup().id))]", + "deliveryRedisStorageName": "[variables('deliveryRedisStorageName')]", + "deliveryRedisStorageId": "[resourceId('Microsoft.Storage/storageAccounts',variables('deliveryRedisStorageName'))]", + "deliveryRedisCacheSKU": "Basic", + "deliveryRedisCacheFamily": "C", + "deliveryRedisCacheCapacity": 0, + "deliveryCosmosDbName": "[concat(parameters('environmentName'),'-d-', uniqueString(resourceGroup().id))]", + "deliveryRedisName": "[concat(parameters('environmentName'),'-d-',uniqueString(resourceGroup().id))]", + "deliveryKeyVaultName": "[concat(parameters('environmentName'),'-d-',uniqueString(resourceGroup().id))]", + "packageMongoDbName": "[concat(parameters('environmentName'),'-p-',uniqueString(resourceGroup().id))]", + "ingestionSBNamespace": "[concat(parameters('environmentName'),'-i-',uniqueString(resourceGroup().id))]", + "ingestionSBNamespaceSKU": "Premium", + "ingestionSBNamespaceTier": "Premium", + "ingestionSBName": "[concat(parameters('environmentName'),'-i-',uniqueString(resourceGroup().id))]", + "ingestionServiceAccessKey": "IngestionServiceAccessKey", + "droneSchedulerKeyVaultName": "[concat(parameters('environmentName'),'-ds-',uniqueString(resourceGroup().id))]", + "droneSchedulerCosmosDbName": "[concat(parameters('environmentName'),'-ds-',uniqueString(resourceGroup().id))]", + "workflowKeyVaultName": "[concat(parameters('environmentName'),'-wf-',uniqueString(resourceGroup().id))]", + "workflowServiceAccessKey": "WorkflowServiceAccessKey", + "agentCount": 1, + "agentVMSize": "Standard_D2_v2", + "workspaceName": "[concat(parameters('environmentName'),'-la-', uniqueString(variables('aksLogAnalyticsNamePrefix'), resourceGroup().id))]", + "workspaceSku": "Free", + "workspaceRetentionInDays": null + }, + "qa": { + "aksClusterName": "[uniqueString(variables('clusterNamePrefix'), resourceGroup().id)]", + "aksMinCount": null, + "aksMaxCount": null, + "aksEnableAutoScaling": false, + "acrName": "[variables('acrName')]", + "appGatewayName": "[concat(parameters('environmentName'),'-ag-', uniqueString(variables('appGatewayNamePrefix'), resourceGroup().id))]", + "applicationGatewayMinCapacity": 1, + "aksVnetName": "[uniqueString(variables('aksVnetNamePrefix'), resourceGroup().id)]", + "aksClusterSubnetName": "[uniqueString(variables('aksClusterSubnetNamePrefix'), resourceGroup().id)]", + "appGatewaySubnetIndex": 1, + "appGatewayPublicIpName": "[concat(parameters('environmentName'),'-agip-', uniqueString(variables('appGatewayNamePrefix'), resourceGroup().id))]", + "appGatewayPublicDnsName": "[concat(parameters('environmentName'),'-ingest-', uniqueString(variables('appGatewayNamePrefix'), resourceGroup().id))]", + "appInsightsName": "[concat(parameters('environmentName'),uniqueString(variables('aiNamePrefix'),resourceGroup().id))]", + "deliveryRedisStorageName": "[variables('deliveryRedisStorageName')]", + "deliveryRedisStorageId": "[resourceId('Microsoft.Storage/storageAccounts',variables('deliveryRedisStorageName'))]", + "deliveryRedisCacheSKU": "Basic", + "deliveryRedisCacheFamily": "C", + "deliveryRedisCacheCapacity": 0, + "deliveryCosmosDbName": "[concat(parameters('environmentName'),'-d-', uniqueString(resourceGroup().id))]", + "deliveryRedisName": "[concat(parameters('environmentName'),'-d-',uniqueString(resourceGroup().id))]", + "deliveryKeyVaultName": "[concat(parameters('environmentName'),'-d-',uniqueString(resourceGroup().id))]", + "packageMongoDbName": "[concat(parameters('environmentName'),'-p-',uniqueString(resourceGroup().id))]", + "ingestionSBNamespace": "[concat(parameters('environmentName'),'-i-',uniqueString(resourceGroup().id))]", + "ingestionSBNamespaceSKU": "Premium", + "ingestionSBNamespaceTier": "Premium", + "ingestionSBName": "[concat(parameters('environmentName'),'-i-',uniqueString(resourceGroup().id))]", + "ingestionServiceAccessKey": "IngestionServiceAccessKey", + "droneSchedulerKeyVaultName": "[concat(parameters('environmentName'),'-ds-',uniqueString(resourceGroup().id))]", + "droneSchedulerCosmosDbName": "[concat(parameters('environmentName'),'-ds-',uniqueString(resourceGroup().id))]", + "workflowKeyVaultName": "[concat(parameters('environmentName'),'-wf-',uniqueString(resourceGroup().id))]", + "workflowServiceAccessKey": "WorkflowServiceAccessKey", + "agentCount": 3, + "agentVMSize": "Standard_D2_v2", + "workspaceName": "[concat(parameters('environmentName'),'-la-', uniqueString(variables('aksLogAnalyticsNamePrefix'), resourceGroup().id))]", + "workspaceSku": "Free", + "workspaceRetentionInDays": null + }, + "staging": { + "aksClusterName": "[uniqueString(variables('clusterNamePrefix'), resourceGroup().id)]", + "aksMinCount": 3, + "aksMaxCount": 50, + "aksEnableAutoScaling": true, + "acrName": "[variables('acrName')]", + "appGatewayName": "[concat(parameters('environmentName'),'-ag-', uniqueString(variables('appGatewayNamePrefix'), resourceGroup().id))]", + "applicationGatewayMinCapacity": 2, + "aksVnetName": "[uniqueString(variables('aksVnetNamePrefix'), resourceGroup().id)]", + "aksClusterSubnetName": "[uniqueString(variables('aksClusterSubnetNamePrefix'), resourceGroup().id)]", + "appGatewaySubnetIndex": 2, + "appGatewayPublicIpName": "[concat(parameters('environmentName'),'-agip-', uniqueString(variables('appGatewayNamePrefix'), resourceGroup().id))]", + "appGatewayPublicDnsName": "[concat(parameters('environmentName'),'-ingest-', uniqueString(variables('appGatewayNamePrefix'), resourceGroup().id))]", + "appInsightsName": "[concat(parameters('environmentName'),uniqueString(variables('aiNamePrefix'),resourceGroup().id))]", + "deliveryRedisStorageName": "[variables('deliveryRedisStorageName')]", + "deliveryRedisStorageId": "[resourceId('Microsoft.Storage/storageAccounts',variables('deliveryRedisStorageName'))]", + "deliveryRedisCacheSKU": "Standard", + "deliveryRedisCacheFamily": "C", + "deliveryRedisCacheCapacity": 1, + "deliveryCosmosDbName": "[concat(parameters('environmentName'),'-d-', uniqueString(resourceGroup().id))]", + "deliveryRedisName": "[concat(parameters('environmentName'),'-d-',uniqueString(resourceGroup().id))]", + "deliveryKeyVaultName": "[concat(parameters('environmentName'),'-d-',uniqueString(resourceGroup().id))]", + "packageMongoDbName": "[concat(parameters('environmentName'),'-p-',uniqueString(resourceGroup().id))]", + "ingestionSBNamespace": "[concat(parameters('environmentName'),'-i-',uniqueString(resourceGroup().id))]", + "ingestionSBNamespaceSKU": "Premium", + "ingestionSBNamespaceTier": "Premium", + "ingestionSBName": "[concat(parameters('environmentName'),'-i-',uniqueString(resourceGroup().id))]", + "ingestionServiceAccessKey": "IngestionServiceAccessKey", + "droneSchedulerKeyVaultName": "[concat(parameters('environmentName'),'-ds-',uniqueString(resourceGroup().id))]", + "droneSchedulerCosmosDbName": "[concat(parameters('environmentName'),'-ds-',uniqueString(resourceGroup().id))]", + "workflowKeyVaultName": "[concat(parameters('environmentName'),'-wf-',uniqueString(resourceGroup().id))]", + "workflowServiceAccessKey": "WorkflowServiceAccessKey", + "agentCount": 3, + "agentVMSize": "Standard_D2_v2", + "workspaceName": "[concat(parameters('environmentName'),'-la-', uniqueString(variables('aksLogAnalyticsNamePrefix'), resourceGroup().id))]", + "workspaceSku": "PerGB2018", + "workspaceRetentionInDays": 730 + }, + "prod": { + "aksClusterName": "[uniqueString(variables('clusterNamePrefix'), resourceGroup().id)]", + "aksMinCount": 3, + "aksMaxCount": 50, + "aksEnableAutoScaling": true, + "acrName": "[concat(parameters('environmentName'),variables('acrName'))]", + "appGatewayName": "[concat(parameters('environmentName'),'-ag-', uniqueString(variables('appGatewayNamePrefix'), resourceGroup().id))]", + "applicationGatewayMinCapacity": 2, + "aksVnetName": "[uniqueString(variables('aksVnetNamePrefix'), resourceGroup().id)]", + "aksClusterSubnetName": "[uniqueString(variables('aksClusterSubnetNamePrefix'), resourceGroup().id)]", + "appGatewaySubnetIndex": 3, + "appGatewayPublicIpName": "[concat(parameters('environmentName'),'-agip-', uniqueString(variables('appGatewayNamePrefix'), resourceGroup().id))]", + "appGatewayPublicDnsName": "[concat(parameters('environmentName'),'-ingest-', uniqueString(variables('appGatewayNamePrefix'), resourceGroup().id))]", + "appInsightsName": "[concat(parameters('environmentName'),uniqueString(variables('aiNamePrefix'),resourceGroup().id))]", + "deliveryRedisStorageName": "[variables('deliveryRedisStorageName')]", + "deliveryRedisStorageId": "[resourceId('Microsoft.Storage/storageAccounts',variables('deliveryRedisStorageName'))]", + "deliveryRedisCacheSKU": "Standard", + "deliveryRedisCacheFamily": "C", + "deliveryRedisCacheCapacity": 1, + "deliveryCosmosDbName": "[concat(parameters('environmentName'),'-d-', uniqueString(resourceGroup().id))]", + "deliveryRedisName": "[concat(parameters('environmentName'),'-d-',uniqueString(resourceGroup().id))]", + "deliveryKeyVaultName": "[concat(parameters('environmentName'),'-d-',uniqueString(resourceGroup().id))]", + "packageMongoDbName": "[concat(parameters('environmentName'),'-p-',uniqueString(resourceGroup().id))]", + "ingestionSBNamespace": "[concat(parameters('environmentName'),'-i-',uniqueString(resourceGroup().id))]", + "ingestionSBNamespaceSKU": "Premium", + "ingestionSBNamespaceTier": "Premium", + "ingestionSBName": "[concat(parameters('environmentName'),'-i-',uniqueString(resourceGroup().id))]", + "ingestionServiceAccessKey": "IngestionServiceAccessKey", + "droneSchedulerKeyVaultName": "[concat(parameters('environmentName'),'-ds-',uniqueString(resourceGroup().id))]", + "droneSchedulerCosmosDbName": "[concat(parameters('environmentName'),'-ds-',uniqueString(resourceGroup().id))]", + "workflowKeyVaultName": "[concat(parameters('environmentName'),'-wf-',uniqueString(resourceGroup().id))]", + "workflowServiceAccessKey": "WorkflowServiceAccessKey", + "agentCount": 3, + "agentVMSize": "Standard_D2_v2", + "workspaceName": "[concat(parameters('environmentName'),'-la-', uniqueString(variables('aksLogAnalyticsNamePrefix'), resourceGroup().id))]", + "workspaceSku": "PerGB2018", + "workspaceRetentionInDays": 730 + } + }, + "aksClusterSubnet": { + "name": "[variables('environmentSettings')[parameters('environmentName')].aksClusterSubnetName]", + "properties": { + "addressPrefix": "[variables('aksClusterSubnetPrefix')]", + "privateEndpointNetworkPolicies": "Enabled", + "privateLinkServiceNetworkPolicies": "Enabled" + } + }, + "firewallSubnet": { + "name": "[variables('firewallSubnetName')]", + "properties": { + "addressPrefix": "[variables('firewallSubnetPrefix')]", + "serviceEndpoints": [ + { + "service": "Microsoft.KeyVault", + "locations": [ + "[resourceGroup().location]" + ] + }, + { + "service": "Microsoft.AzureCosmosDB", + "locations": [ + "[resourceGroup().location]" + ] + }, + { + "service": "Microsoft.Storage", + "locations": [ + "[resourceGroup().location]" + ] + }, + { + "service": "Microsoft.ServiceBus", + "locations": [ + "[resourceGroup().location]" + ] + } + ] + } + }, + "copy": [ + { + "name": "appGatewaySubnetsLoop", + "count": "[length(variables('appGatewaySubnetPrefixes'))]", + "input": { + "name": "[concat('agsn-', uniqueString(variables('appGatewayNamePrefix'), resourceGroup().id), copyIndex('appGatewaySubnetsLoop'))]", + "properties": { + "addressPrefix": "[variables('appGatewaySubnetPrefixes')[copyIndex('appGatewaySubnetsLoop')]]" + } + } + } + ] + }, + "resources": [ + { + "name": "[variables('nestedACRDeploymentName')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "resourceGroup": "[parameters('acrResourceGroupName')]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [ + { + "name": "[variables('environmentSettings')[parameters('environmentName')].acrName]", + "type": "Microsoft.ContainerRegistry/registries", + "apiVersion": "2017-10-01", + "sku": { + "name": "Basic", + "tier": "Basic" + }, + "location": "[resourceGroup().location]", + "tags": { + "displayName": "Container Registry", + "container.registry": "[variables('environmentSettings')[parameters('environmentName')].acrName]", + "clusterName": "[variables('environmentSettings')[parameters('environmentName')].aksClusterName]" + }, + "properties": { + "adminUserEnabled": false + } + }, + { + "type": "Microsoft.ContainerRegistry/registries/providers/roleAssignments", + "name": "[concat(variables('environmentSettings')[parameters('environmentName')].acrName,'/Microsoft.Authorization/',guid(concat('aks',variables('environmentSettings')[parameters('environmentName')].acrName), resourceGroup().id))]", + "apiVersion": "2017-05-01", + "comments": "Grant the AKS cluster access to the ACR instance", + "tags": { + "displayName": "AKS SP RBAC Access to ACR", + "what": "rbac", + "to": "cluster", + "identity-type": "sp", + "access": "acr", + "reason": "pull-images" + }, + "properties": { + "roleDefinitionId": "[variables('readerRoleId')]", + "principalId": "[parameters('servicePrincipalId')]", + "scope": "[concat(subscription().id, '/resourceGroups/',parameters('acrResourceGroupName'),'/providers/Microsoft.ContainerRegistry/registries/',variables('environmentSettings')[parameters('environmentName')].acrName)]" + }, + "dependsOn": [ + "[concat(subscription().id, '/resourceGroups/',parameters('acrResourceGroupName'),'/providers/Microsoft.ContainerRegistry/registries/',variables('environmentSettings')[parameters('environmentName')].acrName)]" + ] + } + ], + "outputs": { + "acrId": { + "value": "[resourceId('Microsoft.ContainerRegistry/registries', variables('environmentSettings')[parameters('environmentName')].acrName)]", + "type": "string" + } + } + } + } + }, + { + "name": "[variables('environmentSettings')[parameters('environmentName')].aksVnetName]", + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2018-06-01", + "location": "[resourceGroup().location]", + "tags": { + "displayName": "Vnet for the aks cluster and the app gateways", + "environment": "shared network" + }, + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[variables('aksVnetAddressPrefix')]" + ] + }, + "subnets": "[concat(createArray(variables('aksClusterSubnet'), variables('firewallSubnet')), variables('appGatewaySubnetsLoop'))]" + } + }, + { + "apiVersion": "2018-08-01", + "type": "Microsoft.Network/publicIPAddresses", + "name": "[variables('environmentSettings')[parameters('environmentName')].appGatewayPublicIpName]", + "location": "[resourceGroup().location]", + "tags": { + "displayName": "Public IP address for the application gateway", + "environment": "[parameters('environmentName')]" + }, + "sku": { + "name": "Standard" + }, + "properties": { + "publicIPAllocationMethod": "Static", + "dnsSettings": { + "domainNameLabel": "[variables('environmentSettings')[parameters('environmentName')].appGatewayPublicDnsName]" + } + } + }, + { + "apiVersion": "2018-08-01", + "name": "[variables('environmentSettings')[parameters('environmentName')].appGatewayName]", + "type": "Microsoft.Network/applicationGateways", + "location": "[resourceGroup().location]", + "tags": { + "displayName": "Application gateway for the app gateway ingress", + "environment": "[parameters('environmentName')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', variables('environmentSettings')[parameters('environmentName')].aksVnetName)]", + "[resourceId('Microsoft.Network/publicIPAddresses', variables('environmentSettings')[parameters('environmentName')].appGatewayPublicIpName)]" + ], + "properties": { + "sku": { + "name": "[parameters('applicationGatewaySize')]", + "tier": "[parameters('applicationGatewayTier')]" + }, + "autoscaleConfiguration": { + "minCapacity": "[variables('environmentSettings')[parameters('environmentName')].applicationGatewayMinCapacity]" + }, + "gatewayIPConfigurations": [ + { + "name": "appGatewayIpConfig", + "properties": { + "subnet": { + "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('environmentSettings')[parameters('environmentName')].aksVnetName, variables('appGatewaySubnetsLoop')[variables('environmentSettings')[parameters('environmentName')].appGatewaySubnetIndex].name)]" + } + } + } + ], + "frontendIPConfigurations": [ + { + "name": "appGatewayFrontendIP", + "properties": { + "PublicIPAddress": { + "id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('environmentSettings')[parameters('environmentName')].appGatewayPublicIpName)]" + } + } + } + ], + "backendAddressPools": [ + { + "name": "defaultAddressPool" + } + ], + "backendHttpSettingsCollection": [ + { + "name": "defaultBackendHttpSettings", + "properties": { + "Port": "80", + "Protocol": "Http" + } + } + ], + "httpListeners": [ + { + "name": "defaultHttpListener", + "properties": { + "FrontendIpConfiguration": { + "Id": "[resourceId('Microsoft.Network/applicationGateways/frontendIPConfigurations', variables('environmentSettings')[parameters('environmentName')].appGatewayName, 'appGatewayFrontendIP')]" + }, + "FrontendPort": { + "Id": "[resourceId('Microsoft.Network/applicationGateways/frontendPorts', variables('environmentSettings')[parameters('environmentName')].appGatewayName, 'http')]" + }, + "Protocol": "Http" + } + } + ], + "requestRoutingRules": [ + { + "Name": "default", + "properties": { + "RuleType": "Basic", + "httpListener": { + "id": "[resourceId('Microsoft.Network/applicationGateways/httpListeners', variables('environmentSettings')[parameters('environmentName')].appGatewayName, 'defaultHttpListener')]" + }, + "backendAddressPool": { + "id": "[resourceId('Microsoft.Network/applicationGateways/backendAddressPools', variables('environmentSettings')[parameters('environmentName')].appGatewayName, 'defaultAddressPool')]" + }, + "backendHttpSettings": { + "id": "[resourceId('Microsoft.Network/applicationGateways/backendHttpSettingsCollection', variables('environmentSettings')[parameters('environmentName')].appGatewayName, 'defaultBackendHttpSettings')]" + } + } + } + ], + "frontendPorts": [ + { + "name": "http", + "properties": { + "Port": "80" + } + } + ], + "webApplicationFirewallConfiguration": { + "enabled": "[parameters('applicationGatewayWafEnabled')]", + "firewallMode": "[parameters('applicationGatewayWafMode')]", + "ruleSetType": "[parameters('applicationGatewayWafRuleSetType')]", + "ruleSetVersion": "[parameters('applicationGatewayWafRuleSetVersion')]", + "disabledRuleGroups": [] + } + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces", + "name": "[variables('environmentSettings')[parameters('environmentName')].workspaceName]", + "apiVersion": "2015-11-01-preview", + "location": "[resourceGroup().location]", + "properties": { + "retentionInDays": "[variables('environmentSettings')[parameters('environmentName')].workspaceRetentionInDays]", + "sku": { + "Name": "[variables('environmentSettings')[parameters('environmentName')].workspaceSku]" + }, + "features": { + "searchVersion": 1 + } + } + }, + { + "name": "[variables('environmentSettings')[parameters('environmentName')].aksClusterName]", + "type": "Microsoft.ContainerService/managedClusters", + "apiVersion": "2019-08-01", + "location": "[resourceGroup().location]", + "tags": { + "environment": "shared cluster" + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', variables('environmentSettings')[parameters('environmentName')].aksVnetName)]", + "[resourceId('Microsoft.OperationalInsights/workspaces', variables('environmentSettings')[parameters('environmentName')].workspaceName)]" + ], + "properties": { + "kubernetesVersion": "[parameters('kubernetesVersion')]", + "dnsPrefix": "[variables('environmentSettings')[parameters('environmentName')].aksClusterName]", + "agentPoolProfiles": [ + { + "name": "agentpool", + "osDiskSizeGB": "[parameters('osDiskSizeGB')]", + "minCount": "[variables('environmentSettings')[parameters('environmentName')].aksMinCount]", + "maxCount": "[variables('environmentSettings')[parameters('environmentName')].aksMaxCount]", + "enableAutoScaling": "[variables('environmentSettings')[parameters('environmentName')].aksEnableAutoScaling]", + "type": "VirtualMachineScaleSets", + "count": "[variables('environmentSettings')[parameters('environmentName')].agentCount]", + "vmSize": "[variables('environmentSettings')[parameters('environmentName')].agentVMSize]", + "osType": "[parameters('osType')]", + "storageProfile": "ManagedDisks", + "vnetSubnetID": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('environmentSettings')[parameters('environmentName')].aksVnetName, variables('environmentSettings')[parameters('environmentName')].aksClusterSubnetName)]" + } + ], + "linuxProfile": { + "adminUsername": "[parameters('adminUsername')]", + "ssh": { + "publicKeys": [ + { + "keyData": "[parameters('sshRSAPublicKey')]" + } + ] + } + }, + "servicePrincipalProfile": { + "clientId": "[parameters('servicePrincipalClientId')]", + "secret": "[parameters('servicePrincipalClientSecret')]" + }, + "addonProfiles": { + "omsagent": { + "config": { + "logAnalyticsWorkspaceResourceID": "[resourceId('Microsoft.OperationalInsights/workspaces', variables('environmentSettings')[parameters('environmentName')].workspaceName)]" + }, + "enabled": true + } + }, + "enableRBAC": true, + "networkProfile": { + "networkPolicy": "[parameters('networkPolicy')]", + "networkPlugin": "azure", + "serviceCidr": "10.2.0.0/24", + "dnsServiceIP": "10.2.0.10", + "dockerBridgeCidr": "172.17.0.1/16" + } + } + }, + { + "name": "[variables('environmentSettings')[parameters('environmentName')].appInsightsName]", + "type": "Microsoft.Insights/components", + "apiVersion": "2015-05-01", + "kind": "other", + "location": "[resourceGroup().location]", + "tags": { + "displayName": "App Insights instance - Distributed Tracing", + "environment": "[parameters('environmentName')]" + }, + "properties": { + "Application_Type": "other" + } + }, + { + "name": "[variables('environmentSettings')[parameters('environmentName')].deliveryRedisStorageName]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2015-06-15", + "location": "[resourceGroup().location]", + "comments": "This storage account is used by Delivery Redis", + "dependsOn": [], + "tags": { + "displayName": "Storage account for inflight deliveries", + "app": "fabrikam-delivery", + "environment": "[parameters('environmentName')]" + }, + "properties": { + "accountType": "[parameters('deliveryRedisStorageType')]" + } + }, + { + "apiVersion": "2018-03-01", + "name": "[variables('environmentSettings')[parameters('environmentName')].deliveryRedisName]", + "type": "Microsoft.Cache/Redis", + "location": "[resourceGroup().location]", + "tags": { + "displayName": "Redis Cache for inflight deliveries", + "app": "fabrikam-delivery", + "environment": "[parameters('environmentName')]" + }, + "dependsOn": [ + "[variables('environmentSettings')[parameters('environmentName')].deliveryRedisStorageId]" + ], + "properties": { + "redisEnableNonSslPort": "false", + "sku": { + "capacity": "[variables('environmentSettings')[parameters('environmentName')].deliveryRedisCacheCapacity]", + "family": "[variables('environmentSettings')[parameters('environmentName')].deliveryRedisCacheFamily]", + "name": "[variables('environmentSettings')[parameters('environmentName')].deliveryRedisCacheSKU]" + } + }, + "resources": [ + { + "name": "firewallAccess", + "type": "firewallRules", + "apiVersion": "2018-03-01", + "dependsOn": [ + "[resourceId('Microsoft.Cache/Redis', variables('environmentSettings')[parameters('environmentName')].deliveryRedisName)]", + "[resourceId('Microsoft.Network/publicIPAddresses', variables('firewallPublicIpName'))]" + ], + "properties": { + "startIP": "[reference(resourceId('Microsoft.Network/publicIPAddresses', variables('firewallPublicIpName'))).ipAddress]", + "endIP": "[reference(resourceId('Microsoft.Network/publicIPAddresses', variables('firewallPublicIpName'))).ipAddress]" + } + }, + { + "apiVersion": "2017-05-01-preview", + "type": "Microsoft.Cache/redis/providers/diagnosticsettings", + "name": "[concat(variables('environmentSettings')[parameters('environmentName')].deliveryRedisName, '/Microsoft.Insights/', variables('environmentSettings')[parameters('environmentName')].deliveryRedisName)]", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[resourceId('Microsoft.Cache/Redis', variables('environmentSettings')[parameters('environmentName')].deliveryRedisName)]" + ], + "properties": { + "storageAccountId": "[variables('environmentSettings')[parameters('environmentName')].deliveryRedisStorageId]", + "logs": [], + "metrics": [ + { + "timeGrain": "AllMetrics", + "enabled": "[parameters('deliveryRedisDiagnosticsEnabled')]", + "retentionPolicy": { + "days": 90, + "enabled": "[parameters('deliveryRedisDiagnosticsEnabled')]" + } + } + ] + } + } + ] + }, + { + "type": "Microsoft.DocumentDB/databaseAccounts", + "name": "[variables('environmentSettings')[parameters('environmentName')].deliveryCosmosDbName]", + "apiVersion": "2016-03-31", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', variables('environmentSettings')[parameters('environmentName')].aksVnetName)]" + ], + "tags": { + "displayName": "Delivery Cosmos Db", + "app": "fabrikam-delivery", + "environment": "[parameters('environmentName')]" + }, + "properties": { + "name": "[variables('environmentSettings')[parameters('environmentName')].deliveryCosmosDbName]", + "databaseAccountOfferType": "Standard", + "isVirtualNetworkFilterEnabled": true, + "virtualNetworkRules": [ + { + "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('environmentSettings')[parameters('environmentName')].aksVnetName, variables('firewallSubnetName'))]" + } + ], + "locations": [ + { + "locationName": "[resourceGroup().location]", + "failoverPriority": 0 + } + ] + } + }, + { + "apiVersion": "2015-04-08", + "type": "Microsoft.DocumentDB/databaseAccounts", + "kind": "MongoDB", + "name": "[variables('environmentSettings')[parameters('environmentName')].packageMongoDbName]", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', variables('environmentSettings')[parameters('environmentName')].aksVnetName)]" + ], + "tags": { + "displayName": "Package Cosmos Db", + "app": "fabrikam-package", + "environment": "[parameters('environmentName')]" + }, + "properties": { + "databaseAccountOfferType": "Standard", + "name": "[variables('environmentSettings')[parameters('environmentName')].packageMongoDbName]", + "isVirtualNetworkFilterEnabled": true, + "virtualNetworkRules": [ + { + "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('environmentSettings')[parameters('environmentName')].aksVnetName, variables('firewallSubnetName'))]" + } + ] + } + }, + { + "type": "Microsoft.DocumentDB/databaseAccounts", + "name": "[variables('environmentSettings')[parameters('environmentName')].droneSchedulerCosmosDbName]", + "apiVersion": "2015-04-08", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', variables('environmentSettings')[parameters('environmentName')].aksVnetName)]" + ], + "properties": { + "name": "[variables('environmentSettings')[parameters('environmentName')].droneSchedulerCosmosDbName]", + "databaseAccountOfferType": "Standard", + "isVirtualNetworkFilterEnabled": true, + "virtualNetworkRules": [ + { + "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('environmentSettings')[parameters('environmentName')].aksVnetName, variables('firewallSubnetName'))]" + } + ], + "locations": [ + { + "locationName": "[resourceGroup().location]", + "failoverPriority": 0 + } + ] + } + }, + { + "type": "Microsoft.ServiceBus/namespaces", + "name": "[variables('environmentSettings')[parameters('environmentName')].ingestionSBNamespace]", + "apiVersion": "2017-04-01", + "location": "[resourceGroup().location]", + "sku": { + "name": "[variables('environmentSettings')[parameters('environmentName')].ingestionSBNamespaceSKU]", + "tier": "[variables('environmentSettings')[parameters('environmentName')].ingestionSBNamespaceTier]" + }, + "tags": { + "displayName": "Ingestion and Workflow Service Bus", + "app": "fabrikam-ingestion", + "app-producer": "fabrikam-ingestion", + "app-consumer": "fabrikam-workflow", + "environment": "[parameters('environmentName')]" + }, + "resources": [ + { + "name": "[variables('environmentSettings')[parameters('environmentName')].ingestionSBName]", + "type": "queues", + "apiVersion": "2017-04-01", + "dependsOn": [ + "[resourceId('Microsoft.ServiceBus/namespaces', variables('environmentSettings')[parameters('environmentName')].ingestionSBNamespace)]" + ], + "properties": { + "lockDuration": "PT5M", + "maxSizeInMegabytes": "1024" + } + }, + { + "name": "[variables('environmentSettings')[parameters('environmentName')].ingestionServiceAccessKey]", + "type": "AuthorizationRules", + "apiVersion": "2017-04-01", + "properties": { + "rights": [ + "Send" + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.ServiceBus/namespaces', variables('environmentSettings')[parameters('environmentName')].ingestionSBNamespace)]" + ] + }, + { + "name": "[variables('environmentSettings')[parameters('environmentName')].workflowServiceAccessKey]", + "type": "AuthorizationRules", + "apiVersion": "2017-04-01", + "properties": { + "rights": [ + "Listen" + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.ServiceBus/namespaces', variables('environmentSettings')[parameters('environmentName')].ingestionSBNamespace)]" + ] + }, + { + "name": "firewall", + "type": "VirtualNetworkRules", + "apiVersion": "2018-01-01-preview", + "properties": { + "virtualNetworkSubnetId": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('environmentSettings')[parameters('environmentName')].aksVnetName, variables('firewallSubnetName'))]" + }, + "dependsOn": [ + "[resourceId('Microsoft.ServiceBus/namespaces', variables('environmentSettings')[parameters('environmentName')].ingestionSBNamespace)]", + "[resourceId('Microsoft.Network/virtualNetworks', variables('environmentSettings')[parameters('environmentName')].aksVnetName)]" + ] + } + ] + }, + { + "type": "Microsoft.KeyVault/vaults", + "name": "[variables('environmentSettings')[parameters('environmentName')].deliveryKeyVaultName]", + "apiVersion": "2016-10-01", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[variables('environmentSettings')[parameters('environmentName')].deliveryRedisStorageId]", + "[resourceId('Microsoft.Network/virtualNetworks', variables('environmentSettings')[parameters('environmentName')].aksVnetName)]" + ], + "tags": { + "displayName": "Delivery Key Vault", + "app": "fabrikam-delivery", + "environment": "[parameters('environmentName')]" + }, + "properties": { + "sku": { + "family": "A", + "name": "standard" + }, + "tenantId": "[subscription().tenantId]", + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "Deny", + "virtualNetworkRules": [ + { + "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('environmentSettings')[parameters('environmentName')].aksVnetName, variables('firewallSubnetName'))]" + } + ] + }, + "accessPolicies": [ + { + "tenantId": "[subscription().tenantId]", + "objectId": "[parameters('deliveryPrincipalId')]", + "permissions": { + "secrets": [ + "get", + "list" + ] + } + }, + { + "tenantId": "[subscription().tenantId]", + "objectId": "[variables('readerRoleObjectId')]", + "permissions": { + "secrets": [ + "get", + "list" + ] + } + } + ] + }, + "resources": [ + { + "type": "secrets", + "name": "CosmosDB-Endpoint", + "apiVersion": "2015-06-01", + "properties": { + "value": "[reference(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('environmentSettings')[parameters('environmentName')].deliveryCosmosDbName)).documentEndpoint]" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].deliveryKeyVaultName)]", + "[resourceId('Microsoft.Cache/Redis', variables('environmentSettings')[parameters('environmentName')].deliveryRedisName)]" + ] + }, + { + "type": "secrets", + "name": "CosmosDB-Key", + "apiVersion": "2015-06-01", + "properties": { + "value": "[listKeys(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('environmentSettings')[parameters('environmentName')].deliveryCosmosDbName), '2016-03-31').primaryMasterKey]" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].deliveryKeyVaultName)]", + "[resourceId('Microsoft.Cache/Redis', variables('environmentSettings')[parameters('environmentName')].deliveryRedisName)]" + ] + }, + { + "type": "secrets", + "name": "Redis-Endpoint", + "apiVersion": "2015-06-01", + "properties": { + "value": "[reference(resourceId('Microsoft.Cache/Redis', variables('environmentSettings')[parameters('environmentName')].deliveryRedisName)).hostName]" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].deliveryKeyVaultName)]", + "[resourceId('Microsoft.Cache/Redis', variables('environmentSettings')[parameters('environmentName')].deliveryRedisName)]" + ] + }, + { + "type": "secrets", + "name": "Redis-AccessKey", + "apiVersion": "2015-06-01", + "properties": { + "value": "[listKeys(resourceId('Microsoft.Cache/Redis', variables('environmentSettings')[parameters('environmentName')].deliveryRedisName), '2016-04-01').primaryKey]" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].deliveryKeyVaultName)]", + "[resourceId('Microsoft.Cache/Redis', variables('environmentSettings')[parameters('environmentName')].deliveryRedisName)]" + ] + }, + { + "type": "secrets", + "name": "ApplicationInsights--InstrumentationKey", + "apiVersion": "2015-06-01", + "properties": { + "value": "[reference(resourceId('Microsoft.Insights/components', variables('environmentSettings')[parameters('environmentName')].appInsightsName),'2015-05-01').InstrumentationKey]" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].deliveryKeyVaultName)]", + "[resourceId('Microsoft.Insights/components', variables('environmentSettings')[parameters('environmentName')].appInsightsName)]" + ] + } + ] + }, + { + "type": "Microsoft.KeyVault/vaults", + "name": "[variables('environmentSettings')[parameters('environmentName')].droneSchedulerKeyVaultName]", + "apiVersion": "2016-10-01", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', variables('environmentSettings')[parameters('environmentName')].aksVnetName)]" + ], + "tags": { + "displayName": "DroneScheduler Key Vault", + "app": "fabrikam-dronescheduler", + "environment": "[parameters('environmentName')]" + }, + "properties": { + "sku": { + "family": "A", + "name": "standard" + }, + "tenantId": "[subscription().tenantId]", + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "Deny", + "virtualNetworkRules": [ + { + "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('environmentSettings')[parameters('environmentName')].aksVnetName, variables('firewallSubnetName'))]" + } + ] + }, + "accessPolicies": [ + { + "tenantId": "[subscription().tenantId]", + "objectId": "[parameters('droneSchedulerPrincipalId')]", + "permissions": { + "secrets": [ + "get", + "list" + ] + } + } + ] + }, + "resources": [ + { + "type": "secrets", + "name": "ApplicationInsights--InstrumentationKey", + "apiVersion": "2015-06-01", + "properties": { + "value": "[reference(resourceId('Microsoft.Insights/components', variables('environmentSettings')[parameters('environmentName')].appInsightsName),'2015-05-01').InstrumentationKey]" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].droneSchedulerKeyVaultName)]", + "[resourceId('Microsoft.Insights/components', variables('environmentSettings')[parameters('environmentName')].appInsightsName)]" + ] + }, + { + "type": "secrets", + "name": "CosmosDBEndpoint", + "apiVersion": "2015-06-01", + "properties": { + "value": "[reference(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('environmentSettings')[parameters('environmentName')].droneSchedulerCosmosDbName)).documentEndpoint]" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].droneSchedulerKeyVaultName)]", + "[resourceId('Microsoft.DocumentDB/databaseAccounts', variables('environmentSettings')[parameters('environmentName')].droneSchedulerCosmosDbName)]" + ] + }, + { + "type": "secrets", + "name": "CosmosDBKey", + "apiVersion": "2015-06-01", + "properties": { + "value": "[listKeys(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('environmentSettings')[parameters('environmentName')].droneSchedulerCosmosDbName), '2016-03-31').primaryMasterKey]" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].droneSchedulerKeyVaultName)]", + "[resourceId('Microsoft.DocumentDB/databaseAccounts', variables('environmentSettings')[parameters('environmentName')].droneSchedulerCosmosDbName)]" + ] + }, + { + "type": "secrets", + "name": "CosmosDBConnectionMode", + "apiVersion": "2015-06-01", + "properties": { + "value": "Gateway" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].droneSchedulerKeyVaultName)]" + ] + }, + { + "type": "secrets", + "name": "CosmosDBConnectionProtocol", + "apiVersion": "2015-06-01", + "properties": { + "value": "Https" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].droneSchedulerKeyVaultName)]" + ] + }, + { + "type": "secrets", + "name": "CosmosDBMaxConnectionsLimit", + "apiVersion": "2015-06-01", + "properties": { + "value": "50" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].droneSchedulerKeyVaultName)]" + ] + }, + { + "type": "secrets", + "name": "CosmosDBMaxParallelism", + "apiVersion": "2015-06-01", + "properties": { + "value": "-1" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].droneSchedulerKeyVaultName)]" + ] + }, + { + "type": "secrets", + "name": "CosmosDBMaxBufferedItemCount", + "apiVersion": "2015-06-01", + "properties": { + "value": "0" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].droneSchedulerKeyVaultName)]" + ] + }, + { + "type": "secrets", + "name": "FeatureManagement--UsePartitionKey", + "apiVersion": "2015-06-01", + "properties": { + "value": "false" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].droneSchedulerKeyVaultName)]" + ] + } + ] + }, + { + "type": "Microsoft.KeyVault/vaults", + "name": "[variables('environmentSettings')[parameters('environmentName')].workflowKeyVaultName]", + "apiVersion": "2016-10-01", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', variables('environmentSettings')[parameters('environmentName')].aksVnetName)]" + ], + "tags": { + "displayName": "Workflow Key Vault", + "app": "fabrikam-workflow", + "environment": "[parameters('environmentName')]" + }, + "properties": { + "sku": { + "family": "A", + "name": "standard" + }, + "tenantId": "[subscription().tenantId]", + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "Deny", + "virtualNetworkRules": [ + { + "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('environmentSettings')[parameters('environmentName')].aksVnetName, variables('firewallSubnetName'))]" + } + ] + }, + "accessPolicies": [ + { + "tenantId": "[subscription().tenantId]", + "objectId": "[parameters('workflowPrincipalId')]", + "permissions": { + "secrets": [ + "get", + "list" + ] + } + } + ] + }, + "resources": [ + { + "type": "secrets", + "name": "QueueName", + "apiVersion": "2015-06-01", + "properties": { + "value": "[variables('environmentSettings')[parameters('environmentName')].ingestionSBName]" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].workflowKeyVaultName)]" + ] + }, + { + "type": "secrets", + "name": "QueueEndpoint", + "apiVersion": "2015-06-01", + "properties": { + "value": "[reference(resourceId('Microsoft.ServiceBus/namespaces', variables('environmentSettings')[parameters('environmentName')].ingestionSBNamespace)).serviceBusEndpoint]" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].workflowKeyVaultName)]", + "[resourceId('Microsoft.ServiceBus/namespaces', variables('environmentSettings')[parameters('environmentName')].ingestionSBNamespace)]" + ] + }, + { + "type": "secrets", + "name": "QueueAccessPolicyName", + "apiVersion": "2015-06-01", + "properties": { + "value": "[variables('environmentSettings')[parameters('environmentName')].workflowServiceAccessKey]" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].workflowKeyVaultName)]" + ] + }, + { + "type": "secrets", + "name": "QueueAccessPolicyKey", + "apiVersion": "2015-06-01", + "properties": { + "value": "[listkeys(resourceId('Microsoft.ServiceBus/namespaces/authorizationRules', variables('environmentSettings')[parameters('environmentName')].ingestionSBNamespace, variables('environmentSettings')[parameters('environmentName')].workflowServiceAccessKey), '2017-04-01').primaryKey]" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].workflowKeyVaultName)]", + "[resourceId('Microsoft.ServiceBus/namespaces/authorizationRules', variables('environmentSettings')[parameters('environmentName')].ingestionSBNamespace, variables('environmentSettings')[parameters('environmentName')].workflowServiceAccessKey)]" + ] + }, + { + "type": "secrets", + "name": "ApplicationInsights-InstrumentationKey", + "apiVersion": "2015-06-01", + "properties": { + "value": "[reference(resourceId('Microsoft.Insights/components', variables('environmentSettings')[parameters('environmentName')].appInsightsName),'2015-05-01').InstrumentationKey]" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].workflowKeyVaultName)]", + "[resourceId('Microsoft.Insights/components', variables('environmentSettings')[parameters('environmentName')].appInsightsName)]" + ] + } + ] + }, + { + "type": "Microsoft.KeyVault/vaults/providers/roleAssignments", + "name": "[concat(variables('environmentSettings')[parameters('environmentName')].deliveryKeyVaultName,'/Microsoft.Authorization/',guid(concat('kv-delivery',parameters('environmentName')), resourceGroup().id))]", + "apiVersion": "2017-05-01", + "tags": { + "displayName": "Delivery app RBAC Reader for Key Vault", + "what": "rbac", + "to": "pod", + "identity-type": "msi", + "access": "keyvault", + "reason": "aad-pod-identity", + "flex-vol": "no", + "app": "fabrikam-delivery", + "environment": "[parameters('environmentName')]" + }, + "properties": { + "roleDefinitionId": "[variables('readerRoleId')]", + "principalId": "[parameters('deliveryPrincipalId')]", + "scope": "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].deliveryKeyVaultName)]" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].deliveryKeyVaultName)]" + ] + }, + { + "type": "Microsoft.KeyVault/vaults/providers/roleAssignments", + "name": "[concat(variables('environmentSettings')[parameters('environmentName')].droneSchedulerKeyVaultName,'/Microsoft.Authorization/',guid(concat('kv-dronescheduler',parameters('environmentName')), resourceGroup().id))]", + "apiVersion": "2017-05-01", + "tags": { + "displayName": "DroneScheduler app RBAC Reader for Key Vault", + "what": "rbac", + "to": "pod", + "identity-type": "msi", + "access": "keyvault", + "reason": "aad-pod-identity", + "flex-vol": "no", + "app": "fabrikam-dronescheduler", + "environment": "[parameters('environmentName')]" + }, + "properties": { + "roleDefinitionId": "[variables('readerRoleId')]", + "principalId": "[parameters('droneSchedulerPrincipalId')]", + "scope": "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].droneSchedulerKeyVaultName)]" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].droneSchedulerKeyVaultName)]" + ] + }, + { + "type": "Microsoft.KeyVault/vaults/providers/roleAssignments", + "name": "[concat(variables('environmentSettings')[parameters('environmentName')].workflowKeyVaultName,'/Microsoft.Authorization/',guid(concat('kv-workflow',parameters('environmentName')), resourceGroup().id))]", + "apiVersion": "2017-05-01", + "tags": { + "displayName": "Workflow app RBAC Reader for Key Vault", + "what": "rbac", + "to": "pod", + "identity-type": "msi", + "access": "keyvault", + "reason": "aad-pod-identity", + "flex-vol": "yes", + "app": "fabrikam-workflow", + "environment": "[parameters('environmentName')]" + }, + "properties": { + "roleDefinitionId": "[variables('readerRoleId')]", + "principalId": "[parameters('workflowPrincipalId')]", + "scope": "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].workflowKeyVaultName)]" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].workflowKeyVaultName)]" + ] + }, + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities/providers/roleAssignments", + "name": "[concat(parameters('deliveryIdName'), '/Microsoft.Authorization/', guid(concat('msi-delivery',parameters('environmentName')), resourceGroup().id))]", + "apiVersion": "2017-05-01", + "comments": "Grant the AKS cluster access to the delivery managed id", + "tags": { + "displayName": "AKS SP RBAC Access for delivery managed identity", + "what": "rbac", + "to": "cluster", + "identity-type": "sp", + "access": "msi", + "reason": "aad-pod-identity", + "app": "fabrikam-delivery", + "environment": "[parameters('environmentName')]" + }, + "properties": { + "roleDefinitionId": "[variables('managedIdentityOperatorRoleId')]", + "principalId": "[parameters('servicePrincipalId')]" + } + }, + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities/providers/roleAssignments", + "name": "[concat(parameters('workflowIdName'), '/Microsoft.Authorization/', guid(concat('msi-workflow',parameters('environmentName')), resourceGroup().id))]", + "apiVersion": "2017-05-01", + "comments": "Grant the AKS cluster access to the workflow managed id", + "tags": { + "displayName": "AKS SP RBAC Access for workflow managed identity", + "what": "rbac", + "to": "cluster", + "identity-type": "sp", + "access": "msi", + "reason": "aad-pod-identity", + "app": "fabrikam-workflow", + "environment": "[parameters('environmentName')]" + }, + "properties": { + "roleDefinitionId": "[variables('managedIdentityOperatorRoleId')]", + "principalId": "[parameters('servicePrincipalId')]" + } + }, + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities/providers/roleAssignments", + "name": "[concat(parameters('droneSchedulerIdName'), '/Microsoft.Authorization/', guid(concat('msi-dronescheduler',parameters('environmentName')), resourceGroup().id))]", + "apiVersion": "2017-05-01", + "comments": "Grant the AKS cluster access to the drone scheduler managed id", + "tags": { + "displayName": "AKS SP RBAC Access for dronescheduler managed identity", + "what": "rbac", + "to": "cluster", + "identity-type": "sp", + "access": "msi", + "reason": "aad-pod-identity", + "app": "fabrikam-dronescheduler", + "environment": "[parameters('environmentName')]" + }, + "properties": { + "roleDefinitionId": "[variables('managedIdentityOperatorRoleId')]", + "principalId": "[parameters('servicePrincipalId')]" + } + }, + { + "type": "Microsoft.Network/applicationGateways/providers/roleAssignments", + "name": "[concat(variables('environmentSettings')[parameters('environmentName')].appGatewayName, '/Microsoft.Authorization/', guid(concat('msi-appgatewaycontroller', 'appgateway'), resourceGroup().id, parameters('environmentName')))]", + "apiVersion": "2017-05-01", + "comments": "Grant the App gateway controller access to the app gateway", + "tags": { + "displayName": "App gateway controller RBAC Contributor", + "what": "rbac", + "to": "pod", + "identity-type": "msi", + "access": "appgateway", + "reason": "aad-pod-identity", + "flex-vol": "no" + }, + "properties": { + "roleDefinitionId": "[variables('contributorRoleId')]", + "principalId": "[parameters('appGatewayControllerPrincipalId')]", + "scope": "[resourceId('Microsoft.Network/applicationGateways', variables('environmentSettings')[parameters('environmentName')].appGatewayName)]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/applicationGateways', variables('environmentSettings')[parameters('environmentName')].appGatewayName)]" + ] + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "name": "[guid(concat('msi-appgatewaycontroller', 'resourcegroup', parameters('environmentName')), resourceGroup().id)]", + "apiVersion": "2017-05-01", + "comments": "Grant the App gateway controller access to the AKS cluster", + "tags": { + "displayName": "App gateway controller RBAC Reader", + "what": "rbac", + "to": "pod", + "identity-type": "msi", + "access": "appgateway", + "reason": "aad-pod-identity", + "flex-vol": "no" + }, + "properties": { + "roleDefinitionId": "[variables('readerRoleId')]", + "principalId": "[parameters('appGatewayControllerPrincipalId')]", + "scope": "[resourceGroup().id]" + } + }, + { + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2019-09-01", + "location": "[resourceGroup().location]", + "name": "[variables('firewallPublicIpName')]", + "sku": { + "name": "Standard" + }, + "properties": { + "publicIPAddressVersion": "IPv4", + "publicIPAllocationMethod": "Static", + "idleTimeoutInMinutes": 4 + } + } + ], + "outputs": { + "acrName": { + "value": "[variables('environmentSettings')[parameters('environmentName')].acrName]", + "type": "string" + }, + "aksClusterName": { + "value": "[variables('environmentSettings')[parameters('environmentName')].aksClusterName]", + "type": "string" + }, + "appInsightsName": { + "value": "[variables('environmentSettings')[parameters('environmentName')].appInsightsName]", + "type": "string" + }, + "deliveryKeyVaultUri": { + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].deliveryKeyVaultName)).vaultUri]", + "type": "string" + }, + "deliveryCosmosDbName": { + "value": "[variables('environmentSettings')[parameters('environmentName')].deliveryCosmosDbName]", + "type": "string" + }, + "droneSchedulerKeyVaultName": { + "value": "[variables('environmentSettings')[parameters('environmentName')].droneSchedulerKeyVaultName]", + "type": "string" + }, + "droneSchedulerKeyVaultUri": { + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', variables('environmentSettings')[parameters('environmentName')].droneSchedulerKeyVaultName)).vaultUri]", + "type": "string" + }, + "droneSchedulerCosmosDbName": { + "value": "[variables('environmentSettings')[parameters('environmentName')].droneSchedulerCosmosDbName]", + "type": "string" + }, + "packageMongoDbName": { + "value": "[variables('environmentSettings')[parameters('environmentName')].packageMongoDbName]", + "type": "string" + }, + "workflowKeyVaultName": { + "value": "[variables('environmentSettings')[parameters('environmentName')].workflowKeyVaultName]", + "type": "string" + }, + "ingestionQueueNamespace": { + "value": "[variables('environmentSettings')[parameters('environmentName')].ingestionSBNamespace]", + "type": "string" + }, + "ingestionQueueName": { + "value": "[variables('environmentSettings')[parameters('environmentName')].ingestionSBName]", + "type": "string" + }, + "ingestionServiceAccessKeyName": { + "value": "[variables('environmentSettings')[parameters('environmentName')].ingestionServiceAccessKey]", + "type": "string" + }, + "acrDeploymentName": { + "value": "[variables('nestedACRDeploymentName')]", + "type": "string" + }, + "appGatewayName": { + "value": "[variables('environmentSettings')[parameters('environmentName')].appGatewayName]", + "type": "string" + }, + "appGatewayPublicIpFqdn": { + "value": "[reference(resourceId('Microsoft.Network/publicIPAddresses', variables('environmentSettings')[parameters('environmentName')].appGatewayPublicIpName)).dnsSettings.fqdn]", + "type": "string" + }, + "appGatewaySubnetPrefix": { + "value": "[variables('appGatewaySubnetsLoop')[variables('environmentSettings')[parameters('environmentName')].appGatewaySubnetIndex].properties.addressPrefix]", + "type": "string" + }, + "aksVNetName": { + "type": "string", + "value": "[variables('environmentSettings')[parameters('environmentName')].aksVnetName]" + }, + "aksClusterSubnetName": { + "type": "string", + "value": "[variables('environmentSettings')[parameters('environmentName')].aksClusterSubnetName]" + }, + "aksClusterSubnetPrefix": { + "type": "string", + "value": "[variables('aksClusterSubnetPrefix')]" + }, + "firewallSubnetName": { + "type": "string", + "value": "[variables('firewallSubnetName')]" + }, + "firewallPublicIpName": { + "type": "string", + "value": "[variables('firewallPublicIpName')]" + }, + "deliveryRedisHostName": { + "value": "[reference(resourceId('Microsoft.Cache/Redis', variables('environmentSettings')[parameters('environmentName')].deliveryRedisName)).hostName]", + "type": "string" + } + } +} diff --git a/charts/delivery/.helmignore b/charts/delivery/.helmignore new file mode 100644 index 00000000..50af0317 --- /dev/null +++ b/charts/delivery/.helmignore @@ -0,0 +1,22 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/delivery/Chart.yaml b/charts/delivery/Chart.yaml new file mode 100644 index 00000000..98047987 --- /dev/null +++ b/charts/delivery/Chart.yaml @@ -0,0 +1,40 @@ +apiVersion: v2 +name: delivery +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Delivery Service +type: application +home: https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/microservices/aks +sources: + - https://github.com/mspnp/microservices-reference-implementation +dependencies: + - name: delivery-dev + repository: "file://envs/delivery-dev" + version: v0.1.0 + condition: envs.dev + import-values: + - data + + - name: delivery-prod + repository: "file://envs/delivery-prod" + version: v0.1.0 + condition: envs.prod + import-values: + - data + + - name: delivery-qa + repository: "file://envs/delivery-qa" + version: v0.1.0 + condition: envs.qa + import-values: + - data + + - name: delivery-staging + repository: "file://envs/delivery-staging" + version: v0.1.0 + condition: envs.staging + import-values: + - data +maintainers: + - email: v-fean@microsoft.com + name: ferantivero diff --git a/charts/delivery/envs/delivery-dev/Chart.yaml b/charts/delivery/envs/delivery-dev/Chart.yaml new file mode 100644 index 00000000..821ec15b --- /dev/null +++ b/charts/delivery/envs/delivery-dev/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: delivery-dev +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Delivery Service +type: application diff --git a/charts/delivery/envs/delivery-dev/values.yaml b/charts/delivery/envs/delivery-dev/values.yaml new file mode 100644 index 00000000..24ea476c --- /dev/null +++ b/charts/delivery/envs/delivery-dev/values.yaml @@ -0,0 +1,18 @@ +# Dev values for delivery. +nameOverride: delivery +exports: + data: + replicaCount: 1 + image: + pullPolicy: Always + telemetry: + level: "Information" + reason: "new dev deploy" + current: true + resources: + requests: + cpu: 80m + memory: 350Mi + limits: + cpu: 130m + memory: 500Mi diff --git a/charts/delivery/envs/delivery-prod/Chart.yaml b/charts/delivery/envs/delivery-prod/Chart.yaml new file mode 100644 index 00000000..349560de --- /dev/null +++ b/charts/delivery/envs/delivery-prod/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: delivery-prod +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Delivery Service +type: application diff --git a/charts/delivery/envs/delivery-prod/templates/delivery-service.yaml b/charts/delivery/envs/delivery-prod/templates/delivery-service.yaml new file mode 100644 index 00000000..7e015d4f --- /dev/null +++ b/charts/delivery/envs/delivery-prod/templates/delivery-service.yaml @@ -0,0 +1,36 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Delivery service +################################################################################################### +{{ if .Values.experimental }} +{{- $appname := include "delivery.name" . -}} +{{- $chart := include "delivery.chart" . -}} +{{- $instancename := .Release.Name }} +# the following object is meant ot be created first time only. +# its configuration will be later managed by CI/CD +apiVersion: v1 +kind: Service +metadata: + name: delivery-experimental + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation + labels: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/managed-by: azuredeveops + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ $chart }} +spec: + ports: + - name: http + port: 80 + targetPort: 8080 + selector: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/instance: {{ $instancename }} +{{ end }} diff --git a/charts/delivery/envs/delivery-prod/values.yaml b/charts/delivery/envs/delivery-prod/values.yaml new file mode 100644 index 00000000..ec646e66 --- /dev/null +++ b/charts/delivery/envs/delivery-prod/values.yaml @@ -0,0 +1,22 @@ +# Production values for delivery. +nameOverride: delivery +exports: + data: + replicaCount: 1 + image: + pullPolicy: IfNotPresent + telemetry: + level: "Error" + reason: "new prod deploy" + resources: + requests: + cpu: 100m + memory: 350Mi + limits: + cpu: 200m + memory: 500Mi + autoscaling: + enabled: true + maxReplicas: 50 + minReplicas: 1 + targetCPUUtilizationPercentage: 50 diff --git a/charts/delivery/envs/delivery-qa/Chart.yaml b/charts/delivery/envs/delivery-qa/Chart.yaml new file mode 100644 index 00000000..f872f4cd --- /dev/null +++ b/charts/delivery/envs/delivery-qa/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: delivery-qa +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Delivery Service +type: application diff --git a/charts/delivery/envs/delivery-qa/values.yaml b/charts/delivery/envs/delivery-qa/values.yaml new file mode 100644 index 00000000..66ce0c20 --- /dev/null +++ b/charts/delivery/envs/delivery-qa/values.yaml @@ -0,0 +1,18 @@ +# QA values for delivery. +nameOverride: delivery +exports: + data: + replicaCount: 1 + image: + pullPolicy: Always + telemetry: + level: "Information" + reason: "new qa deploy" + current: true + resources: + requests: + cpu: 100m + memory: 350Mi + limits: + cpu: 150m + memory: 500Mi diff --git a/charts/delivery/envs/delivery-staging/Chart.yaml b/charts/delivery/envs/delivery-staging/Chart.yaml new file mode 100644 index 00000000..ff47ca2c --- /dev/null +++ b/charts/delivery/envs/delivery-staging/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: delivery-staging +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Delivery Service +type: application diff --git a/charts/delivery/envs/delivery-staging/values.yaml b/charts/delivery/envs/delivery-staging/values.yaml new file mode 100644 index 00000000..b2cbaee4 --- /dev/null +++ b/charts/delivery/envs/delivery-staging/values.yaml @@ -0,0 +1,23 @@ +# Staging values for delivery. +nameOverride: delivery +exports: + data: + replicaCount: 1 + image: + pullPolicy: Always + telemetry: + level: "Information" + reason: "new staging deploy" + current: true + resources: + requests: + cpu: 100m + memory: 350Mi + limits: + cpu: 200m + memory: 500Mi + autoscaling: + enabled: true + maxReplicas: 50 + minReplicas: 1 + targetCPUUtilizationPercentage: 50 diff --git a/charts/delivery/templates/NOTES.txt b/charts/delivery/templates/NOTES.txt new file mode 100644 index 00000000..68d120cf --- /dev/null +++ b/charts/delivery/templates/NOTES.txt @@ -0,0 +1,10 @@ +Thank you for installing {{ .Chart.Name }}. + +Your release is named {{ .Release.Name }}. + +All the objects were created in the namespace {{ .Release.Namespace }} + +To learn more about the release, try: + + $ helm status {{ .Release.Name }} --namespace {{ .Release.Namespace }} + $ helm get all {{ .Release.Name }} --namespace {{ .Release.Namespace }} diff --git a/charts/delivery/templates/_helpers.tpl b/charts/delivery/templates/_helpers.tpl new file mode 100644 index 00000000..432c9ac8 --- /dev/null +++ b/charts/delivery/templates/_helpers.tpl @@ -0,0 +1,61 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "delivery.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create app name with version. +*/}} +{{- define "delivery.versionappname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" $name .Chart.AppVersion | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "delivery.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "delivery.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "delivery.labels" -}} +helm.sh/chart: {{ include "delivery.chart" . }} +{{ include "delivery.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Selector labels +*/}} +{{- define "delivery.selectorLabels" -}} +app.kubernetes.io/name: {{ include "delivery.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + diff --git a/charts/delivery/templates/delivery-deploy.yaml b/charts/delivery/templates/delivery-deploy.yaml new file mode 100644 index 00000000..f5255875 --- /dev/null +++ b/charts/delivery/templates/delivery-deploy.yaml @@ -0,0 +1,101 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Delivery +################################################################################################### +{{- $fullname := include "delivery.fullname" . | replace "." "" }} +apiVersion: apps/v1beta2 +kind: Deployment +metadata: + name: {{ $fullname }} + labels: + app.kubernetes.io/name: {{ include "delivery.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ include "delivery.chart" . }} + aadpodidbinding: {{ $fullname }} + annotations: + kubernetes.io/change-cause: {{ .Values.reason }} +spec: + replicas: {{ default 1 .Values.replicaCount }} + selector: + matchLabels: + app.kubernetes.io/name: {{ include "delivery.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + template: + metadata: + labels: + app.kubernetes.io/name: {{ include "delivery.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ include "delivery.chart" . }} + aadpodidbinding: {{ $fullname }} + spec: + securityContext: + fsGroup: 1 + containers: + - name: fabrikam-delivery + image: {{ .Values.dockerregistry }}{{ .Values.dockerregistrynamespace }}/{{ .Values.image.repository }}:{{ .Values.image.tag }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + readinessProbe: + httpGet: + path: {{ required "readinessProbe.httpGet.path is required" .Values.readinessProbe.httpGet.path }} + port: {{ required "readinessProbe.httpGet.port is required" .Values.readinessProbe.httpGet.port }} +{{- if .Values.readinessProbe.initialDelaySeconds }} + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} +{{- end }} +{{- if .Values.readinessProbe.periodSeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} +{{- end }} +{{- if .Values.readinessProbe.timeoutSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} +{{- end }} +{{- if .Values.readinessProbe.failureThreshold }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} +{{- end }} + livenessProbe: + httpGet: + path: {{ required "livenessProbe.httpGet.path is required" .Values.livenessProbe.httpGet.path }} + port: {{ required "livenessProbe.httpGet.port is required" .Values.livenessProbe.httpGet.port }} +{{- if .Values.livenessProbe.initialDelaySeconds }} + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} +{{- end }} +{{- if .Values.livenessProbe.periodSeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} +{{- end }} +{{- if .Values.livenessProbe.timeoutSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} +{{- end }} +{{- if .Values.livenessProbe.failureThreshold }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} +{{- end }} + resources: + requests: + cpu: {{ required "A valid .Values.resources.requests.cpu entry required!" .Values.resources.requests.cpu }} + memory: {{ required "A valid .Values.resources.requests.memory entry required!" .Values.resources.requests.memory }} + limits: + cpu: {{ required "A valid .Values.resources.limits.cpu entry required!" .Values.resources.limits.cpu }} + memory: {{ required "A valid .Values.resources.limits.memory entry required!" .Values.resources.limits.memory }} + env: + - name: DOCDB_DATABASEID + value: {{ .Values.cosmosdb.id }} + - name: DOCDB_COLLECTIONID + value: {{ .Values.cosmosdb.collectionid }} + - name: KEY_VAULT_URI + value: {{ .Values.keyvault.uri }} + - name: LOGGING__ApplicationInsights__LOGLEVEL__DEFAULT + value: {{ default "Error" .Values.telemetry.level | quote }} + - name: no_proxy + value: 169.254.169.254 + ports: + - name: service + containerPort: 8080 diff --git a/charts/delivery/templates/delivery-hpa.yaml b/charts/delivery/templates/delivery-hpa.yaml new file mode 100644 index 00000000..4c8b22bc --- /dev/null +++ b/charts/delivery/templates/delivery-hpa.yaml @@ -0,0 +1,35 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Delivery HPA Resrouce Metrics (CPU utilization threshold) +################################################################################################### +{{- if .Values.autoscaling.enabled }} +{{- $fullname := include "delivery.fullname" . | replace "." "" }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ $fullname }}-hpa + labels: + app.kubernetes.io/name: {{ include "delivery.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ include "delivery.chart" . }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ $fullname }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} +{{- end }} diff --git a/charts/delivery/templates/delivery-identity.yaml b/charts/delivery/templates/delivery-identity.yaml new file mode 100644 index 00000000..02f099aa --- /dev/null +++ b/charts/delivery/templates/delivery-identity.yaml @@ -0,0 +1,33 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Delivery service identity +################################################################################################### +{{- $fullname := include "delivery.fullname" . | replace "." "" }} +apiVersion: "aadpodidentity.k8s.io/v1" +kind: AzureIdentity +metadata: + name: {{ $fullname }}-identity + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-delete-policy": hook-failed,before-hook-creation + "helm.sh/hook-weight": "0" +spec: + type: 0 + ResourceID: {{ .Values.identity.resourceid }} + ClientID: {{ .Values.identity.clientid }} +--- +apiVersion: "aadpodidentity.k8s.io/v1" +kind: AzureIdentityBinding +metadata: + name: {{ $fullname }}-identity-binding + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-delete-policy": hook-failed,before-hook-creation + "helm.sh/hook-weight": "1" +spec: + AzureIdentity: {{ $fullname }}-identity + Selector: {{ $fullname }} diff --git a/charts/delivery/templates/delivery-ingress.yaml b/charts/delivery/templates/delivery-ingress.yaml new file mode 100644 index 00000000..af80b627 --- /dev/null +++ b/charts/delivery/templates/delivery-ingress.yaml @@ -0,0 +1,49 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# ingress +################################################################################################### +{{- $svcversion := .Chart.AppVersion | replace "." "" }} +{{- $appversion := .Chart.AppVersion }} +{{- $defaultversionedpath := printf "/%s/" $appversion }} +{{- $relname := .Release.Name }} +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: {{ $relname }}-ingress + annotations: + kubernetes.io/ingress.class: azure/application-gateway +spec: + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.hosts }} + {{- if .tls }} + - hosts: + - {{ .name }} + secretName: {{ $relname }}-{{ .tlsSecretName }} + {{- end }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .name }} + http: + paths: + {{- if .path }} + - path: {{ printf "%s/%s/" .path $appversion }}api/deliveries* + {{- else }} + - path: {{ $defaultversionedpath }}api/deliveries* + {{- end }} + backend: + serviceName: "{{ .serviceName }}-{{ $svcversion }}" + servicePort: http + {{- if (eq $appversion "v0.1.0") }} + - path: {{ default "/" .path }}api/deliveries* + backend: + serviceName: "{{ .serviceName }}" + servicePort: http + {{- end }} + {{ end }} diff --git a/charts/delivery/templates/delivery-networkpolicy-allow-egress-traffic.yaml b/charts/delivery/templates/delivery-networkpolicy-allow-egress-traffic.yaml new file mode 100644 index 00000000..4c7e5fd0 --- /dev/null +++ b/charts/delivery/templates/delivery-networkpolicy-allow-egress-traffic.yaml @@ -0,0 +1,49 @@ +## ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Delivery allow egress traffic +################################################################################################### + +{{- if .Values.networkPolicy.egress.enabled }} +{{- $fullname := include "delivery.fullname" . | replace "." "" }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ $fullname }}-np-whitelist-egress-traffic +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: {{ include "delivery.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + app.kubernetes.io/version: {{ .Chart.AppVersion }} + policyTypes: + - Egress + egress: + # allow egress traffic to kubedns + - to: + - podSelector: + matchLabels: + k8s-app: kube-dns + namespaceSelector: {} + ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP +{{- if .Values.networkPolicy.egress.external.enabled }} + # allow egress traffic to all external resources except pods within the + # cluster subnet + - to: + - ipBlock: + cidr: 0.0.0.0/0 + except: + - {{ required "networkPolicy.egress.external.clusterSubnetPrefix is required to enable external traffic" .Values.networkPolicy.egress.external.clusterSubnetPrefix }} +{{- else if .Values.networkPolicy.egress.allowAll }} + - to: [] +{{- end -}} +{{ end }} diff --git a/charts/delivery/templates/delivery-networkpolicy-allow-ingress-traffic.yaml b/charts/delivery/templates/delivery-networkpolicy-allow-ingress-traffic.yaml new file mode 100644 index 00000000..66b9d533 --- /dev/null +++ b/charts/delivery/templates/delivery-networkpolicy-allow-ingress-traffic.yaml @@ -0,0 +1,42 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Delivery allow ingress traffic +################################################################################################### + +{{- if .Values.networkPolicy.ingress.enabled }} +{{- $fullname := include "delivery.fullname" . | replace "." "" }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ $fullname }}-np-whitelist-ingress-traffic +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: {{ include "delivery.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + app.kubernetes.io/version: {{ .Chart.AppVersion }} + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + dd.fabrikam.com/egress-delivery: "true" + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery +{{- if .Values.networkPolicy.ingress.externalSubnet.enabled }} + - ipBlock: + cidr: {{ required "networkPolicy.ingress.externalSubnet.subnetPrefix is required to enable allow traffic from" .Values.networkPolicy.ingress.externalSubnet.subnetPrefix }} +{{- else if .Values.networkPolicy.egress.allowAll }} + - {} +{{- end }} + ports: + - protocol: {{ .Values.service.targetProtocol }} + port: {{ .Values.service.targetPort }} +{{- end }} diff --git a/charts/delivery/templates/delivery-secret-ingress-tls.yaml b/charts/delivery/templates/delivery-secret-ingress-tls.yaml new file mode 100644 index 00000000..3feb02a6 --- /dev/null +++ b/charts/delivery/templates/delivery-secret-ingress-tls.yaml @@ -0,0 +1,14 @@ +{{- if .Values.ingress.tls}} +{{- $relname := .Release.Name }} +{{- range .Values.ingress.tls.secrets }} +kind: Secret +apiVersion: v1 +metadata: + name: {{ $relname }}-{{ .name }} +type: kubernetes.io/tls +data: + tls.crt: {{ .certificate | b64enc }} + tls.key: {{ .key | b64enc }} +--- +{{ end }} +{{ end }} diff --git a/charts/delivery/templates/delivery-service.yaml b/charts/delivery/templates/delivery-service.yaml new file mode 100644 index 00000000..7e24c7d2 --- /dev/null +++ b/charts/delivery/templates/delivery-service.yaml @@ -0,0 +1,55 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Delivery service +################################################################################################### +{{- $appname := include "delivery.name" . -}} +{{- $chart := include "delivery.chart" . -}} +{{- $instancename := .Release.Name }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "delivery.versionappname" . | replace "." "" }} + labels: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/instance: {{ $instancename }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ $chart }} +spec: + ports: + - name: http + port: 80 + targetPort: {{ .Values.service.targetPort }} + selector: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/instance: {{ $instancename }} +{{ if .Values.current }} +--- +apiVersion: v1 +kind: Service +metadata: + name: delivery + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation + labels: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/managed-by: azuredeveops + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ $chart }} +spec: + ports: + - name: http + port: 80 + targetPort: {{ .Values.service.targetPort }} + selector: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/instance: {{ $instancename }} +{{ end }} diff --git a/charts/delivery/values.yaml b/charts/delivery/values.yaml new file mode 100644 index 00000000..a4f2a167 --- /dev/null +++ b/charts/delivery/values.yaml @@ -0,0 +1,93 @@ +# Default values for delivery. +nameOverride: delivery + + +replicaCount: 1 + + +identity: + clientid: + resourceid: + + +dockerregistrynamespace: +dockerregistry: + + +image: + repository: + tag: + pullPolicy: IfNotPresent + + +cosmosdb: + id: + collectionid: + +keyvault: + uri: + + +# probes +readinessProbe: + httpGet: + path: /healthz + port: 8080 + initialDelaySeconds: 40 + periodSeconds: 15 + timeoutSeconds: 2 + failureThreshold: 5 +livenessProbe: + httpGet: + path: /healthz + port: 8080 + initialDelaySeconds: 50 + periodSeconds: 15 + + +telemetry: + level: "Error" + + +# specify an installation/upgrade reason +reason: unknown + + +# indicate what environment is meant to be installed/upgraded +envs: + dev: false + prod: false + qa: false + staging: false + + +current: false + + +# Horizontal Pod Autoscaling +autoscaling: + enabled: false + maxReplicas: + minReplicas: + targetCPUUtilizationPercentage: + + +# Pod-to-pod traffic: east-west +networkPolicy: + egress: + enabled: true + allowAll: false + external: + enabled: false + clusterSubnetPrefix: + ingress: + enabled: true + allowAll: false + externalSubnet: + enabled: false + subnetPrefix: + + +service: + targetPort: 8080 + targetProtocol: TCP diff --git a/charts/dronescheduler/.helmignore b/charts/dronescheduler/.helmignore new file mode 100644 index 00000000..50af0317 --- /dev/null +++ b/charts/dronescheduler/.helmignore @@ -0,0 +1,22 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/dronescheduler/Chart.yaml b/charts/dronescheduler/Chart.yaml new file mode 100644 index 00000000..41b0228f --- /dev/null +++ b/charts/dronescheduler/Chart.yaml @@ -0,0 +1,42 @@ +apiVersion: v2 +name: dronescheduler +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Scheduler Service +type: application +home: https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/microservices/aks +sources: + - https://github.com/mspnp/microservices-reference-implementation +dependencies: + - name: dronescheduler-dev + repository: "file://envs/dronescheduler-dev" + version: v0.1.0 + condition: envs.dev + tags: + - dev + import-values: + - data + + - name: dronescheduler-prod + repository: "file://envs/dronescheduler-prod" + version: v0.1.0 + condition: envs.prod + import-values: + - data + + - name: dronescheduler-qa + repository: "file://envs/dronescheduler-qa" + version: v0.1.0 + condition: envs.qa + import-values: + - data + + - name: dronescheduler-staging + repository: "file://envs/dronescheduler-staging" + version: v0.1.0 + condition: envs.staging + import-values: + - data +maintainers: + - email: v-fean@microsoft.com + name: ferantivero diff --git a/charts/dronescheduler/envs/dronescheduler-dev/Chart.yaml b/charts/dronescheduler/envs/dronescheduler-dev/Chart.yaml new file mode 100644 index 00000000..43231385 --- /dev/null +++ b/charts/dronescheduler/envs/dronescheduler-dev/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: dronescheduler-dev +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Scheduler Service +type: application diff --git a/charts/dronescheduler/envs/dronescheduler-dev/values.yaml b/charts/dronescheduler/envs/dronescheduler-dev/values.yaml new file mode 100644 index 00000000..b70d3353 --- /dev/null +++ b/charts/dronescheduler/envs/dronescheduler-dev/values.yaml @@ -0,0 +1,18 @@ +# Dev values for dronescheduler. +nameOverride: dronescheduler +exports: + data: + replicaCount: 1 + image: + pullPolicy: Always + telemetry: + level: "Information" + reason: "new dev deploy" + current: true + resources: + requests: + cpu: 105m + memory: 350Mi + limits: + cpu: 175m + memory: 500Mi diff --git a/charts/dronescheduler/envs/dronescheduler-prod/Chart.yaml b/charts/dronescheduler/envs/dronescheduler-prod/Chart.yaml new file mode 100644 index 00000000..80404b46 --- /dev/null +++ b/charts/dronescheduler/envs/dronescheduler-prod/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: dronescheduler-prod +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Scheduler Service +type: application diff --git a/charts/dronescheduler/envs/dronescheduler-prod/templates/dronescheduler-service.yaml b/charts/dronescheduler/envs/dronescheduler-prod/templates/dronescheduler-service.yaml new file mode 100644 index 00000000..a933866b --- /dev/null +++ b/charts/dronescheduler/envs/dronescheduler-prod/templates/dronescheduler-service.yaml @@ -0,0 +1,36 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Dronescheduler service experimental +################################################################################################### +{{ if .Values.experimental }} +{{- $appname := include "dronescheduler.name" . -}} +{{- $chart := include "dronescheduler.chart" . -}} +{{- $instancename := .Release.Name }} +# the following object is meant ot be created first time only. +# its configuration will be later managed by CI/CD +apiVersion: v1 +kind: Service +metadata: + name: dronescheduler-experimental + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation + labels: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/managed-by: azuredeveops + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ $chart }} +spec: + ports: + - name: http + port: 80 + targetPort: 8080 + selector: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/instance: {{ $instancename }} +{{ end }} diff --git a/charts/dronescheduler/envs/dronescheduler-prod/values.yaml b/charts/dronescheduler/envs/dronescheduler-prod/values.yaml new file mode 100644 index 00000000..9cd19e8f --- /dev/null +++ b/charts/dronescheduler/envs/dronescheduler-prod/values.yaml @@ -0,0 +1,22 @@ +# Production values for dronescheduler. +nameOverride: dronescheduler +exports: + data: + replicaCount: 1 + image: + pullPolicy: IfNotPresent + telemetry: + level: "Error" + reason: "new prod deploy" + resources: + requests: + cpu: 130m + memory: 350Mi + limits: + cpu: 270m + memory: 500Mi + autoscaling: + enabled: true + maxReplicas: 50 + minReplicas: 1 + targetCPUUtilizationPercentage: 50 diff --git a/charts/dronescheduler/envs/dronescheduler-qa/Chart.yaml b/charts/dronescheduler/envs/dronescheduler-qa/Chart.yaml new file mode 100644 index 00000000..e83a8bf4 --- /dev/null +++ b/charts/dronescheduler/envs/dronescheduler-qa/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: dronescheduler-qa +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Scheduler Service +type: application diff --git a/charts/dronescheduler/envs/dronescheduler-qa/values.yaml b/charts/dronescheduler/envs/dronescheduler-qa/values.yaml new file mode 100644 index 00000000..f9c21ec7 --- /dev/null +++ b/charts/dronescheduler/envs/dronescheduler-qa/values.yaml @@ -0,0 +1,18 @@ +# QA values for dronescheduler. +nameOverride: dronescheduler +exports: + data: + replicaCount: 1 + image: + pullPolicy: Always + telemetry: + level: "Information" + reason: "new qa deploy" + current: true + resources: + requests: + cpu: 130m + memory: 350Mi + limits: + cpu: 200m + memory: 500Mi diff --git a/charts/dronescheduler/envs/dronescheduler-staging/Chart.yaml b/charts/dronescheduler/envs/dronescheduler-staging/Chart.yaml new file mode 100644 index 00000000..26581923 --- /dev/null +++ b/charts/dronescheduler/envs/dronescheduler-staging/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: dronescheduler-staging +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Scheduler Service +type: application diff --git a/charts/dronescheduler/envs/dronescheduler-staging/values.yaml b/charts/dronescheduler/envs/dronescheduler-staging/values.yaml new file mode 100644 index 00000000..3cd9688d --- /dev/null +++ b/charts/dronescheduler/envs/dronescheduler-staging/values.yaml @@ -0,0 +1,23 @@ +# Staging values for dronescheduler. +nameOverride: dronescheduler +exports: + data: + replicaCount: 1 + image: + pullPolicy: Always + telemetry: + level: "Information" + reason: "new staging deploy" + current: true + resources: + requests: + cpu: 130m + memory: 350Mi + limits: + cpu: 270m + memory: 500Mi + autoscaling: + enabled: true + maxReplicas: 50 + minReplicas: 1 + targetCPUUtilizationPercentage: 50 diff --git a/charts/dronescheduler/templates/NOTES.txt b/charts/dronescheduler/templates/NOTES.txt new file mode 100644 index 00000000..68d120cf --- /dev/null +++ b/charts/dronescheduler/templates/NOTES.txt @@ -0,0 +1,10 @@ +Thank you for installing {{ .Chart.Name }}. + +Your release is named {{ .Release.Name }}. + +All the objects were created in the namespace {{ .Release.Namespace }} + +To learn more about the release, try: + + $ helm status {{ .Release.Name }} --namespace {{ .Release.Namespace }} + $ helm get all {{ .Release.Name }} --namespace {{ .Release.Namespace }} diff --git a/charts/dronescheduler/templates/_helpers.tpl b/charts/dronescheduler/templates/_helpers.tpl new file mode 100644 index 00000000..94bf529a --- /dev/null +++ b/charts/dronescheduler/templates/_helpers.tpl @@ -0,0 +1,61 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "dronescheduler.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create app name with version. +*/}} +{{- define "dronescheduler.versionappname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" $name .Chart.AppVersion | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "dronescheduler.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "dronescheduler.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "dronescheduler.labels" -}} +helm.sh/chart: {{ include "dronescheduler.chart" . }} +{{ include "dronescheduler.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Selector labels +*/}} +{{- define "dronescheduler.selectorLabels" -}} +app.kubernetes.io/name: {{ include "dronescheduler.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + diff --git a/charts/dronescheduler/templates/dronescheduler-deploy.yaml b/charts/dronescheduler/templates/dronescheduler-deploy.yaml new file mode 100644 index 00000000..caa28797 --- /dev/null +++ b/charts/dronescheduler/templates/dronescheduler-deploy.yaml @@ -0,0 +1,101 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Dronescheduler +################################################################################################### +{{- $fullname := include "dronescheduler.fullname" . | replace "." "" }} +apiVersion: apps/v1beta2 +kind: Deployment +metadata: + name: {{ $fullname }} + labels: + app.kubernetes.io/name: {{ include "dronescheduler.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ include "dronescheduler.chart" . }} + aadpodidbinding: {{ $fullname }} + annotations: + kubernetes.io/change-cause: {{ .Values.reason }} +spec: + replicas: {{ default 1 .Values.replicaCount }} + selector: + matchLabels: + app.kubernetes.io/name: {{ include "dronescheduler.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + template: + metadata: + labels: + app.kubernetes.io/name: {{ include "dronescheduler.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ include "dronescheduler.chart" . }} + aadpodidbinding: {{ $fullname }} + spec: + securityContext: + fsGroup: 1 + containers: + - name: fabrikam-dronescheduler + image: {{ .Values.dockerregistry }}{{ .Values.dockerregistrynamespace }}/{{ .Values.image.repository }}:{{ .Values.image.tag }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + readinessProbe: + httpGet: + path: {{ required "readinessProbe.httpGet.path is required" .Values.readinessProbe.httpGet.path }} + port: {{ required "readinessProbe.httpGet.port is required" .Values.readinessProbe.httpGet.port }} +{{- if .Values.readinessProbe.initialDelaySeconds }} + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} +{{- end }} +{{- if .Values.readinessProbe.periodSeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} +{{- end }} +{{- if .Values.readinessProbe.timeoutSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} +{{- end }} +{{- if .Values.readinessProbe.failureThreshold }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} +{{- end }} + livenessProbe: + httpGet: + path: {{ required "livenessProbe.httpGet.path is required" .Values.livenessProbe.httpGet.path }} + port: {{ required "livenessProbe.httpGet.port is required" .Values.livenessProbe.httpGet.port }} +{{- if .Values.livenessProbe.initialDelaySeconds }} + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} +{{- end }} +{{- if .Values.livenessProbe.periodSeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} +{{- end }} +{{- if .Values.livenessProbe.timeoutSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} +{{- end }} +{{- if .Values.livenessProbe.failureThreshold }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} +{{- end }} + resources: + requests: + cpu: {{ required "A valid .Values.resources.requests.cpu entry required!" .Values.resources.requests.cpu }} + memory: {{ required "A valid .Values.resources.requests.memory entry required!" .Values.resources.requests.memory }} + limits: + cpu: {{ required "A valid .Values.resources.limits.cpu entry required!" .Values.resources.limits.cpu }} + memory: {{ required "A valid .Values.resources.limits.memory entry required!" .Values.resources.limits.memory }} + env: + - name: KEY_VAULT_URI + value: {{ .Values.keyvault.uri }} + - name: COSMOSDB_DATABASEID + value: {{ required "Cosmos DB name is required" .Values.cosmosdb.id }} + - name: COSMOSDB_COLLECTIONID + value: {{ required "Cosmos DB container name is required" .Values.cosmosdb.collectionid }} + - name: LOGGING__ApplicationInsights__LOGLEVEL__DEFAULT + value: {{ default "Error" .Values.telemetry.level | quote }} + - name: no_proxy + value: 169.254.169.254 + ports: + - name: service + containerPort: 8080 diff --git a/charts/dronescheduler/templates/dronescheduler-hpa.yaml b/charts/dronescheduler/templates/dronescheduler-hpa.yaml new file mode 100644 index 00000000..fdec84ba --- /dev/null +++ b/charts/dronescheduler/templates/dronescheduler-hpa.yaml @@ -0,0 +1,34 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ +################################################################################################### +# Dronescheduler HPA Resrouce Metrics (CPU utilization threshold) +################################################################################################### +{{- if .Values.autoscaling.enabled }} +{{- $fullname := include "dronescheduler.fullname" . | replace "." "" }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ $fullname }}-hpa + labels: + app.kubernetes.io/name: {{ include "dronescheduler.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ include "dronescheduler.chart" . }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ $fullname }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} +{{- end }} diff --git a/charts/dronescheduler/templates/dronescheduler-identity.yaml b/charts/dronescheduler/templates/dronescheduler-identity.yaml new file mode 100644 index 00000000..7b3ca299 --- /dev/null +++ b/charts/dronescheduler/templates/dronescheduler-identity.yaml @@ -0,0 +1,33 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Dronescheduler service identity +################################################################################################### +{{- $fullname := include "dronescheduler.fullname" . | replace "." "" }} +apiVersion: "aadpodidentity.k8s.io/v1" +kind: AzureIdentity +metadata: + name: {{ $fullname }}-identity + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-delete-policy": hook-failed,before-hook-creation + "helm.sh/hook-weight": "0" +spec: + type: 0 + ResourceID: {{ .Values.identity.resourceid }} + ClientID: {{ .Values.identity.clientid }} +--- +apiVersion: "aadpodidentity.k8s.io/v1" +kind: AzureIdentityBinding +metadata: + name: {{ $fullname }}-identity-binding + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-delete-policy": hook-failed,before-hook-creation + "helm.sh/hook-weight": "1" +spec: + AzureIdentity: {{ $fullname }}-identity + Selector: {{ $fullname }} diff --git a/charts/dronescheduler/templates/dronescheduler-networkpolicy-allow-egress-traffic.yaml b/charts/dronescheduler/templates/dronescheduler-networkpolicy-allow-egress-traffic.yaml new file mode 100644 index 00000000..34b94547 --- /dev/null +++ b/charts/dronescheduler/templates/dronescheduler-networkpolicy-allow-egress-traffic.yaml @@ -0,0 +1,49 @@ +## ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Dronescheduler allow egress traffic +################################################################################################### + +{{- if .Values.networkPolicy.egress.enabled }} +{{- $fullname := include "dronescheduler.fullname" . | replace "." "" }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ $fullname }}-np-whitelist-egress-traffic +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: {{ include "dronescheduler.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + app.kubernetes.io/version: {{ .Chart.AppVersion }} + policyTypes: + - Egress + egress: + # allow egress traffic to kubedns + - to: + - podSelector: + matchLabels: + k8s-app: kube-dns + namespaceSelector: {} + ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP +{{- if .Values.networkPolicy.egress.external.enabled }} + # allow egress traffic to all external resources except pods within the + # cluster subnet + - to: + - ipBlock: + cidr: 0.0.0.0/0 + except: + - {{ required "networkPolicy.egress.external.clusterSubnetPrefix is required to enable external traffic" .Values.networkPolicy.egress.external.clusterSubnetPrefix }} +{{- else if .Values.networkPolicy.egress.allowAll }} + - to: [] +{{- end }} +{{ end }} diff --git a/charts/dronescheduler/templates/dronescheduler-networkpolicy-allow-ingress-traffic.yaml b/charts/dronescheduler/templates/dronescheduler-networkpolicy-allow-ingress-traffic.yaml new file mode 100644 index 00000000..908002f1 --- /dev/null +++ b/charts/dronescheduler/templates/dronescheduler-networkpolicy-allow-ingress-traffic.yaml @@ -0,0 +1,42 @@ +## ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Dronescheduler allow ingress traffic +################################################################################################### + +{{- if .Values.networkPolicy.ingress.enabled }} +{{- $fullname := include "dronescheduler.fullname" . | replace "." "" }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ $fullname }}-np-whitelist-ingress-traffic +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: {{ include "dronescheduler.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + app.kubernetes.io/version: {{ .Chart.AppVersion }} + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + dd.fabrikam.com/egress-dronescheduler: "true" + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery +{{- if .Values.networkPolicy.ingress.externalSubnet.enabled }} + - ipBlock: + cidr: {{ required "networkPolicy.ingress.externalSubnet.subnetPrefix is required to enable allow traffic from" .Values.networkPolicy.ingress.externalSubnet.subnetPrefix }} +{{- else if .Values.networkPolicy.egress.allowAll }} + - {} +{{- end }} + ports: + - protocol: {{ .Values.service.targetProtocol }} + port: {{ .Values.service.targetPort }} +{{- end }} diff --git a/charts/dronescheduler/templates/dronescheduler-service.yaml b/charts/dronescheduler/templates/dronescheduler-service.yaml new file mode 100644 index 00000000..9f2ae59c --- /dev/null +++ b/charts/dronescheduler/templates/dronescheduler-service.yaml @@ -0,0 +1,55 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Dronescheduler service +################################################################################################### +{{- $appname := include "dronescheduler.name" . -}} +{{- $chart := include "dronescheduler.chart" . -}} +{{- $instancename := .Release.Name }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "dronescheduler.versionappname" . | replace "." "" }} + labels: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/instance: {{ $instancename }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ $chart }} +spec: + ports: + - name: http + port: 80 + targetPort: {{ .Values.service.targetPort }} + selector: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/instance: {{ $instancename }} +{{ if .Values.current }} +--- +apiVersion: v1 +kind: Service +metadata: + name: dronescheduler + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation + labels: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/managed-by: azuredeveops + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ $chart }} +spec: + ports: + - name: http + port: 80 + targetPort: {{ .Values.service.targetPort }} + selector: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/instance: {{ $instancename }} +{{ end }} diff --git a/charts/dronescheduler/values.yaml b/charts/dronescheduler/values.yaml new file mode 100644 index 00000000..205d0c7d --- /dev/null +++ b/charts/dronescheduler/values.yaml @@ -0,0 +1,90 @@ +# Default values for dronescheduler. +replicaCount: 1 + + +identity: + clientid: + resourceid: + + +dockerregistrynamespace: +dockerregistry: + + +image: + repository: + tag: + pullPolicy: IfNotPresent + + +cosmosdb: + id: + collectionid: + +keyvault: + uri: + + +# probes +readinessProbe: + httpGet: + path: /healthz + port: 8080 + initialDelaySeconds: 40 + periodSeconds: 15 + timeoutSeconds: 2 + failureThreshold: 5 +livenessProbe: + httpGet: + path: /healthz + port: 8080 + initialDelaySeconds: 50 + periodSeconds: 15 + + +# specify an installation/upgrade reason +reason: unknown + + +telemetry: + level: "Error" + + +# indicate what environment is meant to be installed/upgraded +envs: + dev: false + prod: false + qa: false + staging: false + + +current: false + + +# Horizontal Pod Autoscaling +autoscaling: + enabled: false + maxReplicas: + minReplicas: + targetCPUUtilizationPercentage: + + +# Pod-to-pod traffic: east-west +networkPolicy: + egress: + enabled: true + allowAll: false + external: + enabled: false + clusterSubnetPrefix: + ingress: + enabled: true + allowAll: false + externalSubnet: + enabled: false + subnetPrefix: + + +service: + targetPort: 8080 + targetProtocol: TCP diff --git a/charts/ingestion/.helmignore b/charts/ingestion/.helmignore new file mode 100644 index 00000000..50af0317 --- /dev/null +++ b/charts/ingestion/.helmignore @@ -0,0 +1,22 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/ingestion/Chart.yaml b/charts/ingestion/Chart.yaml new file mode 100644 index 00000000..e3e8e168 --- /dev/null +++ b/charts/ingestion/Chart.yaml @@ -0,0 +1,40 @@ +apiVersion: v2 +name: ingestion +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Ingestion Service +type: application +home: https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/microservices/aks +sources: + - https://github.com/mspnp/microservices-reference-implementation +dependencies: +- name: ingestion-dev + repository: "file://envs/ingestion-dev" + version: v0.1.0 + condition: envs.dev + import-values: + - data + +- name: ingestion-prod + repository: "file://envs/ingestion-prod" + version: v0.1.0 + condition: envs.prod + import-values: + - data + +- name: ingestion-qa + repository: "file://envs/ingestion-qa" + version: v0.1.0 + condition: envs.qa + import-values: + - data + +- name: ingestion-staging + repository: "file://envs/ingestion-staging" + version: v0.1.0 + condition: envs.staging + import-values: + - data +maintainers: + - email: v-fean@microsoft.com + name: ferantivero diff --git a/charts/ingestion/envs/ingestion-dev/Chart.yaml b/charts/ingestion/envs/ingestion-dev/Chart.yaml new file mode 100644 index 00000000..64ab4ad2 --- /dev/null +++ b/charts/ingestion/envs/ingestion-dev/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: ingestion-dev +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Ingestion Service +type: application diff --git a/charts/ingestion/envs/ingestion-dev/values.yaml b/charts/ingestion/envs/ingestion-dev/values.yaml new file mode 100644 index 00000000..286f2121 --- /dev/null +++ b/charts/ingestion/envs/ingestion-dev/values.yaml @@ -0,0 +1,18 @@ +# Dev values for ingestion. +nameOverride: ingestion +exports: + data: + replicaCount: 1 + image: + pullPolicy: Always + telemetry: + level: "info" + reason: "new dev deploy" + current: true + resources: + requests: + cpu: 110m + memory: 600Mi + limits: + cpu: 194m + memory: 800Mi diff --git a/charts/ingestion/envs/ingestion-prod/Chart.yaml b/charts/ingestion/envs/ingestion-prod/Chart.yaml new file mode 100644 index 00000000..a12f1d49 --- /dev/null +++ b/charts/ingestion/envs/ingestion-prod/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: ingestion-prod +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Ingestion Service +type: application diff --git a/charts/ingestion/envs/ingestion-prod/templates/ingestion-service.yaml b/charts/ingestion/envs/ingestion-prod/templates/ingestion-service.yaml new file mode 100644 index 00000000..edfa720d --- /dev/null +++ b/charts/ingestion/envs/ingestion-prod/templates/ingestion-service.yaml @@ -0,0 +1,36 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# ingestion service experimental +################################################################################################### +{{ if .Values.experimental }} +{{- $appname := include "ingestion.name" . -}} +{{- $chart := include "ingestion.chart" . -}} +{{- $instancename := .Release.Name }} +# the following object is meant ot be created first time only. +# its configuration will be later managed by CI/CD +apiVersion: v1 +kind: Service +metadata: + name: ingestion-experimental + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation + labels: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/managed-by: azuredeveops + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ $chart }} +spec: + ports: + - name: http + port: 80 + targetPort: 80 + selector: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/instance: {{ $instancename }} +{{ end }} diff --git a/charts/ingestion/envs/ingestion-prod/values.yaml b/charts/ingestion/envs/ingestion-prod/values.yaml new file mode 100644 index 00000000..fd328c17 --- /dev/null +++ b/charts/ingestion/envs/ingestion-prod/values.yaml @@ -0,0 +1,22 @@ +# Production values for ingestion. +nameOverride: ingestion +exports: + data: + replicaCount: 1 + image: + pullPolicy: IfNotPresent + telemetry: + level: "error" + reason: "new prod deploy" + resources: + requests: + cpu: 150m + memory: 600Mi + limits: + cpu: 300m + memory: 800Mi + autoscaling: + enabled: true + maxReplicas: 10 + minReplicas: 1 + targetCPUUtilizationPercentage: 50 diff --git a/charts/ingestion/envs/ingestion-qa/Chart.yaml b/charts/ingestion/envs/ingestion-qa/Chart.yaml new file mode 100644 index 00000000..95c8212c --- /dev/null +++ b/charts/ingestion/envs/ingestion-qa/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: ingestion-qa +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Ingestion Service +type: application diff --git a/charts/ingestion/envs/ingestion-qa/values.yaml b/charts/ingestion/envs/ingestion-qa/values.yaml new file mode 100644 index 00000000..490659b2 --- /dev/null +++ b/charts/ingestion/envs/ingestion-qa/values.yaml @@ -0,0 +1,18 @@ +# QA values for ingestion. +nameOverride: ingestion +exports: + data: + replicaCount: 1 + image: + pullPolicy: Always + telemetry: + level: "info" + reason: "new qa deploy" + current: true + resources: + requests: + cpu: 150m + memory: 600Mi + limits: + cpu: 220m + memory: 800Mi diff --git a/charts/ingestion/envs/ingestion-staging/Chart.yaml b/charts/ingestion/envs/ingestion-staging/Chart.yaml new file mode 100644 index 00000000..d5522948 --- /dev/null +++ b/charts/ingestion/envs/ingestion-staging/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: ingestion-staging +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Ingestion Service +type: application diff --git a/charts/ingestion/envs/ingestion-staging/values.yaml b/charts/ingestion/envs/ingestion-staging/values.yaml new file mode 100644 index 00000000..717acf13 --- /dev/null +++ b/charts/ingestion/envs/ingestion-staging/values.yaml @@ -0,0 +1,23 @@ +# Staging values for ingestion. +nameOverride: ingestion +exports: + data: + replicaCount: 1 + image: + pullPolicy: Always + telemetry: + level: "info" + reason: "new staging deploy" + current: true + resources: + requests: + cpu: 150m + memory: 600Mi + limits: + cpu: 300m + memory: 800Mi + autoscaling: + enabled: true + maxReplicas: 10 + minReplicas: 1 + targetCPUUtilizationPercentage: 50 diff --git a/charts/ingestion/templates/NOTES.txt b/charts/ingestion/templates/NOTES.txt new file mode 100644 index 00000000..68d120cf --- /dev/null +++ b/charts/ingestion/templates/NOTES.txt @@ -0,0 +1,10 @@ +Thank you for installing {{ .Chart.Name }}. + +Your release is named {{ .Release.Name }}. + +All the objects were created in the namespace {{ .Release.Namespace }} + +To learn more about the release, try: + + $ helm status {{ .Release.Name }} --namespace {{ .Release.Namespace }} + $ helm get all {{ .Release.Name }} --namespace {{ .Release.Namespace }} diff --git a/charts/ingestion/templates/_helpers.tpl b/charts/ingestion/templates/_helpers.tpl new file mode 100644 index 00000000..2047d700 --- /dev/null +++ b/charts/ingestion/templates/_helpers.tpl @@ -0,0 +1,61 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "ingestion.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create app name with version. +*/}} +{{- define "ingestion.versionappname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" $name .Chart.AppVersion | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "ingestion.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "ingestion.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "ingestion.labels" -}} +helm.sh/chart: {{ include "ingestion.chart" . }} +{{ include "ingestion.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Selector labels +*/}} +{{- define "ingestion.selectorLabels" -}} +app.kubernetes.io/name: {{ include "ingestion.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + diff --git a/charts/ingestion/templates/ingestion-deploy.yaml b/charts/ingestion/templates/ingestion-deploy.yaml new file mode 100644 index 00000000..a7028f63 --- /dev/null +++ b/charts/ingestion/templates/ingestion-deploy.yaml @@ -0,0 +1,115 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################## +# Ingestion +################################################################################################## +apiVersion: apps/v1beta2 +kind: Deployment +metadata: + name: {{ include "ingestion.fullname" . | replace "." "" }} + labels: + app.kubernetes.io/name: {{ include "ingestion.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ include "ingestion.chart" . }} + annotations: + kubernetes.io/change-cause: {{ .Values.reason }} +spec: + replicas: {{ default 1 .Values.replicaCount }} + selector: + matchLabels: + app.kubernetes.io/name: {{ include "ingestion.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + template: + metadata: + labels: + app.kubernetes.io/name: {{ include "ingestion.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ include "ingestion.chart" . }} + spec: + containers: + - name: &ingestion-container_name fabrikam-ingestion + image: {{ .Values.dockerregistry }}{{ .Values.dockerregistrynamespace }}/{{ .Values.image.repository }}:{{ .Values.image.tag }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + readinessProbe: + httpGet: + path: {{ required "readinessProbe.httpGet.path is required" .Values.readinessProbe.httpGet.path }} + port: {{ required "readinessProbe.httpGet.port is required" .Values.readinessProbe.httpGet.port }} +{{- if .Values.readinessProbe.initialDelaySeconds }} + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} +{{- end }} +{{- if .Values.readinessProbe.periodSeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} +{{- end }} +{{- if .Values.readinessProbe.timeoutSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} +{{- end }} +{{- if .Values.readinessProbe.failureThreshold }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} +{{- end }} + livenessProbe: + httpGet: + path: {{ required "livenessProbe.httpGet.path is required" .Values.livenessProbe.httpGet.path }} + port: {{ required "livenessProbe.httpGet.port is required" .Values.livenessProbe.httpGet.port }} +{{- if .Values.livenessProbe.initialDelaySeconds }} + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} +{{- end }} +{{- if .Values.livenessProbe.periodSeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} +{{- end }} +{{- if .Values.livenessProbe.timeoutSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} +{{- end }} +{{- if .Values.livenessProbe.failureThreshold }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} +{{- end }} + resources: + requests: + cpu: {{ required "A valid .Values.resources.requests.cpu entry required!" .Values.resources.requests.cpu }} + memory: {{ required "A valid .Values.resources.requests.memory entry required!" .Values.resources.requests.memory }} + limits: + cpu: {{ required "A valid .Values.resources.limits.cpu entry required!" .Values.resources.limits.cpu }} + memory: {{ required "A valid .Values.resources.limits.memory entry required!" .Values.resources.limits.memory }} + env: + - name: QUEUE_NAMESPACE + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-secrets + key: queue_namespace + - name: QUEUE_NAME + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-secrets + key: queue_name + - name: QUEUE_KEYNAME + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-secrets + key: queue_keyname + - name: QUEUE_KEYVALUE + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-secrets + key: queue_keyvalue + - name: APPINSIGHTS_INSTRUMENTATIONKEY + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-secrets + key: appinsights-ikey + - name: APPINSIGHTS_LOGGERLEVEL + value: {{ default "error" .Values.telemetry.level | quote }} + - name: CONTAINER_NAME + value: *ingestion-container_name + ports: + - name: service + containerPort: 80 diff --git a/charts/ingestion/templates/ingestion-hpa.yaml b/charts/ingestion/templates/ingestion-hpa.yaml new file mode 100644 index 00000000..2a21c8d7 --- /dev/null +++ b/charts/ingestion/templates/ingestion-hpa.yaml @@ -0,0 +1,34 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ +################################################################################################### +# Ingestion HPA Resrouce Metrics (CPU utilization threshold) +################################################################################################### +{{- if .Values.autoscaling.enabled }} +{{- $fullname := include "ingestion.fullname" . | replace "." "" }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ $fullname }}-hpa + labels: + app.kubernetes.io/name: {{ include "ingestion.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ include "ingestion.chart" . }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ $fullname }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} +{{- end }} diff --git a/charts/ingestion/templates/ingestion-ingress.yaml b/charts/ingestion/templates/ingestion-ingress.yaml new file mode 100644 index 00000000..9f1cedfb --- /dev/null +++ b/charts/ingestion/templates/ingestion-ingress.yaml @@ -0,0 +1,49 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# ingress +################################################################################################### +{{- $svcversion := .Chart.AppVersion | replace "." "" }} +{{- $appversion := .Chart.AppVersion }} +{{- $defaultversionedpath := printf "/%s/" $appversion }} +{{- $relname := .Release.Name }} +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: {{ $relname }}-ingress + annotations: + kubernetes.io/ingress.class: azure/application-gateway +spec: + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.hosts }} + {{- if .tls }} + - hosts: + - {{ .name }} + secretName: {{ $relname }}-{{ .tlsSecretName }} + {{- end }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .name }} + http: + paths: + {{ if .path }} + - path: {{ printf "%s/%s/" .path $appversion }}api/deliveryrequests* + {{ else }} + - path: {{ $defaultversionedpath }}api/deliveryrequests* + {{ end }} + backend: + serviceName: "{{ .serviceName }}-{{ $svcversion }}" + servicePort: http + {{ if (eq $appversion "v0.1.0") }} + - path: {{ default "/" .path }}api/deliveryrequests* + backend: + serviceName: "{{ .serviceName }}" + servicePort: http + {{ end }} + {{ end }} diff --git a/charts/ingestion/templates/ingestion-networkpolicy-allow-egress-traffic.yaml b/charts/ingestion/templates/ingestion-networkpolicy-allow-egress-traffic.yaml new file mode 100644 index 00000000..5c120d38 --- /dev/null +++ b/charts/ingestion/templates/ingestion-networkpolicy-allow-egress-traffic.yaml @@ -0,0 +1,49 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Ingestion allow egress traffic +################################################################################################### + +{{- if .Values.networkPolicy.egress.enabled }} +{{- $fullname := include "ingestion.fullname" . | replace "." "" }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ $fullname }}-np-whitelist-egress-traffic +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: {{ include "ingestion.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + app.kubernetes.io/version: {{ .Chart.AppVersion }} + policyTypes: + - Egress + egress: + # allow egress traffic to kubedns + - to: + - podSelector: + matchLabels: + k8s-app: kube-dns + namespaceSelector: {} + ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP +{{- if .Values.networkPolicy.egress.external.enabled }} + # allow egress traffic to all external resources except pods within the + # cluster subnet + - to: + - ipBlock: + cidr: 0.0.0.0/0 + except: + - {{ required "networkPolicy.egress.external.clusterSubnetPrefix is required to enable external traffic" .Values.networkPolicy.egress.external.clusterSubnetPrefix }} +{{- else if .Values.networkPolicy.egress.allowAll }} + - to: [] +{{- end -}} +{{ end }} diff --git a/charts/ingestion/templates/ingestion-networkpolicy-allow-ingress-traffic.yaml b/charts/ingestion/templates/ingestion-networkpolicy-allow-ingress-traffic.yaml new file mode 100644 index 00000000..ff866dd4 --- /dev/null +++ b/charts/ingestion/templates/ingestion-networkpolicy-allow-ingress-traffic.yaml @@ -0,0 +1,37 @@ +## ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Ingestion allow ingress traffic +################################################################################################### + +{{- if .Values.networkPolicy.ingress.enabled }} +{{- $fullname := include "ingestion.fullname" . | replace "." "" }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ $fullname }}-np-whitelist-ingress-traffic +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: {{ include "ingestion.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + app.kubernetes.io/version: {{ .Chart.AppVersion }} + policyTypes: + - Ingress + ingress: + - from: +{{- if .Values.networkPolicy.ingress.externalSubnet.enabled }} + - ipBlock: + cidr: {{ required "networkPolicy.ingress.externalSubnet.subnetPrefix is required to enable allow traffic from" .Values.networkPolicy.ingress.externalSubnet.subnetPrefix }} +{{- else if .Values.networkPolicy.egress.allowAll }} + - {} +{{- end }} + ports: + - protocol: {{ .Values.service.targetProtocol }} + port: {{ .Values.service.targetPort }} +{{- end }} diff --git a/charts/ingestion/templates/ingestion-secret-ingress-queue.yaml b/charts/ingestion/templates/ingestion-secret-ingress-queue.yaml new file mode 100644 index 00000000..ae109dd1 --- /dev/null +++ b/charts/ingestion/templates/ingestion-secret-ingress-queue.yaml @@ -0,0 +1,11 @@ +kind: Secret +apiVersion: v1 +metadata: + name: {{ .Release.Name }}-secrets +type: Opaque +data: + appinsights-ikey: {{ .Values.secrets.appinsights.ikey | b64enc }} + queue_keyname: {{ .Values.secrets.queue.keyname | b64enc }} + queue_keyvalue: {{ .Values.secrets.queue.keyvalue | b64enc }} + queue_name: {{ .Values.secrets.queue.name | b64enc }} + queue_namespace: {{ .Values.secrets.queue.namespace | b64enc }} diff --git a/charts/ingestion/templates/ingestion-secret-ingress-tls.yaml b/charts/ingestion/templates/ingestion-secret-ingress-tls.yaml new file mode 100644 index 00000000..6312aa6a --- /dev/null +++ b/charts/ingestion/templates/ingestion-secret-ingress-tls.yaml @@ -0,0 +1,19 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +{{- if .Values.ingress.tls}} +{{- $relname := .Release.Name }} +{{- range .Values.ingress.tls.secrets }} +kind: Secret +apiVersion: v1 +metadata: + name: {{ $relname }}-{{ .name }} +type: kubernetes.io/tls +data: + tls.crt: {{ .certificate | b64enc }} + tls.key: {{ .key | b64enc }} +--- +{{ end }} +{{ end }} diff --git a/charts/ingestion/templates/ingestion-service.yaml b/charts/ingestion/templates/ingestion-service.yaml new file mode 100644 index 00000000..aa8cb289 --- /dev/null +++ b/charts/ingestion/templates/ingestion-service.yaml @@ -0,0 +1,55 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# ingestion service +################################################################################################### +{{- $appname := include "ingestion.name" . -}} +{{- $chart := include "ingestion.chart" . -}} +{{- $instancename := .Release.Name }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "ingestion.versionappname" . | replace "." "" }} + labels: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/instance: {{ $instancename }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ $chart }} +spec: + ports: + - name: http + port: 80 + targetPort: {{ .Values.service.targetPort }} + selector: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/instance: {{ $instancename }} +{{ if .Values.current }} +--- +apiVersion: v1 +kind: Service +metadata: + name: ingestion + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation + labels: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/managed-by: azuredeveops + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ $chart }} +spec: + ports: + - name: http + port: 80 + targetPort: {{ .Values.service.targetPort }} + selector: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/instance: {{ $instancename }} +{{ end }} diff --git a/charts/ingestion/values.yaml b/charts/ingestion/values.yaml new file mode 100644 index 00000000..102aaadb --- /dev/null +++ b/charts/ingestion/values.yaml @@ -0,0 +1,79 @@ +# Default values for ingestion. +replicaCount: 1 + + +dockerregistrynamespace: +dockerregistry: + + +image: + repository: + tag: + pullPolicy: IfNotPresent + + +# specify an installation/upgrade reason +reason: unknown + + +# probes +livenessProbe: + httpGet: + path: /actuator/health + port: 80 + initialDelaySeconds: 120 + periodSeconds: 30 + successThreshold: 1 + failureThreshold: 5 + +readinessProbe: + httpGet: + path: /api/probe + port: 80 + initialDelaySeconds: 120 + periodSeconds: 30 + successThreshold: 1 + failureThreshold: 5 + + +telemetry: + level: "error" + + +# indicate what environment is meant to be installed/upgraded +envs: + dev: false + prod: false + qa: false + staging: false + + +current: false + + +# Horizontal Pod Autoscaling +autoscaling: + enabled: false + maxReplicas: + minReplicas: + targetCPUUtilizationPercentage: + + +# Pod-to-pod traffic: east-west +networkPolicy: + egress: + enabled: true + allowAll: false + external: + enabled: false + clusterSubnetPrefix: + ingress: + enabled: true + allowAll: false + externalSubnet: + enabled: false + subnetPrefix: + +service: + targetPort: 80 + targetProtocol: TCP diff --git a/charts/package/.helmignore b/charts/package/.helmignore new file mode 100644 index 00000000..50af0317 --- /dev/null +++ b/charts/package/.helmignore @@ -0,0 +1,22 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/package/Chart.yaml b/charts/package/Chart.yaml new file mode 100644 index 00000000..d8e8c5c4 --- /dev/null +++ b/charts/package/Chart.yaml @@ -0,0 +1,40 @@ +apiVersion: v2 +name: package +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Delivery Package Service +type: application +home: https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/microservices/aks +sources: + - https://github.com/mspnp/microservices-reference-implementation +dependencies: + - name: package-dev + repository: "file://envs/package-dev" + version: v0.1.0 + condition: envs.dev + import-values: + - data + + - name: package-prod + repository: "file://envs/package-prod" + version: v0.1.0 + condition: envs.prod + import-values: + - data + + - name: package-qa + repository: "file://envs/package-qa" + version: v0.1.0 + condition: envs.qa + import-values: + - data + + - name: package-staging + repository: "file://envs/package-staging" + version: v0.1.0 + condition: envs.staging + import-values: + - data +maintainers: + - email: v-fean@microsoft.com + name: ferantivero diff --git a/charts/package/envs/package-dev/Chart.yaml b/charts/package/envs/package-dev/Chart.yaml new file mode 100644 index 00000000..87e0686a --- /dev/null +++ b/charts/package/envs/package-dev/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: package-dev +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Delivery Package Service +type: application diff --git a/charts/package/envs/package-dev/values.yaml b/charts/package/envs/package-dev/values.yaml new file mode 100644 index 00000000..8eebdb82 --- /dev/null +++ b/charts/package/envs/package-dev/values.yaml @@ -0,0 +1,18 @@ +# Dev values for package. +nameOverride: package +exports: + data: + replicaCount: 1 + image: + pullPolicy: Always + log: + level: "info" + reason: "new dev deploy" + current: true + resources: + requests: + cpu: 70m + memory: 100Mi + limits: + cpu: 117m + memory: 140Mi diff --git a/charts/package/envs/package-prod/Chart.yaml b/charts/package/envs/package-prod/Chart.yaml new file mode 100644 index 00000000..edcc14da --- /dev/null +++ b/charts/package/envs/package-prod/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: package-prod +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Delivery Package Service +type: application diff --git a/charts/package/envs/package-prod/templates/package-service.yaml b/charts/package/envs/package-prod/templates/package-service.yaml new file mode 100644 index 00000000..8dbaabc2 --- /dev/null +++ b/charts/package/envs/package-prod/templates/package-service.yaml @@ -0,0 +1,36 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# package service experimental +################################################################################################### +{{ if .Values.experimental }} +{{- $appname := include "package.name" . -}} +{{- $chart := include "package.chart" . -}} +{{- $instancename := .Release.Name }} +# the following object is meant ot be created first time only. +# its configuration will be later managed by CI/CD +apiVersion: v1 +kind: Service +metadata: + name: package-experimental + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation + labels: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/managed-by: azuredeveops + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ $chart }} +spec: + ports: + - name: http + port: 80 + targetPort: 80 + selector: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/instance: {{ $instancename }} +{{ end }} diff --git a/charts/package/envs/package-prod/values.yaml b/charts/package/envs/package-prod/values.yaml new file mode 100644 index 00000000..7c1c90de --- /dev/null +++ b/charts/package/envs/package-prod/values.yaml @@ -0,0 +1,22 @@ +# Production values for package. +nameOverride: package +exports: + data: + replicaCount: 1 + image: + pullPolicy: IfNotPresent + log: + level: "error" + reason: "new prod deploy" + resources: + requests: + cpu: 90m + memory: 100Mi + limits: + cpu: 180m + memory: 140Mi + autoscaling: + enabled: true + maxReplicas: 50 + minReplicas: 1 + targetCPUUtilizationPercentage: 50 diff --git a/charts/package/envs/package-qa/Chart.yaml b/charts/package/envs/package-qa/Chart.yaml new file mode 100644 index 00000000..b75ef3c3 --- /dev/null +++ b/charts/package/envs/package-qa/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: package-qa +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Delivery Package Service +type: application diff --git a/charts/package/envs/package-qa/values.yaml b/charts/package/envs/package-qa/values.yaml new file mode 100644 index 00000000..28668aa4 --- /dev/null +++ b/charts/package/envs/package-qa/values.yaml @@ -0,0 +1,18 @@ +# QA values for package. +nameOverride: package +exports: + data: + replicaCount: 1 + image: + pullPolicy: Always + log: + level: "info" + reason: "new qa deploy" + current: true + resources: + requests: + cpu: 90m + memory: 100Mi + limits: + cpu: 130m + memory: 140Mi diff --git a/charts/package/envs/package-staging/Chart.yaml b/charts/package/envs/package-staging/Chart.yaml new file mode 100644 index 00000000..7111cfd9 --- /dev/null +++ b/charts/package/envs/package-staging/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: package-staging +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Delivery Package Service +type: application diff --git a/charts/package/envs/package-staging/values.yaml b/charts/package/envs/package-staging/values.yaml new file mode 100644 index 00000000..3421a963 --- /dev/null +++ b/charts/package/envs/package-staging/values.yaml @@ -0,0 +1,23 @@ +# Staging values for package. +nameOverride: package +exports: + data: + replicaCount: 1 + image: + pullPolicy: Always + log: + level: "info" + reason: "new staging deploy" + current: true + resources: + requests: + cpu: 90m + memory: 100Mi + limits: + cpu: 180m + memory: 140Mi + autoscaling: + enabled: true + maxReplicas: 50 + minReplicas: 1 + targetCPUUtilizationPercentage: 50 diff --git a/charts/package/templates/NOTES.txt b/charts/package/templates/NOTES.txt new file mode 100644 index 00000000..68d120cf --- /dev/null +++ b/charts/package/templates/NOTES.txt @@ -0,0 +1,10 @@ +Thank you for installing {{ .Chart.Name }}. + +Your release is named {{ .Release.Name }}. + +All the objects were created in the namespace {{ .Release.Namespace }} + +To learn more about the release, try: + + $ helm status {{ .Release.Name }} --namespace {{ .Release.Namespace }} + $ helm get all {{ .Release.Name }} --namespace {{ .Release.Namespace }} diff --git a/charts/package/templates/_helpers.tpl b/charts/package/templates/_helpers.tpl new file mode 100644 index 00000000..62a1b6df --- /dev/null +++ b/charts/package/templates/_helpers.tpl @@ -0,0 +1,60 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "package.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create app name with version. +*/}} +{{- define "package.versionappname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" $name .Chart.AppVersion | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "package.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "package.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "package.labels" -}} +helm.sh/chart: {{ include "package.chart" . }} +{{ include "package.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Selector labels +*/}} +{{- define "package.selectorLabels" -}} +app.kubernetes.io/name: {{ include "package.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} diff --git a/charts/package/templates/package-deploy.yaml b/charts/package/templates/package-deploy.yaml new file mode 100644 index 00000000..5c6db0f5 --- /dev/null +++ b/charts/package/templates/package-deploy.yaml @@ -0,0 +1,102 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Package +################################################################################################### +apiVersion: apps/v1beta2 +kind: Deployment +metadata: + name: {{ include "package.fullname" . | replace "." "" }} + labels: + app.kubernetes.io/name: {{ include "package.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ include "package.chart" . }} + annotations: + kubernetes.io/change-cause: {{ .Values.reason }} +spec: + replicas: {{ default 1 .Values.replicaCount }} + selector: + matchLabels: + app.kubernetes.io/name: {{ include "package.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + template: + metadata: + labels: + app.kubernetes.io/name: {{ include "package.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ include "package.chart" . }} + spec: + containers: + - name: &package-container_name fabrikam-package + image: {{ .Values.dockerregistry }}{{ .Values.dockerregistrynamespace }}/{{ .Values.image.repository }}:{{ .Values.image.tag }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + readinessProbe: + httpGet: + path: {{ required "readinessProbe.httpGet.path is required" .Values.readinessProbe.httpGet.path }} + port: {{ required "readinessProbe.httpGet.port is required" .Values.readinessProbe.httpGet.port }} +{{- if .Values.readinessProbe.initialDelaySeconds }} + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} +{{- end }} +{{- if .Values.readinessProbe.periodSeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} +{{- end }} +{{- if .Values.readinessProbe.timeoutSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} +{{- end }} +{{- if .Values.readinessProbe.failureThreshold }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} +{{- end }} + livenessProbe: + httpGet: + path: {{ required "livenessProbe.httpGet.path is required" .Values.livenessProbe.httpGet.path }} + port: {{ required "livenessProbe.httpGet.port is required" .Values.livenessProbe.httpGet.port }} +{{- if .Values.livenessProbe.initialDelaySeconds }} + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} +{{- end }} +{{- if .Values.livenessProbe.periodSeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} +{{- end }} +{{- if .Values.livenessProbe.timeoutSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} +{{- end }} +{{- if .Values.livenessProbe.failureThreshold }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} +{{- end }} + resources: + requests: + cpu: {{ required "A valid .Values.resources.requests.cpu entry required!" .Values.resources.requests.cpu }} + memory: {{ required "A valid .Values.resources.requests.memory entry required!" .Values.resources.requests.memory }} + limits: + cpu: {{ required "A valid .Values.resources.limits.cpu entry required!" .Values.resources.limits.cpu }} + memory: {{ required "A valid .Values.resources.limits.memory entry required!" .Values.resources.limits.memory }} + env: + - name: CONNECTION_STRING + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-secrets + key: mongodb-pwd + - name: COLLECTION_NAME + value: {{ default "packages" .Values.cosmosDb.collectionName }} + - name: APPINSIGHTS_INSTRUMENTATIONKEY + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-secrets + key: appinsights-ikey + - name: LOG_LEVEL + value: {{ .Values.log.level }} + - name: CONTAINER_NAME + value: *package-container_name + ports: + - name: service + containerPort: 80 diff --git a/charts/package/templates/package-hpa.yaml b/charts/package/templates/package-hpa.yaml new file mode 100644 index 00000000..de87e323 --- /dev/null +++ b/charts/package/templates/package-hpa.yaml @@ -0,0 +1,35 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Package HPA Resrouce Metrics (CPU utilization threshold) +################################################################################################### +{{- if .Values.autoscaling.enabled }} +{{- $fullname := include "package.fullname" . | replace "." "" }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ $fullname }}-hpa + labels: + app.kubernetes.io/name: {{ include "package.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ include "package.chart" . }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ $fullname }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} +{{- end }} diff --git a/charts/package/templates/package-networkpolicy-allow-egress-traffic.yaml b/charts/package/templates/package-networkpolicy-allow-egress-traffic.yaml new file mode 100644 index 00000000..3f25a5f0 --- /dev/null +++ b/charts/package/templates/package-networkpolicy-allow-egress-traffic.yaml @@ -0,0 +1,49 @@ +## ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Package allow egress traffic +################################################################################################### + +{{- if .Values.networkPolicy.egress.enabled }} +{{- $fullname := include "package.fullname" . | replace "." "" }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ $fullname }}-np-whitelist-egress-traffic +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: {{ include "package.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + app.kubernetes.io/version: {{ .Chart.AppVersion }} + policyTypes: + - Egress + egress: + # allow egress traffic to kubedns + - to: + - podSelector: + matchLabels: + k8s-app: kube-dns + namespaceSelector: {} + ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP +{{- if .Values.networkPolicy.egress.external.enabled }} + # allow egress traffic to all external resources except pods within the + # cluster subnet + - to: + - ipBlock: + cidr: 0.0.0.0/0 + except: + - {{ required "networkPolicy.egress.external.clusterSubnetPrefix is required to enable external traffic" .Values.networkPolicy.egress.external.clusterSubnetPrefix }} +{{- else if .Values.networkPolicy.egress.allowAll }} + - to: [] +{{- end }} +{{ end }} diff --git a/charts/package/templates/package-networkpolicy-allow-ingress-traffic.yaml b/charts/package/templates/package-networkpolicy-allow-ingress-traffic.yaml new file mode 100644 index 00000000..ac5f1ffc --- /dev/null +++ b/charts/package/templates/package-networkpolicy-allow-ingress-traffic.yaml @@ -0,0 +1,42 @@ +## ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Package allow ingress traffic +################################################################################################### + +{{- if .Values.networkPolicy.ingress.enabled }} +{{- $fullname := include "package.fullname" . | replace "." "" }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ $fullname }}-np-whitelist-ingress-traffic +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: {{ include "package.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + app.kubernetes.io/version: {{ .Chart.AppVersion }} + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + dd.fabrikam.com/egress-package: "true" + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery +{{- if .Values.networkPolicy.ingress.externalSubnet.enabled }} + - ipBlock: + cidr: {{ required "networkPolicy.ingress.externalSubnet.subnetPrefix is required to enable allow traffic from" .Values.networkPolicy.ingress.externalSubnet.subnetPrefix }} +{{- else if .Values.networkPolicy.egress.allowAll }} + - {} +{{- end }} + ports: + - protocol: {{ .Values.service.targetProtocol }} + port: {{ .Values.service.targetPort }} +{{- end }} diff --git a/charts/package/templates/package-secrets.yaml b/charts/package/templates/package-secrets.yaml new file mode 100644 index 00000000..7e431dd4 --- /dev/null +++ b/charts/package/templates/package-secrets.yaml @@ -0,0 +1,16 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Package secrets +################################################################################################### +kind: Secret +apiVersion: v1 +metadata: + name: {{ .Release.Name }}-secrets +type: Opaque +data: + appinsights-ikey: {{ .Values.secrets.appinsights.ikey | b64enc }} + mongodb-pwd: {{ .Values.secrets.mongo.pwd | b64enc }} diff --git a/charts/package/templates/package-service.yaml b/charts/package/templates/package-service.yaml new file mode 100644 index 00000000..156cb0c8 --- /dev/null +++ b/charts/package/templates/package-service.yaml @@ -0,0 +1,55 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# package service +################################################################################################### +{{- $appname := include "package.name" . -}} +{{- $chart := include "package.chart" . -}} +{{- $instancename := .Release.Name }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "package.versionappname" . | replace "." "" }} + labels: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/instance: {{ $instancename }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ $chart }} +spec: + ports: + - name: http + port: 80 + targetPort: {{ .Values.service.targetPort }} + selector: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/instance: {{ $instancename }} +{{ if .Values.current }} +--- +apiVersion: v1 +kind: Service +metadata: + name: package + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation + labels: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/managed-by: azuredeveops + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ $chart }} +spec: + ports: + - name: http + port: 80 + targetPort: {{ .Values.service.targetPort }} + selector: + app.kubernetes.io/name: {{ $appname }} + app.kubernetes.io/instance: {{ $instancename }} +{{ end }} diff --git a/charts/package/values.yaml b/charts/package/values.yaml new file mode 100644 index 00000000..e4d4b1d1 --- /dev/null +++ b/charts/package/values.yaml @@ -0,0 +1,85 @@ +# Default values for package service. +nameOverride: package + + +replicaCount: 1 + + +dockerregistrynamespace: +dockerregistry: + + +image: + repository: + tag: + pullPolicy: IfNotPresent + + +# specify an installation/upgrade reason +reason: unknown + + +# probes +readinessProbe: + httpGet: + path: /healthz + port: 80 + initialDelaySeconds: 40 + periodSeconds: 15 + timeoutSeconds: 2 + failureThreshold: 5 + +livenessProbe: + httpGet: + path: /healthz + port: 80 + initialDelaySeconds: 50 + periodSeconds: 15 + + +log: + level: error + + +cosmosDb: + collectionName: + + +# indicate what environment is meant to be installed/upgraded +envs: + dev: false + prod: false + qa: false + staging: false + + +current: false + + +# Horizontal Pod Autoscaling +autoscaling: + enabled: false + maxReplicas: + minReplicas: + targetCPUUtilizationPercentage: + + +# Pod-to-pod traffic: east-west +networkPolicy: + egress: + enabled: true + allowAll: false + external: + enabled: false + clusterSubnetPrefix: + ingress: + enabled: true + allowAll: false + externalSubnet: + enabled: false + subnetPrefix: + + +service: + targetPort: 80 + targetProtocol: TCP diff --git a/charts/workflow/.helmignore b/charts/workflow/.helmignore new file mode 100644 index 00000000..50af0317 --- /dev/null +++ b/charts/workflow/.helmignore @@ -0,0 +1,22 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/workflow/Chart.yaml b/charts/workflow/Chart.yaml new file mode 100644 index 00000000..c4c226c7 --- /dev/null +++ b/charts/workflow/Chart.yaml @@ -0,0 +1,40 @@ +apiVersion: v2 +name: workflow +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Delivery Workflow Service +type: application +home: https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/microservices/aks +sources: + - https://github.com/mspnp/microservices-reference-implementation +dependencies: + - name: workflow-dev + repository: "file://envs/workflow-dev" + version: v0.1.0 + condition: envs.dev + import-values: + - data + + - name: workflow-prod + repository: "file://envs/workflow-prod" + version: v0.1.0 + condition: envs.prod + import-values: + - data + + - name: workflow-qa + repository: "file://envs/workflow-qa" + version: v0.1.0 + condition: envs.qa + import-values: + - data + + - name: workflow-staging + repository: "file://envs/workflow-staging" + version: v0.1.0 + condition: envs.staging + import-values: + - data +maintainers: + - email: v-fean@microsoft.com + name: ferantivero diff --git a/charts/workflow/envs/workflow-dev/Chart.yaml b/charts/workflow/envs/workflow-dev/Chart.yaml new file mode 100644 index 00000000..efaec960 --- /dev/null +++ b/charts/workflow/envs/workflow-dev/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: workflow-dev +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Delivery Workflow Service +type: application diff --git a/charts/workflow/envs/workflow-dev/values.yaml b/charts/workflow/envs/workflow-dev/values.yaml new file mode 100644 index 00000000..4a5c9e1c --- /dev/null +++ b/charts/workflow/envs/workflow-dev/values.yaml @@ -0,0 +1,17 @@ +# Dev values for workflow. +nameOverride: workflow +exports: + data: + replicaCount: 1 + image: + pullPolicy: Always + telemetry: + level: "Information" + reason: "new dev deploy" + resources: + requests: + cpu: 400m + memory: 100Mi + limits: + cpu: 664m + memory: 140Mi \ No newline at end of file diff --git a/charts/workflow/envs/workflow-prod/Chart.yaml b/charts/workflow/envs/workflow-prod/Chart.yaml new file mode 100644 index 00000000..bea10f6f --- /dev/null +++ b/charts/workflow/envs/workflow-prod/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: workflow-prod +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Delivery Workflow Service +type: application diff --git a/charts/workflow/envs/workflow-prod/values.yaml b/charts/workflow/envs/workflow-prod/values.yaml new file mode 100644 index 00000000..79e382a1 --- /dev/null +++ b/charts/workflow/envs/workflow-prod/values.yaml @@ -0,0 +1,22 @@ +# Production values for workflow. +nameOverride: workflow +exports: + data: + replicaCount: 1 + image: + pullPolicy: IfNotPresent + telemetry: + level: "Error" + reason: "new prod deploy" + resources: + requests: + cpu: 515m + memory: 100Mi + limits: + cpu: 1000m + memory: 140Mi + autoscaling: + enabled: true + maxReplicas: 50 + minReplicas: 1 + targetCPUUtilizationPercentage: 50 diff --git a/charts/workflow/envs/workflow-qa/Chart.yaml b/charts/workflow/envs/workflow-qa/Chart.yaml new file mode 100644 index 00000000..3e519c1a --- /dev/null +++ b/charts/workflow/envs/workflow-qa/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: workflow-qa +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Delivery Workflow Service +type: application diff --git a/charts/workflow/envs/workflow-qa/values.yaml b/charts/workflow/envs/workflow-qa/values.yaml new file mode 100644 index 00000000..db35b4a2 --- /dev/null +++ b/charts/workflow/envs/workflow-qa/values.yaml @@ -0,0 +1,17 @@ +# QA values for workflow. +nameOverride: workflow +exports: + data: + replicaCount: 1 + image: + pullPolicy: Always + telemetry: + level: "Information" + reason: "new qa deploy" + resources: + requests: + cpu: 515m + memory: 100Mi + limits: + cpu: 770m + memory: 140Mi \ No newline at end of file diff --git a/charts/workflow/envs/workflow-staging/Chart.yaml b/charts/workflow/envs/workflow-staging/Chart.yaml new file mode 100644 index 00000000..938d091b --- /dev/null +++ b/charts/workflow/envs/workflow-staging/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: workflow-staging +version: v0.1.0 +appVersion: v0.1.0 +description: Fabrikam Drone Delivery Workflow Service +type: application diff --git a/charts/workflow/envs/workflow-staging/values.yaml b/charts/workflow/envs/workflow-staging/values.yaml new file mode 100644 index 00000000..9bc915ae --- /dev/null +++ b/charts/workflow/envs/workflow-staging/values.yaml @@ -0,0 +1,22 @@ +# Staging values for workflow. +nameOverride: workflow +exports: + data: + replicaCount: 1 + image: + pullPolicy: Always + telemetry: + level: "Information" + reason: "new staging deploy" + resources: + requests: + cpu: 515m + memory: 100Mi + limits: + cpu: 1000m + memory: 140Mi + autoscaling: + enabled: true + maxReplicas: 50 + minReplicas: 1 + targetCPUUtilizationPercentage: 50 diff --git a/charts/workflow/templates/NOTES.txt b/charts/workflow/templates/NOTES.txt new file mode 100644 index 00000000..c863060c --- /dev/null +++ b/charts/workflow/templates/NOTES.txt @@ -0,0 +1,10 @@ +Thank you for installing {{ .Chart.Name }}. + +Your release is named {{ .Release.Name }}. + +All the objects were created in the namespace {{ .Values.namespace }} + +To learn more about the release, try: + + $ helm status {{ .Release.Name }} + $ helm get all {{ .Release.Name }} --namespace {{ .Release.Namespace }} diff --git a/charts/workflow/templates/_helpers.tpl b/charts/workflow/templates/_helpers.tpl new file mode 100644 index 00000000..82ff0779 --- /dev/null +++ b/charts/workflow/templates/_helpers.tpl @@ -0,0 +1,60 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "workflow.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create app name with version. +*/}} +{{- define "workflow.versionappname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" $name .Chart.AppVersion | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "workflow.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "workflow.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "workflow.labels" -}} +helm.sh/chart: {{ include "workflow.chart" . }} +{{ include "workflow.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Selector labels +*/}} +{{- define "workflow.selectorLabels" -}} +app.kubernetes.io/name: {{ include "workflow.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} diff --git a/charts/workflow/templates/workflow-deploy.yaml b/charts/workflow/templates/workflow-deploy.yaml new file mode 100644 index 00000000..8e56f405 --- /dev/null +++ b/charts/workflow/templates/workflow-deploy.yaml @@ -0,0 +1,138 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Workflow +################################################################################################### +{{- $fullname := include "workflow.fullname" . | replace "." "" }} +apiVersion: apps/v1beta2 +kind: Deployment +metadata: + name: {{ $fullname }} + labels: + app.kubernetes.io/name: {{ include "workflow.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ include "workflow.chart" . }} + aadpodidbinding: {{ $fullname }} + annotations: + kubernetes.io/change-cause: {{ .Values.reason }} +spec: + replicas: {{ default 1 .Values.replicaCount }} + selector: + matchLabels: + app.kubernetes.io/name: {{ include "workflow.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + template: + metadata: + labels: + app.kubernetes.io/name: {{ include "workflow.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ include "workflow.chart" . }} + aadpodidbinding: {{ $fullname }} +{{ toYaml .Values.workflow.customPodLabels | indent 8 }} + spec: + securityContext: + fsGroup: 1 + containers: + - name: fabrikam-workflow + image: {{ .Values.dockerregistry }}{{ .Values.dockerregistrynamespace }}/{{ .Values.image.repository }}:{{ .Values.image.tag }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + readinessProbe: + exec: + command: +{{- range .Values.readinessProbe.exec.command }} + - {{ . | quote }} +{{- end }} +{{- if .Values.readinessProbe.initialDelaySeconds }} + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} +{{- end }} +{{- if .Values.readinessProbe.periodSeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} +{{- end }} +{{- if .Values.readinessProbe.timeoutSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} +{{- end }} +{{- if .Values.readinessProbe.failureThreshold }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} +{{- end }} + livenessProbe: + exec: + command: +{{- range .Values.livenessProbe.exec.command }} + - {{ . | quote }} +{{- end }} +{{- if .Values.livenessProbe.initialDelaySeconds }} + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} +{{- end }} +{{- if .Values.livenessProbe.periodSeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} +{{- end }} +{{- if .Values.livenessProbe.timeoutSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} +{{- end }} +{{- if .Values.livenessProbe.failureThreshold }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} +{{- end }} + resources: + requests: + cpu: {{ required "A valid .Values.resources.requests.cpu entry required!" .Values.resources.requests.cpu }} + memory: {{ required "A valid .Values.resources.requests.memory entry required!" .Values.resources.requests.memory }} + limits: + cpu: {{ required "A valid .Values.resources.limits.cpu entry required!" .Values.resources.limits.cpu }} + memory: {{ required "A valid .Values.resources.limits.memory entry required!" .Values.resources.limits.memory }} + volumeMounts: + - name: workflow + mountPath: /kvmnt + readOnly: true + env: + - name: CONFIGURATION_FOLDER + value: /kvmnt + - name: HEALTHCHECK_INITIAL_DELAY + value: {{ default "30000" .Values.healthcheck.delay | quote }} + - name: SERVICE_URI_DELIVERY + value: {{ .Values.serviceuri.delivery }} + - name: SERVICE_URI_DRONE + value: {{ .Values.serviceuri.drone }} + - name: SERVICE_URI_PACKAGE + value: {{ .Values.serviceuri.package }} + - name: SERVICEREQUEST__MAXRETRIES + value: "{{ .Values.servicerequest.maxretries }}" + - name: SERVICEREQUEST__CIRCUITBREAKERTHRESHOLD + value: "{{ .Values.servicerequest.circuitbreakerthreshold }}" + - name: SERVICEREQUEST__CIRCUITBREAKERSAMPLINGPERIODSECONDS + value: "{{ .Values.servicerequest.circuitbreakersamplingperiodseconds }}" + - name: SERVICEREQUEST__CIRCUITBREAKERMINIMUMTHROUGHPUT + value: "{{ .Values.servicerequest.circuitbreakerminimumthroughput }}" + - name: SERVICEREQUEST__CIRCUITBREAKERBREAKDURATION + value: "{{ .Values.servicerequest.circuitbreakerbreakduration }}" + - name: SERVICEREQUEST__MAXBULKHEADSIZE + value: "{{ .Values.servicerequest.maxbulkheadsize }}" + - name: SERVICEREQUEST__MAXBULKHEADQUEUESIZE + value: "{{ .Values.servicerequest.maxbulkheadqueuesize }}" + - name: LOGGING__ApplicationInsights__LOGLEVEL__DEFAULT + value: {{ default "Error" .Values.telemetry.level | quote }} + - name: no_proxy + value: 169.254.169.254 + volumes: + - name: workflow + flexVolume: + driver: "azure/kv" + options: + usepodidentity: "true" + keyvaultname: {{ .Values.keyvault.name }} + keyvaultobjectnames: QueueName;QueueEndpoint;QueueAccessPolicyName;QueueAccessPolicyKey;ApplicationInsights-InstrumentationKey + keyvaultobjecttypes: secret;secret;secret;secret;secret + keyvaultobjectversions: "" + resourcegroup: {{ .Values.keyvault.resourcegroup }} + subscriptionid: {{ .Values.keyvault.subscriptionid }} + tenantid: {{ .Values.keyvault.tenantid }} diff --git a/charts/workflow/templates/workflow-hpa.yaml b/charts/workflow/templates/workflow-hpa.yaml new file mode 100644 index 00000000..02ec80a1 --- /dev/null +++ b/charts/workflow/templates/workflow-hpa.yaml @@ -0,0 +1,34 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ +################################################################################################### +# Workflow HPA Resrouce Metrics (CPU utilization threshold) +################################################################################################### +{{- if .Values.autoscaling.enabled }} +{{- $fullname := include "workflow.fullname" . | replace "." "" }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ $fullname }}-hpa + labels: + app.kubernetes.io/name: {{ include "workflow.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + helm.sh/chart: {{ include "workflow.chart" . }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ $fullname }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} +{{- end }} diff --git a/charts/workflow/templates/workflow-identity.yaml b/charts/workflow/templates/workflow-identity.yaml new file mode 100644 index 00000000..a74a526e --- /dev/null +++ b/charts/workflow/templates/workflow-identity.yaml @@ -0,0 +1,33 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Workflow service identity +################################################################################################### +{{- $fullname := include "workflow.fullname" . | replace "." "" }} +apiVersion: "aadpodidentity.k8s.io/v1" +kind: AzureIdentity +metadata: + name: {{ $fullname }}-identity + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-delete-policy": hook-failed,before-hook-creation + "helm.sh/hook-weight": "0" +spec: + type: 0 + ResourceID: {{ .Values.identity.resourceid }} + ClientID: {{ .Values.identity.clientid }} +--- +apiVersion: "aadpodidentity.k8s.io/v1" +kind: AzureIdentityBinding +metadata: + name: {{ $fullname }}-identity-binding + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-delete-policy": hook-failed,before-hook-creation + "helm.sh/hook-weight": "1" +spec: + AzureIdentity: {{ $fullname }}-identity + Selector: {{ $fullname }} diff --git a/charts/workflow/templates/workflow-networkpolicy-allow-egress-traffic.yaml b/charts/workflow/templates/workflow-networkpolicy-allow-egress-traffic.yaml new file mode 100644 index 00000000..f119404d --- /dev/null +++ b/charts/workflow/templates/workflow-networkpolicy-allow-egress-traffic.yaml @@ -0,0 +1,49 @@ +## ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +################################################################################################### +# Workflow allow egress traffic +################################################################################################### + +{{- if .Values.networkPolicy.egress.enabled }} +{{- $fullname := include "workflow.fullname" . | replace "." "" }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ $fullname }}-np-whitelist-egress-traffic +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: {{ include "workflow.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: backend + app.kubernetes.io/part-of: dronedelivery + app.kubernetes.io/version: {{ .Chart.AppVersion }} + policyTypes: + - Egress + egress: + # allow egress traffic to kubedns + - to: + - podSelector: + matchLabels: + k8s-app: kube-dns + namespaceSelector: {} + ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP +{{- if .Values.networkPolicy.egress.external.enabled }} + # allow egress traffic to all external resources except pods within the + # cluster subnet + - to: + - ipBlock: + cidr: 0.0.0.0/0 + except: + - {{ required "networkPolicy.egress.external.clusterSubnetPrefix is required to enable external traffic" .Values.networkPolicy.egress.external.clusterSubnetPrefix }} +{{- else if .Values.networkPolicy.egress.allowAll }} + - to: [] +{{- end -}} +{{ end }} diff --git a/charts/workflow/values.yaml b/charts/workflow/values.yaml new file mode 100644 index 00000000..37950879 --- /dev/null +++ b/charts/workflow/values.yaml @@ -0,0 +1,107 @@ +# Default values for workflow. +nameOverride: workflow + + +replicaCount: 1 + + +dockerregistrynamespace: +dockerregistry: + + +identity: + clientid: + resourceid: + + +image: + repository: + tag: + pullPolicy: IfNotPresent + + +# specify an installation/upgrade reason +reason: unknown + + +serviceuri: + delivery: http://delivery/api/Deliveries/ + drone: http://dronescheduler/api/DroneDeliveries/ + package: http://package/api/packages/ + +servicerequest: + maxretries: 3 + circuitbreakerthreshold: 0.5 + circuitbreakersamplingperiodseconds: 5 + circuitbreakerminimumthroughput: 20 + circuitbreakerbreakduration: 30 + maxbulkheadsize: 100 + maxbulkheadqueuesize: 25 + + +# probes +healthcheck: + delay: +readinessProbe: + exec: + command: + - cat + - /app/healthz + initialDelaySeconds: 40 + periodSeconds: 15 + timeoutSeconds: 2 + failureThreshold: 5 + +livenessProbe: + exec: + command: + - find + - /app/healthz + - -mmin + - -1 + initialDelaySeconds: 50 + periodSeconds: 30 + + +keyvault: + name: + resourcegroup: + subscriptionid: + tenantid: + + +telemetry: + level: "Error" + + +# indicate what environment is meant to be installed/upgraded +envs: + dev: false + prod: false + qa: false + staging: false + + +# Horizontal Pod Autoscaling +autoscaling: + enabled: false + maxReplicas: + minReplicas: + targetCPUUtilizationPercentage: + + +# Pod-to-pod traffic: east-west +networkPolicy: + egress: + enabled: true + allowAll: false + external: + enabled: false + clusterSubnetPrefix: + +# indicate pods this app will attemp to establish a connection with +workflow: + customPodLabels: + dd.fabrikam.com/egress-delivery: "true" + dd.fabrikam.com/egress-dronescheduler: "true" + dd.fabrikam.com/egress-package: "true" diff --git a/deployment.md b/deployment.md index 8ea496a5..e00dd725 100644 --- a/deployment.md +++ b/deployment.md @@ -1,13 +1,16 @@ # Deploying the Reference Implementation - - ## Prerequisites -- Azure suscription +- Azure subscription + > Important: The user initiating the deployment process must have access to the `Microsoft.Authorization/roleAssignments/write` permission. For more information, please refer to [the Container Insights doc](https://docs.microsoft.com/en-us/azure/azure-monitor/insights/container-insights-troubleshoot#authorization-error-during-onboarding-or-update-operation) - [Azure CLI 2.0](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) - [Docker](https://docs.docker.com/) -- [Docker Compose](https://docs.docker.com/compose/install/) +- [JQ](https://stedolan.github.io/jq/download/) + +> Note: in linux systems, it is possible to run the docker command without prefacing +> with sudo. For more information, please refer to [the Post-installation steps +> for linux](https://docs.docker.com/install/linux/linux-postinstall/) Clone or download this repo locally. @@ -15,351 +18,602 @@ Clone or download this repo locally. git clone https://github.com/mspnp/microservices-reference-implementation.git ``` -## Create the Kubernetes cluster +The deployment steps shown here use Bash shell commands. On Windows, you can use the [Windows Subsystem for Linux](https://docs.microsoft.com/windows/wsl/about) to run Bash. + +## Generate a SSH rsa public/private key pair + +the SSH rsa key pair can be generated using ssh-keygen, among other tools, on Linux, Mac, or Windows. If you already have an ~/.ssh/id_rsa.pub file, you could provide the same later on. If you need to create an SSH key pair, see [How to create and use an SSH key pair](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/mac-create-ssh-keys). +> Note: the SSH rsa public key will be requested when deploying your Kubernetes cluster in Azure. + +## Azure Resources Provisioning Set environment variables. ```bash +export SSH_PUBLIC_KEY_FILE=[YOUR_RECENTLY_GENERATED_SSH_RSA_PUBLIC_KEY_FILE_HERE] + export LOCATION=[YOUR_LOCATION_HERE] -export UNIQUE_APP_NAME_PREFIX=[YOUR_UNIQUE_APPLICATION_NAME_HERE] +export RESOURCE_GROUP=[YOUR_RESOURCE_GROUP_HERE] -export RESOURCE_GROUP="${UNIQUE_APP_NAME_PREFIX}-rg" && \ -export CLUSTER_NAME="${UNIQUE_APP_NAME_PREFIX}-cluster" +export SUBSCRIPTION_ID=$(az account show --query id --output tsv) +export SUBSCRIPTION_NAME=$(az account show --query name --output tsv) +export TENANT_ID=$(az account show --query tenantId --output tsv) + +export PROJECT_ROOT=./microservices-reference-implementation +export K8S=$PROJECT_ROOT/k8s +export HELM_CHARTS=$PROJECT_ROOT/charts ``` -Provision a Kubernetes cluster in ACS +Infrastructure Prerequisites ```bash # Log in to Azure az login -# Create a resource group for ACS -az group create --name $RESOURCE_GROUP --location $LOCATION +# Create service principal for AKS +export SP_DETAILS=$(az ad sp create-for-rbac --role="Contributor" -o json) && \ +export SP_APP_ID=$(echo $SP_DETAILS | jq ".appId" -r) && \ +export SP_CLIENT_SECRET=$(echo $SP_DETAILS | jq ".password" -r) && \ +export SP_OBJECT_ID=$(az ad sp show --id $SP_APP_ID -o tsv --query objectId) +``` + +## Optional: Set up automated CI/CD for dev, test, qa and production with Azure DevOps + +Add [CI/CD to Drone Delivery using Azure Pipelines with YAML](./deploymentCICD.md). + +> Important: If you don't want to set up the CI/CD pipelines, you can manually deploy the application for development as follows. + +## Manual deployment for dev -# Create the ACS cluster -az acs create --orchestrator-type kubernetes --resource-group $RESOURCE_GROUP --name $CLUSTER_NAME --generate-ssh-keys +> Note: this deployment might take up to 20 minutes -# Install kubectl -sudo az acs kubernetes install-cli +Infrastructure + +```bash +# Deploy the resource groups and managed identities +# These are deployed first in a separate template to avoid propagation delays with AAD +az deployment create \ + --name azuredeploy-prereqs-dev \ + --location $LOCATION \ + --template-file ${PROJECT_ROOT}/azuredeploy-prereqs.json \ + --parameters resourceGroupName=$RESOURCE_GROUP \ + resourceGroupLocation=$LOCATION + +export IDENTITIES_DEPLOYMENT_NAME=$(az deployment show -n azuredeploy-prereqs-dev --query properties.outputs.identitiesDeploymentName.value -o tsv) && \ +export DELIVERY_ID_NAME=$(az group deployment show -g $RESOURCE_GROUP -n $IDENTITIES_DEPLOYMENT_NAME --query properties.outputs.deliveryIdName.value -o tsv) && \ +export DELIVERY_ID_PRINCIPAL_ID=$(az identity show -g $RESOURCE_GROUP -n $DELIVERY_ID_NAME --query principalId -o tsv) && \ +export DRONESCHEDULER_ID_NAME=$(az group deployment show -g $RESOURCE_GROUP -n $IDENTITIES_DEPLOYMENT_NAME --query properties.outputs.droneSchedulerIdName.value -o tsv) && \ +export DRONESCHEDULER_ID_PRINCIPAL_ID=$(az identity show -g $RESOURCE_GROUP -n $DRONESCHEDULER_ID_NAME --query principalId -o tsv) && \ +export WORKFLOW_ID_NAME=$(az group deployment show -g $RESOURCE_GROUP -n $IDENTITIES_DEPLOYMENT_NAME --query properties.outputs.workflowIdName.value -o tsv) && \ +export WORKFLOW_ID_PRINCIPAL_ID=$(az identity show -g $RESOURCE_GROUP -n $WORKFLOW_ID_NAME --query principalId -o tsv) && \ +export GATEWAY_CONTROLLER_ID_NAME=$(az group deployment show -g $RESOURCE_GROUP -n $IDENTITIES_DEPLOYMENT_NAME --query properties.outputs.appGatewayControllerIdName.value -o tsv) && \ +export GATEWAY_CONTROLLER_ID_PRINCIPAL_ID=$(az identity show -g $RESOURCE_GROUP -n $GATEWAY_CONTROLLER_ID_NAME --query principalId -o tsv) && \ +export RESOURCE_GROUP_ACR=$(az group deployment show -g $RESOURCE_GROUP -n $IDENTITIES_DEPLOYMENT_NAME --query properties.outputs.acrResourceGroupName.value -o tsv) + +# Wait for AAD propagation +until az ad sp show --id ${DELIVERY_ID_PRINCIPAL_ID} &> /dev/null ; do echo "Waiting for AAD propagation" && sleep 5; done +until az ad sp show --id ${DRONESCHEDULER_ID_PRINCIPAL_ID} &> /dev/null ; do echo "Waiting for AAD propagation" && sleep 5; done +until az ad sp show --id ${WORKFLOW_ID_PRINCIPAL_ID} &> /dev/null ; do echo "Waiting for AAD propagation" && sleep 5; done +until az ad sp show --id ${GATEWAY_CONTROLLER_ID_PRINCIPAL_ID} &> /dev/null ; do echo "Waiting for AAD propagation" && sleep 5; done + +# Export the kubernetes cluster version +export KUBERNETES_VERSION=$(az aks get-versions -l $LOCATION --query "orchestrators[?default!=null].orchestratorVersion" -o tsv) +export SERVICETAGS_LOCATION=$(az account list-locations --query "[?name=='${LOCATION}'].displayName" -o tsv | sed 's/[[:space:]]//g') + +# Deploy cluster and microservices Azure services +az group deployment create -g $RESOURCE_GROUP --name azuredeploy-dev --template-file ${PROJECT_ROOT}/azuredeploy.json \ +--parameters servicePrincipalClientId=${SP_APP_ID} \ + servicePrincipalClientSecret=${SP_CLIENT_SECRET} \ + servicePrincipalId=${SP_OBJECT_ID} \ + kubernetesVersion=${KUBERNETES_VERSION} \ + sshRSAPublicKey="$(cat ${SSH_PUBLIC_KEY_FILE})" \ + deliveryIdName=${DELIVERY_ID_NAME} \ + deliveryPrincipalId=${DELIVERY_ID_PRINCIPAL_ID} \ + droneSchedulerIdName=${DRONESCHEDULER_ID_NAME} \ + droneSchedulerPrincipalId=${DRONESCHEDULER_ID_PRINCIPAL_ID} \ + workflowIdName=${WORKFLOW_ID_NAME} \ + workflowPrincipalId=${WORKFLOW_ID_PRINCIPAL_ID} \ + appGatewayControllerIdName=${GATEWAY_CONTROLLER_ID_NAME} \ + appGatewayControllerPrincipalId=${GATEWAY_CONTROLLER_ID_PRINCIPAL_ID} \ + acrResourceGroupName=${RESOURCE_GROUP_ACR} \ + acrResourceGroupLocation=$LOCATION + +export VNET_NAME=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-dev --query properties.outputs.aksVNetName.value -o tsv) && \ +export CLUSTER_SUBNET_NAME=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-dev --query properties.outputs.aksClusterSubnetName.value -o tsv) && \ +export CLUSTER_SUBNET_PREFIX=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-dev --query properties.outputs.aksClusterSubnetPrefix.value -o tsv) && \ +export CLUSTER_NAME=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-dev --query properties.outputs.aksClusterName.value -o tsv) && \ +export CLUSTER_SERVER=$(az aks show -n $CLUSTER_NAME -g $RESOURCE_GROUP --query fqdn -o tsv) && \ +export FIREWALL_PIP_NAME=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-dev --query properties.outputs.firewallPublicIpName.value -o tsv) && \ +export ACR_NAME=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-dev --query properties.outputs.acrName.value -o tsv) && \ +export ACR_SERVER=$(az acr show -n $ACR_NAME --query loginServer -o tsv) && \ +export DELIVERY_REDIS_HOSTNAME=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-dev --query properties.outputs.deliveryRedisHostName.value -o tsv) + +# Restrict cluster egress traffic +az group deployment create -g $RESOURCE_GROUP --name azuredeploy-firewall --template-file ${PROJECT_ROOT}/azuredeploy-firewall.json \ +--parameters aksVnetName=${VNET_NAME} \ + aksClusterSubnetName=${CLUSTER_SUBNET_NAME} \ + aksClusterSubnetPrefix=${CLUSTER_SUBNET_PREFIX} \ + firewallPublicIpName=${FIREWALL_PIP_NAME} \ + serviceTagsLocation=${SERVICETAGS_LOCATION} \ + aksFqdns="['${CLUSTER_SERVER}']" \ + acrServers="['${ACR_SERVER}']" \ + deliveryRedisHostNames="['${DELIVERY_REDIS_HOSTNAME}']" +``` + +Get outputs from Azure Deploy +```bash +# Shared +export GATEWAY_SUBNET_PREFIX=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-dev --query properties.outputs.appGatewaySubnetPrefix.value -o tsv) +``` + +Download kubectl and create a k8s namespace +```bash +# Install kubectl +sudo az aks install-cli # Get the Kubernetes cluster credentials -az acs kubernetes get-credentials --resource-group=$RESOURCE_GROUP --name=$CLUSTER_NAME +az aks get-credentials --resource-group=$RESOURCE_GROUP --name=$CLUSTER_NAME -# Create the Shipping BC namespace -kubectl create namespace bc-shipping +# Create namespaces +kubectl create namespace backend-dev ``` -Create an Azure Container Registry instance. +Setup Helm + +```bash +# install helm CLI +curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash +``` -> Note: Azure Container Registory is not required. If you prefer, you can store the Docker images for this solution in another container registry. +Integrate Application Insights instance ```bash -export ACR_NAME=[YOUR_CONTAINER_REGISTRY_NAME_HERE] +# Acquire Instrumentation Key +export AI_NAME=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-dev --query properties.outputs.appInsightsName.value -o tsv) +export AI_IKEY=$(az resource show \ + -g $RESOURCE_GROUP \ + -n $AI_NAME \ + --resource-type "Microsoft.Insights/components" \ + --query properties.InstrumentationKey \ + -o tsv) + +# add RBAC for AppInsights +kubectl apply -f $K8S/k8s-rbac-ai.yaml +``` -# Create the ACR instance -az acr create --name $ACR_NAME --resource-group $RESOURCE_GROUP --sku Basic +## Setup AAD pod identity and key vault flexvol infrastructure -# Log in to ACR -az acr login --name $ACR_NAME +Complete instructions can be found at https://github.com/Azure/kubernetes-keyvault-flexvol -# Get the ACR login server name -export ACR_SERVER=$(az acr show -g $RESOURCE_GROUP -n $ACR_NAME --query "loginServer") +Note: the tested nmi version was 1.4. It enables namespaced pod identity. -# Strip quotes -export ACR_SERVER=("${ACR_SERVER[@]//\"/}") -``` +```bash +# setup AAD pod identity +helm repo add aad-pod-identity https://raw.githubusercontent.com/Azure/aad-pod-identity/master/charts && \ +helm install aad-pod-identity/aad-pod-identity -n kube-system -## Deploy the Delivery service +kubectl create -f https://raw.githubusercontent.com/Azure/kubernetes-keyvault-flexvol/master/deployment/kv-flexvol-installer.yaml +``` -Provision Azure resources +## Deploy the ingress controllers ```bash -export REDIS_NAME="${UNIQUE_APP_NAME_PREFIX}-delivery-service-redis" && \ -export COSMOSDB_NAME="${UNIQUE_APP_NAME_PREFIX}-delivery-service-cosmosdb" && \ -export DATABASE_NAME="${COSMOSDB_NAME}-db" && \ -export COLLECTION_NAME="${DATABASE_NAME}-col" - -# Create Azure Redis Cache -az redis create --location $LOCATION \ - --name $REDIS_NAME \ - --resource-group $RESOURCE_GROUP \ - --sku Premium \ - --vm-size P4 - -# Create Cosmos DB account with DocumentDB API -az cosmosdb create \ - --name $COSMOSDB_NAME \ - --kind GlobalDocumentDB \ - --resource-group $RESOURCE_GROUP \ - --max-interval 10 \ - --max-staleness-prefix 200 - -# Create a Cosmos DB database -az cosmosdb database create \ - --name $COSMOSDB_NAME \ - --db-name=$DATABASE_NAME \ - --resource-group $RESOURCE_GROUP - -# Create a Cosmos DB collection -az cosmosdb collection create \ - --collection-name $COLLECTION_NAME \ - --name $COSMOSDB_NAME \ - --db-name $DATABASE_NAME \ - --resource-group $RESOURCE_GROUP +# Deploy the AppGateway ingress controller +helm repo add application-gateway-kubernetes-ingress https://appgwingress.blob.core.windows.net/ingress-azure-helm-package/ +helm repo update + +export GATEWAY_CONTROLLER_PRINCIPAL_RESOURCE_ID=$(az group deployment show -g $RESOURCE_GROUP -n $IDENTITIES_DEPLOYMENT_NAME --query properties.outputs.appGatewayControllerPrincipalResourceId.value -o tsv) && \ +export GATEWAY_CONTROLLER_PRINCIPAL_CLIENT_ID=$(az identity show -g $RESOURCE_GROUP -n $GATEWAY_CONTROLLER_ID_NAME --query clientId -o tsv) +export APP_GATEWAY_NAME=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-dev --query properties.outputs.appGatewayName.value -o tsv) +export APP_GATEWAY_PUBLIC_IP_FQDN=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-dev --query properties.outputs.appGatewayPublicIpFqdn.value -o tsv) + +helm install ingress-azure-dev application-gateway-kubernetes-ingress/ingress-azure \ + --namespace kube-system \ + --set appgw.name=$APP_GATEWAY_NAME \ + --set appgw.resourceGroup=$RESOURCE_GROUP \ + --set appgw.subscriptionId=$SUBSCRIPTION_ID \ + --set appgw.shared=false \ + --set kubernetes.watchNamespace=backend-dev \ + --set armAuth.type=aadPodIdentity \ + --set armAuth.identityResourceID=$GATEWAY_CONTROLLER_PRINCIPAL_RESOURCE_ID \ + --set armAuth.identityClientID=$GATEWAY_CONTROLLER_PRINCIPAL_CLIENT_ID \ + --set rbac.enabled=true \ + --set verbosityLevel=3 \ + --set aksClusterConfiguration.apiServerAddress=$CLUSTER_SERVER \ + --set appgw.usePrivateIP=false + +# Create a self-signed certificate for TLS +export EXTERNAL_INGEST_FQDN=$APP_GATEWAY_PUBLIC_IP_FQDN +openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -out ingestion-ingress-tls.crt \ + -keyout ingestion-ingress-tls.key \ + -subj "/CN=${APP_GATEWAY_PUBLIC_IP_FQDN}/O=fabrikam" ``` -Build the Delivery service +## Setup cluster resource quota ```bash -export DELIVERY_PATH=./microservices-reference-implementation/src/bc-shipping/delivery -docker-compose -f $DELIVERY_PATH/docker-compose.ci.build.yml up +kubectl apply -f $K8S/k8s-resource-quotas-dev.yaml ``` -Build and publish the container image +## Deny all ingress and egress traffic ```bash -# Build the Docker image -docker build -t $ACR_SERVER/fabrikam.dronedelivery.deliveryservice:0.1.0 $DELIVERY_PATH/Fabrikam.DroneDelivery.DeliveryService/. +kubectl apply -f $K8S/k8s-deny-all-non-whitelisted-traffic-dev.yaml +``` -# Push the image to ACR -az acr login --name $ACR_NAME -docker push $ACR_SERVER/fabrikam.dronedelivery.deliveryservice:0.1.0 +## Deploy the Delivery service + +Extract resource details from deployment +```bash +export COSMOSDB_NAME=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-dev --query properties.outputs.deliveryCosmosDbName.value -o tsv) && \ +export DATABASE_NAME="${COSMOSDB_NAME}-db" && \ +export COLLECTION_NAME="${DATABASE_NAME}-col" && \ +export DELIVERY_KEYVAULT_URI=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-dev --query properties.outputs.deliveryKeyVaultUri.value -o tsv) ``` -Create Kubernetes secrets +Build the Delivery service ```bash -export REDIS_CONNECTION_STRING=[YOUR_REDIS_CONNECTION_STRING] +export DELIVERY_PATH=$PROJECT_ROOT/src/shipping/delivery +``` -export COSMOSDB_KEY=$(az cosmosdb list-keys --name $COSMOSDB_NAME --resource-group $RESOURCE_GROUP --query primaryMasterKey) && \ -export COSMOSDB_ENDPOINT=$(az cosmosdb show --name $COSMOSDB_NAME --resource-group $RESOURCE_GROUP --query documentEndpoint) +Build and publish the container image + +```bash +# Build the Docker image +docker build --pull --compress -t $ACR_SERVER/delivery:0.1.0 $DELIVERY_PATH/. -kubectl --namespace bc-shipping create --save-config=true secret generic delivery-storageconf \ - --from-literal=CosmosDB_Key=${COSMOSDB_KEY[@]//\"/} \ - --from-literal=CosmosDB_Endpoint=${COSMOSDB_ENDPOINT[@]//\"/} \ - --from-literal=Redis_ConnectionString=${REDIS_CONNECTION_STRING} \ - --from-literal=EH_ConnectionString= \ - --from-literal=Redis_SecondaryKey= +# Push the image to ACR +az acr login --name $ACR_NAME +docker push $ACR_SERVER/delivery:0.1.0 ``` Deploy the Delivery service: ```bash -# Update the image tag in the deployment YAML -sed -i "s#image:#image: $ACR_SERVER/fabrikam.dronedelivery.deliveryservice:0.1.0#g" ./microservices-reference-implementation/k8s/delivery.yaml - -## Update config values in the deployment YAML -sed -i "s/value: \"CosmosDB_DatabaseId\"/value: $DATABASE_NAME/g" "./microservices-reference-implementation/k8s/delivery.yaml" && \ -sed -i "s/value: \"CosmosDB_CollectionId\"/value: $COLLECTION_NAME/g" "./microservices-reference-implementation/k8s/delivery.yaml" && \ -sed -i "s/value: \"EH_EntityPath\"/value:/g" "./microservices-reference-implementation/k8s/delivery.yaml" +# Extract pod identity outputs from deployment +export DELIVERY_PRINCIPAL_RESOURCE_ID=$(az group deployment show -g $RESOURCE_GROUP -n $IDENTITIES_DEPLOYMENT_NAME --query properties.outputs.deliveryPrincipalResourceId.value -o tsv) && \ +export DELIVERY_PRINCIPAL_CLIENT_ID=$(az identity show -g $RESOURCE_GROUP -n $DELIVERY_ID_NAME --query clientId -o tsv) +export DELIVERY_INGRESS_TLS_SECRET_NAME=delivery-ingress-tls # Deploy the service -kubectl --namespace bc-shipping apply -f ./microservices-reference-implementation/k8s/delivery.yaml +helm package $HELM_CHARTS/delivery/ -u && \ +helm install delivery-v0.1.0-dev delivery-v0.1.0.tgz \ + --set image.tag=0.1.0 \ + --set image.repository=delivery \ + --set dockerregistry=$ACR_SERVER \ + --set ingress.hosts[0].name=$EXTERNAL_INGEST_FQDN \ + --set ingress.hosts[0].serviceName=delivery \ + --set ingress.hosts[0].tls=true \ + --set ingress.hosts[0].tlsSecretName=$DELIVERY_INGRESS_TLS_SECRET_NAME \ + --set ingress.tls.secrets[0].name=$DELIVERY_INGRESS_TLS_SECRET_NAME \ + --set ingress.tls.secrets[0].key="$(cat ingestion-ingress-tls.key)" \ + --set ingress.tls.secrets[0].certificate="$(cat ingestion-ingress-tls.crt)" \ + --set networkPolicy.egress.external.enabled=true \ + --set networkPolicy.egress.external.clusterSubnetPrefix=$CLUSTER_SUBNET_PREFIX \ + --set networkPolicy.ingress.externalSubnet.enabled=true \ + --set networkPolicy.ingress.externalSubnet.subnetPrefix=$GATEWAY_SUBNET_PREFIX \ + --set identity.clientid=$DELIVERY_PRINCIPAL_CLIENT_ID \ + --set identity.resourceid=$DELIVERY_PRINCIPAL_RESOURCE_ID \ + --set cosmosdb.id=$DATABASE_NAME \ + --set cosmosdb.collectionid=$COLLECTION_NAME \ + --set keyvault.uri=$DELIVERY_KEYVAULT_URI \ + --set reason="Initial deployment" \ + --set envs.dev=true \ + --namespace backend-dev + +# Verify the pod is created +helm status delivery-v0.1.0-dev --namespace backend-dev ``` ## Deploy the Package service -Provision Azure resources +Extract resource details from deployment ```bash -export COSMOSDB_NAME="${UNIQUE_APP_NAME_PREFIX}-package-service-cosmosdb" -az cosmosdb create --name $COSMOSDB_NAME --kind MongoDB --resource-group $RESOURCE_GROUP +export COSMOSDB_NAME=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-dev --query properties.outputs.packageMongoDbName.value -o tsv) ``` Build the Package service ```bash -export PACKAGE_PATH=microservices-reference-implementation/src/bc-shipping/package - -# Build the app -docker-compose -f $PACKAGE_PATH/build/docker-compose.ci.build.yml up +export PACKAGE_PATH=$PROJECT_ROOT/src/shipping/package # Build the docker image -sudo docker build -f $PACKAGE_PATH/build/prod.dockerfile -t $ACR_SERVER/package-service:0.1.0 $PACKAGE_PATH +docker build -f $PACKAGE_PATH/Dockerfile -t $ACR_SERVER/package:0.1.0 $PACKAGE_PATH # Push the docker image to ACR az acr login --name $ACR_NAME -docker push $ACR_SERVER/package-service:0.1.0 +docker push $ACR_SERVER/package:0.1.0 ``` Deploy the Package service ```bash -# Update deployment YAML with image tage -sed -i "s#image:#image: $ACR_SERVER/package-service:0.1.0#g" ./microservices-reference-implementation/k8s/package.yml - # Create secret -export COSMOSDB_CONNECTION=$(az cosmosdb list-connection-strings --name $COSMOSDB_NAME --resource-group $RESOURCE_GROUP --query "connectionStrings[0].connectionString") -kubectl -n bc-shipping create secret generic package-secrets --from-literal=mongodb-pwd=${COSMOSDB_CONNECTION[@]//\"/} +# Note: Connection strings cannot be exported as outputs in ARM deployments +export COSMOSDB_CONNECTION=$(az cosmosdb list-connection-strings --name $COSMOSDB_NAME --resource-group $RESOURCE_GROUP --query "connectionStrings[0].connectionString" -o tsv | sed 's/==/%3D%3D/g') && \ +export COSMOSDB_COL_NAME=packages # Deploy service -kubectl --namespace bc-shipping apply -f ./microservices-reference-implementation/k8s/package.yml +helm package $HELM_CHARTS/package/ -u && \ +helm install package-v0.1.0-dev package-v0.1.0.tgz \ + --set image.tag=0.1.0 \ + --set image.repository=package \ + --set ingress.hosts[0].name=$EXTERNAL_INGEST_FQDN \ + --set ingress.hosts[0].serviceName=package \ + --set ingress.hosts[0].tls=false \ + --set networkPolicy.egress.external.enabled=true \ + --set networkPolicy.egress.external.clusterSubnetPrefix=$CLUSTER_SUBNET_PREFIX \ + --set secrets.appinsights.ikey=$AI_IKEY \ + --set secrets.mongo.pwd=$COSMOSDB_CONNECTION \ + --set cosmosDb.collectionName=$COSMOSDB_COL_NAME \ + --set dockerregistry=$ACR_SERVER \ + --set reason="Initial deployment" \ + --set envs.dev=true \ + --namespace backend-dev + +# Verify the pod is created +helm status package-v0.1.0-dev --namespace backend-dev ``` -## Deploy the Ingestion service -Provision Azure resources +## Deploy the Workflow service -```bash -export INGESTION_EH_NS=[INGESTION_EVENT_HUB_NAMESPACE_HERE] -export INGESTION_EH_NAME=[INGESTION_EVENT_HUB_NAME_HERE] -export INGESTION_EH_CONSUMERGROUP_NAME=[INGESTION_EVENT_HUB_CONSUMERGROUP_NAME_HERE] +Extract resource details from deployment -wget https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/201-event-hubs-create-event-hub-and-consumer-group/azuredeploy.json && \ -sed -i 's#"partitionCount": "4"#"partitionCount": "32"#g' azuredeploy.json && \ -az group deployment create -g $RESOURCE_GROUP --template-file azuredeploy.json --parameters \ -'{ \ - "namespaceName": {"value": "'${INGESTION_EH_NS}'"}, \ - "eventHubName": {"value": "'${INGESTION_EH_NAME}'"}, \ - "consumerGroupName": {"value": "'${INGESTION_EH_CONSUMERGROUP_NAME}'"} \ -}' +```bash +export WORKFLOW_KEYVAULT_NAME=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-dev --query properties.outputs.workflowKeyVaultName.value -o tsv) ``` -Note: you could also create this from [the Azure Portal](https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-create) -Build the Ingestion service +Build the workflow service ```bash -export INGESTION_PATH=./microservices-reference-implementation/src/bc-shipping/ingestion +export WORKFLOW_PATH=$PROJECT_ROOT/src/shipping/workflow -# Build the app -docker build -t openjdk_and_mvn-build:8-jdk -f $INGESTION_PATH/Dockerfilemaven $INGESTION_PATH && \ -docker run -it --rm -v $( cd "${INGESTION_PATH}" && pwd )/:/sln openjdk_and_mvn-build:8-jdk - -# Build the docker image -docker build -f $INGESTION_PATH/Dockerfile -t $ACR_SERVER/ingestion:0.1.0 $INGESTION_PATH +# Build the Docker image +docker build --pull --compress -t $ACR_SERVER/workflow:0.1.0 $WORKFLOW_PATH/. -# Push the docker image to ACR +# Push the image to ACR az acr login --name $ACR_NAME -docker push $ACR_SERVER/ingestion:0.1.0 +docker push $ACR_SERVER/workflow:0.1.0 ``` -Deploy the Ingestion service +Create and set up pod identity ```bash -# Update deployment YAML with image tage -sed -i "s#image:#image: $ACR_SERVER/ingestion:0.1.0#g" ./microservices-reference-implementation/k8s/ingestion.yaml - -# Get the EventHub shared access policy name and key from the Azure Portal -export EH_ACCESS_KEY_NAME=[YOUR_SHARED_ACCESS_POLICY_NAME_HERE] -export EH_ACCESS_KEY_VALUE=[YOUR_SHARED_ACCESS_POLICY_VALUE_HERE] +# Extract outputs from deployment +export WORKFLOW_PRINCIPAL_RESOURCE_ID=$(az group deployment show -g $RESOURCE_GROUP -n $IDENTITIES_DEPLOYMENT_NAME --query properties.outputs.workflowPrincipalResourceId.value -o tsv) && \ +export WORKFLOW_PRINCIPAL_CLIENT_ID=$(az identity show -g $RESOURCE_GROUP -n $WORKFLOW_ID_NAME --query clientId -o tsv) +``` -# Create secret -kubectl -n bc-shipping create secret generic ingestion-secrets --from-literal=eventhub_namespace=${INGESTION_EH_NS} \ ---from-literal=eventhub_name=${INGESTION_EH_NAME} \ ---from-literal=eventhub_keyname=${EH_ACCESS_KEY_NAME} \ ---from-literal=eventhub_keyvalue=${EH_ACCESS_KEY_VALUE} +Deploy the Workflow service: -# Deploy service -kubectl --namespace bc-shipping apply -f ./microservices-reference-implementation/k8s/ingestion.yaml +```bash +# Deploy the service +helm package $HELM_CHARTS/workflow/ -u && \ +helm install workflow-v0.1.0-dev workflow-v0.1.0.tgz \ + --set image.tag=0.1.0 \ + --set image.repository=workflow \ + --set dockerregistry=$ACR_SERVER \ + --set identity.clientid=$WORKFLOW_PRINCIPAL_CLIENT_ID \ + --set identity.resourceid=$WORKFLOW_PRINCIPAL_RESOURCE_ID \ + --set networkPolicy.egress.external.enabled=true \ + --set networkPolicy.egress.external.clusterSubnetPrefix=$CLUSTER_SUBNET_PREFIX \ + --set keyvault.name=$WORKFLOW_KEYVAULT_NAME \ + --set keyvault.resourcegroup=$RESOURCE_GROUP \ + --set keyvault.subscriptionid=$SUBSCRIPTION_ID \ + --set keyvault.tenantid=$TENANT_ID \ + --set reason="Initial deployment" \ + --set envs.dev=true \ + --namespace backend-dev + +# Verify the pod is created +helm status workflow-v0.1.0-dev --namespace backend-dev ``` -## Deploy the Scheduler service +## Deploy the Ingestion service -Provision Azure resources -```bash -export SCHEDULER_STORAGE_ACCOUNT_NAME=[SCHEDULER_STORAGE_ACCOUNT_NAME_HERE] +Extract resource details from deployment -az storage account create --resource-group $RESOURCE_GROUP --name $SCHEDULER_STORAGE_ACCOUNT_NAME --sku Standard_LRS +```bash +export INGESTION_QUEUE_NAMESPACE=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-dev --query properties.outputs.ingestionQueueNamespace.value -o tsv) && \ +export INGESTION_QUEUE_NAME=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-dev --query properties.outputs.ingestionQueueName.value -o tsv) +export INGESTION_ACCESS_KEY_NAME=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-dev --query properties.outputs.ingestionServiceAccessKeyName.value -o tsv) +export INGESTION_ACCESS_KEY_VALUE=$(az servicebus namespace authorization-rule keys list --resource-group $RESOURCE_GROUP --namespace-name $INGESTION_QUEUE_NAMESPACE --name $INGESTION_ACCESS_KEY_NAME --query primaryKey -o tsv) ``` -Build the Scheduler service +Build the Ingestion service ```bash -export SCHEDULER_PATH=./microservices-reference-implementation/src/bc-shipping/scheduler - -# Build the app -docker build -t openjdk_and_mvn-build:8-jdk -f $SCHEDULER_PATH/Dockerfilemaven $SCHEDULER_PATH && \ -docker run -it --rm -v $( cd "${SCHEDULER_PATH}" && pwd )/:/sln openjdk_and_mvn-build:8-jdk +export INGESTION_PATH=$PROJECT_ROOT/src/shipping/ingestion # Build the docker image -docker build -f $SCHEDULER_PATH/Dockerfile -t $ACR_SERVER/scheduler:0.1.0 $SCHEDULER_PATH +docker build -f $INGESTION_PATH/Dockerfile -t $ACR_SERVER/ingestion:0.1.0 $INGESTION_PATH # Push the docker image to ACR az acr login --name $ACR_NAME -docker push $ACR_SERVER/scheduler:0.1.0 +docker push $ACR_SERVER/ingestion:0.1.0 ``` -Deploy the Scheduler service +Deploy the Ingestion service ```bash -# Update deployment YAML with image tage -sed -i "s#image:#image: $ACR_SERVER/scheduler:0.1.0#g" ./microservices-reference-implementation/k8s/scheduler.yaml +# Set secreat name +export INGRESS_TLS_SECRET_NAME=ingestion-ingress-tls -# Get the following values from the Azure Portal -export EH_CONNECTION_STRING="[YOUR_EVENT_HUB_CONNECTION_STRING_HERE]" -export STORAGE_ACCOUNT_ACCESS_KEY=[YOUR_STORAGE_ACCOUNT_ACCESS_KEY_HERE] -export STORAGE_ACCOUNT_CONNECTION_STRING="[YOUR_STORAGE_ACCOUNT_CONNECTION_STRING_HERE]" +# Deploy service +helm package $HELM_CHARTS/ingestion/ -u && \ +helm install ingestion-v0.1.0-dev ingestion-v0.1.0.tgz \ + --set image.tag=0.1.0 \ + --set image.repository=ingestion \ + --set dockerregistry=$ACR_SERVER \ + --set ingress.hosts[0].name=$EXTERNAL_INGEST_FQDN \ + --set ingress.hosts[0].serviceName=ingestion \ + --set ingress.hosts[0].tls=true \ + --set ingress.hosts[0].tlsSecretName=$INGRESS_TLS_SECRET_NAME \ + --set ingress.tls.secrets[0].name=$INGRESS_TLS_SECRET_NAME \ + --set ingress.tls.secrets[0].key="$(cat ingestion-ingress-tls.key)" \ + --set ingress.tls.secrets[0].certificate="$(cat ingestion-ingress-tls.crt)" \ + --set networkPolicy.egress.external.enabled=true \ + --set networkPolicy.egress.external.clusterSubnetPrefix=$CLUSTER_SUBNET_PREFIX \ + --set networkPolicy.ingress.externalSubnet.enabled=true \ + --set networkPolicy.ingress.externalSubnet.subnetPrefix=$GATEWAY_SUBNET_PREFIX \ + --set secrets.appinsights.ikey=${AI_IKEY} \ + --set secrets.queue.keyname=IngestionServiceAccessKey \ + --set secrets.queue.keyvalue=${INGESTION_ACCESS_KEY_VALUE} \ + --set secrets.queue.name=${INGESTION_QUEUE_NAME} \ + --set secrets.queue.namespace=${INGESTION_QUEUE_NAMESPACE} \ + --set reason="Initial deployment" \ + --set envs.dev=true \ + --namespace backend-dev + +# Verify the pod is created +helm status ingestion-v0.1.0-dev --namespace backend-dev +``` -# Create secrets -kubectl -n bc-shipping create secret generic scheduler-secrets --from-literal=eventhub_name=${INGESTION_EH_NAME} \ ---from-literal=eventhub_sas_connection_string=${EH_CONNECTION_STRING} \ ---from-literal=storageaccount_name=${SCHEDULER_STORAGE_ACCOUNT_NAME} \ ---from-literal=storageaccount_key=${STORAGE_ACCOUNT_ACCESS_KEY} \ ---from-literal=queueconstring=${STORAGE_ACCOUNT_CONNECTION_STRING} +## Deploy DroneScheduler service -# Deploy service -kubectl --namespace bc-shipping apply -f ./microservices-reference-implementation/k8s/scheduler.yaml +Extract resource details from deployment + +```bash +export DRONESCHEDULER_KEYVAULT_URI=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-dev --query properties.outputs.droneSchedulerKeyVaultUri.value -o tsv) +export DRONESCHEDULER_COSMOSDB_NAME=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-dev --query properties.outputs.droneSchedulerCosmosDbName.value -o tsv) && \ +export ENDPOINT_URL=$(az cosmosdb show -n $DRONESCHEDULER_COSMOSDB_NAME -g $RESOURCE_GROUP --query documentEndpoint -o tsv) && \ +export AUTH_KEY=$(az cosmosdb list-keys -n $DRONESCHEDULER_COSMOSDB_NAME -g $RESOURCE_GROUP --query primaryMasterKey -o tsv) && \ +export DATABASE_NAME="invoicing" && \ +export COLLECTION_NAME="utilization" ``` -## Deploy mock services +Build the dronescheduler services -Build the mock services +```bash +export DRONE_PATH=$PROJECT_ROOT/src/shipping/dronescheduler +``` + +Create and set up pod identity ```bash -export MOCKS_PATH=microservices-reference-implementation/src/bc-shipping/delivery -docker-compose -f $MOCKS_PATH/docker-compose.ci.build.yml up +# Extract outputs from deployment +export DRONESCHEDULER_PRINCIPAL_RESOURCE_ID=$(az group deployment show -g $RESOURCE_GROUP -n $IDENTITIES_DEPLOYMENT_NAME --query properties.outputs.droneSchedulerPrincipalResourceId.value -o tsv) && \ +export DRONESCHEDULER_PRINCIPAL_CLIENT_ID=$(az identity show -g $RESOURCE_GROUP -n $DRONESCHEDULER_ID_NAME --query clientId -o tsv) ``` -Build and publish the container image +Build and publish the container image ```bash # Build the Docker image -docker build -t $ACR_SERVER/account:0.1.0 $MOCKS_PATH/MockAccountService/. && \ -docker build -t $ACR_SERVER/dronescheduler:0.1.0 $MOCKS_PATH/MockDroneScheduler/. && \ -docker build -t $ACR_SERVER/thirdparty:0.1.0 $MOCKS_PATH/MockThirdPartyService/. +docker build -f $DRONE_PATH/Dockerfile -t $ACR_SERVER/dronescheduler:0.1.0 $DRONE_PATH/../ -# Push the image to ACR +# Push the images to ACR az acr login --name $ACR_NAME -docker push $ACR_SERVER/account:0.1.0 && \ -docker push $ACR_SERVER/dronescheduler:0.1.0 && \ -docker push $ACR_SERVER/thirdparty:0.1.0 +docker push $ACR_SERVER/dronescheduler:0.1.0 ``` -Deploy the mock services: - +Deploy the dronescheduler service: ```bash -# Update the image tag in the deployment YAML -sed -i "s#image:#image: $ACR_SERVER/account:0.1.0#g" ./microservices-reference-implementation/k8s/account.yaml && \ -sed -i "s#image:#image: $ACR_SERVER/dronescheduler:0.1.0#g" ./microservices-reference-implementation/k8s/dronescheduler.yaml && \ -sed -i "s#image:#image: $ACR_SERVER/thirdparty:0.1.0#g" ./microservices-reference-implementation/k8s/thirdparty.yaml - # Deploy the service -kubectl --namespace bc-shipping apply -f ./microservices-reference-implementation/k8s/account.yaml && \ -kubectl --namespace bc-shipping apply -f ./microservices-reference-implementation/k8s/dronescheduler.yaml && \ -kubectl --namespace bc-shipping apply -f ./microservices-reference-implementation/k8s/thirdparty.yaml +helm package $HELM_CHARTS/dronescheduler/ -u && \ +helm install dronescheduler-v0.1.0-dev dronescheduler-v0.1.0.tgz \ + --set image.tag=0.1.0 \ + --set image.repository=dronescheduler \ + --set dockerregistry=$ACR_SERVER \ + --set ingress.hosts[0].name=$EXTERNAL_INGEST_FQDN \ + --set ingress.hosts[0].serviceName=dronescheduler \ + --set ingress.hosts[0].tls=false \ + --set identity.clientid=$DRONESCHEDULER_PRINCIPAL_CLIENT_ID \ + --set identity.resourceid=$DRONESCHEDULER_PRINCIPAL_RESOURCE_ID \ + --set networkPolicy.egress.external.enabled=true \ + --set networkPolicy.egress.external.clusterSubnetPrefix=$CLUSTER_SUBNET_PREFIX \ + --set keyvault.uri=$DRONESCHEDULER_KEYVAULT_URI \ + --set cosmosdb.id=$DATABASE_NAME \ + --set cosmosdb.collectionid=$COLLECTION_NAME \ + --set reason="Initial deployment" \ + --set envs.dev=true \ + --namespace backend-dev + +# Verify the pod is created +helm status dronescheduler-v0.1.0-dev --namespace backend-dev ``` -## Verify all services are running: +## Validate the application is running + +You can send delivery requests and check their statuses using curl. + +### Send a request + +Since the certificate used for TLS is self-signed, the request disables TLS validation using the '-k' option. ```bash -kubectl get all -n bc-shipping +curl -X POST "https://$EXTERNAL_INGEST_FQDN/api/deliveryrequests" --header 'Content-Type: application/json' --header 'Accept: application/json' -k -d '{ + "confirmationRequired": "None", + "deadline": "", + "dropOffLocation": "drop off", + "expedited": true, + "ownerId": "myowner", + "packageInfo": { + "packageId": "mypackage", + "size": "Small", + "tag": "mytag", + "weight": 10 + }, + "pickupLocation": "my pickup", + "pickupTime": "2019-05-08T20:00:00.000Z" + }' > deliveryresponse.json ``` +### Check the request status +```bash +DELIVERY_ID=$(cat deliveryresponse.json | jq -r .deliveryId) +curl "https://$EXTERNAL_INGEST_FQDN/api/deliveries/$DELIVERY_ID" --header 'Accept: application/json' -k +``` + +## Optional steps + +### Load Test the application + +To run load testing against the solution, follow the steps listed [here](./src/loadtests/readme.md). + +### Fluentd and Elastic Search + +Follow these steps to add logging and monitoring capabilities to the solution. + Deploy Elasticsearch. For more information, see https://github.com/kubernetes/examples/tree/master/staging/elasticsearch -Deploy Fluend. For more information, see https://docs.fluentd.org/v0.12/articles/kubernetes-fluentd +```bash +kubectl --namespace kube-system apply -f https://raw.githubusercontent.com/kubernetes/examples/master/staging/elasticsearch/service-account.yaml && \ +kubectl --namespace kube-system apply -f https://raw.githubusercontent.com/kubernetes/examples/master/staging/elasticsearch/es-svc.yaml && \ +kubectl --namespace kube-system apply -f https://raw.githubusercontent.com/kubernetes/examples/master/staging/elasticsearch/es-rc.yaml +``` -Deploy linkerd. For more information, see https://linkerd.io/getting-started/k8s/ +Deploy Fluentd. For more information, see https://docs.fluentd.org/v0.12/articles/kubernetes-fluentd -Deploy Prometheus and Grafana. For more information, see https://github.com/linkerd/linkerd-viz#kubernetes-deploy +```bash +# The example elasticsearch yaml files deploy a service named "elasticsearch" +wget https://raw.githubusercontent.com/fluent/fluentd-kubernetes-daemonset/master/fluentd-daemonset-elasticsearch.yaml && \ +sed -i "s/elasticsearch-logging/elasticsearch/" fluentd-daemonset-elasticsearch.yaml + +# Commenting out X-Pack credentials for demo purposes. +# Make sure to configure X-Pack in elasticsearch and provide credentials here for production workloads +sed -i "s/- name: FLUENT_ELASTICSEARCH_USER/#- name: FLUENT_ELASTICSEARCH_USER/" fluentd-daemonset-elasticsearch.yaml && \ +sed -i 's/ value: "elastic"/# value: "elastic"/' fluentd-daemonset-elasticsearch.yaml && \ +sed -i "s/- name: FLUENT_ELASTICSEARCH_PASSWORD/#- name: FLUENT_ELASTICSEARCH_PASSWORD/" fluentd-daemonset-elasticsearch.yaml && \ +sed -i 's/ value: "changeme"/# value: "changeme"/' fluentd-daemonset-elasticsearch.yaml && \ +kubectl --namespace kube-system apply -f fluentd-daemonset-elasticsearch.yaml +``` -It is recommended to put an API Gateway in front of all APIs you want exposed to the public, -however for convenience, we exposed the Ingestion service with a public IP address. +## Clean up -You can send delivery requests to the ingestion service using the swagger ui. +Follow the steps below to remove the Fabrikam Drone Delivery app from your cluster ```bash -export INGESTION_SERVICE_EXTERNAL_IP_ADDRESS=$(kubectl get --namespace bc-shipping svc ingestion -o jsonpath="{.status.loadBalancer.ingress[0].*}") -curl "http://${INGESTION_SERVICE_EXTERNAL_IP_ADDRESS}"/swagger-ui.html#/ingestion45controller/scheduleDeliveryAsyncUsingPOST -``` \ No newline at end of file +helm uninstall $(helm ls --all --short -n ingress-controllers) -n ingress-controllers && \ +helm uninstall $(helm ls --all --short -n backend-dev) -n backend-dev + +# if you've choosen the CI/CD path +helm uninstall $(helm ls --all --short -n backend-qa) -n backend-qa && \ +helm uninstall $(helm ls --all --short -n backend-staging) -n backend-staging && \ +helm uninstall $(helm ls --all --short -n backend-prod) -n backend-prod +``` diff --git a/deploymentCICD.md b/deploymentCICD.md new file mode 100644 index 00000000..57f272f6 --- /dev/null +++ b/deploymentCICD.md @@ -0,0 +1,816 @@ +# Setup Reference Implementation CI/CD with Azure DevOps + +## Prerequisites + +- Azure subscription +- [Azure CLI 2.0.49 or later](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) +- [Azure DevOps account](https://azure.microsoft.com/services/devops) +- [Values from deployment instructions](./deployment.md) + +## Infrastructure for dev, test, staging and production + +```bash +# Export the kubernetes cluster version and deploy +export KUBERNETES_VERSION=$(az aks get-versions -l $LOCATION --query "orchestrators[?default!=null].orchestratorVersion" -o tsv) && \ +export SERVICETAGS_LOCATION=$(az account list-locations --query "[?name=='${LOCATION}'].displayName" -o tsv | sed 's/[[:space:]]//g') +for env in dev qa staging prod; do +ENV=${env^^} +az deployment create \ + --name azuredeploy-prereqs-${env} \ + --location $LOCATION \ + --template-file ${PROJECT_ROOT}/azuredeploy-prereqs.json \ + --parameters resourceGroupName=$RESOURCE_GROUP \ + resourceGroupLocation=$LOCATION \ + environmentName=${env} + +export {${ENV}_IDENTITIES_DEPLOYMENT_NAME,IDENTITIES_DEPLOYMENT_NAME}=$(az deployment show -n azuredeploy-prereqs-${env} --query properties.outputs.identitiesDeploymentName.value -o tsv) && \ +export {${ENV}_DELIVERY_ID_NAME,DELIVERY_ID_NAME}=$(az group deployment show -g $RESOURCE_GROUP -n $IDENTITIES_DEPLOYMENT_NAME --query properties.outputs.deliveryIdName.value -o tsv) +export DELIVERY_ID_PRINCIPAL_ID=$(az identity show -g $RESOURCE_GROUP -n $DELIVERY_ID_NAME --query principalId -o tsv) +export {${ENV}_DRONESCHEDULER_ID_NAME,DRONESCHEDULER_ID_NAME}=$(az group deployment show -g $RESOURCE_GROUP -n $IDENTITIES_DEPLOYMENT_NAME --query properties.outputs.droneSchedulerIdName.value -o tsv) +export DRONESCHEDULER_ID_PRINCIPAL_ID=$(az identity show -g $RESOURCE_GROUP -n $DRONESCHEDULER_ID_NAME --query principalId -o tsv) +export {${ENV}_WORKFLOW_ID_NAME,WORKFLOW_ID_NAME}=$(az group deployment show -g $RESOURCE_GROUP -n $IDENTITIES_DEPLOYMENT_NAME --query properties.outputs.workflowIdName.value -o tsv) +export WORKFLOW_ID_PRINCIPAL_ID=$(az identity show -g $RESOURCE_GROUP -n $WORKFLOW_ID_NAME --query principalId -o tsv) +export {${ENV}_GATEWAY_CONTROLLER_ID_NAME,GATEWAY_CONTROLLER_ID_NAME}=$(az group deployment show -g $RESOURCE_GROUP -n $IDENTITIES_DEPLOYMENT_NAME --query properties.outputs.appGatewayControllerIdName.value -o tsv) && \ +export GATEWAY_CONTROLLER_ID_PRINCIPAL_ID=$(az identity show -g $RESOURCE_GROUP -n $GATEWAY_CONTROLLER_ID_NAME --query principalId -o tsv) && \ +export RESOURCE_GROUP_ACR=$(az group deployment show -g $RESOURCE_GROUP -n $IDENTITIES_DEPLOYMENT_NAME --query properties.outputs.acrResourceGroupName.value -o tsv) + +# Wait for AAD propagation +until az ad sp show --id $DELIVERY_ID_PRINCIPAL_ID &> /dev/null ; do echo "Waiting for AAD propagation" && sleep 5; done +until az ad sp show --id $DRONESCHEDULER_ID_PRINCIPAL_ID &> /dev/null ; do echo "Waiting for AAD propagation" && sleep 5; done +until az ad sp show --id $WORKFLOW_ID_PRINCIPAL_ID &> /dev/null ; do echo "Waiting for AAD propagation" && sleep 5; done +until az ad sp show --id $GATEWAY_CONTROLLER_ID_PRINCIPAL_ID &> /dev/null ; do echo "Waiting for AAD propagation" && sleep 5; done + +az group deployment create -g $RESOURCE_GROUP --name azuredeploy-${env} --template-file ${PROJECT_ROOT}/azuredeploy.json \ + --parameters servicePrincipalClientId=${SP_APP_ID} \ + servicePrincipalClientSecret=${SP_CLIENT_SECRET} \ + servicePrincipalId=${SP_OBJECT_ID} \ + kubernetesVersion=${KUBERNETES_VERSION} \ + sshRSAPublicKey="$(cat ${SSH_PUBLIC_KEY_FILE})" \ + deliveryIdName="$DELIVERY_ID_NAME" \ + deliveryPrincipalId=$DELIVERY_ID_PRINCIPAL_ID \ + droneSchedulerIdName=$DRONESCHEDULER_ID_NAME \ + droneSchedulerPrincipalId=$DRONESCHEDULER_ID_PRINCIPAL_ID \ + workflowIdName=$WORKFLOW_ID_NAME \ + appGatewayControllerIdName=${GATEWAY_CONTROLLER_ID_NAME} \ + appGatewayControllerPrincipalId=${GATEWAY_CONTROLLER_ID_PRINCIPAL_ID} \ + workflowPrincipalId=$WORKFLOW_ID_PRINCIPAL_ID \ + acrResourceGroupName=${RESOURCE_GROUP_ACR} \ + acrResourceGroupLocation=$LOCATION \ + environmentName=${env} + +export {${ENV}_AI_NAME,AI_NAME}=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-${env} --query properties.outputs.appInsightsName.value -o tsv) +export ${ENV}_AI_IKEY=$(az resource show -g $RESOURCE_GROUP -n $AI_NAME --resource-type "Microsoft.Insights/components" --query properties.InstrumentationKey -o tsv) +export {${ENV}_ACR_NAME,ACR_NAME}=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-${env} --query properties.outputs.acrName.value -o tsv) +export ${ENV}_GATEWAY_SUBNET_PREFIX=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-${env} --query properties.outputs.appGatewaySubnetPrefix.value -o tsv) +export {${ENV}_VNET_NAME,VNET_NAME}=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-${env} --query properties.outputs.aksVNetName.value -o tsv) +export {${ENV}_CLUSTER_SUBNET_NAME,CLUSTER_SUBNET_NAME}=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-${env} --query properties.outputs.aksClusterSubnetName.value -o tsv) +export {${ENV}_CLUSTER_SUBNET_PREFIX,CLUSTER_SUBNET_PREFIX}=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-${env} --query properties.outputs.aksClusterSubnetPrefix.value -o tsv) +export {${ENV}_CLUSTER_FQDN,CLUSTER_FQDN}=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-${env} --query properties.outputs.aksFqdn.value -o tsv) +export {${ENV}_CLUSTER_NAME,CLUSTER_NAME}=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-${env} --query properties.outputs.aksClusterName.value -o tsv) && \ +export {${ENV}_CLUSTER_SERVER,CLUSTER_SERVER}=$(az aks show -n $CLUSTER_NAME -g $RESOURCE_GROUP --query fqdn -o tsv) +export CLUSTER_SERVERS=${CLUSTER_SERVERS}\'${CLUSTER_SERVER}\', +export {${ENV}_ACR_SERVER,ACR_SERVER}=$(az acr show -n $ACR_NAME --query loginServer -o tsv) +export ACR_SERVERS=${ACR_SERVERS}\'${ACR_SERVER}\', +export DELIVERY_REDIS_HOSTNAME=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-${env} --query properties.outputs.deliveryRedisHostName.value -o tsv) +export DELIVERY_REDIS_HOSTNAMES=${DELIVERY_REDIS_HOSTNAMES}\'${DELIVERY_REDIS_HOSTNAME}\', +done + +# Restrict cluster egress traffic +export FIREWALL_PIP_NAME=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-${env} --query properties.outputs.firewallPublicIpName.value -o tsv) && \ +az group deployment create -g $RESOURCE_GROUP --name azuredeploy-firewall --template-file ${PROJECT_ROOT}/azuredeploy-firewall.json \ +--parameters aksVnetName=${VNET_NAME} \ + aksClusterSubnetName=${CLUSTER_SUBNET_NAME} \ + aksClusterSubnetPrefix=${CLUSTER_SUBNET_PREFIX} \ + firewallPublicIpName=${FIREWALL_PIP_NAME} \ + serviceTagsLocation=${SERVICETAGS_LOCATION} \ + aksFqdns="[${CLUSTER_SERVERS%?}]" \ + acrServers="[${ACR_SERVERS%?}]" \ + deliveryRedisHostNames="[${DELIVERY_REDIS_HOSTNAMES%?}]" +``` + +Download kubectl and create a k8s namespace +```bash +# Install kubectl +sudo az aks install-cli + +# Get the Kubernetes cluster credentials +az aks get-credentials --resource-group=$RESOURCE_GROUP --name=$CLUSTER_NAME + +# Create namespaces +kubectl create namespace backend-dev && \ +kubectl create namespace backend-qa && \ +kubectl create namespace backend-staging && \ +kubectl create namespace backend +``` + +Setup Helm + +```bash +# install helm CLI +curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash +``` + +## Integrate Application Insights instance + +```bash +# add RBAC for AppInsights +kubectl apply -f $K8S/k8s-rbac-ai.yaml +``` + +## Setup AAD pod identity and key vault flexvol infrastructure + +Complete instructions can be found at https://github.com/Azure/kubernetes-keyvault-flexvol + +Note: the tested nmi version was 1.4. It enables namespaced pod identity. + +```bash +# setup AAD pod identity +helm repo add aad-pod-identity https://raw.githubusercontent.com/Azure/aad-pod-identity/master/chart && \ +helm install aad-pod-identity/aad-pod-identity -n kube-system + +kubectl create -f https://raw.githubusercontent.com/Azure/kubernetes-keyvault-flexvol/master/deployment/kv-flexvol-installer.yaml +``` + +## Setup cluster resource quota + +```bash +kubectl apply -f $K8S/k8s-resource-quotas-dev.yaml -f $K8S/k8s-resource-quotas-qa-stg-prod.yaml +``` + +## Deny all ingress and egress traffic + +```bash +kubectl apply -f $K8S/k8s-deny-all-non-whitelisted-traffic-dev.yaml -f $K8S/k8s-deny-all-non-whitelisted-traffic-qa-stg-prod.yaml +``` + +## Setup Azure DevOps + +``` +# add extensions +az extension add --name azure-devops + +# export +AZURE_DEVOPS_ORG_NAME= +AZURE_DEVOPS_ORG=https://dev.azure.com/$AZURE_DEVOPS_ORG_NAME +AZURE_DEVOPS_VSRM_ORG=https://vsrm.dev.azure.com/$AZURE_DEVOPS_ORG_NAME +AZURE_DEVOPS_PROJECT_NAME= +AZURE_DEVOPS_REPOS_NAME= +AZURE_PIPELINES_SERVICE_CONN_NAME=default_cicd_service-connection + +# create project or skip this step if you are using an existent Azure DevOps project +az devops project create \ + --name $AZURE_DEVOPS_PROJECT_NAME \ + --organization $AZURE_DEVOPS_ORG + +# create repo +az repos create \ + --name $AZURE_DEVOPS_REPOS_NAME \ + --organization $AZURE_DEVOPS_ORG \ + --project $AZURE_DEVOPS_PROJECT_NAME + +# create service principal for Azure Pipelines +az ad sp create-for-rbac + +# create Service Connection +az devops service-endpoint create \ + --service-endpoint-type azurerm \ + --name $AZURE_PIPELINES_SERVICE_CONN_NAME \ + --authorization-scheme ServicePrincipal \ + --azure-rm-tenant-id $TENANT_ID \ + --azure-rm-subscription-id $SUBSCRIPTION_ID \ + --azure-rm-subscription-name "$SUBSCRIPTION_NAME" \ + --organization $AZURE_DEVOPS_ORG \ + --project $AZURE_DEVOPS_PROJECT_NAME \ + --azure-rm-service-principal-id --azure-rm-service-principal-key + +# navigate to the repo and add ssh following links below or just skip this step for https +open $AZURE_DEVOPS_ORG/$AZURE_DEVOPS_PROJECT_NAME/_git/$AZURE_DEVOPS_REPOS_NAME +``` + +> Follow instructions at [Use SSH Key authentication](https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate?view=azure-devops) to add ssh. +For more information on the different authentication types, please take a look at [Authentication comparison](https://docs.microsoft.com/en-us/azure/devops/repos/git/auth-overview?view=azure-devops#authentication-comparison) + +![](https://docs.microsoft.com/en-us/azure/devops/repos/git/_img/ssh_add_public_key.gif?view=azure-devops) + +## Add new remote for the new Azure Repo +``` +# get the ssh url. For https just replace sshUrl with remoteUrl below +export NEW_REMOTE=$(az repos show -r $AZURE_DEVOPS_REPOS_NAME --organization $AZURE_DEVOPS_ORG --project $AZURE_DEVOPS_PROJECT_NAME --query sshUrl -o tsv) + +# push master from cloned repo to the new remote +cd && \ +git remote add newremote $NEW_REMOTE +``` + +## Obtain Azure DevOps resources + +Extract details from devops, repos and projects + +```bash +# navigate to the organization tokens and create a new Personal Access Token +open $AZURE_DEVOPS_ORG/_usersSettings/tokens + +# export token for making REST API calls +export AZURE_DEVEOPS_USER= +export AZURE_DEVOPS_PAT= +export AZURE_DEVOPS_AUTHN_BASIC_TOKEN=$(echo -n ${AZURE_DEVOPS_USER}:${AZURE_DEVOPS_PAT} | base64 | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n//g') + +export AZURE_DEVOPS_SERVICE_CONN_ID=$(az devops service-endpoint list --organization $AZURE_DEVOPS_ORG --project $AZURE_DEVOPS_PROJECT_NAME --query "[?name=='${AZURE_PIPELINES_SERVICE_CONN_NAME}'].id" -o tsv) && \ +export AZURE_DEVOPS_REPOS_ID=$(az repos show --organization $AZURE_DEVOPS_ORG --project $AZURE_DEVOPS_PROJECT_NAME --repository $AZURE_DEVOPS_REPOS_NAME --query id -o tsv) && \ +export AZURE_DEVOPS_PROJECT_ID=$(az devops project show --organization $AZURE_DEVOPS_ORG --project $AZURE_DEVOPS_PROJECT_NAME --query id -o tsv) && \ +export AZURE_DEVOPS_USER_ID=$(az devops user show --user ${AZURE_DEVEOPS_USER} --organization ${AZURE_DEVOPS_ORG} --query id -o tsv) +``` + +### Build pipelines pre-requisites + +```bash +# Deploy the AppGateway ingress controller +helm repo add application-gateway-kubernetes-ingress https://appgwingress.blob.core.windows.net/ingress-azure-helm-package/ +helm repo update + +for env in dev qa staging prod;do +ENV=${env^^} + +export IDENTITIES_DEPLOYMENT_NAME_VARIABLE=${ENV}_IDENTITIES_DEPLOYMENT_NAME +export GATEWAY_CONTROLLER_PRINCIPAL_RESOURCE_ID=$(az group deployment show -g $RESOURCE_GROUP -n ${!IDENTITIES_DEPLOYMENT_NAME_VARIABLE} --query properties.outputs.appGatewayControllerPrincipalResourceId.value -o tsv) && \ +export GATEWAY_CONTROLLER_ID_NAME=$(az group deployment show -g $RESOURCE_GROUP -n ${!IDENTITIES_DEPLOYMENT_NAME_VARIABLE} --query properties.outputs.appGatewayControllerIdName.value -o tsv) && \ +export GATEWAY_CONTROLLER_PRINCIPAL_CLIENT_ID=$(az identity show -g $RESOURCE_GROUP -n $GATEWAY_CONTROLLER_ID_NAME --query clientId -o tsv) + +# Deploy the App Gateway ingress controller +export APP_GATEWAY_NAME=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-${env} --query properties.outputs.appGatewayName.value -o tsv) +export {${ENV}_APP_GATEWAY_PUBLIC_IP_FQDN,APP_GATEWAY_PUBLIC_IP_FQDN}=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-${env} --query properties.outputs.appGatewayPublicIpFqdn.value -o tsv) +export ENV_NAMESPACE=$([ $env == 'prod' ] && echo 'backend' || echo "backend-$env") + +helm install ingress-azure-${env} application-gateway-kubernetes-ingress/ingress-azure \ + --namespace kube-system \ + --set appgw.name=$APP_GATEWAY_NAME \ + --set appgw.resourceGroup=$RESOURCE_GROUP \ + --set appgw.subscriptionId=$SUBSCRIPTION_ID \ + --set appgw.shared=false \ + --set kubernetes.watchNamespace=$ENV_NAMESPACE \ + --set armAuth.type=aadPodIdentity \ + --set armAuth.identityResourceID=$GATEWAY_CONTROLLER_PRINCIPAL_RESOURCE_ID \ + --set armAuth.identityClientID=$GATEWAY_CONTROLLER_PRINCIPAL_CLIENT_ID \ + --set rbac.enabled=true \ + --set verbosityLevel=3 \ + --set aksClusterConfiguration.apiServerAddress=$CLUSTER_SERVER \ + --set appgw.usePrivateIP=false + +# Create a self-signed certificate for TLS for the environment +export {${ENV}_EXTERNAL_INGEST_FQDN,EXTERNAL_INGEST_FQDN}=$APP_GATEWAY_PUBLIC_IP_FQDN +openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -out ingestion-ingress-tls-${env}.crt \ + -keyout ingestion-ingress-tls-${env}.key \ + -subj "/CN=${EXTERNAL_INGEST_FQDN}/O=fabrikam" +export "${ENV}_INGRESS_TLS_SECRET_CERT=$(echo $(cat ingestion-ingress-tls-${env}.crt) | tr '\n' "\\n")" +export "${ENV}_INGRESS_TLS_SECRET_KEY=$(echo $(cat ingestion-ingress-tls-${env}.key) | tr '\n' "\\n")" + +done + +# export app paths +export DELIVERY_PATH=$PROJECT_ROOT/src/shipping/delivery && \ +export PACKAGE_PATH=$PROJECT_ROOT/src/shipping/package && \ +export WORKFLOW_PATH=$PROJECT_ROOT/src/shipping/workflow && \ +export INGESTION_PATH=$PROJECT_ROOT/src/shipping/ingestion && \ +export DRONE_PATH=$PROJECT_ROOT/src/shipping/dronescheduler + +# configure build YAML definitions +for pipelinePath in $DELIVERY_PATH $PACKAGE_PATH $WORKFLOW_PATH $INGESTION_PATH $DRONE_PATH; do +sed -i \ + -e "s#ACR_SERVER_VAR_VAL#$DEV_ACR_SERVER#g" \ + -e "s#ACR_NAME_VAR_VAL#$DEV_ACR_NAME#g" \ + -e "s#AZURE_PIPELINES_SERVICE_CONN_NAME_VAR_VAL#$AZURE_PIPELINES_SERVICE_CONN_NAME#g" \ + ${pipelinePath}/azure-pipelines.yml +done + +# push changes to the repo +cd $PROJECT_ROOT && \ +git add -u && \ +git commit -m "set build pipelines variables" && \ +git push newremote master && \ +cd - +``` + +## Add Delivery CI/CD + +``` +# add build definition +az pipelines create \ + --organization $AZURE_DEVOPS_ORG \ + --project $AZURE_DEVOPS_PROJECT_NAME \ + --name delivery-ci \ + --service-connection $AZURE_DEVOPS_SERVICE_CONN_ID \ + --yml-path src/shipping/delivery/azure-pipelines.yml \ + --repository-type tfsgit \ + --repository $AZURE_DEVOPS_REPOS_NAME \ + --branch master + +# query build definition details and resources +export AZURE_DEVOPS_DELIVERY_BUILD_ID=$(az pipelines build definition list --organization $AZURE_DEVOPS_ORG --project $AZURE_DEVOPS_PROJECT_NAME --query "[?name=='delivery-ci'].id" -o tsv) && \ +export AZURE_DEVOPS_DELIVERY_QUEUE_ID=$(az pipelines build definition list --organization $AZURE_DEVOPS_ORG --project $AZURE_DEVOPS_PROJECT_NAME --query "[?name=='delivery-ci'].queue.id" -o tsv) && \ +for env in dev qa staging prod;do +ENV=${env^^} +export ${ENV}_DATABASE_NAME="deliveries-db" +export ${ENV}_COLLECTION_NAME="deliveries-col" +envIdentitiesDeploymentName="${ENV}_IDENTITIES_DEPLOYMENT_NAME" +export ${ENV}_DELIVERY_KEYVAULT_URI=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-${env} --query properties.outputs.deliveryKeyVaultUri.value -o tsv) +export ${ENV}_DELIVERY_PRINCIPAL_RESOURCE_ID=$(az group deployment show -g $RESOURCE_GROUP -n ${!envIdentitiesDeploymentName} --query properties.outputs.deliveryPrincipalResourceId.value -o tsv) +envDeliveryIdName="${ENV}_DELIVERY_ID_NAME" +export ${ENV}_DELIVERY_PRINCIPAL_CLIENT_ID=$(az identity show -g $RESOURCE_GROUP -n ${!envDeliveryIdName} --query clientId -o tsv) +done && \ +export INGRESS_TLS_SECRET_NAME=delivery-ingress-tls + +# add relese definition +cat $DELIVERY_PATH/azure-pipelines-cd.json | \ + sed "s#AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL#$AZURE_DEVOPS_SERVICE_CONN_ID#g" | \ + sed "s#AZURE_DEVOPS_DELIVERY_BUILD_ID_VAR_VAL#$AZURE_DEVOPS_DELIVERY_BUILD_ID#g" | \ + sed "s#AZURE_DEVOPS_REPOS_ID_VAR_VAL#$AZURE_DEVOPS_REPOS_ID#g" | \ + sed "s#AZURE_DEVOPS_PROJECT_ID_VAR_VAL#$AZURE_DEVOPS_PROJECT_ID#g" | \ + sed "s#AZURE_DEVOPS_QUEUE_ID_VAR_VAL#$AZURE_DEVOPS_DELIVERY_QUEUE_ID#g" | \ + sed "s#AZURE_DEVOPS_USER_ID_VAR_VAL#$AZURE_DEVOPS_USER_ID#g" | \ + sed "s#CLUSTER_NAME_VAR_VAL#$CLUSTER_NAME#g" | \ + sed "s#RESOURCE_GROUP_VAR_VAL#$RESOURCE_GROUP#g" | \ + # development resources + sed "s#DEV_ACR_SERVER_VAR_VAL#$DEV_ACR_SERVER#g" | \ + sed "s#DEV_ACR_NAME_VAR_VAL#$DEV_ACR_NAME#g" | \ + sed "s#DEV_DELIVERY_PRINCIPAL_CLIENT_ID_VAR_VAL#$DEV_DELIVERY_PRINCIPAL_CLIENT_ID#g" | \ + sed "s#DEV_DELIVERY_PRINCIPAL_RESOURCE_ID_VAR_VAL#$DEV_DELIVERY_PRINCIPAL_RESOURCE_ID#g" | \ + sed "s#DEV_DATABASE_NAME_VAR_VAL#$DEV_DATABASE_NAME#g" | \ + sed "s#DEV_COLLECTION_NAME_VAR_VAL#$DEV_COLLECTION_NAME#g" | \ + sed "s#DEV_DELIVERY_KEYVAULT_URI_VAR_VAL#$DEV_DELIVERY_KEYVAULT_URI#g" | \ + sed "s#DEV_EXTERNAL_INGEST_FQDN_VAR_VAL#$DEV_EXTERNAL_INGEST_FQDN#g" | \ + sed "s#DEV_INGRESS_TLS_SECRET_CERT_VAR_VAL#$DEV_INGRESS_TLS_SECRET_CERT#g" | \ + sed "s#DEV_INGRESS_TLS_SECRET_KEY_VAR_VAL#$DEV_INGRESS_TLS_SECRET_KEY#g" | \ + sed "s#DEV_INGRESS_TLS_SECRET_NAME_VAR_VAL#$INGRESS_TLS_SECRET_NAME#g" | \ + sed "s#DEV_GATEWAY_SUBNET_PREFIX_VAR_VAL#$DEV_GATEWAY_SUBNET_PREFIX#g" | \ + sed "s#DEV_CLUSTER_SUBNET_PREFIX_VAR_VAL#$DEV_CLUSTER_SUBNET_PREFIX#g" | \ + # qa resources + sed "s#QA_ACR_SERVER_VAR_VAL#$QA_ACR_SERVER#g" | \ + sed "s#QA_ACR_NAME_VAR_VAL#$QA_ACR_NAME#g" | \ + sed "s#QA_DELIVERY_PRINCIPAL_CLIENT_ID_VAR_VAL#$QA_DELIVERY_PRINCIPAL_CLIENT_ID#g" | \ + sed "s#QA_DELIVERY_PRINCIPAL_RESOURCE_ID_VAR_VAL#$QA_DELIVERY_PRINCIPAL_RESOURCE_ID#g" | \ + sed "s#QA_DATABASE_NAME_VAR_VAL#$QA_DATABASE_NAME#g" | \ + sed "s#QA_COLLECTION_NAME_VAR_VAL#$QA_COLLECTION_NAME#g" | \ + sed "s#QA_DELIVERY_KEYVAULT_URI_VAR_VAL#$QA_DELIVERY_KEYVAULT_URI#g" | \ + sed "s#QA_EXTERNAL_INGEST_FQDN_VAR_VAL#$QA_EXTERNAL_INGEST_FQDN#g" | \ + sed "s#QA_INGRESS_TLS_SECRET_CERT_VAR_VAL#$QA_INGRESS_TLS_SECRET_CERT#g" | \ + sed "s#QA_INGRESS_TLS_SECRET_KEY_VAR_VAL#$QA_INGRESS_TLS_SECRET_KEY#g" | \ + sed "s#QA_INGRESS_TLS_SECRET_NAME_VAR_VAL#$INGRESS_TLS_SECRET_NAME#g" | \ + sed "s#QA_GATEWAY_SUBNET_PREFIX_VAR_VAL#$QA_GATEWAY_SUBNET_PREFIX#g" | \ + sed "s#QA_CLUSTER_SUBNET_PREFIX_VAR_VAL#$QA_CLUSTER_SUBNET_PREFIX#g" | \ + # staging resources + sed "s#STAGING_ACR_SERVER_VAR_VAL#$STAGING_ACR_SERVER#g" | \ + sed "s#STAGING_ACR_NAME_VAR_VAL#$STAGING_ACR_NAME#g" | \ + sed "s#STAGING_DELIVERY_PRINCIPAL_CLIENT_ID_VAR_VAL#$STAGING_DELIVERY_PRINCIPAL_CLIENT_ID#g" | \ + sed "s#STAGING_DELIVERY_PRINCIPAL_RESOURCE_ID_VAR_VAL#$STAGING_DELIVERY_PRINCIPAL_RESOURCE_ID#g" | \ + sed "s#STAGING_DATABASE_NAME_VAR_VAL#$STAGING_DATABASE_NAME#g" | \ + sed "s#STAGING_COLLECTION_NAME_VAR_VAL#$STAGING_COLLECTION_NAME#g" | \ + sed "s#STAGING_DELIVERY_KEYVAULT_URI_VAR_VAL#$STAGING_DELIVERY_KEYVAULT_URI#g" | \ + sed "s#STAGING_EXTERNAL_INGEST_FQDN_VAR_VAL#$STAGING_EXTERNAL_INGEST_FQDN#g" | \ + sed "s#STAGING_INGRESS_TLS_SECRET_CERT_VAR_VAL#$STAGING_INGRESS_TLS_SECRET_CERT#g" | \ + sed "s#STAGING_INGRESS_TLS_SECRET_KEY_VAR_VAL#$STAGING_INGRESS_TLS_SECRET_KEY#g" | \ + sed "s#STAGING_INGRESS_TLS_SECRET_NAME_VAR_VAL#$INGRESS_TLS_SECRET_NAME#g" | \ + sed "s#STAGING_GATEWAY_SUBNET_PREFIX_VAR_VAL#$STAGING_GATEWAY_SUBNET_PREFIX#g" | \ + sed "s#STAGING_CLUSTER_SUBNET_PREFIX_VAR_VAL#$STAGING_CLUSTER_SUBNET_PREFIX#g" | \ + # production resources + sed "s#SOURCE_ACR_SERVER_VAR_VAL#$STAGING_ACR_SERVER#g" | \ + sed "s#SOURCE_ACR_NAME_VAR_VAL#$STAGING_ACR_NAME#g" | \ + sed "s#PROD_ACR_SERVER_VAR_VAL#$PROD_ACR_SERVER#g" | \ + sed "s#PROD_ACR_NAME_VAR_VAL#$PROD_ACR_NAME#g" | \ + sed "s#PROD_DELIVERY_PRINCIPAL_CLIENT_ID_VAR_VAL#$PROD_DELIVERY_PRINCIPAL_CLIENT_ID#g" | \ + sed "s#PROD_DELIVERY_PRINCIPAL_RESOURCE_ID_VAR_VAL#$PROD_DELIVERY_PRINCIPAL_RESOURCE_ID#g" | \ + sed "s#PROD_DATABASE_NAME_VAR_VAL#$PROD_DATABASE_NAME#g" | \ + sed "s#PROD_COLLECTION_NAME_VAR_VAL#$PROD_COLLECTION_NAME#g" | \ + sed "s#PROD_DELIVERY_KEYVAULT_URI_VAR_VAL#$PROD_DELIVERY_KEYVAULT_URI#g" | \ + sed "s#PROD_EXTERNAL_INGEST_FQDN_VAR_VAL#$PROD_EXTERNAL_INGEST_FQDN#g" | \ + sed "s#PROD_INGRESS_TLS_SECRET_CERT_VAR_VAL#$PROD_INGRESS_TLS_SECRET_CERT#g" | \ + sed "s#PROD_INGRESS_TLS_SECRET_KEY_VAR_VAL#$PROD_INGRESS_TLS_SECRET_KEY#g" | \ + sed "s#PROD_INGRESS_TLS_SECRET_NAME_VAR_VAL#$INGRESS_TLS_SECRET_NAME#g" | \ + sed "s#PROD_GATEWAY_SUBNET_PREFIX_VAR_VAL#$PROD_GATEWAY_SUBNET_PREFIX#g" | \ + sed "s#PROD_CLUSTER_SUBNET_PREFIX_VAR_VAL#$PROD_CLUSTER_SUBNET_PREFIX#g" \ + > $DELIVERY_PATH/azure-pipelines-cd-0.json + +curl -sL -w "%{http_code}" -X POST ${AZURE_DEVOPS_VSRM_ORG}/${AZURE_DEVOPS_PROJECT_NAME}/_apis/release/definitions?api-version=5.1-preview.3 \ + -d@${DELIVERY_PATH}/azure-pipelines-cd-0.json \ + -H "Authorization: Basic ${AZURE_DEVOPS_AUTHN_BASIC_TOKEN}" \ + -H "Content-Type: application/json" \ + -o /dev/null +``` + +Kick off CI/CD pipelines + +```bash +git checkout -b release/delivery/v0.1.0 && \ +git push newremote release/delivery/v0.1.0 +``` + +Verify delivery was deployed + +```bash +helm status delivery-v0.1.0 --namespace backend-dev +``` + +## Add Package CI/CD + +Create build and release pipeline definitions +``` +# add build definitions +az pipelines create \ + --organization $AZURE_DEVOPS_ORG \ + --project $AZURE_DEVOPS_PROJECT_NAME \ + --name package-ci \ + --service-connection $AZURE_DEVOPS_SERVICE_CONN_ID \ + --yml-path src/shipping/package/azure-pipelines.yml \ + --repository-type tfsgit \ + --repository $AZURE_DEVOPS_REPOS_NAME \ + --branch master + +# query build definition details and resources +export AZURE_DEVOPS_PACKAGE_BUILD_ID=$(az pipelines build definition list --organization $AZURE_DEVOPS_ORG --project $AZURE_DEVOPS_PROJECT_NAME --query "[?name=='package-ci'].id" -o tsv) && \ +export AZURE_DEVOPS_PACKAGE_QUEUE_ID=$(az pipelines build definition list --organization $AZURE_DEVOPS_ORG --project $AZURE_DEVOPS_PROJECT_NAME --query "[?name=='package-ci'].queue.id" -o tsv) && \ +for env in dev qa staging prod;do +ENV=${env^^} +export COSMOSDB_NAME=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-${env} --query properties.outputs.packageMongoDbName.value -o tsv) +export ${ENV}_COSMOSDB_CONNECTION=$(az cosmosdb list-connection-strings --name $COSMOSDB_NAME --resource-group $RESOURCE_GROUP --query "connectionStrings[0].connectionString" -o tsv | sed 's/==/%3D%3D/g') +export ${ENV}_COSMOSDB_COL_NAME=packages +done + +# add relese definition +cat $PACKAGE_PATH/azure-pipelines-cd.json | \ + sed "s#AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL#$AZURE_DEVOPS_SERVICE_CONN_ID#g" | \ + sed "s#AZURE_DEVOPS_PACKAGE_BUILD_ID_VAR_VAL#$AZURE_DEVOPS_PACKAGE_BUILD_ID#g" | \ + sed "s#AZURE_DEVOPS_REPOS_ID_VAR_VAL#$AZURE_DEVOPS_REPOS_ID#g" | \ + sed "s#AZURE_DEVOPS_PROJECT_ID_VAR_VAL#$AZURE_DEVOPS_PROJECT_ID#g" | \ + sed "s#AZURE_DEVOPS_PACKAGE_QUEUE_ID_VAR_VAL#$AZURE_DEVOPS_PACKAGE_QUEUE_ID#g" | \ + sed "s#AZURE_DEVOPS_USER_ID_VAR_VAL#$AZURE_DEVOPS_USER_ID#g" | \ + sed "s#CLUSTER_NAME_VAR_VAL#$CLUSTER_NAME#g" | \ + sed "s#RESOURCE_GROUP_VAR_VAL#$RESOURCE_GROUP#g" | \ + # development resources + sed "s#DEV_AI_IKEY_VAR_VAL#$DEV_AI_IKEY#g" | \ + sed "s#DEV_ACR_SERVER_VAR_VAL#$DEV_ACR_SERVER#g" | \ + sed "s#DEV_ACR_NAME_VAR_VAL#$DEV_ACR_NAME#g" | \ + sed "s#DEV_COSMOSDB_COL_NAME_VAR_VAL#$DEV_COSMOSDB_COL_NAME#g" | \ + sed "s#DEV_COSMOSDB_CONNECTION_VAR_VAL#${DEV_COSMOSDB_CONNECTION//&/\\&}#g" | \ + sed "s#DEV_CLUSTER_SUBNET_PREFIX_VAR_VAL#$DEV_CLUSTER_SUBNET_PREFIX#g" | \ + # qa resources + sed "s#QA_AI_IKEY_VAR_VAL#$QA_AI_IKEY#g" | \ + sed "s#QA_ACR_SERVER_VAR_VAL#$QA_ACR_SERVER#g" | \ + sed "s#QA_ACR_NAME_VAR_VAL#$QA_ACR_NAME#g" | \ + sed "s#QA_COSMOSDB_COL_NAME_VAR_VAL#$QA_COSMOSDB_COL_NAME#g" | \ + sed "s#QA_COSMOSDB_CONNECTION_VAR_VAL#${QA_COSMOSDB_CONNECTION//&/\\&}#g" | \ + sed "s#QA_CLUSTER_SUBNET_PREFIX_VAR_VAL#$QA_CLUSTER_SUBNET_PREFIX#g" | \ + # staging resources + sed "s#STAGING_AI_IKEY_VAR_VAL#$STAGING_AI_IKEY#g" | \ + sed "s#STAGING_ACR_SERVER_VAR_VAL#$STAGING_ACR_SERVER#g" | \ + sed "s#STAGING_ACR_NAME_VAR_VAL#$STAGING_ACR_NAME#g" | \ + sed "s#STAGING_COSMOSDB_COL_NAME_VAR_VAL#$STAGING_COSMOSDB_COL_NAME#g" | \ + sed "s#STAGING_COSMOSDB_CONNECTION_VAR_VAL#${STAGING_COSMOSDB_CONNECTION//&/\\&}#g" | \ + sed "s#STAGING_CLUSTER_SUBNET_PREFIX_VAR_VAL#$STAGING_CLUSTER_SUBNET_PREFIX#g" | \ + # production resources + sed "s#PROD_AI_IKEY_VAR_VAL#$PROD_AI_IKEY#g" | \ + sed "s#SOURCE_ACR_SERVER_VAR_VAL#$STAGING_ACR_SERVER#g" | \ + sed "s#SOURCE_ACR_NAME_VAR_VAL#$STAGING_ACR_NAME#g" | \ + sed "s#PROD_ACR_SERVER_VAR_VAL#$PROD_ACR_SERVER#g" | \ + sed "s#PROD_ACR_NAME_VAR_VAL#$PROD_ACR_NAME#g" | \ + sed "s#PROD_COSMOSDB_COL_NAME_VAR_VAL#$PROD_COSMOSDB_COL_NAME#g" | \ + sed "s#PROD_COSMOSDB_CONNECTION_VAR_VAL#${PROD_COSMOSDB_CONNECTION//&/\\&}#g" | \ + sed "s#PROD_CLUSTER_SUBNET_PREFIX_VAR_VAL#$PROD_CLUSTER_SUBNET_PREFIX#g" \ + > $PACKAGE_PATH/azure-pipelines-cd-0.json + +curl -sL -w "%{http_code}" -X POST ${AZURE_DEVOPS_VSRM_ORG}/${AZURE_DEVOPS_PROJECT_NAME}/_apis/release/definitions?api-version=5.1-preview.3 \ + -d@${PACKAGE_PATH}/azure-pipelines-cd-0.json \ + -H "Authorization: Basic ${AZURE_DEVOPS_AUTHN_BASIC_TOKEN}" \ + -H "Content-Type: application/json" \ + -o /dev/null +``` + +Kick off CI/CD pipeline + +```bash +git checkout -b release/package/v0.1.0 && \ +git push newremote release/package/v0.1.0 +``` + +Verify package was deployed + +```bash +helm status package-v0.1.0 --namespace backend-dev +``` + +## Add Workflow CI/CD + +``` +# add build definitions +az pipelines create \ + --organization $AZURE_DEVOPS_ORG \ + --project $AZURE_DEVOPS_PROJECT_NAME \ + --name workflow-ci \ + --service-connection $AZURE_DEVOPS_SERVICE_CONN_ID \ + --yml-path src/shipping/workflow/azure-pipelines.yml \ + --repository-type tfsgit \ + --repository $AZURE_DEVOPS_REPOS_NAME \ + --branch master + +# query build definition details and resources +export AZURE_DEVOPS_WORKFLOW_BUILD_ID=$(az pipelines build definition list --organization $AZURE_DEVOPS_ORG --project $AZURE_DEVOPS_PROJECT_NAME --query "[?name=='workflow-ci'].id" -o tsv) && \ +export AZURE_DEVOPS_WORKFLOW_QUEUE_ID=$(az pipelines build definition list --organization $AZURE_DEVOPS_ORG --project $AZURE_DEVOPS_PROJECT_NAME --query "[?name=='workflow-ci'].queue.id" -o tsv) && \ +for env in dev qa staging prod;do +ENV=${env^^} +envIdentitiesDeploymentName="${ENV}_IDENTITIES_DEPLOYMENT_NAME" +export ${ENV}_WORKFLOW_KEYVAULT_NAME=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-${env} --query properties.outputs.workflowKeyVaultName.value -o tsv) +export ${ENV}_WORKFLOW_PRINCIPAL_RESOURCE_ID=$(az group deployment show -g $RESOURCE_GROUP -n ${!envIdentitiesDeploymentName} --query properties.outputs.workflowPrincipalResourceId.value -o tsv) +envWorkflowIdName="${ENV}_WORKFLOW_ID_NAME" +export ${ENV}_WORKFLOW_PRINCIPAL_CLIENT_ID=$(az identity show -g $RESOURCE_GROUP -n ${!envWorkflowIdName} --query clientId -o tsv) +done + +# add relese definition +cat $WORKFLOW_PATH/azure-pipelines-cd.json | \ + sed "s#AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL#$AZURE_DEVOPS_SERVICE_CONN_ID#g" | \ + sed "s#AZURE_DEVOPS_WORKFLOW_BUILD_ID_VAR_VAL#$AZURE_DEVOPS_WORKFLOW_BUILD_ID#g" | \ + sed "s#AZURE_DEVOPS_REPOS_ID_VAR_VAL#$AZURE_DEVOPS_REPOS_ID#g" | \ + sed "s#AZURE_DEVOPS_PROJECT_ID_VAR_VAL#$AZURE_DEVOPS_PROJECT_ID#g" | \ + sed "s#AZURE_DEVOPS_WORKFLOW_QUEUE_ID_VAR_VAL#$AZURE_DEVOPS_WORKFLOW_QUEUE_ID#g" | \ + sed "s#AZURE_DEVOPS_USER_ID_VAR_VAL#$AZURE_DEVOPS_USER_ID#g" | \ + sed "s#CLUSTER_NAME_VAR_VAL#$CLUSTER_NAME#g" | \ + sed "s#CLUSTER_RESOURCE_GROUP_VAR_VAL#$RESOURCE_GROUP#g" | \ + # development resources + sed "s#DEV_ACR_SERVER_VAR_VAL#$DEV_ACR_SERVER#g" | \ + sed "s#DEV_ACR_NAME_VAR_VAL#$DEV_ACR_NAME#g" | \ + sed "s#DEV_WORKFLOW_KEYVAULT_RESOURCE_GROUP_VAR_VAL#$RESOURCE_GROUP#g" | \ + sed "s#DEV_WORKFLOW_PRINCIPAL_CLIENT_ID_VAR_VAL#$DEV_WORKFLOW_PRINCIPAL_CLIENT_ID#g" | \ + sed "s#DEV_WORKFLOW_PRINCIPAL_RESOURCE_ID_VAR_VAL#$DEV_WORKFLOW_PRINCIPAL_RESOURCE_ID#g" | \ + sed "s#DEV_WORKFLOW_KEYVAULT_NAME_VAR_VAL#$DEV_WORKFLOW_KEYVAULT_NAME#g" | \ + sed "s#DEV_SUBSCRIPTION_ID_VAR_VAL#$SUBSCRIPTION_ID#g" | \ + sed "s#DEV_TENANT_ID_VAR_VAL#$TENANT_ID#g" | \ + sed "s#DEV_CLUSTER_SUBNET_PREFIX_VAR_VAL#$DEV_CLUSTER_SUBNET_PREFIX#g" | \ + # qa resources + sed "s#QA_ACR_SERVER_VAR_VAL#$QA_ACR_SERVER#g" | \ + sed "s#QA_ACR_NAME_VAR_VAL#$QA_ACR_NAME#g" | \ + sed "s#QA_WORKFLOW_KEYVAULT_RESOURCE_GROUP_VAR_VAL#$RESOURCE_GROUP#g" | \ + sed "s#QA_WORKFLOW_PRINCIPAL_CLIENT_ID_VAR_VAL#$QA_WORKFLOW_PRINCIPAL_CLIENT_ID#g" | \ + sed "s#QA_WORKFLOW_PRINCIPAL_RESOURCE_ID_VAR_VAL#$QA_WORKFLOW_PRINCIPAL_RESOURCE_ID#g" | \ + sed "s#QA_WORKFLOW_KEYVAULT_NAME_VAR_VAL#$QA_WORKFLOW_KEYVAULT_NAME#g" | \ + sed "s#QA_SUBSCRIPTION_ID_VAR_VAL#$SUBSCRIPTION_ID#g" | \ + sed "s#QA_TENANT_ID_VAR_VAL#$TENANT_ID#g" | \ + sed "s#QA_CLUSTER_SUBNET_PREFIX_VAR_VAL#$QA_CLUSTER_SUBNET_PREFIX#g" | \ + # staging resources + sed "s#STAGING_ACR_SERVER_VAR_VAL#$STAGING_ACR_SERVER#g" | \ + sed "s#STAGING_ACR_NAME_VAR_VAL#$STAGING_ACR_NAME#g" | \ + sed "s#STAGING_WORKFLOW_KEYVAULT_RESOURCE_GROUP_VAR_VAL#$RESOURCE_GROUP#g" | \ + sed "s#STAGING_WORKFLOW_PRINCIPAL_CLIENT_ID_VAR_VAL#$STAGING_WORKFLOW_PRINCIPAL_CLIENT_ID#g" | \ + sed "s#STAGING_WORKFLOW_PRINCIPAL_RESOURCE_ID_VAR_VAL#$STAGING_WORKFLOW_PRINCIPAL_RESOURCE_ID#g" | \ + sed "s#STAGING_WORKFLOW_KEYVAULT_NAME_VAR_VAL#$STAGING_WORKFLOW_KEYVAULT_NAME#g" | \ + sed "s#STAGING_SUBSCRIPTION_ID_VAR_VAL#$SUBSCRIPTION_ID#g" | \ + sed "s#STAGING_TENANT_ID_VAR_VAL#$TENANT_ID#g" | \ + sed "s#STAGING_CLUSTER_SUBNET_PREFIX_VAR_VAL#$STAGING_CLUSTER_SUBNET_PREFIX#g" | \ + # production resources + sed "s#SOURCE_ACR_SERVER_VAR_VAL#$STAGING_ACR_SERVER#g" | \ + sed "s#SOURCE_ACR_NAME_VAR_VAL#$STAGING_ACR_NAME#g" | \ + sed "s#PROD_ACR_SERVER_VAR_VAL#$PROD_ACR_SERVER#g" | \ + sed "s#PROD_ACR_NAME_VAR_VAL#$PROD_ACR_NAME#g" | \ + sed "s#PROD_WORKFLOW_KEYVAULT_RESOURCE_GROUP_VAR_VAL#$RESOURCE_GROUP#g" | \ + sed "s#PROD_WORKFLOW_PRINCIPAL_CLIENT_ID_VAR_VAL#$PROD_WORKFLOW_PRINCIPAL_CLIENT_ID#g" | \ + sed "s#PROD_WORKFLOW_PRINCIPAL_RESOURCE_ID_VAR_VAL#$PROD_WORKFLOW_PRINCIPAL_RESOURCE_ID#g" | \ + sed "s#PROD_WORKFLOW_KEYVAULT_NAME_VAR_VAL#$PROD_WORKFLOW_KEYVAULT_NAME#g" | \ + sed "s#PROD_SUBSCRIPTION_ID_VAR_VAL#$SUBSCRIPTION_ID#g" | \ + sed "s#PROD_TENANT_ID_VAR_VAL#$TENANT_ID#g" | \ + sed "s#PROD_CLUSTER_SUBNET_PREFIX_VAR_VAL#$PROD_CLUSTER_SUBNET_PREFIX#g" \ + > $WORKFLOW_PATH/azure-pipelines-cd-0.json + +curl -sL -w "%{http_code}" -X POST ${AZURE_DEVOPS_VSRM_ORG}/${AZURE_DEVOPS_PROJECT_NAME}/_apis/release/definitions?api-version=5.1-preview.3 \ + -d@${WORKFLOW_PATH}/azure-pipelines-cd-0.json \ + -H "Authorization: Basic ${AZURE_DEVOPS_AUTHN_BASIC_TOKEN}" \ + -H "Content-Type: application/json" \ + -o /dev/null +``` + +Kick off CI/CD pipeline + +```bash +git checkout -b release/workflow/v0.1.0 && \ +git push newremote release/workflow/v0.1.0 +``` + +Verify workflow was deployed + +```bash +helm status workflow-v0.1.0 --namespace backend-dev +``` + +## Add Ingestion CI/CD + +Ingestion pre-requisites + +Create build and release pipeline definitions +``` +# add build definitions +az pipelines create \ + --organization $AZURE_DEVOPS_ORG \ + --project $AZURE_DEVOPS_PROJECT_NAME \ + --name ingestion-ci \ + --service-connection $AZURE_DEVOPS_SERVICE_CONN_ID \ + --yml-path src/shipping/ingestion/azure-pipelines.yml \ + --repository-type tfsgit \ + --repository $AZURE_DEVOPS_REPOS_NAME \ + --branch master + +# query build definition details and resources +export AZURE_DEVOPS_INGESTION_BUILD_ID=$(az pipelines build definition list --organization $AZURE_DEVOPS_ORG --project $AZURE_DEVOPS_PROJECT_NAME --query "[?name=='ingestion-ci'].id" -o tsv) && \ +export AZURE_DEVOPS_INGESTION_QUEUE_ID=$(az pipelines build definition list --organization $AZURE_DEVOPS_ORG --project $AZURE_DEVOPS_PROJECT_NAME --query "[?name=='ingestion-ci'].queue.id" -o tsv) && \ +for env in dev qa staging prod;do +ENV=${env^^} +export {${ENV}_INGESTION_QUEUE_NAMESPACE,INGESTION_QUEUE_NAMESPACE}=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-${env} --query properties.outputs.ingestionQueueNamespace.value -o tsv) +export ${ENV}_INGESTION_QUEUE_NAME=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-${env} --query properties.outputs.ingestionQueueName.value -o tsv) +export INGESTION_ACCESS_KEY_NAME=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-${env} --query properties.outputs.ingestionServiceAccessKeyName.value -o tsv) +export ${ENV}_INGESTION_ACCESS_KEY_VALUE=$(az servicebus namespace authorization-rule keys list --resource-group $RESOURCE_GROUP --namespace-name $INGESTION_QUEUE_NAMESPACE --name $INGESTION_ACCESS_KEY_NAME --query primaryKey -o tsv) +done && \ +export INGRESS_TLS_SECRET_NAME=ingestion-ingress-tls + +# add relese definition +cat $INGESTION_PATH/azure-pipelines-cd.json | \ + sed "s#AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL#$AZURE_DEVOPS_SERVICE_CONN_ID#g" | \ + sed "s#AZURE_DEVOPS_INGESTION_BUILD_ID_VAR_VAL#$AZURE_DEVOPS_INGESTION_BUILD_ID#g" | \ + sed "s#AZURE_DEVOPS_REPOS_ID_VAR_VAL#$AZURE_DEVOPS_REPOS_ID#g" | \ + sed "s#AZURE_DEVOPS_PROJECT_ID_VAR_VAL#$AZURE_DEVOPS_PROJECT_ID#g" | \ + sed "s#AZURE_DEVOPS_INGESTION_QUEUE_ID_VAR_VAL#$AZURE_DEVOPS_INGESTION_QUEUE_ID#g" | \ + sed "s#AZURE_DEVOPS_USER_ID_VAR_VAL#$AZURE_DEVOPS_USER_ID#g" | \ + sed "s#CLUSTER_NAME_VAR_VAL#$CLUSTER_NAME#g" | \ + sed "s#RESOURCE_GROUP_VAR_VAL#$RESOURCE_GROUP#g" | \ + # development resources + sed "s#DEV_AI_IKEY_VAR_VAL#$DEV_AI_IKEY#g" | \ + sed "s#DEV_ACR_SERVER_VAR_VAL#$DEV_ACR_SERVER#g" | \ + sed "s#DEV_ACR_NAME_VAR_VAL#$DEV_ACR_NAME#g" | \ + sed "s#DEV_EXTERNAL_INGEST_FQDN_VAR_VAL#$DEV_EXTERNAL_INGEST_FQDN#g" | \ + sed "s#DEV_INGESTION_QUEUE_NAMESPACE_VAR_VAL#$DEV_INGESTION_QUEUE_NAMESPACE#g" | \ + sed "s#DEV_INGESTION_QUEUE_NAME_VAR_VAL#$DEV_INGESTION_QUEUE_NAME#g" | \ + sed "s#DEV_INGESTION_ACCESS_KEY_VALUE_VAR_VAL#$DEV_INGESTION_ACCESS_KEY_VALUE#g" | \ + sed "s#DEV_INGRESS_TLS_SECRET_CERT_VAR_VAL#$DEV_INGRESS_TLS_SECRET_CERT#g" | \ + sed "s#DEV_INGRESS_TLS_SECRET_KEY_VAR_VAL#$DEV_INGRESS_TLS_SECRET_KEY#g" | \ + sed "s#DEV_INGRESS_TLS_SECRET_NAME_VAR_VAL#$INGRESS_TLS_SECRET_NAME#g" | \ + sed "s#DEV_GATEWAY_SUBNET_PREFIX_VAR_VAL#$DEV_GATEWAY_SUBNET_PREFIX#g" | \ + sed "s#DEV_CLUSTER_SUBNET_PREFIX_VAR_VAL#$DEV_CLUSTER_SUBNET_PREFIX#g" | \ + # qa resources + sed "s#QA_AI_IKEY_VAR_VAL#$QA_AI_IKEY#g" | \ + sed "s#QA_ACR_SERVER_VAR_VAL#$QA_ACR_SERVER#g" | \ + sed "s#QA_ACR_NAME_VAR_VAL#$QA_ACR_NAME#g" | \ + sed "s#QA_EXTERNAL_INGEST_FQDN_VAR_VAL#$QA_EXTERNAL_INGEST_FQDN#g" | \ + sed "s#QA_INGESTION_QUEUE_NAMESPACE_VAR_VAL#$QA_INGESTION_QUEUE_NAMESPACE#g" | \ + sed "s#QA_INGESTION_QUEUE_NAME_VAR_VAL#$QA_INGESTION_QUEUE_NAME#g" | \ + sed "s#QA_INGESTION_ACCESS_KEY_VALUE_VAR_VAL#$QA_INGESTION_ACCESS_KEY_VALUE#g" | \ + sed "s#QA_INGRESS_TLS_SECRET_CERT_VAR_VAL#$QA_INGRESS_TLS_SECRET_CERT#g" | \ + sed "s#QA_INGRESS_TLS_SECRET_KEY_VAR_VAL#$QA_INGRESS_TLS_SECRET_KEY#g" | \ + sed "s#QA_INGRESS_TLS_SECRET_NAME_VAR_VAL#$INGRESS_TLS_SECRET_NAME#g" | \ + sed "s#QA_GATEWAY_SUBNET_PREFIX_VAR_VAL#$QA_GATEWAY_SUBNET_PREFIX#g" | \ + sed "s#QA_CLUSTER_SUBNET_PREFIX_VAR_VAL#$QA_CLUSTER_SUBNET_PREFIX#g" | \ + # staging resources + sed "s#STAGING_AI_IKEY_VAR_VAL#$STAGING_AI_IKEY#g" | \ + sed "s#STAGING_ACR_SERVER_VAR_VAL#$STAGING_ACR_SERVER#g" | \ + sed "s#STAGING_ACR_NAME_VAR_VAL#$STAGING_ACR_NAME#g" | \ + sed "s#STAGING_EXTERNAL_INGEST_FQDN_VAR_VAL#$STAGING_EXTERNAL_INGEST_FQDN#g" | \ + sed "s#STAGING_INGESTION_QUEUE_NAMESPACE_VAR_VAL#$STAGING_INGESTION_QUEUE_NAMESPACE#g" | \ + sed "s#STAGING_INGESTION_QUEUE_NAME_VAR_VAL#$STAGING_INGESTION_QUEUE_NAME#g" | \ + sed "s#STAGING_INGESTION_ACCESS_KEY_VALUE_VAR_VAL#$STAGING_INGESTION_ACCESS_KEY_VALUE#g" | \ + sed "s#STAGING_INGRESS_TLS_SECRET_CERT_VAR_VAL#$STAGING_INGRESS_TLS_SECRET_CERT#g" | \ + sed "s#STAGING_INGRESS_TLS_SECRET_KEY_VAR_VAL#$STAGING_INGRESS_TLS_SECRET_KEY#g" | \ + sed "s#STAGING_INGRESS_TLS_SECRET_NAME_VAR_VAL#$INGRESS_TLS_SECRET_NAME#g" | \ + sed "s#STAGING_GATEWAY_SUBNET_PREFIX_VAR_VAL#$STAGING_GATEWAY_SUBNET_PREFIX#g" | \ + sed "s#STAGING_CLUSTER_SUBNET_PREFIX_VAR_VAL#$STAGING_CLUSTER_SUBNET_PREFIX#g" | \ + # production resources + sed "s#PROD_AI_IKEY_VAR_VAL#$PROD_AI_IKEY#g" | \ + sed "s#SOURCE_ACR_SERVER_VAR_VAL#$STAGING_ACR_SERVER#g" | \ + sed "s#SOURCE_ACR_NAME_VAR_VAL#$STAGING_ACR_NAME#g" | \ + sed "s#PROD_ACR_SERVER_VAR_VAL#$PROD_ACR_SERVER#g" | \ + sed "s#PROD_ACR_NAME_VAR_VAL#$PROD_ACR_NAME#g" | \ + sed "s#PROD_EXTERNAL_INGEST_FQDN_VAR_VAL#$PROD_EXTERNAL_INGEST_FQDN#g" | \ + sed "s#PROD_INGESTION_QUEUE_NAMESPACE_VAR_VAL#$PROD_INGESTION_QUEUE_NAMESPACE#g" | \ + sed "s#PROD_INGESTION_QUEUE_NAME_VAR_VAL#$PROD_INGESTION_QUEUE_NAME#g" | \ + sed "s#PROD_INGESTION_ACCESS_KEY_VALUE_VAR_VAL#$PROD_INGESTION_ACCESS_KEY_VALUE#g" | \ + sed "s#PROD_INGRESS_TLS_SECRET_CERT_VAR_VAL#$PROD_INGRESS_TLS_SECRET_CERT#g" | \ + sed "s#PROD_INGRESS_TLS_SECRET_KEY_VAR_VAL#$PROD_INGRESS_TLS_SECRET_KEY#g" | \ + sed "s#PROD_INGRESS_TLS_SECRET_NAME_VAR_VAL#$INGRESS_TLS_SECRET_NAME#g" | \ + sed "s#PROD_GATEWAY_SUBNET_PREFIX_VAR_VAL#$PROD_GATEWAY_SUBNET_PREFIX#g" | \ + sed "s#PROD_CLUSTER_SUBNET_PREFIX_VAR_VAL#$PROD_CLUSTER_SUBNET_PREFIX#g" \ + > $INGESTION_PATH/azure-pipelines-cd-0.json + +curl -sL -w "%{http_code}" -X POST ${AZURE_DEVOPS_VSRM_ORG}/${AZURE_DEVOPS_PROJECT_NAME}/_apis/release/definitions?api-version=5.1-preview.3 \ + -d@${INGESTION_PATH}/azure-pipelines-cd-0.json \ + -H "Authorization: Basic ${AZURE_DEVOPS_AUTHN_BASIC_TOKEN}" \ + -H "Content-Type: application/json" \ + -o /dev/null +``` + +Kick off CI/CD pipeline + +```bash +git checkout -b release/ingestion/v0.1.0 && \ +git push newremote release/ingestion/v0.1.0 +``` + +Verify ingestion was deployed + +```bash +helm status ingestion-v0.1.0 --namespace backend-dev +``` + +## Add DroneScheduler CI/CD + +Create build and release pipeline definitions +``` +# add build definitions +az pipelines create \ + --organization $AZURE_DEVOPS_ORG \ + --project $AZURE_DEVOPS_PROJECT_NAME \ + --name dronescheduler-ci \ + --service-connection $AZURE_DEVOPS_SERVICE_CONN_ID \ + --yml-path src/shipping/dronescheduler/azure-pipelines.yml \ + --repository-type tfsgit \ + --repository $AZURE_DEVOPS_REPOS_NAME \ + --branch master + +# query build definition details and resources +export AZURE_DEVOPS_DRONE_BUILD_ID=$(az pipelines build definition list --organization $AZURE_DEVOPS_ORG --project $AZURE_DEVOPS_PROJECT_NAME --query "[?name=='dronescheduler-ci'].id" -o tsv) && \ +export AZURE_DEVOPS_DRONE_QUEUE_ID=$(az pipelines build definition list --organization $AZURE_DEVOPS_ORG --project $AZURE_DEVOPS_PROJECT_NAME --query "[?name=='dronescheduler-ci'].queue.id" -o tsv) && \ +for env in dev qa staging prod;do +ENV=${env^^} +envIdentitiesDeploymentName="${ENV}_IDENTITIES_DEPLOYMENT_NAME" +export ${ENV}_DRONESCHEDULER_PRINCIPAL_RESOURCE_ID=$(az group deployment show -g $RESOURCE_GROUP -n ${!envIdentitiesDeploymentName} --query properties.outputs.droneSchedulerPrincipalResourceId.value -o tsv) +envDroneSchedulerIdName="${ENV}_DRONESCHEDULER_ID_NAME" +export ${ENV}_DRONESCHEDULER_PRINCIPAL_CLIENT_ID=$(az identity show -g $RESOURCE_GROUP -n ${!envDroneSchedulerIdName} --query clientId -o tsv) +export ${ENV}_DRONESCHEDULER_KEYVAULT_URI=$(az group deployment show -g $RESOURCE_GROUP -n azuredeploy-${env} --query properties.outputs.droneSchedulerKeyVaultUri.value -o tsv) && \ +export ${ENV}_COSMOSDB_DATABASEID="${env}_invoicing" && \ +export ${ENV}_COSMOSDB_COLLECTIONID="${env}_utilization" +done + +# add relese definition +cat $DRONE_PATH/azure-pipelines-cd.json | \ + sed "s#AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL#$AZURE_DEVOPS_SERVICE_CONN_ID#g" | \ + sed "s#AZURE_DEVOPS_DRONE_BUILD_ID_VAR_VAL#$AZURE_DEVOPS_DRONE_BUILD_ID#g" | \ + sed "s#AZURE_DEVOPS_REPOS_ID_VAR_VAL#$AZURE_DEVOPS_REPOS_ID#g" | \ + sed "s#AZURE_DEVOPS_PROJECT_ID_VAR_VAL#$AZURE_DEVOPS_PROJECT_ID#g" | \ + sed "s#AZURE_DEVOPS_DRONE_QUEUE_ID_VAR_VAL#$AZURE_DEVOPS_DRONE_QUEUE_ID#g" | \ + sed "s#AZURE_DEVOPS_USER_ID_VAR_VAL#$AZURE_DEVOPS_USER_ID#g" | \ + sed "s#CLUSTER_NAME_VAR_VAL#$CLUSTER_NAME#g" | \ + sed "s#RESOURCE_GROUP_VAR_VAL#$RESOURCE_GROUP#g" | \ + # development resources + sed "s#DEV_ACR_SERVER_VAR_VAL#$DEV_ACR_SERVER#g" | \ + sed "s#DEV_ACR_NAME_VAR_VAL#$DEV_ACR_NAME#g" | \ + sed "s#DEV_DRONESCHEDULER_PRINCIPAL_CLIENT_ID_VAR_VAL#$DEV_DRONESCHEDULER_PRINCIPAL_CLIENT_ID#g" | \ + sed "s#DEV_DRONESCHEDULER_PRINCIPAL_RESOURCE_ID_VAR_VAL#$DEV_DRONESCHEDULER_PRINCIPAL_RESOURCE_ID#g" | \ + sed "s#DEV_DRONESCHEDULER_KEYVAULT_URI_VAR_VAL#$DEV_DRONESCHEDULER_KEYVAULT_URI#g" | \ + sed "s#DEV_COSMOSDB_DATABASEID_VAR_VAL#$DEV_COSMOSDB_DATABASEID#g" | \ + sed "s#DEV_COSMOSDB_COLLECTIONID_VAR_VAL#$DEV_COSMOSDB_COLLECTIONID#g" | \ + sed "s#DEV_CLUSTER_SUBNET_PREFIX_VAR_VAL#$DEV_CLUSTER_SUBNET_PREFIX#g" | \ + # qa resources + sed "s#QA_ACR_SERVER_VAR_VAL#$QA_ACR_SERVER#g" | \ + sed "s#QA_ACR_NAME_VAR_VAL#$QA_ACR_NAME#g" | \ + sed "s#QA_DRONESCHEDULER_PRINCIPAL_CLIENT_ID_VAR_VAL#$QA_DRONESCHEDULER_PRINCIPAL_CLIENT_ID#g" | \ + sed "s#QA_DRONESCHEDULER_PRINCIPAL_RESOURCE_ID_VAR_VAL#$QA_DRONESCHEDULER_PRINCIPAL_RESOURCE_ID#g" | \ + sed "s#QA_DRONESCHEDULER_KEYVAULT_URI_VAR_VAL#$QA_DRONESCHEDULER_KEYVAULT_URI#g" | \ + sed "s#QA_COSMOSDB_DATABASEID_VAR_VAL#$QA_COSMOSDB_DATABASEID#g" | \ + sed "s#QA_COSMOSDB_COLLECTIONID_VAR_VAL#$QA_COSMOSDB_COLLECTIONID#g" | \ + sed "s#QA_CLUSTER_SUBNET_PREFIX_VAR_VAL#$QA_CLUSTER_SUBNET_PREFIX#g" | \ + # staging resources + sed "s#STAGING_ACR_SERVER_VAR_VAL#$STAGING_ACR_SERVER#g" | \ + sed "s#STAGING_ACR_NAME_VAR_VAL#$STAGING_ACR_NAME#g" | \ + sed "s#STAGING_DRONESCHEDULER_PRINCIPAL_CLIENT_ID_VAR_VAL#$STAGING_DRONESCHEDULER_PRINCIPAL_CLIENT_ID#g" | \ + sed "s#STAGING_DRONESCHEDULER_PRINCIPAL_RESOURCE_ID_VAR_VAL#$STAGING_DRONESCHEDULER_PRINCIPAL_RESOURCE_ID#g" | \ + sed "s#STAGING_DRONESCHEDULER_KEYVAULT_URI_VAR_VAL#$STAGING_DRONESCHEDULER_KEYVAULT_URI#g" | \ + sed "s#STAGING_COSMOSDB_DATABASEID_VAR_VAL#$STAGING_COSMOSDB_DATABASEID#g" | \ + sed "s#STAGING_COSMOSDB_COLLECTIONID_VAR_VAL#$STAGING_COSMOSDB_COLLECTIONID#g" | \ + sed "s#STAGING_CLUSTER_SUBNET_PREFIX_VAR_VAL#$STAGING_CLUSTER_SUBNET_PREFIX#g" | \ + # production resources + sed "s#SOURCE_ACR_SERVER_VAR_VAL#$STAGING_ACR_SERVER#g" | \ + sed "s#SOURCE_ACR_NAME_VAR_VAL#$STAGING_ACR_NAME#g" | \ + sed "s#PROD_ACR_SERVER_VAR_VAL#$PROD_ACR_SERVER#g" | \ + sed "s#PROD_ACR_NAME_VAR_VAL#$PROD_ACR_NAME#g" | \ + sed "s#PROD_DRONESCHEDULER_PRINCIPAL_CLIENT_ID_VAR_VAL#$PROD_DRONESCHEDULER_PRINCIPAL_CLIENT_ID#g" | \ + sed "s#PROD_DRONESCHEDULER_PRINCIPAL_RESOURCE_ID_VAR_VAL#$PROD_DRONESCHEDULER_PRINCIPAL_RESOURCE_ID#g" | \ + sed "s#PROD_DRONESCHEDULER_KEYVAULT_URI_VAR_VAL#$PROD_DRONESCHEDULER_KEYVAULT_URI#g" | \ + sed "s#PROD_COSMOSDB_DATABASEID_VAR_VAL#$PROD_COSMOSDB_DATABASEID#g" | \ + sed "s#PROD_COSMOSDB_COLLECTIONID_VAR_VAL#$PROD_COSMOSDB_COLLECTIONID#g" | \ + sed "s#PROD_CLUSTER_SUBNET_PREFIX_VAR_VAL#$PROD_CLUSTER_SUBNET_PREFIX#g" \ + > $DRONE_PATH/azure-pipelines-cd-0.json + +curl -sL -w "%{http_code}" -X POST ${AZURE_DEVOPS_VSRM_ORG}/${AZURE_DEVOPS_PROJECT_NAME}/_apis/release/definitions?api-version=5.1-preview.3 \ + -d@${DRONE_PATH}/azure-pipelines-cd-0.json \ + -H "Authorization: Basic ${AZURE_DEVOPS_AUTHN_BASIC_TOKEN}" \ + -H "Content-Type: application/json" \ + -o /dev/null +``` + +Kick off CI/CD pipeline + +```bash +git checkout -b release/dronescheduler/v0.1.0 && \ +git push newremote release/dronescheduler/v0.1.0 +``` + +Verify dronescheduler was deployed + +```bash +helm status dronescheduler-v0.1.0 --namespace backend-dev +``` + +## Validate the application is running + +You can resume the [the original deployment instructions](./deployment.md#validate-the-application-is-running) to validate the application is running. diff --git a/k8s/account.yaml b/k8s/account.yaml deleted file mode 100644 index 6350a5cb..00000000 --- a/k8s/account.yaml +++ /dev/null @@ -1,66 +0,0 @@ -# ------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -# ------------------------------------------------------------ - -################################################################################################### -# Account service -################################################################################################### -apiVersion: v1 -kind: Service -metadata: - name: account - labels: - app: account - bc: shipping -spec: - ports: - - name: http - port: 80 - targetPort: 80 - selector: - app: account - clusterIP: None ---- -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: account - labels: # In API version apps/v1beta2, .metadata.labels no longer default to .spec.template.metadata.labels. - app: account - version: 0.1.0 - bc: shipping -spec: - replicas: 1 - selector: # In API version apps/v1beta2, .spec.selector no longer default to .spec.template.metadata.labels. - matchLabels: # spec.selector is immutable after creation of the Deployment in apps/v1beta2. - app: account - template: # A Deployment may terminate Pods whose labels match the selector if their template is different from .spec.template - metadata: - labels: - app: account - version: 0.1.0 - bc: shipping - annotations: - team: accountservice - spec: - containers: - - name: account - image: - env: - # Integration with l5d Daemon Sets - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - - name: POD_IP - valueFrom: - fieldRef: - fieldPath: status.podIP - - name: http_proxy - value: $(NODE_NAME):4140 - - name: CORRELATION_HEADER - value: l5d-ctx-trace - ports: - - name: service - containerPort: 80 diff --git a/k8s/delivery.yaml b/k8s/delivery.yaml deleted file mode 100644 index 01a1e2d1..00000000 --- a/k8s/delivery.yaml +++ /dev/null @@ -1,94 +0,0 @@ -# ------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -# ------------------------------------------------------------ - -################################################################################################### -# Delivery service -################################################################################################### -apiVersion: v1 -kind: Service -metadata: - name: delivery - labels: - app: delivery - bc: shipping -spec: - ports: - - name: http - port: 80 - targetPort: 80 - selector: - app: delivery - clusterIP: None ---- -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: delivery - labels: # In API version apps/v1beta2, .metadata.labels no longer default to .spec.template.metadata.labels. - app: delivery - version: 0.1.0 - bc: shipping -spec: - replicas: 1 - selector: # In API version apps/v1beta2, .spec.selector no longer default to .spec.template.metadata.labels. - matchLabels: # spec.selector is immutable after creation of the Deployment in apps/v1beta2. - app: delivery - template: # A Deployment may terminate Pods whose labels match the selector if their template is different from .spec.template - metadata: - labels: - app: delivery - version: 0.1.0 - bc: shipping - annotations: - team: deliveryservice - spec: - containers: - - name: delivery - image: - env: - # Export K8s secrets - - name: DOCDB_KEY - valueFrom: - secretKeyRef: - name: delivery-storageconf - key: CosmosDB_Key - - name: DOCDB_ENDPOINT - valueFrom: - secretKeyRef: - name: delivery-storageconf - key: CosmosDB_Endpoint - - name: DOCDB_DATABASEID - value: "CosmosDB_DatabaseId" - - name: DOCDB_COLLECTIONID - valueFrom: - value: "CosmosDB_CollectionId" - - name: REDIS_CONNSTR - valueFrom: - secretKeyRef: - name: delivery-storageconf - key: Redis_ConnectionString - - name: EH_CONNSTR - valueFrom: - secretKeyRef: - name: delivery-storageconf - key: EH_ConnectionString - - name: EH_ENTITYPATH - value: "EH_EntityPath" - # Integration with l5d Daemon Sets - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - - name: POD_IP - valueFrom: - fieldRef: - fieldPath: status.podIP - - name: http_proxy - value: $(NODE_NAME):4140 - - name: CORRELATION_HEADER - value: l5d-ctx-trace - ports: - - name: service - containerPort: 80 diff --git a/k8s/dronescheduler.yaml b/k8s/dronescheduler.yaml deleted file mode 100644 index 9a5b4c31..00000000 --- a/k8s/dronescheduler.yaml +++ /dev/null @@ -1,66 +0,0 @@ -# ------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -# ------------------------------------------------------------ - -################################################################################################### -# Drone service -################################################################################################### -apiVersion: v1 -kind: Service -metadata: - name: dronescheduler - labels: - app: dronescheduler - bc: shipping -spec: - ports: - - name: http - port: 80 - targetPort: 80 - selector: - app: dronescheduler - clusterIP: None ---- -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: dronescheduler - labels: # In API version apps/v1beta2, .metadata.labels no longer default to .spec.template.metadata.labels. - app: dronescheduler - version: 0.1.0 - bc: shipping -spec: - replicas: 1 - selector: # In API version apps/v1beta2, .spec.selector no longer default to .spec.template.metadata.labels. - matchLabels: # spec.selector is immutable after creation of the Deployment in apps/v1beta2. - app: dronescheduler - template: # A Deployment may terminate Pods whose labels match the selector if their template is different from .spec.template - metadata: - labels: - app: dronescheduler - version: 0.1.0 - bc: shipping - annotations: - team: droneschedulerservice - spec: - containers: - - name: dronescheduler - image: - env: - # Integration with l5d Daemon Sets - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - - name: POD_IP - valueFrom: - fieldRef: - fieldPath: status.podIP - - name: http_proxy - value: $(NODE_NAME):4140 - - name: CORRELATION_HEADER - value: l5d-ctx-trace - ports: - - name: service - containerPort: 80 diff --git a/k8s/ingestion.yaml b/k8s/ingestion.yaml deleted file mode 100644 index c96672f3..00000000 --- a/k8s/ingestion.yaml +++ /dev/null @@ -1,88 +0,0 @@ -# ------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -# ------------------------------------------------------------ - -################################################################################################## -# Ingestion service -################################################################################################## -apiVersion: v1 -kind: Service -metadata: - name: ingestion - labels: - app: ingestion - bc: shipping -spec: - ports: - - port: 80 - name: http - selector: - app: ingestion - type: - LoadBalancer ---- -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: ingestion -spec: - replicas: 1 - template: - metadata: - labels: - app: ingestion - version: 0.1.0 - bc: shipping - spec: - containers: - - name: ingestion - image: - imagePullPolicy: IfNotPresent - ports: - - name: http - containerPort: 80 - livenessProbe: - httpGet: - path: /health - port: 80 - initialDelaySeconds: 30 - periodSeconds: 20 - readinessProbe: - httpGet: - path: /api/probe - port: 80 - initialDelaySeconds: 30 - periodSeconds: 20 - env: - - name: EH_NAMESPACE - valueFrom: - secretKeyRef: - name: ingestion-secrets - key: eventhub_namespace - - name: EH_NAME - valueFrom: - secretKeyRef: - name: ingestion-secrets - key: eventhub_name - - name: EH_KEYNAME - valueFrom: - secretKeyRef: - name: ingestion-secrets - key: eventhub_keyname - - name: EH_KEYVALUE - valueFrom: - secretKeyRef: - name: ingestion-secrets - key: eventhub_keyvalue - # Integration with l5d Daemon Sets - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - - name: POD_IP - valueFrom: - fieldRef: - fieldPath: status.podIP - - name: http_proxy - value: $(NODE_NAME):4140 \ No newline at end of file diff --git a/k8s/k8s-deny-all-non-whitelisted-traffic-dev.yaml b/k8s/k8s-deny-all-non-whitelisted-traffic-dev.yaml new file mode 100644 index 00000000..487ccf19 --- /dev/null +++ b/k8s/k8s-deny-all-non-whitelisted-traffic-dev.yaml @@ -0,0 +1,15 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: dev-deny-all + namespace: backend-dev +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress diff --git a/k8s/k8s-deny-all-non-whitelisted-traffic-qa-stg-prod.yaml b/k8s/k8s-deny-all-non-whitelisted-traffic-qa-stg-prod.yaml new file mode 100644 index 00000000..4ade2795 --- /dev/null +++ b/k8s/k8s-deny-all-non-whitelisted-traffic-qa-stg-prod.yaml @@ -0,0 +1,37 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: qa-deny-all + namespace: backend-qa +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: staging-deny-all + namespace: backend-staging +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: prod-deny-all + namespace: backend +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress diff --git a/k8s/k8s-rbac-ai.yaml b/k8s/k8s-rbac-ai.yaml new file mode 100644 index 00000000..e71c2336 --- /dev/null +++ b/k8s/k8s-rbac-ai.yaml @@ -0,0 +1,32 @@ +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: appinsights-k8s-property-reader +rules: +- apiGroups: ["","extensions"] + resources: ["pods","nodes", "replicasets","deployments"] + verbs: ["get", "watch", "list"] +--- +# actual binding to the role +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: appinsights-k8s-property-reader-binding +subjects: +- kind: ServiceAccount + name: default + namespace: backend +- kind: ServiceAccount + name: default + namespace: backend-dev +- kind: ServiceAccount + name: default + namespace: backend-qa +- kind: ServiceAccount + name: default + namespace: backend-staging +roleRef: + kind: ClusterRole + name: appinsights-k8s-property-reader + apiGroup: rbac.authorization.k8s.io + diff --git a/k8s/k8s-resource-quotas-dev.yaml b/k8s/k8s-resource-quotas-dev.yaml new file mode 100644 index 00000000..b919c811 --- /dev/null +++ b/k8s/k8s-resource-quotas-dev.yaml @@ -0,0 +1,17 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +apiVersion: v1 +kind: ResourceQuota +metadata: + name: dev + namespace: backend-dev +spec: + hard: + requests.cpu: "1" + requests.memory: 2Gi + limits.cpu: "2" + limits.memory: 5Gi + pods: "5" diff --git a/k8s/k8s-resource-quotas-qa-stg-prod.yaml b/k8s/k8s-resource-quotas-qa-stg-prod.yaml new file mode 100644 index 00000000..3e90cbd2 --- /dev/null +++ b/k8s/k8s-resource-quotas-qa-stg-prod.yaml @@ -0,0 +1,39 @@ +# ------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +# ------------------------------------------------------------ + +apiVersion: v1 +kind: ResourceQuota +metadata: + name: qa + namespace: backend-qa +spec: + hard: + requests.cpu: "1" + requests.memory: 2Gi + limits.cpu: "2" + limits.memory: 5Gi + pods: "5" +--- +apiVersion: v1 +kind: ResourceQuota +metadata: + name: staging + namespace: backend-staging +spec: + hard: + cpu: "50" + memory: 200Gi + pods: "250" +--- +apiVersion: v1 +kind: ResourceQuota +metadata: + name: prod + namespace: backend +spec: + hard: + cpu: "50" + memory: 200Gi + pods: "250" diff --git a/k8s/mockdeliveryscheduler.yaml b/k8s/mockdeliveryscheduler.yaml deleted file mode 100644 index 7b6b34f0..00000000 --- a/k8s/mockdeliveryscheduler.yaml +++ /dev/null @@ -1,66 +0,0 @@ -# ------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -# ------------------------------------------------------------ - -################################################################################################### -# Mock Delivery Scheduler service -################################################################################################### -apiVersion: v1 -kind: Service -metadata: - name: deliveryscheduler - labels: - app: deliveryscheduler - bc: shipping -spec: - ports: - - name: http - port: 80 - targetPort: 80 - selector: - app: deliveryscheduler - type: LoadBalancer ---- -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: deliveryscheduler - labels: # In API version apps/v1beta2, .metadata.labels no longer default to .spec.template.metadata.labels. - app: deliveryscheduler - version: 0.1.0 - bc: shipping -spec: - replicas: 1 - selector: # In API version apps/v1beta2, .spec.selector no longer default to .spec.template.metadata.labels. - matchLabels: # spec.selector is immutable after creation of the Deployment in apps/v1beta2. - app: deliveryscheduler - template: # A Deployment may terminate Pods whose labels match the selector if their template is different from .spec.template - metadata: - labels: - app: deliveryscheduler - version: 0.1.0 - bc: shipping - annotations: - team: deliveryschedulerservice - spec: - containers: - - name: deliveryscheduler - image: dronedelivery.azurecr.io/mockdeliveryscheduler:0.1.0 - env: - # Integration with l5d Daemon Sets - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - - name: POD_IP - valueFrom: - fieldRef: - fieldPath: status.podIP - - name: http_proxy - value: $(NODE_NAME):4140 - - name: CORRELATION_HEADER - value: l5d-ctx-trace - ports: - - name: service - containerPort: 80 diff --git a/k8s/package.yml b/k8s/package.yml deleted file mode 100644 index 3abb9e29..00000000 --- a/k8s/package.yml +++ /dev/null @@ -1,58 +0,0 @@ -# ------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -# ------------------------------------------------------------ - -apiVersion: apps/v1beta1 -kind: Deployment -metadata: - name: package - labels: - app: package - version: 0.1.0 - bc: shipping -spec: - replicas: 1 - selector: - matchLabels: - app: package - template: - metadata: - labels: - app: package - version: 0.1.0 - bc: shipping - spec: - containers: - - name: package - image: - env: - - name: CONNECTION_STRING - valueFrom: - secretKeyRef: - name: package-secrets - key: mongodb-pwd - - name: COLLECTION_NAME - value: packages - - name: CORRELATION_HEADER - value: l5d-ctx-trace - - name: LOG_LEVEL - value: error - ports: - - containerPort: 80 - imagePullPolicy: Always ---- -apiVersion: v1 -kind: Service -metadata: - name: package - labels: - app: package - bc: shipping -spec: - selector: - app: package - ports: - - name: http - port: 80 - clusterIP: None diff --git a/k8s/scheduler.yaml b/k8s/scheduler.yaml deleted file mode 100644 index 762f3bc4..00000000 --- a/k8s/scheduler.yaml +++ /dev/null @@ -1,89 +0,0 @@ -# ------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -# ------------------------------------------------------------ - -################################################################################################### -# Scheduler service -################################################################################################### -apiVersion: apps/v1beta1 -kind: StatefulSet -metadata: - name: scheduler - labels: - app: scheduler - bc: shipping -spec: - serviceName: scheduler - replicas: 32 - selector: - matchLabels: - app: scheduler - template: - metadata: - labels: - app: scheduler - version: 0.1.0 - bc: shipping - spec: - containers: - - image: - name: scheduler - imagePullPolicy: IfNotPresent - env: - - name: HOST_POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: SERVICE_URI_ACCOUNT - value: http://account/api/Account - - name: SERVICE_URI_DELIVERY - value: http://delivery/api/Deliveries - - name: SERVICE_URI_DRONE - value: http://dronescheduler/api/DroneDeliveries - - name: SERVICE_URI_PACKAGE - value: http://package/api/packages - - name: SERVICE_URI_THIRDPARTY - value: http://thirdparty/api/ThirdPartyDeliveries - ## set this env to read from period of time - ## otherwise is from last known checkpoint - - name: CHECKP_TIME_MINUTES - value: "0" - - name: IOTHUB_EVENTHUB_NAME - valueFrom: - secretKeyRef: - name: scheduler-secrets - key: eventhub_name - - name: IOTHUB_EVENTHUB_ENDPOINT - valueFrom: - secretKeyRef: - name: scheduler-secrets - key: eventhub_sas_connection_string - - name: IOTHUB_ACCESS_CONNSTRING - valueFrom: - secretKeyRef: - name: scheduler-secrets - key: eventhub_sas_connection_string - - name: IOTHUB_CHECKPOINT_AZSTORAGE_ACCOUNT - valueFrom: - secretKeyRef: - name: scheduler-secrets - key: storageaccount_name - - name: IOTHUB_CHECKPOINT_AZSTORAGE_KEY - valueFrom: - secretKeyRef: - name: scheduler-secrets - key: storageaccount_key - - name: STORAGE_QUEUE_NAME - value: "CompensatingTransactionQueue" - - name: STORAGE_QUEUE_CONNECTION_STRING - valueFrom: - secretKeyRef: - name: scheduler-secrets - key: queueconstring - - name: IOTHUB_EVENTHUB_PARTITIONS - value: "32" - - name: SERVICEMESH_HEADERS - value: l5d-ctx-deadline,l5d-ctx-trace - - name: SERVICEMESH_CORRELATION_HEADER - value: l5d-ctx-trace diff --git a/k8s/thirdparty.yaml b/k8s/thirdparty.yaml deleted file mode 100644 index f5563b03..00000000 --- a/k8s/thirdparty.yaml +++ /dev/null @@ -1,66 +0,0 @@ -# ------------------------------------------------------------ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -# ------------------------------------------------------------ - -################################################################################################### -# ThirdParty service -################################################################################################### -apiVersion: v1 -kind: Service -metadata: - name: thirdparty - labels: - app: thirdparty - bc: shipping -spec: - ports: - - name: http - port: 80 - targetPort: 80 - selector: - app: thirdparty - clusterIP: None ---- -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: thirdparty - labels: # In API version apps/v1beta2, .metadata.labels no longer default to .spec.template.metadata.labels. - app: thirdparty - version: 0.1.0 - bc: shipping -spec: - replicas: 1 - selector: # In API version apps/v1beta2, .spec.selector no longer default to .spec.template.metadata.labels. - matchLabels: # spec.selector is immutable after creation of the Deployment in apps/v1beta2. - app: thirdparty - template: # A Deployment may terminate Pods whose labels match the selector if their template is different from .spec.template - metadata: - labels: - app: thirdparty - bc: shipping - version: 0.1.0 - annotations: - team: thirdpartyservice - spec: - containers: - - name: thirdparty - image: - env: - # Integration with l5d Daemon Sets - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - - name: POD_IP - valueFrom: - fieldRef: - fieldPath: status.podIP - - name: http_proxy - value: $(NODE_NAME):4140 - - name: CORRELATION_HEADER - value: l5d-ctx-trace - ports: - - name: service - containerPort: 80 diff --git a/k8s/tiller-rbac.yaml b/k8s/tiller-rbac.yaml new file mode 100644 index 00000000..1fcf47dc --- /dev/null +++ b/k8s/tiller-rbac.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tiller + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: tiller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: + - kind: ServiceAccount + name: tiller + namespace: kube-system diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.Common/CorrelationLogEventEnricher.cs b/src/bc-shipping/delivery/Fabrikam.DroneDelivery.Common/CorrelationLogEventEnricher.cs deleted file mode 100644 index 19a0d130..00000000 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.Common/CorrelationLogEventEnricher.cs +++ /dev/null @@ -1,38 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -// ------------------------------------------------------------ - -using System.Linq; -using Microsoft.AspNetCore.Http; -using Serilog.Core; -using Serilog.Events; - -namespace Fabrikam.DroneDelivery.Common -{ - public class CorrelationLogEventEnricher : ILogEventEnricher - { - public const string CorrelationPropertyName = "CorrelationId"; - - public CorrelationLogEventEnricher(IHttpContextAccessor httpContextAccessor, string correlationIdHeaderKey) - { - HttpContextAccessor = httpContextAccessor; - CorrelationIdHeaderKey = correlationIdHeaderKey; - } - - public IHttpContextAccessor HttpContextAccessor { get; } - public string CorrelationIdHeaderKey { get; } - - public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) - { - if (HttpContextAccessor.HttpContext == null) return; - - var requestHeaders = HttpContextAccessor.HttpContext.Request.Headers; - var correlationId = requestHeaders[CorrelationIdHeaderKey].FirstOrDefault(); - if (correlationId != null) - { - logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(CorrelationPropertyName, correlationId)); - } - } - } -} diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.Common/Fabrikam.DroneDelivery.Common.csproj b/src/bc-shipping/delivery/Fabrikam.DroneDelivery.Common/Fabrikam.DroneDelivery.Common.csproj deleted file mode 100644 index 96305d20..00000000 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.Common/Fabrikam.DroneDelivery.Common.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - netcoreapp2.0 - - - - - - - - \ No newline at end of file diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/Fabrikam.DroneDelivery.DeliveryService.Tests.csproj b/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/Fabrikam.DroneDelivery.DeliveryService.Tests.csproj deleted file mode 100644 index b13e25b5..00000000 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/Fabrikam.DroneDelivery.DeliveryService.Tests.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - netcoreapp2.0 - - - - - - - - - - - - - - - - - - - diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Dockerfile b/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Dockerfile deleted file mode 100644 index 5aa81526..00000000 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM microsoft/aspnetcore:2.0 -ARG source -WORKDIR /app -EXPOSE 80 -COPY ${source:-obj/Docker/publish} . -ENTRYPOINT ["dotnet", "Fabrikam.DroneDelivery.DeliveryService.dll"] diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Program.cs b/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Program.cs deleted file mode 100644 index 9af68a79..00000000 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Program.cs +++ /dev/null @@ -1,23 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -// ------------------------------------------------------------ - -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; - -namespace Fabrikam.DroneDelivery.DeliveryService -{ - public class Program - { - public static void Main(string[] args) - { - BuildWebHost(args).Run(); - } - - public static IWebHost BuildWebHost(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseStartup() - .Build(); - } -} diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/DeliveryHistoryService.cs b/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/DeliveryHistoryService.cs deleted file mode 100644 index 34d17899..00000000 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/DeliveryHistoryService.cs +++ /dev/null @@ -1,26 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -// ------------------------------------------------------------ - -using System; -using System.Threading.Tasks; -using Fabrikam.DroneDelivery.DeliveryService.Models; -using Fabrikam.DroneDelivery.Common; - -namespace Fabrikam.DroneDelivery.DeliveryService.Services -{ - public class DeliveryHistoryService : IDeliveryHistoryService - { - public async Task CompleteAsync(InternalDelivery delivery, InternalConfirmation confirmation, params DeliveryTrackingEvent[] deliveryTrackingEvents) - { - //TODO: shallowing confirmation (TBD) - await EventHubSender.SendMessageAsync(new DeliveryHistory(delivery.Id, delivery, deliveryTrackingEvents), nameof(DeliveryStage.Completed), delivery.Id.Substring(0, Constants.PartitionKeyLength)).ConfigureAwait(continueOnCapturedContext: false); - } - - public async Task CancelAsync(InternalDelivery delivery, params DeliveryTrackingEvent[] deliveryTrackingEvents) - { - await EventHubSender.SendMessageAsync(new DeliveryHistory(delivery.Id, delivery, deliveryTrackingEvents), nameof(DeliveryStage.Cancelled), delivery.Id.Substring(0, Constants.PartitionKeyLength)).ConfigureAwait(continueOnCapturedContext: false); - } - } -} \ No newline at end of file diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/EventHubSender.cs b/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/EventHubSender.cs deleted file mode 100644 index f1c79f9f..00000000 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/EventHubSender.cs +++ /dev/null @@ -1,55 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -// ------------------------------------------------------------ - -using System; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Azure.EventHubs; -using Newtonsoft.Json; -using Fabrikam.DroneDelivery.DeliveryService.Models; - -namespace Fabrikam.DroneDelivery.DeliveryService.Services -{ - public static class EventHubSender where T : BaseMessage - { - private static string EhConnectionString; - private static string EhEntityPath; - - private static Lazy lazyConnection = new Lazy(() => - { - // it is does guarantee thread-safety - var connectionStringBuilder = new EventHubsConnectionStringBuilder(EhConnectionString) - { - EntityPath = EhEntityPath - }; - - return EventHubClient.CreateFromConnectionString(connectionStringBuilder.ToString()); - - }); - - private static EventHubClient connection - { - get - { - return lazyConnection.Value; - } - } - - public static void Configure(string connectionString, string entityPath) - { - EhConnectionString = connectionString; - EhEntityPath = entityPath; - } - - public static async Task SendMessageAsync(DeliveryHistory deliveryHistory, string messageType, string partitionKey) - { - deliveryHistory.PartitionKey = partitionKey; - deliveryHistory.MessageType = messageType; - string jsonDeliveryHistory = await Task.Factory.StartNew(() => JsonConvert.SerializeObject(deliveryHistory)); - // TODO: send a batch to EH improves the performance a lot. Therefore, instead of sending milestones, we could send them all in a batch (TBD) - await connection.SendAsync(new EventData(Encoding.UTF8.GetBytes(jsonDeliveryHistory)), partitionKey).ConfigureAwait(continueOnCapturedContext: false); - } - } -} \ No newline at end of file diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/IDeliveryHistoryService.cs b/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/IDeliveryHistoryService.cs deleted file mode 100644 index 3924edb4..00000000 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/IDeliveryHistoryService.cs +++ /dev/null @@ -1,16 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -// ------------------------------------------------------------ - -using System.Threading.Tasks; -using Fabrikam.DroneDelivery.DeliveryService.Models; - -namespace Fabrikam.DroneDelivery.DeliveryService.Services -{ - public interface IDeliveryHistoryService - { - Task CompleteAsync(InternalDelivery delivery, InternalConfirmation confirmation, params DeliveryTrackingEvent[] deliveryTrackingEvents); - Task CancelAsync(InternalDelivery delivery, params DeliveryTrackingEvent[] deliveryTrackingEvents); - } -} \ No newline at end of file diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.sln b/src/bc-shipping/delivery/Fabrikam.DroneDelivery.sln deleted file mode 100644 index daae1a2f..00000000 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.sln +++ /dev/null @@ -1,78 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.15 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fabrikam.DroneDelivery.DeliveryService", "Fabrikam.DroneDelivery.DeliveryService\Fabrikam.DroneDelivery.DeliveryService.csproj", "{D01A5347-3D4F-44F4-98C2-AD73D363631B}" -EndProject -Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{2670610E-2495-42AA-BB4C-915D0B74E5CE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MockDroneScheduler", "MockDroneScheduler\MockDroneScheduler.csproj", "{D3DE4852-7375-44CE-B3A3-C358473776FD}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MockServices", "MockServices", "{76102849-AD4D-4E0C-BE88-D212E0DDE108}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MockDeliveryScheduler", "MockDeliveryScheduler\MockDeliveryScheduler.csproj", "{6B8AA758-C692-4204-9F10-F4EB72C3868C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fabrikam.DroneDelivery.Common", "Fabrikam.DroneDelivery.Common\Fabrikam.DroneDelivery.Common.csproj", "{530D7BEC-E8B0-4B0A-B608-EAC10E4E858E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MockAccountService", "MockAccountService\MockAccountService.csproj", "{9C184612-E6AE-4A62-B66B-F7348B78E289}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MockThirdPartyService", "MockThirdPartyService\MockThirdPartyService.csproj", "{200F7B50-7D90-4BB8-BF28-87A3A3DA8CC2}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{4D81DB69-724B-44C2-A779-66778C21722E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fabrikam.DroneDelivery.DeliveryService.Tests", "Fabrikam.DroneDelivery.DeliveryService.Tests\Fabrikam.DroneDelivery.DeliveryService.Tests.csproj", "{5B8E1AE5-2C26-41D0-9035-74295F83FCB8}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D01A5347-3D4F-44F4-98C2-AD73D363631B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D01A5347-3D4F-44F4-98C2-AD73D363631B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D01A5347-3D4F-44F4-98C2-AD73D363631B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D01A5347-3D4F-44F4-98C2-AD73D363631B}.Release|Any CPU.Build.0 = Release|Any CPU - {2670610E-2495-42AA-BB4C-915D0B74E5CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2670610E-2495-42AA-BB4C-915D0B74E5CE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2670610E-2495-42AA-BB4C-915D0B74E5CE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2670610E-2495-42AA-BB4C-915D0B74E5CE}.Release|Any CPU.Build.0 = Release|Any CPU - {D3DE4852-7375-44CE-B3A3-C358473776FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D3DE4852-7375-44CE-B3A3-C358473776FD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D3DE4852-7375-44CE-B3A3-C358473776FD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D3DE4852-7375-44CE-B3A3-C358473776FD}.Release|Any CPU.Build.0 = Release|Any CPU - {6B8AA758-C692-4204-9F10-F4EB72C3868C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6B8AA758-C692-4204-9F10-F4EB72C3868C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6B8AA758-C692-4204-9F10-F4EB72C3868C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6B8AA758-C692-4204-9F10-F4EB72C3868C}.Release|Any CPU.Build.0 = Release|Any CPU - {530D7BEC-E8B0-4B0A-B608-EAC10E4E858E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {530D7BEC-E8B0-4B0A-B608-EAC10E4E858E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {530D7BEC-E8B0-4B0A-B608-EAC10E4E858E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {530D7BEC-E8B0-4B0A-B608-EAC10E4E858E}.Release|Any CPU.Build.0 = Release|Any CPU - {9C184612-E6AE-4A62-B66B-F7348B78E289}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9C184612-E6AE-4A62-B66B-F7348B78E289}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9C184612-E6AE-4A62-B66B-F7348B78E289}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9C184612-E6AE-4A62-B66B-F7348B78E289}.Release|Any CPU.Build.0 = Release|Any CPU - {200F7B50-7D90-4BB8-BF28-87A3A3DA8CC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {200F7B50-7D90-4BB8-BF28-87A3A3DA8CC2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {200F7B50-7D90-4BB8-BF28-87A3A3DA8CC2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {200F7B50-7D90-4BB8-BF28-87A3A3DA8CC2}.Release|Any CPU.Build.0 = Release|Any CPU - {5B8E1AE5-2C26-41D0-9035-74295F83FCB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5B8E1AE5-2C26-41D0-9035-74295F83FCB8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5B8E1AE5-2C26-41D0-9035-74295F83FCB8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5B8E1AE5-2C26-41D0-9035-74295F83FCB8}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {D3DE4852-7375-44CE-B3A3-C358473776FD} = {76102849-AD4D-4E0C-BE88-D212E0DDE108} - {6B8AA758-C692-4204-9F10-F4EB72C3868C} = {76102849-AD4D-4E0C-BE88-D212E0DDE108} - {9C184612-E6AE-4A62-B66B-F7348B78E289} = {76102849-AD4D-4E0C-BE88-D212E0DDE108} - {200F7B50-7D90-4BB8-BF28-87A3A3DA8CC2} = {76102849-AD4D-4E0C-BE88-D212E0DDE108} - {5B8E1AE5-2C26-41D0-9035-74295F83FCB8} = {4D81DB69-724B-44C2-A779-66778C21722E} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {246E8998-E2AB-41E6-B682-905A9F32CA9F} - EndGlobalSection -EndGlobal diff --git a/src/bc-shipping/delivery/MockAccountService/Controllers/AccountController.cs b/src/bc-shipping/delivery/MockAccountService/Controllers/AccountController.cs deleted file mode 100644 index d85c3bf6..00000000 --- a/src/bc-shipping/delivery/MockAccountService/Controllers/AccountController.cs +++ /dev/null @@ -1,32 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -// ------------------------------------------------------------ - -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace MockAccountService.Controllers -{ - [Route("api/[controller]")] - public class AccountController : Controller - { - private readonly ILogger logger; - - public AccountController(ILoggerFactory loggerFactory) - { - this.logger = loggerFactory.CreateLogger(); - } - - // GET api/account/{accountId} - [HttpGet("{accountId}")] - //TODO: [Authorize] - public bool Get(string accountId) - { - logger.LogInformation("In Get action with accountId: {AccountId}", accountId); - - return true; - } - } -} diff --git a/src/bc-shipping/delivery/MockAccountService/Dockerfile b/src/bc-shipping/delivery/MockAccountService/Dockerfile deleted file mode 100644 index b6096abe..00000000 --- a/src/bc-shipping/delivery/MockAccountService/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM microsoft/aspnetcore:2.0 -ARG source -WORKDIR /app -EXPOSE 80 -COPY ${source:-obj/Docker/publish} . -ENTRYPOINT ["dotnet", "MockAccountService.dll"] diff --git a/src/bc-shipping/delivery/MockAccountService/MockAccountService.csproj b/src/bc-shipping/delivery/MockAccountService/MockAccountService.csproj deleted file mode 100644 index 1fce6be8..00000000 --- a/src/bc-shipping/delivery/MockAccountService/MockAccountService.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - netcoreapp2.0 - ..\docker-compose.dcproj - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/bc-shipping/delivery/MockAccountService/Program.cs b/src/bc-shipping/delivery/MockAccountService/Program.cs deleted file mode 100644 index 16c24515..00000000 --- a/src/bc-shipping/delivery/MockAccountService/Program.cs +++ /dev/null @@ -1,24 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -// ------------------------------------------------------------ - -using System.IO; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; - -namespace MockAccountService -{ - public class Program - { - public static void Main(string[] args) - { - BuildWebHost(args).Run(); - } - - public static IWebHost BuildWebHost(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseStartup() - .Build(); - } -} diff --git a/src/bc-shipping/delivery/MockAccountService/Startup.cs b/src/bc-shipping/delivery/MockAccountService/Startup.cs deleted file mode 100644 index 0de176b0..00000000 --- a/src/bc-shipping/delivery/MockAccountService/Startup.cs +++ /dev/null @@ -1,72 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -// ------------------------------------------------------------ - -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Swashbuckle.AspNetCore.Swagger; -using Serilog; -using Serilog.Formatting.Compact; -using Fabrikam.DroneDelivery.Common; - -namespace MockAccountService -{ - public class Startup - { - public Startup(IHostingEnvironment env) - { - var builder = new ConfigurationBuilder() - .SetBasePath(env.ContentRootPath) - .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) - .AddEnvironmentVariables(); - Configuration = builder.Build(); - } - - public IConfigurationRoot Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddSingleton(); - - services.AddLogging(loggingBuilder => - loggingBuilder.AddSerilog(dispose: true)); - - // Add framework services. - services.AddMvc(); - - // Register the Swagger generator, defining one or more Swagger documents - services.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new Info { Title = "Mock AccountService API", Version = "v1" }); - }); - } - - // 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, IHttpContextAccessor httpContextAccessor) - { - Log.Logger = new LoggerConfiguration() - .WriteTo.Console(new CompactJsonFormatter()) - .ReadFrom.Configuration(Configuration) - .Enrich.With(new CorrelationLogEventEnricher(httpContextAccessor, Configuration["Logging:CorrelationHeaderKey"])) - .CreateLogger(); - - app.UseMvc(); - - // Enable middleware to serve generated Swagger as a JSON endpoint. - app.UseSwagger(); - - // Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint. - app.UseSwaggerUI(c => - { - c.SwaggerEndpoint("/swagger/v1/swagger.json", "Mock AccountService API V1"); - }); - } - } -} diff --git a/src/bc-shipping/delivery/MockDeliveryScheduler/.dockerignore b/src/bc-shipping/delivery/MockDeliveryScheduler/.dockerignore deleted file mode 100644 index d8f8175f..00000000 --- a/src/bc-shipping/delivery/MockDeliveryScheduler/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!obj/Docker/publish/* -!obj/Docker/empty/ diff --git a/src/bc-shipping/delivery/MockDeliveryScheduler/Controllers/HomeController.cs b/src/bc-shipping/delivery/MockDeliveryScheduler/Controllers/HomeController.cs deleted file mode 100644 index dfb37d69..00000000 --- a/src/bc-shipping/delivery/MockDeliveryScheduler/Controllers/HomeController.cs +++ /dev/null @@ -1,75 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -// ------------------------------------------------------------ - -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using System.Text; -using System; -using Microsoft.Extensions.Logging; - -namespace MockDeliveryScheduler.Controllers -{ - public class HomeController : Controller - { - private readonly IConfigurationRoot _configuration; - private readonly ILogger _logger; - - public HomeController(IConfigurationRoot configuration, ILoggerFactory loggerFactory) - { - _configuration = configuration; - _logger = loggerFactory.CreateLogger(); - } - - public async Task Index() - { - _logger.LogInformation("In Index action!!!"); - - var guid = Guid.NewGuid(); - var client = new HttpClient(); - client.DefaultRequestHeaders.Add("l5d-ctx-trace", $"TestCorrelationId{guid}"); - - var deliveryId = $"deliveryId{guid}"; - var droneDelivery = @"{""deliveryId"": """ + deliveryId + @""", ""pickup"": {""altitude"": 1, ""latitude"": 2, ""longitude"": 3}, ""dropoff"": {""altitude"": 3, ""latitude"": 2, ""longitude"": 1}, ""packageDetails"": [{""id"": ""packageId"", ""size"": 0}], ""expedited"": true}"; - var response = await client.PutAsync($"http://dronescheduler/api/dronedeliveries/{deliveryId}" , new StringContent(droneDelivery, Encoding.UTF8, "application/json")); - var droneId = await response.Content.ReadAsStringAsync(); - ViewBag.Message1 = droneId; - - var delivery = @"{ - ""id"": """ + deliveryId + @""", - ""owner"": { - ""userId"": ""user123"", - ""accountId"": ""account123"" - }, - ""pickup"": { - ""altitude"": 1, - ""latitude"": 2, - ""longitude"": 3 - }, - ""dropoff"": { - ""altitude"": 3, - ""latitude"": 2, - ""longitude"": 1 - }, - ""packageIds"": [ - ""packageId1"", ""packageId2"" - ], - ""deadline"": ""deadline"", - ""expedited"": true, - ""droneId"": """ + droneId + @""" - }"; - response = await client.PutAsync($"http://delivery/api/deliveries/{deliveryId}", new StringContent(delivery, Encoding.UTF8, "application/json")); - ViewBag.Message2 = await response.Content.ReadAsStringAsync(); - - return View(); - } - - public IActionResult Error() - { - return View(); - } - } -} diff --git a/src/bc-shipping/delivery/MockDeliveryScheduler/Dockerfile b/src/bc-shipping/delivery/MockDeliveryScheduler/Dockerfile deleted file mode 100644 index 9f8c901a..00000000 --- a/src/bc-shipping/delivery/MockDeliveryScheduler/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM microsoft/aspnetcore:2.0 -ARG source -WORKDIR /app -EXPOSE 80 -COPY ${source:-obj/Docker/publish} . -ENTRYPOINT ["dotnet", "MockDeliveryScheduler.dll"] diff --git a/src/bc-shipping/delivery/MockDeliveryScheduler/MockDeliveryScheduler.csproj b/src/bc-shipping/delivery/MockDeliveryScheduler/MockDeliveryScheduler.csproj deleted file mode 100644 index ab929bab..00000000 --- a/src/bc-shipping/delivery/MockDeliveryScheduler/MockDeliveryScheduler.csproj +++ /dev/null @@ -1,31 +0,0 @@ - - - - netcoreapp2.0 - - - - ..\docker-compose.dcproj - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/bc-shipping/delivery/MockDeliveryScheduler/Program.cs b/src/bc-shipping/delivery/MockDeliveryScheduler/Program.cs deleted file mode 100644 index 94d3abc7..00000000 --- a/src/bc-shipping/delivery/MockDeliveryScheduler/Program.cs +++ /dev/null @@ -1,28 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -// ------------------------------------------------------------ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; - -namespace MockDeliveryScheduler -{ - public class Program - { - public static void Main(string[] args) - { - BuildWebHost(args).Run(); - } - - public static IWebHost BuildWebHost(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseStartup() - .Build(); - } -} diff --git a/src/bc-shipping/delivery/MockDeliveryScheduler/Startup.cs b/src/bc-shipping/delivery/MockDeliveryScheduler/Startup.cs deleted file mode 100644 index 0143adc9..00000000 --- a/src/bc-shipping/delivery/MockDeliveryScheduler/Startup.cs +++ /dev/null @@ -1,74 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -// ------------------------------------------------------------ - -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.AspNetCore.Http; -using Serilog; -using Serilog.Formatting.Compact; -using Fabrikam.DroneDelivery.Common; - -namespace MockDeliveryScheduler -{ - public class Startup - { - public Startup(IHostingEnvironment env) - { - var builder = new ConfigurationBuilder() - .SetBasePath(env.ContentRootPath) - .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) - .AddEnvironmentVariables(); - Configuration = builder.Build(); - } - - public IConfigurationRoot Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddSingleton(); - - services.AddLogging(loggingBuilder => - loggingBuilder.AddSerilog(dispose: true)); - - // Add framework services. - services.AddMvc(); - services.AddSingleton(Configuration); - } - - // 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, IHttpContextAccessor httpContextAccessor) - { - Log.Logger = new LoggerConfiguration() - .WriteTo.Console(new CompactJsonFormatter()) - .ReadFrom.Configuration(Configuration) - .Enrich.With(new CorrelationLogEventEnricher(httpContextAccessor, Configuration["Logging:CorrelationHeaderKey"])) - .CreateLogger(); - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - app.UseBrowserLink(); - } - else - { - app.UseExceptionHandler("/Home/Error"); - } - - app.UseStaticFiles(); - - app.UseMvc(routes => - { - routes.MapRoute( - name: "default", - template: "{controller=Home}/{action=Index}/{id?}"); - }); - } - } -} diff --git a/src/bc-shipping/delivery/MockDeliveryScheduler/Views/Home/Index.cshtml b/src/bc-shipping/delivery/MockDeliveryScheduler/Views/Home/Index.cshtml deleted file mode 100644 index f148e52b..00000000 --- a/src/bc-shipping/delivery/MockDeliveryScheduler/Views/Home/Index.cshtml +++ /dev/null @@ -1,5 +0,0 @@ -@{ - ViewData["Title"] = "Home Page"; -} -

DroneScheduler PUT: @ViewBag.Message1

-

DeliveryService PUT: @ViewBag.Message2

\ No newline at end of file diff --git a/src/bc-shipping/delivery/MockDeliveryScheduler/Views/Shared/Error.cshtml b/src/bc-shipping/delivery/MockDeliveryScheduler/Views/Shared/Error.cshtml deleted file mode 100644 index e514139c..00000000 --- a/src/bc-shipping/delivery/MockDeliveryScheduler/Views/Shared/Error.cshtml +++ /dev/null @@ -1,14 +0,0 @@ -@{ - ViewData["Title"] = "Error"; -} - -

Error.

-

An error occurred while processing your request.

- -

Development Mode

-

- Swapping to Development environment will display more detailed information about the error that occurred. -

-

- Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. -

diff --git a/src/bc-shipping/delivery/MockDeliveryScheduler/Views/Shared/_Layout.cshtml b/src/bc-shipping/delivery/MockDeliveryScheduler/Views/Shared/_Layout.cshtml deleted file mode 100644 index 1e4ac33b..00000000 --- a/src/bc-shipping/delivery/MockDeliveryScheduler/Views/Shared/_Layout.cshtml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - @ViewData["Title"] - MockDeliveryScheduler - - - - @RenderBody() - - diff --git a/src/bc-shipping/delivery/MockDeliveryScheduler/Views/Shared/_ValidationScriptsPartial.cshtml b/src/bc-shipping/delivery/MockDeliveryScheduler/Views/Shared/_ValidationScriptsPartial.cshtml deleted file mode 100644 index 27e0ea7c..00000000 --- a/src/bc-shipping/delivery/MockDeliveryScheduler/Views/Shared/_ValidationScriptsPartial.cshtml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - diff --git a/src/bc-shipping/delivery/MockDeliveryScheduler/Views/_ViewImports.cshtml b/src/bc-shipping/delivery/MockDeliveryScheduler/Views/_ViewImports.cshtml deleted file mode 100644 index 7c55c2e3..00000000 --- a/src/bc-shipping/delivery/MockDeliveryScheduler/Views/_ViewImports.cshtml +++ /dev/null @@ -1,2 +0,0 @@ -@using MockDeliveryScheduler -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/src/bc-shipping/delivery/MockDeliveryScheduler/Views/_ViewStart.cshtml b/src/bc-shipping/delivery/MockDeliveryScheduler/Views/_ViewStart.cshtml deleted file mode 100644 index a5f10045..00000000 --- a/src/bc-shipping/delivery/MockDeliveryScheduler/Views/_ViewStart.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@{ - Layout = "_Layout"; -} diff --git a/src/bc-shipping/delivery/MockDeliveryScheduler/appsettings.Development.json b/src/bc-shipping/delivery/MockDeliveryScheduler/appsettings.Development.json deleted file mode 100644 index fa8ce71a..00000000 --- a/src/bc-shipping/delivery/MockDeliveryScheduler/appsettings.Development.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } - } -} diff --git a/src/bc-shipping/delivery/MockDeliveryScheduler/wwwroot/favicon.ico b/src/bc-shipping/delivery/MockDeliveryScheduler/wwwroot/favicon.ico deleted file mode 100644 index a3a79998..00000000 Binary files a/src/bc-shipping/delivery/MockDeliveryScheduler/wwwroot/favicon.ico and /dev/null differ diff --git a/src/bc-shipping/delivery/MockDroneScheduler/.dockerignore b/src/bc-shipping/delivery/MockDroneScheduler/.dockerignore deleted file mode 100644 index d8f8175f..00000000 --- a/src/bc-shipping/delivery/MockDroneScheduler/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!obj/Docker/publish/* -!obj/Docker/empty/ diff --git a/src/bc-shipping/delivery/MockDroneScheduler/Controllers/DroneDeliveriesController.cs b/src/bc-shipping/delivery/MockDroneScheduler/Controllers/DroneDeliveriesController.cs deleted file mode 100644 index 5920d2c4..00000000 --- a/src/bc-shipping/delivery/MockDroneScheduler/Controllers/DroneDeliveriesController.cs +++ /dev/null @@ -1,40 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -// ------------------------------------------------------------ - -using System; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using MockDroneScheduler.Models; - -namespace MockDroneScheduler.Controllers -{ - [Route("api/[controller]")] - public class DroneDeliveriesController : Controller - { - private readonly ILogger logger; - - public DroneDeliveriesController(ILoggerFactory loggerFactory) - { - this.logger = loggerFactory.CreateLogger(); - } - - // PUT api/dronedeliveries/5 - [HttpPut("{id}")] - public string Put([FromBody]DroneDelivery droneDelivery, string id) - { - logger.LogInformation("In Put action with DeliveryId: {DeliveryId}", id); - - var guid = Guid.NewGuid(); - return $"AssignedDroneId{guid}"; - } - - // DELETE api/dronedeliveries/5 - [HttpDelete("{id}")] - public void Delete(string id) - { - logger.LogInformation("In Delete action with DeliveryId: {DeliveryId}", id); - } - } -} diff --git a/src/bc-shipping/delivery/MockDroneScheduler/Dockerfile b/src/bc-shipping/delivery/MockDroneScheduler/Dockerfile deleted file mode 100644 index 90333913..00000000 --- a/src/bc-shipping/delivery/MockDroneScheduler/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM microsoft/aspnetcore:2.0 -ARG source -WORKDIR /app -EXPOSE 80 -COPY ${source:-obj/Docker/publish} . -ENTRYPOINT ["dotnet", "MockDroneScheduler.dll"] diff --git a/src/bc-shipping/delivery/MockDroneScheduler/MockDroneScheduler.csproj b/src/bc-shipping/delivery/MockDroneScheduler/MockDroneScheduler.csproj deleted file mode 100644 index 1fce6be8..00000000 --- a/src/bc-shipping/delivery/MockDroneScheduler/MockDroneScheduler.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - netcoreapp2.0 - ..\docker-compose.dcproj - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/bc-shipping/delivery/MockDroneScheduler/Program.cs b/src/bc-shipping/delivery/MockDroneScheduler/Program.cs deleted file mode 100644 index 42cacc20..00000000 --- a/src/bc-shipping/delivery/MockDroneScheduler/Program.cs +++ /dev/null @@ -1,28 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -// ------------------------------------------------------------ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; - -namespace MockDroneScheduler -{ - public class Program - { - public static void Main(string[] args) - { - BuildWebHost(args).Run(); - } - - public static IWebHost BuildWebHost(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseStartup() - .Build(); - } -} diff --git a/src/bc-shipping/delivery/MockDroneScheduler/appsettings.Development.json b/src/bc-shipping/delivery/MockDroneScheduler/appsettings.Development.json deleted file mode 100644 index fa8ce71a..00000000 --- a/src/bc-shipping/delivery/MockDroneScheduler/appsettings.Development.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } - } -} diff --git a/src/bc-shipping/delivery/MockThirdPartyService/.dockerignore b/src/bc-shipping/delivery/MockThirdPartyService/.dockerignore deleted file mode 100644 index d8f8175f..00000000 --- a/src/bc-shipping/delivery/MockThirdPartyService/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!obj/Docker/publish/* -!obj/Docker/empty/ diff --git a/src/bc-shipping/delivery/MockThirdPartyService/Controllers/ThirdPartyDeliveriesController.cs b/src/bc-shipping/delivery/MockThirdPartyService/Controllers/ThirdPartyDeliveriesController.cs deleted file mode 100644 index e9589eaa..00000000 --- a/src/bc-shipping/delivery/MockThirdPartyService/Controllers/ThirdPartyDeliveriesController.cs +++ /dev/null @@ -1,31 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -// ------------------------------------------------------------ - -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using Fabrikam.DroneDelivery.Common; - -namespace MockThirdPartyService.Controllers -{ - [Route("api/[controller]")] - public class ThirdPartyDeliveriesController : Controller - { - private readonly ILogger logger; - - public ThirdPartyDeliveriesController(ILoggerFactory loggerFactory) - { - this.logger = loggerFactory.CreateLogger(); - } - - // PUT api/thirdpartydeliveries/5 - [HttpPut("{id}")] - public IActionResult Put([FromBody]Location pickup, [FromBody]Location dropoff, string id) - { - logger.LogInformation("In Put action with DeliveryId: {DeliveryId}", id); - - return Ok(false); - } - } -} diff --git a/src/bc-shipping/delivery/MockThirdPartyService/Dockerfile b/src/bc-shipping/delivery/MockThirdPartyService/Dockerfile deleted file mode 100644 index 7a7d7017..00000000 --- a/src/bc-shipping/delivery/MockThirdPartyService/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM microsoft/aspnetcore:2.0 -ARG source -WORKDIR /app -EXPOSE 80 -COPY ${source:-obj/Docker/publish} . -ENTRYPOINT ["dotnet", "MockThirdPartyService.dll"] diff --git a/src/bc-shipping/delivery/MockThirdPartyService/MockThirdPartyService.csproj b/src/bc-shipping/delivery/MockThirdPartyService/MockThirdPartyService.csproj deleted file mode 100644 index 1fce6be8..00000000 --- a/src/bc-shipping/delivery/MockThirdPartyService/MockThirdPartyService.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - netcoreapp2.0 - ..\docker-compose.dcproj - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/bc-shipping/delivery/MockThirdPartyService/Program.cs b/src/bc-shipping/delivery/MockThirdPartyService/Program.cs deleted file mode 100644 index dc276e40..00000000 --- a/src/bc-shipping/delivery/MockThirdPartyService/Program.cs +++ /dev/null @@ -1,28 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -// ------------------------------------------------------------ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; - -namespace MockThirdPartyService -{ - public class Program - { - public static void Main(string[] args) - { - BuildWebHost(args).Run(); - } - - public static IWebHost BuildWebHost(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseStartup() - .Build(); - } -} diff --git a/src/bc-shipping/delivery/MockThirdPartyService/Startup.cs b/src/bc-shipping/delivery/MockThirdPartyService/Startup.cs deleted file mode 100644 index ecc259df..00000000 --- a/src/bc-shipping/delivery/MockThirdPartyService/Startup.cs +++ /dev/null @@ -1,72 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -// ------------------------------------------------------------ - -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Swashbuckle.AspNetCore.Swagger; -using Serilog; -using Serilog.Formatting.Compact; -using Fabrikam.DroneDelivery.Common; - -namespace MockThirdPartyService -{ - public class Startup - { - public Startup(IHostingEnvironment env) - { - var builder = new ConfigurationBuilder() - .SetBasePath(env.ContentRootPath) - .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) - .AddEnvironmentVariables(); - Configuration = builder.Build(); - } - - public IConfigurationRoot Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddSingleton(); - - services.AddLogging(loggingBuilder => - loggingBuilder.AddSerilog(dispose: true)); - - // Add framework services. - services.AddMvc(); - - // Register the Swagger generator, defining one or more Swagger documents - services.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new Info { Title = "Mock ThirdPartyService API", Version = "v1" }); - }); - } - - // 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, IHttpContextAccessor httpContextAccessor) - { - Log.Logger = new LoggerConfiguration() - .WriteTo.Console(new CompactJsonFormatter()) - .ReadFrom.Configuration(Configuration) - .Enrich.With(new CorrelationLogEventEnricher(httpContextAccessor, Configuration["Logging:CorrelationHeaderKey"])) - .CreateLogger(); - - app.UseMvc(); - - // Enable middleware to serve generated Swagger as a JSON endpoint. - app.UseSwagger(); - - // Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint. - app.UseSwaggerUI(c => - { - c.SwaggerEndpoint("/swagger/v1/swagger.json", "Mock ThirdPartyService API V1"); - }); - } - } -} diff --git a/src/bc-shipping/delivery/MockThirdPartyService/appsettings.Development.json b/src/bc-shipping/delivery/MockThirdPartyService/appsettings.Development.json deleted file mode 100644 index fa8ce71a..00000000 --- a/src/bc-shipping/delivery/MockThirdPartyService/appsettings.Development.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } - } -} diff --git a/src/bc-shipping/delivery/MockThirdPartyService/appsettings.json b/src/bc-shipping/delivery/MockThirdPartyService/appsettings.json deleted file mode 100644 index 815fe795..00000000 --- a/src/bc-shipping/delivery/MockThirdPartyService/appsettings.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Information" - }, - "CorrelationHeaderKey": "l5d-ctx-trace" - }, - "Serilog": { - "MinimumLevel": "Verbose", - "Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ], - "WriteTo": [] - } -} \ No newline at end of file diff --git a/src/bc-shipping/delivery/docker-compose.ci.build.yml b/src/bc-shipping/delivery/docker-compose.ci.build.yml deleted file mode 100644 index 4bf45581..00000000 --- a/src/bc-shipping/delivery/docker-compose.ci.build.yml +++ /dev/null @@ -1,9 +0,0 @@ -version: '3' - -services: - ci-build: - image: microsoft/aspnetcore-build:1.0-2.0 - volumes: - - .:/src - working_dir: /src - command: /bin/bash -c "dotnet restore ./Fabrikam.DroneDelivery.sln && dotnet test Fabrikam.DroneDelivery.DeliveryService.Tests/Fabrikam.DroneDelivery.DeliveryService.Tests.csproj && dotnet publish ./Fabrikam.DroneDelivery.sln -c Release -o ./obj/Docker/publish" diff --git a/src/bc-shipping/delivery/docker-compose.dcproj b/src/bc-shipping/delivery/docker-compose.dcproj deleted file mode 100644 index 6f92515a..00000000 --- a/src/bc-shipping/delivery/docker-compose.dcproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - 2670610e-2495-42aa-bb4c-915d0b74e5ce - True - http://localhost:{ServicePort}/api/values - fabrikam.dronedelivery.deliveryservice - Linux - 2.0 - - - - - docker-compose.yml - - - - \ No newline at end of file diff --git a/src/bc-shipping/delivery/docker-compose.override.yml b/src/bc-shipping/delivery/docker-compose.override.yml deleted file mode 100644 index 9bb326a3..00000000 --- a/src/bc-shipping/delivery/docker-compose.override.yml +++ /dev/null @@ -1,32 +0,0 @@ -version: '3' - -services: - delivery: - environment: - - ASPNETCORE_ENVIRONMENT=Development - ports: - - "80" - - dronescheduler: - environment: - - ASPNETCORE_ENVIRONMENT=Development - ports: - - "80" - - deliveryscheduler: - environment: - - ASPNETCORE_ENVIRONMENT=Development - ports: - - "80" - - account: - environment: - - ASPNETCORE_ENVIRONMENT=Development - ports: - - "80" - - thirdparty: - environment: - - ASPNETCORE_ENVIRONMENT=Development - ports: - - "80" diff --git a/src/bc-shipping/delivery/docker-compose.yml b/src/bc-shipping/delivery/docker-compose.yml deleted file mode 100644 index e1824ea8..00000000 --- a/src/bc-shipping/delivery/docker-compose.yml +++ /dev/null @@ -1,32 +0,0 @@ -version: '3' - -services: - delivery: - image: fabrikam.dronedelivery.deliveryservice - build: - context: ./Fabrikam.DroneDelivery.DeliveryService - dockerfile: Dockerfile - - dronescheduler: - image: mockdronescheduler - build: - context: ./MockDroneScheduler - dockerfile: Dockerfile - - deliveryscheduler: - image: mockdeliveryscheduler - build: - context: ./MockDeliveryScheduler - dockerfile: Dockerfile - - account: - image: mockaccountservice - build: - context: ./MockAccountService - dockerfile: Dockerfile - - thirdparty: - image: mockthirdpartyservice - build: - context: ./MockThirdPartyService - dockerfile: Dockerfile diff --git a/src/bc-shipping/deliveryhistory/Model.csx b/src/bc-shipping/deliveryhistory/Model.csx deleted file mode 100644 index d7d9b72d..00000000 --- a/src/bc-shipping/deliveryhistory/Model.csx +++ /dev/null @@ -1,184 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using Newtonsoft.Json; - - public abstract class BaseCache - { - public abstract string Key { get; } - } - public class DateTimeStamp - { - public DateTimeStamp(string dateTimeValue) - { - DateTimeValue = dateTimeValue; - } - - public string DateTimeValue { get; set; } - } - public class Location - { - public Location(double altitude, double latitude, double longitude) - { - Altitude = altitude; - Latitude = latitude; - Longitude = longitude; - } - public double Altitude { get; } - public double Latitude { get; } - public double Longitude { get; } - } - public enum ConfirmationType - { - FingerPrint, - Picture, - Voice, - None - } - public class Confirmation - { - public Confirmation(DateTimeStamp dateTime, Location geoCoordinates, ConfirmationType confirmationType, string confirmationBlob) - { - DateTime = dateTime; - GeoCoordinates = geoCoordinates; - ConfirmationType = confirmationType; - ConfirmationBlob = confirmationBlob; - } - public DateTimeStamp DateTime { get; } - public Location GeoCoordinates { get; } - public ConfirmationType ConfirmationType { get; } - public string ConfirmationBlob { get; } - } - public enum DeliveryStage - { - Created, - Rescheduled, - HeadedToPickup, - HeadedToDropoff, - Completed, - Cancelled - } - public class DeliveryTrackingEvent : BaseCache - { - public string DeliveryId { get; set; } - public DeliveryStage Stage { get; set; } - public Location Location { get; set; } - public override string Key => $"{this.DeliveryId}_{this.Stage.ToString()}"; - } - - public class InternalDelivery : BaseCache - { - public InternalDelivery(string id, - UserAccount owner, - Location pickup, - Location dropoff, - ReadOnlyCollection packageids, - string deadline, - bool expedited, - ConfirmationType confirmationRequired, - string droneid) - { - Id = id; - Owner = owner; - Pickup = pickup; - Dropoff = dropoff; - PackageIds = packageids; - Deadline = deadline; - Expedited = expedited; - ConfirmationRequired = confirmationRequired; - DroneId = droneid; - } - - [JsonProperty(PropertyName = "id")] - public string Id { get; } - public UserAccount Owner { get; } - public Location Pickup { get; } - public Location Dropoff { get; } - public ReadOnlyCollection PackageIds { get; } - public string Deadline { get; } - public bool Expedited { get; } - public ConfirmationType ConfirmationRequired { get; } - public string DroneId { get; } - public override string Key => this.Id; - } - public class BaseMessage - { - [JsonProperty(PropertyName = "partitionKey")] - public string PartitionKey { get; set; } - public string MessageType { get; set; } - - } - - public class UserAccount - { - public UserAccount(string userid, string accountid) - { - UserId = userid; - AccountId = accountid; - } - public string UserId { get; } - public string AccountId { get; } - } - public class DeliveryStatus - { - public DeliveryStatus(DeliveryStage deliveryStage, Location lastKnownLocation, string pickupeta, string deliveryeta) - { - Stage = deliveryStage; - LastKnownLocation = lastKnownLocation; - PickupETA = pickupeta; - DeliveryETA = deliveryeta; - } - public DeliveryStage Stage { get; } - public Location LastKnownLocation { get; } - public string PickupETA { get; } - public string DeliveryETA { get; } - } - public class Delivery - { - public Delivery(string id, - UserAccount owner, - Location pickup, - Location dropoff, - ReadOnlyCollection packageids, - string deadline, - bool expedited, - ConfirmationType confirmationRequired, - string droneid) - { - Id = id; - Owner = owner; - Pickup = pickup; - Dropoff = dropoff; - PackageIds = packageids; - Deadline = deadline; - Expedited = expedited; - ConfirmationRequired = confirmationRequired; - DroneId = droneid; - } - - public string Id { get; } - public UserAccount Owner { get; } - public Location Pickup { get; } - public Location Dropoff { get; } - public ReadOnlyCollection PackageIds { get; } - public string Deadline { get; } - public bool Expedited { get; } - public ConfirmationType ConfirmationRequired { get; } - public string DroneId { get; } - } - public class DeliveryHistory : BaseMessage - { - public DeliveryHistory(string id, - InternalDelivery delivery, - params DeliveryTrackingEvent[] deliveryTrackingEvents) - { - Id = id; - Delivery = delivery; - DeliveryTrackingEvents = deliveryTrackingEvents; - } - - [JsonProperty(PropertyName = "id")] - public string Id { get; } - public InternalDelivery Delivery { get; } - public DeliveryTrackingEvent[] DeliveryTrackingEvents { get; } - } - diff --git a/src/bc-shipping/deliveryhistory/function.json b/src/bc-shipping/deliveryhistory/function.json deleted file mode 100644 index 28f17dc7..00000000 --- a/src/bc-shipping/deliveryhistory/function.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "bindings": [ - { - "type": "eventHubTrigger", - "name": "EventHubMessages", - "direction": "in", - "path": "deliveryeventstream", - "connection": "Your shared access policy", - "consumerGroup": "Your consumer group", - "cardinality": "many" - }, - { - "type": "documentDB", - "name": "outputDocument", - "databaseName": "outDatabase", - "collectionName": "MyCollection", - "createIfNotExists": false, - "partitionKey": "/PartitionKey", - "connection": "Your DocumentDB", - "direction": "out" - } - ], - "disabled": false -} diff --git a/src/bc-shipping/deliveryhistory/historydocument.csx b/src/bc-shipping/deliveryhistory/historydocument.csx deleted file mode 100644 index ab3e5a7b..00000000 --- a/src/bc-shipping/deliveryhistory/historydocument.csx +++ /dev/null @@ -1,17 +0,0 @@ -#load "Model.csx" -using System; - - -public class HistoryDocument -{ - public HistoryDocument(DeliveryHistory history) - { - //Set the first character of deliveryID as partition key - this.PartitionKey = history.Id.ToCharArray()[0].ToString(); - this.DeliveryTrackingEvents = history.DeliveryTrackingEvents; - - } - public string PartitionKey {get;} - public DeliveryTrackingEvent[] DeliveryTrackingEvents { get; } - -} diff --git a/src/bc-shipping/deliveryhistory/project.json b/src/bc-shipping/deliveryhistory/project.json deleted file mode 100644 index a7d296cd..00000000 --- a/src/bc-shipping/deliveryhistory/project.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "frameworks": { - "net46":{ - "dependencies": { - "Newtonsoft.Json": "6.0.8" - } - } - } -} diff --git a/src/bc-shipping/deliveryhistory/run.csx b/src/bc-shipping/deliveryhistory/run.csx deleted file mode 100644 index a9abe37b..00000000 --- a/src/bc-shipping/deliveryhistory/run.csx +++ /dev/null @@ -1,23 +0,0 @@ -#r "Microsoft.ServiceBus" -#load "Model.csx" -#load "historydocument.csx" - -using System; -using System.Text; -using Microsoft.ServiceBus.Messaging; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - - -public static async Task Run(EventData[] EventHubMessages, IAsyncCollector outputDocument, TraceWriter log) -{ - foreach (var message in EventHubMessages) - { - // Save each event in CosmosDB - var sr = new StreamReader(message.GetBodyStream()); - var serializer = new JsonSerializer(); - DeliveryHistory history = (DeliveryHistory)serializer.Deserialize(sr, typeof(DeliveryHistory)); - HistoryDocument historyDocument = new HistoryDocument(history); - await outputDocument.AddAsync(historyDocument); - } -} diff --git a/src/bc-shipping/ingestion/Dockerfile b/src/bc-shipping/ingestion/Dockerfile deleted file mode 100644 index 7e33186b..00000000 --- a/src/bc-shipping/ingestion/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM openjdk:8-jre-alpine - -ENTRYPOINT ["/usr/bin/java", "-jar", "/usr/share/dronedelivery/Ingestion-1.0.0.jar"] - -# Add Maven dependencies (not shaded into the artifact; Docker-cached) -COPY target /usr/share/dronedelivery diff --git a/src/bc-shipping/ingestion/Dockerfilemaven b/src/bc-shipping/ingestion/Dockerfilemaven deleted file mode 100644 index 2188d84a..00000000 --- a/src/bc-shipping/ingestion/Dockerfilemaven +++ /dev/null @@ -1,25 +0,0 @@ -FROM openjdk:8-jdk - -ARG MAVEN_VERSION=3.5.2 -ARG USER_HOME_DIR="/root" -ARG SHA=707b1f6e390a65bde4af4cdaf2a24d45fc19a6ded00fff02e91626e3e42ceaff -ARG BASE_URL=https://apache.osuosl.org/maven/maven-3/${MAVEN_VERSION}/binaries - -RUN mkdir -p /usr/share/maven /usr/share/maven/ref \ - && curl -fsSL -o /tmp/apache-maven.tar.gz ${BASE_URL}/apache-maven-${MAVEN_VERSION}-bin.tar.gz \ - && echo "${SHA} /tmp/apache-maven.tar.gz" | sha256sum -c - \ - && tar -xzf /tmp/apache-maven.tar.gz -C /usr/share/maven --strip-components=1 \ - && rm -f /tmp/apache-maven.tar.gz \ - && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn - -ENV MAVEN_HOME /usr/share/maven -ENV MAVEN_CONFIG "$USER_HOME_DIR/.m2" -ENV SOURCEPATH /sln - -COPY mvn-entrypoint.sh /usr/local/bin/mvn-entrypoint.sh -COPY settings-docker.xml /usr/share/maven/ref/ -RUN chmod +x /usr/local/bin/mvn-entrypoint.sh -VOLUME "$USER_HOME_DIR/.m2" - -ENTRYPOINT ["/usr/local/bin/mvn-entrypoint.sh"] -CMD ["mvn","package"] diff --git a/src/bc-shipping/ingestion/mvn-entrypoint.sh b/src/bc-shipping/ingestion/mvn-entrypoint.sh deleted file mode 100644 index f8ed7fda..00000000 --- a/src/bc-shipping/ingestion/mvn-entrypoint.sh +++ /dev/null @@ -1,41 +0,0 @@ -#! /bin/bash -eu - -set -o pipefail - -# Copy files from /usr/share/maven/ref into ${MAVEN_CONFIG} -# So the initial ~/.m2 is set with expected content. -# Don't override, as this is just a reference setup -copy_reference_file() { - local root="${1}" - local f="${2%/}" - local logfile="${3}" - local rel="${f/${root}/}" # path relative to /usr/share/maven/ref/ - echo "$f" >> "$logfile" - echo " $f -> $rel" >> "$logfile" - if [[ ! -e ${MAVEN_CONFIG}/${rel} || $f = *.override ]] - then - echo "copy $rel to ${MAVEN_CONFIG}" >> "$logfile" - mkdir -p "${MAVEN_CONFIG}/$(dirname "${rel}")" - cp -r "${f}" "${MAVEN_CONFIG}/${rel}"; - fi; -} - -copy_reference_files() { - local log="$MAVEN_CONFIG/copy_reference_file.log" - - if (touch "${log}" > /dev/null 2>&1) - then - echo "--- Copying files at $(date)" >> "$log" - find /usr/share/maven/ref/ -type f -exec bash -eu -c 'copy_reference_file /usr/share/maven/ref/ "$1" "$2"' _ {} "$log" \; - else - echo "Can not write to ${log}. Wrong volume permissions? Carrying on ..." - fi -} - -export -f copy_reference_file -copy_reference_files -unset MAVEN_CONFIG - -cd $SOURCEPATH - -exec "$@" diff --git a/src/bc-shipping/ingestion/mvnw b/src/bc-shipping/ingestion/mvnw deleted file mode 100644 index a1ba1bf5..00000000 --- a/src/bc-shipping/ingestion/mvnw +++ /dev/null @@ -1,233 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Maven2 Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # - # Look for the Apple JDKs first to preserve the existing behaviour, and then look - # for the new JDKs provided by Oracle. - # - if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then - # - # Apple JDKs - # - export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home - fi - - if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then - # - # Apple JDKs - # - export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home - fi - - if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then - # - # Oracle JDKs - # - export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home - fi - - if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then - # - # Apple JDKs - # - export JAVA_HOME=`/usr/libexec/java_home` - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` - fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi - -# For Migwn, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" - # TODO classpath? -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` -fi - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - local basedir=$(pwd) - local wdir=$(pwd) - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - wdir=$(cd "$wdir/.."; pwd) - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" - fi -} - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} "$@" diff --git a/src/bc-shipping/ingestion/mvnw.cmd b/src/bc-shipping/ingestion/mvnw.cmd deleted file mode 100644 index 2b934e89..00000000 --- a/src/bc-shipping/ingestion/mvnw.cmd +++ /dev/null @@ -1,145 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -set MAVEN_CMD_LINE_ARGS=%* - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" - -set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% \ No newline at end of file diff --git a/src/bc-shipping/ingestion/settings-docker.xml b/src/bc-shipping/ingestion/settings-docker.xml deleted file mode 100644 index 586c587c..00000000 --- a/src/bc-shipping/ingestion/settings-docker.xml +++ /dev/null @@ -1,6 +0,0 @@ - - /usr/share/maven/ref/repository - diff --git a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/service/IngestionImpl.java b/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/service/IngestionImpl.java deleted file mode 100644 index e4aee6fb..00000000 --- a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/service/IngestionImpl.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.fabrikam.dronedelivery.ingestion.service; -import com.fabrikam.dronedelivery.ingestion.models.*; - -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; - -import com.fabrikam.dronedelivery.ingestion.configuration.ApplicationProperties; -import com.fabrikam.dronedelivery.ingestion.util.ClientPool; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -import com.microsoft.azure.eventhubs.EventData; -import com.microsoft.azure.servicebus.ServiceBusException; - -@Service -public class IngestionImpl implements Ingestion { - - @Autowired - private ClientPool clientPool; - - @Autowired - private ApplicationProperties appProps; - - @Autowired - public IngestionImpl(ClientPool clientPool, ApplicationProperties appProps) { - this.clientPool = clientPool; - this.appProps = appProps; - } - - @Async - @Override - public void scheduleDeliveryAsync(DeliveryBase delivery, Map httpHeaders) { - EventData sendEvent = getEventData(delivery, httpHeaders); - sendEvent.getProperties().put("operation", "delivery"); - try { - clientPool.getConnection().send(sendEvent).thenApply((Void) -> "result"); - } catch (InterruptedException | ExecutionException | ServiceBusException | IOException e) { - throw new RuntimeException(e); - } - } - - @Async - @Override - public void cancelDeliveryAsync(String deliveryId, Map httpHeaders) { - EventData sendEvent = getEventData(deliveryId, httpHeaders); - - sendEvent.getProperties().put("operation", "cancel"); - try { - clientPool.getConnection().send(sendEvent).thenApply((Void) -> "result"); - } catch (InterruptedException | ExecutionException | ServiceBusException | IOException e) { - throw new RuntimeException(e); - - } - } - - @Async - @Override - public void rescheduleDeliveryAsync(DeliveryBase rescheduledDelivery, Map httpHeaders) { - EventData sendEvent = getEventData(rescheduledDelivery, httpHeaders); - sendEvent.getProperties().put("operation", "reschedule"); - try { - clientPool.getConnection().send(sendEvent).thenApply((Void) -> "result"); - } catch (InterruptedException | ExecutionException | ServiceBusException | IOException e) { - throw new RuntimeException(e); - } - } - - private EventData getEventData(Object deliveryObj, Map httpHeaders){ - Gson gson = new GsonBuilder().create(); - byte[] payloadBytes = gson.toJson(deliveryObj).getBytes(Charset.defaultCharset()); - EventData sendEvent = new EventData(payloadBytes); - - Map eventProps = new HashMap(); - for(String header:appProps.getServiceMeshHeaders()){ - if(httpHeaders.containsKey(header)){ - eventProps.put(header, httpHeaders.get(header)); - } - } - - if(eventProps.size()>0){ - sendEvent.getProperties().putAll(eventProps); - } - - return sendEvent; - } -} diff --git a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/ClientPool.java b/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/ClientPool.java deleted file mode 100644 index 9ee854fa..00000000 --- a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/ClientPool.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.fabrikam.dronedelivery.ingestion.util; - -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -import org.springframework.scheduling.annotation.Async; - -import com.microsoft.azure.eventhubs.EventHubClient; -import com.microsoft.azure.servicebus.ServiceBusException; - -public interface ClientPool { - - @Async - public EventHubClient getConnection() - throws InterruptedException, ExecutionException, ServiceBusException, IOException; - -} diff --git a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/ClientPoolImpl.java b/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/ClientPoolImpl.java deleted file mode 100644 index f8f219eb..00000000 --- a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/ClientPoolImpl.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.fabrikam.dronedelivery.ingestion.util; - -import com.fabrikam.dronedelivery.ingestion.configuration.*; -import com.microsoft.azure.eventhubs.EventHubClient; -import com.microsoft.azure.servicebus.ConnectionStringBuilder; -import com.microsoft.azure.servicebus.ServiceBusException; - -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; - -@Service -public class ClientPoolImpl implements ClientPool { - - private final EventHubClient[] eventHubClients; - private final String[] eventHubNames; - private final ApplicationProperties appProperties; - private final String nameSpace; - private final String sasKeyName; - private final String sasKey; - - - @Autowired - public ClientPoolImpl(ApplicationProperties appProps) - throws IOException, ServiceBusException, InterruptedException, ExecutionException { - this.appProperties = appProps; - - - this.eventHubNames = System.getenv(appProperties.getEnvHubName()).split(","); - nameSpace = System.getenv(appProperties.getEnvNameSpace()); - sasKeyName = System.getenv(appProperties.getEnvsasKeyName()); - sasKey = System.getenv(appProperties.getEnvsasKey()); - - this.eventHubClients = new EventHubClient[this.appProperties.getMessageAmqpClientPoolSize()]; - } - - @Async - @Override - public EventHubClient getConnection() - throws InterruptedException, ExecutionException, ServiceBusException, IOException { - - int poolId = (int) (Math.random() * eventHubClients.length); - int eventHubId = (int) (Math.random() * eventHubNames.length); - - if (eventHubClients[poolId] == null) { - ConnectionStringBuilder connectionString = new ConnectionStringBuilder(nameSpace, - eventHubNames[eventHubId], sasKeyName, sasKey); - eventHubClients[poolId] = EventHubClient.createFromConnectionString(connectionString.toString()).get(); - } - - EventHubClient vclient = eventHubClients[poolId]; - - return eventHubClients[poolId]; - } - -} diff --git a/src/bc-shipping/ingestion/src/main/resources/application.properties b/src/bc-shipping/ingestion/src/main/resources/application.properties deleted file mode 100644 index dadc26ab..00000000 --- a/src/bc-shipping/ingestion/src/main/resources/application.properties +++ /dev/null @@ -1,19 +0,0 @@ -############################################### -# add environment variables -# EH_NAMESPACE EH_NAME EH_KEYNAME EH_KEYVALUE -# To debug/run tests. Yaml file will have to provide -# environment va -################################################ -service.threadPoolExecutorQueueSize=10000 -service.threadPoolExecutorPoolSize=100 -service.threadPoolExecutorMaxPoolSize=200 -service.messageAmqpClientPoolSize=100 -service.envNameSpace=EH_NAMESPACE -service.envHubName=EH_NAME -service.envsasKeyName=EH_KEYNAME -service.envsasKey=EH_KEYVALUE -service.serviceMeshHeaders=l5d-ctx-deadline,l5d-ctx-trace -service.serviceMeshCorrelationHeader=l5d-ctx-trace -server.port:80 - - diff --git a/src/bc-shipping/ingestion/src/main/resources/log4j2.xml b/src/bc-shipping/ingestion/src/main/resources/log4j2.xml deleted file mode 100644 index 70cd2f93..00000000 --- a/src/bc-shipping/ingestion/src/main/resources/log4j2.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/bc-shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/IngestionPerformanceTest.java b/src/bc-shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/IngestionPerformanceTest.java deleted file mode 100644 index 63b8fac0..00000000 --- a/src/bc-shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/IngestionPerformanceTest.java +++ /dev/null @@ -1,221 +0,0 @@ -package com.fabrikam.dronedelivery.ingestion; - -import static org.junit.Assert.*; -import com.google.gson.*; - -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; -//import org.junit.runner.RunWith; -import org.springframework.web.client.*; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.ExecutionException; - -import org.springframework.util.concurrent.ListenableFuture; -//import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.HttpEntity; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.StringHttpMessageConverter; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; - -import com.fabrikam.dronedelivery.ingestion.models.*; -import com.fasterxml.jackson.databind.ObjectMapper; - -public class IngestionPerformanceTest { - - private AsyncRestTemplate asyncRest; - private HttpHeaders requestHeaders; - private String uri; - private String IpAddress; - private String Port; - - @Before - public void setUp() throws Exception { - - asyncRest = new AsyncRestTemplate(); - asyncRest.getMessageConverters().add(new MappingJackson2HttpMessageConverter()); - asyncRest.getMessageConverters().add(new StringHttpMessageConverter()); - requestHeaders = new HttpHeaders(); - requestHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); - // 157.55.175.96 - // 23.96.201.211 - // 52.179.159.74 - // 52.179.158.181 - IpAddress = "localhost"; - Port = "8080"; - - } - - @Test - @Ignore - public void PostDeliveryAsync() throws InterruptedException, ExecutionException { - - uri = new StringBuffer().append("https://").append(IpAddress).append(":").append(Port) - .append("/api/deliveryrequests").toString(); - PackageInfo pack = new PackageInfo(); - - pack.setPackageId(UUID.randomUUID().toString()); - pack.setSize(ContainerSize.Medium); - - List Packages = new ArrayList(); - Packages.add(pack); - - ExternalDelivery delivery = new ExternalDelivery(); - Date date = new Date(); - - delivery.setOwnerId(UUID.randomUUID().toString()); - delivery.setPickupTime(date); - delivery.setDropOffLocation("Austin"); - delivery.setPickupLocation("Austin"); - delivery.setDeadline("deadline"); - delivery.setConfirmationRequired(ConfirmationRequired.Picture); - - delivery.setPackageInfo(pack); - - HttpEntity entity = new HttpEntity(delivery, requestHeaders); - - ListenableFuture> response = asyncRest.postForEntity(uri, entity, - ExternalDelivery.class, delivery); - - System.out.println(response.get().getBody().getPickupTime()); - - assertEquals(HttpStatus.ACCEPTED, response.get().getStatusCode()); - - } - - @Test - @Ignore - public void DeleteCancelDeliveryAsync() throws InterruptedException, ExecutionException { - - UUID deliveryId = UUID.randomUUID(); - uri = new StringBuffer().append("https://").append(IpAddress).append(":").append(Port) - .append("/api/canceldelivery/").append(deliveryId).toString(); - - @SuppressWarnings("unchecked") - ListenableFuture> response = (ListenableFuture>) asyncRest.delete(uri, - deliveryId); - - response.get(); - // response.get().getBody(); - - // assertEquals(HttpStatus.OK,response.get().getStatusCode()); - - } - - @Test - @Ignore - public void GetProbeAsync() throws InterruptedException, ExecutionException { - - uri = new StringBuffer().append("https://").append(IpAddress).append(":").append(Port).append("/api/probe") - .toString(); - - ListenableFuture> response = asyncRest.getForEntity(uri, String.class); - assertEquals(HttpStatus.OK, response.get().getStatusCode()); - - } - - @Test - @Ignore - public void CanConvertToJson() throws InterruptedException { - - PackageInfo pack = new PackageInfo(); - - pack.setPackageId(UUID.randomUUID().toString()); - pack.setSize(ContainerSize.Medium); - - List Packages = new ArrayList(); - Packages.add(pack); - - ExternalDelivery delivery = new ExternalDelivery(); - Date date = new Date(); - - delivery.setOwnerId(UUID.randomUUID().toString()); - delivery.setPickupTime(date); - delivery.setDropOffLocation("Austin"); - delivery.setPickupLocation("Austin"); - delivery.setDeadline("deadline"); - delivery.setConfirmationRequired(ConfirmationRequired.Picture); - - delivery.setPackageInfo(pack); - - Gson gson = new GsonBuilder().create(); - System.out.println(gson.toJson(delivery)); - System.out.println(asJsonString(delivery)); - - } - - @Test - @Ignore - public void CanDeserializeDelivery() throws InterruptedException { - - PackageInfo pacKage = new PackageInfo(); - pacKage.setSize(ContainerSize.Small); - pacKage.setPackageId(UUID.randomUUID().toString()); - - List Packages = new ArrayList(); - Packages.add(pacKage); - - ExternalDelivery delivery = new ExternalDelivery(); - Date date = new Date(); - delivery.setOwnerId(UUID.randomUUID().toString()); - delivery.setPickupTime(date); - delivery.setDropOffLocation("Austin"); - delivery.setPickupLocation("Austin"); - delivery.setConfirmationRequired(ConfirmationRequired.Picture); - delivery.setDeadline("dealine"); - delivery.setExpedited(true); - delivery.setPackageInfo(pacKage); - - Gson gson = new GsonBuilder().create(); - - DeliveryBase intdelivery = new Delivery(UUID.randomUUID().toString(), delivery.getOwnerId().toString(), delivery.getPickupLocation(), - delivery.getDropOffLocation(), delivery.getPickupTime(), delivery.isExpedited(), - delivery.getConfirmationRequired(), delivery.getPackageInfo(), delivery.getDeadline()); - - String jsondelivery = gson.toJson(intdelivery); - System.out.println(gson.toJson(jsondelivery)); - - } - - @Test - @Ignore - public void CanConvertToJson2() throws InterruptedException { - - // ExternalRescheduledDelivery delivery = - // new ExternalRescheduledDelivery(); - // delivery.setDeadline("deadline"); - // delivery.setDelivery(UUID.randomUUID()); - // delivery.setDropOffLocation("location"); - // delivery.setPickupLocation("pickuplocation"); - // - // Gson gson = new GsonBuilder().create(); - // System.out.println(gson.toJson(delivery)); - // System.out.println(asJsonString(delivery)); - // - - } - - @BeforeClass - public static void setUpBeforeClass() throws Exception { - } - - private static String asJsonString(ExternalDelivery obj) { - try { - ObjectMapper mapper = new ObjectMapper(); - String jsonContent = mapper.writeValueAsString(obj); - return jsonContent; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - -} diff --git a/src/bc-shipping/package/app/api.json b/src/bc-shipping/package/app/api.json deleted file mode 100644 index 54d268d1..00000000 --- a/src/bc-shipping/package/app/api.json +++ /dev/null @@ -1,139 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "description": "Drone Delivery - Package Service", - "version": "1.0.0", - "title": "Package Service" - }, - "schemes": [ - "http", - "https" - ], - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "basePath": "/api", - "paths": { - "/packages/{packageId}": { - "get": { - "summary": "Get information about a specific package from the service", - "description": "", - "operationId": "getById", - "parameters": [ - { - "name": "packageId", - "in": "path", - "description": "ID of package to return", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "successful operation", - "schema": { - "$ref": "#/definitions/Package" - } - }, - "400": { - "description": "Invalid ID supplied" - }, - "404": { - "description": "Package not found" - } - } - }, - "patch": { - "summary": "Update an existing package", - "description": "", - "operationId": "updateById", - "parameters": [ - { - "name": "packageId", - "in": "path", - "description": "ID of package to patch", - "required": true, - "type": "string" - } - ], - "responses": { - "204": { - "description": "successful operation" - }, - "400": { - "description": "Invalid ID supplied" - }, - "404": { - "description": "Package not found" - }, - "405": { - "description": "Validation exception" - } - } - }, - "put": { - "summary": "Create or update a package", - "description": "", - "operationId": "createOrUpdate", - "parameters": [ - { - "name": "packageId", - "in": "path", - "description": "ID of package", - "required": true, - "type": "string" - } - ], - "responses": { - "201": { - "description": "Created new package", - "schema": { - "$ref": "#/definitions/Package" - } - }, - "204": { - "description": "Updated existing package" - } - } - } - } - }, - "definitions": { - "Package": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "size": { - "type": "string", - "enum": [ - "small", - "medium", - "large" - ] - }, - "weight": { - "type": "number" - }, - "tag": { - "type": "string" - } - } - }, - "Error": { - "type": "object", - "properties": { - "code": { - "type": "number" - }, - "message": { - "type": "string" - } - } - } - } -} \ No newline at end of file diff --git a/src/bc-shipping/package/app/controllers/package-controllers.ts b/src/bc-shipping/package/app/controllers/package-controllers.ts deleted file mode 100644 index 47540636..00000000 --- a/src/bc-shipping/package/app/controllers/package-controllers.ts +++ /dev/null @@ -1,129 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -// ------------------------------------------------------------ - -import { Repository, UpsertStatus } from '../models/repository' -import * as apiModels from '../models/api-models' -import { Package, PackageSize } from '../models/package' -import { ILogger } from '../util/logging' -import { MongoErrors } from '../util/mongo-err' - -export class PackageControllers { - - constructor(private repository: Repository) { - - } - - mapPackageDbToApi(pkg: Package): apiModels.Package { - // consider moving this mapping code to data access - // might also want to consider allowing repository to deal with API types or.. automapping, mapping through convention or config - // we could simpley add/remove/change the model in the api vs database - let apiPkg = new apiModels.Package(); - apiPkg.id = pkg._id; - apiPkg.size = pkg.size ? pkg.size.toString() : null; - apiPkg.tag = pkg.tag; - apiPkg.weight = pkg.weight; - return apiPkg; - } - - - mapPackageApiToDb(apiPkg: apiModels.Package, id?: string): Package { - let pkg = new Package(id); - pkg.size = apiPkg.size; - pkg.weight = apiPkg.weight; - pkg.tag = apiPkg.tag; - return pkg; - } - - async getById(ctx: any, next: any) { - - var logger : ILogger = ctx.state.logger; - var packageId = ctx.params.packageId; - logger.info('Entering getById, packageId = %s', packageId); - - await next(); - - let pkg = await this.repository.findPackage(ctx.params.packageId) - - if (pkg == null) { - logger.info(`getById: %s not found`, packageId); - ctx.response.status= 404; - return; - } - - ctx.response.status = 200; - ctx.response.body = this.mapPackageDbToApi(pkg); - } - - async updateById(ctx: any, next: any) { - - var logger : ILogger = ctx.state.logger; - var packageId = ctx.params.packageId; - logger.info('updateById', packageId); - - try { - await next(); - - let apiPkg = ctx.request.body; - let pkg = this.mapPackageApiToDb(apiPkg, packageId) - - // the update package should take a dictionary of fields instead of package model - await this.repository.updatePackage(pkg); - - ctx.response.status = 204; - } - catch (ex) { - // Need to handle the case were it's 404 vs invalid data - ctx.throw(ex.message, 400); - } - - return; - } - - // Creates or updates a package using the data provided in the API - async createOrUpdate(ctx: any, next: any) { - - var logger : ILogger = ctx.state.logger; - var packageId = ctx.params.packageId; - logger.info('create', ctx.request.body) - - try { - await next(); - - let apiPkg = ctx.request.body; - let pkg = this.mapPackageApiToDb(apiPkg, packageId); - - var result = await this.repository.addPackage(pkg); - - switch (result) { - case UpsertStatus.Created: - ctx.body = this.mapPackageDbToApi(pkg); - ctx.response.status = 201; - break; - case UpsertStatus.Updated: - ctx.response.status = 204; - break; - } - - return; - } - catch (ex) { - switch (ex.code) { - case MongoErrors.ShardKeyNotFound: - logger.error('Missing shard key', ctx.request.body) - ctx.response.status = 400; - ctx.response.message = "Missing shard key"; - break; - - case MongoErrors.TooManyRequests: - logger.error('Too many requests', ctx.request.body) - ctx.response.status, 429; - break; - - default: - ctx.throw(ex.message, 500); - } - } - } -} \ No newline at end of file diff --git a/src/bc-shipping/package/app/initializer.ts b/src/bc-shipping/package/app/initializer.ts deleted file mode 100644 index 004127bb..00000000 --- a/src/bc-shipping/package/app/initializer.ts +++ /dev/null @@ -1,24 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -// ------------------------------------------------------------ - -import { MongoErrors } from './util/mongo-err' - -var MongoClient = require('mongodb').MongoClient; - -export class PackageServiceInitializer -{ - static async initialize(connection: string, collectionName: string) { - try { - var db = await MongoClient.connect(connection); - await db.command({ shardCollection: db.databaseName + '.' + collectionName, key: { tag: "hashed" } }); - } - catch (ex) { - if (ex.code != MongoErrors.CommandNotFound && ex.code != 9) { - console.log(ex); - } - } - } -} - diff --git a/src/bc-shipping/package/app/server.ts b/src/bc-shipping/package/app/server.ts deleted file mode 100644 index bada2e9d..00000000 --- a/src/bc-shipping/package/app/server.ts +++ /dev/null @@ -1,99 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -// ------------------------------------------------------------ - -import * as Koa from 'koa'; -import * as bodyParser from "koa-bodyparser"; - -var compress = require('koa-compress'); - -const fleekCtx = require('fleek-context'); -const fleekRouter = require('fleek-router'); -const fleekValidator = require('fleek-validator'); - -const SWAGGER = require('./api.json'); - -import { PackageControllers } from './controllers'; -import { Repository } from './models/repository'; -import { Settings } from './util/settings'; -import { logger, ILogger } from './util/logging' - -export class PackageService { - - static start() { - - console.log('Package service starting...') - - // Initialize repository with connection string - Promise.resolve(Repository.initialize(Settings.connectionString())) - .catch((ex) => { - console.error("failed to initialize repository - make sure a connectiong string has been configured"); - console.error(ex.message); - process.exit(1); // Crash the container - }); - - var packageControllers = new PackageControllers(new Repository()); - - let app = new Koa(); - - // Configure logging - app.use(logger(Settings.logLevel(), function (ctx) { - return ctx.headers[Settings.correlationHeader()]; - })); - - // Configure global exception handling - // Use: ctx.throw('Error Message', 500); - // in the controller methods to set the status code and exception message - app.use(async (ctx, next) => { - try { - await next(); - } catch (ex) { - - var logger : ILogger = ctx.state.logger; - if (logger) { - logger.error(ex.message); - } - - ctx.status = ex.status || 500; - // consider api specific codes and localized messages as opposed to internal codes - ctx.body = { - level: "error", - code: ex.code, - message: ex.message - } - ctx.app.emit('error', ex, ctx); - } - }); - - // add compression and body parser to the pipeline - app.use(compress()); - app.use(bodyParser()); - - // configure fleek to handle validation and routing based on api document - app.use(fleekCtx(SWAGGER)); - app.use(fleekValidator().catch((ctx, next) => { - if (ctx.fleek.validation.failed) { - - var logger : ILogger = ctx.state.logger; - if (logger) { - logger.error(ctx.fleek.validation); - } - - ctx.body = ctx.fleek.validation; - ctx.status = 400; - return; - } - return next(); - })); - - let router = new fleekRouter.Router({ controllers: `${__dirname}/controllers` }); - - app.use(router.controllers({ - operation: packageControllers - })); - - console.log('listening on port 80'); - app.listen(80); - } -} diff --git a/src/bc-shipping/package/app/util/logging.ts b/src/bc-shipping/package/app/util/logging.ts deleted file mode 100644 index 50463679..00000000 --- a/src/bc-shipping/package/app/util/logging.ts +++ /dev/null @@ -1,48 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. -// ------------------------------------------------------------ - -import * as winston from 'winston'; -import {Context} from "koa"; -export interface ILogger { - log(level: string, msg: string, meta?: any) - debug(msg: string, meta?: any) - info(msg: string, meta?: any) - warn(msg: string, meta?: any) - error(msg: string, meta?: any) -} -export type CorrelationIdFn = (ctx: Context) => string; -export function logger(level: string, getCorrelationId: CorrelationIdFn) { - - winston.configure({ - level: level, - transports: [ new (winston.transports.Console)()] - }); - return async function(ctx: any, next: any) { - ctx.state.logger = new WinstonLogger(getCorrelationId(ctx)); - await next(); - } -} -class WinstonLogger { - constructor(private correlationId: string) {} - log(level: string, msg: string, payload?: any) { - var meta : any = {}; - if (payload) { meta.payload = payload }; - if (this.correlationId) { meta.correlationId = this.correlationId } - winston.log(level, msg, meta) - } - - info(msg: string, payload?: any) { - this.log('info', msg, payload); - } - debug(msg: string, payload?: any) { - this.log('debug', msg, payload); - } - warn(msg: string, payload?: any) { - this.log('warn', msg, payload); - } - error(msg: string, payload?: any) { - this.log('error', msg, payload); - } -} diff --git a/src/bc-shipping/package/build/create-secrets.sh b/src/bc-shipping/package/build/create-secrets.sh deleted file mode 100644 index c0a65d05..00000000 --- a/src/bc-shipping/package/build/create-secrets.sh +++ /dev/null @@ -1,15 +0,0 @@ -#! /bin/bash - -CosmosDB=$1 -ResourceGroup=$2 - -# Get the CosmosDB connection string -ConnectionString=`az cosmosdb list-connection-strings --name $CosmosDB --resource-group $ResourceGroup --query "connectionStrings[0].connectionString"` - -# Trim quotes -ConnectionString="${ConnectionString%\"}" -ConnectionString="${ConnectionString#\"}" - -# Create the app secret -echo 'Creating k8s secrets' -kubectl create secret generic package-secrets --from-literal=mongodb-pwd=$ConnectionString \ No newline at end of file diff --git a/src/bc-shipping/package/build/dev.dockerfile b/src/bc-shipping/package/build/dev.dockerfile deleted file mode 100644 index 100065a5..00000000 --- a/src/bc-shipping/package/build/dev.dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM node:8.0.0 - -RUN apt-get update && \ - apt-get install -y tmux supervisor inotify-tools && \ - rm -rf /var/lib/apt/lists; rm /tmp/*; apt-get autoremove -y diff --git a/src/bc-shipping/package/build/docker-compose.ci.build.yml b/src/bc-shipping/package/build/docker-compose.ci.build.yml deleted file mode 100644 index 53936632..00000000 --- a/src/bc-shipping/package/build/docker-compose.ci.build.yml +++ /dev/null @@ -1,11 +0,0 @@ -# Build package service -version: '3' - -services: - ci-build: - image: node:8.0.0 - volumes: - - ..:/src - working_dir: /src - command: /bin/bash -c "npm install && npm run build" - diff --git a/src/bc-shipping/package/build/prod.dockerfile b/src/bc-shipping/package/build/prod.dockerfile deleted file mode 100644 index fb0fca46..00000000 --- a/src/bc-shipping/package/build/prod.dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM node:8.0.0 - -WORKDIR /app -EXPOSE 80 - -COPY package.json /app/ -RUN npm install --production - -COPY .bin/app /app - -ENTRYPOINT ["node", "main.js"] diff --git a/src/bc-shipping/package/package-service.md b/src/bc-shipping/package/package-service.md deleted file mode 100644 index bb68baa9..00000000 --- a/src/bc-shipping/package/package-service.md +++ /dev/null @@ -1,24 +0,0 @@ -# Package service - -## Create local development environment - -1. Install Docker -2. From a bash CLI navigate to the project root and type `./up.sh` -3. From the app environment type `npm install` -4. To run the app in the local dev environment, type `npm start` -5. The app listens on port 80. In the dev environment, this is mapped to localhost:7080 - -## Build docker image - -``` -npm run build -docker build -f ./build/prod.dockerfile -t /package-service . -``` - -## Provision database and create secrets - -1. In Azure, create a Cosmos DB database with MongoDB API -2. Install Azure CLI 2.0 -3. Run `az login` -4. Run create-secrets.sh - diff --git a/src/bc-shipping/package/package.json b/src/bc-shipping/package/package.json deleted file mode 100644 index 8fe5e8fb..00000000 --- a/src/bc-shipping/package/package.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "drone-delivery-package-service", - "version": "0.0.1", - "description": "Drone Delivery Package Service", - "scripts": { - "start": "gulp serve", - "build": "gulp build", - "clean": "gulp clean", - "api": "gulp create-api-classes", - "test": "gulp test", - "ncu": "ncu", - "ncu:update": "ncu -u" - }, - "repository": { - "type": "git", - "url": "ssh://pnp@pnp.visualstudio.com:22/DefaultCollection/_git/DroneDelivery-PackageService" - }, - "keywords": [ - "Drone", - "Delivery", - "Package" - ], - "author": "Microsoft Patterns and Practices", - "license": "MIT", - "dependencies": { - "fleek-context": "1.0.6", - "fleek-router": "2.0.2", - "fleek-validator": "2.0.0", - "koa": "2.2.0", - "koa-body": "2.1.0", - "koa-bodyparser": "4.2.0", - "koa-compress": "2.0.0", - "koa-convert": "1.2.0", - "koa-cors": "0.0.16", - "koa-route": "3.2.0", - "mongodb": "2.2.28", - "winston": "^2.4.0" - }, - "devDependencies": { - "@types/koa": "2.0.39", - "@types/koa-bodyparser": "3.0.23", - "@types/mocha": "2.2.41", - "@types/node": "7.0.29", - "@types/winston": "^2.3.6", - "del": "3.0.0", - "gulp": "3.9.1", - "gulp-cli": "1.3.0", - "gulp-mocha": "^4.3.1", - "gulp-nodemon": "2.2.1", - "gulp-typescript": "3.1.7", - "mocha": "3.4.2", - "nodemon": "1.11.0", - "npm-check-updates": "2.11.3", - "typescript": "2.3.4" - } -} diff --git a/src/bc-shipping/scheduler/.classpath b/src/bc-shipping/scheduler/.classpath deleted file mode 100644 index 7a2b4048..00000000 --- a/src/bc-shipping/scheduler/.classpath +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/bc-shipping/scheduler/.project b/src/bc-shipping/scheduler/.project deleted file mode 100644 index faa44025..00000000 --- a/src/bc-shipping/scheduler/.project +++ /dev/null @@ -1,29 +0,0 @@ - - - deliveryscheduler.delivery-dispatcher-service - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - org.springframework.ide.eclipse.core.springbuilder - - - - - - org.springframework.ide.eclipse.core.springnature - org.eclipse.jdt.core.javanature - org.eclipse.m2e.core.maven2Nature - - diff --git a/src/bc-shipping/scheduler/Dockerfile b/src/bc-shipping/scheduler/Dockerfile deleted file mode 100644 index 736b823a..00000000 --- a/src/bc-shipping/scheduler/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM openjdk:8-jdk-alpine -MAINTAINER Kirpa Singh - -ENTRYPOINT ["/usr/bin/java", "-jar", "/usr/share/dronedelivery/deliveryscheduler.delivery-dispatcher-service-0.0.1-SNAPSHOT.jar"] - -# Add Maven dependencies (not shaded into the artifact; Docker-cached) -ADD target/lib /usr/share/dronedelivery/lib - -# Add the service itself -ADD target/deliveryscheduler.delivery-dispatcher-service-0.0.1-SNAPSHOT.jar /usr/share/dronedelivery/deliveryscheduler.delivery-dispatcher-service-0.0.1-SNAPSHOT.jar - - diff --git a/src/bc-shipping/scheduler/Dockerfilemaven b/src/bc-shipping/scheduler/Dockerfilemaven deleted file mode 100644 index 2188d84a..00000000 --- a/src/bc-shipping/scheduler/Dockerfilemaven +++ /dev/null @@ -1,25 +0,0 @@ -FROM openjdk:8-jdk - -ARG MAVEN_VERSION=3.5.2 -ARG USER_HOME_DIR="/root" -ARG SHA=707b1f6e390a65bde4af4cdaf2a24d45fc19a6ded00fff02e91626e3e42ceaff -ARG BASE_URL=https://apache.osuosl.org/maven/maven-3/${MAVEN_VERSION}/binaries - -RUN mkdir -p /usr/share/maven /usr/share/maven/ref \ - && curl -fsSL -o /tmp/apache-maven.tar.gz ${BASE_URL}/apache-maven-${MAVEN_VERSION}-bin.tar.gz \ - && echo "${SHA} /tmp/apache-maven.tar.gz" | sha256sum -c - \ - && tar -xzf /tmp/apache-maven.tar.gz -C /usr/share/maven --strip-components=1 \ - && rm -f /tmp/apache-maven.tar.gz \ - && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn - -ENV MAVEN_HOME /usr/share/maven -ENV MAVEN_CONFIG "$USER_HOME_DIR/.m2" -ENV SOURCEPATH /sln - -COPY mvn-entrypoint.sh /usr/local/bin/mvn-entrypoint.sh -COPY settings-docker.xml /usr/share/maven/ref/ -RUN chmod +x /usr/local/bin/mvn-entrypoint.sh -VOLUME "$USER_HOME_DIR/.m2" - -ENTRYPOINT ["/usr/local/bin/mvn-entrypoint.sh"] -CMD ["mvn","package"] diff --git a/src/bc-shipping/scheduler/Readme.txt b/src/bc-shipping/scheduler/Readme.txt deleted file mode 100644 index cd89ccb1..00000000 --- a/src/bc-shipping/scheduler/Readme.txt +++ /dev/null @@ -1,7 +0,0 @@ -# Instructions for building docker image -1. Make sure maven is installed and in the PATH. -2. Make sure 'Docker for Windows' is installed. -3. Open a command prompt and CD into the project folder. Make sure POM.xml is in the same folder. -4. Run 'mvn clean install'. -5. Run 'docker build -t dispatcher: .' where is the version you want to assign, e.g. v1.0. -6. Run 'docker run -it dispatcher:' where is the version you assigned in the previous step. \ No newline at end of file diff --git a/src/bc-shipping/scheduler/conf/Config.properties b/src/bc-shipping/scheduler/conf/Config.properties deleted file mode 100644 index 5909b105..00000000 --- a/src/bc-shipping/scheduler/conf/Config.properties +++ /dev/null @@ -1,14 +0,0 @@ -env.account.service.uri.key = SERVICE_URI_ACCOUNT -env.delivery.service.uri.key = SERVICE_URI_DELIVERY -env.dronescheduler.service.uri.key = SERVICE_URI_DRONE -env.package.service.uri.key = SERVICE_URI_PACKAGE -env.thirdparty.service.uri.key = SERVICE_URI_THIRDPARTY - -env.hostname.key = HOST_POD_NAME -env.proxyname.key = http_proxy -env.service.mesh.headers.key = SERVICEMESH_HEADERS -env.service.mesh.correlation.header.key = SERVICEMESH_CORRELATION_HEADER -env.checkpoint.time = CHECKP_TIME_MINUTES - -env.storage.queue.connection.string = STORAGE_QUEUE_CONNECTION_STRING -env.storage.queue.name = STORAGE_QUEUE_NAME \ No newline at end of file diff --git a/src/bc-shipping/scheduler/conf/application.conf b/src/bc-shipping/scheduler/conf/application.conf deleted file mode 100644 index d4b3f006..00000000 --- a/src/bc-shipping/scheduler/conf/application.conf +++ /dev/null @@ -1,85 +0,0 @@ -// Configuration file [HOCON format] - -// @see http://doc.akka.io/docs/akka/2.4.10/scala/logging.html -akka { - # Options: OFF, ERROR, WARNING, INFO, DEBUG - loglevel = "ERROR" -} - -iothub-react { - - // Connection settings can be retrieved from the Azure portal at https://portal.azure.com - // For more information about IoT Hub settings, see: - // https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-create-through-portal#endpoints - // https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-java-java-getstarted - connection { - // see: Endpoints ⇒ Messaging ⇒ Events ⇒ "Event Hub-compatible name" - hubName = ${?IOTHUB_EVENTHUB_NAME} - - // see: Endpoints ⇒ Messaging ⇒ Events ⇒ "Event Hub-compatible endpoint" - hubEndpoint = ${?IOTHUB_EVENTHUB_ENDPOINT} - - // see: Endpoints ⇒ Messaging ⇒ Events ⇒ Partitions - hubPartitions = ${?IOTHUB_EVENTHUB_PARTITIONS} - - // see: Shared access policies ⇒ key name ⇒ Connection string - accessConnString = ${?IOTHUB_ACCESS_CONNSTRING} - } - - streaming { - - // see: "IoT Hub" >> your hub > "Messaging" >> Consumer groups - // "$Default" is predefined and is the typical scenario - consumerGroup = "$Default" - - // Value expressed as a duration, e.g. 3s, 3000ms, "3 seconds", etc. - receiverTimeout = 3s - - // How many messages to retrieve on each pull, max is 900 - receiverBatchSize = 999 - - // Whether to retrieve information about the partitions while streming events from IoT Hub - retrieveRuntimeInfo = true - } - - checkpointing { - - // Checkpoints frequency (best effort), for each IoT hub partition - // Min: 1 second, Max: 1 minute - frequency = 15s - - // How many messages to stream before saving the position, for each IoT hub partition. - // Since the stream position is saved in the Source, before the rest of the - // Graph (Flows/Sinks), this provides a mechanism to replay buffered messages. - // The value should be greater than receiverBatchSize - countThreshold = 2700 - - // Store a position if its value is older than this amount of time, ignoring the threshold. - // For instance when the telemetry stops, this will force to write the last offset after some time. - // Min: 1 second, Max: 1 hour. Value is rounded to seconds. - timeThreshold = 1200s - - storage { - - // Value expressed as a duration, e.g. 3s, 3000ms, "3 seconds", etc. - rwTimeout = 5s - - // Supported types (not case sensitive): Cassandra, AzureBlob - backendType = "AzureBlob" - - // If you use the same storage while processing multiple streams, you'll want - // to use a distinct table/container/path in each job, to to keep state isolated - namespace = "ehubcheckpoint" - azureblob { - // Time allowed for a checkpoint to be written, rounded to seconds (min 15, max 60) - lease = 15s - // Whether to use the Azure Storage Emulator - useEmulator = false - // Storage credentials - protocol = "https" - account = ${?IOTHUB_CHECKPOINT_AZSTORAGE_ACCOUNT} - key = ${?IOTHUB_CHECKPOINT_AZSTORAGE_KEY} - } - } - } -} \ No newline at end of file diff --git a/src/bc-shipping/scheduler/conf/log4j2.xml b/src/bc-shipping/scheduler/conf/log4j2.xml deleted file mode 100644 index bb5c4ba7..00000000 --- a/src/bc-shipping/scheduler/conf/log4j2.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/bc-shipping/scheduler/mvn-entrypoint.sh b/src/bc-shipping/scheduler/mvn-entrypoint.sh deleted file mode 100644 index f8ed7fda..00000000 --- a/src/bc-shipping/scheduler/mvn-entrypoint.sh +++ /dev/null @@ -1,41 +0,0 @@ -#! /bin/bash -eu - -set -o pipefail - -# Copy files from /usr/share/maven/ref into ${MAVEN_CONFIG} -# So the initial ~/.m2 is set with expected content. -# Don't override, as this is just a reference setup -copy_reference_file() { - local root="${1}" - local f="${2%/}" - local logfile="${3}" - local rel="${f/${root}/}" # path relative to /usr/share/maven/ref/ - echo "$f" >> "$logfile" - echo " $f -> $rel" >> "$logfile" - if [[ ! -e ${MAVEN_CONFIG}/${rel} || $f = *.override ]] - then - echo "copy $rel to ${MAVEN_CONFIG}" >> "$logfile" - mkdir -p "${MAVEN_CONFIG}/$(dirname "${rel}")" - cp -r "${f}" "${MAVEN_CONFIG}/${rel}"; - fi; -} - -copy_reference_files() { - local log="$MAVEN_CONFIG/copy_reference_file.log" - - if (touch "${log}" > /dev/null 2>&1) - then - echo "--- Copying files at $(date)" >> "$log" - find /usr/share/maven/ref/ -type f -exec bash -eu -c 'copy_reference_file /usr/share/maven/ref/ "$1" "$2"' _ {} "$log" \; - else - echo "Can not write to ${log}. Wrong volume permissions? Carrying on ..." - fi -} - -export -f copy_reference_file -copy_reference_files -unset MAVEN_CONFIG - -cd $SOURCEPATH - -exec "$@" diff --git a/src/bc-shipping/scheduler/pom.xml b/src/bc-shipping/scheduler/pom.xml deleted file mode 100644 index a3ae66ef..00000000 --- a/src/bc-shipping/scheduler/pom.xml +++ /dev/null @@ -1,191 +0,0 @@ - - 4.0.0 - com.fabrikam.dronedelivery - deliveryscheduler.delivery-dispatcher-service - 0.0.1-SNAPSHOT - Processes the messages dropped to the event hub - - UTF-8 - - - - alfresco-public - https://artifacts.alfresco.com/nexus/content/groups/public - - - central - Central - http://repo1.maven.org/maven2 - - - toketi-snapshots - https://dl.bintray.com/microsoftazuretoketi/toketi-repo/ - - - - src - - - conf - - - - - maven-compiler-plugin - 3.5.1 - - 1.8 - 1.8 - - - - org.apache.maven.plugins - maven-dependency-plugin - - - copy-dependencies - package - - copy-dependencies - - - runtime - ${project.build.directory}/lib - - - - - - org.apache.maven.plugins - maven-jar-plugin - 2.6 - - - - true - lib/ - com.fabrikam.dronedelivery.deliveryscheduler.akkareader.Main - - - - - - - - - - com.microsoft.azure - azure - 1.0.0 - - - org.slf4j - slf4j-simple - - - - - - com.fasterxml.jackson.core - jackson-databind - 2.9.0.pr3 - - - - - io.projectreactor - reactor-core - 3.0.7.RELEASE - - - - - org.springframework - spring-web - 5.0.0.M5 - - - - org.springframework - spring-web-reactive - 5.0.0.M4 - - - - org.springframework - spring-webflux - 5.0.0.M5 - - - - junit - junit - 4.9 - - - - com.microsoft.azure.iot - iothub-react_2.12 - 0.10.0-DEV.170725c - compile - - - - org.apache.httpcomponents - httpasyncclient - 4.1.3 - - - - commons-io - commons-io - 2.6 - - - - net.javacrumbs.future-converter - future-converter-spring-java8 - 1.1.0 - - - - com.github.stefanbirkner - system-rules - 1.16.1 - - - - com.github.tomakehurst - wiremock-standalone - 2.9.0 - - - - - - org.apache.httpcomponents - fluent-hc - 4.5.3 - test - - - - org.apache.logging.log4j - log4j-api - 2.9.1 - - - org.apache.logging.log4j - log4j-core - 2.9.1 - - - - com.microsoft.azure - azure-storage - 6.1.0 - - - - \ No newline at end of file diff --git a/src/bc-shipping/scheduler/settings-docker.xml b/src/bc-shipping/scheduler/settings-docker.xml deleted file mode 100644 index 586c587c..00000000 --- a/src/bc-shipping/scheduler/settings-docker.xml +++ /dev/null @@ -1,6 +0,0 @@ - - /usr/share/maven/ref/repository - diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/akkareader/AkkaDelivery.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/akkareader/AkkaDelivery.java deleted file mode 100644 index 53999fa5..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/akkareader/AkkaDelivery.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.akkareader; - -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.Delivery; -import com.microsoft.azure.iot.iothubreact.MessageFromDevice; - -/* - * AkkaDelivery aids in flowing the original message received from event hub - * through the fluent akka api, eventually being used for checkpointing. - */ -public class AkkaDelivery { - private Delivery delivery; - private MessageFromDevice messageFromDevice; - - public Delivery getDelivery() { - return delivery; - } - - public void setDelivery(Delivery delivery) { - this.delivery = delivery; - } - - public MessageFromDevice getMessageFromDevice() { - return messageFromDevice; - } - - public void setMessageFromDevice(MessageFromDevice messageFromDevice) { - this.messageFromDevice = messageFromDevice; - } -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/akkareader/Main.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/akkareader/Main.java deleted file mode 100644 index 5441b752..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/akkareader/Main.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.akkareader; - -import java.time.temporal.ChronoUnit; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; - - -import java.util.stream.Collectors; - -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - - -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.DeliveryRequestEventProcessor; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.SchedulerSettings; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.invoker.DeliverySchedule; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.utils.ConfigReader; - -import com.microsoft.azure.iot.iothubreact.MessageFromDevice; -import com.microsoft.azure.iot.iothubreact.SourceOptions; -import com.microsoft.azure.iot.iothubreact.javadsl.IoTHub; -import com.typesafe.config.ConfigFactory; - -import akka.NotUsed; -import akka.stream.javadsl.Flow; -import akka.stream.javadsl.Source; - -public class Main extends ReactiveStreamingApp { - - private static final Logger Log = LogManager.getLogger(Main.class); - - public static void main(String args[]) { - Map configSet = ConfigReader.readAllConfigurationValues("Config.properties"); - - // Uris for backend services and other env variables - SchedulerSettings.AccountServiceUri = System.getenv(configSet.get("env.account.service.uri.key")); - SchedulerSettings.DeliveryServiceUri = System.getenv(configSet.get("env.delivery.service.uri.key")); - SchedulerSettings.DroneSchedulerServiceUri = System.getenv(configSet.get("env.dronescheduler.service.uri.key")); - SchedulerSettings.PackageServiceUri = System.getenv(configSet.get("env.package.service.uri.key")); - SchedulerSettings.ThirdPartyServiceUri = System.getenv(configSet.get("env.thirdparty.service.uri.key")); - SchedulerSettings.ServiceMeshHeaders = Arrays.asList(System.getenv(configSet.get("env.service.mesh.headers.key")).split("\\s*,\\s*")); - SchedulerSettings.CorrelationHeader= System.getenv(configSet.get("env.service.mesh.correlation.header.key")); - SchedulerSettings.HostNameValue = System.getenv(configSet.get("env.hostname.key")); - SchedulerSettings.HttpProxyValue = System.getenv(configSet.get("env.proxyname.key")); - - SchedulerSettings.storageQueueConnectionString = System.getenv(configSet.get("env.storage.queue.connection.string")); - SchedulerSettings.storageQueueName = System.getenv(configSet.get("env.storage.queue.name")); - - List partitionsList = getPartitionsList(); - String partitionNumber = partitionsList.stream().map(Object::toString).collect(Collectors.joining(",")); - - Log.info("Reading from partitions: {}", partitionNumber); - - int checkpointMin = Integer.parseInt(System.getenv(configSet.get("env.checkpoint.time"))); - - - SourceOptions options; - // either we read from time - // or from last known checkpoint - if (checkpointMin > 0){ - options = new SourceOptions().partitions(partitionsList) - .fromTime(java.time.Instant.now().minus(checkpointMin,ChronoUnit.MINUTES)); - } - else{ - options = new SourceOptions().partitions(partitionsList) - .fromCheckpoint(null); - } - - // .fromCheckpoint(java.time.Instant.now().minus(checkpointMin , ChronoUnit.MINUTES)); - //java.time.Instant.now().minus(4, ChronoUnit.HOURS) - - IoTHub iotHub = new IoTHub(); - Source messages = iotHub.source(options); - - messages.map(msg -> DeliveryRequestEventProcessor.parseDeliveryRequest(msg)) - .filter(ad -> ad.getDelivery() != null).via(deliveryProcessor()).to(iotHub.checkpointSink()) - .run(streamMaterializer); - } - - /* - * Retrieves the list of partitions either from env or config - */ - private static List getPartitionsList() { - // Read setting for the partition from environment variable first - Integer partitionId = -1; - if (StringUtils.isNotEmpty(SchedulerSettings.HostNameValue)) { - partitionId = Integer.valueOf(SchedulerSettings.HostNameValue.trim().split("\\s*-\\s*")[1]); - } - - List partitionsList = null; - if (partitionId >= 0) { - partitionsList = (List) Arrays.asList(partitionId); - } else { - // Read using ConfigFactory so as to have custom configuration for - // each node in terms of partition(s) being read - partitionsList = ConfigFactory.load().getIntList("iothub-react.connection.hubPartitions"); - } - - return partitionsList; - } - - /* - * Implementation of Akka workflow in fluent mode - */ - - - private static Flow deliveryProcessor() { - return Flow.of(AkkaDelivery.class).map(delivery -> { - CompletableFuture completableSchedule = DeliveryRequestEventProcessor - .processDeliveryRequestAsync(delivery.getDelivery(), - delivery.getMessageFromDevice().properties()); - - completableSchedule.whenComplete((deliverySchedule,error) -> { - if (error!=null){ - Log.info("failed delivery" + error.getStackTrace()); - } - else{ - Log.info("Completed Delivery",deliverySchedule.toString()); - } - - }); - - completableSchedule = null; - return delivery.getMessageFromDevice(); - }); - } - - - - /* - * Implementation of workflow using fluent api - */ - -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/akkareader/ReactiveStreamingApp.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/akkareader/ReactiveStreamingApp.java deleted file mode 100644 index 40a46bad..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/akkareader/ReactiveStreamingApp.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.akkareader; - -import akka.actor.ActorSystem; -import akka.stream.ActorMaterializer; -import akka.stream.Materializer; - -public class ReactiveStreamingApp { - private static ActorSystem system = ActorSystem.create("Dispatcher"); - protected final static Materializer streamMaterializer = ActorMaterializer.create(system); -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/DeliveryRequestEventProcessor.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/DeliveryRequestEventProcessor.java deleted file mode 100644 index 4472e375..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/DeliveryRequestEventProcessor.java +++ /dev/null @@ -1,247 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler; - -import com.fabrikam.dronedelivery.deliveryscheduler.akkareader.AkkaDelivery; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.StorageQueue.StorageQueueClientFactory; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.invoker.DeliverySchedule; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.invoker.PackageGen; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.Delivery; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.PackageInfo; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.services.*; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonSyntaxException; -import com.microsoft.azure.iot.iothubreact.MessageFromDevice; -import com.microsoft.azure.storage.StorageException; -import com.microsoft.azure.storage.queue.CloudQueue; -import com.microsoft.azure.storage.queue.CloudQueueClient; -import com.microsoft.azure.storage.queue.CloudQueueMessage; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.logging.log4j.CloseableThreadContext; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.springframework.http.HttpHeaders; - -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.EnumMap; -import java.util.Map; -import java.util.concurrent.CompletableFuture; - -public class DeliveryRequestEventProcessor { - private static final Logger Log = LogManager.getLogger(DeliveryRequestEventProcessor.class); - - private static final String CorrelationHeaderTag = "CorrelationId"; - - private static Gson deserializer = new GsonBuilder().setPrettyPrinting().create(); - - - - private static final EnumMap backendServicesMap = new EnumMap( - ServiceName.class); - - // Ensures that only one instance of each backend service is created with - // it's own connection pool - static { - backendServicesMap.put(ServiceName.AccountService, new AccountServiceCallerImpl()); - backendServicesMap.put(ServiceName.DeliveryService, new DeliveryServiceCallerImpl()); - backendServicesMap.put(ServiceName.DroneSchedulerService, new DroneSchedulerServiceCallerImpl()); - backendServicesMap.put(ServiceName.PackageService, new PackageServiceCallerImpl()); - backendServicesMap.put(ServiceName.ThirdPartyService, new ThirdPartyServiceCallerImpl()); - } - - public static AkkaDelivery parseDeliveryRequest(MessageFromDevice message) { - AkkaDelivery deliveryRequest = null; - - try { - - String dataReceived = message.contentAsString(); - Delivery delivery = deserializer.fromJson(dataReceived, Delivery.class); - deliveryRequest = new AkkaDelivery(); - deliveryRequest.setDelivery(delivery); - deliveryRequest.setMessageFromDevice(message); - } catch (JsonSyntaxException | IllegalStateException e) { - Log.error("throwable: {}", ExceptionUtils.getStackTrace(e).toString()); - } - - return deliveryRequest; - } - - public static CompletableFuture processDeliveryRequestAsync(Delivery deliveryRequest, - Map properties) { - DeliverySchedule deliverySchedule = null; - // Extract the correlation id and log it - String correlationId = properties.get(SchedulerSettings.CorrelationHeader); - try (final CloseableThreadContext.Instance ctc = CloseableThreadContext.put(CorrelationHeaderTag, - correlationId)) { - Log.info("Processing delivery request: Calling backend services for delivery id: {}", - deliveryRequest.getDeliveryId()); - - Boolean isAccountActive = invokeAccountServiceAsync(deliveryRequest, properties); - - if (isAccountActive) { - Log.info("Account is {}", (isAccountActive ? "active." : "suspended.")); - Boolean isThirdPartyRequired = invokeThirdPartyServiceAsync(deliveryRequest, properties); - Log.info("Third party is {}", (isThirdPartyRequired ? "required." : "not required.")); - - if (!isThirdPartyRequired) { - PackageGen packageGen = invokePackageServiceAsync(deliveryRequest, properties); - if (packageGen != null) { - Log.info("Package generated: {}", packageGen.toString()); - String droneId = invokeDroneSchedulerServiceAsync(deliveryRequest, properties); - - if (droneId != null) { - Log.info("Drone assigned: {}", droneId); - deliverySchedule = invokeDeliverySchedulerServiceAsync(deliveryRequest, droneId, properties); - } - } - } - } - } - - return CompletableFuture.completedFuture(deliverySchedule); - - } - - public static Boolean invokeAccountServiceAsync(Delivery deliveryRequest, Map properties) { - Boolean accountResult = new Boolean(false); - try { - AccountServiceCallerImpl backendService = (AccountServiceCallerImpl) backendServicesMap.get(ServiceName.AccountService); - appendServiceMeshHeaders(backendService, properties); - accountResult = backendService.isAccountActiveAsync(deliveryRequest.getOwnerId(), SchedulerSettings.AccountServiceUri); - } catch (Exception e) { - // Assume failure of service here - a crude supervisor - // implementation - superviseFailureAsync(deliveryRequest, ServiceName.AccountService, ExceptionUtils.getMessage(e)) - .thenRunAsync(() -> { - Log.error("throwable: {}", ExceptionUtils.getStackTrace(e)); - }); - - throw e; - } - - return accountResult; - } - - private static Boolean invokeThirdPartyServiceAsync(Delivery deliveryRequest, Map properties) { - Boolean thirdPartyResult = new Boolean(false); - try { - ThirdPartyServiceCallerImpl backendService = (ThirdPartyServiceCallerImpl) backendServicesMap - .get(ServiceName.ThirdPartyService); - appendServiceMeshHeaders(backendService, properties); - thirdPartyResult = backendService.isThirdPartyServiceRequiredAsync(deliveryRequest.getDropOffLocation(), - SchedulerSettings.ThirdPartyServiceUri); - } catch (Exception e) { - // Assume failure of service here - a crude supervisor - // implementation - superviseFailureAsync(deliveryRequest, ServiceName.ThirdPartyService, ExceptionUtils.getMessage(e)) - .thenRunAsync(() -> { - Log.error("throwable: {}", ExceptionUtils.getStackTrace(e)); - }); - - throw e; - } - - return thirdPartyResult; - } - - private static String invokeDroneSchedulerServiceAsync(Delivery deliveryRequest, Map properties) { - String droneScheduleResult = null; - try { - DroneSchedulerServiceCallerImpl backendService = (DroneSchedulerServiceCallerImpl) backendServicesMap - .get(ServiceName.DroneSchedulerService); - appendServiceMeshHeaders(backendService, properties); - droneScheduleResult = backendService.getDroneIdAsync(deliveryRequest, - SchedulerSettings.DroneSchedulerServiceUri); - } catch (Exception e) { - // Assume failure of service here - a crude supervisor - // implementation - superviseFailureAsync(deliveryRequest, ServiceName.DroneSchedulerService, ExceptionUtils.getMessage(e)) - .thenRunAsync(() -> { - Log.error("throwable: {}", ExceptionUtils.getStackTrace(e)); - }); - - throw e; - } - - return droneScheduleResult; - } - - public static PackageGen invokePackageServiceAsync(Delivery deliveryRequest, Map properties) { - PackageGen packageResult = new PackageGen(); - try { - PackageInfo packageInfo = deliveryRequest.getPackageInfo(); - PackageServiceCallerImpl backendService = (PackageServiceCallerImpl) backendServicesMap.get(ServiceName.PackageService); - appendServiceMeshHeaders(backendService, properties); - packageResult = backendService.createPackageAsync(packageInfo, SchedulerSettings.PackageServiceUri); - } catch (Exception e) { - // Assume failure of service here - a crude supervisor - // implementation - superviseFailureAsync(deliveryRequest, ServiceName.PackageService, ExceptionUtils.getMessage(e)) - .thenRunAsync(() -> { - Log.error("throwable: {}", ExceptionUtils.getStackTrace(e)); - }); - throw e; - } - - return packageResult; - } - - private static DeliverySchedule invokeDeliverySchedulerServiceAsync(Delivery deliveryRequest, - String droneId, Map properties) { - DeliverySchedule deliveryResult = null; - try { - DeliveryServiceCallerImpl backendService = (DeliveryServiceCallerImpl) backendServicesMap.get(ServiceName.DeliveryService); - appendServiceMeshHeaders(backendService, properties); - deliveryResult = backendService.scheduleDeliveryAsync(deliveryRequest, droneId, SchedulerSettings.DeliveryServiceUri); - } catch (Exception e) { - // Assume failure of service here - a crude supervisor - // implementation - superviseFailureAsync(deliveryRequest, ServiceName.DeliveryService, ExceptionUtils.getMessage(e)) - .thenRunAsync(() -> { - Log.error("throwable: {}", ExceptionUtils.getStackTrace(e)); - }); - - throw e; - } - - return deliveryResult; - } - - private static void appendServiceMeshHeaders(ServiceCallerImpl service, Map properties) { - HttpHeaders httpHeaders = service.getRequestHeaders(); - for (String headerName : SchedulerSettings.ServiceMeshHeaders) { - String headerValue = properties.get(headerName); - if (headerValue != null) { - if (httpHeaders.containsKey(headerName)) { - httpHeaders.replace(headerName, Arrays.asList(headerValue)); - } else { - httpHeaders.add(headerName, headerValue); - } - } - } - } - - /* - * Supervisor implementation - */ - private static CompletableFuture superviseFailureAsync(Delivery deliveryRequest, ServiceName serviceName, String errorMessage) { - CloudQueueClient queueClient = StorageQueueClientFactory.get(); - try { - CloudQueue queueReference = queueClient.getQueueReference(SchedulerSettings.storageQueueName); - queueReference.createIfNotExists(); - - String requestInJson = deserializer.toJson(deliveryRequest, Delivery.class); - byte[] requestInJsonBytes = requestInJson.getBytes(StandardCharsets.UTF_8); - CloudQueueMessage message = new CloudQueueMessage(requestInJsonBytes); - - queueReference.addMessage(message); - - } catch (URISyntaxException | StorageException e) { - e.printStackTrace(); - } finally { - return null; - } - - } -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/SchedulerSettings.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/SchedulerSettings.java deleted file mode 100644 index c6371aa8..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/SchedulerSettings.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler; - -import java.util.ArrayList; -import java.util.List; - -public class SchedulerSettings { - // Service URIs - public static String DeliveryServiceUri; - public static String PackageServiceUri; - public static String DroneSchedulerServiceUri; - public static String AccountServiceUri; - public static String ThirdPartyServiceUri; - - public static List ServiceMeshHeaders = new ArrayList(); - public static String CorrelationHeader; - public static String HttpProxyValue; - public static String HostNameValue; - - //StorageQueue - public static String storageQueueConnectionString; - public static String storageQueueName; -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/ServiceFailureMetadata.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/ServiceFailureMetadata.java deleted file mode 100644 index 50291fd8..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/ServiceFailureMetadata.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler; - -public class ServiceFailureMetadata { - -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/ServiceName.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/ServiceName.java deleted file mode 100644 index 44f0aa4b..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/ServiceName.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler; - -public enum ServiceName { - AccountService, ThirdPartyService, PackageService, DroneSchedulerService, DeliveryService -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/StorageQueue/StorageQueueClientFactory.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/StorageQueue/StorageQueueClientFactory.java deleted file mode 100644 index 83b76729..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/StorageQueue/StorageQueueClientFactory.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.StorageQueue; - -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.SchedulerSettings; -import com.microsoft.azure.storage.CloudStorageAccount; -import com.microsoft.azure.storage.queue.CloudQueueClient; - -import java.net.URISyntaxException; -import java.security.InvalidKeyException; - -public class StorageQueueClientFactory { - - private static CloudQueueClient queueClient; - - static { - - try { - CloudStorageAccount cloudStorageAccount = CloudStorageAccount.parse(SchedulerSettings.storageQueueConnectionString); - queueClient = cloudStorageAccount.createCloudQueueClient(); - } catch (URISyntaxException | InvalidKeyException e) { - e.printStackTrace(); - } - } - - public static CloudQueueClient get() { - - return queueClient; - - } - - -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/StorageQueue/StorageQueueTest.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/StorageQueue/StorageQueueTest.java deleted file mode 100644 index 369eb0a9..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/StorageQueue/StorageQueueTest.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.StorageQueue; - -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.SchedulerSettings; -import com.microsoft.azure.storage.queue.CloudQueue; -import com.microsoft.azure.storage.queue.CloudQueueClient; -import com.microsoft.azure.storage.queue.CloudQueueMessage; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - - -public class StorageQueueTest { - - private CloudQueueClient storageClient ; - - private String someStringContent; - -// This test will work only if environment variables are set -// STORAGE_QUEUE_CONNECTION_STRING and STORAGE_QUEUE_NAME - - @Before - public void setup() { - - SchedulerSettings.storageQueueConnectionString = System.getenv("STORAGE_QUEUE_CONNECTION_STRING"); - SchedulerSettings.storageQueueName = System.getenv("STORAGE_QUEUE_NAME"); - storageClient = StorageQueueClientFactory.get(); - someStringContent = "This is test message to queue"; - } - - @Test - public void it_should_add_message_to_queue() { - - //Arrange - CloudQueueMessage queueMessage = new CloudQueueMessage(someStringContent); - - CloudQueue queueReference = null; - try { - - queueReference = storageClient.getQueueReference(SchedulerSettings.storageQueueName); - queueReference.createIfNotExists(); - - - //Act - queueReference.addMessage(queueMessage); - - CloudQueueMessage peekMessage = queueReference.peekMessage(); - - - //Assert - Assert.assertTrue(peekMessage.getMessageContentAsString().equalsIgnoreCase(someStringContent)); - } catch (Exception e) { - e.printStackTrace(); - } - } - - - - -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/ConfirmationType.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/ConfirmationType.java deleted file mode 100644 index c67fc42c..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/ConfirmationType.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.invoker; - -public enum ConfirmationType -{ - FingerPrint, - Picture, - Voice, - None -} \ No newline at end of file diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/DeliverySchedule.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/DeliverySchedule.java deleted file mode 100644 index 83eb903c..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/DeliverySchedule.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.invoker; - -public class DeliverySchedule { - private String Id; - private UserAccount owner; - private Location pickup; - private Location dropoff; - private String deadline; - private Boolean expedited; - private ConfirmationType confirmationRequired; - private String droneId; -// private String packageId; - - public Location getPickup() { - return pickup; - } - - public void setPickup(Location pickup) { - this.pickup = pickup; - } - - public Location getDropoff() { - return dropoff; - } - - public void setDropoff(Location dropoff) { - this.dropoff = dropoff; - } - - public ConfirmationType getConfirmationRequired() { - return confirmationRequired; - } - - public void setConfirmationRequired(ConfirmationType confirmationRequired) { - this.confirmationRequired = confirmationRequired; - } - - public String getDroneId() { - return droneId; - } - - public void setDroneId(String droneId) { - this.droneId = droneId; - } - -// public String getPackageId() { -// return packageId; -// } -// -// public void setPackageId(String packageId) { -// this.packageId = packageId; -// } - - public String getDeadline() { - return this.deadline; - } - - public void setDeadline(String deadline) { - this.deadline = deadline; - } - - public Boolean getExpedited() { - return expedited; - } - - public void setExpedited(Boolean expedited) { - this.expedited = expedited; - } - - public UserAccount getOwner() { - return owner; - } - - public void setOwner(UserAccount owner) { - this.owner = owner; - } - - public String getId() { - return Id; - } - - public void setId(String id) { - Id = id; - } - - @Override - public String toString() { - return "DeliverySchedule [Id=" + Id + ", owner=" + owner.toString() + ", pickup=" + pickup + ", dropoff=" + dropoff - + ", deadline=" + deadline + ", expedited=" + expedited + ", confirmationRequired=" - + confirmationRequired.name() + ", droneId=" + droneId + "]"; //, packageId=" + packageId + " - } -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/DroneDelivery.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/DroneDelivery.java deleted file mode 100644 index 1123025e..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/DroneDelivery.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.invoker; - -public class DroneDelivery { - - public DroneDelivery() { - // TODO Auto-generated constructor stub - } - - private String deliveryId; - private Location pickup; - private Location dropoff; - private PackageDetail packageDetail; - private boolean expedited; - - public String getDeliveryId() { - return deliveryId; - } - - public void setDeliveryId(String deliveryId) { - this.deliveryId = deliveryId; - } - - public Location getPickup() { - return pickup; - } - - public void setPickup(Location pickup) { - this.pickup = pickup; - } - - public Location getDropoff() { - return dropoff; - } - - public void setDropoff(Location dropoff) { - this.dropoff = dropoff; - } - - public boolean getExpedited() { - return expedited; - } - - public void setExpedited(boolean expedited) { - this.expedited = expedited; - } - - public PackageDetail getPackageDetail() { - return packageDetail; - } - - public void setPackageDetail(PackageDetail packageDetail) { - this.packageDetail = packageDetail; - } - -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/Location.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/Location.java deleted file mode 100644 index 51197916..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/Location.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.invoker; - -public class Location { - private double altitude; - private double latitude; - private double longitude; - - public double getAltitude() { - return altitude; - } - - public void setAltitude(double altitude) { - this.altitude = altitude; - } - - public double getLatitude() { - return latitude; - } - - public void setLatitude(double latitude) { - this.latitude = latitude; - } - - public double getLongitude() { - return longitude; - } - - public void setLongitude(double longitude) { - this.longitude = longitude; - } -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/PackageDetail.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/PackageDetail.java deleted file mode 100644 index 16042548..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/PackageDetail.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.invoker; - -public class PackageDetail { - private String Id; - private PackageSize Size; - - public PackageDetail() { - - } - - public String getId() { - return Id; - } - - public void setId(String id) { - Id = id; - } - - public PackageSize getSize() { - return Size; - } - - public void setSize(PackageSize size) { - Size = size; - } - - public PackageDetail(String id, PackageSize size){ - this.Id = id; - this.Size = size; - } -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/PackageGen.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/PackageGen.java deleted file mode 100644 index 39daadaa..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/PackageGen.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.invoker; - -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.ContainerSize; - -public class PackageGen { - - public PackageGen() { - // TODO Auto-generated constructor stub - } - - private String id; - private ContainerSize size; - private String tag; - private double weight; - - public String getId() { - return id; - } - public void setId(String id) { - this.id = id; - } - public ContainerSize getSize() { - return size; - } - public void setSize(ContainerSize size) { - this.size = size; - } - public String getTag() { - return tag; - } - public void setTag(String tag) { - this.tag = tag; - } - public double getWeight() { - return weight; - } - public void setWeight(double weight) { - this.weight = weight; - } - - @Override - public String toString() { - return "PackageGen [id=" + id + ", size=" + size.name() + ", tag=" + tag + ", weight=" + weight + "]"; - } -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/PackageSize.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/PackageSize.java deleted file mode 100644 index fdcd0fcc..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/PackageSize.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.invoker; - -public enum PackageSize { - Small, Medium, Large -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/UserAccount.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/UserAccount.java deleted file mode 100644 index 9e2fb2cc..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/invoker/UserAccount.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.invoker; - -public class UserAccount { - private String userId; - private String accountId; - - public UserAccount(){ - - } - - public UserAccount(String userId, String accountId) { - this.userId = userId; - this.accountId = accountId; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public String getAccountId() { - return accountId; - } - - public void setAccountId(String accountId) { - this.accountId = accountId; - } - - @Override - public String toString() { - return "UserAccount [userId=" + userId + ", accountId=" + accountId + "]"; - } - -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/receiver/ConfirmationRequired.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/receiver/ConfirmationRequired.java deleted file mode 100644 index f8562589..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/receiver/ConfirmationRequired.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver; - -public enum ConfirmationRequired -{ - FingerPrint, - Picture, - Voice, - None -} \ No newline at end of file diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/receiver/ContainerSize.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/receiver/ContainerSize.java deleted file mode 100644 index d25dbcfb..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/receiver/ContainerSize.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver; - -public enum ContainerSize { - Small, Medium, Large -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/receiver/Delivery.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/receiver/Delivery.java deleted file mode 100644 index 8814ef6a..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/receiver/Delivery.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver; - -import java.util.Date; - -public class Delivery { - private String deliveryId; - private String ownerId; - private String pickupLocation; - private String dropoffLocation; - private String deadline; - private Boolean expedited; - private ConfirmationRequired confirmationRequired; - - private Date pickupTime; - - private PackageInfo packageInfo; - - public Date getPickupTime() { - return this.pickupTime; - } - - public void setPickupTime(Date pickupTime){ - this.pickupTime = pickupTime; - } - - public String getDropOffLocation() { - return this.dropoffLocation; - } - - public void setDropOffLocation(String dropoffLocation){ - this.dropoffLocation = dropoffLocation; - } - - public String getPickupLocation() { - return this.pickupLocation; - } - - public void setPickupLocation(String pickupLocation){ - this.pickupLocation = pickupLocation; - } - - public ConfirmationRequired getConfirmationRequired() { - return this.confirmationRequired; - } - - public void setConfirmationRequired(ConfirmationRequired confReq) { - this.confirmationRequired = confReq; - } - - public Boolean isExpedited() { - return expedited; - } - - public void setExpedited(Boolean expedited) { - this.expedited = expedited; - } - - public String getDeadline() { - return this.deadline; - } - - public void setDeadline(String deadline) { - this.deadline = deadline; - } - - public String getDeliveryId() { - return deliveryId; - } - - public void setDeliveryId(String deliveryId) { - this.deliveryId = deliveryId; - } - - public String getOwnerId() { - return ownerId; - } - - public void setOwnerId(String ownerId) { - this.ownerId = ownerId; - } - - @Override - public String toString() { - return String.format( - "DeliveryId:%s, OwnerId:%s, Locations [Pickup:%s, Dropoff:%s], PickupTime:%tc, Package:%s, ConfirmationType:%s, Deadline:%s, Expedited:%s", - this.deliveryId, this.ownerId, this.pickupLocation, this.dropoffLocation, this.pickupTime, - this.packageInfo.toString(), this.confirmationRequired.name(), this.deadline, this.expedited); - } - - public PackageInfo getPackageInfo() { - return packageInfo; - } - - public void setPackageInfo(PackageInfo packageInfo) { - this.packageInfo = packageInfo; - } -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/receiver/PackageInfo.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/receiver/PackageInfo.java deleted file mode 100644 index 4f96f0cc..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/models/receiver/PackageInfo.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver; - -public class PackageInfo { - private String packageId; - private ContainerSize size; - private double weight; - private String tag; - - public String getPackageId() { - return this.packageId; - } - - public void setPackageId(String packageId) { - this.packageId = packageId; - } - - public ContainerSize getSize() { - return size; - } - - public void setSize(ContainerSize size) { - this.size = size; - } - - @Override - public String toString() { - return "PackageInfo [packageId=" + packageId + ", size=" + size.name() + ", weight=" + weight + ", tag=" + tag + "]"; - } - - public double getWeight() { - return weight; - } - - public void setWeight(double weight) { - this.weight = weight; - } - - public String getTag() { - return tag; - } - - public void setTag(String tag) { - this.tag = tag; - } -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/AccountServiceCallerImpl.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/AccountServiceCallerImpl.java deleted file mode 100644 index a6568b3d..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/AccountServiceCallerImpl.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.services; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -import org.apache.commons.lang3.exception.ExceptionUtils; -//import org.apache.http.nio.reactor.IOReactorException; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.util.concurrent.ListenableFuture; - - -import static net.javacrumbs.futureconverter.springjava.FutureConverter.*; - -public final class AccountServiceCallerImpl extends ServiceCallerImpl { - - private Boolean isAccountActive = false; - - // Calls the super constructor and sets the HTTP context - public AccountServiceCallerImpl() { - super(); - } - - @Override - public ListenableFuture getData(String url, T data) { - String accountId = (String) data; - url = url.concat(accountId); - return getAsyncRestTemplate().getForEntity(url, String.class); - } - - public boolean isAccountActive(String accountId, String uri) throws InterruptedException, ExecutionException { - ListenableFuture accountMock = this.getData(uri + '/', accountId); - return Boolean.valueOf(((ResponseEntity) accountMock.get()).getBody().toString()); - } - - - @SuppressWarnings("unchecked") - public Boolean isAccountActiveAsync(String accountId, String uri) { - // Let's call the backend - ListenableFuture> future = (ListenableFuture>) this - .getData(uri + '/', accountId); - - CompletableFuture> cfuture = toCompletableFuture(future); - - cfuture.thenAcceptAsync(response -> { - if (response.getStatusCode() == HttpStatus.OK) { - isAccountActive = Boolean.valueOf(response.getBody()); - } else { - throw new BackendServiceCallFailedException(response.getStatusCode().getReasonPhrase()); - } - }).exceptionally(e -> { - throw new BackendServiceCallFailedException(ExceptionUtils.getStackTrace(e)); - }); - - future = null; - cfuture = null; - - return isAccountActive; - } -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/BackendServiceCallFailedException.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/BackendServiceCallFailedException.java deleted file mode 100644 index 0a3cd124..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/BackendServiceCallFailedException.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.services; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class BackendServiceCallFailedException extends RuntimeException { - - private static final long serialVersionUID = 123456798765045L; - private static final Logger Log = LogManager.getLogger(BackendServiceCallFailedException.class); - - public BackendServiceCallFailedException(String message) { - super(message); - Log.debug("BackendServiceCallFailedException raised: {}", message); - - } - -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/DeliveryServiceCallerImpl.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/DeliveryServiceCallerImpl.java deleted file mode 100644 index c63820c4..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/DeliveryServiceCallerImpl.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.services; - -import static net.javacrumbs.futureconverter.springjava.FutureConverter.toCompletableFuture; - -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -import org.apache.commons.lang3.exception.ExceptionUtils; -//import org.apache.http.nio.reactor.IOReactorException; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.util.concurrent.ListenableFuture; - - -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.invoker.DeliverySchedule; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.invoker.UserAccount; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.Delivery; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.utils.LocationRandomizer; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.utils.ModelsConverter; - -public class DeliveryServiceCallerImpl extends ServiceCallerImpl { - - private DeliverySchedule deliverySchedule = null; - - // Calls the super constructor and sets the HTTP context - public DeliveryServiceCallerImpl() { - super(); - } - - @Override - public ListenableFuture postData(String url, T entity) { - HttpEntity httpEntity = new HttpEntity(entity, this.getRequestHeaders()); - ListenableFuture> response = getAsyncRestTemplate().postForEntity(url, - httpEntity, DeliverySchedule.class, entity); - - return response; - } - - @Override - public ListenableFuture putData(String url, T entity, Object... args) { - HttpEntity httpEntity = new HttpEntity(entity, this.getRequestHeaders()); - ListenableFuture> response = getAsyncRestTemplate().exchange(url, HttpMethod.PUT, httpEntity, DeliverySchedule.class); - return response; - } - - @SuppressWarnings("unchecked") - public DeliverySchedule scheduleDelivery(Delivery deliveryRequest, String droneId, String uri) - throws InterruptedException, ExecutionException { - DeliverySchedule schedule = this.createDeliverySchedule(deliveryRequest, droneId); - ListenableFuture response = this.postData(uri, schedule); - - ResponseEntity entity = (ResponseEntity) response.get(); - return entity.getStatusCode() == HttpStatus.CREATED ? entity.getBody() : null; - } - - - @SuppressWarnings("unchecked") - public DeliverySchedule scheduleDeliveryAsync(Delivery deliveryRequest, String droneId, - String uri) { - - // Create delivery schedule to post as data - DeliverySchedule schedule = this.createDeliverySchedule(deliveryRequest, droneId); - - // Let's call the backend - ListenableFuture> future = (ListenableFuture>) this - .putData(uri + '/' + schedule.getId(), schedule); - - CompletableFuture> cfuture = toCompletableFuture(future); - - cfuture.thenAcceptAsync(response -> { - deliverySchedule = response.getBody(); - if (response.getStatusCode() != HttpStatus.CREATED) { - throw new BackendServiceCallFailedException(response.getStatusCode().getReasonPhrase()); - } - }).exceptionally(e -> { - throw new BackendServiceCallFailedException(ExceptionUtils.getStackTrace(e)); - }); - - future = null; - cfuture = null; - deliveryRequest = null; - schedule = null; - - return deliverySchedule; - } - - private DeliverySchedule createDeliverySchedule(Delivery deliveryRequest, String droneId) { - UserAccount account = new UserAccount(UUID.randomUUID().toString(), deliveryRequest.getOwnerId()); - - DeliverySchedule scheduleDelivery = new DeliverySchedule(); - scheduleDelivery.setId(deliveryRequest.getDeliveryId()); - scheduleDelivery.setOwner(account); - scheduleDelivery.setPickup(LocationRandomizer.getRandomLocation()); - scheduleDelivery.setDropoff(LocationRandomizer.getRandomLocation()); - //scheduleDelivery.setPackageId(deliveryRequest.getPackageInfo().getPackageId()); - scheduleDelivery.setDeadline(deliveryRequest.getDeadline()); - scheduleDelivery.setExpedited(deliveryRequest.isExpedited()); - scheduleDelivery - .setConfirmationRequired(ModelsConverter.getConfirmationType(deliveryRequest.getConfirmationRequired())); - scheduleDelivery.setDroneId(droneId); - - return scheduleDelivery; - } -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/DroneSchedulerServiceCallerImpl.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/DroneSchedulerServiceCallerImpl.java deleted file mode 100644 index 27a3a17a..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/DroneSchedulerServiceCallerImpl.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.services; - -import static net.javacrumbs.futureconverter.springjava.FutureConverter.toCompletableFuture; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -import org.apache.commons.lang3.exception.ExceptionUtils; - -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.util.concurrent.ListenableFuture; - -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.invoker.DroneDelivery; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.Delivery; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.utils.LocationRandomizer; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.utils.ModelsConverter; - -public class DroneSchedulerServiceCallerImpl extends ServiceCallerImpl { - - private String droneId = null; - // Calls the super constructor and sets the HTTP context - public DroneSchedulerServiceCallerImpl() { - super(); - } - - @Override - public ListenableFuture postData(String url, T entity) { - HttpEntity httpEntity = new HttpEntity(entity, this.getRequestHeaders()); - ListenableFuture> response = getAsyncRestTemplate().postForEntity(url, httpEntity, - String.class, entity); - - return response; - } - - @Override - public ListenableFuture putData(String url, T entity, Object... args) { - HttpEntity httpEntity = new HttpEntity(entity, this.getRequestHeaders()); - ListenableFuture> response = getAsyncRestTemplate().exchange(url, HttpMethod.PUT, httpEntity, String.class); - - return response; - } - - @SuppressWarnings("unchecked") - public String getDroneId(Delivery deliveryRequest, String uri) throws InterruptedException, ExecutionException { - DroneDelivery delivery = createDroneDelivery(deliveryRequest); - ListenableFuture response = this.postData(uri, delivery); - - ResponseEntity entity = (ResponseEntity) response.get(); - return entity.getStatusCode() == HttpStatus.OK ? entity.getBody().toString() : null; - } - - - private DroneDelivery createDroneDelivery(Delivery deliveryRequest) { - DroneDelivery delivery = new DroneDelivery(); - delivery.setDeliveryId(deliveryRequest.getDeliveryId()); - - // TODO: Convert string location to Location instead of using below hack - delivery.setDropoff(LocationRandomizer.getRandomLocation()); - delivery.setPickup(LocationRandomizer.getRandomLocation()); - - delivery.setExpedited(delivery.getExpedited()); - delivery.setPackageDetail(ModelsConverter.getPackageDetail(deliveryRequest.getPackageInfo())); - - return delivery; - } - - @SuppressWarnings("unchecked") - public String getDroneIdAsync(Delivery deliveryRequest, String uri) { - // Create drone delivery to post data - DroneDelivery delivery = createDroneDelivery(deliveryRequest); - - // Let's call the backend - ListenableFuture> future = (ListenableFuture>) this.putData(uri+"/"+delivery.getDeliveryId(), - delivery); - - CompletableFuture> cfuture = toCompletableFuture(future); - - cfuture.thenAcceptAsync(response -> { - droneId = response.getBody(); - if (response.getStatusCode() != HttpStatus.OK) { - throw new BackendServiceCallFailedException(response.getStatusCode().getReasonPhrase()); - } - }).exceptionally(e -> { - throw new BackendServiceCallFailedException(ExceptionUtils.getStackTrace(e)); - }); - - future = null; - cfuture = null; - - return droneId; - } -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/PackageServiceCallerImpl.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/PackageServiceCallerImpl.java deleted file mode 100644 index 3be7cfed..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/PackageServiceCallerImpl.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.services; - -import static net.javacrumbs.futureconverter.springjava.FutureConverter.toCompletableFuture; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -import org.apache.commons.lang3.exception.ExceptionUtils; - -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.util.concurrent.ListenableFuture; - - -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.invoker.PackageGen; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.ContainerSize; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.PackageInfo; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - -public class PackageServiceCallerImpl extends ServiceCallerImpl { - - private final static JsonParser jsonParser = new JsonParser(); - - private PackageGen packageGen = null; - - // Calls the super constructor and sets the HTTP context - public PackageServiceCallerImpl() { - super(); - } - - @Override - public ListenableFuture getData(String url, T data) { - // TODO implement get methods of package service - return null; - } - - @Override - public ListenableFuture postData(String url, T entity) { - HttpEntity httpEntity = new HttpEntity(entity, this.getRequestHeaders()); - ListenableFuture> response = getAsyncRestTemplate().postForEntity(url, httpEntity, - String.class, entity); - - return response; - } - - @Override - public ListenableFuture putData(String url, T entity, Object... args) { - HttpEntity httpEntity = new HttpEntity(entity, this.getRequestHeaders()); - ListenableFuture> response = getAsyncRestTemplate().exchange(url, HttpMethod.PUT, httpEntity, String.class); - - return response; - } - - @SuppressWarnings("unchecked") - public PackageGen createPackage(PackageInfo packageInfo, String uri) - throws InterruptedException, ExecutionException { - JsonObject packObj = new JsonObject(); - - packObj.addProperty("size", packageInfo.getSize().name()); - packObj.addProperty("tag", packageInfo.getTag()); - packObj.addProperty("weight", packageInfo.getWeight()); - - ListenableFuture response = this.putData(uri+'/' + packageInfo.getPackageId(), packObj.toString()); - ResponseEntity entity = (ResponseEntity) response.get(); - - return (entity.getStatusCode() == HttpStatus.CREATED ? getPackageGen(entity.getBody()) : null); - } - - @SuppressWarnings("unchecked") - public PackageGen createPackageAsync(PackageInfo packageInfo, String uri) { - // Let's call the backend - ListenableFuture> future = (ListenableFuture>) this - .putData(uri + '/' + packageInfo.getTag(), packageInfo); - - CompletableFuture> cfuture = toCompletableFuture(future); - - cfuture.thenAcceptAsync(response -> { - if (response.getStatusCode() == HttpStatus.CREATED) { - packageGen = getPackageGen(response.getBody()); - } else { - throw new BackendServiceCallFailedException(response.getStatusCode().getReasonPhrase()); - } - }).exceptionally(e -> { - throw new BackendServiceCallFailedException(ExceptionUtils.getStackTrace(e)); - }); - - future = null; - cfuture = null; - packageInfo = null; - - return packageGen; - } - - private PackageGen getPackageGen(String jsonStr) { - PackageGen pack = new PackageGen(); - JsonElement jsonElem = jsonParser.parse(jsonStr); - JsonObject jObject = jsonElem.getAsJsonObject(); - pack.setId(jObject.get("id").getAsString()); - pack.setSize(ContainerSize.valueOf(jObject.get("size").getAsString())); - pack.setTag(jObject.get("tag").getAsString()); - - JsonElement weight = jObject.get("weight"); - pack.setWeight(weight.isJsonNull() ? 0.0 : weight.getAsDouble()); - - return pack; - } -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/ServiceCaller.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/ServiceCaller.java deleted file mode 100644 index 4bfbe0ec..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/ServiceCaller.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.services; - -import org.springframework.util.concurrent.ListenableFuture; - -public interface ServiceCaller { - ListenableFuture getData(String url, T data); - - ListenableFuture postData(String url, T entity); - - ListenableFuture putData(String url, T entity, Object... args); -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/ServiceCallerImpl.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/ServiceCallerImpl.java deleted file mode 100644 index e2a667cc..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/ServiceCallerImpl.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.services; -import java.util.Collections; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.client.HttpComponentsAsyncClientHttpRequestFactory; -import org.springframework.util.concurrent.ListenableFuture; -import org.springframework.web.client.AsyncRestTemplate; - -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.SchedulerSettings; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.utils.IdleConnectionMonitorThread; - -import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; -import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; -import org.apache.http.impl.nio.client.HttpAsyncClients; -import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager; -import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor; -import org.apache.http.nio.reactor.ConnectingIOReactor; -import org.apache.http.nio.reactor.IOReactorException; -import org.apache.http.protocol.HttpContext; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.http.ConnectionReuseStrategy; -import org.apache.http.HttpHost; -import org.apache.http.HttpResponse; - -public abstract class ServiceCallerImpl implements ServiceCaller { - private AsyncRestTemplate asyncRestTemplate; - private HttpHeaders requestHeaders; - private static final Logger Log = LogManager.getLogger(ServiceCallerImpl.class); - - public AsyncRestTemplate getAsyncRestTemplate() { - return asyncRestTemplate; - } - - public void setAsyncRestTemplate(AsyncRestTemplate asyncRest) { - this.asyncRestTemplate = asyncRest; - } - - public HttpHeaders getRequestHeaders() { - return requestHeaders; - } - - public void setRequestHeaders(HttpHeaders requestHeaders) { - this.requestHeaders = requestHeaders; - } - - @Override - public ListenableFuture getData(String url, T data) { - return null; - } - - public ServiceCallerImpl() { - ConnectingIOReactor ioReactor = null; - - try { - ioReactor = new DefaultConnectingIOReactor(); - } catch (IOReactorException e) { - Log.error(ExceptionUtils.getStackTrace(e)); - } - - PoolingNHttpClientConnectionManager poolingConnManager = - new PoolingNHttpClientConnectionManager(ioReactor); - - HttpHost httpProxy = null; - if (StringUtils.isNotEmpty(SchedulerSettings.HttpProxyValue)) { - String[] address = SchedulerSettings.HttpProxyValue.split("\\s*:\\s*"); - httpProxy = new HttpHost(address[0], Integer.parseInt(address[1])); - } - - HttpAsyncClientBuilder builder = HttpAsyncClients.custom().setConnectionManager(poolingConnManager) - .setConnectionReuseStrategy(new ConnectionReuseStrategy() { - - @Override - public boolean keepAlive(HttpResponse response, HttpContext context) { - return true; - } - }) - // .setKeepAliveStrategy(KeepAliveStrategy.getCustomKeepAliveStrategy()) - .setMaxConnPerRoute(Integer.MAX_VALUE).setMaxConnTotal(Integer.MAX_VALUE); - - if (httpProxy != null) { - builder.setProxy(httpProxy); - } - - CloseableHttpAsyncClient client = builder.build(); - - IdleConnectionMonitorThread worker = new IdleConnectionMonitorThread(poolingConnManager); - worker.start(); - - HttpComponentsAsyncClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsAsyncClientHttpRequestFactory(); - clientHttpRequestFactory.setConnectionRequestTimeout(0); - clientHttpRequestFactory.setConnectTimeout(0); - clientHttpRequestFactory.setBufferRequestBody(false); - clientHttpRequestFactory.setReadTimeout(0); - - clientHttpRequestFactory.setHttpAsyncClient(client); - asyncRestTemplate = new AsyncRestTemplate(clientHttpRequestFactory); - this.requestHeaders = new HttpHeaders(); - this.requestHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); - this.requestHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8); - asyncRestTemplate.setErrorHandler(new ServiceCallerResponseErrorHandler()); - } - - @Override - public ListenableFuture postData(String url, T entity) { - return null; - } - - @Override - public ListenableFuture putData(String url, T entity, Object... args) { - return null; - } -} \ No newline at end of file diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/ServiceCallerResponseErrorHandler.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/ServiceCallerResponseErrorHandler.java deleted file mode 100644 index 068ad590..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/ServiceCallerResponseErrorHandler.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.services; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -import org.apache.commons.io.IOUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.web.client.ResponseErrorHandler; - -public class ServiceCallerResponseErrorHandler implements ResponseErrorHandler { - - private static final Logger log = LogManager.getLogger(ServiceCallerResponseErrorHandler.class); - - @Override - public void handleError(ClientHttpResponse clienthttpresponse) throws IOException { - if(clienthttpresponse.getStatusCode().value()>=400){ - throw new BackendServiceCallFailedException(IOUtils.toString(clienthttpresponse.getBody(), StandardCharsets.UTF_8.name())); - } - } - - @Override - public boolean hasError(ClientHttpResponse clienthttpresponse) throws IOException { - if (clienthttpresponse.getStatusCode().value() >=400) { - log.error("Status code: {}", clienthttpresponse.getStatusCode()); - log.error("Response: {}", clienthttpresponse.getStatusText()); - log.error(clienthttpresponse.getBody()); - return true; - } - - return false; - } -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/ThirdPartyServiceCallerImpl.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/ThirdPartyServiceCallerImpl.java deleted file mode 100644 index cf785513..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/ThirdPartyServiceCallerImpl.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.services; - -import static net.javacrumbs.futureconverter.springjava.FutureConverter.toCompletableFuture; - -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -import org.apache.commons.lang3.exception.ExceptionUtils; - -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.util.concurrent.ListenableFuture; - -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.invoker.Location; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.utils.LocationRandomizer; - -public class ThirdPartyServiceCallerImpl extends ServiceCallerImpl { - - private Boolean isThirdPartyRequired = true; - - public ThirdPartyServiceCallerImpl() { - super(); - } - - @Override - public ListenableFuture postData(String url, T entity) { - HttpEntity httpEntity = new HttpEntity(entity); - ListenableFuture> response = getAsyncRestTemplate().postForEntity(url, httpEntity, - String.class, entity); - - return response; - } - - @Override - public ListenableFuture putData(String url, T entity, Object... args) { - HttpEntity httpEntity = new HttpEntity(entity, this.getRequestHeaders()); - ListenableFuture> response = getAsyncRestTemplate().exchange(url, HttpMethod.PUT, httpEntity, String.class); - - return response; - } - - @SuppressWarnings("unchecked") - public boolean isThirdPartyServiceRequired(String pickupLocation, String uri) - throws InterruptedException, ExecutionException { - // TODO: Convert string location to Location instead of using the below - // hack - Location pickup = LocationRandomizer.getRandomLocation(); - ListenableFuture response = this.postData(uri, pickup); - return Boolean.valueOf(((ResponseEntity) response.get()).getBody().toString()); - } - - @SuppressWarnings("unchecked") - public Boolean isThirdPartyServiceRequiredAsync(String dropOffLocation, String uri) { - - Location pickup = LocationRandomizer.getRandomLocation(); - - // Let's call the backend - ListenableFuture> future = (ListenableFuture>) this - .putData(uri + '/' + UUID.randomUUID().toString(), pickup); - - CompletableFuture> cfuture = toCompletableFuture(future); - - cfuture.thenAcceptAsync(response -> { - if (response.getStatusCode() == HttpStatus.OK) { - isThirdPartyRequired = Boolean.valueOf(response.getBody()); - } else { - throw new BackendServiceCallFailedException(response.getStatusCode().getReasonPhrase()); - } - }).exceptionally(e -> { - throw new BackendServiceCallFailedException(ExceptionUtils.getStackTrace(e)); - }); - - future = null; - cfuture = null; - - return isThirdPartyRequired; - } -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/tests/PostDeliveryRequests.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/tests/PostDeliveryRequests.java deleted file mode 100644 index b487a41a..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/tests/PostDeliveryRequests.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.services.tests; - -import static org.junit.Assert.assertEquals; - -import java.util.Collections; -import java.util.Date; -import java.util.Random; -import java.util.UUID; -import java.util.concurrent.ExecutionException; - -import org.junit.Before; -import org.junit.Test; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; - -import org.springframework.util.concurrent.ListenableFuture; -import org.springframework.web.client.AsyncRestTemplate; - -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.ConfirmationRequired; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.ContainerSize; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.Delivery; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.PackageInfo; - -public class PostDeliveryRequests { - - private AsyncRestTemplate asyncRest; - private HttpHeaders requestHeaders; - private static String postUri = "http://23.101.149.1:8080/api/deliveryrequests"; - - private final String Locations[] = {"Austin", "Seattle", "Berkley", "Oregon", "Florida", "Blaine", "Renton"}; - private static Random random = new Random(); - - private final ContainerSize[] containers = ContainerSize.values(); - private final ConfirmationRequired[] confirmations = ConfirmationRequired.values(); - - private final double leftLimit = 10D; - private final double rightLimit = 100D; - - @Before - public void setUp() throws Exception { - asyncRest = new AsyncRestTemplate(); - requestHeaders = new HttpHeaders(); - requestHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); - requestHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8); - //addServiceMeshHeaders(); - } - - @Test - public void PostDeliveryRequestsToWeb() throws InterruptedException, ExecutionException { - for(int i=0;i<5000;i++){ - Delivery delivery = createDeliveryRequest(); - System.out.println(delivery.toString()); - - HttpEntity entity = new HttpEntity(delivery, requestHeaders); - - ListenableFuture> response = asyncRest.postForEntity(postUri, entity, - Delivery.class, delivery); - - assertEquals(HttpStatus.ACCEPTED, response.get().getStatusCode()); - } - } - - private Delivery createDeliveryRequest() { - PackageInfo pack = new PackageInfo(); - - pack.setPackageId(UUID.randomUUID().toString()); - pack.setSize(containers[random.nextInt(containers.length)]); - pack.setTag(UUID.randomUUID().toString()); - pack.setWeight(leftLimit + new Random().nextDouble() * (rightLimit - leftLimit)); - - Delivery delivery = new Delivery(); - delivery.setDeliveryId(UUID.randomUUID().toString()); - delivery.setOwnerId(UUID.randomUUID().toString()); - delivery.setPickupTime(new Date()); - delivery.setDropOffLocation(Locations[random.nextInt(Locations.length)]); - delivery.setPickupLocation(Locations[random.nextInt(Locations.length)]); - delivery.setConfirmationRequired(confirmations[random.nextInt(confirmations.length)]); - delivery.setDeadline("LineOfDeadPeople"); - delivery.setExpedited(true); - - delivery.setPackageInfo(pack); - - return delivery; - } - - -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/tests/TestBackendServices.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/tests/TestBackendServices.java deleted file mode 100644 index 53da2fde..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/tests/TestBackendServices.java +++ /dev/null @@ -1,180 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.services.tests; - -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.DeliveryRequestEventProcessor; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.SchedulerSettings; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.invoker.Location; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.ConfirmationRequired; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.ContainerSize; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.Delivery; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.PackageInfo; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.utils.LocationRandomizer; -import com.github.tomakehurst.wiremock.junit.WireMockClassRule; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; -import org.springframework.web.client.AsyncRestTemplate; - -import java.io.IOException; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -import static com.github.tomakehurst.wiremock.client.WireMock.*; -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static net.javacrumbs.futureconverter.springjava.FutureConverter.toCompletableFuture; -import static org.junit.Assert.assertEquals; - -public class TestBackendServices { - @ClassRule - public static WireMockClassRule wiremock = new WireMockClassRule(wireMockConfig().dynamicPort().dynamicHttpsPort()); - - public final String baseUri = "http://localhost:" + wiremock.port(); - - private Random random = new Random(); - - private final String Locations[] = { "Austin", "Seattle", "Berkley", "Oregon", "Florida", "Blaine", "Renton" }; - - private final ContainerSize[] containers = ContainerSize.values(); - private final ConfirmationRequired[] confirmations = ConfirmationRequired.values(); - - private AsyncRestTemplate restTemplate = new AsyncRestTemplate(); - - @Before - public void setUp() throws Exception { - - } - - - private void setupStubAccountService() { - // Account service stub - wiremock.stubFor(get(urlPathMatching("/api/Account/.*")).willReturn( - aResponse().withStatus(200).withHeader("Content-Type", "text/plain").withBody("true"))); - } - - private void setupStubThirdPartyService(){ - wiremock.stubFor(put(urlPathMatching("/api/ThirdPartyDeliveries/.*")) - .withHeader("Content-Type", equalTo("application/json")) - .withRequestBody(matchingJsonPath("{ \"altitude\": 0, \"latitude\": 0, \"longitude\": 0 }")) - .willReturn( - aResponse().withStatus(200) - .withHeader("Content-Type", "application/json; charset=utf-8") - .withBody("false"))); - - } - - @Test - public void CanRetrieveAccountStatusFromMockServiceAsync() throws IOException, InterruptedException, ExecutionException { - setupStubAccountService(); - String uri = this.baseUri + "/api/Account/some-random-account-id"; - - CompletableFuture> cfuture = toCompletableFuture( - this.restTemplate.getForEntity(uri, String.class)); - String result = ((HttpEntity)cfuture.get()).getBody(); - assertEquals(result, "true"); - } - - @Test - public void CanRetrieveThirdPartyStatusFromMockServiceAsync() throws IOException, InterruptedException, ExecutionException { - setupStubThirdPartyService(); - String uri = this.baseUri + "/api/ThirdPartyDeliveries/some-random-delivery-id"; - - HttpEntity entity = new HttpEntity(LocationRandomizer.getRandomLocation()); - CompletableFuture> cfuture = toCompletableFuture( - this.restTemplate.exchange(uri, HttpMethod.PUT, entity, String.class)); - String result = ((HttpEntity)cfuture.get()).getBody(); - assertEquals(result, "false"); - } - - @Test - public void CanRetrieveAccountStatusFromAccountServiceAsync() throws InterruptedException, ExecutionException { - - Map properties = new HashMap(); - SchedulerSettings.AccountServiceUri = "http://51.179.155.83/api/Account"; - DeliveryRequestEventProcessor.invokeAccountServiceAsync(this.createDeliveryRequest(), properties); - -// CompletableFuture cfuture = toCompletableFuture( -// accountService.getData("http://51.179.155.83/api/Account/", accountId)); -// String result = ((HttpEntity)cfuture.get()).getBody(); -// assertEquals(result, "true"); - } - - -// @Test -// public void CanRetrieveThirdPartyConsentFromServiceAsync() throws InterruptedException, ExecutionException { -// // TODO: Revisit this implementation since response body parameters are -// // split though passing one works -// ThirdPartyServiceCallerImpl thirdpartySvc = new ThirdPartyServiceCallerImpl(); -// DeferredResult result = thirdpartySvc.isThirdPartyServiceRequiredAsync("Idaho", SchedulerSettings.ThirdPartyServiceUri); -// result.onCompletion(() -> { -// assertEquals(false, result.getResult()); -// }); -// } - -// @Test -// public void CanRetrieveDroneIdFromDroneDeliveryServiceAsync() throws InterruptedException, ExecutionException { -// DroneSchedulerServiceCallerImpl droneSvc = new DroneSchedulerServiceCallerImpl(); -// Delivery deliveryRequest = this.createDeliveryRequest(); -// DeferredResult droneId = droneSvc.getDroneIdAsync(deliveryRequest, SchedulerSettings.DroneSchedulerServiceUri); -// -// droneId.onCompletion(() -> { -// assertTrue(DroneServiceReturnValuePrefix, -// ((String) droneId.getResult()).startsWith(DroneServiceReturnValuePrefix)); -// }); -// } - -// @Test -// public void CanScheduleDeliveryWithDeliveryServiceAsync() throws InterruptedException, ExecutionException { -// String droneId = UUID.randomUUID().toString(); -// DeliveryServiceCallerImpl deliverySvc = new DeliveryServiceCallerImpl(); -// DeferredResult deliveryScheduled = deliverySvc -// .scheduleDeliveryAsync(this.createDeliveryRequest(), droneId, SchedulerSettings.DeliveryServiceUri); -// -// deliveryScheduled.onCompletion(() -> { -// DeliverySchedule delivery = (DeliverySchedule) deliveryScheduled.getResult(); -// assertEquals(droneId, delivery.getDroneId()); -// }); -// } - -// @Test -// public void CanRetrievePackagesInfoFromPackageServiceAsync() throws InterruptedException, ExecutionException { -// PackageInfo pack = new PackageInfo(); -// -// pack.setPackageId(UUID.randomUUID().toString()); -// pack.setSize(containers[random.nextInt(containers.length)]); -// -// PackageServiceCallerImpl packageSvc = new PackageServiceCallerImpl(); -// DeferredResult defResult = packageSvc.createPackageAsync(pack, -// SchedulerSettings.PackageServiceUri); -// -// defResult.onCompletion(() -> { -// PackageGen packGen = (PackageGen) defResult.getResult(); -// assertEquals(pack.getPackageId(), packGen.getTag()); -// }); -// } - - private Delivery createDeliveryRequest() { - PackageInfo pack = new PackageInfo(); - - pack.setPackageId(UUID.randomUUID().toString()); - pack.setSize(containers[random.nextInt(containers.length)]); - - Delivery delivery = new Delivery(); - - delivery.setDeliveryId(UUID.randomUUID().toString()); - delivery.setOwnerId(UUID.randomUUID().toString()); - delivery.setPickupTime(new Date()); - delivery.setDropOffLocation(Locations[random.nextInt(Locations.length)]); - delivery.setPickupLocation(Locations[random.nextInt(Locations.length)]); - delivery.setConfirmationRequired(confirmations[random.nextInt(confirmations.length)]); - delivery.setDeadline("LineOfDeadPeople"); - delivery.setExpedited(true); - - delivery.setPackageInfo(pack); - - return delivery; - } -} - diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/tests/ValidateSerializationOptions.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/tests/ValidateSerializationOptions.java deleted file mode 100644 index 5816b7da..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/services/tests/ValidateSerializationOptions.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.services.tests; - -import static org.junit.Assert.*; - -import java.io.IOException; -import java.util.Date; -import java.util.Random; -import java.util.UUID; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.springframework.util.Assert; - -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.ConfirmationRequired; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.ContainerSize; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.Delivery; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.PackageInfo; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.gson.Gson; -import org.junit.contrib.java.lang.system.EnvironmentVariables; - -public class ValidateSerializationOptions { - - private Gson serializer; - private ObjectMapper objMapper; - private final String Locations[] = { "Austin", "Seattle", "Berkley", "Oregon", "Florida", "Blaine", "Renton" }; - private final static Random random = new Random(); - - private final ContainerSize[] containers = ContainerSize.values(); - private final ConfirmationRequired[] confirmations = ConfirmationRequired.values(); - - private static final String envHostNameString = "HOST_POD_NAME"; - private static final String envHttpProxyString = "http_proxy"; - - @Rule - public final EnvironmentVariables environmentVariables = new EnvironmentVariables(); - - @Before - public void setUp() throws Exception { - serializer = new Gson(); - objMapper = new ObjectMapper(); - } - - @Test - public void ValidateSerializationWithGson() { - Delivery delivery = createDelivery(); - String jsonDelivery = serializer.toJson(delivery); - Delivery rehydratedDelivery = serializer.fromJson(jsonDelivery, Delivery.class); - Assert.isInstanceOf(Delivery.class, rehydratedDelivery, "Delivery serialized correctly"); - } - - @Test - public void ValidateSerializationWithJackson() throws IOException { - Delivery delivery = createDelivery(); - String jsonDelivery = objMapper.writeValueAsString(delivery); - Delivery rehydratedDelivery = objMapper.readValue(jsonDelivery, Delivery.class); - Assert.isInstanceOf(Delivery.class, rehydratedDelivery, "Delivery serialized correctly"); - } - - @Test - public void ValidateParseDeliveryWithSpecificPayload() { - String jsonPayload = "{\"OwnerId\":\"f0a8680e-574a-4d30-89a6-9c8d8ca2302d\",\"DeliveryId\":\"37bbe418-2254-4ebf-a848-bd9929f10b75\",\"Packages\":[{\"PackageId\":\"5114e11d-0e23-47d4-a36c-2a297c83037d\",\"Size\":1}],\"deadline\":\"LineOfDeadlyZombiatedPeople\",\"confirmationRequired\":3,\"expedited\":false,\"DropoffLocation\":\"Seattle\",\"PickupTime\":\"Jul 28, 2017 04:06:24 PM\",\"PickupLocation\":\"Florida\"}"; - Delivery delivery = serializer.fromJson(jsonPayload, Delivery.class); - Assert.isInstanceOf(Delivery.class, delivery); - } - - @Test - public void validateSetEnvironmentVariable() { - environmentVariables.set(envHostNameString, "mypod-1"); - assertEquals("mypod-1", System.getenv(envHostNameString)); - } - - @Test - public void validateSetEnvironmentVariableHttpProxy(){ - environmentVariables.set(envHttpProxyString, "k8s-agent-f1cfb2cf-0:4140"); - String[] address = System.getenv(envHttpProxyString).split("\\s*:\\s*"); - assertEquals(address[0], "k8s-agent-f1cfb2cf-0"); - assertEquals(Integer.parseInt(address[1]), 4140); - } - - @Test - public void validateParseEnvironmentVariable() { - environmentVariables.set(envHostNameString, "mypod-1"); - String hostName = System.getenv(envHostNameString); - int partitionId = -1; - if (hostName != null) { - String trimmedHostName = hostName.trim(); - if (trimmedHostName != "") { - partitionId = Integer.parseInt(trimmedHostName.substring(trimmedHostName.indexOf('-') + 1)); - } - } - - assertEquals(1, partitionId); - } - - private Delivery createDelivery() { - PackageInfo pack = new PackageInfo(); - pack.setSize(containers[random.nextInt(containers.length)]); - pack.setPackageId(UUID.randomUUID().toString()); - - Delivery delivery = new Delivery(); - - delivery.setOwnerId(UUID.randomUUID().toString()); - delivery.setPickupTime(new Date()); - delivery.setDropOffLocation(Locations[random.nextInt(Locations.length)]); - delivery.setPickupLocation(Locations[random.nextInt(Locations.length)]); - delivery.setConfirmationRequired(confirmations[random.nextInt(confirmations.length)]); - - delivery.setPackageInfo(pack); - - return delivery; - } -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/utils/ConfigReader.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/utils/ConfigReader.java deleted file mode 100644 index db66c7ae..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/utils/ConfigReader.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.utils; - -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class ConfigReader { - - private static final Logger Log = LogManager.getLogger(ConfigReader.class); - - private static Map getConfig(Properties properties) { - Map config = new HashMap(10); - for (Object id : properties.keySet()) { - String key = (String) id; - config.put(key, properties.getProperty(key)); - } - - return config; - } - - public static void printProperties(Map map) { - for (Map.Entry entry : map.entrySet()) { - Log.error(entry.getKey() + ": " + entry.getValue()); - } - } - - public static Map readAllConfigurationValues(String filename) { - Map appConfig = null; - Properties properties = new Properties(); - try { - properties.load(ConfigReader.class.getClassLoader().getResourceAsStream(filename)); - appConfig = getConfig(properties); - } catch (Exception e) { - Log.error(ExceptionUtils.getStackTrace(e).toString()); - } - - return appConfig; - } -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/utils/CustomDateTimeDeserializer.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/utils/CustomDateTimeDeserializer.java deleted file mode 100644 index 5421e30c..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/utils/CustomDateTimeDeserializer.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.utils; - -import java.io.IOException; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; - -public class CustomDateTimeDeserializer extends JsonDeserializer { - private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - @Override - public Date deserialize(JsonParser paramJsonParser, DeserializationContext paramDeserializationContext) - throws IOException, JsonProcessingException { - String str = paramJsonParser.getText().trim(); - try { - return dateFormat.parse(str); - } catch (ParseException e) { - - } - - return paramDeserializationContext.parseDate(str); - } -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/utils/IdleConnectionMonitorThread.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/utils/IdleConnectionMonitorThread.java deleted file mode 100644 index f349b372..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/utils/IdleConnectionMonitorThread.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.utils; - -import java.util.concurrent.TimeUnit; - -import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager; - -public class IdleConnectionMonitorThread extends Thread { - - private final PoolingNHttpClientConnectionManager connMgr; - private volatile boolean shutdown; - - public IdleConnectionMonitorThread(PoolingNHttpClientConnectionManager poolingConnManager) { - super(); - this.connMgr = poolingConnManager; - } - - @Override - public void run() { - try { - while (!shutdown) { - synchronized (this) { - wait(5000); - // Close expired connections - connMgr.closeExpiredConnections(); - // Optionally, close connections - // that have been idle longer than 30 sec - connMgr.closeIdleConnections(30, TimeUnit.SECONDS); - } - } - } catch (InterruptedException ex) { - // terminate - } - } - - public void shutdown() { - shutdown = true; - synchronized (this) { - notifyAll(); - } - } - -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/utils/KeepAliveStrategy.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/utils/KeepAliveStrategy.java deleted file mode 100644 index 35ee877f..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/utils/KeepAliveStrategy.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.utils; - -import org.apache.http.HeaderElement; -import org.apache.http.HeaderElementIterator; -import org.apache.http.HttpResponse; -import org.apache.http.conn.ConnectionKeepAliveStrategy; -import org.apache.http.message.BasicHeaderElementIterator; -import org.apache.http.protocol.HTTP; -import org.apache.http.protocol.HttpContext; - -public class KeepAliveStrategy { - - public static ConnectionKeepAliveStrategy getCustomKeepAliveStrategy() { - return new ConnectionKeepAliveStrategy() { - @Override - public long getKeepAliveDuration(HttpResponse response, HttpContext context) { - // Honor 'keep-alive' header - HeaderElementIterator it = new BasicHeaderElementIterator( - response.headerIterator(HTTP.CONN_KEEP_ALIVE)); - while (it.hasNext()) { - HeaderElement he = it.nextElement(); - String param = he.getName(); - String value = he.getValue(); - if (value != null && param.equalsIgnoreCase("timeout")) { - try { - return Long.parseLong(value) * 1000; - } catch (NumberFormatException ignore) { - } - } - } - - return Long.MAX_VALUE; - } - }; - } -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/utils/LocationRandomizer.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/utils/LocationRandomizer.java deleted file mode 100644 index 6dbb8d07..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/utils/LocationRandomizer.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.utils; - -import java.util.Random; - -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.invoker.Location; - -public class LocationRandomizer { - - private static Random random = new Random(); - - public static Location getRandomLocation() { - Location location = new Location(); - location.setAltitude(random.nextDouble()); - location.setLatitude(random.nextDouble()); - location.setLongitude(random.nextDouble()); - - return location; - } -} diff --git a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/utils/ModelsConverter.java b/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/utils/ModelsConverter.java deleted file mode 100644 index 62f8bfcf..00000000 --- a/src/bc-shipping/scheduler/src/com/fabrikam/dronedelivery/deliveryscheduler/scheduler/utils/ModelsConverter.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.fabrikam.dronedelivery.deliveryscheduler.scheduler.utils; - -import java.util.ArrayList; -import java.util.List; - -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.invoker.ConfirmationType; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.invoker.PackageDetail; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.invoker.PackageSize; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.ConfirmationRequired; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.ContainerSize; -import com.fabrikam.dronedelivery.deliveryscheduler.scheduler.models.receiver.PackageInfo; - -public class ModelsConverter { - public static PackageDetail getPackageDetail(PackageInfo packageInfo) { - PackageDetail packageDetail = new PackageDetail(); - packageDetail.setId(packageInfo.getPackageId()); - packageDetail.setSize(getPackageSize(packageInfo.getSize())); - return packageDetail; - } - - public static List getListOfPackageDetail(List packages) { - List listOfPackages = new ArrayList(); - for (PackageInfo packageInfo : packages) { - PackageDetail packageDetail = new PackageDetail(); - packageDetail.setId(packageInfo.getPackageId()); - packageDetail.setSize(getPackageSize(packageInfo.getSize())); - listOfPackages.add(packageDetail); - } - - return listOfPackages; - } - - public static PackageSize getPackageSize(ContainerSize containerSize) { - return containerSize != null ? PackageSize.values()[containerSize.ordinal()] : PackageSize.Small; - } - - public static ConfirmationType getConfirmationType(ConfirmationRequired confirm) { - return confirm != null ? ConfirmationType.values()[confirm.ordinal()] : ConfirmationType.None; - } -} diff --git a/src/loadtests/Fabrikam.Shipping.LoadTests.sln b/src/loadtests/Fabrikam.Shipping.LoadTests.sln new file mode 100644 index 00000000..5d5b4438 --- /dev/null +++ b/src/loadtests/Fabrikam.Shipping.LoadTests.sln @@ -0,0 +1,30 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28714.193 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fabrikam.Shipping.LoadTests", "Fabrikam.Shipping.LoadTests\Fabrikam.Shipping.LoadTests.csproj", "{4BD986A7-3F52-46FA-AA35-CD5D8AC532E0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AD4DC75F-D0D4-4ACA-B852-508F78588C4B}" + ProjectSection(SolutionItems) = preProject + FabrikamIngestion-CreateDynamicDeliveryRequests.testsettings = FabrikamIngestion-CreateDynamicDeliveryRequests.testsettings + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4BD986A7-3F52-46FA-AA35-CD5D8AC532E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4BD986A7-3F52-46FA-AA35-CD5D8AC532E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4BD986A7-3F52-46FA-AA35-CD5D8AC532E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4BD986A7-3F52-46FA-AA35-CD5D8AC532E0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BE782D95-E9EF-4B0E-9C6E-99A3288DB6EA} + EndGlobalSection +EndGlobal diff --git a/src/loadtests/Fabrikam.Shipping.LoadTests/ContextParamLoadTestPlugin.cs b/src/loadtests/Fabrikam.Shipping.LoadTests/ContextParamLoadTestPlugin.cs new file mode 100644 index 00000000..60c1247b --- /dev/null +++ b/src/loadtests/Fabrikam.Shipping.LoadTests/ContextParamLoadTestPlugin.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.VisualStudio.TestTools.LoadTesting; + +namespace Fabrikam.Shipping.LoadTests +{ + public class ContextParameterLoadTestPlugin : ILoadTestPlugin + { + public void Initialize(LoadTest loadTest) + { + loadTest.TestStarting += (s, e) => + { + foreach (string key in loadTest.Context.Keys) + { + e.TestContextProperties.Add(key, loadTest.Context[key]); + } + }; + } + } +} diff --git a/src/loadtests/Fabrikam.Shipping.LoadTests/DeliveryRequestLoadTest.loadtest b/src/loadtests/Fabrikam.Shipping.LoadTests/DeliveryRequestLoadTest.loadtest new file mode 100644 index 00000000..8999387f --- /dev/null +++ b/src/loadtests/Fabrikam.Shipping.LoadTests/DeliveryRequestLoadTest.loadtest @@ -0,0 +1,473 @@ + + + + + + + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/loadtests/Fabrikam.Shipping.LoadTests/DeliveryRequestWebTest.cs b/src/loadtests/Fabrikam.Shipping.LoadTests/DeliveryRequestWebTest.cs new file mode 100644 index 00000000..2f9236ca --- /dev/null +++ b/src/loadtests/Fabrikam.Shipping.LoadTests/DeliveryRequestWebTest.cs @@ -0,0 +1,109 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +using Microsoft.VisualStudio.TestTools.WebTesting; +using Newtonsoft.Json; + +namespace Fabrikam.Shipping.LoadTests +{ + public class DeliveryRequestWebTest : WebTest + { + private const string ContextParamIngestUrl = "INGEST_URL"; + + private const string DeliveryId = "42"; + private const string PackageId = "442"; + private const string PackageLocationDropOff = "0,37.4315730000000,-78.65689399999997"; + private const string PackageLocationPickup = "0,36.778261,-119.41793200000001"; + + public DeliveryRequestWebTest() + { + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; + ServicePointManager.ServerCertificateValidationCallback = RemoteCertificateValidationCallBack; + } + + public static bool RemoteCertificateValidationCallBack( + object sender, + X509Certificate certificate, + X509Chain chain, + SslPolicyErrors sslPolicyErrors) => true; + + public override IEnumerator GetRequestEnumerator() + { + Uri deliveryRequestUri = this.CreateDeliveryRequestUri(); + + var deliveryRequest = new WebTestRequest(deliveryRequestUri) + { + Method = "POST", + Body = CreateRandomHttpBodyString() + }; + + yield return deliveryRequest; + } + + private Uri CreateDeliveryRequestUri() + { + if (!(this.Context[ContextParamIngestUrl].ToString() is var ingestUrl + && !string.IsNullOrEmpty(ingestUrl))) + { + throw new ArgumentNullException($"{ContextParamIngestUrl} load test context param value can not be null"); + } + + if (!Uri.TryCreate( + ingestUrl, + UriKind.Absolute, + out Uri ingestUri)) + { + throw new ArgumentException($"{ingestUrl} is not a valid absolute URI"); + } + + if (!Uri.TryCreate( + ingestUri, + "/api/deliveryrequests", + out Uri deliveryRequestUri)) + { + throw new ArgumentException($"{ingestUrl}/api/deliveryrequests is not a valid URI"); + } + + return deliveryRequestUri; + } + + private static StringHttpBody CreateRandomHttpBodyString() + { + Guid randomTag = Guid.NewGuid(); + + var httpBodyRequestWithRandomTag = + new StringHttpBody + { + ContentType = "application/json", + InsertByteOrderMark = false, + BodyString = JsonConvert.SerializeObject(new + { + confirmationRequired = "FingerPrint", + deadline = "DeadlyQueueOfZombiatedDemons", + deliveryId = DeliveryId, + dropOffLocation = PackageLocationDropOff, + expedited = true, + ownerId = "1", + packageInfo = new + { + packageId = PackageId, + size = "Small", + tag = randomTag, + weight = "14" + }, + pickupLocation = PackageLocationPickup, + pickupTime = "2019-04-05T11:00:00.000Z" + }) + }; + + return httpBodyRequestWithRandomTag; + } + } +} diff --git a/src/loadtests/Fabrikam.Shipping.LoadTests/Fabrikam.Shipping.LoadTests.csproj b/src/loadtests/Fabrikam.Shipping.LoadTests/Fabrikam.Shipping.LoadTests.csproj new file mode 100644 index 00000000..5c8aeda9 --- /dev/null +++ b/src/loadtests/Fabrikam.Shipping.LoadTests/Fabrikam.Shipping.LoadTests.csproj @@ -0,0 +1,94 @@ + + + + Debug + AnyCPU + + + 2.0 + {4BD986A7-3F52-46FA-AA35-CD5D8AC532E0} + Library + Properties + Fabrikam.Shipping.LoadTests + Fabrikam.Shipping.LoadTests + v4.7.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + WebTest + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + + + + ..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll + + + + + + False + + + + + + + + + + Designer + + + + + + + + False + + + False + + + False + + + False + + + + + + + + diff --git a/src/loadtests/Fabrikam.Shipping.LoadTests/Properties/AssemblyInfo.cs b/src/loadtests/Fabrikam.Shipping.LoadTests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..7ac70ccd --- /dev/null +++ b/src/loadtests/Fabrikam.Shipping.LoadTests/Properties/AssemblyInfo.cs @@ -0,0 +1,39 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DeliverySchedulerLoadTest")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DeliverySchedulerLoadTest")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("4bd986a7-3f52-46fa-aa35-cd5d8ac532e0")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("0.1.0.0")] +[assembly: AssemblyFileVersion("0.1.0.0")] diff --git a/src/loadtests/Fabrikam.Shipping.LoadTests/packages.config b/src/loadtests/Fabrikam.Shipping.LoadTests/packages.config new file mode 100644 index 00000000..97c22dc5 --- /dev/null +++ b/src/loadtests/Fabrikam.Shipping.LoadTests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/loadtests/FabrikamIngestion-CreateDynamicDeliveryRequests.testsettings b/src/loadtests/FabrikamIngestion-CreateDynamicDeliveryRequests.testsettings new file mode 100644 index 00000000..ba006600 --- /dev/null +++ b/src/loadtests/FabrikamIngestion-CreateDynamicDeliveryRequests.testsettings @@ -0,0 +1,29 @@ + + + These are default test settings for a local test run. + + + + + + + + + + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/src/loadtests/readme.md b/src/loadtests/readme.md new file mode 100644 index 00000000..8a6b9145 --- /dev/null +++ b/src/loadtests/readme.md @@ -0,0 +1,27 @@ +## Delivery Request Load Testing + +### Prerequisites +a. Microsoft Visual Studio 2017 or later Enterprise Edition +b. Web performance and load testing tools must be installed. For more information, please take a look at [Install the Load testing component](https://docs.microsoft.com/en-us/visualstudio/test/quickstart-create-a-load-test-project?view=vs-2017#install-the-load-testing-component) +c. Azure DevOps subscription +d. ```EXTERNAL_INGEST_FQDN``` value from [deployment instructions](../../../deployment.md) + +### Run load testing in Azure DevOps + +1. open Microsoft Visual Studio 2017 or later +2. open the solution ./Fabrikam.Shipping.LoadTests.sln +3. navigate to Team Explorer and connect to your Azure DevOps subscription +4. double click on DeliveryRequestLoadTest.loadtest + > if the xml is open instead of the load test designer. + Right click on .loadtest file from the Solution Explorer, and then click ```Open With``` and select Load Test Editor +5. double click ```INGEST_URL``` Context Parameter +6. from Properties panel, set its value using the environment variable ```EXTERNAL_INGEST_FQDN``` as shown below: + > https://```$EXTERNAL_INGEST_FQDN``` + + > Important: the url should look like, https://```.```.cloudapp.azure.com +7. click Run Load Test + > if the ```EXTERNAL_INGEST_FQDN``` was not configured properly it is noticed from Test Results ```Graph``` because Total Requests metric is 0. + Please check from ```Details``` looking for errors and ensure the check FQDN is well formed. + +> Note: this load test executes two separate scenarios, the first one will use a single user to delivery at about 200 request/sec. +While the second scenario is designed to load 40 users to deliver up to 2600 request/sec. Depending on how much ingestion and backend services instances are configured, the load they will ingest/egress respectively. diff --git a/src/shipping/delivery/Dockerfile b/src/shipping/delivery/Dockerfile new file mode 100644 index 00000000..2ac03568 --- /dev/null +++ b/src/shipping/delivery/Dockerfile @@ -0,0 +1,58 @@ +FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 as base +WORKDIR /app +EXPOSE 8080 + +FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build + +WORKDIR /app +COPY Fabrikam.DroneDelivery.Common/*.csproj ./Fabrikam.DroneDelivery.Common/ +COPY Fabrikam.DroneDelivery.DeliveryService/*.csproj ./Fabrikam.DroneDelivery.DeliveryService/ +WORKDIR /app +RUN dotnet restore /app/Fabrikam.DroneDelivery.Common/ +RUN dotnet restore /app/Fabrikam.DroneDelivery.DeliveryService/ + +WORKDIR /app +COPY Fabrikam.DroneDelivery.Common/. ./Fabrikam.DroneDelivery.Common/ +COPY Fabrikam.DroneDelivery.DeliveryService/. ./Fabrikam.DroneDelivery.DeliveryService/ + +FROM build AS testrunner + +WORKDIR /app/tests +COPY Fabrikam.DroneDelivery.DeliveryService.Tests/*.csproj . +WORKDIR /app/tests +RUN dotnet restore /app/tests/ + +WORKDIR /app/tests +COPY Fabrikam.DroneDelivery.DeliveryService.Tests/. . +ENTRYPOINT ["dotnet", "test", "--logger:trx"] + +FROM build AS publish + +MAINTAINER Fernando Antivero (https://github.com/ferantivero) + +WORKDIR /app +RUN dotnet publish /app/Fabrikam.DroneDelivery.DeliveryService/ -c release -o ./out --no-restore + +FROM base AS runtime + +MAINTAINER Fernando Antivero (https://github.com/ferantivero) + +LABEL Tags="Azure,AKS,DroneDelivery" + +ARG user=deliveryuser + +RUN useradd -m -s /bin/bash -U $user + +WORKDIR /app +COPY --from=publish /app/out ./ +COPY scripts/. ./ +RUN \ + # Ensures the entry point is executable + chmod ugo+x /app/run.sh + +RUN chown -R $user.$user /app + +# Set it for subsequent commands +USER $user + +ENTRYPOINT ["/bin/bash", "/app/run.sh"] \ No newline at end of file diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.Common/DeliveryStage.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.Common/DeliveryStage.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.Common/DeliveryStage.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.Common/DeliveryStage.cs diff --git a/src/shipping/delivery/Fabrikam.DroneDelivery.Common/Fabrikam.DroneDelivery.Common.csproj b/src/shipping/delivery/Fabrikam.DroneDelivery.Common/Fabrikam.DroneDelivery.Common.csproj new file mode 100644 index 00000000..db0c8a56 --- /dev/null +++ b/src/shipping/delivery/Fabrikam.DroneDelivery.Common/Fabrikam.DroneDelivery.Common.csproj @@ -0,0 +1,13 @@ + + + + netcoreapp3.1 + + + + + + + + + \ No newline at end of file diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.Common/Location.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.Common/Location.cs similarity index 77% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.Common/Location.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.Common/Location.cs index ebde932b..21fd6888 100644 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.Common/Location.cs +++ b/src/shipping/delivery/Fabrikam.DroneDelivery.Common/Location.cs @@ -7,14 +7,16 @@ namespace Fabrikam.DroneDelivery.Common { public class Location { + public Location() {} + public Location(double altitude, double latitude, double longitude) { Altitude = altitude; Latitude = latitude; Longitude = longitude; } - public double Altitude { get; } - public double Latitude { get; } - public double Longitude { get; } + public double Altitude { get; set; } + public double Latitude { get; set; } + public double Longitude { get; set; } } } diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.Common/UserAccount.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.Common/UserAccount.cs similarity index 80% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.Common/UserAccount.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.Common/UserAccount.cs index 6f9d49a4..b407236b 100644 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.Common/UserAccount.cs +++ b/src/shipping/delivery/Fabrikam.DroneDelivery.Common/UserAccount.cs @@ -7,12 +7,14 @@ namespace Fabrikam.DroneDelivery.Common { public class UserAccount { + public UserAccount() {} + public UserAccount(string userid, string accountid) { UserId = userid; AccountId = accountid; } - public string UserId { get; } - public string AccountId { get; } + public string UserId { get; set;} + public string AccountId { get; set;} } } diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/Common/DocumentClientExceptionExtensions.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/Common/DocumentClientExceptionExtensions.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/Common/DocumentClientExceptionExtensions.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/Common/DocumentClientExceptionExtensions.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/DeliveriesControllerFixture.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/DeliveriesControllerFixture.cs similarity index 83% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/DeliveriesControllerFixture.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/DeliveriesControllerFixture.cs index eb88c5ef..548e54af 100644 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/DeliveriesControllerFixture.cs +++ b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/DeliveriesControllerFixture.cs @@ -3,17 +3,18 @@ // Licensed under the MIT License (MIT). See License.txt in the repo root for license information. // ------------------------------------------------------------ +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; using Fabrikam.DroneDelivery.Common; using Fabrikam.DroneDelivery.DeliveryService.Controllers; -using Fabrikam.DroneDelivery.DeliveryService.Services; using Fabrikam.DroneDelivery.DeliveryService.Models; +using Fabrikam.DroneDelivery.DeliveryService.Services; +using Moq; namespace Fabrikam.DroneDelivery.DeliveryService.Tests { @@ -40,10 +41,9 @@ public async Task Get_Returns404_IfDeliveryIdNotValid() var target = new DeliveriesController(new Mock().Object, new Mock().Object, new Mock().Object, - new Mock().Object, new Mock().Object, loggerFactory.Object); - + // Act var result = await target.Get("invaliddeliveryid") as NotFoundResult; @@ -62,7 +62,6 @@ public async Task GetOwner_Returns404_IfDeliveryIdNotValid() var target = new DeliveriesController(new Mock().Object, new Mock().Object, new Mock().Object, - new Mock().Object, new Mock().Object, loggerFactory.Object); @@ -88,10 +87,9 @@ public async Task GetOwner_ReturnsOwner() var target = new DeliveriesController(deliveryRepository.Object, new Mock().Object, new Mock().Object, - new Mock().Object, new Mock().Object, loggerFactory.Object); - + // Act var result = await target.GetOwner("deliveryid") as OkObjectResult; @@ -110,7 +108,6 @@ public async Task GetStatus_Returns404_IfDeliveryIdNotValid() var target = new DeliveriesController(new Mock().Object, new Mock().Object, new Mock().Object, - new Mock().Object, new Mock().Object, loggerFactory.Object); @@ -137,7 +134,6 @@ public async Task Put_Returns204_IfDeliveryIdExists() var target = new DeliveriesController(deliveryRepository.Object, new Mock().Object, new Mock().Object, - new Mock().Object, new Mock().Object, loggerFactory.Object); @@ -163,7 +159,6 @@ public async Task Put_AddsToCache() var target = new DeliveriesController(deliveryRepository.Object, new Mock().Object, new Mock().Object, - new Mock().Object, new Mock().Object, loggerFactory.Object); // Act @@ -192,7 +187,6 @@ public async Task Put_AddscreatedDeliveryEvent() var target = new DeliveriesController(new Mock().Object, new Mock().Object, new Mock().Object, - new Mock().Object, deliveryStatusEventRepository.Object, loggerFactory.Object); // Act @@ -217,7 +211,6 @@ public async Task Patch_Returns404_IfDeliveryIdNotValid() var target = new DeliveriesController(new Mock().Object, new Mock().Object, new Mock().Object, - new Mock().Object, new Mock().Object, loggerFactory.Object); @@ -251,7 +244,6 @@ public async Task Patch_UpdatesCache() var target = new DeliveriesController(deliveryRepository.Object, new Mock().Object, new Mock().Object, - new Mock().Object, new Mock().Object, loggerFactory.Object); @@ -295,7 +287,6 @@ public async Task Patch_AddsRescheduledDeliveryEvent() var target = new DeliveriesController(deliveryRepository.Object, new Mock().Object, new Mock().Object, - new Mock().Object, deliveryStatusEventRepository.Object, loggerFactory.Object); @@ -319,7 +310,6 @@ public async Task Delete_Returns404_IfDeliveryIdNotValid() var target = new DeliveriesController(new Mock().Object, new Mock().Object, new Mock().Object, - new Mock().Object, new Mock().Object, loggerFactory.Object); @@ -336,19 +326,10 @@ public async Task Delete_SendsMessageWithCancelledTrackingEvent() { // Arrange DeliveryTrackingEvent cancelledDelivery = null; - DeliveryTrackingEvent[] allTrackingEvents = null; var deliveryRepository = new Mock(); deliveryRepository.Setup(r => r.GetAsync("deliveryid")).ReturnsAsync(delivery); - var deliveryHistoryService = new Mock(); - deliveryHistoryService.Setup(r => r.CancelAsync(It.IsAny(), It.IsAny())) - .Returns(Task.CompletedTask) - .Callback((d, es) => - { - allTrackingEvents = es; - }); - var deliveryStatusEventRepository = new Mock(); deliveryStatusEventRepository.Setup(r => r.AddAsync(It.IsAny())) .Returns(Task.CompletedTask) @@ -363,7 +344,6 @@ public async Task Delete_SendsMessageWithCancelledTrackingEvent() var target = new DeliveriesController(deliveryRepository.Object, new Mock().Object, new Mock().Object, - deliveryHistoryService.Object, deliveryStatusEventRepository.Object, loggerFactory.Object); // Act @@ -374,7 +354,6 @@ public async Task Delete_SendsMessageWithCancelledTrackingEvent() Assert.AreEqual("deliveryid", cancelledDelivery.DeliveryId); Assert.AreEqual(DeliveryStage.Cancelled, cancelledDelivery.Stage); deliveryRepository.VerifyAll(); - deliveryHistoryService.Verify(s => s.CancelAsync(delivery, allTrackingEvents), Times.Once); } [TestMethod] @@ -387,7 +366,6 @@ public async Task NotifyMe_Returns404_IfDeliveryIdNotValid() var target = new DeliveriesController(new Mock().Object, new Mock().Object, new Mock().Object, - new Mock().Object, new Mock().Object, loggerFactory.Object); @@ -419,12 +397,11 @@ public async Task NotifyMe_AddsNotifyMeRequest() var target = new DeliveriesController(deliveryRepository.Object, notifyMeRequestRepository.Object, new Mock().Object, - new Mock().Object, new Mock().Object, loggerFactory.Object); var notifyMeRequest = new NotifyMeRequest("email@test.com", "1234567"); - + // Act var result = await target.NotifyMe("deliveryid", notifyMeRequest) as NoContentResult; @@ -445,7 +422,6 @@ public async Task Confirm_Returns404_IfDeliveryIdNotValid() var target = new DeliveriesController(new Mock().Object, new Mock().Object, new Mock().Object, - new Mock().Object, new Mock().Object, loggerFactory.Object); // Act @@ -486,16 +462,12 @@ public async Task Confirm_SendsNotifications() deliveryStatusEventRepository.Setup(r => r.GetByDeliveryIdAsync("deliveryid")) .ReturnsAsync(new ReadOnlyCollection(new List() { completedDelivery })); - var deliveryHistoryService = new Mock(); - deliveryHistoryService.Setup(r => r.CompleteAsync(delivery, It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); - var loggerFactory = new Mock(); loggerFactory.Setup(f => f.CreateLogger(It.IsAny())).Returns(new Mock().Object); var target = new DeliveriesController(deliveryRepository.Object, notifyMeRequestRepository.Object, notificationService.Object, - new Mock().Object, deliveryStatusEventRepository.Object, loggerFactory.Object); @@ -512,20 +484,17 @@ public async Task Confirm_SendsNotifications() } [TestMethod] - public async Task Confirm_SendsMessageCompleteToDeliveryHistory() + public async Task Confirm_DeletesDeliveryLogically() { // Arrange InternalDelivery confirmedDelivery = null; - InternalConfirmation sentConfirmation = null; DeliveryTrackingEvent completedDelivery = null; - DeliveryTrackingEvent[] allTrackingEvents = null; var deliveryRepository = new Mock(); deliveryRepository.Setup(r => r.GetAsync("deliveryid")).ReturnsAsync(delivery); - - var notifyMeRequestRepository = new Mock(); - notifyMeRequestRepository.Setup(r => r.GetAllByDeliveryIdAsync("deliveryid")) - .ReturnsAsync(new List()); + deliveryRepository.Setup(r => r.DeleteAsync("deliveryid", It.IsAny())) + .Returns(Task.CompletedTask) + .Callback((i, d) => confirmedDelivery = d); var deliveryStatusEventRepository = new Mock(); deliveryStatusEventRepository.Setup(r => r.AddAsync(It.IsAny())) @@ -535,28 +504,17 @@ public async Task Confirm_SendsMessageCompleteToDeliveryHistory() deliveryStatusEventRepository.Setup(r => r.GetByDeliveryIdAsync("deliveryid")) .ReturnsAsync(new ReadOnlyCollection(new List() { completedDelivery })); - var deliveryHistoryService = new Mock(); - deliveryHistoryService.Setup(r => r.CompleteAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(Task.CompletedTask) - .Callback((d, c, es) => - { - confirmedDelivery = d; - sentConfirmation = c; - allTrackingEvents = es; - }); - var loggerFactory = new Mock(); loggerFactory.Setup(f => f.CreateLogger(It.IsAny())).Returns(new Mock().Object); var target = new DeliveriesController(deliveryRepository.Object, - notifyMeRequestRepository.Object, + new Mock().Object, new Mock().Object, - deliveryHistoryService.Object, deliveryStatusEventRepository.Object, loggerFactory.Object); - + var location = new Location(1, 2, 3); var confirmation = new Confirmation(new DateTimeStamp("datetimevalue"), - new Location(1, 2, 3), + location, ConfirmationType.Picture, "confirmationblob"); @@ -565,27 +523,18 @@ public async Task Confirm_SendsMessageCompleteToDeliveryHistory() // Assert Assert.IsNotNull(result); - Assert.AreEqual("datetimevalue", sentConfirmation.DateTime.DateTimeValue); - Assert.AreEqual(1, sentConfirmation.GeoCoordinates.Altitude); - Assert.AreEqual(2, sentConfirmation.GeoCoordinates.Latitude); - Assert.AreEqual(3, sentConfirmation.GeoCoordinates.Longitude); - Assert.AreEqual(ConfirmationType.Picture, sentConfirmation.ConfirmationType); - Assert.AreEqual("confirmationblob", sentConfirmation.ConfirmationBlob); - deliveryHistoryService.Verify(s => s.CompleteAsync(confirmedDelivery, It.IsAny(), allTrackingEvents), Times.Once); + deliveryRepository.Verify(r => r.DeleteAsync("deliveryid", confirmedDelivery)); + Assert.AreEqual(location, confirmedDelivery.Dropoff); } [TestMethod] - public async Task Confirm_DeletesDeliveryLogically() + public async Task Confirm_AddsDeliveryCompletedEvent() { // Arrange - InternalDelivery confirmedDelivery = null; DeliveryTrackingEvent completedDelivery = null; var deliveryRepository = new Mock(); deliveryRepository.Setup(r => r.GetAsync("deliveryid")).ReturnsAsync(delivery); - deliveryRepository.Setup(r => r.DeleteAsync("deliveryid", It.IsAny())) - .Returns(Task.CompletedTask) - .Callback((i, d) => confirmedDelivery = d); var deliveryStatusEventRepository = new Mock(); deliveryStatusEventRepository.Setup(r => r.AddAsync(It.IsAny())) @@ -601,12 +550,11 @@ public async Task Confirm_DeletesDeliveryLogically() var target = new DeliveriesController(deliveryRepository.Object, new Mock().Object, new Mock().Object, - new Mock().Object, deliveryStatusEventRepository.Object, loggerFactory.Object); - var location = new Location(1, 2, 3); + var confirmation = new Confirmation(new DateTimeStamp("datetimevalue"), - location, + new Location(1, 2, 3), ConfirmationType.Picture, "confirmationblob"); @@ -615,26 +563,20 @@ public async Task Confirm_DeletesDeliveryLogically() // Assert Assert.IsNotNull(result); - deliveryRepository.Verify(r => r.DeleteAsync("deliveryid", confirmedDelivery)); - Assert.AreEqual(location, confirmedDelivery.Dropoff); + Assert.IsNotNull(completedDelivery); + Assert.AreEqual(DeliveryStage.Completed, completedDelivery.Stage); } [TestMethod] - public async Task Confirm_AddsDeliveryCompletedEvent() + public async Task GetSummry_ReturnsSummary() { - // Arrange - DeliveryTrackingEvent completedDelivery = null; + const int TestDeliveryCount = 5000; + // Arrange var deliveryRepository = new Mock(); - deliveryRepository.Setup(r => r.GetAsync("deliveryid")).ReturnsAsync(delivery); - - var deliveryStatusEventRepository = new Mock(); - deliveryStatusEventRepository.Setup(r => r.AddAsync(It.IsAny())) - .Returns(Task.CompletedTask) - .Callback(e => completedDelivery = e); - - deliveryStatusEventRepository.Setup(r => r.GetByDeliveryIdAsync("deliveryid")) - .ReturnsAsync(new ReadOnlyCollection(new List() { completedDelivery })); + deliveryRepository.Setup(r => r.GetDeliveryCountAsync("owner", 2019, 01)) + .ReturnsAsync(TestDeliveryCount) + .Verifiable(); var loggerFactory = new Mock(); loggerFactory.Setup(f => f.CreateLogger(It.IsAny())).Returns(new Mock().Object); @@ -642,23 +584,17 @@ public async Task Confirm_AddsDeliveryCompletedEvent() var target = new DeliveriesController(deliveryRepository.Object, new Mock().Object, new Mock().Object, - new Mock().Object, - deliveryStatusEventRepository.Object, + new Mock().Object, loggerFactory.Object); - var confirmation = new Confirmation(new DateTimeStamp("datetimevalue"), - new Location(1, 2, 3), - ConfirmationType.Picture, - "confirmationblob"); - // Act - var result = await target.Confirm("deliveryid", confirmation) as OkResult; + var result = await target.GetSummary("owner", 2019, 01) as OkObjectResult; // Assert Assert.IsNotNull(result); - Assert.IsNotNull(completedDelivery); - Assert.AreEqual(DeliveryStage.Completed, completedDelivery.Stage); + Assert.IsInstanceOfType(result.Value, typeof(DeliveriesSummary)); + Assert.AreEqual(TestDeliveryCount, ((DeliveriesSummary)result.Value).Count); + deliveryRepository.VerifyAll(); } - } } diff --git a/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/DeliveriesControllerRoutingFixture.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/DeliveriesControllerRoutingFixture.cs new file mode 100644 index 00000000..9b99f947 --- /dev/null +++ b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/DeliveriesControllerRoutingFixture.cs @@ -0,0 +1,206 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Fabrikam.DroneDelivery.Common; +using Fabrikam.DroneDelivery.DeliveryService.Middlewares.Builder; +using Fabrikam.DroneDelivery.DeliveryService.Models; +using Fabrikam.DroneDelivery.DeliveryService.Services; +using Moq; + +namespace Fabrikam.DroneDelivery.DeliveryService.Tests +{ + [TestClass] + public class DeliveriesControllerRoutingFixture + { + private readonly TestServer _testServer; + private readonly Mock _deliveryRepositoryMock; + private readonly Mock _notifyMeRequestRepository; + private readonly Mock _notificationService; + private readonly Mock _deliveryTrackingRepository; + + public DeliveriesControllerRoutingFixture() + { + _deliveryRepositoryMock = new Mock(); + _notifyMeRequestRepository = new Mock(); + _notificationService = new Mock(); + _deliveryTrackingRepository = new Mock(); + + _testServer = + new TestServer( + new WebHostBuilder() + .UseTestServer() + .Configure(builder => + { + builder.UseGlobalExceptionHandler(); + + builder.UseRouting(); + builder.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + }) + .ConfigureServices(builder => + { + builder.AddControllers(); + + builder.AddSingleton(_deliveryRepositoryMock.Object); + builder.AddSingleton(_notifyMeRequestRepository.Object); + builder.AddSingleton(_notificationService.Object); + builder.AddSingleton(_deliveryTrackingRepository.Object); + })); + } + + [TestCleanup] + public void TearDown() + { + _testServer?.Dispose(); + } + + [TestMethod] + public async Task GetDelivery_GetsResponse() + { + var deliveryId = Guid.NewGuid().ToString(); + + _deliveryRepositoryMock + .Setup(r => r.GetAsync(deliveryId)) + .ReturnsAsync(new InternalDelivery(deliveryId, null, null, null, null, false, ConfirmationType.None, null)) + .Verifiable(); + + using (var client = _testServer.CreateClient()) + { + var response = await client.GetAsync($"http://localhost/api/deliveries/{deliveryId}"); + response.EnsureSuccessStatusCode(); + + var delivery = await response.Content.ReadAsAsync(); + + Assert.AreEqual(deliveryId, delivery.Id); + } + + _deliveryRepositoryMock.VerifyAll(); + } + + [TestMethod] + public async Task GetDeliveryThroughPublicRoute_GetsResponse() + { + var deliveryId = Guid.NewGuid().ToString(); + + _deliveryRepositoryMock + .Setup(r => r.GetAsync(deliveryId)) + .ReturnsAsync(new InternalDelivery(deliveryId, null, null, null, null, false, ConfirmationType.None, null)) + .Verifiable(); + + using (var client = _testServer.CreateClient()) + { + var response = await client.GetAsync($"http://localhost/api/deliveries/public/{deliveryId}"); + response.EnsureSuccessStatusCode(); + + var delivery = await response.Content.ReadAsAsync(); + + Assert.AreEqual(deliveryId, delivery.Id); + } + + _deliveryRepositoryMock.VerifyAll(); + } + + [TestMethod] + public async Task PutDelivery_GetsResponse() + { + var deliveryId = Guid.NewGuid().ToString(); + var delivery = new Delivery(deliveryId, new UserAccount("user", "accound"), null, null, null, false, ConfirmationType.None, null); + + _deliveryRepositoryMock + .Setup(r => r.CreateAsync(It.Is(d => d.Id == deliveryId))) + .Returns(Task.CompletedTask) + .Verifiable(); + _deliveryTrackingRepository + .Setup(r => r.AddAsync(It.Is(e => e.DeliveryId == deliveryId))) + .Returns(Task.CompletedTask) + .Verifiable(); + + using (var client = _testServer.CreateClient()) + { + var response = await client.PutAsJsonAsync($"http://localhost/api/deliveries/{deliveryId}", delivery); + Assert.AreEqual(HttpStatusCode.Created, response.StatusCode); + + var createdDelivery = await response.Content.ReadAsAsync(); + Assert.AreEqual(deliveryId, delivery.Id); + } + + _deliveryRepositoryMock.VerifyAll(); + _deliveryTrackingRepository.VerifyAll(); + } + + [TestMethod] + public async Task PutDeliveryThroughPublicRoute_GetsMethodNotAllowedResponse() + { + var deliveryId = Guid.NewGuid().ToString(); + var delivery = new Delivery(deliveryId, new UserAccount("user", "accound"), null, null, null, false, ConfirmationType.None, null); + + using (var client = _testServer.CreateClient()) + { + var response = await client.PutAsJsonAsync($"http://localhost/api/deliveries/public/{deliveryId}", delivery); + Assert.AreEqual(HttpStatusCode.MethodNotAllowed, response.StatusCode); + } + + _deliveryRepositoryMock.VerifyAll(); + _deliveryTrackingRepository.VerifyAll(); + } + + [TestMethod] + public async Task GetOwner_GetsResponse() + { + var deliveryId = Guid.NewGuid().ToString(); + + _deliveryRepositoryMock + .Setup(r => r.GetAsync(deliveryId)) + .ReturnsAsync(new InternalDelivery(deliveryId, new UserAccount("user", "account"), null, null, null, false, ConfirmationType.None, null)) + .Verifiable(); + + using (var client = _testServer.CreateClient()) + { + var response = await client.GetAsync($"http://localhost/api/deliveries/{deliveryId}/owner"); + response.EnsureSuccessStatusCode(); + + var userAccount = await response.Content.ReadAsAsync(); + + Assert.AreEqual("user", userAccount.UserId); + } + + _deliveryRepositoryMock.VerifyAll(); + } + + [TestMethod] + public async Task GetStatus_GetsResponse() + { + var deliveryId = Guid.NewGuid().ToString(); + + _deliveryRepositoryMock + .Setup(r => r.GetAsync(deliveryId)) + .ReturnsAsync(new InternalDelivery(deliveryId, new UserAccount("user", "account"), null, null, null, false, ConfirmationType.None, null)) + .Verifiable(); + + using (var client = _testServer.CreateClient()) + { + var response = await client.GetAsync($"http://localhost/api/deliveries/{deliveryId}/status"); + response.EnsureSuccessStatusCode(); + + var status = await response.Content.ReadAsAsync(); + + Assert.AreEqual(DeliveryStage.Created, status.Stage); // exposes deserialization issue + } + + _deliveryRepositoryMock.VerifyAll(); + } + } +} diff --git a/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/Fabrikam.DroneDelivery.DeliveryService.Tests.csproj b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/Fabrikam.DroneDelivery.DeliveryService.Tests.csproj new file mode 100644 index 00000000..dcf23957 --- /dev/null +++ b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/Fabrikam.DroneDelivery.DeliveryService.Tests.csproj @@ -0,0 +1,31 @@ + + + + netcoreapp3.1 + false + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/GlobalInternalErrorHandlerMiddlewareFixture.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/GlobalInternalErrorHandlerMiddlewareFixture.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/GlobalInternalErrorHandlerMiddlewareFixture.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/GlobalInternalErrorHandlerMiddlewareFixture.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/GlobalLoggerMiddlewareFixture.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/GlobalLoggerMiddlewareFixture.cs similarity index 86% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/GlobalLoggerMiddlewareFixture.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/GlobalLoggerMiddlewareFixture.cs index 172529e0..17acf30c 100644 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/GlobalLoggerMiddlewareFixture.cs +++ b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService.Tests/GlobalLoggerMiddlewareFixture.cs @@ -70,12 +70,12 @@ public async Task IfHandledInternalServerError_ItLogsError() await logRequestMiddleware.Invoke(contextMock.Object); // Assert - loggerMock.Verify(l => l.Log( + loggerMock.Verify(l => l.Log( LogLevel.Error, It.IsAny(), - It.Is(fV => fV.ToString().Equals(($"An internal handled exception has occurred: {exMessage}"))), + It.Is((object fV, Type _) => fV.ToString().Equals(($"An internal handled exception has occurred: {exMessage}"))), It.IsAny(), - It.IsAny>())); + (Func)It.IsAny())); } [TestMethod] @@ -117,12 +117,12 @@ public async Task IfHandledClientError_ItLogsError() await logRequestMiddleware.Invoke(contextMock.Object); // Assert - loggerMock.Verify(l => l.Log( + loggerMock.Verify(l => l.Log( LogLevel.Error, It.IsAny(), - It.Is(fV => fV.ToString().Equals(($"An error has occurred: 499"))), + It.Is((object fV, Type _) => fV.ToString().Equals(($"An error has occurred: 499"))), null, - It.IsAny>())); + (Func)It.IsAny())); } [TestMethod] @@ -164,12 +164,12 @@ public async Task IfTooManyRequestIsHandled_ItLogsError() await logRequestMiddleware.Invoke(contextMock.Object); // Assert - loggerMock.Verify(l => l.Log( + loggerMock.Verify(l => l.Log( LogLevel.Error, It.IsAny(), - It.Is(fV => fV.ToString().Equals(($"An error has occurred: 429"))), + It.Is((object fV, Type _) => fV.ToString().Equals(($"An error has occurred: 429"))), null, - It.IsAny>())); + (Func)It.IsAny())); } [TestMethod] @@ -213,24 +213,23 @@ public async Task IfUnhandledExceptionWhileResponding_ItLogsErrorPlusWarningAndR try { await logRequestMiddleware.Invoke(contextMock.Object); - } // Assert catch (Exception) { - loggerMock.Verify(l => l.Log( + loggerMock.Verify(l => l.Log( LogLevel.Error, It.IsAny(), - It.Is(fV => fV.ToString().Equals(($"An exception was thrown attempting to execute the global internal server error handler: {exMessage}"))), + It.Is((object fV, Type _) => fV.ToString().Equals(($"An exception was thrown attempting to execute the global internal server error handler: {exMessage}"))), It.IsAny(), - It.IsAny>())); + (Func)It.IsAny())); - loggerMock.Verify(l => l.Log( + loggerMock.Verify(l => l.Log( LogLevel.Warning, It.IsAny(), - It.Is(fV => fV.ToString().Equals(("The response has already started, the error handler will not be executed."))), + It.Is((object fV, Type _) => fV.ToString().Equals(("The response has already started, the error handler will not be executed."))), null, - It.IsAny>())); + (Func)It.IsAny())); // re-throw the actual re-throw throw; } diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/.dockerignore b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/.dockerignore similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/.dockerignore rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/.dockerignore diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Controllers/DeliveriesController.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Controllers/DeliveriesController.cs similarity index 81% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Controllers/DeliveriesController.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Controllers/DeliveriesController.cs index 7ae462f6..59107998 100644 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Controllers/DeliveriesController.cs +++ b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Controllers/DeliveriesController.cs @@ -6,8 +6,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Fabrikam.DroneDelivery.Common; @@ -16,42 +16,41 @@ namespace Fabrikam.DroneDelivery.DeliveryService.Controllers { + [ApiController] [Route("api/[controller]")] - public class DeliveriesController : Controller + public class DeliveriesController : ControllerBase { private readonly IDeliveryRepository deliveryRepository; private readonly INotifyMeRequestRepository notifyMeRequestRepository; private readonly INotificationService notificationService; - private readonly IDeliveryHistoryService deliveryHistoryService; private readonly IDeliveryTrackingEventRepository deliveryTrackingRepository; private readonly ILogger logger; public DeliveriesController(IDeliveryRepository deliveryRepository, INotifyMeRequestRepository notifyMeRequestRepository, INotificationService notificationService, - IDeliveryHistoryService deliveryHistoryRepository, IDeliveryTrackingEventRepository deliveryTrackingRepository, ILoggerFactory loggerFactory) { this.deliveryRepository = deliveryRepository; this.notifyMeRequestRepository = notifyMeRequestRepository; this.notificationService = notificationService; - this.deliveryHistoryService = deliveryHistoryRepository; this.deliveryTrackingRepository = deliveryTrackingRepository; this.logger = loggerFactory.CreateLogger(); } // GET api/deliveries/5 - [Route("/api/[controller]/{id}", Name = "GetDelivery")] - [HttpGet] - [ProducesResponseType(typeof(Delivery), 200)] + [HttpGet("{id}", Name = "GetDelivery")] + [HttpGet("public/{id}")] + [ProducesResponseType(typeof(Delivery), StatusCodes.Status200OK)] public async Task Get(string id) { logger.LogInformation("In Get action with id: {Id}", id); var internalDelivery = await deliveryRepository.GetAsync(id); - if (internalDelivery == null) { + if (internalDelivery == null) + { logger.LogDebug("Delivery id: {Id} not found", id); return NotFound(); } @@ -60,9 +59,8 @@ public async Task Get(string id) } // GET api/deliveries/5/owner - [Route("/api/[controller]/{id}/owner")] - [HttpGet] - [ProducesResponseType(typeof(UserAccount), 200)] + [HttpGet("{id}/owner")] + [ProducesResponseType(typeof(UserAccount), StatusCodes.Status200OK)] public async Task GetOwner(string id) { logger.LogInformation("In GetOwner action with id: {Id}", id); @@ -78,13 +76,12 @@ public async Task GetOwner(string id) } // GET api/deliveries/5/status - [Route("/api/[controller]/{id}/status")] - [HttpGet] - [ProducesResponseType(typeof(DeliveryStatus), 200)] + [HttpGet("{id}/status")] + [ProducesResponseType(typeof(DeliveryStatus), StatusCodes.Status200OK)] public async Task GetStatus(string id) { logger.LogInformation("In GetStatus action with id: {Id}", id); - + var delivery = await deliveryRepository.GetAsync(id); if (delivery == null) { @@ -92,14 +89,14 @@ public async Task GetStatus(string id) return NotFound(); } - var status = new DeliveryStatus(DeliveryStage.HeadedToDropoff, new Location(0,0,0), DateTime.Now.AddMinutes(10).ToString(), DateTime.Now.AddHours(1).ToString()); + var status = new DeliveryStatus(DeliveryStage.HeadedToDropoff, new Location(0, 0, 0), DateTime.Now.AddMinutes(10).ToString(), DateTime.Now.AddHours(1).ToString()); return Ok(status); } // PUT api/deliveries/5 [HttpPut("{id}")] - [ProducesResponseType(typeof(Delivery), 201)] - [ProducesResponseType(typeof(void), 204)] + [ProducesResponseType(typeof(Delivery), StatusCodes.Status201Created)] + [ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)] public async Task Put([FromBody]Delivery delivery, string id) { logger.LogInformation("In Put action with delivery {Id}: {@DeliveryInfo}", id, delivery.ToLogInfo()); @@ -115,7 +112,7 @@ public async Task Put([FromBody]Delivery delivery, string id) var deliveryTrackingEvent = new DeliveryTrackingEvent { DeliveryId = delivery.Id, Stage = DeliveryStage.Created }; await deliveryTrackingRepository.AddAsync(deliveryTrackingEvent); - return CreatedAtRoute("GetDelivery", new { id= delivery.Id }, delivery); + return CreatedAtRoute("GetDelivery", new { id = delivery.Id }, delivery); } catch (DuplicateResourceException) { @@ -152,7 +149,7 @@ public async Task Patch(string id, [FromBody]RescheduledDelivery delivery.Expedited, delivery.ConfirmationRequired, delivery.DroneId); - + // Adds the delivery rescheduled status event var deliveryTrackingEvent = new DeliveryTrackingEvent { DeliveryId = id, Stage = DeliveryStage.Rescheduled }; await deliveryTrackingRepository.AddAsync(deliveryTrackingEvent); @@ -179,10 +176,6 @@ public async Task Delete(string id) var deliveryTrackingEvent = new DeliveryTrackingEvent { DeliveryId = id, Stage = DeliveryStage.Cancelled }; await deliveryTrackingRepository.AddAsync(deliveryTrackingEvent); - // forwards cancelled delivery to the Delivery History - var allTrackingEvents = await deliveryTrackingRepository.GetByDeliveryIdAsync(id); - await deliveryHistoryService.CancelAsync(delivery, allTrackingEvents.ToArray()); - // logical delivery deletion await deliveryRepository.DeleteAsync(id, delivery); @@ -190,8 +183,7 @@ public async Task Delete(string id) } // POST api/deliveries/5/notifymerequests - [Route("/api/[controller]/{id}/notifymerequests")] - [HttpPost] + [HttpPost("{id}/notifymerequests")] public async Task NotifyMe(string id, [FromBody]NotifyMeRequest notifyMeRequest) { logger.LogInformation("In NotifyMe action with id: {Id} and notifyMeRequest: {@NotifyMeRequest}", id, notifyMeRequest.ToLogInfo()); @@ -225,8 +217,7 @@ public async Task NotifyMe(string id, [FromBody]NotifyMeRequest n /// /// // POST api/deliveries/5/confirmations - [Route("/api/[controller]/{id}/confirmations")] - [HttpPost] + [HttpPost("{id}/confirmations")] public async Task Confirm(string id, [FromBody]Confirmation confirmation) { logger.LogInformation("In Confirm action with id: {Id} and confirmation: {@Confirmation}", id, confirmation.ToLogInfo()); @@ -256,16 +247,12 @@ public async Task Confirm(string id, [FromBody]Confirmation confi }; // Adds the delivery complete status event - await deliveryTrackingRepository.AddAsync(new DeliveryTrackingEvent - { - DeliveryId = id, - Stage = DeliveryStage.Completed - }); - // get all the milestones from cache - var allTrackingEvents = await deliveryTrackingRepository.GetByDeliveryIdAsync(id); - - // archives Delivery by sending it to the Delivery History + Confirmantion details as well as forwarding milestones to the Delivery History - await deliveryHistoryService.CompleteAsync(confirmedDelivery, internalConfirmation, allTrackingEvents.ToArray()); + await deliveryTrackingRepository.AddAsync( + new DeliveryTrackingEvent + { + DeliveryId = id, + Stage = DeliveryStage.Completed + }); // sends notifications var notifyMeRequests = await notifyMeRequestRepository.GetAllByDeliveryIdAsync(id); @@ -278,5 +265,18 @@ await deliveryTrackingRepository.AddAsync(new DeliveryTrackingEvent return Ok(); } + + [HttpGet("summary")] + [ProducesResponseType(typeof(DeliveriesSummary), StatusCodes.Status200OK)] + public async Task GetSummary([FromQuery] string ownerId, [FromQuery] int year, [FromQuery] int month) + { + var deliveryCount = await deliveryRepository.GetDeliveryCountAsync(ownerId, year, month); + + return Ok( + new DeliveriesSummary + { + Count = deliveryCount + }); + } } } diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Fabrikam.DroneDelivery.DeliveryService.csproj b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Fabrikam.DroneDelivery.DeliveryService.csproj similarity index 59% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Fabrikam.DroneDelivery.DeliveryService.csproj rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Fabrikam.DroneDelivery.DeliveryService.csproj index 7b1d4e88..9d0d3031 100644 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Fabrikam.DroneDelivery.DeliveryService.csproj +++ b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Fabrikam.DroneDelivery.DeliveryService.csproj @@ -1,8 +1,7 @@  - netcoreapp2.0 - ..\docker-compose.dcproj + netcoreapp3.1 @@ -10,17 +9,19 @@ - + + + + + - - - - - - + + + + diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Middlewares/Builder/GlobalHandlersBuilder.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Middlewares/Builder/GlobalHandlersBuilder.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Middlewares/Builder/GlobalHandlersBuilder.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Middlewares/Builder/GlobalHandlersBuilder.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Middlewares/GlobalInternalErrorHandlerMiddleware.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Middlewares/GlobalInternalErrorHandlerMiddleware.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Middlewares/GlobalInternalErrorHandlerMiddleware.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Middlewares/GlobalInternalErrorHandlerMiddleware.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Middlewares/GlobalLoggerMiddleware.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Middlewares/GlobalLoggerMiddleware.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Middlewares/GlobalLoggerMiddleware.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Middlewares/GlobalLoggerMiddleware.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/BaseCache.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/BaseCache.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/BaseCache.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/BaseCache.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/BaseDocument.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/BaseDocument.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/BaseDocument.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/BaseDocument.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/BaseMessage.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/BaseMessage.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/BaseMessage.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/BaseMessage.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/Confirmation.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/Confirmation.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/Confirmation.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/Confirmation.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/ConfirmationType.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/ConfirmationType.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/ConfirmationType.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/ConfirmationType.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DateTimeStamp.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DateTimeStamp.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DateTimeStamp.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DateTimeStamp.cs diff --git a/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DeliveriesSummary.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DeliveriesSummary.cs new file mode 100644 index 00000000..304a3108 --- /dev/null +++ b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DeliveriesSummary.cs @@ -0,0 +1,14 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; + +namespace Fabrikam.DroneDelivery.DeliveryService.Models +{ + public class DeliveriesSummary + { + public int Count { get; internal set; } + } +} diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/Delivery.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/Delivery.cs similarity index 72% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/Delivery.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/Delivery.cs index e9b9e749..f2f4cfc3 100644 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/Delivery.cs +++ b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/Delivery.cs @@ -3,13 +3,14 @@ // Licensed under the MIT License (MIT). See License.txt in the repo root for license information. // ------------------------------------------------------------ -using System.Collections.ObjectModel; using Fabrikam.DroneDelivery.Common; namespace Fabrikam.DroneDelivery.DeliveryService.Models { public class Delivery { + public Delivery() {} + public Delivery(string id, UserAccount owner, Location pickup, @@ -29,13 +30,13 @@ public Delivery(string id, DroneId = droneid; } - public string Id { get; } - public UserAccount Owner { get; } - public Location Pickup { get; } - public Location Dropoff { get; } - public string Deadline { get; } - public bool Expedited { get; } - public ConfirmationType ConfirmationRequired { get; } - public string DroneId { get; } + public string Id { get; set;} + public UserAccount Owner { get; set;} + public Location Pickup { get; set;} + public Location Dropoff { get; set;} + public string Deadline { get; set;} + public bool Expedited { get; set;} + public ConfirmationType ConfirmationRequired { get; set;} + public string DroneId { get; set;} } } diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DeliveryHistory.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DeliveryHistory.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DeliveryHistory.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DeliveryHistory.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DeliveryStatus.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DeliveryStatus.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DeliveryStatus.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DeliveryStatus.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DeliveryTrackingEvent.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DeliveryTrackingEvent.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DeliveryTrackingEvent.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DeliveryTrackingEvent.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DuplicateResourceException.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DuplicateResourceException.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DuplicateResourceException.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/DuplicateResourceException.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/Extensions.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/Extensions.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/Extensions.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/Extensions.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/InternalConfirmation.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/InternalConfirmation.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/InternalConfirmation.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/InternalConfirmation.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/InternalDelivery.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/InternalDelivery.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/InternalDelivery.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/InternalDelivery.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/InternalNotifyMeRequest.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/InternalNotifyMeRequest.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/InternalNotifyMeRequest.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/InternalNotifyMeRequest.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/NotifyMeRequest.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/NotifyMeRequest.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/NotifyMeRequest.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/NotifyMeRequest.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/RescheduledDelivery.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/RescheduledDelivery.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/RescheduledDelivery.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Models/RescheduledDelivery.cs diff --git a/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Program.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Program.cs new file mode 100644 index 00000000..c592c4c7 --- /dev/null +++ b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Program.cs @@ -0,0 +1,39 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Serilog; + +namespace Fabrikam.DroneDelivery.DeliveryService +{ + public class Program + { + public static void Main(string[] args) + { + var host = CreateHostBuilder(args).Build(); + var logger = host.Services.GetRequiredService>(); + logger.LogInformation("Fabrikam Delivery Service is starting."); + + host.Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder + .UseStartup() + .ConfigureLogging((hostingContext, loggingBuilder) => + { + loggingBuilder.AddApplicationInsights(); + loggingBuilder.AddSerilog(dispose: true); + }) + .UseUrls("http://0.0.0.0:8080"); + }); + } +} \ No newline at end of file diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/Constants.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/Constants.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/Constants.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/Constants.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/DeliveryRepository.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/DeliveryRepository.cs similarity index 80% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/DeliveryRepository.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/DeliveryRepository.cs index 3151657e..85c4bccc 100644 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/DeliveryRepository.cs +++ b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/DeliveryRepository.cs @@ -3,6 +3,7 @@ // Licensed under the MIT License (MIT). See License.txt in the repo root for license information. // ------------------------------------------------------------ +using System; using System.Threading.Tasks; using Fabrikam.DroneDelivery.DeliveryService.Models; @@ -29,5 +30,15 @@ public async Task DeleteAsync(string id, InternalDelivery delivery) { await RedisCache.DeleteItemAsync(id, delivery).ConfigureAwait(continueOnCapturedContext: false); } + + public Task GetDeliveryCountAsync(string ownerId, int year, int month) + { + const int MinDeliveries = 1000; + const int MaxDeliveries = 10000; + + var deliveryCount = new Random().Next(MinDeliveries, MaxDeliveries); + + return Task.FromResult(deliveryCount); + } } } diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/DeliveryTrackingEventRepository.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/DeliveryTrackingEventRepository.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/DeliveryTrackingEventRepository.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/DeliveryTrackingEventRepository.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/DocumentDBRepository.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/DocumentDBRepository.cs similarity index 85% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/DocumentDBRepository.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/DocumentDBRepository.cs index 825d0adb..0e671d08 100644 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/DocumentDBRepository.cs +++ b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/DocumentDBRepository.cs @@ -35,6 +35,18 @@ public static void Configure(string endpoint, string key, string databaseId, str client = new DocumentClient(new Uri(Endpoint), Key); logger = loggerFactory.CreateLogger(nameof(DocumentDBRepository)); + logger.LogInformation($"Creating CosmosDb Database {DatabaseId} if not exists..."); + var taskCreateDb = client.CreateDatabaseIfNotExistsAsync(new Database { Id = DatabaseId }); + taskCreateDb.GetAwaiter().GetResult(); + logger.LogInformation($"CosmosDb Database {DatabaseId} creation if not exists: OK!"); + var dataBaseUri = UriFactory.CreateDatabaseUri(DatabaseId); + logger.LogInformation($"Creating CosmosDb Collection {CollectionId} for {dataBaseUri.ToString()} if not exists..."); + var taskCreateDocCollection = client.CreateDocumentCollectionIfNotExistsAsync( + UriFactory.CreateDatabaseUri(DatabaseId), + new DocumentCollection { Id = CollectionId }, + new RequestOptions { OfferThroughput = 1000 }); + taskCreateDocCollection.GetAwaiter().GetResult(); + logger.LogInformation($"CosmosDb Collection {CollectionId} creation if not exists: OK!"); } public static async Task GetItemAsync(string id, string partitionKey) diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/IDeliveryRepository.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/IDeliveryRepository.cs similarity index 88% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/IDeliveryRepository.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/IDeliveryRepository.cs index 9c16bc53..a6fbed69 100644 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/IDeliveryRepository.cs +++ b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/IDeliveryRepository.cs @@ -3,6 +3,7 @@ // Licensed under the MIT License (MIT). See License.txt in the repo root for license information. // ------------------------------------------------------------ +using System; using System.Threading.Tasks; using Fabrikam.DroneDelivery.DeliveryService.Models; @@ -14,5 +15,6 @@ public interface IDeliveryRepository Task CreateAsync(InternalDelivery delivery); Task UpdateAsync(string id, InternalDelivery updatedDelivery); Task DeleteAsync(string id, InternalDelivery delivery); + Task GetDeliveryCountAsync(string ownerId, int year, int month); } } \ No newline at end of file diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/IDeliveryTrackingEventRepository.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/IDeliveryTrackingEventRepository.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/IDeliveryTrackingEventRepository.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/IDeliveryTrackingEventRepository.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/INotificationService.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/INotificationService.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/INotificationService.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/INotificationService.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/INotifyMeRequestRepository.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/INotifyMeRequestRepository.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/INotifyMeRequestRepository.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/INotifyMeRequestRepository.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/NoOpNotificationService.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/NoOpNotificationService.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/NoOpNotificationService.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/NoOpNotificationService.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/NotifyMeRequestRepository.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/NotifyMeRequestRepository.cs similarity index 100% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/NotifyMeRequestRepository.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/NotifyMeRequestRepository.cs diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/RedisCache.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/RedisCache.cs similarity index 91% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/RedisCache.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/RedisCache.cs index dbfad577..a14a2682 100644 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/RedisCache.cs +++ b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Services/RedisCache.cs @@ -16,14 +16,14 @@ namespace Fabrikam.DroneDelivery.DeliveryService.Services { public static class RedisCache where T : BaseCache { - private static string ConnectionString; + private static ConfigurationOptions ConfigOptions; private static int DB; private static ILogger logger; private static Lazy lazyConnection = new Lazy(() => { // automatically disposed when the AppDomain is torn down - var connection = ConnectionMultiplexer.Connect(ConnectionString); + var connection = ConnectionMultiplexer.Connect(ConfigOptions); return connection; }); @@ -49,10 +49,16 @@ private static IDatabase cache } } - public static void Configure(int db, string connectionString, ILoggerFactory loggerFactory) + public static void Configure(int db, string endPoint, string key, ILoggerFactory loggerFactory) { DB = db; - ConnectionString = connectionString; + ConfigOptions = new ConfigurationOptions + { + Password = key, + EndPoints = { { endPoint } }, + Ssl = true, + AbortOnConnectFail = false + }; logger = loggerFactory.CreateLogger(nameof(RedisCache)); } diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Startup.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Startup.cs similarity index 68% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Startup.cs rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Startup.cs index dd90b9cf..c604aa30 100644 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Startup.cs +++ b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/Startup.cs @@ -6,28 +6,39 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; -using Swashbuckle.AspNetCore.Swagger; +using Microsoft.OpenApi.Models; using Fabrikam.DroneDelivery.DeliveryService.Models; using Fabrikam.DroneDelivery.DeliveryService.Services; using Fabrikam.DroneDelivery.DeliveryService.Middlewares.Builder; using Serilog; using Serilog.Formatting.Compact; -using Fabrikam.DroneDelivery.Common; namespace Fabrikam.DroneDelivery.DeliveryService { public class Startup { - public Startup(IHostingEnvironment env) + private const string HealCheckName = "ReadinessLiveness"; + + public Startup(IWebHostEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) .AddEnvironmentVariables(); + + var buildConfig = builder.Build(); + + if (buildConfig["KEY_VAULT_URI"] is var keyVaultUri && !string.IsNullOrEmpty(keyVaultUri)) + { + builder.AddAzureKeyVault(keyVaultUri); + } + Configuration = builder.Build(); } @@ -38,32 +49,35 @@ public void ConfigureServices(IServiceCollection services) { services.AddSingleton(); - services.AddLogging(loggingBuilder => - loggingBuilder.AddSerilog(dispose: true)); + // Configure AppInsights + services.AddApplicationInsightsKubernetesEnricher(); + services.AddApplicationInsightsTelemetry(Configuration); + + // Add health check + services.AddHealthChecks().AddCheck( + HealCheckName, + () => HealthCheckResult.Healthy("OK")); - // Add framework services. - services.AddMvc(); + services.AddControllers(); // Register the Swagger generator, defining one or more Swagger documents services.AddSwaggerGen(c => { - c.SwaggerDoc("v1", new Info { Title = "Fabrikam DroneDelivery DeliveryService API", Version = "v1" }); + c.SwaggerDoc("v1", new OpenApiInfo { Title = "Fabrikam DroneDelivery DeliveryService API", Version = "v1" }); }); - + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); 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, IHttpContextAccessor httpContextAccessor) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory, IHttpContextAccessor httpContextAccessor) { Log.Logger = new LoggerConfiguration() .WriteTo.Console(new CompactJsonFormatter()) .ReadFrom.Configuration(Configuration) - .Enrich.With(new CorrelationLogEventEnricher(httpContextAccessor, Configuration["Logging:CorrelationHeaderKey"])) .CreateLogger(); // Important: it has to be first: enable global logger @@ -74,7 +88,12 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF // TODO: Add middleware AuthZ here - app.UseMvc(); + app.UseRouting(); + app.UseEndpoints(endpoints => + { + endpoints.MapHealthChecks("/healthz"); + endpoints.MapControllers(); + }); // Enable middleware to serve generated Swagger as a JSON endpoint. app.UseSwagger(); @@ -85,16 +104,10 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF c.SwaggerEndpoint("/swagger/v1/swagger.json", "Fabrikam DroneDelivery DeliveryService API V1"); }); - //TODO look into creating a factory of DocDBRepos/RedisCache/EventHubMessenger - DocumentDBRepository.Configure(Configuration["DOCDB_ENDPOINT"], Configuration["DOCDB_KEY"], Configuration["DOCDB_DATABASEID"], Configuration["DOCDB_COLLECTIONID"], loggerFactory); - - RedisCache.Configure(Constants.RedisCacheDBId_Delivery, Configuration["REDIS_CONNSTR"], loggerFactory); - RedisCache.Configure(Constants.RedisCacheDBId_DeliveryStatus, Configuration["REDIS_CONNSTR"], loggerFactory); - - EventHubSender.Configure(Configuration["EH_CONNSTR"], Configuration["EH_ENTITYPATH"]); - - EventHubSender.Configure(Configuration["EH_CONNSTR"], Configuration["EH_ENTITYPATH"]); + DocumentDBRepository.Configure(Configuration["CosmosDB-Endpoint"], Configuration["CosmosDB-Key"], Configuration["DOCDB_DATABASEID"], Configuration["DOCDB_COLLECTIONID"], loggerFactory); + RedisCache.Configure(Constants.RedisCacheDBId_Delivery, Configuration["Redis-Endpoint"], Configuration["Redis-AccessKey"], loggerFactory); + RedisCache.Configure(Constants.RedisCacheDBId_DeliveryStatus, Configuration["Redis-Endpoint"], Configuration["Redis-AccessKey"], loggerFactory); } } } diff --git a/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/TracingExtensions.cs b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/TracingExtensions.cs new file mode 100644 index 00000000..28eea3d3 --- /dev/null +++ b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/TracingExtensions.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.ApplicationInsights.Extensibility; +using Microsoft.Extensions.DependencyInjection; + +namespace Fabrikam.DroneDelivery.DeliveryService +{ + public static class TracingExtensions + { + public static IServiceCollection AddApplicationInsightsKubernetesEnricher (this IServiceCollection services) + { + services.Configure( + (config) => + config.AddApplicationInsightsKubernetesEnricher( + applyOptions: null) + ); + + return services; + } + } +} diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/appsettings.Development.json b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/appsettings.Development.json similarity index 65% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/appsettings.Development.json rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/appsettings.Development.json index fa8ce71a..6c53351c 100644 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/appsettings.Development.json +++ b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/appsettings.Development.json @@ -1,6 +1,11 @@ { "Logging": { "IncludeScopes": false, + "ApplicationInsights": { + "LogLevel": { + "Default": "None" + } + }, "LogLevel": { "Default": "Debug", "System": "Information", diff --git a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/appsettings.json b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/appsettings.json similarity index 85% rename from src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/appsettings.json rename to src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/appsettings.json index 8909c7bf..9dcddd40 100644 --- a/src/bc-shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/appsettings.json +++ b/src/shipping/delivery/Fabrikam.DroneDelivery.DeliveryService/appsettings.json @@ -1,10 +1,14 @@ { "Logging": { "IncludeScopes": false, + "ApplicationInsights": { + "LogLevel": { + "Default": "Error" + } + }, "LogLevel": { "Default": "Information" - }, - "CorrelationHeaderKey": "l5d-ctx-trace" + } }, "Serilog": { "MinimumLevel": "Verbose", @@ -20,4 +24,4 @@ //} ] } -} \ No newline at end of file +} diff --git a/src/shipping/delivery/Fabrikam.DroneDelivery.sln b/src/shipping/delivery/Fabrikam.DroneDelivery.sln new file mode 100644 index 00000000..d355f4ad --- /dev/null +++ b/src/shipping/delivery/Fabrikam.DroneDelivery.sln @@ -0,0 +1,42 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.15 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fabrikam.DroneDelivery.DeliveryService", "Fabrikam.DroneDelivery.DeliveryService\Fabrikam.DroneDelivery.DeliveryService.csproj", "{D01A5347-3D4F-44F4-98C2-AD73D363631B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fabrikam.DroneDelivery.Common", "Fabrikam.DroneDelivery.Common\Fabrikam.DroneDelivery.Common.csproj", "{530D7BEC-E8B0-4B0A-B608-EAC10E4E858E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{4D81DB69-724B-44C2-A779-66778C21722E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fabrikam.DroneDelivery.DeliveryService.Tests", "Fabrikam.DroneDelivery.DeliveryService.Tests\Fabrikam.DroneDelivery.DeliveryService.Tests.csproj", "{5B8E1AE5-2C26-41D0-9035-74295F83FCB8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D01A5347-3D4F-44F4-98C2-AD73D363631B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D01A5347-3D4F-44F4-98C2-AD73D363631B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D01A5347-3D4F-44F4-98C2-AD73D363631B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D01A5347-3D4F-44F4-98C2-AD73D363631B}.Release|Any CPU.Build.0 = Release|Any CPU + {530D7BEC-E8B0-4B0A-B608-EAC10E4E858E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {530D7BEC-E8B0-4B0A-B608-EAC10E4E858E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {530D7BEC-E8B0-4B0A-B608-EAC10E4E858E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {530D7BEC-E8B0-4B0A-B608-EAC10E4E858E}.Release|Any CPU.Build.0 = Release|Any CPU + {5B8E1AE5-2C26-41D0-9035-74295F83FCB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B8E1AE5-2C26-41D0-9035-74295F83FCB8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B8E1AE5-2C26-41D0-9035-74295F83FCB8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B8E1AE5-2C26-41D0-9035-74295F83FCB8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {5B8E1AE5-2C26-41D0-9035-74295F83FCB8} = {4D81DB69-724B-44C2-A779-66778C21722E} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {246E8998-E2AB-41E6-B682-905A9F32CA9F} + EndGlobalSection +EndGlobal diff --git a/src/shipping/delivery/azure-pipelines-cd.json b/src/shipping/delivery/azure-pipelines-cd.json new file mode 100644 index 00000000..50e66be5 --- /dev/null +++ b/src/shipping/delivery/azure-pipelines-cd.json @@ -0,0 +1,1812 @@ +{ + "source": 1, + "revision": 13, + "description": null, + "isDeleted": false, + "variables": { + "REPO_NAME": { + "value": "delivery" + }, + "REASON": { + "value": "Azure DevOps CD Pipeline", + "allowOverride": true + }, + "SERVICE_NAME": { + "value": "delivery" + } + }, + "variableGroups": [], + "environments": [ + { + "id": 2, + "name": "dev", + "rank": 1, + "owner": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "variables": { + "DELIVERY_PRINCIPAL_CLIENT_ID": { + "value": "DEV_DELIVERY_PRINCIPAL_CLIENT_ID_VAR_VAL" + }, + "DELIVERY_PRINCIPAL_RESOURCE_ID": { + "value": "DEV_DELIVERY_PRINCIPAL_RESOURCE_ID_VAR_VAL" + }, + "DATABASE_NAME": { + "value": "DEV_DATABASE_NAME_VAR_VAL" + }, + "COLLECTION_NAME": { + "value": "DEV_COLLECTION_NAME_VAR_VAL" + }, + "DELIVERY_KEYVAULT_URI": { + "value": "DEV_DELIVERY_KEYVAULT_URI_VAR_VAL" + }, + "EXTERNAL_INGEST_FQDN": { + "value": "DEV_EXTERNAL_INGEST_FQDN_VAR_VAL" + }, + "INGRESS_TLS_SECRET_CERT": { + "value": "DEV_INGRESS_TLS_SECRET_CERT_VAR_VAL" + }, + "INGRESS_TLS_SECRET_KEY": { + "value": "DEV_INGRESS_TLS_SECRET_KEY_VAR_VAL", + "isSecret": true + }, + "INGRESS_TLS_SECRET_NAME": { + "value": "DEV_INGRESS_TLS_SECRET_NAME_VAR_VAL" + }, + "ACR_SERVER": { + "value": "DEV_ACR_SERVER_VAR_VAL" + }, + "ACR_NAME": { + "value": "DEV_ACR_NAME_VAR_VAL" + }, + "GATEWAY_SUBNET_PREFIX": { + "value": "DEV_GATEWAY_SUBNET_PREFIX_VAR_VAL" + }, + "CLUSTER_SUBNET_PREFIX": { + "value": "DEV_CLUSTER_SUBNET_PREFIX_VAR_VAL" + } + }, + "variableGroups": [], + "preDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 4 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 1 + } + }, + "deployStep": { + "id": 11 + }, + "postDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 12 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 2 + } + }, + "deployPhases": [ + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [ + { + "alias": "ci-delivery", + "artifactType": "Build", + "artifactDownloadMode": "All", + "artifactItems": [] + } + ] + }, + "queueId": AZURE_DEVOPS_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 1, + "phaseType": 1, + "name": "Agent job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "helm upgrade delivery dev", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend-dev", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)-dev", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),ingress.hosts[0].name=$(EXTERNAL_INGEST_FQDN),ingress.hosts[0].serviceName=$(SERVICE_NAME),ingress.hosts[0].tls=true,ingress.hosts[0].tlsSecretName=$(INGRESS_TLS_SECRET_NAME),ingress.tls.secrets[0].name=$(INGRESS_TLS_SECRET_NAME),ingress.tls.secrets[0].key=\"$(INGRESS_TLS_SECRET_KEY)\",ingress.tls.secrets[0].certificate=\"$(INGRESS_TLS_SECRET_CERT)\",networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",networkPolicy.ingress.externalSubnet.enabled=true,networkPolicy.ingress.externalSubnet.subnetPrefix=\"$(GATEWAY_SUBNET_PREFIX)\",identity.clientid=$(DELIVERY_PRINCIPAL_CLIENT_ID),identity.resourceid=$(DELIVERY_PRINCIPAL_RESOURCE_ID),cosmosdb.id=$(DATABASE_NAME),cosmosdb.collectionid=$(COLLECTION_NAME),keyvault.uri=$(DELIVERY_KEYVAULT_URI),reason=\"$(REASON)\",envs.dev=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + } + ], + "environmentOptions": { + "emailNotificationType": "OnlyOnFailure", + "emailRecipients": "release.environment.owner;release.creator", + "skipArtifactsDownload": false, + "timeoutInMinutes": 0, + "enableAccessToken": false, + "publishDeploymentStatus": true, + "badgeEnabled": false, + "autoLinkWorkItems": false, + "pullRequestDeploymentEnabled": false + }, + "demands": [], + "conditions": [ + { + "name": "ReleaseStarted", + "conditionType": 1, + "value": "" + }, + { + "name": "ci-delivery", + "conditionType": 4, + "value": "{\"sourceBranch\":\"release/delivery/v*\",\"tags\":[],\"useBuildDefinitionBranch\":false,\"createReleaseOnBuildTagging\":false}" + } + ], + "executionPolicy": { + "concurrencyCount": 1, + "queueDepthCount": 0 + }, + "schedules": [], + "currentRelease": null, + "retentionPolicy": { + "daysToKeep": 30, + "releasesToKeep": 3, + "retainBuild": true + }, + "processParameters": { + "inputs": [ + { + "aliases": [], + "options": { + "Azure Resource Manager": "Azure Resource Manager", + "Kubernetes Service Connection": "Kubernetes Service Connection" + }, + "properties": { + "EditableOptions": "false" + }, + "name": "connectionType", + "label": "Connection Type.", + "defaultValue": "Azure Resource Manager", + "type": "pickList", + "helpMarkDown": "", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "azureSubscriptionEndpoint", + "label": "Azure subscription", + "defaultValue": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "required": true, + "type": "connectedService:AzureRM", + "helpMarkDown": "Select an Azure subscription, which has your Azure Container Registry.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "azureResourceGroup", + "label": "Resource group", + "defaultValue": "RESOURCE_GROUP_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Resource Group.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "kubernetesCluster", + "label": "Kubernetes cluster", + "defaultValue": "CLUSTER_NAME_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Kubernetes Service cluster.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "kubernetesServiceEndpoint", + "label": "Kubernetes Service Connection", + "defaultValue": "", + "required": true, + "type": "connectedService:kubernetes", + "helpMarkDown": "Select a Kubernetes service connection.", + "visibleRule": "connectionType = Kubernetes Service Connection", + "groupName": "" + } + ], + "dataSourceBindings": [ + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "kubernetesCluster", + "resultTemplate": "{{{name}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + }, + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "azureResourceGroup", + "resultTemplate": "{{{ #extractResource id resourcegroups}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + } + ] + }, + "properties": {}, + "preDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "postDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "environmentTriggers": [], + "badgeUrl": null + }, + { + "id": 3, + "name": "QA", + "rank": 2, + "owner": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "variables": { + "DELIVERY_PRINCIPAL_CLIENT_ID": { + "value": "QA_DELIVERY_PRINCIPAL_CLIENT_ID_VAR_VAL" + }, + "DELIVERY_PRINCIPAL_RESOURCE_ID": { + "value": "QA_DELIVERY_PRINCIPAL_RESOURCE_ID_VAR_VAL" + }, + "DATABASE_NAME": { + "value": "QA_DATABASE_NAME_VAR_VAL" + }, + "COLLECTION_NAME": { + "value": "QA_COLLECTION_NAME_VAR_VAL" + }, + "DELIVERY_KEYVAULT_URI": { + "value": "QA_DELIVERY_KEYVAULT_URI_VAR_VAL" + }, + "EXTERNAL_INGEST_FQDN": { + "value": "QA_EXTERNAL_INGEST_FQDN_VAR_VAL" + }, + "INGRESS_TLS_SECRET_CERT": { + "value": "QA_INGRESS_TLS_SECRET_CERT_VAR_VAL" + }, + "INGRESS_TLS_SECRET_KEY": { + "value": "QA_INGRESS_TLS_SECRET_KEY_VAR_VAL", + "isSecret": true + }, + "INGRESS_TLS_SECRET_NAME": { + "value": "QA_INGRESS_TLS_SECRET_NAME_VAR_VAL" + }, + "ACR_SERVER": { + "value": "QA_ACR_SERVER_VAR_VAL" + }, + "ACR_NAME": { + "value": "QA_ACR_NAME_VAR_VAL" + }, + "GATEWAY_SUBNET_PREFIX": { + "value": "QA_GATEWAY_SUBNET_PREFIX_VAR_VAL" + }, + "CLUSTER_SUBNET_PREFIX": { + "value": "QA_CLUSTER_SUBNET_PREFIX_VAR_VAL" + } + }, + "variableGroups": [], + "preDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 5 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 1 + } + }, + "deployStep": { + "id": 10 + }, + "postDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 13 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 2 + } + }, + "deployPhases": [ + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [ + { + "alias": "ci-delivery", + "artifactType": "Build", + "artifactDownloadMode": "All", + "artifactItems": [] + } + ] + }, + "queueId": AZURE_DEVOPS_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 1, + "phaseType": 1, + "name": "Agent job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "helm upgrade delivery qa", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend-qa", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)-qa", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),ingress.hosts[0].name=$(EXTERNAL_INGEST_FQDN),ingress.hosts[0].serviceName=$(SERVICE_NAME),ingress.hosts[0].tls=true,ingress.hosts[0].tlsSecretName=$(INGRESS_TLS_SECRET_NAME),ingress.tls.secrets[0].name=$(INGRESS_TLS_SECRET_NAME),ingress.tls.secrets[0].key=\"$(INGRESS_TLS_SECRET_KEY)\",ingress.tls.secrets[0].certificate=\"$(INGRESS_TLS_SECRET_CERT)\",networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",networkPolicy.ingress.externalSubnet.enabled=true,networkPolicy.ingress.externalSubnet.subnetPrefix=\"$(GATEWAY_SUBNET_PREFIX)\",identity.clientid=$(DELIVERY_PRINCIPAL_CLIENT_ID),identity.resourceid=$(DELIVERY_PRINCIPAL_RESOURCE_ID),cosmosdb.id=$(DATABASE_NAME),cosmosdb.collectionid=$(COLLECTION_NAME),keyvault.uri=$(DELIVERY_KEYVAULT_URI),reason=\"$(REASON)\",envs.qa=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + } + ], + "environmentOptions": { + "emailNotificationType": "OnlyOnFailure", + "emailRecipients": "release.environment.owner;release.creator", + "skipArtifactsDownload": false, + "timeoutInMinutes": 0, + "enableAccessToken": false, + "publishDeploymentStatus": true, + "badgeEnabled": false, + "autoLinkWorkItems": false, + "pullRequestDeploymentEnabled": false + }, + "demands": [], + "conditions": [ + { + "name": "dev", + "conditionType": 2, + "value": "4" + } + ], + "executionPolicy": { + "concurrencyCount": 1, + "queueDepthCount": 0 + }, + "schedules": [], + "currentRelease": null, + "retentionPolicy": { + "daysToKeep": 30, + "releasesToKeep": 3, + "retainBuild": true + }, + "processParameters": { + "inputs": [ + { + "aliases": [], + "options": { + "Azure Resource Manager": "Azure Resource Manager", + "Kubernetes Service Connection": "Kubernetes Service Connection" + }, + "properties": { + "EditableOptions": "false" + }, + "name": "connectionType", + "label": "Connection Type.", + "defaultValue": "Azure Resource Manager", + "type": "pickList", + "helpMarkDown": "", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "azureSubscriptionEndpoint", + "label": "Azure subscription", + "defaultValue": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "required": true, + "type": "connectedService:AzureRM", + "helpMarkDown": "Select an Azure subscription, which has your Azure Container Registry.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "azureResourceGroup", + "label": "Resource group", + "defaultValue": "RESOURCE_GROUP_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Resource Group.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "kubernetesCluster", + "label": "Kubernetes cluster", + "defaultValue": "CLUSTER_NAME_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Kubernetes Service cluster.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "kubernetesServiceEndpoint", + "label": "Kubernetes Service Connection", + "defaultValue": "", + "required": true, + "type": "connectedService:kubernetes", + "helpMarkDown": "Select a Kubernetes service connection.", + "visibleRule": "connectionType = Kubernetes Service Connection", + "groupName": "" + } + ], + "dataSourceBindings": [ + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "kubernetesCluster", + "resultTemplate": "{{{name}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + }, + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "azureResourceGroup", + "resultTemplate": "{{{ #extractResource id resourcegroups}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + } + ] + }, + "properties": {}, + "preDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "postDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "environmentTriggers": [], + "badgeUrl": null + }, + { + "id": 4, + "name": "staging", + "rank": 3, + "owner": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "variables": { + "DELIVERY_PRINCIPAL_CLIENT_ID": { + "value": "STAGING_DELIVERY_PRINCIPAL_CLIENT_ID_VAR_VAL" + }, + "DELIVERY_PRINCIPAL_RESOURCE_ID": { + "value": "STAGING_DELIVERY_PRINCIPAL_RESOURCE_ID_VAR_VAL" + }, + "DATABASE_NAME": { + "value": "STAGING_DATABASE_NAME_VAR_VAL" + }, + "COLLECTION_NAME": { + "value": "STAGING_COLLECTION_NAME_VAR_VAL" + }, + "DELIVERY_KEYVAULT_URI": { + "value": "STAGING_DELIVERY_KEYVAULT_URI_VAR_VAL" + }, + "EXTERNAL_INGEST_FQDN": { + "value": "STAGING_EXTERNAL_INGEST_FQDN_VAR_VAL" + }, + "INGRESS_TLS_SECRET_CERT": { + "value": "STAGING_INGRESS_TLS_SECRET_CERT_VAR_VAL" + }, + "INGRESS_TLS_SECRET_KEY": { + "value": "STAGING_INGRESS_TLS_SECRET_KEY_VAR_VAL", + "isSecret": true + }, + "INGRESS_TLS_SECRET_NAME": { + "value": "STAGING_INGRESS_TLS_SECRET_NAME_VAR_VAL" + }, + "ACR_SERVER": { + "value": "STAGING_ACR_SERVER_VAR_VAL" + }, + "ACR_NAME": { + "value": "STAGING_ACR_NAME_VAR_VAL" + }, + "GATEWAY_SUBNET_PREFIX": { + "value": "STAGING_GATEWAY_SUBNET_PREFIX_VAR_VAL" + }, + "CLUSTER_SUBNET_PREFIX": { + "value": "STAGING_CLUSTER_SUBNET_PREFIX_VAR_VAL" + } + }, + "variableGroups": [], + "preDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 7 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 1 + } + }, + "deployStep": { + "id": 8 + }, + "postDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 15 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 2 + } + }, + "deployPhases": [ + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [ + { + "alias": "ci-delivery", + "artifactType": "Build", + "artifactDownloadMode": "All", + "artifactItems": [] + } + ] + }, + "queueId": AZURE_DEVOPS_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 1, + "phaseType": 1, + "name": "Agent job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "helm upgrade delivery staging", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend-staging", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)-staging", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),ingress.hosts[0].name=$(EXTERNAL_INGEST_FQDN),ingress.hosts[0].serviceName=$(SERVICE_NAME),ingress.hosts[0].tls=true,ingress.hosts[0].tlsSecretName=$(INGRESS_TLS_SECRET_NAME),ingress.tls.secrets[0].name=$(INGRESS_TLS_SECRET_NAME),ingress.tls.secrets[0].key=\"$(INGRESS_TLS_SECRET_KEY)\",ingress.tls.secrets[0].certificate=\"$(INGRESS_TLS_SECRET_CERT)\",networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",networkPolicy.ingress.externalSubnet.enabled=true,networkPolicy.ingress.externalSubnet.subnetPrefix=\"$(GATEWAY_SUBNET_PREFIX)\",identity.clientid=$(DELIVERY_PRINCIPAL_CLIENT_ID),identity.resourceid=$(DELIVERY_PRINCIPAL_RESOURCE_ID),cosmosdb.id=$(DATABASE_NAME),cosmosdb.collectionid=$(COLLECTION_NAME),keyvault.uri=$(DELIVERY_KEYVAULT_URI),reason=\"$(REASON)\",envs.staging=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + } + ], + "environmentOptions": { + "emailNotificationType": "OnlyOnFailure", + "emailRecipients": "release.environment.owner;release.creator", + "skipArtifactsDownload": false, + "timeoutInMinutes": 0, + "enableAccessToken": false, + "publishDeploymentStatus": true, + "badgeEnabled": false, + "autoLinkWorkItems": false, + "pullRequestDeploymentEnabled": false + }, + "demands": [], + "conditions": [ + { + "name": "dev", + "conditionType": 2, + "value": "4" + } + ], + "executionPolicy": { + "concurrencyCount": 1, + "queueDepthCount": 0 + }, + "schedules": [], + "retentionPolicy": { + "daysToKeep": 30, + "releasesToKeep": 3, + "retainBuild": true + }, + "processParameters": { + "inputs": [ + { + "aliases": [], + "options": { + "Azure Resource Manager": "Azure Resource Manager", + "Kubernetes Service Connection": "Kubernetes Service Connection" + }, + "properties": { + "EditableOptions": "false" + }, + "name": "connectionType", + "label": "Connection Type.", + "defaultValue": "Azure Resource Manager", + "type": "pickList", + "helpMarkDown": "", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "azureSubscriptionEndpoint", + "label": "Azure subscription", + "defaultValue": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "required": true, + "type": "connectedService:AzureRM", + "helpMarkDown": "Select an Azure subscription, which has your Azure Container Registry.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "azureResourceGroup", + "label": "Resource group", + "defaultValue": "RESOURCE_GROUP_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Resource Group.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "kubernetesCluster", + "label": "Kubernetes cluster", + "defaultValue": "CLUSTER_NAME_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Kubernetes Service cluster.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "kubernetesServiceEndpoint", + "label": "Kubernetes Service Connection", + "defaultValue": "", + "required": true, + "type": "connectedService:kubernetes", + "helpMarkDown": "Select a Kubernetes service connection.", + "visibleRule": "connectionType = Kubernetes Service Connection", + "groupName": "" + } + ], + "dataSourceBindings": [ + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "kubernetesCluster", + "resultTemplate": "{{{name}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + }, + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "azureResourceGroup", + "resultTemplate": "{{{ #extractResource id resourcegroups}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + } + ] + }, + "properties": {}, + "preDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "postDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "environmentTriggers": [], + "badgeUrl": null + }, + { + "id": 5, + "name": "production", + "owner": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "rank": 4, + "variables": { + "DELIVERY_PRINCIPAL_CLIENT_ID": { + "value": "PROD_DELIVERY_PRINCIPAL_CLIENT_ID_VAR_VAL" + }, + "DELIVERY_PRINCIPAL_RESOURCE_ID": { + "value": "PROD_DELIVERY_PRINCIPAL_RESOURCE_ID_VAR_VAL" + }, + "DATABASE_NAME": { + "value": "PROD_DATABASE_NAME_VAR_VAL" + }, + "COLLECTION_NAME": { + "value": "PROD_COLLECTION_NAME_VAR_VAL" + }, + "DELIVERY_KEYVAULT_URI": { + "value": "PROD_DELIVERY_KEYVAULT_URI_VAR_VAL" + }, + "EXTERNAL_INGEST_FQDN": { + "value": "PROD_EXTERNAL_INGEST_FQDN_VAR_VAL" + }, + "INGRESS_TLS_SECRET_CERT": { + "value": "PROD_INGRESS_TLS_SECRET_CERT_VAR_VAL" + }, + "INGRESS_TLS_SECRET_KEY": { + "value": "PROD_INGRESS_TLS_SECRET_KEY_VAR_VAL", + "isSecret": true + }, + "INGRESS_TLS_SECRET_NAME": { + "value": "PROD_INGRESS_TLS_SECRET_NAME_VAR_VAL" + }, + "SOURCE_ACR_SERVER": { + "value": "SOURCE_ACR_SERVER_VAR_VAL" + }, + "SOURCE_ACR_NAME": { + "value": "SOURCE_ACR_NAME_VAR_VAL" + }, + "ACR_SERVER": { + "value": "PROD_ACR_SERVER_VAR_VAL" + }, + "ACR_NAME": { + "value": "PROD_ACR_NAME_VAR_VAL" + }, + "GATEWAY_SUBNET_PREFIX": { + "value": "PROD_GATEWAY_SUBNET_PREFIX_VAR_VAL" + }, + "CLUSTER_SUBNET_PREFIX": { + "value": "PROD_CLUSTER_SUBNET_PREFIX_VAR_VAL" + } + }, + "variableGroups": [], + "preDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": false, + "isNotificationOn": false, + "approver": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "id": 14 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": true, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 1 + } + }, + "deployStep": { + "id": 16 + }, + "postDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 17 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 2 + } + }, + "deployPhases": [ + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [ + { + "alias": "ci-delivery", + "artifactType": "Build", + "artifactDownloadMode": "All", + "artifactItems": [] + } + ] + }, + "queueId": AZURE_DEVOPS_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 1, + "phaseType": 1, + "name": "Agent job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "promote delivery image to production", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr import --name $(ACR_NAME) --source $(SOURCE_ACR_SERVER)/$(REPO_NAME):$(Build.SourceBranchName) --force", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(SOURCE_ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "install new delivery version in the green slot", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(SOURCE_ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),ingress.hosts[0].name=$(EXTERNAL_INGEST_FQDN),ingress.hosts[0].serviceName=$(SERVICE_NAME),ingress.hosts[0].tls=true,ingress.hosts[0].tlsSecretName=$(INGRESS_TLS_SECRET_NAME),ingress.tls.secrets[0].name=$(INGRESS_TLS_SECRET_NAME),ingress.tls.secrets[0].key=\"$(INGRESS_TLS_SECRET_KEY)\",ingress.tls.secrets[0].certificate=\"$(INGRESS_TLS_SECRET_CERT)\",networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",networkPolicy.ingress.externalSubnet.enabled=true,networkPolicy.ingress.externalSubnet.subnetPrefix=\"$(GATEWAY_SUBNET_PREFIX)\",identity.clientid=$(DELIVERY_PRINCIPAL_CLIENT_ID),identity.resourceid=$(DELIVERY_PRINCIPAL_RESOURCE_ID),cosmosdb.id=$(DATABASE_NAME),cosmosdb.collectionid=$(COLLECTION_NAME),keyvault.uri=$(DELIVERY_KEYVAULT_URI),reason=\"$(REASON)\",envs.prod=true,delivery-prod.experimental=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + }, + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 2, + "phaseType": 2, + "name": "Agentless job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "bcb64569-d51a-4af0-9c01-ea5d05b3b622", + "version": "8.*", + "name": "Swap (blue-green)", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 3600, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "instructions": "consider running some canary or just resume for swapping blue and green versions", + "emailRecipients": "", + "onTimeout": "reject" + } + } + ] + }, + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [ + { + "alias": "ci-delivery", + "artifactType": "Build", + "artifactDownloadMode": "All", + "artifactItems": [] + } + ] + }, + "queueId": AZURE_DEVOPS_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 3, + "phaseType": 1, + "name": "Agent job (swap)", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "cbc316a2-586f-4def-be79-488a1f503564", + "version": "1.*", + "name": "get current delivery blue slot version", + "refName": "BlueVersion", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "Azure Resource Manager", + "kubernetesServiceEndpoint": "", + "azureSubscriptionEndpoint": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "azureResourceGroup": "RESOURCE_GROUP_VAR_VAL", + "kubernetesCluster": "CLUSTER_NAME_VAR_VAL", + "useClusterAdmin": "false", + "namespace": "", + "command": "get", + "useConfigurationFile": "false", + "configurationType": "configuration", + "configuration": "", + "inline": "", + "arguments": "-n backend svc/delivery -o \"jsonpath={.spec.selector['app\\.kubernetes\\.io\\/instance']}\" --ignore-not-found=true", + "secretType": "dockerRegistry", + "secretArguments": "", + "containerRegistryType": "Azure Container Registry", + "dockerRegistryEndpoint": "", + "azureSubscriptionEndpointForSecrets": "", + "azureContainerRegistry": "", + "secretName": "", + "forceUpdate": "true", + "configMapName": "", + "forceUpdateConfigMap": "false", + "useConfigMapFile": "false", + "configMapFile": "", + "configMapArguments": "", + "versionOrLocation": "version", + "versionSpec": "1.12.4", + "checkLatest": "false", + "specifyLocation": "", + "cwd": "$(System.DefaultWorkingDirectory)", + "outputFormat": "" + } + }, + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(SOURCE_ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "cbc316a2-586f-4def-be79-488a1f503564", + "version": "1.*", + "name": "swap blue delivery version to green slot", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "Azure Resource Manager", + "kubernetesServiceEndpoint": "", + "azureSubscriptionEndpoint": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "azureResourceGroup": "RESOURCE_GROUP_VAR_VAL", + "kubernetesCluster": "CLUSTER_NAME_VAR_VAL", + "useClusterAdmin": "false", + "namespace": "", + "command": "set", + "useConfigurationFile": "false", + "configurationType": "configuration", + "configuration": "", + "inline": "", + "arguments": "selector -n backend svc/delivery-experimental app.kubernetes.io/name=delivery,app.kubernetes.io/instance=$(BlueVersion.KubectlOutput)", + "secretType": "dockerRegistry", + "secretArguments": "", + "containerRegistryType": "Azure Container Registry", + "dockerRegistryEndpoint": "", + "azureSubscriptionEndpointForSecrets": "", + "azureContainerRegistry": "", + "secretName": "", + "forceUpdate": "true", + "configMapName": "", + "forceUpdateConfigMap": "false", + "useConfigMapFile": "false", + "configMapFile": "", + "configMapArguments": "", + "versionOrLocation": "version", + "versionSpec": "1.12.4", + "checkLatest": "false", + "specifyLocation": "", + "cwd": "$(System.DefaultWorkingDirectory)", + "outputFormat": "json" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "install new delivery version in the blue slot", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(SOURCE_ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),ingress.hosts[0].name=$(EXTERNAL_INGEST_FQDN),ingress.hosts[0].serviceName=$(SERVICE_NAME),ingress.hosts[0].tls=true,ingress.hosts[0].tlsSecretName=$(INGRESS_TLS_SECRET_NAME),ingress.tls.secrets[0].name=$(INGRESS_TLS_SECRET_NAME),ingress.tls.secrets[0].key=\"$(INGRESS_TLS_SECRET_KEY)\",ingress.tls.secrets[0].certificate=\"$(INGRESS_TLS_SECRET_CERT)\",networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",networkPolicy.ingress.externalSubnet.enabled=true,networkPolicy.ingress.externalSubnet.subnetPrefix=\"$(GATEWAY_SUBNET_PREFIX)\",identity.clientid=$(DELIVERY_PRINCIPAL_CLIENT_ID),identity.resourceid=$(DELIVERY_PRINCIPAL_RESOURCE_ID),cosmosdb.id=$(DATABASE_NAME),cosmosdb.collectionid=$(COLLECTION_NAME),keyvault.uri=$(DELIVERY_KEYVAULT_URI),reason=\"$(REASON)\",envs.prod=true,current=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + } + ], + "environmentOptions": { + "emailNotificationType": "OnlyOnFailure", + "emailRecipients": "release.environment.owner;release.creator", + "skipArtifactsDownload": false, + "timeoutInMinutes": 0, + "enableAccessToken": false, + "publishDeploymentStatus": true, + "badgeEnabled": false, + "autoLinkWorkItems": false, + "pullRequestDeploymentEnabled": false + }, + "demands": [], + "conditions": [ + { + "name": "QA", + "conditionType": 2, + "value": "4" + }, + { + "name": "staging", + "conditionType": 2, + "value": "4" + } + ], + "executionPolicy": { + "concurrencyCount": 1, + "queueDepthCount": 0 + }, + "schedules": [], + "retentionPolicy": { + "daysToKeep": 30, + "releasesToKeep": 3, + "retainBuild": true + }, + "processParameters": { + "inputs": [ + { + "aliases": [], + "options": { + "Azure Resource Manager": "Azure Resource Manager", + "Kubernetes Service Connection": "Kubernetes Service Connection" + }, + "properties": { + "EditableOptions": "false" + }, + "name": "connectionType", + "label": "Connection Type.", + "defaultValue": "Azure Resource Manager", + "type": "pickList", + "helpMarkDown": "", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "azureSubscriptionEndpoint", + "label": "Azure subscription", + "defaultValue": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "required": true, + "type": "connectedService:AzureRM", + "helpMarkDown": "Select an Azure subscription, which has your Azure Container Registry.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "azureResourceGroup", + "label": "Resource group", + "defaultValue": "RESOURCE_GROUP_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Resource Group.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "kubernetesCluster", + "label": "Kubernetes cluster", + "defaultValue": "CLUSTER_NAME_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Kubernetes Service cluster.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "kubernetesServiceEndpoint", + "label": "Kubernetes Service Connection", + "defaultValue": "", + "required": true, + "type": "connectedService:kubernetes", + "helpMarkDown": "Select a Kubernetes service connection.", + "visibleRule": "connectionType = Kubernetes Service Connection", + "groupName": "" + } + ], + "dataSourceBindings": [ + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "kubernetesCluster", + "resultTemplate": "{{{name}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + }, + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "azureResourceGroup", + "resultTemplate": "{{{ #extractResource id resourcegroups}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + } + ] + }, + "properties": {}, + "preDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "postDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "environmentTriggers": [], + "badgeUrl": null + } + ], + "artifacts": [ + { + "sourceId": "AZURE_DEVOPS_PROJECT_ID_VAR_VAL:AZURE_DEVOPS_DELIVERY_BUILD_ID_VAR_VAL", + "type": "Build", + "alias": "ci-delivery", + "definitionReference": { + "artifactSourceDefinitionUrl": { + "id": "", + "name": "" + }, + "defaultVersionBranch": { + "id": "", + "name": "" + }, + "defaultVersionSpecific": { + "id": "", + "name": "" + }, + "defaultVersionTags": { + "id": "", + "name": "" + }, + "defaultVersionType": { + "id": "selectDuringReleaseCreationType", + "name": "Specify at the time of release creation" + }, + "definition": { + "id": "AZURE_DEVOPS_DELIVERY_BUILD_ID_VAR_VAL", + "name": "aks-ri-ci-delivery" + }, + "definitions": { + "id": "", + "name": "" + }, + "IsMultiDefinitionType": { + "id": "False", + "name": "False" + }, + "project": { + "id": "AZURE_DEVOPS_PROJECT_ID_VAR_VAL", + "name": "roadmap" + }, + "repository": { + "id": "AZURE_DEVOPS_REPOS_ID_VAR_VALL", + "name": "roadmap" + } + }, + "isPrimary": true, + "isRetained": false + } + ], + "triggers": [ + { + "artifactAlias": "ci-delivery", + "triggerConditions": [ + { + "sourceBranch": "release/$(REPO_NAME)/v*", + "tags": [], + "useBuildDefinitionBranch": false, + "createReleaseOnBuildTagging": false + } + ], + "triggerType": 1 + } + ], + "releaseNameFormat": "release-$(rev:r)", + "tags": [], + "pipelineProcess": { + "type": 1 + }, + "properties": { + "DefinitionCreationSource": { + "$type": "System.String", + "$value": "Other" + } + }, + "id": 2, + "name": "delivery-cd", + "path": null, + "projectReference": null, + "url": null +} diff --git a/src/shipping/delivery/azure-pipelines.yml b/src/shipping/delivery/azure-pipelines.yml new file mode 100644 index 00000000..5b9917a8 --- /dev/null +++ b/src/shipping/delivery/azure-pipelines.yml @@ -0,0 +1,158 @@ +variables: + repositoryName: delivery + chartPath: charts/delivery + dockerFileName: src/shipping/delivery/Dockerfile + imageName: $(repositoryName):$(Build.SourceBranchName) + azureSubscription: AZURE_PIPELINES_SERVICE_CONN_NAME_VAR_VAL + azureContainerRegistry: ACR_SERVER_VAR_VAL + azureContainerRegistryName: ACR_NAME_VAR_VAL + +name: $(build.sourceBranch)-$(Date:yyyyMMdd)$(Rev:.rr) + +pr: # only valid for GitHub. Using Azure repo it must be configure as a Branch Policy + paths: + include: + - /src/shipping/delivery/ + + branches: + include: + - master + - release/delivery/v* # for bug fixes + +trigger: + batch: true + branches: + include: + # for new release to production: release flow strategy + - release/delivery/v* + - refs/release/delivery/v* + - master + - feature/delivery/* + - topic/delivery/* + paths: + include: + - /src/shipping/delivery/ + +resources: +- repo: self + +jobs: + +# CI +- job: deliveryjobci + displayName: "Delivery CI" + pool: + vmImage: 'Ubuntu 16.04' + timeoutInMinutes: 90 + variables: + fullCI: $[ startsWith(variables['build.sourceBranch'], 'refs/heads/release/delivery/v') ] + buildImage: $[ eq(variables['build.sourceBranch'], 'refs/heads/master') ] + steps: + - task: Docker@1 + displayName: 'Build testrunner image' + inputs: + azureSubscriptionEndpoint: $(azureSubscription) + + azureContainerRegistry: $(azureContainerRegistry) + + arguments: '--pull --target testrunner' + + dockerFile: $(System.DefaultWorkingDirectory)/$(dockerFileName) + + imageName: '$(imageName)-test' + + - task: Docker@1 + displayName: 'Run tests' + inputs: + azureSubscriptionEndpoint: $(azureSubscription) + + azureContainerRegistry: $(azureContainerRegistry) + + command: 'run' + + containerName: testrunner + + volumes: '$(System.DefaultWorkingDirectory)/TestResults:/app/tests/TestResults' + + imageName: '$(imageName)-test' + + runInBackground: false + + - task: PublishTestResults@2 + displayName: 'Publish test results' + inputs: + testResultsFormat: 'VSTest' # Options: JUnit, NUnit, VSTest, xUnit + + testResultsFiles: 'TestResults/*.trx' + + searchFolder: '$(System.DefaultWorkingDirectory)' + + publishRunAttachments: true + + - task: Docker@1 + condition: or(eq(variables['buildImage'],True),eq(variables['fullCI'],True)) + displayName: 'Build runtime image' + inputs: + + azureSubscriptionEndpoint: $(azureSubscription) + + azureContainerRegistry: $(azureContainerRegistry) + + dockerFile: $(System.DefaultWorkingDirectory)/$(dockerFileName) + + includeLatestTag: false + + imageName: '$(imageName)' + + - task: Docker@1 + condition: eq(variables['fullCI'],True) + displayName: 'Push runtime image' + inputs: + azureSubscriptionEndpoint: $(azureSubscription) + + azureContainerRegistry: $(azureContainerRegistry) + + command: 'Push an image' + + imageName: '$(imageName)' + + includeSourceTags: false + + - task: HelmInstaller@0 + condition: eq(variables['fullCI'],True) + displayName: 'Install Helm 3.0.3' + inputs: + helmVersion: 3.0.3 + + checkLatestHelmVersion: false + + kubectlVersion: 1.12.4 + + checkLatestKubectl: false + + - task: HelmDeploy@0 + condition: eq(variables['fullCI'],True) + displayName: 'helm package' + inputs: + command: package + + chartPath: $(chartPath) + + chartVersion: $(Build.SourceBranchName) + + updateDependency: true + + save: false + + arguments: '--app-version $(Build.SourceBranchName)' + + - task: AzureCLI@1 + condition: eq(variables['fullCI'],True) + displayName: 'Push a helm package' + inputs: + azureSubscription: $(azureSubscription) + + scriptLocation: inlineScript + + inlineScript: | + az acr helm push $(System.ArtifactsDirectory)/$(repositoryName)-$(Build.SourceBranchName).tgz --name $(azureContainerRegistryName) --force; diff --git a/src/shipping/delivery/scripts/run.sh b/src/shipping/delivery/scripts/run.sh new file mode 100644 index 00000000..fd583e96 --- /dev/null +++ b/src/shipping/delivery/scripts/run.sh @@ -0,0 +1,2 @@ +#!/bin/bash +dotnet Fabrikam.DroneDelivery.DeliveryService.dll diff --git a/src/shipping/dronescheduler/Dockerfile b/src/shipping/dronescheduler/Dockerfile new file mode 100644 index 00000000..d0dcb15a --- /dev/null +++ b/src/shipping/dronescheduler/Dockerfile @@ -0,0 +1,49 @@ +FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 as base +WORKDIR /app +EXPOSE 80 + +FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build + +MAINTAINER Fernando Antivero (https://github.com/ferantivero) + +WORKDIR /app +COPY delivery/Fabrikam.DroneDelivery.Common/*.csproj ./delivery/Fabrikam.DroneDelivery.Common/ +COPY dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/*.csproj ./dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/ +WORKDIR /app +RUN dotnet restore /app/delivery/Fabrikam.DroneDelivery.Common/ +RUN dotnet restore /app/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/ + +WORKDIR /app +COPY delivery/Fabrikam.DroneDelivery.Common/. ./delivery/Fabrikam.DroneDelivery.Common/ +COPY dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/. ./dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/ + +FROM build AS publish + +MAINTAINER Fernando Antivero (https://github.com/ferantivero) + +WORKDIR /app +RUN dotnet publish /app/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/ -c release -o ./out --no-restore + +FROM base AS runtime + +MAINTAINER Fernando Antivero (https://github.com/ferantivero) + +LABEL Tags="Azure,AKS,DroneDelivery" + +ARG user=deliveryuser + +RUN useradd -m -s /bin/bash -U $user + +WORKDIR /app +COPY --from=publish /app/out ./ +COPY dronescheduler/scripts/. ./ +RUN \ + # Ensures the entry point is executable + chmod ugo+x /app/run.sh + +RUN chown -R $user.$user /app + +# Set it for subsequent commands +USER $user + +ENTRYPOINT ["/bin/bash", "/app/run.sh"] diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport.csproj b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport.csproj new file mode 100644 index 00000000..e847f7ff --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp3.1 + 7.1 + + + + + + + + + + diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/ImportConfiguration.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/ImportConfiguration.cs new file mode 100644 index 00000000..5799d687 --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/ImportConfiguration.cs @@ -0,0 +1,39 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport +{ + internal class ImportConfiguration + { + public string EndpointUrl { get; set; } + + public string AuthorizationKey { get; set; } + + public string DatabaseName { get; set; } + + public string CollectionName { get; set; } + + public string CollectionPartitionKey { get; set; } + + public int CollectionThroughput { get; set; } + + public int NumberOfBatches { get; set; } + + public long NumberOfDocuments { get; set; } + + public int NumberDocumentsPerPartitionExpFactor { get; set; } + + public string DocumentTypeName { get; set; } + + public bool FlattenPartitionKey { get; set; } + + public long NumberOfDocumentsPerBatch() + { + return (long)Math.Floor(((double)NumberOfDocuments) / NumberOfBatches); + } + } +} \ No newline at end of file diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/Program.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/Program.cs new file mode 100644 index 00000000..56e0aa5c --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/Program.cs @@ -0,0 +1,300 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Azure.Documents; +using Microsoft.Azure.Documents.Client; +using Microsoft.Azure.CosmosDB.BulkExecutor; +using Microsoft.Azure.CosmosDB.BulkExecutor.BulkImport; +using Microsoft.Extensions.Configuration; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport +{ + class Program + { + private static readonly ConnectionPolicy ConnectionPolicy = + new ConnectionPolicy + { + ConnectionMode = ConnectionMode.Direct, + ConnectionProtocol = Protocol.Tcp + }; + + private static readonly Dictionary SwitchMappings = + new Dictionary + { + { "--auth-key", "importConfig:authorizationKey" }, + { "--endpoint-url", "importConfig:endpointUrl" }, + { "--database-name", "importConfig:databaseName" }, + { "--collection-name", "importConfig:collectionName" }, + { "--collection-partition-key", "importConfig:collectionPartitionKey" }, + { "--collection-throughput", "importConfig:collectionThroughput" }, + { "--document-type-name", "importConfig:documentTypeName" }, + { "--flatten-partition-key", "importConfig:flattenPartitionKey" }, + { "--number-of-batches", "importConfig:numberOfBatches" }, + { "--number-of-documents", "importConfig:numberOfDocuments" }, + { "--number-of-documents-exp-factor", "importConfig:numberOfDocumentsExpFactor" } + }; + + private readonly DocumentClient _client; + private readonly ImportConfiguration _importConfig; + + private readonly AsyncLazy _col; + private readonly AsyncLazy _bulkExecutor; + + private Program( + DocumentClient client, + ImportConfiguration importConfig) + { + this._client = client; + this._importConfig = importConfig; + + this._col = new AsyncLazy(async () + => await InitDbAndCollectionAsync().ConfigureAwait(false)); + + this._bulkExecutor = new AsyncLazy(async () => + { + // Set retry options high for initialization (default values). + _client.ConnectionPolicy.RetryOptions.MaxRetryWaitTimeInSeconds = 30; + _client.ConnectionPolicy.RetryOptions.MaxRetryAttemptsOnThrottledRequests = 9; + + var bulkExecutor = new BulkExecutor(_client, await _col.Value); + await bulkExecutor.InitializeAsync(); + + // Set retries to 0 to pass control to bulk executor. + _client.ConnectionPolicy.RetryOptions.MaxRetryWaitTimeInSeconds = 0; + _client.ConnectionPolicy.RetryOptions.MaxRetryAttemptsOnThrottledRequests = 0; + + return bulkExecutor; + }); + } + + private static async Task Main(string[] args) + { + try + { + IConfiguration config = new ConfigurationBuilder() + .AddCommandLine(args, SwitchMappings) + .Build(); + + var importCfg = config + .GetSection("importConfig") + .Get(); + + await Console.Out.WriteLineAsync("\n--------------------------------------------------------------------- "); + await Console.Out.WriteLineAsync($"Endpoint: {importCfg.EndpointUrl}"); + await Console.Out.WriteLineAsync($"Collections : {importCfg.DatabaseName}.{importCfg.CollectionName}"); + await Console.Out.WriteLineAsync("--------------------------------------------------------------------- \n"); + + using (var client = new DocumentClient( + new Uri(importCfg.EndpointUrl), + importCfg.AuthorizationKey, + ConnectionPolicy)) + { + var program = new Program(client, importCfg); + + await program.BulkImportAsync() + .ConfigureAwait(false); + } + } + catch (AggregateException e) + { + await Console.Out.WriteLineAsync($"Caught AggregateException in Main, Inner Exception:\n {e.Message}"); + } + catch (Exception e) + { + await Console.Out.WriteLineAsync($"Caught Exception in Main:\n {e.Message}"); + } + finally + { + await Console.Out.WriteLineAsync("\nPress any key to exit."); + Console.ReadKey(); + } + } + + private async Task BulkImportAsync() + { + var tokenSource = new CancellationTokenSource(); + var token = tokenSource.Token; + string partitionKeyProperty = (await _col.Value) + .PartitionKey + .Paths[0] + .Replace("/", ""); + + long totalNumberOfDocumentsInserted = 0; + double totalRequestUnitsConsumed = 0; + double totalTimeTakenSec = 0; + + for (int i = 0; i < _importConfig.NumberOfBatches; i++) + { + long numberDocsCurrentBatch = + _importConfig.NumberDocumentsPerPartitionExpFactor > 0 + ? ExpNumberOfDocumentsPerBatch(i) + : _importConfig.NumberOfDocumentsPerBatch(); + + var documentsToImportInBatch = new List(); + long seed = i * numberDocsCurrentBatch; + + await Console.Out.WriteLineAsync($"\nGenerating {numberDocsCurrentBatch} documents to import for batch {i}"); + for (int j = 0; j < numberDocsCurrentBatch; j++) + { + var id = (seed + j).ToString(); + string doc = Utils.GenerateSyntheticDoc( + id, + i, + j, + _importConfig.DocumentTypeName, + partitionKeyProperty, + _importConfig.FlattenPartitionKey); + + documentsToImportInBatch.Add(doc); + } + + var (batchDocsImported, batchReqUnitsConsumed, batchSeconds) = + await ImportBatch(i, documentsToImportInBatch, token); + + totalNumberOfDocumentsInserted += batchDocsImported; + totalRequestUnitsConsumed += batchReqUnitsConsumed; + totalTimeTakenSec += batchSeconds; + } + + await Console.Out.WriteLineAsync("\nOverall Summary:"); + await Console.Out.WriteLineAsync("--------------------------------------------------------------------- "); + await Console.Out.WriteLineAsync(String.Format("Inserted {0} docs @ {1} writes/s, {2} RU/s in {3} sec", + totalNumberOfDocumentsInserted, + Math.Round(totalNumberOfDocumentsInserted / totalTimeTakenSec), + Math.Round(totalRequestUnitsConsumed / totalTimeTakenSec), + totalTimeTakenSec)); + await Console.Out.WriteLineAsync(String.Format("Average RU consumption per document: {0}", + (totalRequestUnitsConsumed / totalNumberOfDocumentsInserted))); + await Console.Out.WriteLineAsync("--------------------------------------------------------------------- "); + } + + private async Task> ImportBatch( + int batchNuber, + IEnumerable documentsToImportInBatch, + CancellationToken token) + { + await Console.Out.WriteLineAsync($"Executing bulk import for batch {batchNuber}"); + BulkImportResponse bulkImportResponse = null; + + do + { + try + { + bulkImportResponse = + await (await _bulkExecutor.Value).BulkImportAsync( + documents: documentsToImportInBatch, + enableUpsert: false, + disableAutomaticIdGeneration: true, + maxConcurrencyPerPartitionKeyRange: null, + maxInMemorySortingBatchSize: null, + cancellationToken: token); + } + catch (DocumentClientException de) + { + await Console.Out.WriteLineAsync($"Document client exception: {de.Message}"); + break; + } + catch (Exception e) + { + await Console.Out.WriteLineAsync($"Exception: {e.Message}"); + break; + } + } while (bulkImportResponse.NumberOfDocumentsImported < documentsToImportInBatch.Count()); + + await Console.Out.WriteLineAsync($"\nBatch Summary {batchNuber}:"); + await Console.Out.WriteLineAsync("--------------------------------------------------------------------- "); + await Console.Out.WriteLineAsync(String.Format("Inserted {0} docs @ {1} writes/s, {2} RU/s in {3} sec", + bulkImportResponse.NumberOfDocumentsImported, + Math.Round(bulkImportResponse.NumberOfDocumentsImported / bulkImportResponse.TotalTimeTaken.TotalSeconds), + Math.Round(bulkImportResponse.TotalRequestUnitsConsumed / bulkImportResponse.TotalTimeTaken.TotalSeconds), + bulkImportResponse.TotalTimeTaken.TotalSeconds)); + await Console.Out.WriteLineAsync(String.Format("Average RU consumption per document: {0}", + (bulkImportResponse.TotalRequestUnitsConsumed / bulkImportResponse.NumberOfDocumentsImported))); + await Console.Out.WriteLineAsync("--------------------------------------------------------------------- "); + + return Tuple.Create( + bulkImportResponse.NumberOfDocumentsImported, + bulkImportResponse.TotalRequestUnitsConsumed, + bulkImportResponse.TotalTimeTaken.TotalSeconds); + } + + private long ExpNumberOfDocumentsPerBatch(double x) + => (long)(Math.Exp(x) * _importConfig.NumberDocumentsPerPartitionExpFactor); + + private async Task InitDbAndCollectionAsync() + { + try + { + Database db = _client + .CreateDatabaseQuery() + .Where(d => d.Id == _importConfig.DatabaseName) + .AsEnumerable() + .FirstOrDefault(); + + if (db != null) + { + await Console.Out.WriteLineAsync($"Deleting pre-existent database {_importConfig.DatabaseName}..."); + await _client.DeleteDatabaseAsync(db.SelfLink); + } + + await Console.Out.WriteLineAsync($"Creating database {_importConfig.DatabaseName}..."); + db = await _client.CreateDatabaseAsync( + new Database + { + Id = _importConfig.DatabaseName + }); + + var partitionKey = new PartitionKeyDefinition + { + Paths = new Collection + { + _importConfig.CollectionPartitionKey + } + }; + + var collection = new DocumentCollection + { + Id = _importConfig.CollectionName, + PartitionKey = partitionKey + }; + + await Console.Out.WriteLineAsync($"Creating collection {_importConfig.CollectionName} with {_importConfig.CollectionThroughput} RU/s..."); + collection = await _client.CreateDocumentCollectionAsync( + db.SelfLink, + collection, + new RequestOptions + { + OfferThroughput = _importConfig.CollectionThroughput + }); + + return collection; + } + catch (Exception e) + { + await Console.Out.WriteLineAsync($"Unable to initialize, exception message: {e.Message}"); + throw; + } + } + + private class AsyncLazy : Lazy> + { + public AsyncLazy(Func valueFactory) : + base(() => Task.Run(valueFactory)) + { + } + + public AsyncLazy(Func> taskFactory) : + base(() => Task.Run(taskFactory)) + { + } + } + } +} \ No newline at end of file diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/Utils.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/Utils.cs new file mode 100644 index 00000000..6c09642a --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport/Utils.cs @@ -0,0 +1,59 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using Microsoft.Extensions.Configuration; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService.BulkImport +{ + internal static class Utils + { + internal static String GenerateSyntheticDoc( + string id, + int batchNumber, + int docNumberCurrentBatch, + string docTypeName, + string partitionKeyProperty, + bool flattenPartitionKey) + { + const string deliveryPrefix = "d000"; + const string ownerPrefix = "o000"; + const double MinMiles = 30d; + const double MaxMiles = 500d; + const double MinHours = 1d; + const double MaxHours = 30d; + + string deliveryId = string.Concat(deliveryPrefix, id); + string ownerId = string.Concat(ownerPrefix, docNumberCurrentBatch); + + int year = DateTime.UtcNow.Year + (batchNumber / 12); + int month = batchNumber % 12 + 1; + + var random = new Random(); + + double traveledMiles = MinMiles + + random.NextDouble() + * (MaxMiles - MinMiles); + double assignedHours = MinHours + + random.NextDouble() + * (MaxHours - MinHours); + + string partitonKeyValue = flattenPartitionKey + ? deliveryId + : ownerId; + + return "{\n" + + " \"id\": \"" + deliveryId + "\",\n" + + " \"" + partitionKeyProperty + "\": \"" + partitonKeyValue + "\",\n" + + " \"type\": \"" + docTypeName + "\",\n" + + " \"ownerId\": \"" + ownerId + "\",\n" + + " \"travelledMiles\": " + traveledMiles.ToString() + ",\n" + + " \"assignedHours\": " + assignedHours.ToString() + ",\n" + + " \"year\": " + year.ToString() + ",\n" + + " \"month\": " + month.ToString() + "\n" + + "}"; + } + } +} diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.Tests/CosmosDBRespositoryTests.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.Tests/CosmosDBRespositoryTests.cs new file mode 100644 index 00000000..2ad0ed20 --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.Tests/CosmosDBRespositoryTests.cs @@ -0,0 +1,182 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Azure.Cosmos; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using Xunit; +using Fabrikam.DroneDelivery.DroneSchedulerService.Models; +using Fabrikam.DroneDelivery.DroneSchedulerService.Services; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService.Tests +{ + public class CosmosDBRespositoryTests + { + private readonly ILogger> _loggerDebug; + + private readonly IOptions> _optionsMockObject; + private readonly Container _containerMockObject; + private readonly CosmosClient _clientMockObject; + private readonly ICosmosDBRepositoryMetricsTracker _metricsTrackerMockObject; + + private readonly List _fakeResults; + + public CosmosDBRespositoryTests() + { + var servicesBuilder = new ServiceCollection(); + servicesBuilder.AddLogging(logging => logging.AddDebug()); + var services = servicesBuilder.BuildServiceProvider(); + + _loggerDebug = services.GetService< + ILogger< + CosmosRepository< + InternalDroneUtilization>>>(); + + _fakeResults = new List { + new InternalDroneUtilization { + Id = "d0001", + PartitionKey = "o00042", + OwnerId = "o00042", + Month = 6, + Year = 2019, + TraveledMiles =10d, + AssignedHours=1d, + DocumentType = typeof(InternalDroneUtilization).Name + }, + new InternalDroneUtilization { + Id = "d0002", + PartitionKey = "o00042", + OwnerId = "o00042", + Month = 6, + Year = 2019, + TraveledMiles=32d, + AssignedHours=2d, + DocumentType = typeof(InternalDroneUtilization).Name + } + }; + + _clientMockObject = Mock.Of(c => c.ClientOptions == new CosmosClientOptions()); + + var responseMock = new Mock>(); + responseMock.Setup(r => r.Count).Returns(() => _fakeResults.Count); + responseMock.Setup(r => r.GetEnumerator()).Returns(() => _fakeResults.GetEnumerator()); + + var mockFeedIterator = new Mock>(); + mockFeedIterator.Setup(i => i.HasMoreResults).Returns(new Queue(new[] { true, false }).Dequeue); + mockFeedIterator.Setup(i => i.ReadNextAsync(It.IsAny())).ReturnsAsync(responseMock.Object); + + _containerMockObject = + Mock.Of(c => + c.GetItemQueryIterator(It.IsAny(), It.IsAny(), It.IsAny()) + == mockFeedIterator.Object); + + var fakeOptionsValue = + new CosmosDBRepositoryOptions + { + Container = _containerMockObject + }; + + var optionsMock = new Mock< + IOptions< + CosmosDBRepositoryOptions< + InternalDroneUtilization>>>(); + optionsMock + .Setup(o => o.Value) + .Returns(fakeOptionsValue); + + _optionsMockObject = optionsMock.Object; + + _metricsTrackerMockObject = + Mock.Of>( + t => t.GetQueryMetricsTracker( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()) + == Mock.Of>()); + } + + [Fact] + public async Task WhenGetItemsAsyncWithPartitionId_ThenClientMakesAQueryWithPartitionId() + { + // Arrange + string ownerId = "o00042"; + + var repo = new CosmosRepository( + _clientMockObject, + _optionsMockObject, + _loggerDebug, + _metricsTrackerMockObject); + + // Act + var res = await repo.GetItemsAsync( + new QueryDefinition("SELECT *"), + ownerId); + + // Assert + Assert.NotNull(res); + Assert.Equal(_fakeResults.Count(), res.Count()); + Assert.All( + res, + r => + { + Assert.Equal(ownerId, r.PartitionKey); + Assert.Equal(typeof(InternalDroneUtilization).Name, r.DocumentType); + }); + + Mock.Get(_containerMockObject) + .Verify(c => + c.GetItemQueryIterator( + It.IsAny(), + null, + It.Is(ro => + ro.PartitionKey != null + && ro.PartitionKey.ToString().Contains(ownerId)))); + } + + [Fact] + public async Task WhenGetItemsAsyncWithoutPartitionId_ThenClientMakesAQueryWithoutPartitionIdAndEnablesCrossPartition() + { + // Arrange + string ownerId = "o00042"; + + var repo = new CosmosRepository( + _clientMockObject, + _optionsMockObject, + _loggerDebug, + _metricsTrackerMockObject); + + // Act + var res = await repo.GetItemsAsync( + new QueryDefinition("SELECT *"), + null); + + // Assert + Assert.NotNull(res); + Assert.Equal(_fakeResults.Count(), res.Count()); + Assert.All( + res, + r => + { + Assert.Equal(ownerId, r.PartitionKey); + Assert.Equal(typeof(InternalDroneUtilization).Name, r.DocumentType); + }); + Mock.Get(_containerMockObject) + .Verify(c => + c.GetItemQueryIterator( + It.IsAny(), + null, + It.Is(ro => ro.PartitionKey == null))); + } + } +} diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.Tests/CustomWebApplicationFactory.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.Tests/CustomWebApplicationFactory.cs new file mode 100644 index 00000000..eccdba2e --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.Tests/CustomWebApplicationFactory.cs @@ -0,0 +1,31 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService.Tests +{ + public class CustomWebApplicationFactory + : WebApplicationFactory + { + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder + .UseContentRoot(".") + .UseEnvironment("Test") + .ConfigureTestServices(s => + { + s.AddLogging(b => b.AddDebug()); + }); + + base.ConfigureWebHost(builder); + } + } +} diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.Tests/DroneDeliveriesUtilizationIntegrationTests.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.Tests/DroneDeliveriesUtilizationIntegrationTests.cs new file mode 100644 index 00000000..9a8c5647 --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.Tests/DroneDeliveriesUtilizationIntegrationTests.cs @@ -0,0 +1,157 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Azure.Cosmos; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Moq; +using Xunit; +using Fabrikam.DroneDelivery.DroneSchedulerService.Models; +using Fabrikam.DroneDelivery.DroneSchedulerService.Services; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService.Tests +{ + public class DroneDeliveriesUtilizationIntegrationTests : + IClassFixture, + IDisposable + { + private const string RequestUri = "localhost/api/dronedeliveries/utilization"; + private const string ExpectedContentType = "application/json; charset=utf-8"; + + private readonly HttpClient _client; + private readonly CustomWebApplicationFactory _factory; + + private readonly List _fakeResults; + + public DroneDeliveriesUtilizationIntegrationTests( + CustomWebApplicationFactory factory) + { + _fakeResults = new List(); + + var responseMock = new Mock>(); + responseMock.Setup(r => r.Count).Returns(() => _fakeResults.Count); + responseMock.Setup(r => r.GetEnumerator()).Returns(() => _fakeResults.GetEnumerator()); + + var mockFeedIterator = new Mock>(); + mockFeedIterator.Setup(i => i.HasMoreResults).Returns(new Queue(new[] { true, false }).Dequeue); + mockFeedIterator.Setup(i => i.ReadNextAsync(It.IsAny())).ReturnsAsync(responseMock.Object); + + var _containerMockObject = Mock.Of(); + _containerMockObject = + Mock.Of(c => + c.GetItemQueryIterator(It.IsAny(), It.IsAny(), It.IsAny()) + == mockFeedIterator.Object); + + var configOptMock = new Mock>>(); + configOptMock + .Setup(c => c.Configure( + It.IsAny>())) + .Callback>( + o => o.Container = _containerMockObject); + + var configOptMockObject = configOptMock.Object; + + var clientMockObject = Mock.Of(c => c.ClientOptions == new CosmosClientOptions()); + + _factory = factory; + _client = factory.WithWebHostBuilder(b => + b.ConfigureTestServices(s => + { + s.ConfigureOptions(configOptMockObject); + s.AddSingleton(clientMockObject); + s.AddSingleton( + Mock.Of>( + t => t.GetQueryMetricsTracker( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()) + == Mock.Of>())); + })) + .CreateClient(); + } + + [Fact] + public async Task GetInvoicingWithoutQueryParams_ThenResponseBadRequestStatusCode() + { + // Arrange + var droneUtilizationUriWithoutParams = new UriBuilder(RequestUri); + + // Act + var response = await _client.GetAsync(droneUtilizationUriWithoutParams.ToString()); + + // Assert + Assert.NotNull(response); + Assert.Equal(System.Net.HttpStatusCode.BadRequest, response.StatusCode); + } + + [Fact] + public async Task GetInvoicingMonthlyBasisWithValidCriteria_ThenResponseWithSuccessStatusCode() + { + // Arrange + string ownerId = "o00042"; + int year = 2019, month = 6; + + _fakeResults.Add( + new InternalDroneUtilization + { + Id = "d0001", + PartitionKey = "o00042", + OwnerId = "o00042", + Month = 6, + Year = 2019, + TraveledMiles = 10d, + AssignedHours = 1d, + DocumentType = typeof(InternalDroneUtilization).Name + }); + + var droneUtilizationUriWithParams = new UriBuilder(RequestUri); + droneUtilizationUriWithParams.Query = + $"ownerId={ownerId}&year={year}&month={month}"; + + // Act + var response = await _client.GetAsync(droneUtilizationUriWithParams.ToString()); + + // Assert + Assert.NotNull(response); + response.EnsureSuccessStatusCode(); // Status Code 200-299 + Assert.Equal(ExpectedContentType, + response.Content.Headers.ContentType.ToString()); + } + + [Fact] + public async Task GetInvoicingForNotExistentData_ThenResponseWithNotFoundStatusCode() + { + // Arrange + string ownerId = "o00042"; + var minValidDateTime = DateTime.MinValue; + int year = minValidDateTime.Year, month = minValidDateTime.Month; + + var droneUtilizationUriWithParams = new UriBuilder(RequestUri); + droneUtilizationUriWithParams.Query = + $"ownerId={ownerId}&year={year}&month={month}"; + + // Act + var response = await _client.GetAsync(droneUtilizationUriWithParams.ToString()); + + // Assert + Assert.NotNull(response); + Assert.Equal(System.Net.HttpStatusCode.NotFound, response.StatusCode); + } + + public void Dispose() + { + _factory.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.Tests/Fabrikam.DroneDelivery.DroneSchedulerService.Tests.csproj b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.Tests/Fabrikam.DroneDelivery.DroneSchedulerService.Tests.csproj new file mode 100644 index 00000000..180e86d3 --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.Tests/Fabrikam.DroneDelivery.DroneSchedulerService.Tests.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp3.1 + false + + + + + + + + + + + + + + + + + + diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.Tests/InvoicingRepositoryTests.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.Tests/InvoicingRepositoryTests.cs new file mode 100644 index 00000000..bba68339 --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.Tests/InvoicingRepositoryTests.cs @@ -0,0 +1,133 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Azure.Cosmos; +using Microsoft.FeatureManagement; +using Moq; +using Xunit; +using Fabrikam.DroneDelivery.DroneSchedulerService.Models; +using Fabrikam.DroneDelivery.DroneSchedulerService.Services; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService.Tests +{ + public class InvoicingRepositoryTests + { + [Fact] + public async Task WhenGetAggregatedInvoincingData_ThenInvokesDroneUtilizationRepository() + { + // Arrange + string ownerId = "o00042"; + int year = 2019; + int month = 6; + + var cosmosDbMock = new Mock>(); + + var featureToggleMock = new Mock(); + featureToggleMock.Setup(fm => fm.IsEnabled(It.IsAny())).Returns(true); + + var repo = new InvoicingRepository(cosmosDbMock.Object, featureToggleMock.Object); + + // Act + var result = await repo.GetAggreatedInvoincingDataAsync(ownerId, year, month); + + // Assert + Assert.NotNull(result); + Assert.Equal(0d, result.Item1); + Assert.Equal(0d, result.Item2); + cosmosDbMock + .Verify(p => + p.GetItemsAsync( + It.IsAny(), + ownerId), + Times.Once); + } + + [Fact] + public async Task WhenGetAggregatedInvoincingDataAndFeatureForPartitionKeyIsDisabled_ThenInvokesDroneUtilizationRepositoryWithoutPartitionKey() + { + // Arrange + string ownerId = "o00042"; + int year = 2019; + int month = 6; + + var cosmosDbMock = new Mock>(); + + var featureToggleMock = new Mock(); + featureToggleMock.Setup(fm => fm.IsEnabled(It.IsAny())).Returns(false); + + var repo = new InvoicingRepository(cosmosDbMock.Object, featureToggleMock.Object); + + // Act + var result = await repo.GetAggreatedInvoincingDataAsync(ownerId, year, month); + + // Assert + Assert.NotNull(result); + Assert.Equal(0d, result.Item1); + Assert.Equal(0d, result.Item2); + cosmosDbMock + .Verify(p => + p.GetItemsAsync( + It.IsAny(), + null), + Times.Once); + } + + [Fact] + public async Task WhenGetAggregatedInvoincingDataForAValidPeriod_ThenRepoReturnsData() + { + // Arrange + string ownerId = "o00042"; + int year = 2019; + int month = 6; + + var invoicingData = new List { + new InternalDroneUtilization{ + TraveledMiles=10d, + AssignedHours=1d + }, + new InternalDroneUtilization{ + TraveledMiles=32d, + AssignedHours=2d + } + }; + var cosmosDbMock = new Mock>(); + cosmosDbMock.Setup(r => + r.GetItemsAsync( + It.IsAny(), + ownerId)) + .ReturnsAsync(invoicingData.AsEnumerable()); + + var featureToggleMock = new Mock(); + featureToggleMock.Setup(fm => fm.IsEnabled(It.IsAny())).Returns(true); + + var repo = new InvoicingRepository(cosmosDbMock.Object, featureToggleMock.Object); + + // Act + var (traveledMiles, assignedHours) = + await repo.GetAggreatedInvoincingDataAsync( + ownerId, + year, + month); + + // Assert + Assert.Equal( + invoicingData.Sum(d => d.TraveledMiles), + traveledMiles); + Assert.Equal( + invoicingData.Sum(d => d.AssignedHours), + assignedHours); + cosmosDbMock + .Verify(p => + p.GetItemsAsync( + It.IsAny(), + ownerId), + Times.Once); + } + } +} diff --git a/src/bc-shipping/delivery/MockDeliveryScheduler/appsettings.json b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.Tests/appsettings.Test.json similarity index 56% rename from src/bc-shipping/delivery/MockDeliveryScheduler/appsettings.json rename to src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.Tests/appsettings.Test.json index 815fe795..031a67b9 100644 --- a/src/bc-shipping/delivery/MockDeliveryScheduler/appsettings.json +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.Tests/appsettings.Test.json @@ -1,14 +1,20 @@ { "Logging": { "IncludeScopes": false, + "ApplicationInsights": { + "LogLevel": { + "Default": "Error" + } + }, "LogLevel": { "Default": "Information" - }, - "CorrelationHeaderKey": "l5d-ctx-trace" + } }, "Serilog": { "MinimumLevel": "Verbose", "Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ], "WriteTo": [] - } -} \ No newline at end of file + }, + "CosmosDBEndpoint": "https://fake-invoicing.documents.azure.com:443/", + "CosmosDBKey": "ZmFrZXBhc3MK" +} diff --git a/src/bc-shipping/delivery/MockAccountService/appsettings.json b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.Tests/appsettings.json similarity index 74% rename from src/bc-shipping/delivery/MockAccountService/appsettings.json rename to src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.Tests/appsettings.json index 5d345bf0..8e39a665 100644 --- a/src/bc-shipping/delivery/MockAccountService/appsettings.json +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.Tests/appsettings.json @@ -1,10 +1,14 @@ { "Logging": { "IncludeScopes": false, + "ApplicationInsights": { + "LogLevel": { + "Default": "Error" + } + }, "LogLevel": { "Default": "Information" - }, - "CorrelationHeaderKey": "l5d-ctx-trace" + } }, "Serilog": { "MinimumLevel": "Verbose", diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.sln b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.sln new file mode 100644 index 00000000..7620e661 --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28917.182 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fabrikam.DroneDelivery.Common", "..\delivery\Fabrikam.DroneDelivery.Common\Fabrikam.DroneDelivery.Common.csproj", "{81A668CF-785F-4A63-A2C7-76C86F16F49A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fabrikam.DroneDelivery.DroneSchedulerService.Tests", "Fabrikam.DroneDelivery.DroneSchedulerService.Tests\Fabrikam.DroneDelivery.DroneSchedulerService.Tests.csproj", "{4D0F39C2-0653-4E01-B1F6-9E39CAB2D3DD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{44E83671-6389-4799-92EF-E3D4743925DD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{ECB4611F-B203-4C60-BE5C-30ACF51839F1}" + ProjectSection(SolutionItems) = preProject + Dockerfile = Dockerfile + scripts\run.sh = scripts\run.sh + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fabrikam.DroneDelivery.DroneSchedulerService", "Fabrikam.DroneDelivery.DroneSchedulerService\Fabrikam.DroneDelivery.DroneSchedulerService.csproj", "{8EC22B29-F95C-4B0C-8274-3FCDB55C555C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {81A668CF-785F-4A63-A2C7-76C86F16F49A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81A668CF-785F-4A63-A2C7-76C86F16F49A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81A668CF-785F-4A63-A2C7-76C86F16F49A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81A668CF-785F-4A63-A2C7-76C86F16F49A}.Release|Any CPU.Build.0 = Release|Any CPU + {4D0F39C2-0653-4E01-B1F6-9E39CAB2D3DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D0F39C2-0653-4E01-B1F6-9E39CAB2D3DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D0F39C2-0653-4E01-B1F6-9E39CAB2D3DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D0F39C2-0653-4E01-B1F6-9E39CAB2D3DD}.Release|Any CPU.Build.0 = Release|Any CPU + {8EC22B29-F95C-4B0C-8274-3FCDB55C555C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8EC22B29-F95C-4B0C-8274-3FCDB55C555C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8EC22B29-F95C-4B0C-8274-3FCDB55C555C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8EC22B29-F95C-4B0C-8274-3FCDB55C555C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {4D0F39C2-0653-4E01-B1F6-9E39CAB2D3DD} = {44E83671-6389-4799-92EF-E3D4743925DD} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C81CEA0C-455B-422D-BEB3-AB22F0F3F95B} + EndGlobalSection +EndGlobal diff --git a/src/bc-shipping/delivery/MockAccountService/.dockerignore b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/.dockerignore similarity index 100% rename from src/bc-shipping/delivery/MockAccountService/.dockerignore rename to src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/.dockerignore diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Controllers/DroneDeliveriesController.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Controllers/DroneDeliveriesController.cs new file mode 100644 index 00000000..5c8a78ca --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Controllers/DroneDeliveriesController.cs @@ -0,0 +1,80 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Fabrikam.DroneDelivery.DroneSchedulerService.Models; +using Fabrikam.DroneDelivery.DroneSchedulerService.Services; +using Microsoft.AspNetCore.Http; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class DroneDeliveriesController : ControllerBase + { + private readonly ILogger logger; + private readonly IInvoicingRepository _invoicingRepository; + + public DroneDeliveriesController( + IInvoicingRepository invoicingRepository, + ILoggerFactory loggerFactory) + { + this.logger = loggerFactory.CreateLogger(); + this._invoicingRepository = invoicingRepository; + } + + // PUT api/dronedeliveries/5 + [HttpPut("{id}")] + public string Put([FromBody]Models.DroneDelivery droneDelivery, string id) + { + logger.LogInformation("In Put action with DeliveryId: {DeliveryId}", id); + + var guid = Guid.NewGuid(); + return $"AssignedDroneId{guid}"; + } + + // DELETE api/dronedeliveries/5 + [HttpDelete("{id}")] + public void Delete(string id) + { + logger.LogInformation("In Delete action with DeliveryId: {DeliveryId}", id); + } + + // GET api/dronedeliveries/utilization + [HttpGet("utilization")] + [ProducesResponseType(StatusCodes.Status200OK, + Type = typeof(DroneUtilization))] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task GetDroneUtilization([FromQuery] string ownerId, [FromQuery] int year, [FromQuery] int month) + { + // TODO: improve binding model --> improve err msg. + if (string.IsNullOrEmpty(ownerId) + || year < 1 + || month < 1) + { + return BadRequest(); + } + + var (traveledMiles, assignedHours) = await _invoicingRepository. + GetAggreatedInvoincingDataAsync(ownerId, year, month); + + if (traveledMiles == 0d && + assignedHours == 0d) + { + return NotFound(); + } + + return Ok(new DroneUtilization + { + TraveledMiles = traveledMiles, + AssignedHours = assignedHours + }); + } + } +} diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/CosmosDBExtensions.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/CosmosDBExtensions.cs new file mode 100644 index 00000000..d470eecc --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/CosmosDBExtensions.cs @@ -0,0 +1,52 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Linq; +using Microsoft.Azure.Cosmos; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Fabrikam.DroneDelivery.DroneSchedulerService.Models; +using Fabrikam.DroneDelivery.DroneSchedulerService.Services; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService +{ + public static class CosmosDBExtensions + { + public static IServiceCollection AddCosmosRepository( + this IServiceCollection services, + IConfiguration config) + where T : BaseDocument + { + services.AddSingleton(s => + { + var options = s.GetRequiredService>(); + + var clientOptions = new CosmosClientOptions { ConnectionMode = options.Value.CosmosDBConnectionMode }; + if (clientOptions.ConnectionMode == ConnectionMode.Gateway) + { + clientOptions.GatewayModeMaxConnectionLimit = options.Value.CosmosDBMaxConnectionsLimit; + } + + return new CosmosClient( + options.Value.CosmosDBEndpoint, + options.Value.CosmosDBKey, + clientOptions: clientOptions); + }); + services.ConfigureOptions>(); + + if (services.Any(sr => sr.ServiceType == typeof(CosmosDBConnectionOptions)) == false) + { + services.Configure(config); + } + + services.AddSingleton, CosmosRepository>(); + services.AddSingleton, CosmosDBRepositoryMetricsTracker>(); + + return services; + } + } +} \ No newline at end of file diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Fabrikam.DroneDelivery.DroneSchedulerService.csproj b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Fabrikam.DroneDelivery.DroneSchedulerService.csproj new file mode 100644 index 00000000..5e5645a5 --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Fabrikam.DroneDelivery.DroneSchedulerService.csproj @@ -0,0 +1,32 @@ + + + + netcoreapp3.1 + Fabrikam.DroneDelivery.DroneSchedulerService + Fabrikam.DroneDelivery.DroneSchedulerService + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Models/BaseDocument.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Models/BaseDocument.cs new file mode 100644 index 00000000..bea24eb9 --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Models/BaseDocument.cs @@ -0,0 +1,18 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using Newtonsoft.Json; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService.Models +{ + public class BaseDocument + { + [JsonProperty(PropertyName = "partitionKey")] + public string PartitionKey { get; internal set; } + + [JsonProperty(PropertyName = "type")] + public string DocumentType { get; internal set; } + } +} diff --git a/src/bc-shipping/delivery/MockDroneScheduler/Models/DroneDelivery.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Models/DroneDelivery.cs similarity index 91% rename from src/bc-shipping/delivery/MockDroneScheduler/Models/DroneDelivery.cs rename to src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Models/DroneDelivery.cs index c683103f..e4fbe2c7 100644 --- a/src/bc-shipping/delivery/MockDroneScheduler/Models/DroneDelivery.cs +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Models/DroneDelivery.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using Fabrikam.DroneDelivery.Common; -namespace MockDroneScheduler.Models +namespace Fabrikam.DroneDelivery.DroneSchedulerService.Models { public class DroneDelivery { diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Models/DroneUtilization.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Models/DroneUtilization.cs new file mode 100644 index 00000000..41791028 --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Models/DroneUtilization.cs @@ -0,0 +1,15 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService.Models +{ + public class DroneUtilization + { + public double TraveledMiles { get; internal set; } + public double AssignedHours { get; internal set; } + } +} diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Models/InternalDroneUtilization.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Models/InternalDroneUtilization.cs new file mode 100644 index 00000000..fc66ba09 --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Models/InternalDroneUtilization.cs @@ -0,0 +1,31 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using Newtonsoft.Json; + +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Fabrikam.DroneDelivery.DroneSchedulerService.Tests")] +namespace Fabrikam.DroneDelivery.DroneSchedulerService.Models +{ + public class InternalDroneUtilization: BaseDocument + { + [JsonProperty(PropertyName = "id")] + public string Id { get; internal set; } + + [JsonProperty(PropertyName = "year")] + public int Year { get; internal set; } + + [JsonProperty(PropertyName = "month")] + public int Month { get; internal set; } + + [JsonProperty(PropertyName = "ownerId")] + public string OwnerId { get; internal set; } + + [JsonProperty(PropertyName = "travelledMiles")] + public double TraveledMiles { get; internal set; } + + [JsonProperty(PropertyName = "assignedHours")] + public double AssignedHours { get; internal set; } + } +} diff --git a/src/bc-shipping/delivery/MockDroneScheduler/Models/PackageDetail.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Models/PackageDetail.cs similarity index 89% rename from src/bc-shipping/delivery/MockDroneScheduler/Models/PackageDetail.cs rename to src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Models/PackageDetail.cs index 6c828d4a..4ab88ca4 100644 --- a/src/bc-shipping/delivery/MockDroneScheduler/Models/PackageDetail.cs +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Models/PackageDetail.cs @@ -8,7 +8,7 @@ using System.Linq; using System.Threading.Tasks; -namespace MockDroneScheduler.Models +namespace Fabrikam.DroneDelivery.DroneSchedulerService.Models { public class PackageDetail { diff --git a/src/bc-shipping/delivery/MockDroneScheduler/Models/PackageSize.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Models/PackageSize.cs similarity index 86% rename from src/bc-shipping/delivery/MockDroneScheduler/Models/PackageSize.cs rename to src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Models/PackageSize.cs index 554a005d..704a5808 100644 --- a/src/bc-shipping/delivery/MockDroneScheduler/Models/PackageSize.cs +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Models/PackageSize.cs @@ -3,7 +3,7 @@ // Licensed under the MIT License (MIT). See License.txt in the repo root for license information. // ------------------------------------------------------------ -namespace MockDroneScheduler.Models +namespace Fabrikam.DroneDelivery.DroneSchedulerService.Models { public enum PackageSize { diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Program.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Program.cs new file mode 100644 index 00000000..a53f62f4 --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Program.cs @@ -0,0 +1,49 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Serilog; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService +{ + public class Program + { + public static void Main(string[] args) + { + var host = CreateHostBuilder(args).Build(); + var logger = host.Services.GetRequiredService>(); + logger.LogInformation("Fabrikam Dronescheduler Service is starting."); + + host.Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder + .UseStartup() + .ConfigureAppConfiguration(configurationBuilder => + { + var buildConfig = configurationBuilder.Build(); + + if (buildConfig["KEY_VAULT_URI"] is var keyVaultUri && !string.IsNullOrEmpty(keyVaultUri)) + { + configurationBuilder.AddAzureKeyVault(keyVaultUri); + } + }) + .ConfigureLogging((hostingContext, loggingBuilder) => + { + loggingBuilder.AddApplicationInsights(); + loggingBuilder.AddSerilog(dispose: true); + }) + .UseUrls("http://0.0.0.0:8080"); + }); + } +} diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/ConfigureCosmosDBRepositoryOptions.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/ConfigureCosmosDBRepositoryOptions.cs new file mode 100644 index 00000000..c8f3eed9 --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/ConfigureCosmosDBRepositoryOptions.cs @@ -0,0 +1,47 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System.Globalization; +using Microsoft.Azure.Cosmos; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; +using Fabrikam.DroneDelivery.DroneSchedulerService.Models; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService.Services +{ + public class ConfigureCosmosDBRepositoryOptions : IConfigureOptions> + where T : BaseDocument + { + private readonly IConfiguration _config; + private readonly CosmosClient _client; + + public ConfigureCosmosDBRepositoryOptions( + IConfiguration config, + CosmosClient client) + { + _config = config; + _client = client; + } + + public void Configure(CosmosDBRepositoryOptions options) + { + options.DatabaseId = _config["COSMOSDB_DATABASEID"]; + options.CollectionId = _config["COSMOSDB_COLLECTIONID"]; + + options.Container = _client + .GetContainer(options.DatabaseId, options.CollectionId); + + if (string.IsNullOrEmpty(_config["CosmosDBMaxParallelism"]) == false) + { + options.MaxParallelism = int.Parse(_config["CosmosDBMaxParallelism"], CultureInfo.InvariantCulture); + } + + if (string.IsNullOrEmpty(_config["CosmosDBMaxBufferedItemCount"]) == false) + { + options.MaxBufferedItemCount = int.Parse(_config["CosmosDBMaxBufferedItemCount"], CultureInfo.InvariantCulture); + } + } + } +} \ No newline at end of file diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/CosmosDBConnectionOptions.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/CosmosDBConnectionOptions.cs new file mode 100644 index 00000000..9c93af56 --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/CosmosDBConnectionOptions.cs @@ -0,0 +1,20 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.Azure.Cosmos; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService.Services +{ + public class CosmosDBConnectionOptions + { + public string CosmosDBEndpoint { get; set; } + + public string CosmosDBKey { get; set; } + + public ConnectionMode CosmosDBConnectionMode { get; set; } + + public int CosmosDBMaxConnectionsLimit { get; set; } + } +} diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/CosmosDBRepository.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/CosmosDBRepository.cs new file mode 100644 index 00000000..3458b2c0 --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/CosmosDBRepository.cs @@ -0,0 +1,83 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Azure.Cosmos; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Fabrikam.DroneDelivery.DroneSchedulerService.Models; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService.Services +{ + public class CosmosRepository : ICosmosRepository + where T : BaseDocument + { + private readonly CosmosClient _client; + private readonly CosmosDBRepositoryOptions _options; + private readonly ILogger> _logger; + private readonly ICosmosDBRepositoryMetricsTracker _metricsTracker; + private readonly string _collectionIdentifier; + + public CosmosRepository( + CosmosClient client, + IOptions> options, + ILogger> logger, + ICosmosDBRepositoryMetricsTracker metricsTracker) + { + this._client = client; + this._options = options.Value; + this._logger = logger; + this._metricsTracker = metricsTracker; + this._collectionIdentifier = $"{options.Value.DatabaseId}.{options.Value.CollectionId}"; + } + + public async Task> GetItemsAsync( + QueryDefinition query, + string partitionKey) + { + var results = new List(); + + using (_logger.BeginScope(nameof(GetItemsAsync))) + { + _logger.LogInformation( + "partitionKey: {PartitionKey}", + partitionKey); + + using (var queryMetricsTracker = + this._metricsTracker.GetQueryMetricsTracker( + this._collectionIdentifier, + partitionKey, + this._options.MaxParallelism, + this._client.ClientOptions.GatewayModeMaxConnectionLimit, + this._client.ClientOptions.ConnectionMode, + this._options.MaxBufferedItemCount)) + { + FeedIterator iterator = + this._options.Container.GetItemQueryIterator( + query, + requestOptions: new QueryRequestOptions + { + MaxConcurrency = this._options.MaxParallelism, + PartitionKey = partitionKey != null ? new PartitionKey(partitionKey) : new PartitionKey?(), + MaxBufferedItemCount = this._options.MaxBufferedItemCount + }); + + _logger.LogInformation("Start: reading results from query"); + while (iterator.HasMoreResults) + { + var feed = await iterator.ReadNextAsync(); + queryMetricsTracker.TrackResponseMetrics(feed); + results.AddRange(feed); + } + } + + _logger.LogInformation("End: reading results from query"); + + return results; + } + } + } +} diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/CosmosDBRepositoryMetricsTracker.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/CosmosDBRepositoryMetricsTracker.cs new file mode 100644 index 00000000..3c6cace9 --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/CosmosDBRepositoryMetricsTracker.cs @@ -0,0 +1,151 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using System.Globalization; +using Microsoft.ApplicationInsights; +using Microsoft.ApplicationInsights.DataContracts; +using Microsoft.Azure.Cosmos; +using Fabrikam.DroneDelivery.DroneSchedulerService.Models; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService.Services +{ + public class CosmosDBRepositoryMetricsTracker + : ICosmosDBRepositoryMetricsTracker + where T : BaseDocument + { + private const string RequestUnitsMetricId = "CosmosDb-RequestUnits"; + private const string CollectionDimensionName = "Collection"; + private const string DocumentDimensionName = "Document"; + private const string PartitionKeyDimensionName = "PartitionKey"; + private const string DocumentCountDimensionName = "DocumentCount"; + private const string CosmosDbPropertyPrefix = "CosmosDb."; + private const string CollectionPropertyKey = CosmosDbPropertyPrefix + CollectionDimensionName; + private const string DocumentPropertyKey = CosmosDbPropertyPrefix + DocumentDimensionName; + private const string PartitionKeyPropertyKey = CosmosDbPropertyPrefix + PartitionKeyDimensionName; + private const string DocumentCountPropertyKey = CosmosDbPropertyPrefix + DocumentCountDimensionName; + private const string RequestChargePropertyKey = CosmosDbPropertyPrefix + "RequestCharge"; + private const string MaxParallelismPropertyKey = CosmosDbPropertyPrefix + "MaxParallelism"; + private const string MaxConnectionsPropertyKey = CosmosDbPropertyPrefix + "MaxConnections"; + private const string ConnectionModePropertyKey = CosmosDbPropertyPrefix + "ConnectionMode"; + private const string MaxBufferedItemCountPropertyKey = CosmosDbPropertyPrefix + "MaxBufferedItemCount"; + + private readonly TelemetryClient _telemetryClient; + private readonly Metric _metric; + + public CosmosDBRepositoryMetricsTracker(TelemetryClient telemetryClient) + { + this._telemetryClient = telemetryClient; + this._metric = + telemetryClient.GetMetric( + RequestUnitsMetricId, + CollectionDimensionName, + DocumentDimensionName, + PartitionKeyDimensionName, + DocumentCountDimensionName); + } + + public ICosmosDBRepositoryQueryMetricsTracker GetQueryMetricsTracker( + string collection, + string partitionKey, + int maxParallelism, + int maxConnections, + ConnectionMode connectionMode, + int maxBufferedItemCount) + { + return new QueryMetricsTracker( + this._telemetryClient, + this._metric, + collection, + partitionKey, + maxParallelism, + maxConnections, + connectionMode, + maxBufferedItemCount); + } + + private sealed class QueryMetricsTracker : ICosmosDBRepositoryQueryMetricsTracker + { + private readonly TelemetryClient _telemetryClient; + private readonly Metric _metric; + private readonly string _collection; + private readonly string _partitionKey; + private readonly string _maxParallelism; + private readonly string _maxConnections; + private readonly string _connectionMode; + private readonly string _maxBufferedItemCount; + private double _totalCharge; + private long _totalDocumentCount; + + + public QueryMetricsTracker( + TelemetryClient telemetryClient, + Metric metric, + string collection, + string partitionKey, + int maxParallelism, + int maxConnections, + ConnectionMode connectionMode, + int maxBufferedItemCount) + { + this._telemetryClient = telemetryClient; + this._metric = metric; + this._collection = collection; + this._partitionKey = partitionKey; + this._maxParallelism = maxParallelism.ToString(CultureInfo.InvariantCulture); + this._maxConnections = maxConnections.ToString(CultureInfo.InvariantCulture); + this._connectionMode = connectionMode.ToString(); + this._maxBufferedItemCount = maxBufferedItemCount.ToString(CultureInfo.InvariantCulture); + } + + public void Dispose() + { + TraceMetrics( + "Completed document query", + this._totalCharge.ToString(CultureInfo.InvariantCulture), + this._totalDocumentCount.ToString(CultureInfo.InvariantCulture)); + } + + public void TrackResponseMetrics(FeedResponse response) + { + var responseCount = response.Count.ToString(CultureInfo.InvariantCulture); + + this._metric.TrackValue( + response.RequestCharge, + this._collection, + typeof(T).Name, + this._partitionKey ?? "", + responseCount); + + TraceMetrics( + "Partial document query", + response.RequestCharge.ToString(CultureInfo.InvariantCulture), + responseCount); + + this._totalCharge += response.RequestCharge; + this._totalDocumentCount += response.Count; + } + + private void TraceMetrics(string message, string requestCharge, string documentCount) + { + this._telemetryClient.TrackTrace( + message, + SeverityLevel.Information, + new Dictionary + { + [RequestChargePropertyKey] = requestCharge, + [CollectionPropertyKey] = this._collection, + [DocumentPropertyKey] = typeof(T).Name, + [PartitionKeyPropertyKey] = this._partitionKey, + [DocumentCountPropertyKey] = documentCount, + [MaxParallelismPropertyKey] = this._maxParallelism, + [MaxConnectionsPropertyKey] = this._maxConnections, + [ConnectionModePropertyKey] = this._connectionMode, + [MaxBufferedItemCountPropertyKey] = this._maxBufferedItemCount, + }); + } + } + } +} \ No newline at end of file diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/CosmosDBRepositoryOptions.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/CosmosDBRepositoryOptions.cs new file mode 100644 index 00000000..f2579403 --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/CosmosDBRepositoryOptions.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.Azure.Cosmos; +using Fabrikam.DroneDelivery.DroneSchedulerService.Models; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService.Services +{ + public class CosmosDBRepositoryOptions + where T : BaseDocument + { + public string DatabaseId { get; set; } + + public string CollectionId { get; set; } + + public Container Container { get; set; } + + public int MaxParallelism { get; set; } = -1; + + public int MaxBufferedItemCount { get; set; } + } +} diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/ICosmosDBRepository.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/ICosmosDBRepository.cs new file mode 100644 index 00000000..52543924 --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/ICosmosDBRepository.cs @@ -0,0 +1,18 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Azure.Cosmos; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService.Services +{ + public interface ICosmosRepository + { + Task> GetItemsAsync( + QueryDefinition query, + string partitionKey); + } +} diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/ICosmosDBRepositoryMetricsTracker.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/ICosmosDBRepositoryMetricsTracker.cs new file mode 100644 index 00000000..5e3aa617 --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/ICosmosDBRepositoryMetricsTracker.cs @@ -0,0 +1,22 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.Azure.Cosmos; +using Fabrikam.DroneDelivery.DroneSchedulerService.Models; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService.Services +{ + public interface ICosmosDBRepositoryMetricsTracker + where T : BaseDocument + { + ICosmosDBRepositoryQueryMetricsTracker GetQueryMetricsTracker( + string collection, + string partitionKey, + int maxParallelism, + int maxConnections, + ConnectionMode connectionMode, + int maxBufferedItemCount); + } +} diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/ICosmosDBRepositoryQueryMetricsTracker.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/ICosmosDBRepositoryQueryMetricsTracker.cs new file mode 100644 index 00000000..82a9ffc4 --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/ICosmosDBRepositoryQueryMetricsTracker.cs @@ -0,0 +1,17 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using Microsoft.Azure.Cosmos; +using Fabrikam.DroneDelivery.DroneSchedulerService.Models; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService.Services +{ + public interface ICosmosDBRepositoryQueryMetricsTracker : IDisposable + where T : BaseDocument + { + void TrackResponseMetrics(FeedResponse response); + } +} diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/IInvoicingRepository.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/IInvoicingRepository.cs new file mode 100644 index 00000000..03180a25 --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/IInvoicingRepository.cs @@ -0,0 +1,18 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Threading.Tasks; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService.Services +{ + public interface IInvoicingRepository + { + Task> GetAggreatedInvoincingDataAsync( + string ownerId, + int year, + int month); + } +} diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/InvoicingRepository.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/InvoicingRepository.cs new file mode 100644 index 00000000..f1c2efec --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Services/InvoicingRepository.cs @@ -0,0 +1,67 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Azure.Cosmos; +using Microsoft.FeatureManagement; +using Fabrikam.DroneDelivery.DroneSchedulerService.Models; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService.Services +{ + public class InvoicingRepository : IInvoicingRepository + { + private const string UsePartitionKeyFeatureName = "UsePartitionKey"; + private readonly ICosmosRepository _repository; + private readonly IFeatureManager _featureManager; + + public InvoicingRepository( + ICosmosRepository repository, + IFeatureManager featureManager) + { + this._repository = repository; + this._featureManager = featureManager; + } + + public async Task> GetAggreatedInvoincingDataAsync( + string ownerId, + int year, + int month) + { + (QueryDefinition query, string partitionKey) = + this._featureManager.IsEnabled(UsePartitionKeyFeatureName) + ? (new QueryDefinition("SELECT VALUE root FROM root WHERE root.year = @year AND root.month = @month") + .WithParameter("@year", year) + .WithParameter("@month", month), + ownerId) + : (new QueryDefinition("SELECT VALUE root FROM root WHERE root.year = @year AND root.month = @month AND root.ownerId = @ownerId") + .WithParameter("@year", year) + .WithParameter("@month", month) + .WithParameter("@ownerId", ownerId), + default(string)); + + var results = await _repository.GetItemsAsync(query, partitionKey); + + var result = results + .Aggregate( + new + { + TraveledMiles = 0d, + AssignedHours = 0d + }, + (c, n) => new + { + TraveledMiles = c.TraveledMiles + n.TraveledMiles, + AssignedHours = c.AssignedHours + n.AssignedHours + }); + + return Tuple.Create( + result.TraveledMiles, + result.AssignedHours); + } + } +} \ No newline at end of file diff --git a/src/bc-shipping/delivery/MockDroneScheduler/Startup.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Startup.cs similarity index 52% rename from src/bc-shipping/delivery/MockDroneScheduler/Startup.cs rename to src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Startup.cs index 64e5b211..b1cd4c34 100644 --- a/src/bc-shipping/delivery/MockDroneScheduler/Startup.cs +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/Startup.cs @@ -4,60 +4,76 @@ // ------------------------------------------------------------ using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; -using Swashbuckle.AspNetCore.Swagger; +using Microsoft.FeatureManagement; +using Microsoft.OpenApi.Models; +using Fabrikam.DroneDelivery.DroneSchedulerService.Models; +using Fabrikam.DroneDelivery.DroneSchedulerService.Services; using Serilog; using Serilog.Formatting.Compact; -using Fabrikam.DroneDelivery.Common; -namespace MockDroneScheduler +namespace Fabrikam.DroneDelivery.DroneSchedulerService { public class Startup { - public Startup(IHostingEnvironment env) + private const string HealCheckName = "ReadinessLiveness"; + + public Startup(IConfiguration configuration) { - var builder = new ConfigurationBuilder() - .SetBasePath(env.ContentRootPath) - .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) - .AddEnvironmentVariables(); - Configuration = builder.Build(); + Configuration = configuration; } - public IConfigurationRoot Configuration { get; } + public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddSingleton(); - services.AddLogging(loggingBuilder => - loggingBuilder.AddSerilog(dispose: true)); + services.AddFeatureManagement(); + + // Configure AppInsights + services.AddApplicationInsightsKubernetesEnricher(); + services.AddApplicationInsightsTelemetry(Configuration); // Add framework services. - services.AddMvc(); + services.AddControllers(); + + // Add health check + services.AddHealthChecks().AddCheck( + HealCheckName, + () => HealthCheckResult.Healthy("OK")); // Register the Swagger generator, defining one or more Swagger documents services.AddSwaggerGen(c => { - c.SwaggerDoc("v1", new Info { Title = "Mock DroneScheduler API", Version = "v1" }); + c.SwaggerDoc("v1", new OpenApiInfo { Title = "Mock DroneScheduler API", Version = "v1" }); }); + + services.AddSingleton(); + services + .AddCosmosRepository< + InternalDroneUtilization>(Configuration); } // 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, IHttpContextAccessor httpContextAccessor) + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IHttpContextAccessor httpContextAccessor) { Log.Logger = new LoggerConfiguration() .WriteTo.Console(new CompactJsonFormatter()) .ReadFrom.Configuration(Configuration) - .Enrich.With(new CorrelationLogEventEnricher(httpContextAccessor, Configuration["Logging:CorrelationHeaderKey"])) .CreateLogger(); - app.UseMvc(); + app.UseRouting(); + app.UseEndpoints(endpoints => + { + endpoints.MapHealthChecks("/healthz"); + endpoints.MapControllers(); + }); // Enable middleware to serve generated Swagger as a JSON endpoint. app.UseSwagger(); diff --git a/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/TracingExtensions.cs b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/TracingExtensions.cs new file mode 100644 index 00000000..4ad59283 --- /dev/null +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/TracingExtensions.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.ApplicationInsights.Extensibility; +using Microsoft.Extensions.DependencyInjection; + +namespace Fabrikam.DroneDelivery.DroneSchedulerService +{ + public static class TracingExtensions + { + public static IServiceCollection AddApplicationInsightsKubernetesEnricher (this IServiceCollection services) + { + services.Configure( + (config) => + config.AddApplicationInsightsKubernetesEnricher( + applyOptions: null) + ); + + return services; + } + } +} diff --git a/src/bc-shipping/delivery/MockAccountService/appsettings.Development.json b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/appsettings.Development.json similarity index 65% rename from src/bc-shipping/delivery/MockAccountService/appsettings.Development.json rename to src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/appsettings.Development.json index fa8ce71a..6c53351c 100644 --- a/src/bc-shipping/delivery/MockAccountService/appsettings.Development.json +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/appsettings.Development.json @@ -1,6 +1,11 @@ { "Logging": { "IncludeScopes": false, + "ApplicationInsights": { + "LogLevel": { + "Default": "None" + } + }, "LogLevel": { "Default": "Debug", "System": "Information", diff --git a/src/bc-shipping/delivery/MockDroneScheduler/appsettings.json b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/appsettings.json similarity index 64% rename from src/bc-shipping/delivery/MockDroneScheduler/appsettings.json rename to src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/appsettings.json index 815fe795..66139898 100644 --- a/src/bc-shipping/delivery/MockDroneScheduler/appsettings.json +++ b/src/shipping/dronescheduler/Fabrikam.DroneDelivery.DroneSchedulerService/appsettings.json @@ -1,14 +1,21 @@ { "Logging": { "IncludeScopes": false, + "ApplicationInsights": { + "LogLevel": { + "Default": "Error" + } + }, "LogLevel": { "Default": "Information" - }, - "CorrelationHeaderKey": "l5d-ctx-trace" + } }, "Serilog": { "MinimumLevel": "Verbose", "Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ], "WriteTo": [] + }, + "FeatureManagement": { + "UsePartitionKey": false } } \ No newline at end of file diff --git a/src/shipping/dronescheduler/azure-pipelines-cd.json b/src/shipping/dronescheduler/azure-pipelines-cd.json new file mode 100644 index 00000000..2f438a29 --- /dev/null +++ b/src/shipping/dronescheduler/azure-pipelines-cd.json @@ -0,0 +1,1738 @@ +{ + "source": 1, + "revision": 3, + "description": null, + "isDeleted": false, + "variables": { + "REASON": { + "value": "Azure DevOps CD Pipeline", + "allowOverride": true + }, + "REPO_NAME": { + "value": "dronescheduler" + } + }, + "variableGroups": [], + "environments": [ + { + "id": 30, + "name": "dev", + "rank": 1, + "owner": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "variables": { + "DRONESCHEDULER_KEYVAULT_URI": { + "value": "DEV_DRONESCHEDULER_KEYVAULT_URI_VAR_VAL" + }, + "DRONESCHEDULER_PRINCIPAL_CLIENT_ID": { + "value": "DEV_DRONESCHEDULER_PRINCIPAL_CLIENT_ID_VAR_VAL" + }, + "DRONESCHEDULER_PRINCIPAL_RESOURCE_ID": { + "value": "DEV_DRONESCHEDULER_PRINCIPAL_RESOURCE_ID_VAR_VAL" + }, + "ACR_SERVER": { + "value": "DEV_ACR_SERVER_VAR_VAL" + }, + "ACR_NAME": { + "value": "DEV_ACR_NAME_VAR_VAL" + }, + "COSMOSDB_DATABASEID": { + "value": "DEV_COSMOSDB_DATABASEID_VAR_VAL" + }, + "COSMOSDB_COLLECTIONID": { + "value": "DEV_COSMOSDB_COLLECTIONID_VAR_VAL" + }, + "CLUSTER_SUBNET_PREFIX": { + "value": "DEV_CLUSTER_SUBNET_PREFIX_VAR_VAL" + } + }, + "variableGroups": [], + "preDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 90 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 1 + } + }, + "deployStep": { + "id": 97 + }, + "postDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 98 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 2 + } + }, + "deployPhases": [ + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [ + { + "alias": "ci-dronescheduler", + "artifactType": "Build", + "artifactDownloadMode": "All", + "artifactItems": [] + } + ] + }, + "queueId": AZURE_DEVOPS_DRONE_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 1, + "phaseType": 1, + "name": "Agent job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "helm upgrade dronescheduler dev", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend-dev", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)-dev", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),identity.clientid=$(DRONESCHEDULER_PRINCIPAL_CLIENT_ID),identity.resourceid=$(DRONESCHEDULER_PRINCIPAL_RESOURCE_ID),networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",keyvault.uri=$(DRONESCHEDULER_KEYVAULT_URI),cosmosdb.id=$(COSMOSDB_DATABASEID),cosmosdb.collectionid=$(COSMOSDB_COLLECTIONID),reason=\"$(REASON)\",envs.dev=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + } + ], + "environmentOptions": { + "emailNotificationType": "OnlyOnFailure", + "emailRecipients": "release.environment.owner;release.creator", + "skipArtifactsDownload": false, + "timeoutInMinutes": 0, + "enableAccessToken": false, + "publishDeploymentStatus": true, + "badgeEnabled": false, + "autoLinkWorkItems": false, + "pullRequestDeploymentEnabled": false + }, + "demands": [], + "conditions": [ + { + "name": "ReleaseStarted", + "conditionType": 1, + "value": "" + } + ], + "executionPolicy": { + "concurrencyCount": 1, + "queueDepthCount": 0 + }, + "schedules": [], + "retentionPolicy": { + "daysToKeep": 30, + "releasesToKeep": 3, + "retainBuild": true + }, + "processParameters": { + "inputs": [ + { + "aliases": [], + "options": { + "Azure Resource Manager": "Azure Resource Manager", + "Kubernetes Service Connection": "Kubernetes Service Connection" + }, + "properties": { + "EditableOptions": "false" + }, + "name": "connectionType", + "label": "Connection Type.", + "defaultValue": "Azure Resource Manager", + "type": "pickList", + "helpMarkDown": "", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "azureSubscriptionEndpoint", + "label": "Azure subscription", + "defaultValue": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "required": true, + "type": "connectedService:AzureRM", + "helpMarkDown": "Select an Azure subscription, which has your Azure Container Registry.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "azureResourceGroup", + "label": "Resource group", + "defaultValue": "RESOURCE_GROUP_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Resource Group.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "kubernetesCluster", + "label": "Kubernetes cluster", + "defaultValue": "CLUSTER_NAME_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Kubernetes Service cluster.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "kubernetesServiceEndpoint", + "label": "Kubernetes Service Connection", + "defaultValue": "", + "required": true, + "type": "connectedService:kubernetes", + "helpMarkDown": "Select a Kubernetes service connection.", + "visibleRule": "connectionType = Kubernetes Service Connection", + "groupName": "" + } + ], + "dataSourceBindings": [ + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "kubernetesCluster", + "resultTemplate": "{{{name}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + }, + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "azureResourceGroup", + "resultTemplate": "{{{ #extractResource id resourcegroups}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + } + ] + }, + "properties": {}, + "preDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "postDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "environmentTriggers": [], + "badgeUrl": null + }, + { + "id": 31, + "name": "QA", + "rank": 2, + "owner": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "variables": { + "DRONESCHEDULER_KEYVAULT_URI": { + "value": "QA_DRONESCHEDULER_KEYVAULT_URI_VAR_VAL" + }, + "DRONESCHEDULER_PRINCIPAL_CLIENT_ID": { + "value": "QA_DRONESCHEDULER_PRINCIPAL_CLIENT_ID_VAR_VAL" + }, + "DRONESCHEDULER_PRINCIPAL_RESOURCE_ID": { + "value": "QA_DRONESCHEDULER_PRINCIPAL_RESOURCE_ID_VAR_VAL" + }, + "ACR_SERVER": { + "value": "QA_ACR_SERVER_VAR_VAL" + }, + "ACR_NAME": { + "value": "QA_ACR_NAME_VAR_VAL" + }, + "COSMOSDB_DATABASEID": { + "value": "QA_COSMOSDB_DATABASEID_VAR_VAL" + }, + "COSMOSDB_COLLECTIONID": { + "value": "QA_COSMOSDB_COLLECTIONID_VAR_VAL" + }, + "CLUSTER_SUBNET_PREFIX": { + "value": "QA_CLUSTER_SUBNET_PREFIX_VAR_VAL" + } + }, + "variableGroups": [], + "preDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 91 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 1 + } + }, + "deployStep": { + "id": 96 + }, + "postDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 99 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 2 + } + }, + "deployPhases": [ + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [ + { + "alias": "ci-dronescheduler", + "artifactType": "Build", + "artifactDownloadMode": "All", + "artifactItems": [] + } + ] + }, + "queueId": AZURE_DEVOPS_DRONE_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 1, + "phaseType": 1, + "name": "Agent job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "helm upgrade dronescheduler qa", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend-qa", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)-qa", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),identity.clientid=$(DRONESCHEDULER_PRINCIPAL_CLIENT_ID),identity.resourceid=$(DRONESCHEDULER_PRINCIPAL_RESOURCE_ID),networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",keyvault.uri=$(DRONESCHEDULER_KEYVAULT_URI),cosmosdb.id=$(COSMOSDB_DATABASEID),cosmosdb.collectionid=$(COSMOSDB_COLLECTIONID),reason=\"$(REASON)\",envs.qa=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + } + ], + "environmentOptions": { + "emailNotificationType": "OnlyOnFailure", + "emailRecipients": "release.environment.owner;release.creator", + "skipArtifactsDownload": false, + "timeoutInMinutes": 0, + "enableAccessToken": false, + "publishDeploymentStatus": true, + "badgeEnabled": false, + "autoLinkWorkItems": false, + "pullRequestDeploymentEnabled": false + }, + "demands": [], + "conditions": [ + { + "name": "dev", + "conditionType": 2, + "value": "4" + } + ], + "executionPolicy": { + "concurrencyCount": 1, + "queueDepthCount": 0 + }, + "schedules": [], + "retentionPolicy": { + "daysToKeep": 30, + "releasesToKeep": 3, + "retainBuild": true + }, + "processParameters": { + "inputs": [ + { + "aliases": [], + "options": { + "Azure Resource Manager": "Azure Resource Manager", + "Kubernetes Service Connection": "Kubernetes Service Connection" + }, + "properties": { + "EditableOptions": "false" + }, + "name": "connectionType", + "label": "Connection Type.", + "defaultValue": "Azure Resource Manager", + "type": "pickList", + "helpMarkDown": "", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "azureSubscriptionEndpoint", + "label": "Azure subscription", + "defaultValue": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "required": true, + "type": "connectedService:AzureRM", + "helpMarkDown": "Select an Azure subscription, which has your Azure Container Registry.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "azureResourceGroup", + "label": "Resource group", + "defaultValue": "RESOURCE_GROUP_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Resource Group.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "kubernetesCluster", + "label": "Kubernetes cluster", + "defaultValue": "CLUSTER_NAME_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Kubernetes Service cluster.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "kubernetesServiceEndpoint", + "label": "Kubernetes Service Connection", + "defaultValue": "", + "required": true, + "type": "connectedService:kubernetes", + "helpMarkDown": "Select a Kubernetes service connection.", + "visibleRule": "connectionType = Kubernetes Service Connection", + "groupName": "" + } + ], + "dataSourceBindings": [ + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "kubernetesCluster", + "resultTemplate": "{{{name}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + }, + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "azureResourceGroup", + "resultTemplate": "{{{ #extractResource id resourcegroups}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + } + ] + }, + "properties": {}, + "preDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "postDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "environmentTriggers": [], + "badgeUrl": null + }, + { + "id": 32, + "name": "staging", + "rank": 3, + "owner": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "variables": { + "DRONESCHEDULER_KEYVAULT_URI": { + "value": "STAGING_DRONESCHEDULER_KEYVAULT_URI_VAR_VAL" + }, + "DRONESCHEDULER_PRINCIPAL_CLIENT_ID": { + "value": "STAGING_DRONESCHEDULER_PRINCIPAL_CLIENT_ID_VAR_VAL" + }, + "DRONESCHEDULER_PRINCIPAL_RESOURCE_ID": { + "value": "STAGING_DRONESCHEDULER_PRINCIPAL_RESOURCE_ID_VAR_VAL" + }, + "ACR_SERVER": { + "value": "STAGING_ACR_SERVER_VAR_VAL" + }, + "ACR_NAME": { + "value": "STAGING_ACR_NAME_VAR_VAL" + }, + "COSMOSDB_DATABASEID": { + "value": "STAGING_COSMOSDB_DATABASEID_VAR_VAL" + }, + "COSMOSDB_COLLECTIONID": { + "value": "STAGING_COSMOSDB_COLLECTIONID_VAR_VAL" + }, + "CLUSTER_SUBNET_PREFIX": { + "value": "STAGING_CLUSTER_SUBNET_PREFIX_VAR_VAL" + } + }, + "variableGroups": [], + "preDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 92 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 1 + } + }, + "deployStep": { + "id": 95 + }, + "postDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 100 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 2 + } + }, + "deployPhases": [ + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [ + { + "alias": "ci-dronescheduler", + "artifactType": "Build", + "artifactDownloadMode": "All", + "artifactItems": [] + } + ] + }, + "queueId": AZURE_DEVOPS_DRONE_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 1, + "phaseType": 1, + "name": "Agent job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "helm upgrate dronescheduler staging", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend-staging", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)-staging", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),identity.clientid=$(DRONESCHEDULER_PRINCIPAL_CLIENT_ID),identity.resourceid=$(DRONESCHEDULER_PRINCIPAL_RESOURCE_ID),networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",keyvault.uri=$(DRONESCHEDULER_KEYVAULT_URI),cosmosdb.id=$(COSMOSDB_DATABASEID),cosmosdb.collectionid=$(COSMOSDB_COLLECTIONID),reason=\"$(REASON)\",envs.staging=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + } + ], + "environmentOptions": { + "emailNotificationType": "OnlyOnFailure", + "emailRecipients": "release.environment.owner;release.creator", + "skipArtifactsDownload": false, + "timeoutInMinutes": 0, + "enableAccessToken": false, + "publishDeploymentStatus": true, + "badgeEnabled": false, + "autoLinkWorkItems": false, + "pullRequestDeploymentEnabled": false + }, + "demands": [], + "conditions": [ + { + "name": "dev", + "conditionType": 2, + "value": "4" + } + ], + "executionPolicy": { + "concurrencyCount": 1, + "queueDepthCount": 0 + }, + "schedules": [], + "retentionPolicy": { + "daysToKeep": 30, + "releasesToKeep": 3, + "retainBuild": true + }, + "processParameters": { + "inputs": [ + { + "aliases": [], + "options": { + "Azure Resource Manager": "Azure Resource Manager", + "Kubernetes Service Connection": "Kubernetes Service Connection" + }, + "properties": { + "EditableOptions": "false" + }, + "name": "connectionType", + "label": "Connection Type.", + "defaultValue": "Azure Resource Manager", + "type": "pickList", + "helpMarkDown": "", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "azureSubscriptionEndpoint", + "label": "Azure subscription", + "defaultValue": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "required": true, + "type": "connectedService:AzureRM", + "helpMarkDown": "Select an Azure subscription, which has your Azure Container Registry.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "azureResourceGroup", + "label": "Resource group", + "defaultValue": "RESOURCE_GROUP_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Resource Group.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "kubernetesCluster", + "label": "Kubernetes cluster", + "defaultValue": "CLUSTER_NAME_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Kubernetes Service cluster.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "kubernetesServiceEndpoint", + "label": "Kubernetes Service Connection", + "defaultValue": "", + "required": true, + "type": "connectedService:kubernetes", + "helpMarkDown": "Select a Kubernetes service connection.", + "visibleRule": "connectionType = Kubernetes Service Connection", + "groupName": "" + } + ], + "dataSourceBindings": [ + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "kubernetesCluster", + "resultTemplate": "{{{name}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + }, + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "azureResourceGroup", + "resultTemplate": "{{{ #extractResource id resourcegroups}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + } + ] + }, + "properties": {}, + "preDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "postDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "environmentTriggers": [], + "badgeUrl": null + }, + { + "id": 33, + "name": "production", + "rank": 4, + "owner": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "variables": { + "DRONESCHEDULER_KEYVAULT_URI": { + "value": "PROD_DRONESCHEDULER_KEYVAULT_URI_VAR_VAL" + }, + "DRONESCHEDULER_PRINCIPAL_CLIENT_ID": { + "value": "PROD_DRONESCHEDULER_PRINCIPAL_CLIENT_ID_VAR_VAL" + }, + "DRONESCHEDULER_PRINCIPAL_RESOURCE_ID": { + "value": "PROD_DRONESCHEDULER_PRINCIPAL_RESOURCE_ID_VAR_VAL" + }, + "SOURCE_ACR_SERVER": { + "value": "SOURCE_ACR_SERVER_VAR_VAL" + }, + "SOURCE_ACR_NAME": { + "value": "SOURCE_ACR_NAME_VAR_VAL" + }, + "ACR_SERVER": { + "value": "PROD_ACR_SERVER_VAR_VAL" + }, + "ACR_NAME": { + "value": "PROD_ACR_NAME_VAR_VAL" + }, + "COSMOSDB_DATABASEID": { + "value": "PROD_COSMOSDB_DATABASEID_VAR_VAL" + }, + "COSMOSDB_COLLECTIONID": { + "value": "PROD_COSMOSDB_COLLECTIONID_VAR_VAL" + }, + "CLUSTER_SUBNET_PREFIX": { + "value": "PROD_CLUSTER_SUBNET_PREFIX_VAR_VAL" + } + }, + "variableGroups": [], + "preDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": false, + "isNotificationOn": false, + "approver": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "id": 93 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": true, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 1 + } + }, + "deployStep": { + "id": 94 + }, + "postDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 101 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 2 + } + }, + "deployPhases": [ + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [ + { + "alias": "ci-dronescheduler", + "artifactType": "Build", + "artifactDownloadMode": "All", + "artifactItems": [] + } + ] + }, + "queueId": AZURE_DEVOPS_DRONE_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 1, + "phaseType": 1, + "name": "Agent job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "promote dronescheduler image to production", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr import --name $(ACR_NAME) --source $(SOURCE_ACR_SERVER)/$(REPO_NAME):$(Build.SourceBranchName) --force", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(SOURCE_ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "install new dronescheduler version in the green slot", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(SOURCE_ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),identity.clientid=$(DRONESCHEDULER_PRINCIPAL_CLIENT_ID),identity.resourceid=$(DRONESCHEDULER_PRINCIPAL_RESOURCE_ID),networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",keyvault.uri=$(DRONESCHEDULER_KEYVAULT_URI),cosmosdb.id=$(COSMOSDB_DATABASEID),cosmosdb.collectionid=$(COSMOSDB_COLLECTIONID),reason=\"$(REASON)\",envs.prod=true,dronescheduler-prod.experimental=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + }, + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 2, + "phaseType": 2, + "name": "Agentless job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "bcb64569-d51a-4af0-9c01-ea5d05b3b622", + "version": "8.*", + "name": "Swap (blue-green)", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 3600, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "instructions": "consider running some canary or just resume for swapping blue and green versions", + "emailRecipients": "", + "onTimeout": "reject" + } + } + ] + }, + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [ + { + "alias": "ci-dronescheduler", + "artifactType": "Build", + "artifactDownloadMode": "All", + "artifactItems": [] + } + ] + }, + "queueId": AZURE_DEVOPS_DRONE_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 3, + "phaseType": 1, + "name": "Agent job (swap)", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "cbc316a2-586f-4def-be79-488a1f503564", + "version": "1.*", + "name": "get current dronescheduler blue slot version", + "refName": "BlueVersion", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "Azure Resource Manager", + "kubernetesServiceEndpoint": "", + "azureSubscriptionEndpoint": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "azureResourceGroup": "RESOURCE_GROUP_VAR_VAL", + "kubernetesCluster": "CLUSTER_NAME_VAR_VAL", + "useClusterAdmin": "false", + "namespace": "", + "command": "get", + "useConfigurationFile": "false", + "configurationType": "configuration", + "configuration": "", + "inline": "", + "arguments": "-n backend svc/dronescheduler -o \"jsonpath={.spec.selector['app\\.kubernetes\\.io\\/instance']}\" --ignore-not-found=true", + "secretType": "dockerRegistry", + "secretArguments": "", + "containerRegistryType": "Azure Container Registry", + "dockerRegistryEndpoint": "", + "azureSubscriptionEndpointForSecrets": "", + "azureContainerRegistry": "", + "secretName": "", + "forceUpdate": "true", + "configMapName": "", + "forceUpdateConfigMap": "false", + "useConfigMapFile": "false", + "configMapFile": "", + "configMapArguments": "", + "versionOrLocation": "version", + "versionSpec": "1.12.4", + "checkLatest": "false", + "specifyLocation": "", + "cwd": "$(System.DefaultWorkingDirectory)", + "outputFormat": "" + } + }, + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(SOURCE_ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "cbc316a2-586f-4def-be79-488a1f503564", + "version": "1.*", + "name": "swap blue dronescheduler version to green slot", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "Azure Resource Manager", + "kubernetesServiceEndpoint": "", + "azureSubscriptionEndpoint": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "azureResourceGroup": "RESOURCE_GROUP_VAR_VAL", + "kubernetesCluster": "CLUSTER_NAME_VAR_VAL", + "useClusterAdmin": "false", + "namespace": "", + "command": "set", + "useConfigurationFile": "false", + "configurationType": "configuration", + "configuration": "", + "inline": "", + "arguments": "selector -n backend svc/dronescheduler-experimental app.kubernetes.io/name=dronescheduler,app.kubernetes.io/instance=$(BlueVersion.KubectlOutput)", + "secretType": "dockerRegistry", + "secretArguments": "", + "containerRegistryType": "Azure Container Registry", + "dockerRegistryEndpoint": "", + "azureSubscriptionEndpointForSecrets": "", + "azureContainerRegistry": "", + "secretName": "", + "forceUpdate": "true", + "configMapName": "", + "forceUpdateConfigMap": "false", + "useConfigMapFile": "false", + "configMapFile": "", + "configMapArguments": "", + "versionOrLocation": "version", + "versionSpec": "1.12.4", + "checkLatest": "false", + "specifyLocation": "", + "cwd": "$(System.DefaultWorkingDirectory)", + "outputFormat": "json" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "install new dronescheduler version in the blue slot", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(SOURCE_ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),identity.clientid=$(DRONESCHEDULER_PRINCIPAL_CLIENT_ID),identity.resourceid=$(DRONESCHEDULER_PRINCIPAL_RESOURCE_ID),networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",keyvault.uri=$(DRONESCHEDULER_KEYVAULT_URI),cosmosdb.id=$(COSMOSDB_DATABASEID),cosmosdb.collectionid=$(COSMOSDB_COLLECTIONID),reason=\"$(REASON)\",envs.prod=true,current=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + } + ], + "environmentOptions": { + "emailNotificationType": "OnlyOnFailure", + "emailRecipients": "release.environment.owner;release.creator", + "skipArtifactsDownload": false, + "timeoutInMinutes": 0, + "enableAccessToken": false, + "publishDeploymentStatus": true, + "badgeEnabled": false, + "autoLinkWorkItems": false, + "pullRequestDeploymentEnabled": false + }, + "demands": [], + "conditions": [ + { + "name": "QA", + "conditionType": 2, + "value": "4" + }, + { + "name": "staging", + "conditionType": 2, + "value": "4" + } + ], + "executionPolicy": { + "concurrencyCount": 1, + "queueDepthCount": 0 + }, + "schedules": [], + "retentionPolicy": { + "daysToKeep": 30, + "releasesToKeep": 3, + "retainBuild": true + }, + "processParameters": { + "inputs": [ + { + "aliases": [], + "options": { + "Azure Resource Manager": "Azure Resource Manager", + "Kubernetes Service Connection": "Kubernetes Service Connection" + }, + "properties": { + "EditableOptions": "false" + }, + "name": "connectionType", + "label": "Connection Type.", + "defaultValue": "Azure Resource Manager", + "type": "pickList", + "helpMarkDown": "", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "azureSubscriptionEndpoint", + "label": "Azure subscription", + "defaultValue": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "required": true, + "type": "connectedService:AzureRM", + "helpMarkDown": "Select an Azure subscription, which has your Azure Container Registry.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "azureResourceGroup", + "label": "Resource group", + "defaultValue": "RESOURCE_GROUP_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Resource Group.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "kubernetesCluster", + "label": "Kubernetes cluster", + "defaultValue": "CLUSTER_NAME_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Kubernetes Service cluster.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "kubernetesServiceEndpoint", + "label": "Kubernetes Service Connection", + "defaultValue": "", + "required": true, + "type": "connectedService:kubernetes", + "helpMarkDown": "Select a Kubernetes service connection.", + "visibleRule": "connectionType = Kubernetes Service Connection", + "groupName": "" + } + ], + "dataSourceBindings": [ + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "kubernetesCluster", + "resultTemplate": "{{{name}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + }, + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "azureResourceGroup", + "resultTemplate": "{{{ #extractResource id resourcegroups}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + } + ] + }, + "properties": {}, + "preDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "postDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "environmentTriggers": [], + "badgeUrl": null + } + ], + "artifacts": [ + { + "sourceId": "AZURE_DEVOPS_PROJECT_ID_VAR_VAL:AZURE_DEVOPS_DRONE_BUILD_ID_VAR_VAL", + "type": "Build", + "alias": "ci-dronescheduler", + "definitionReference": { + "artifactSourceDefinitionUrl": { + "id": "", + "name": "" + }, + "defaultVersionBranch": { + "id": "", + "name": "" + }, + "defaultVersionSpecific": { + "id": "", + "name": "" + }, + "defaultVersionTags": { + "id": "", + "name": "" + }, + "defaultVersionType": { + "id": "selectDuringReleaseCreationType", + "name": "Specify at the time of release creation" + }, + "definition": { + "id": "AZURE_DEVOPS_DRONE_BUILD_ID_VAR_VAL", + "name": "aks-ri-ci-dronescheduler" + }, + "definitions": { + "id": "", + "name": "" + }, + "IsMultiDefinitionType": { + "id": "False", + "name": "False" + }, + "project": { + "id": "AZURE_DEVOPS_PROJECT_ID_VAR_VAL", + "name": "roadmap" + }, + "repository": { + "id": "AZURE_DEVOPS_REPOS_ID_VAR_VALL", + "name": "roadmap" + } + }, + "isPrimary": true, + "isRetained": false + } + ], + "triggers": [ + { + "artifactAlias": "ci-dronescheduler", + "triggerConditions": [ + { + "sourceBranch": "release/$(REPO_NAME)/v*", + "tags": [], + "useBuildDefinitionBranch": false, + "createReleaseOnBuildTagging": false + } + ], + "triggerType": 1 + } + ], + "releaseNameFormat": "release-$(rev:r)", + "tags": [], + "pipelineProcess": { + "type": 1 + }, + "properties": { + "DefinitionCreationSource": { + "$type": "System.String", + "$value": "ReleaseImport" + } + }, + "id": 16, + "name": "dronescheduler-cd", + "path": null, + "projectReference": null, + "url": null +} diff --git a/src/shipping/dronescheduler/azure-pipelines.yml b/src/shipping/dronescheduler/azure-pipelines.yml new file mode 100644 index 00000000..a79a6615 --- /dev/null +++ b/src/shipping/dronescheduler/azure-pipelines.yml @@ -0,0 +1,122 @@ +variables: + repositoryName: dronescheduler + chartPath: charts/dronescheduler + dockerFileName: src/shipping/dronescheduler/Dockerfile + buildContext: src/shipping + imageName: $(repositoryName):$(Build.SourceBranchName) + azureSubscription: AZURE_PIPELINES_SERVICE_CONN_NAME_VAR_VAL + azureContainerRegistry: ACR_SERVER_VAR_VAL + azureContainerRegistryName: ACR_NAME_VAR_VAL + +name: $(build.sourceBranch)-$(Date:yyyyMMdd)$(Rev:.rr) + +pr: # only valid for GitHub. Using Azure repo it must be configure as a Branch Policy + paths: + include: + - /src/shipping/dronescheduler/ + + branches: + include: + - master + - release/dronescheduler/v* # for bug fixes + +trigger: + batch: true + branches: + include: + # for new release to production: release flow strategy + - release/dronescheduler/v* + - refs/relelase/dronescheduler/v* + - master + - feature/dronescheduler/* + - topic/dronescheduler/* + paths: + include: + - /src/shipping/dronescheduler/ + +resources: +- repo: self + +jobs: + +# CI +- job: droneschedulerjobci + displayName: "dronescheduler CI" + pool: + vmImage: 'Ubuntu 16.04' + timeoutInMinutes: 90 + variables: + fullCI: $[ startsWith(variables['build.sourceBranch'], 'refs/heads/release/dronescheduler/v') ] + buildImage: $[ eq(variables['build.sourceBranch'], 'refs/heads/master') ] + steps: + - task: Docker@1 + condition: or(eq(variables['buildImage'],True),eq(variables['fullCI'],True)) + displayName: 'Build runtime image' + inputs: + + azureSubscriptionEndpoint: $(azureSubscription) + + azureContainerRegistry: $(azureContainerRegistry) + + dockerFile: $(System.DefaultWorkingDirectory)/$(dockerFileName) + + includeLatestTag: false + + imageName: '$(imageName)' + + buildContext: $(buildContext) + + useDefaultContext: false + + - task: Docker@1 + condition: eq(variables['fullCI'],True) + displayName: 'Push runtime image' + inputs: + azureSubscriptionEndpoint: $(azureSubscription) + + azureContainerRegistry: $(azureContainerRegistry) + + command: 'Push an image' + + imageName: '$(imageName)' + + includeSourceTags: false + + - task: HelmInstaller@0 + condition: eq(variables['fullCI'],True) + displayName: 'Install Helm 3.0.3' + inputs: + helmVersion: 3.0.3 + + checkLatestHelmVersion: false + + kubectlVersion: 1.12.4 + + checkLatestKubectl: false + + - task: HelmDeploy@0 + condition: eq(variables['fullCI'],True) + displayName: 'helm package' + inputs: + command: package + + chartPath: $(chartPath) + + chartVersion: $(Build.SourceBranchName) + + updateDependency: true + + save: false + + arguments: '--app-version $(Build.SourceBranchName)' + + - task: AzureCLI@1 + condition: eq(variables['fullCI'],True) + displayName: 'Push a helm package' + inputs: + azureSubscription: $(azureSubscription) + + scriptLocation: inlineScript + + inlineScript: | + az acr helm push $(System.ArtifactsDirectory)/$(repositoryName)-$(Build.SourceBranchName).tgz --name $(azureContainerRegistryName) --force; diff --git a/src/shipping/dronescheduler/scripts/run.sh b/src/shipping/dronescheduler/scripts/run.sh new file mode 100644 index 00000000..f2deec86 --- /dev/null +++ b/src/shipping/dronescheduler/scripts/run.sh @@ -0,0 +1,2 @@ +#!/bin/bash +dotnet Fabrikam.DroneDelivery.DroneSchedulerService.dll diff --git a/src/bc-shipping/ingestion/.gitignore b/src/shipping/ingestion/.gitignore similarity index 100% rename from src/bc-shipping/ingestion/.gitignore rename to src/shipping/ingestion/.gitignore diff --git a/src/bc-shipping/ingestion/.mvn/wrapper/maven-wrapper.jar b/src/shipping/ingestion/.mvn/wrapper/maven-wrapper.jar similarity index 100% rename from src/bc-shipping/ingestion/.mvn/wrapper/maven-wrapper.jar rename to src/shipping/ingestion/.mvn/wrapper/maven-wrapper.jar diff --git a/src/bc-shipping/ingestion/.mvn/wrapper/maven-wrapper.properties b/src/shipping/ingestion/.mvn/wrapper/maven-wrapper.properties similarity index 100% rename from src/bc-shipping/ingestion/.mvn/wrapper/maven-wrapper.properties rename to src/shipping/ingestion/.mvn/wrapper/maven-wrapper.properties diff --git a/src/shipping/ingestion/Dockerfile b/src/shipping/ingestion/Dockerfile new file mode 100644 index 00000000..1ea40cd3 --- /dev/null +++ b/src/shipping/ingestion/Dockerfile @@ -0,0 +1,36 @@ +FROM openjdk:8-jre-alpine as base +WORKDIR /usr/src/app +EXPOSE 80 + +FROM maven:3.6.0-jdk-8-slim as maven +WORKDIR /usr/src/app + +COPY pom.xml ./ +RUN mvn clean dependency:go-offline + +FROM maven as build +WORKDIR /usr/src/app + +COPY src ./src +RUN mvn compile + +FROM build as testrunner +WORKDIR /usr/src/app + +ENTRYPOINT ["mvn", "verify"] + +FROM build as package +WORKDIR /usr/src/app + +RUN mvn package -Dmaven.test.skip=true -Dcheckstyle.skip=true -Dmaven.javadoc.skip=true + +FROM base as final + +WORKDIR /app + +ADD https://github.com/Microsoft/ApplicationInsights-Java/releases/download/2.3.0/applicationinsights-agent-2.3.0.jar ./appinsights/applicationinsights-agent-2.3.0.jar +ADD /etc/AI-Agent.xml ./appinsights/AI-Agent.xml + +COPY --from=package /usr/src/app/target/ingestion-0.1.0.jar ./ + +ENTRYPOINT ["java","-Djava.security.egdfile=file:/dev/./urandom","-javaagent:/app/appinsights/applicationinsights-agent-2.3.0.jar","-jar","ingestion-0.1.0.jar"] diff --git a/src/shipping/ingestion/azure-pipelines-cd.json b/src/shipping/ingestion/azure-pipelines-cd.json new file mode 100644 index 00000000..4c7bb36c --- /dev/null +++ b/src/shipping/ingestion/azure-pipelines-cd.json @@ -0,0 +1,1766 @@ +{ + "source": 1, + "revision": 5, + "description": null, + "isDeleted": false, + "variables": { + "REASON": { + "value": "Azure DevOps CD Pipeline", + "allowOverride": true + }, + "REPO_NAME": { + "value": "ingestion" + }, + "SERVICE_NAME": { + "value": "ingestion" + } + }, + "variableGroups": [], + "environments": [ + { + "id": 26, + "name": "dev", + "rank": 1, + "owner": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "variables": { + "AI_IKEY": { + "value": "DEV_AI_IKEY_VAR_VAL", + "isSecret": true + }, + "EXTERNAL_INGEST_FQDN": { + "value": "DEV_EXTERNAL_INGEST_FQDN_VAR_VAL" + }, + "INGESTION_ACCESS_KEY_VALUE": { + "value": "DEV_INGESTION_ACCESS_KEY_VALUE_VAR_VAL", + "isSecret": true + }, + "INGESTION_QUEUE_NAME": { + "value": "DEV_INGESTION_QUEUE_NAME_VAR_VAL" + }, + "INGESTION_QUEUE_NAMESPACE": { + "value": "DEV_INGESTION_QUEUE_NAMESPACE_VAR_VAL" + }, + "INGRESS_TLS_SECRET_CERT": { + "value": "DEV_INGRESS_TLS_SECRET_CERT_VAR_VAL" + }, + "INGRESS_TLS_SECRET_KEY": { + "value": "DEV_INGRESS_TLS_SECRET_KEY_VAR_VAL", + "isSecret": true + }, + "INGRESS_TLS_SECRET_NAME": { + "value": "DEV_INGRESS_TLS_SECRET_NAME_VAR_VAL" + }, + "ACR_SERVER": { + "value": "DEV_ACR_SERVER_VAR_VAL" + }, + "ACR_NAME": { + "value": "DEV_ACR_NAME_VAR_VAL" + }, + "GATEWAY_SUBNET_PREFIX": { + "value": "DEV_GATEWAY_SUBNET_PREFIX_VAR_VAL" + }, + "CLUSTER_SUBNET_PREFIX": { + "value": "DEV_CLUSTER_SUBNET_PREFIX_VAR_VAL" + } + }, + "variableGroups": [], + "preDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 78 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 1 + } + }, + "deployStep": { + "id": 85 + }, + "postDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 86 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 2 + } + }, + "deployPhases": [ + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [] + }, + "queueId": AZURE_DEVOPS_INGESTION_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 1, + "phaseType": 1, + "name": "Agent job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "helm upgrade ingestion dev", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend-dev", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)-dev", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),ingress.hosts[0].name=$(EXTERNAL_INGEST_FQDN),ingress.hosts[0].serviceName=$(SERVICE_NAME),ingress.hosts[0].tls=true,ingress.hosts[0].tlsSecretName=$(INGRESS_TLS_SECRET_NAME),ingress.tls.secrets[0].name=$(INGRESS_TLS_SECRET_NAME),ingress.tls.secrets[0].key=\"$(INGRESS_TLS_SECRET_KEY)\",ingress.tls.secrets[0].certificate=\"$(INGRESS_TLS_SECRET_CERT)\",networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",networkPolicy.ingress.externalSubnet.enabled=true,networkPolicy.ingress.externalSubnet.subnetPrefix=\"$(GATEWAY_SUBNET_PREFIX)\",secrets.appinsights.ikey=$(AI_IKEY),secrets.queue.keyname=IngestionServiceAccessKey,secrets.queue.keyvalue=$(INGESTION_ACCESS_KEY_VALUE),secrets.queue.name=$(INGESTION_QUEUE_NAME),secrets.queue.namespace=$(INGESTION_QUEUE_NAMESPACE),reason=\"$(REASON)\",envs.dev=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + } + ], + "environmentOptions": { + "emailNotificationType": "OnlyOnFailure", + "emailRecipients": "release.environment.owner;release.creator", + "skipArtifactsDownload": false, + "timeoutInMinutes": 0, + "enableAccessToken": false, + "publishDeploymentStatus": true, + "badgeEnabled": false, + "autoLinkWorkItems": false, + "pullRequestDeploymentEnabled": false + }, + "demands": [], + "conditions": [ + { + "name": "ReleaseStarted", + "conditionType": 1, + "value": "" + } + ], + "executionPolicy": { + "concurrencyCount": 1, + "queueDepthCount": 0 + }, + "schedules": [], + "retentionPolicy": { + "daysToKeep": 30, + "releasesToKeep": 3, + "retainBuild": true + }, + "processParameters": { + "inputs": [ + { + "aliases": [], + "options": { + "Azure Resource Manager": "Azure Resource Manager", + "Kubernetes Service Connection": "Kubernetes Service Connection" + }, + "properties": { + "EditableOptions": "false" + }, + "name": "connectionType", + "label": "Connection Type.", + "defaultValue": "Azure Resource Manager", + "type": "pickList", + "helpMarkDown": "", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "azureSubscriptionEndpoint", + "label": "Azure subscription", + "defaultValue": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "required": true, + "type": "connectedService:AzureRM", + "helpMarkDown": "Select an Azure subscription, which has your Azure Container Registry.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "azureResourceGroup", + "label": "Resource group", + "defaultValue": "RESOURCE_GROUP_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Resource Group.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "kubernetesCluster", + "label": "Kubernetes cluster", + "defaultValue": "CLUSTER_NAME_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Kubernetes Service cluster.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "kubernetesServiceEndpoint", + "label": "Kubernetes Service Connection", + "defaultValue": "", + "required": true, + "type": "connectedService:kubernetes", + "helpMarkDown": "Select a Kubernetes service connection.", + "visibleRule": "connectionType = Kubernetes Service Connection", + "groupName": "" + } + ], + "dataSourceBindings": [ + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "kubernetesCluster", + "resultTemplate": "{{{name}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + }, + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "azureResourceGroup", + "resultTemplate": "{{{ #extractResource id resourcegroups}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + } + ] + }, + "properties": {}, + "preDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "postDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "environmentTriggers": [], + "badgeUrl": null + }, + { + "id": 27, + "name": "QA", + "rank": 2, + "owner": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "variables": { + "AI_IKEY": { + "value": "QA_AI_IKEY_VAR_VAL", + "isSecret": true + }, + "EXTERNAL_INGEST_FQDN": { + "value": "QA_EXTERNAL_INGEST_FQDN_VAR_VAL" + }, + "INGESTION_ACCESS_KEY_VALUE": { + "value": "QA_INGESTION_ACCESS_KEY_VALUE_VAR_VAL", + "isSecret": true + }, + "INGESTION_QUEUE_NAME": { + "value": "QA_INGESTION_QUEUE_NAME_VAR_VAL" + }, + "INGESTION_QUEUE_NAMESPACE": { + "value": "QA_INGESTION_QUEUE_NAMESPACE_VAR_VAL" + }, + "INGRESS_TLS_SECRET_CERT": { + "value": "QA_INGRESS_TLS_SECRET_CERT_VAR_VAL" + }, + "INGRESS_TLS_SECRET_KEY": { + "value": "QA_INGRESS_TLS_SECRET_KEY_VAR_VAL", + "isSecret": true + }, + "INGRESS_TLS_SECRET_NAME": { + "value": "QA_INGRESS_TLS_SECRET_NAME_VAR_VAL" + }, + "ACR_SERVER": { + "value": "QA_ACR_SERVER_VAR_VAL" + }, + "ACR_NAME": { + "value": "QA_ACR_NAME_VAR_VAL" + }, + "GATEWAY_SUBNET_PREFIX": { + "value": "QA_GATEWAY_SUBNET_PREFIX_VAR_VAL" + }, + "CLUSTER_SUBNET_PREFIX": { + "value": "QA_CLUSTER_SUBNET_PREFIX_VAR_VAL" + } + }, + "variableGroups": [], + "preDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 79 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 1 + } + }, + "deployStep": { + "id": 84 + }, + "postDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 87 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 2 + } + }, + "deployPhases": [ + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [] + }, + "queueId": AZURE_DEVOPS_INGESTION_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 1, + "phaseType": 1, + "name": "Agent job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "helm upgrade ingestion qa", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend-qa", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)-qa", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),ingress.hosts[0].name=$(EXTERNAL_INGEST_FQDN),ingress.hosts[0].serviceName=$(SERVICE_NAME),ingress.hosts[0].tls=true,ingress.hosts[0].tlsSecretName=$(INGRESS_TLS_SECRET_NAME),ingress.tls.secrets[0].name=$(INGRESS_TLS_SECRET_NAME),ingress.tls.secrets[0].key=\"$(INGRESS_TLS_SECRET_KEY)\",ingress.tls.secrets[0].certificate=\"$(INGRESS_TLS_SECRET_CERT)\",networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",networkPolicy.ingress.externalSubnet.enabled=true,networkPolicy.ingress.externalSubnet.subnetPrefix=\"$(GATEWAY_SUBNET_PREFIX)\",secrets.appinsights.ikey=$(AI_IKEY),secrets.queue.keyname=IngestionServiceAccessKey,secrets.queue.keyvalue=$(INGESTION_ACCESS_KEY_VALUE),secrets.queue.name=$(INGESTION_QUEUE_NAME),secrets.queue.namespace=$(INGESTION_QUEUE_NAMESPACE),reason=\"$(REASON)\",envs.qa=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + } + ], + "environmentOptions": { + "emailNotificationType": "OnlyOnFailure", + "emailRecipients": "release.environment.owner;release.creator", + "skipArtifactsDownload": false, + "timeoutInMinutes": 0, + "enableAccessToken": false, + "publishDeploymentStatus": true, + "badgeEnabled": false, + "autoLinkWorkItems": false, + "pullRequestDeploymentEnabled": false + }, + "demands": [], + "conditions": [ + { + "name": "dev", + "conditionType": 2, + "value": "4" + } + ], + "executionPolicy": { + "concurrencyCount": 1, + "queueDepthCount": 0 + }, + "schedules": [], + "retentionPolicy": { + "daysToKeep": 30, + "releasesToKeep": 3, + "retainBuild": true + }, + "processParameters": { + "inputs": [ + { + "aliases": [], + "options": { + "Azure Resource Manager": "Azure Resource Manager", + "Kubernetes Service Connection": "Kubernetes Service Connection" + }, + "properties": { + "EditableOptions": "false" + }, + "name": "connectionType", + "label": "Connection Type.", + "defaultValue": "Azure Resource Manager", + "type": "pickList", + "helpMarkDown": "", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "azureSubscriptionEndpoint", + "label": "Azure subscription", + "defaultValue": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "required": true, + "type": "connectedService:AzureRM", + "helpMarkDown": "Select an Azure subscription, which has your Azure Container Registry.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "azureResourceGroup", + "label": "Resource group", + "defaultValue": "RESOURCE_GROUP_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Resource Group.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "kubernetesCluster", + "label": "Kubernetes cluster", + "defaultValue": "CLUSTER_NAME_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Kubernetes Service cluster.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "kubernetesServiceEndpoint", + "label": "Kubernetes Service Connection", + "defaultValue": "", + "required": true, + "type": "connectedService:kubernetes", + "helpMarkDown": "Select a Kubernetes service connection.", + "visibleRule": "connectionType = Kubernetes Service Connection", + "groupName": "" + } + ], + "dataSourceBindings": [ + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "kubernetesCluster", + "resultTemplate": "{{{name}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + }, + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "azureResourceGroup", + "resultTemplate": "{{{ #extractResource id resourcegroups}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + } + ] + }, + "properties": {}, + "preDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "postDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "environmentTriggers": [], + "badgeUrl": null + }, + { + "id": 28, + "name": "staging", + "rank": 3, + "owner": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "variables": { + "AI_IKEY": { + "value": "STAGING_AI_IKEY_VAR_VAL", + "isSecret": true + }, + "EXTERNAL_INGEST_FQDN": { + "value": "STAGING_EXTERNAL_INGEST_FQDN_VAR_VAL" + }, + "INGESTION_ACCESS_KEY_VALUE": { + "value": "STAGING_INGESTION_ACCESS_KEY_VALUE_VAR_VAL", + "isSecret": true + }, + "INGESTION_QUEUE_NAME": { + "value": "STAGING_INGESTION_QUEUE_NAME_VAR_VAL" + }, + "INGESTION_QUEUE_NAMESPACE": { + "value": "STAGING_INGESTION_QUEUE_NAMESPACE_VAR_VAL" + }, + "INGRESS_TLS_SECRET_CERT": { + "value": "STAGING_INGRESS_TLS_SECRET_CERT_VAR_VAL" + }, + "INGRESS_TLS_SECRET_KEY": { + "value": "STAGING_INGRESS_TLS_SECRET_KEY_VAR_VAL", + "isSecret": true + }, + "INGRESS_TLS_SECRET_NAME": { + "value": "STAGING_INGRESS_TLS_SECRET_NAME_VAR_VAL" + }, + "ACR_SERVER": { + "value": "STAGING_ACR_SERVER_VAR_VAL" + }, + "ACR_NAME": { + "value": "STAGING_ACR_NAME_VAR_VAL" + }, + "GATEWAY_SUBNET_PREFIX": { + "value": "STAGING_GATEWAY_SUBNET_PREFIX_VAR_VAL" + }, + "CLUSTER_SUBNET_PREFIX": { + "value": "STAGING_CLUSTER_SUBNET_PREFIX_VAR_VAL" + } + }, + "variableGroups": [], + "preDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 80 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 1 + } + }, + "deployStep": { + "id": 83 + }, + "postDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 88 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 2 + } + }, + "deployPhases": [ + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [] + }, + "queueId": AZURE_DEVOPS_INGESTION_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 1, + "phaseType": 1, + "name": "Agent job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "helm upgrade ingestion staging", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend-staging", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)-staging", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),ingress.hosts[0].name=$(EXTERNAL_INGEST_FQDN),ingress.hosts[0].serviceName=$(SERVICE_NAME),ingress.hosts[0].tls=true,ingress.hosts[0].tlsSecretName=$(INGRESS_TLS_SECRET_NAME),ingress.tls.secrets[0].name=$(INGRESS_TLS_SECRET_NAME),ingress.tls.secrets[0].key=\"$(INGRESS_TLS_SECRET_KEY)\",ingress.tls.secrets[0].certificate=\"$(INGRESS_TLS_SECRET_CERT)\",networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",networkPolicy.ingress.externalSubnet.enabled=true,networkPolicy.ingress.externalSubnet.subnetPrefix=\"$(GATEWAY_SUBNET_PREFIX)\",secrets.appinsights.ikey=$(AI_IKEY),secrets.queue.keyname=IngestionServiceAccessKey,secrets.queue.keyvalue=$(INGESTION_ACCESS_KEY_VALUE),secrets.queue.name=$(INGESTION_QUEUE_NAME),secrets.queue.namespace=$(INGESTION_QUEUE_NAMESPACE),reason=\"$(REASON)\",envs.staging=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + } + ], + "environmentOptions": { + "emailNotificationType": "OnlyOnFailure", + "emailRecipients": "release.environment.owner;release.creator", + "skipArtifactsDownload": false, + "timeoutInMinutes": 0, + "enableAccessToken": false, + "publishDeploymentStatus": true, + "badgeEnabled": false, + "autoLinkWorkItems": false, + "pullRequestDeploymentEnabled": false + }, + "demands": [], + "conditions": [ + { + "name": "dev", + "conditionType": 2, + "value": "4" + } + ], + "executionPolicy": { + "concurrencyCount": 1, + "queueDepthCount": 0 + }, + "schedules": [], + "retentionPolicy": { + "daysToKeep": 30, + "releasesToKeep": 3, + "retainBuild": true + }, + "processParameters": { + "inputs": [ + { + "aliases": [], + "options": { + "Azure Resource Manager": "Azure Resource Manager", + "Kubernetes Service Connection": "Kubernetes Service Connection" + }, + "properties": { + "EditableOptions": "false" + }, + "name": "connectionType", + "label": "Connection Type.", + "defaultValue": "Azure Resource Manager", + "type": "pickList", + "helpMarkDown": "", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "azureSubscriptionEndpoint", + "label": "Azure subscription", + "defaultValue": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "required": true, + "type": "connectedService:AzureRM", + "helpMarkDown": "Select an Azure subscription, which has your Azure Container Registry.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "azureResourceGroup", + "label": "Resource group", + "defaultValue": "RESOURCE_GROUP_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Resource Group.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "kubernetesCluster", + "label": "Kubernetes cluster", + "defaultValue": "CLUSTER_NAME_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Kubernetes Service cluster.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "kubernetesServiceEndpoint", + "label": "Kubernetes Service Connection", + "defaultValue": "", + "required": true, + "type": "connectedService:kubernetes", + "helpMarkDown": "Select a Kubernetes service connection.", + "visibleRule": "connectionType = Kubernetes Service Connection", + "groupName": "" + } + ], + "dataSourceBindings": [ + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "kubernetesCluster", + "resultTemplate": "{{{name}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + }, + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "azureResourceGroup", + "resultTemplate": "{{{ #extractResource id resourcegroups}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + } + ] + }, + "properties": {}, + "preDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "postDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "environmentTriggers": [], + "badgeUrl": null + }, + { + "id": 29, + "name": "production", + "rank": 4, + "owner": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "variables": { + "AI_IKEY": { + "value": "PROD_AI_IKEY_VAR_VAL", + "isSecret": true + }, + "EXTERNAL_INGEST_FQDN": { + "value": "PROD_EXTERNAL_INGEST_FQDN_VAR_VAL" + }, + "INGESTION_ACCESS_KEY_VALUE": { + "value": "PROD_INGESTION_ACCESS_KEY_VALUE_VAR_VAL", + "isSecret": true + }, + "INGESTION_QUEUE_NAME": { + "value": "PROD_INGESTION_QUEUE_NAME_VAR_VAL" + }, + "INGESTION_QUEUE_NAMESPACE": { + "value": "PROD_INGESTION_QUEUE_NAMESPACE_VAR_VAL" + }, + "INGRESS_TLS_SECRET_CERT": { + "value": "PROD_INGRESS_TLS_SECRET_CERT_VAR_VAL" + }, + "INGRESS_TLS_SECRET_KEY": { + "value": "PROD_INGRESS_TLS_SECRET_KEY_VAR_VAL", + "isSecret": true + }, + "INGRESS_TLS_SECRET_NAME": { + "value": "PROD_INGRESS_TLS_SECRET_NAME_VAR_VAL" + }, + "SOURCE_ACR_SERVER": { + "value": "SOURCE_ACR_SERVER_VAR_VAL" + }, + "SOURCE_ACR_NAME": { + "value": "SOURCE_ACR_NAME_VAR_VAL" + }, + "ACR_SERVER": { + "value": "PROD_ACR_SERVER_VAR_VAL" + }, + "ACR_NAME": { + "value": "PROD_ACR_NAME_VAR_VAL" + }, + "GATEWAY_SUBNET_PREFIX": { + "value": "PROD_GATEWAY_SUBNET_PREFIX_VAR_VAL" + }, + "CLUSTER_SUBNET_PREFIX": { + "value": "PROD_CLUSTER_SUBNET_PREFIX_VAR_VAL" + } + }, + "variableGroups": [], + "preDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": false, + "isNotificationOn": false, + "approver": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "id": 81 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": true, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 1 + } + }, + "deployStep": { + "id": 82 + }, + "postDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 89 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 2 + } + }, + "deployPhases": [ + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [] + }, + "queueId": AZURE_DEVOPS_INGESTION_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 1, + "phaseType": 1, + "name": "Agent job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "promote ingestion image to production", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr import --name $(ACR_NAME) --source $(SOURCE_ACR_SERVER)/$(REPO_NAME):$(Build.SourceBranchName) --force", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(SOURCE_ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "install new ingestion version in the green slot", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(SOURCE_ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),ingress.hosts[0].name=$(EXTERNAL_INGEST_FQDN),ingress.hosts[0].serviceName=$(SERVICE_NAME),ingress.hosts[0].tls=true,ingress.hosts[0].tlsSecretName=$(INGRESS_TLS_SECRET_NAME),ingress.tls.secrets[0].name=$(INGRESS_TLS_SECRET_NAME),ingress.tls.secrets[0].key=\"$(INGRESS_TLS_SECRET_KEY)\",ingress.tls.secrets[0].certificate=\"$(INGRESS_TLS_SECRET_CERT)\",networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",networkPolicy.ingress.externalSubnet.enabled=true,networkPolicy.ingress.externalSubnet.subnetPrefix=\"$(GATEWAY_SUBNET_PREFIX)\",secrets.appinsights.ikey=$(AI_IKEY),secrets.queue.keyname=IngestionServiceAccessKey,secrets.queue.keyvalue=$(INGESTION_ACCESS_KEY_VALUE),secrets.queue.name=$(INGESTION_QUEUE_NAME),secrets.queue.namespace=$(INGESTION_QUEUE_NAMESPACE),reason=\"$(REASON)\",envs.prod=true,ingestion-prod.experimental=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + }, + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 2, + "phaseType": 2, + "name": "Agentless job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "bcb64569-d51a-4af0-9c01-ea5d05b3b622", + "version": "8.*", + "name": "Swap (blue-green)", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 3600, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "instructions": "consider running some canary or just resume for swapping blue and green versions", + "emailRecipients": "", + "onTimeout": "reject" + } + } + ] + }, + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [] + }, + "queueId": AZURE_DEVOPS_INGESTION_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 3, + "phaseType": 1, + "name": "Agent job (swap)", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "cbc316a2-586f-4def-be79-488a1f503564", + "version": "1.*", + "name": "get current ingestion blue slot version", + "refName": "BlueVersion", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "Azure Resource Manager", + "kubernetesServiceEndpoint": "", + "azureSubscriptionEndpoint": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "azureResourceGroup": "RESOURCE_GROUP_VAR_VAL", + "kubernetesCluster": "CLUSTER_NAME_VAR_VAL", + "useClusterAdmin": "false", + "namespace": "", + "command": "get", + "useConfigurationFile": "false", + "configurationType": "configuration", + "configuration": "", + "inline": "", + "arguments": "-n backend svc/ingestion -o \"jsonpath={.spec.selector['app\\.kubernetes\\.io\\/instance']}\" --ignore-not-found=true", + "secretType": "dockerRegistry", + "secretArguments": "", + "containerRegistryType": "Azure Container Registry", + "dockerRegistryEndpoint": "", + "azureSubscriptionEndpointForSecrets": "", + "azureContainerRegistry": "", + "secretName": "", + "forceUpdate": "true", + "configMapName": "", + "forceUpdateConfigMap": "false", + "useConfigMapFile": "false", + "configMapFile": "", + "configMapArguments": "", + "versionOrLocation": "version", + "versionSpec": "1.12.4", + "checkLatest": "false", + "specifyLocation": "", + "cwd": "$(System.DefaultWorkingDirectory)", + "outputFormat": "" + } + }, + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(SOURCE_ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "cbc316a2-586f-4def-be79-488a1f503564", + "version": "1.*", + "name": "swap blue ingestion version to green slot", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "Azure Resource Manager", + "kubernetesServiceEndpoint": "", + "azureSubscriptionEndpoint": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "azureResourceGroup": "RESOURCE_GROUP_VAR_VAL", + "kubernetesCluster": "CLUSTER_NAME_VAR_VAL", + "useClusterAdmin": "false", + "namespace": "", + "command": "set", + "useConfigurationFile": "false", + "configurationType": "configuration", + "configuration": "", + "inline": "", + "arguments": "selector -n backend svc/ingestion-experimental app.kubernetes.io/name=ingestion,app.kubernetes.io/instance=$(BlueVersion.KubectlOutput)", + "secretType": "dockerRegistry", + "secretArguments": "", + "containerRegistryType": "Azure Container Registry", + "dockerRegistryEndpoint": "", + "azureSubscriptionEndpointForSecrets": "", + "azureContainerRegistry": "", + "secretName": "", + "forceUpdate": "true", + "configMapName": "", + "forceUpdateConfigMap": "false", + "useConfigMapFile": "false", + "configMapFile": "", + "configMapArguments": "", + "versionOrLocation": "version", + "versionSpec": "1.12.4", + "checkLatest": "false", + "specifyLocation": "", + "cwd": "$(System.DefaultWorkingDirectory)", + "outputFormat": "json" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "install new ingestion version in the blue slot", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(SOURCE_ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),ingress.hosts[0].name=$(EXTERNAL_INGEST_FQDN),ingress.hosts[0].serviceName=$(SERVICE_NAME),ingress.hosts[0].tls=true,ingress.hosts[0].tlsSecretName=$(INGRESS_TLS_SECRET_NAME),ingress.tls.secrets[0].name=$(INGRESS_TLS_SECRET_NAME),ingress.tls.secrets[0].key=\"$(INGRESS_TLS_SECRET_KEY)\",ingress.tls.secrets[0].certificate=\"$(INGRESS_TLS_SECRET_CERT)\",networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",networkPolicy.ingress.externalSubnet.enabled=true,networkPolicy.ingress.externalSubnet.subnetPrefix=\"$(GATEWAY_SUBNET_PREFIX)\",secrets.appinsights.ikey=$(AI_IKEY),secrets.queue.keyname=IngestionServiceAccessKey,secrets.queue.keyvalue=$(INGESTION_ACCESS_KEY_VALUE),secrets.queue.name=$(INGESTION_QUEUE_NAME),secrets.queue.namespace=$(INGESTION_QUEUE_NAMESPACE),reason=\"$(REASON)\",envs.prod=true,current=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + } + ], + "environmentOptions": { + "emailNotificationType": "OnlyOnFailure", + "emailRecipients": "release.environment.owner;release.creator", + "skipArtifactsDownload": false, + "timeoutInMinutes": 0, + "enableAccessToken": false, + "publishDeploymentStatus": true, + "badgeEnabled": false, + "autoLinkWorkItems": false, + "pullRequestDeploymentEnabled": false + }, + "demands": [], + "conditions": [ + { + "name": "QA", + "conditionType": 2, + "value": "4" + }, + { + "name": "staging", + "conditionType": 2, + "value": "4" + } + ], + "executionPolicy": { + "concurrencyCount": 1, + "queueDepthCount": 0 + }, + "schedules": [], + "retentionPolicy": { + "daysToKeep": 30, + "releasesToKeep": 3, + "retainBuild": true + }, + "processParameters": { + "inputs": [ + { + "aliases": [], + "options": { + "Azure Resource Manager": "Azure Resource Manager", + "Kubernetes Service Connection": "Kubernetes Service Connection" + }, + "properties": { + "EditableOptions": "false" + }, + "name": "connectionType", + "label": "Connection Type.", + "defaultValue": "Azure Resource Manager", + "type": "pickList", + "helpMarkDown": "", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "azureSubscriptionEndpoint", + "label": "Azure subscription", + "defaultValue": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "required": true, + "type": "connectedService:AzureRM", + "helpMarkDown": "Select an Azure subscription, which has your Azure Container Registry.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "azureResourceGroup", + "label": "Resource group", + "defaultValue": "RESOURCE_GROUP_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Resource Group.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "kubernetesCluster", + "label": "Kubernetes cluster", + "defaultValue": "CLUSTER_NAME_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Kubernetes Service cluster.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "kubernetesServiceEndpoint", + "label": "Kubernetes Service Connection", + "defaultValue": "", + "required": true, + "type": "connectedService:kubernetes", + "helpMarkDown": "Select a Kubernetes service connection.", + "visibleRule": "connectionType = Kubernetes Service Connection", + "groupName": "" + } + ], + "dataSourceBindings": [ + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "kubernetesCluster", + "resultTemplate": "{{{name}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + }, + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "azureResourceGroup", + "resultTemplate": "{{{ #extractResource id resourcegroups}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + } + ] + }, + "properties": {}, + "preDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "postDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "environmentTriggers": [], + "badgeUrl": null + } + ], + "artifacts": [ + { + "sourceId": "AZURE_DEVOPS_PROJECT_ID_VAR_VAL:AZURE_DEVOPS_INGESTION_BUILD_ID_VAR_VAL", + "type": "Build", + "alias": "ci-ingestion", + "definitionReference": { + "artifactSourceDefinitionUrl": { + "id": "", + "name": "" + }, + "defaultVersionBranch": { + "id": "", + "name": "" + }, + "defaultVersionSpecific": { + "id": "", + "name": "" + }, + "defaultVersionTags": { + "id": "", + "name": "" + }, + "defaultVersionType": { + "id": "selectDuringReleaseCreationType", + "name": "Specify at the time of release creation" + }, + "definition": { + "id": "AZURE_DEVOPS_INGESTION_BUILD_ID_VAR_VAL", + "name": "aks-ri-ci-ingestion" + }, + "definitions": { + "id": "", + "name": "" + }, + "IsMultiDefinitionType": { + "id": "False", + "name": "False" + }, + "project": { + "id": "AZURE_DEVOPS_PROJECT_ID_VAR_VAL", + "name": "roadmap" + }, + "repository": { + "id": "AZURE_DEVOPS_REPOS_ID_VAR_VALL", + "name": "roadmap" + } + }, + "isPrimary": true, + "isRetained": false + } + ], + "triggers": [ + { + "artifactAlias": "ci-ingestion", + "triggerConditions": [ + { + "sourceBranch": "release/$(REPO_NAME)/v*", + "tags": [], + "useBuildDefinitionBranch": false, + "createReleaseOnBuildTagging": false + } + ], + "triggerType": 1 + } + ], + "releaseNameFormat": "release-$(rev:r)", + "tags": [], + "pipelineProcess": { + "type": 1 + }, + "properties": { + "DefinitionCreationSource": { + "$type": "System.String", + "$value": "ReleaseImport" + } + }, + "id": 15, + "name": "ingestion-cd", + "path": null, + "projectReference": null, + "url": null +} diff --git a/src/shipping/ingestion/azure-pipelines.yml b/src/shipping/ingestion/azure-pipelines.yml new file mode 100644 index 00000000..28fb672c --- /dev/null +++ b/src/shipping/ingestion/azure-pipelines.yml @@ -0,0 +1,158 @@ +variables: + repositoryName: ingestion + chartPath: charts/ingestion + dockerFileName: src/shipping/ingestion/Dockerfile + imageName: $(repositoryName):$(Build.SourceBranchName) + azureSubscription: AZURE_PIPELINES_SERVICE_CONN_NAME_VAR_VAL + azureContainerRegistry: ACR_SERVER_VAR_VAL + azureContainerRegistryName: ACR_NAME_VAR_VAL + +name: $(build.sourceBranch)-$(Date:yyyyMMdd)$(Rev:.rr) + +pr: # only valid for GitHub. Using Azure repo it must be configure as a Branch Policy + paths: + include: + - /src/shipping/ingestion/ + + branches: + include: + - master + - release/ingestion/v* # for bug fixes + +trigger: + batch: true + branches: + include: + # for new release to production: release flow strategy + - release/ingestion/v* + - refs/relelase/ingestion/v* + - master + - feature/ingestion/* + - topic/ingestion/* + paths: + include: + - /src/shipping/ingestion/ + +resources: +- repo: self + +jobs: + +# CI +- job: ingestionjobci + displayName: "ingestion CI" + pool: + vmImage: 'Ubuntu 16.04' + timeoutInMinutes: 90 + variables: + fullCI: $[ startsWith(variables['build.sourceBranch'], 'refs/heads/release/ingestion/v') ] + buildImage: $[ eq(variables['build.sourceBranch'], 'refs/heads/master') ] + steps: + - task: Docker@1 + displayName: 'Build testrunner image' + inputs: + azureSubscriptionEndpoint: $(azureSubscription) + + azureContainerRegistry: $(azureContainerRegistry) + + arguments: '--pull --target testrunner' + + dockerFile: $(System.DefaultWorkingDirectory)/$(dockerFileName) + + imageName: '$(imageName)-test' + + - task: Docker@1 + displayName: 'Run tests' + inputs: + azureSubscriptionEndpoint: $(azureSubscription) + + azureContainerRegistry: $(azureContainerRegistry) + + command: 'run' + + containerName: testrunner + + volumes: '$(System.DefaultWorkingDirectory)/TestResults:/usr/src/app/target/surefire-reports/' + + imageName: '$(imageName)-test' + + runInBackground: false + + - task: PublishTestResults@2 + displayName: 'Publish test results' + inputs: + testResultsFormat: 'JUnit' # Options: JUnit, NUnit, VSTest, xUnit + + testResultsFiles: 'TestResults/TEST*.xml' + + searchFolder: '$(System.DefaultWorkingDirectory)' + + publishRunAttachments: true + + - task: Docker@1 + condition: or(eq(variables['buildImage'],True),eq(variables['fullCI'],True)) + displayName: 'Build runtime image' + inputs: + + azureSubscriptionEndpoint: $(azureSubscription) + + azureContainerRegistry: $(azureContainerRegistry) + + dockerFile: $(System.DefaultWorkingDirectory)/$(dockerFileName) + + includeLatestTag: false + + imageName: '$(imageName)' + + - task: Docker@1 + condition: eq(variables['fullCI'],True) + displayName: 'Push runtime image' + inputs: + azureSubscriptionEndpoint: $(azureSubscription) + + azureContainerRegistry: $(azureContainerRegistry) + + command: 'Push an image' + + imageName: '$(imageName)' + + includeSourceTags: false + + - task: HelmInstaller@0 + condition: eq(variables['fullCI'],True) + displayName: 'Install Helm 3.0.3' + inputs: + helmVersion: 3.0.3 + + checkLatestHelmVersion: false + + kubectlVersion: 1.12.4 + + checkLatestKubectl: false + + - task: HelmDeploy@0 + condition: eq(variables['fullCI'],True) + displayName: 'helm package' + inputs: + command: package + + chartPath: $(chartPath) + + chartVersion: $(Build.SourceBranchName) + + updateDependency: true + + save: false + + arguments: '--app-version $(Build.SourceBranchName)' + + - task: AzureCLI@1 + condition: eq(variables['fullCI'],True) + displayName: 'Push a helm package' + inputs: + azureSubscription: $(azureSubscription) + + scriptLocation: inlineScript + + inlineScript: | + az acr helm push $(System.ArtifactsDirectory)/$(repositoryName)-$(Build.SourceBranchName).tgz --name $(azureContainerRegistryName) --force; diff --git a/src/shipping/ingestion/etc/AI-Agent.xml b/src/shipping/ingestion/etc/AI-Agent.xml new file mode 100755 index 00000000..53acb593 --- /dev/null +++ b/src/shipping/ingestion/etc/AI-Agent.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/bc-shipping/ingestion/pom.xml b/src/shipping/ingestion/pom.xml similarity index 52% rename from src/bc-shipping/ingestion/pom.xml rename to src/shipping/ingestion/pom.xml index 7c33cbf8..559169c2 100644 --- a/src/bc-shipping/ingestion/pom.xml +++ b/src/shipping/ingestion/pom.xml @@ -3,19 +3,18 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - DroneDelivery - Ingestion - 1.0.0 + com.fabrikam.dronedelivery + ingestion + 0.1.0 jar Ingestion - Ingestion Service + Fabrikam Drone Delivery Ingestion Service org.springframework.boot spring-boot-starter-parent - 1.5.2.RELEASE - + 2.1.2.RELEASE @@ -24,19 +23,13 @@ 1.8 1.16.10 2.7.5 - 1.4.1.RELEASE - 1.1.3 - 1.7.12 + 2.1.2.RELEASE org.springframework.boot - spring-boot-starter-data-rest - - - org.springframework.boot - spring-boot-starter-web-services + spring-boot-starter-web @@ -44,57 +37,30 @@ spring-boot-starter-test test - - - - - - - - - - com.google.common.html.types - types - 1.0.5 - - org.mockito - mockito-core + org.mockito + mockito-core + 2.13.0 + test - + junit junit - - com.microsoft.azure - azure-storage - 5.0.0 - - - javax.ejb - ejb-api - 3.0 - - - org.apache.openejb - openejb-core - 4.7.4 - - org.springframework.boot spring-boot-starter-actuator - + io.springfox springfox-swagger2 2.7.0 - - + + io.springfox springfox-swagger-ui @@ -103,39 +69,34 @@ com.microsoft.azure - azure - 1.0.0 - - - com.microsoft.azure - azure-core - 0.9.7 + azure-servicebus + 1.2.8 + + + com.microsoft.azure + applicationinsights-spring-boot-starter + 1.1.1 + + + + com.microsoft.azure + applicationinsights-web + 2.3.0 + + com.microsoft.azure - azure-eventhubs - 0.13.1 - - - org.springframework.boot - spring-boot-starter-batch - - - org.springframework.boot - spring-boot-configuration-processor - true - - - org.apache.logging.log4j - log4j-api - 2.9.1 + applicationinsights-logging-logback + 2.3.0 - - org.apache.logging.log4j - log4j-core - 2.9.1 - - + + + org.bouncycastle + bcprov-jdk15on + 1.61 + + @@ -143,7 +104,27 @@ org.springframework.boot spring-boot-maven-plugin - + + + org.apache.maven.plugins + maven-failsafe-plugin + + + integration-test + + integration-test + + + + verify + + verify + + + + + + org.apache.maven.plugins maven-dependency-plugin @@ -162,6 +143,4 @@ - - diff --git a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/IngestionApplication.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/IngestionApplication.java similarity index 100% rename from src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/IngestionApplication.java rename to src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/IngestionApplication.java diff --git a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/configuration/ApplicationProperties.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/configuration/ApplicationProperties.java similarity index 72% rename from src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/configuration/ApplicationProperties.java rename to src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/configuration/ApplicationProperties.java index f8b46a6b..ac59bb19 100644 --- a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/configuration/ApplicationProperties.java +++ b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/configuration/ApplicationProperties.java @@ -1,8 +1,5 @@ package com.fabrikam.dronedelivery.ingestion.configuration; -import java.util.ArrayList; -import java.util.List; - import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; @@ -25,14 +22,14 @@ public class ApplicationProperties { // the properties are overriden by values // in application.properties - // Eventhub properties - private String namespace = "eventhubNamespace"; - private String eventHubName = "eventHubName"; + // Queue properties + private String namespace = "queueNamespace"; + private String queueName = "queueName"; private String sasKeyName = "sasKeyName"; private String sasKey = "sasKey"; - private String envNameSpace = "ENV_HUB_NS"; - private String envHubName = "ENV_HUB_NAME"; + private String envNameSpace = "ENV_QUEUE_NS"; + private String envQueueName = "ENV_QUEUE_NAME"; private String envsasKeyName = "ENV_KEY_NAME"; private String envsasKey = "ENV_KEY_VALUE"; @@ -41,12 +38,6 @@ public class ApplicationProperties { private int threadPoolExecutorQueueSize = 0; private int threadPoolExecutorMaxPoolSize = 0; private int messageAmqpClientPoolSize = 0; - - // Istio properties for distributed tracing - private List serviceMeshHeaders = new ArrayList(); - - // Correlation header for breadcrumb trail - private String serviceMeshCorrelationHeader = "serviceMeshCorrelationHeader"; public String getNamespace() { return namespace; @@ -56,12 +47,12 @@ public void setNamespace(String nameSpace) { this.namespace = nameSpace; } - public String getEventHubName() { - return eventHubName; + public String getQueueName() { + return queueName; } - public void setEventHubName(String eventHubName) { - this.eventHubName = eventHubName; + public void setQueueName(String queueName) { + this.queueName = queueName; } public String getSasKeyName() { @@ -103,29 +94,13 @@ public int getThreadPoolExecutorMaxPoolSize() { public void setThreadPoolExecutorMaxPoolSize(int maxPoolSize) { this.threadPoolExecutorMaxPoolSize = maxPoolSize; } - - public List getServiceMeshHeaders() { - return serviceMeshHeaders; - } - - public void setServiceMeshHeaders(List serviceMeshHeaders) { - this.serviceMeshHeaders = serviceMeshHeaders; - } - + //To resolve ${} in @Value @Bean public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() { return new PropertySourcesPlaceholderConfigurer(); } - public String getServiceMeshCorrelationHeader() { - return serviceMeshCorrelationHeader; - } - - public void setServiceMeshCorrelationHeader(String serviceMeshCorrelationHeader) { - this.serviceMeshCorrelationHeader = serviceMeshCorrelationHeader; - } - public int getMessageAmqpClientPoolSize() { return messageAmqpClientPoolSize; } @@ -142,12 +117,12 @@ public void setEnvNameSpace(String envNameSpace) { this.envNameSpace = envNameSpace; } - public String getEnvHubName() { - return envHubName; + public String getEnvQueueName() { + return envQueueName; } - public void setEnvHubName(String envHubName) { - this.envHubName = envHubName; + public void setEnvQueueName(String envQueueName) { + this.envQueueName = envQueueName; } public String getEnvsasKeyName() { diff --git a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/configuration/AsyncConfiguration.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/configuration/AsyncConfiguration.java similarity index 100% rename from src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/configuration/AsyncConfiguration.java rename to src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/configuration/AsyncConfiguration.java diff --git a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/configuration/AsyncExceptionHandler.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/configuration/AsyncExceptionHandler.java similarity index 76% rename from src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/configuration/AsyncExceptionHandler.java rename to src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/configuration/AsyncExceptionHandler.java index e7ef22a1..a5fc4ec6 100644 --- a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/configuration/AsyncExceptionHandler.java +++ b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/configuration/AsyncExceptionHandler.java @@ -1,14 +1,14 @@ package com.fabrikam.dronedelivery.ingestion.configuration; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import java.lang.reflect.Method; public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler { - private final static Logger log = LogManager.getLogger(AsyncExceptionHandler.class); + private final static Logger log = LoggerFactory.getLogger(AsyncExceptionHandler.class); @Override public void handleUncaughtException(final Throwable throwable, final Method method, final Object... obj) { diff --git a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/controller/IngestionController.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/controller/IngestionController.java similarity index 81% rename from src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/controller/IngestionController.java rename to src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/controller/IngestionController.java index a06b3e46..189372d7 100644 --- a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/controller/IngestionController.java +++ b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/controller/IngestionController.java @@ -2,7 +2,6 @@ import org.springframework.web.bind.annotation.RestController; -import com.fabrikam.dronedelivery.ingestion.configuration.ApplicationProperties; import com.fabrikam.dronedelivery.ingestion.models.*; import com.fabrikam.dronedelivery.ingestion.service.*; @@ -11,8 +10,10 @@ import javax.servlet.http.HttpServletResponse; -import org.apache.logging.log4j.CloseableThreadContext; -import org.apache.logging.log4j.LogManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -27,28 +28,21 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; -import com.microsoft.azure.servicebus.ServiceBusException; +import com.microsoft.azure.servicebus.primitives.ServiceBusException; import java.io.IOException; import com.fasterxml.jackson.core.JsonProcessingException; -import org.apache.logging.log4j.Logger; @RestController public class IngestionController { - private final static Logger log = LogManager.getLogger(IngestionController.class); - - private static final String CorrelationHeaderTag = "CorrelationId"; + private final static Logger log = LoggerFactory.getLogger(IngestionController.class); - @Autowired - private ApplicationProperties appProps; - @Autowired private Ingestion ingestion; @Autowired - public IngestionController(Ingestion ingestion, ApplicationProperties appProps) { + public IngestionController(Ingestion ingestion) { this.ingestion = ingestion; - this.appProps = appProps; } @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Bad Format data") // 400 @@ -86,12 +80,11 @@ public void exHandlerJsonError(JsonProcessingException e) { public CompletableFuture> scheduleDeliveryAsync(HttpServletResponse response, @RequestBody ExternalDelivery externalDelivery, @RequestHeader HttpHeaders httpHeaders) { - // Extract the correlation id and log it - String correlationId = httpHeaders.getFirst(appProps.getServiceMeshCorrelationHeader()); String deliveryId = UUID.randomUUID().toString(); - - try (final CloseableThreadContext.Instance ctc = CloseableThreadContext.put(CorrelationHeaderTag, - correlationId).put("DeliveryId", deliveryId)) { + + try { + MDC.put("DeliveryId", deliveryId); + log.info("In schedule delivery action with delivery request {}", externalDelivery.toString()); // Exceptions handled by exception handler @@ -103,12 +96,14 @@ public CompletableFuture> scheduleDeliveryAsync externalDelivery.getDeadline()); externalDelivery.setDeliveryId(delivery.getDeliveryId()); - response.setHeader("Content-Type", "application/json; charset=utf-8 "); + response.setHeader("Content-Type", "application/json;charset=utf-8"); response.setHeader("Location", "http://deliveries/api/deliveries/" + delivery.getDeliveryId()); // Extract the headers as a map and pass on to eventhub message // dumper ingestion.scheduleDeliveryAsync(delivery, httpHeaders.toSingleValueMap()); + } finally { + MDC.remove("DeliveryId"); } return CompletableFuture.completedFuture(new ResponseEntity<>(externalDelivery, HttpStatus.ACCEPTED)); @@ -122,13 +117,15 @@ public CompletableFuture> cancelDeliveryAsync(HttpServlet // Exceptions handled by exception handler // making standard in the controller - String correlationId = httpHeaders.getFirst(appProps.getServiceMeshCorrelationHeader()); - try (final CloseableThreadContext.Instance ctc = CloseableThreadContext.put(CorrelationHeaderTag, correlationId)) - { + try { + MDC.put("DeliveryId", deliveryId); + log.info("In cancel delivery action with id: {}", deliveryId); ingestion.cancelDeliveryAsync(deliveryId.toString(), httpHeaders.toSingleValueMap()); + } finally { + MDC.remove("DeliveryId"); } - + return CompletableFuture.completedFuture(new ResponseEntity<>(deliveryId, HttpStatus.NO_CONTENT)); } @@ -136,10 +133,8 @@ public CompletableFuture> cancelDeliveryAsync(HttpServlet @ResponseBody public CompletableFuture> rescheduleDeliveryAsync(HttpServletResponse response, @RequestBody ExternalRescheduledDelivery externalRescheduledDelivery, @PathVariable("id") String deliveryId, @RequestHeader HttpHeaders httpHeaders) { - - String correlationId = httpHeaders.getFirst(appProps.getServiceMeshCorrelationHeader()); - try (final CloseableThreadContext.Instance ctc = CloseableThreadContext.put(CorrelationHeaderTag, correlationId) - .put("DeliveryId", deliveryId)) { + try { + MDC.put("DeliveryId", deliveryId); log.info("In reschedule delivery action with delivery request: {}", externalRescheduledDelivery.toString()); @@ -153,6 +148,8 @@ public CompletableFuture> rescheduleDeliveryAsync(HttpSer externalRescheduledDelivery.getDeadline(), externalRescheduledDelivery.getPickupTime()); ingestion.rescheduleDeliveryAsync(rescheduledDelivery, httpHeaders.toSingleValueMap()); + } finally { + MDC.remove("DeliveryId"); } return CompletableFuture.completedFuture(new ResponseEntity<>(deliveryId, HttpStatus.OK)); @@ -165,4 +162,4 @@ public CompletableFuture> probeDeliveryAsync() { //implement logic to test readiness return CompletableFuture.completedFuture(new ResponseEntity<>(HttpStatus.OK)); } -} +} \ No newline at end of file diff --git a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/ConfirmationRequired.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/ConfirmationRequired.java similarity index 100% rename from src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/ConfirmationRequired.java rename to src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/ConfirmationRequired.java diff --git a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/ContainerSize.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/ContainerSize.java similarity index 100% rename from src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/ContainerSize.java rename to src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/ContainerSize.java diff --git a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/Delivery.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/Delivery.java similarity index 100% rename from src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/Delivery.java rename to src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/Delivery.java diff --git a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/DeliveryBase.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/DeliveryBase.java similarity index 100% rename from src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/DeliveryBase.java rename to src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/DeliveryBase.java diff --git a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/ExternalDelivery.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/ExternalDelivery.java similarity index 100% rename from src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/ExternalDelivery.java rename to src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/ExternalDelivery.java diff --git a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/ExternalRescheduledDelivery.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/ExternalRescheduledDelivery.java similarity index 100% rename from src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/ExternalRescheduledDelivery.java rename to src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/ExternalRescheduledDelivery.java diff --git a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/PackageInfo.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/PackageInfo.java similarity index 100% rename from src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/PackageInfo.java rename to src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/PackageInfo.java diff --git a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/RescheduledDelivery.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/RescheduledDelivery.java similarity index 100% rename from src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/RescheduledDelivery.java rename to src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/models/RescheduledDelivery.java diff --git a/src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/service/Ingestion.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/service/Ingestion.java similarity index 100% rename from src/bc-shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/service/Ingestion.java rename to src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/service/Ingestion.java diff --git a/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/service/IngestionImpl.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/service/IngestionImpl.java new file mode 100644 index 00000000..94f57e11 --- /dev/null +++ b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/service/IngestionImpl.java @@ -0,0 +1,69 @@ +package com.fabrikam.dronedelivery.ingestion.service; + +import com.fabrikam.dronedelivery.ingestion.models.*; + +import java.net.URISyntaxException; +import java.nio.charset.Charset; +import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import com.fabrikam.dronedelivery.ingestion.util.ClientPool; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.microsoft.azure.servicebus.Message; +import com.microsoft.azure.servicebus.primitives.ServiceBusException; + +@Service +public class IngestionImpl implements Ingestion { + + private ClientPool clientPool; + + @Autowired + public IngestionImpl(ClientPool clientPool) { + this.clientPool = clientPool; + } + + @Async + @Override + public void scheduleDeliveryAsync(DeliveryBase delivery, Map httpHeaders) { + Message sendEvent = getMessage(delivery, httpHeaders); + sendEvent.getProperties().put("operation", "delivery"); + this.enqueuMessageAsync(sendEvent); + } + + @Async + @Override + public void cancelDeliveryAsync(String deliveryId, Map httpHeaders) { + Message sendEvent = getMessage(deliveryId, httpHeaders); + + sendEvent.getProperties().put("operation", "cancel"); + this.enqueuMessageAsync(sendEvent); + } + + @Async + @Override + public void rescheduleDeliveryAsync(DeliveryBase rescheduledDelivery, Map httpHeaders) { + Message sendEvent = getMessage(rescheduledDelivery, httpHeaders); + sendEvent.getProperties().put("operation", "reschedule"); + this.enqueuMessageAsync(sendEvent); + } + + private Message getMessage(Object deliveryObj, Map httpHeaders) { + Gson gson = new GsonBuilder().create(); + byte[] payloadBytes = gson.toJson(deliveryObj).getBytes(Charset.defaultCharset()); + Message sendEvent = new Message(payloadBytes); + + return sendEvent; + } + + @Async + private void enqueuMessageAsync(Message message) { + try { + this.clientPool.getConnection().sendAsync(message).thenApply((Void) -> "result"); + } catch (InterruptedException | ServiceBusException | URISyntaxException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/ClientPool.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/ClientPool.java new file mode 100644 index 00000000..4dfddec3 --- /dev/null +++ b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/ClientPool.java @@ -0,0 +1,13 @@ +package com.fabrikam.dronedelivery.ingestion.util; + +import org.springframework.scheduling.annotation.Async; + +import java.net.URISyntaxException; + +import com.microsoft.azure.servicebus.primitives.ServiceBusException; + +public interface ClientPool { + + @Async + public InstrumentedQueueClient getConnection() throws InterruptedException, ServiceBusException, URISyntaxException; +} diff --git a/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/ClientPoolImpl.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/ClientPoolImpl.java new file mode 100644 index 00000000..af3a61bb --- /dev/null +++ b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/ClientPoolImpl.java @@ -0,0 +1,68 @@ +package com.fabrikam.dronedelivery.ingestion.util; + +import java.net.URI; +import java.net.URISyntaxException; + +import com.fabrikam.dronedelivery.ingestion.configuration.*; + +import com.microsoft.azure.servicebus.QueueClient; +import com.microsoft.azure.servicebus.ReceiveMode; +import com.microsoft.azure.servicebus.primitives.ConnectionStringBuilder; +import com.microsoft.azure.servicebus.primitives.ServiceBusException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +@Service +public class ClientPoolImpl implements ClientPool { + + private static final String SCHEME = "https"; + + private final InstrumentedQueueClient[] queueClients; + private final String[] queueNames; + private final ApplicationProperties appProperties; + private final String nameSpace; + private final String sasKeyName; + private final String sasKey; + private final ServiceBusTracing tracing; + + @Autowired + public ClientPoolImpl(ApplicationProperties appProps, ServiceBusTracing tracing, Environment environment) { + this.appProperties = appProps; + this.tracing = tracing; + + this.queueNames = environment.getenv(appProperties.getEnvQueueName()).split(","); + nameSpace = environment.getenv(appProperties.getEnvNameSpace()); + sasKeyName = environment.getenv(appProperties.getEnvsasKeyName()); + sasKey = environment.getenv(appProperties.getEnvsasKey()); + + this.queueClients = new InstrumentedQueueClient[this.appProperties.getMessageAmqpClientPoolSize()]; + } + + @Async + @Override + public InstrumentedQueueClient getConnection() + throws InterruptedException, ServiceBusException, URISyntaxException { + + int poolId = (int) (Math.random() * queueClients.length); + int eventHubId = (int) (Math.random() * queueNames.length); + + if (queueClients[poolId] == null) { + ConnectionStringBuilder connectionString = new ConnectionStringBuilder(nameSpace, queueNames[eventHubId], + sasKeyName, sasKey); + + queueClients[poolId] = new InstrumentedQueueClientImpl( + new URI(SCHEME, + connectionString + .getEndpoint() + .getHost(), + "/", + null).toString(), + new QueueClient(connectionString, ReceiveMode.PEEKLOCK), + tracing); + } + + return queueClients[poolId]; + } +} \ No newline at end of file diff --git a/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/Environment.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/Environment.java new file mode 100644 index 00000000..3b167ee6 --- /dev/null +++ b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/Environment.java @@ -0,0 +1,10 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +package com.fabrikam.dronedelivery.ingestion.util; + +public interface Environment { + public String getenv(String name); +} diff --git a/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/EnvironmentImpl.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/EnvironmentImpl.java new file mode 100644 index 00000000..4475df5f --- /dev/null +++ b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/EnvironmentImpl.java @@ -0,0 +1,15 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +package com.fabrikam.dronedelivery.ingestion.util; + +import org.springframework.stereotype.Component; + +@Component +public class EnvironmentImpl implements Environment { + public String getenv(String name) { + return System.getenv(name); + } +} diff --git a/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/InstrumentedQueueClient.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/InstrumentedQueueClient.java new file mode 100644 index 00000000..139493fa --- /dev/null +++ b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/InstrumentedQueueClient.java @@ -0,0 +1,14 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +package com.fabrikam.dronedelivery.ingestion.util; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.azure.servicebus.IMessage; + +public interface InstrumentedQueueClient { + CompletableFuture sendAsync(IMessage message); +} diff --git a/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/InstrumentedQueueClientImpl.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/InstrumentedQueueClientImpl.java new file mode 100644 index 00000000..65e62093 --- /dev/null +++ b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/InstrumentedQueueClientImpl.java @@ -0,0 +1,40 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +package com.fabrikam.dronedelivery.ingestion.util; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.azure.servicebus.IMessage; +import com.microsoft.azure.servicebus.IQueueClient; + +public class InstrumentedQueueClientImpl implements InstrumentedQueueClient { + private final ServiceBusTracing tracing; + private final String endpoint; + private final IQueueClient client; + + public InstrumentedQueueClientImpl( + String endpoint, + IQueueClient client, + ServiceBusTracing tracing) { + this.endpoint = endpoint; + this.client = client; + this.tracing = tracing; + } + + @Override + public CompletableFuture sendAsync(IMessage message) { + String queueName = this.client.getQueueName(); + + return this.tracing.trackAndCorrelateServiceBusDependency( + endpoint, + queueName, + message, + (m) -> + { + return this.client.sendAsync(m); + }); + } +} diff --git a/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/ServiceBusTracing.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/ServiceBusTracing.java new file mode 100644 index 00000000..23ea5653 --- /dev/null +++ b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/ServiceBusTracing.java @@ -0,0 +1,20 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +package com.fabrikam.dronedelivery.ingestion.util; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +import com.microsoft.azure.servicebus.IMessage; + +public interface ServiceBusTracing { + + public CompletableFuture trackAndCorrelateServiceBusDependency( + String endpoint, + String queueName, + IMessage message, + Function> func); +} diff --git a/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/ServiceBusTracingImpl.java b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/ServiceBusTracingImpl.java new file mode 100644 index 00000000..f52f1cf0 --- /dev/null +++ b/src/shipping/ingestion/src/main/java/com/fabrikam/dronedelivery/ingestion/util/ServiceBusTracingImpl.java @@ -0,0 +1,150 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +package com.fabrikam.dronedelivery.ingestion.util; + +import java.util.Locale; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +import com.microsoft.applicationinsights.TelemetryClient; +import com.microsoft.applicationinsights.extensibility.context.OperationContext; +import com.microsoft.applicationinsights.telemetry.Duration; +import com.microsoft.applicationinsights.telemetry.RemoteDependencyTelemetry; +import com.microsoft.applicationinsights.telemetry.RequestTelemetry; +import com.microsoft.applicationinsights.web.internal.ThreadContext; +import com.microsoft.applicationinsights.web.internal.correlation.TelemetryCorrelationUtils; +import com.microsoft.azure.servicebus.IMessage; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class ServiceBusTracingImpl implements ServiceBusTracing { + private static final long NANO_TO_MILI_SECONDS = TimeUnit.MILLISECONDS.toNanos(1); + + private static final String DIAGNOSTIC_ID_PROPERTY_NAME = "Diagnostic-Id"; + + private static final String SERVICE_BUS_REMOTE_DEPENDENCY_TYPE = "Azure Service Bus"; + + private static final String SERVICE_BUS_REMOTE_DEPENDENDENCY_NAME = "Send"; + + private static final String TARGET_REMOTE_DEPENDENDENCY_FORMAT = "%s | %s"; + + private final TelemetryClient telemetryClient; + + @Autowired + public ServiceBusTracingImpl(TelemetryClient telemetryClient) + { + this.telemetryClient = telemetryClient; + } + + @Override + public CompletableFuture trackAndCorrelateServiceBusDependency( + String endpoint, + String queueName, + IMessage message, + Function> func) { + + CompletableFuture result; + + String target = String.format( + Locale.US, + TARGET_REMOTE_DEPENDENDENCY_FORMAT, + endpoint, + queueName); + + propagateCorrelationProperties(message); + + final RemoteDependencyTelemetry remoteDependency = createRemoteDependencyTelemetry( + target); + + final long start = System.nanoTime(); + result = func.apply(message); + result.whenComplete((r,t) -> { + final long finish = System.nanoTime(); + final long intervalMs = (finish - start) / NANO_TO_MILI_SECONDS; + boolean successful = false; + if (t == null) { + successful = true; + } + + remoteDependency.setDuration(new Duration(intervalMs)); + remoteDependency.setSuccess(successful); + + telemetryClient.trackDependency(remoteDependency); + + if(successful == false){ + RuntimeException runtimeEx = new RuntimeException(t); + telemetryClient.trackException(runtimeEx); + } + }); + + return result; + } + + private static void propagateCorrelationProperties(IMessage message) { + String parentId = ThreadContext + .getRequestTelemetryContext() + .getHttpRequestTelemetry() + .getId(); + + // propagate Service Bus required properties + // https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-end-to-end-tracing + // https://docs.microsoft.com/en-us/azure/azure-monitor/app/correlation#telemetry-correlation-in-the-java-sdk + message.getProperties().put(DIAGNOSTIC_ID_PROPERTY_NAME, parentId); + } + + private static RemoteDependencyTelemetry createRemoteDependencyTelemetry( + String target){ + + return createRemoteDependencyTelemetry(target, new Duration(0L), true); + } + + private static RemoteDependencyTelemetry createRemoteDependencyTelemetry( + String target, + Duration duration, + boolean successful) { + String dependencyId = TelemetryCorrelationUtils + .generateChildDependencyId(); + + RemoteDependencyTelemetry dependencyTelemetry = + new RemoteDependencyTelemetry( + SERVICE_BUS_REMOTE_DEPENDENDENCY_NAME, + "", + duration, + successful); + + dependencyTelemetry.setId(dependencyId); + dependencyTelemetry.setType(SERVICE_BUS_REMOTE_DEPENDENCY_TYPE); + dependencyTelemetry.setTarget(target); + + RequestTelemetry requestTelemetry = ThreadContext + .getRequestTelemetryContext() + .getHttpRequestTelemetry(); + + String parentId = requestTelemetry.getId(); + String operationId = extractRootId(parentId); + + OperationContext operationContext = dependencyTelemetry + .getContext() + .getOperation(); + operationContext.setParentId(parentId); + operationContext.setId(operationId); + + return dependencyTelemetry; + } + + private static String extractRootId(String parentId) { + int rootEnd = parentId.indexOf('.'); + if (rootEnd < 0) { + rootEnd = parentId.length(); + } + + int rootStart = parentId.charAt(0) == '|' ? 1 : 0; + return parentId.substring(rootStart, rootEnd); + } +} diff --git a/src/bc-shipping/ingestion/src/main/resources/DeliveryBean.xml b/src/shipping/ingestion/src/main/resources/DeliveryBean.xml similarity index 100% rename from src/bc-shipping/ingestion/src/main/resources/DeliveryBean.xml rename to src/shipping/ingestion/src/main/resources/DeliveryBean.xml diff --git a/src/shipping/ingestion/src/main/resources/application.properties b/src/shipping/ingestion/src/main/resources/application.properties new file mode 100644 index 00000000..9c7cdcb9 --- /dev/null +++ b/src/shipping/ingestion/src/main/resources/application.properties @@ -0,0 +1,22 @@ +############################################### +# add environment variables +# QUEUE_NAMESPACE QUEUE_NAME QUEUE_KEYNAME QUEUE_KEYVALUE +# To debug/run tests. Yaml file will have to provide +# environment va +################################################ +service.threadPoolExecutorQueueSize=10000 +service.threadPoolExecutorPoolSize=100 +service.threadPoolExecutorMaxPoolSize=200 +service.messageAmqpClientPoolSize=100 +service.envNameSpace=QUEUE_NAMESPACE +service.envQueueName=QUEUE_NAME +service.envsasKeyName=QUEUE_KEYNAME +service.envsasKey=QUEUE_KEYVALUE +server.port:80 + +logging.level.org.springframework.web=INFO +# Specify the name of your springboot application. This can be any logical name you would like to give to your app. +spring.application.name=${CONTAINER_NAME} + +# Logging level [all, trace, info, warn, error, off]. Default value: error. +azure.application-insights.logger.level=${APPINSIGHTS_LOGGERLEVEL} \ No newline at end of file diff --git a/src/shipping/ingestion/src/main/resources/logback-spring.xml b/src/shipping/ingestion/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..1ac77874 --- /dev/null +++ b/src/shipping/ingestion/src/main/resources/logback-spring.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/ApplicationPropertiesTest.java b/src/shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/ApplicationPropertiesTest.java new file mode 100644 index 00000000..e0736cca --- /dev/null +++ b/src/shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/ApplicationPropertiesTest.java @@ -0,0 +1,26 @@ +package com.fabrikam.dronedelivery.ingestion; + +import static org.junit.Assert.*; + +import org.junit.Test; +import com.fabrikam.dronedelivery.ingestion.configuration.ApplicationProperties; + +public class ApplicationPropertiesTest { + @Test + public void canSetPropertyValuesUsedByClientPoolImpl_thenGetPropertyValues() { + // Arrange + ApplicationProperties appProps = new ApplicationProperties(); + + // Act + appProps.setEnvQueueName("ENV_QUEUE_NAME_MODIFIED"); + appProps.setEnvNameSpace("ENV_QUEUE_NS_MODIFIED"); + appProps.setEnvsasKeyName("ENV_KEY_NAME_MODIFIED"); + appProps.setEnvsasKey("ENV_KEY_VALUE_MODIFIED"); + + // Assert + assertEquals("ENV_QUEUE_NAME_MODIFIED", appProps.getEnvQueueName()); + assertEquals("ENV_QUEUE_NS_MODIFIED", appProps.getEnvNameSpace()); + assertEquals("ENV_KEY_NAME_MODIFIED", appProps.getEnvsasKeyName()); + assertEquals("ENV_KEY_VALUE_MODIFIED", appProps.getEnvsasKey()); + } +} diff --git a/src/shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/IngestionControllerTest.java b/src/shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/IngestionControllerTest.java new file mode 100644 index 00000000..69165a07 --- /dev/null +++ b/src/shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/IngestionControllerTest.java @@ -0,0 +1,490 @@ +package com.fabrikam.dronedelivery.ingestion; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.mockito.Mockito.times; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import com.microsoft.azure.servicebus.primitives.ServiceBusException; + +import java.util.Date; +import java.util.UUID; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +//import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +import com.fabrikam.dronedelivery.ingestion.configuration.ApplicationProperties; +import com.fabrikam.dronedelivery.ingestion.controller.IngestionController; +import com.fabrikam.dronedelivery.ingestion.models.ConfirmationRequired; +import com.fabrikam.dronedelivery.ingestion.models.ContainerSize; +import com.fabrikam.dronedelivery.ingestion.models.DeliveryBase; +import com.fabrikam.dronedelivery.ingestion.models.ExternalDelivery; +import com.fabrikam.dronedelivery.ingestion.models.ExternalRescheduledDelivery; +import com.fabrikam.dronedelivery.ingestion.models.PackageInfo; +import com.fabrikam.dronedelivery.ingestion.service.IngestionImpl; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; + +import java.io.IOException; + + + +public class IngestionControllerTest { + + private MockMvc mockMvc; + private PackageInfo packageInfo; + private ExternalDelivery externalDelivery; + ExternalRescheduledDelivery externalRDelivery; + + @Mock + private IngestionImpl ingestionimplMock; + + @Mock + private ApplicationProperties appPropsMock; + + @InjectMocks + private IngestionController ingestionController; + + @Before + public void setUp() throws Exception { + + + + MockitoAnnotations.initMocks(this); + mockMvc = MockMvcBuilders + .standaloneSetup(ingestionController) + .build(); + + + packageInfo = new PackageInfo(); + packageInfo.setSize(ContainerSize.Large); + packageInfo.setPackageId(UUID.randomUUID().toString()); + + externalDelivery = new ExternalDelivery(); + externalDelivery.setOwnerId(UUID.randomUUID().toString()); + externalDelivery.setPickupTime(new Date()); + externalDelivery.setDropOffLocation("Austin"); + externalDelivery.setPickupLocation("Texas"); + externalDelivery.setExpedited(false); + externalDelivery.setConfirmationRequired(ConfirmationRequired.FingerPrint); + externalDelivery.setDeadline("LineOfDeadlyZombiatedPeople"); + externalDelivery.setPackageInfo(packageInfo); + + externalRDelivery = new ExternalRescheduledDelivery(); + externalRDelivery.setDeadline("deadline"); + externalRDelivery.setDeliveryId(UUID.randomUUID().toString()); + externalRDelivery.setDropOffLocation("location"); + externalRDelivery.setPickupLocation("location"); + + + + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void ScheduleDeliveryIsAccepted() throws Exception { + + Mockito.doNothing().when(ingestionimplMock) + .scheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + + MvcResult resultActions = mockMvc.perform( + post("/api/deliveryrequests") + .contentType(MediaType.APPLICATION_JSON) + .content(asJsonString(externalDelivery))) + .andExpect(request().asyncStarted()).andReturn(); + + mockMvc.perform(asyncDispatch(resultActions)) + .andExpect(header().string("Content-Type", containsString("application/json"))) + .andExpect(status().isAccepted()); + + Mockito.verify(ingestionimplMock, times(1)) + .scheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + + } + + @Test + public void RescheduleDeliveryIsOk() throws Exception { + + Mockito.doNothing().when(ingestionimplMock) + .rescheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + + String deliveryId = externalRDelivery.getDeliveryId().toString(); + + MvcResult resultActions = mockMvc.perform(patch("/api/deliveryrequests/" + deliveryId) + .content(asJsonString(externalRDelivery)).contentType(MediaType.APPLICATION_JSON)) + .andExpect(request().asyncStarted()).andReturn(); + + mockMvc.perform(asyncDispatch(resultActions)) + .andExpect(header().string("Content-Type", containsString("application/json"))) + .andExpect(status().is2xxSuccessful()); + + Mockito.verify(ingestionimplMock, times(1)) + .rescheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + } + + @Test + public void CancelDeliveryIsOk() throws Exception { + String deliveryId = externalRDelivery.getDeliveryId().toString(); + Mockito.doNothing().when(ingestionimplMock) + .cancelDeliveryAsync(Mockito.anyString(), Mockito.anyMap()); + + + MvcResult resultActions = mockMvc + .perform(delete("/api/deliveryrequests/" + deliveryId).contentType(MediaType.APPLICATION_JSON)) + .andExpect(request().asyncStarted()).andReturn(); + + mockMvc.perform(asyncDispatch(resultActions)) + .andExpect(status().is2xxSuccessful()); + + Mockito.verify(ingestionimplMock, times(1)) + .cancelDeliveryAsync(Mockito.anyString(), Mockito.anyMap()); + } + + + @Test + public void ProbeDeliveryIsOk() throws Exception { + MvcResult resultActions = mockMvc.perform(get("/api/probe/").contentType(MediaType.APPLICATION_JSON)) + .andExpect(request().asyncStarted()).andReturn(); + + mockMvc.perform(asyncDispatch(resultActions)).andExpect(status().is2xxSuccessful()); + } + + + @Test + public void scheduleDeliveryHandlesServiceBusException() throws Exception { + + Mockito.doThrow(new RuntimeException(new ServiceBusException(true,"message"))) + .when(ingestionimplMock) + .scheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + + + mockMvc.perform( + post("/api/deliveryrequests") + .contentType(MediaType.APPLICATION_JSON) + .content(asJsonString(externalDelivery))) + .andExpect(status().is5xxServerError()); + + Mockito.verify(ingestionimplMock, times(1)) + .scheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + } + + @Test + public void schedulerDeliveryHandlesIllegalArgumentException() throws Exception { + + Mockito.doThrow(new RuntimeException(new IllegalArgumentException())) + .when(ingestionimplMock) + .scheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + + + mockMvc.perform( + post("/api/deliveryrequests") + .contentType(MediaType.APPLICATION_JSON) + .content(asJsonString(externalDelivery))) + .andExpect(status().isBadRequest()); + + Mockito.verify(ingestionimplMock, times(1)) + .scheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + } + + @Test + public void scheduleDeliveryHandlesIOException() throws Exception { + + Mockito.doThrow(new RuntimeException(new IOException())) + .when(ingestionimplMock) + .scheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + + + mockMvc.perform( + post("/api/deliveryrequests") + .contentType(MediaType.APPLICATION_JSON) + .content(asJsonString(externalDelivery))) + .andExpect(status().is5xxServerError()); + + Mockito.verify(ingestionimplMock, times(1)) + .scheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + } + + @Test + public void scheduleDeliveryHandlesJsonProcessingException() throws Exception { + + Mockito.doThrow(new RuntimeException(new JsonParseException(null, "error"))) + .when(ingestionimplMock) + .scheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + + mockMvc.perform( + post("/api/deliveryrequests") + .contentType(MediaType.APPLICATION_JSON) + .content(asJsonString(externalDelivery))) + .andExpect(status().is5xxServerError()); + + Mockito.verify(ingestionimplMock, times(1)) + .scheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + } + + + @Test + public void scheduleDeliveryHandlesMethodArgumentTypeMismatchException() throws Exception { + + Mockito.doThrow(new RuntimeException(new MethodArgumentTypeMismatchException(appPropsMock, null, null, null, null))) + .when(ingestionimplMock) + .scheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + + mockMvc.perform( + post("/api/deliveryrequests") + .contentType(MediaType.APPLICATION_JSON) + .content(asJsonString(externalDelivery))) + .andExpect(status().isBadRequest()); + + Mockito.verify(ingestionimplMock, times(1)) + .scheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + } + + + //------------------ + + @Test + public void rescheduleDeliveryHandlesServiceBusException() throws Exception { + + Mockito.doThrow(new RuntimeException(new ServiceBusException(true,"message"))) + .when(ingestionimplMock) + .rescheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + + + String deliveryId = externalRDelivery.getDeliveryId().toString(); + + mockMvc.perform( + patch("/api/deliveryrequests/" + deliveryId) + .contentType(MediaType.APPLICATION_JSON) + .content(asJsonString(externalDelivery))) + .andExpect(status().is5xxServerError()); + + Mockito.verify(ingestionimplMock, times(1)) + .rescheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + } + + @Test + public void reschedulerDeliveryHandlesIllegalArgumentException() throws Exception { + + Mockito.doThrow(new RuntimeException(new IllegalArgumentException())) + .when(ingestionimplMock) + .rescheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + + + String deliveryId = externalRDelivery.getDeliveryId().toString(); + + + mockMvc.perform( + patch("/api/deliveryrequests/" + deliveryId) + .contentType(MediaType.APPLICATION_JSON) + .content(asJsonString(externalDelivery))) + .andExpect(status().isBadRequest()); + + Mockito.verify(ingestionimplMock, times(1)) + .rescheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + } + + @Test + public void rescheduleDeliveryHandlesIOException() throws Exception { + + Mockito.doThrow(new RuntimeException(new IOException())) + .when(ingestionimplMock) + .rescheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + + + String deliveryId = externalRDelivery.getDeliveryId().toString(); + + mockMvc.perform( + patch("/api/deliveryrequests/" + deliveryId) + .contentType(MediaType.APPLICATION_JSON) + .content(asJsonString(externalDelivery))) + .andExpect(status().is5xxServerError()); + + Mockito.verify(ingestionimplMock, times(1)) + .rescheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + } + + @Test + public void rescheduleDeliveryHandlesJsonProcessingException() throws Exception { + + Mockito.doThrow(new RuntimeException(new JsonParseException(null, "error"))) + .when(ingestionimplMock) + .rescheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + + String deliveryId = externalRDelivery.getDeliveryId().toString(); + + mockMvc.perform( + patch("/api/deliveryrequests/" + deliveryId) + .contentType(MediaType.APPLICATION_JSON) + .content(asJsonString(externalDelivery))) + .andExpect(status().is5xxServerError()); + + Mockito.verify(ingestionimplMock, times(1)) + .rescheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + } + + + @Test + public void rescheduleDeliveryHandlesMethodArgumentTypeMismatchException() throws Exception { + + Mockito.doThrow(new RuntimeException(new MethodArgumentTypeMismatchException(appPropsMock, null, null, null, null))) + .when(ingestionimplMock) + .rescheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + + String deliveryId = externalRDelivery.getDeliveryId().toString(); + + mockMvc.perform( + patch("/api/deliveryrequests/" + deliveryId) + .contentType(MediaType.APPLICATION_JSON) + .content(asJsonString(externalDelivery))) + .andExpect(status().isBadRequest()); + + Mockito.verify(ingestionimplMock, times(1)) + .rescheduleDeliveryAsync(Mockito.any(DeliveryBase.class), Mockito.anyMap()); + } + + + //--------------------- + + @Test + public void cancelscheduleDeliveryHandlesServiceBusException() throws Exception { + + Mockito.doThrow(new RuntimeException(new ServiceBusException(true,"message"))) + .when(ingestionimplMock) + .cancelDeliveryAsync(Mockito.anyString(), Mockito.anyMap()); + + + String deliveryId = externalRDelivery.getDeliveryId().toString(); + + mockMvc.perform( + delete("/api/deliveryrequests/" + deliveryId) + .contentType(MediaType.APPLICATION_JSON) + .content(asJsonString(externalDelivery))) + .andExpect(status().is5xxServerError()); + + Mockito.verify(ingestionimplMock, times(1)) + .cancelDeliveryAsync(Mockito.anyString(), Mockito.anyMap()); + } + + @Test + public void cancelschedulerDeliveryHandlesIllegalArgumentException() throws Exception { + + Mockito.doThrow(new RuntimeException(new IllegalArgumentException())) + .when(ingestionimplMock) + .cancelDeliveryAsync(Mockito.anyString(), Mockito.anyMap()); + + + String deliveryId = externalRDelivery.getDeliveryId().toString(); + + + mockMvc.perform( + delete("/api/deliveryrequests/" + deliveryId) + .contentType(MediaType.APPLICATION_JSON) + .content(asJsonString(externalDelivery))) + .andExpect(status().isBadRequest()); + + Mockito.verify(ingestionimplMock, times(1)) + .cancelDeliveryAsync(Mockito.anyString(), Mockito.anyMap()); + } + + @Test + public void cancelscheduleDeliveryHandlesIOException() throws Exception { + + + Mockito.doThrow(new RuntimeException(new IOException())) + .when(ingestionimplMock) + .cancelDeliveryAsync(Mockito.anyString(), Mockito.anyMap()); + + + String deliveryId = externalRDelivery.getDeliveryId().toString(); + + mockMvc.perform( + delete("/api/deliveryrequests/" + deliveryId) + .contentType(MediaType.APPLICATION_JSON) + .content(asJsonString(externalDelivery))) + .andExpect(status().is5xxServerError()); + + Mockito.verify(ingestionimplMock, times(1)) + .cancelDeliveryAsync(Mockito.anyString(), Mockito.anyMap()); + } + + @Test + public void cancelscheduleDeliveryHandlesJsonProcessingException() throws Exception { + + Mockito.doThrow(new RuntimeException(new JsonParseException(null, "error"))) + .when(ingestionimplMock) + .cancelDeliveryAsync(Mockito.anyString(), Mockito.anyMap()); + + String deliveryId = externalRDelivery.getDeliveryId().toString(); + + mockMvc.perform( + delete("/api/deliveryrequests/" + deliveryId) + .contentType(MediaType.APPLICATION_JSON) + .content(asJsonString(externalDelivery))) + .andExpect(status().is5xxServerError()); + + Mockito.verify(ingestionimplMock, times(1)) + .cancelDeliveryAsync(Mockito.anyString(), Mockito.anyMap()); + } + + + @Test + public void cancelscheduleDeliveryHandlesMethodArgumentTypeMismatchException() throws Exception { + + Mockito.doThrow(new RuntimeException(new MethodArgumentTypeMismatchException(appPropsMock, null, null, null, null))) + .when(ingestionimplMock) + .cancelDeliveryAsync(Mockito.anyString(), Mockito.anyMap()); + + String deliveryId = externalRDelivery.getDeliveryId().toString(); + + mockMvc.perform( + delete("/api/deliveryrequests/" + deliveryId) + .contentType(MediaType.APPLICATION_JSON) + .content(asJsonString(externalDelivery))) + .andExpect(status().isBadRequest()); + + Mockito.verify(ingestionimplMock, times(1)) + .cancelDeliveryAsync(Mockito.anyString(), Mockito.anyMap()); + } + + + + + + + + + + + + + private static String asJsonString(final Object obj) { + try { + final ObjectMapper mapper = new ObjectMapper(); + final String jsonContent = mapper.writeValueAsString(obj); + return jsonContent; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/src/bc-shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/IngestionControllerTest.java b/src/shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/IngestionIT.java similarity index 75% rename from src/bc-shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/IngestionControllerTest.java rename to src/shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/IngestionIT.java index d0b71186..ec19d60b 100644 --- a/src/bc-shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/IngestionControllerTest.java +++ b/src/shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/IngestionIT.java @@ -6,13 +6,9 @@ import java.util.Date; import java.util.UUID; -import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; -//import org.mockito.runners.MockitoJUnitRunner; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -20,9 +16,6 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.web.context.WebApplicationContext; -//import org.springframework.test.web.servlet.setup.MockMvcBuilders; -//import org.springframework.web.context.WebApplicationContext; -//import org.springframework.web.context.WebApplicationContext; import com.fabrikam.dronedelivery.ingestion.models.ConfirmationRequired; import com.fabrikam.dronedelivery.ingestion.models.ContainerSize; @@ -34,23 +27,29 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + import org.springframework.test.context.junit4.SpringRunner; + import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; - @RunWith(SpringRunner.class) @SpringBootTest -public class IngestionControllerTest { +public class IngestionIT { + + static { + System.setProperty("CONTAINER_NAME", "test-container"); + } @Autowired private WebApplicationContext wac; private MockMvc mockMvc; private PackageInfo packageInfo; - private ExternalDelivery delivery; + private ExternalDelivery externalDelivery; ExternalRescheduledDelivery externalRDelivery; - + + @Before public void setUp() throws Exception { mockMvc = webAppContextSetup(this.wac).build(); @@ -58,15 +57,15 @@ public void setUp() throws Exception { packageInfo.setSize(ContainerSize.Large); packageInfo.setPackageId(UUID.randomUUID().toString()); - delivery = new ExternalDelivery(); - delivery.setOwnerId(UUID.randomUUID().toString()); - delivery.setPickupTime(new Date()); - delivery.setDropOffLocation("Austin"); - delivery.setPickupLocation("Texas"); - delivery.setExpedited(false); - delivery.setConfirmationRequired(ConfirmationRequired.FingerPrint); - delivery.setDeadline("LineOfDeadlyZombiatedPeople"); - delivery.setPackageInfo(packageInfo); + externalDelivery = new ExternalDelivery(); + externalDelivery.setOwnerId(UUID.randomUUID().toString()); + externalDelivery.setPickupTime(new Date()); + externalDelivery.setDropOffLocation("Austin"); + externalDelivery.setPickupLocation("Texas"); + externalDelivery.setExpedited(false); + externalDelivery.setConfirmationRequired(ConfirmationRequired.FingerPrint); + externalDelivery.setDeadline("LineOfDeadlyZombiatedPeople"); + externalDelivery.setPackageInfo(packageInfo); externalRDelivery = new ExternalRescheduledDelivery(); externalRDelivery.setDeadline("deadline"); @@ -75,24 +74,18 @@ public void setUp() throws Exception { externalRDelivery.setPickupLocation("location"); } - - @After - public void tearDown() throws Exception { - } - - @Ignore + @Test - public void ScheduleDeliveryIsOk() throws Exception { + public void ScheduleDeliveryITIsAccepted() throws Exception { MvcResult resultActions = mockMvc.perform( - post("/api/deliveryrequests").content(asJsonString(delivery)).contentType(MediaType.APPLICATION_JSON)) + post("/api/deliveryrequests").content(asJsonString(externalDelivery)).contentType(MediaType.APPLICATION_JSON)) .andExpect(request().asyncStarted()).andReturn(); - mockMvc.perform(asyncDispatch(resultActions)).andExpect(status().is2xxSuccessful()); + mockMvc.perform(asyncDispatch(resultActions)).andExpect(status().isAccepted()); } - @Ignore @Test - public void RescheduleDeliveryIsOk() throws Exception { + public void RescheduleDeliveryITIsOk() throws Exception { String deliveryId = externalRDelivery.getDeliveryId().toString(); MvcResult resultActions = mockMvc.perform(patch("/api/deliveryrequests/" + deliveryId) @@ -101,10 +94,10 @@ public void RescheduleDeliveryIsOk() throws Exception { mockMvc.perform(asyncDispatch(resultActions)).andExpect(status().is2xxSuccessful()); } - - @Ignore + + @Test - public void CancelDeliveryIsOk() throws Exception { + public void CancelDeliveryITIsOk() throws Exception { String deliveryId = externalRDelivery.getDeliveryId().toString(); MvcResult resultActions = mockMvc @@ -113,16 +106,16 @@ public void CancelDeliveryIsOk() throws Exception { mockMvc.perform(asyncDispatch(resultActions)).andExpect(status().is2xxSuccessful()); } - - @Ignore + + @Test - public void ProbeDeliveryIsOk() throws Exception { + public void ProbeDeliveryITIsOk() throws Exception { MvcResult resultActions = mockMvc.perform(get("/api/probe/").contentType(MediaType.APPLICATION_JSON)) .andExpect(request().asyncStarted()).andReturn(); mockMvc.perform(asyncDispatch(resultActions)).andExpect(status().is2xxSuccessful()); } - + private static String asJsonString(final Object obj) { try { final ObjectMapper mapper = new ObjectMapper(); diff --git a/src/shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/InstrumentedQueueClientTest.java b/src/shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/InstrumentedQueueClientTest.java new file mode 100644 index 00000000..ec35467e --- /dev/null +++ b/src/shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/InstrumentedQueueClientTest.java @@ -0,0 +1,67 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +package com.fabrikam.dronedelivery.ingestion; + +import com.fabrikam.dronedelivery.ingestion.util.InstrumentedQueueClientImpl; +import com.fabrikam.dronedelivery.ingestion.util.ServiceBusTracing; +import com.microsoft.azure.servicebus.IMessage; +import com.microsoft.azure.servicebus.IQueueClient; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +public class InstrumentedQueueClientTest { + + private static final String SB_ENDPOINT = "sbEndpoint"; + + private static final String SB_QUEUE_NAME = "sbQueueName"; + + private @Mock ServiceBusTracing tracingMock; + + private @Mock IMessage messageMock; + + private @InjectMocks InstrumentedQueueClientImpl instrumentedQueueClientImpl; + + private IQueueClient queueClientlMock; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + queueClientlMock = mock(IQueueClient.class); + when(queueClientlMock.getQueueName()).thenReturn(SB_QUEUE_NAME); + + instrumentedQueueClientImpl = + new InstrumentedQueueClientImpl( + SB_ENDPOINT, + queueClientlMock, + tracingMock); + } + + @Test + public void sendAsync_ThenWrappedByTrackAndCorrelatedWithProperInfo() throws Exception { + + // Arrange + + // Act + instrumentedQueueClientImpl.sendAsync(messageMock); + + // Assert + verify(queueClientlMock, times(1)).getQueueName(); + verify(tracingMock, times(1)) + .trackAndCorrelateServiceBusDependency( + eq(SB_ENDPOINT), + eq(SB_QUEUE_NAME), + eq(messageMock), + Mockito.any()); + } +} diff --git a/src/shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/TracingTest.java b/src/shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/TracingTest.java new file mode 100644 index 00000000..7493463d --- /dev/null +++ b/src/shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/TracingTest.java @@ -0,0 +1,179 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +package com.fabrikam.dronedelivery.ingestion; + +import com.fabrikam.dronedelivery.ingestion.util.ServiceBusTracingImpl; +import com.microsoft.applicationinsights.TelemetryClient; +import com.microsoft.applicationinsights.telemetry.RemoteDependencyTelemetry; +import com.microsoft.applicationinsights.telemetry.RequestTelemetry; +import com.microsoft.applicationinsights.web.internal.RequestTelemetryContext; +import com.microsoft.applicationinsights.web.internal.ThreadContext; +import com.microsoft.azure.servicebus.IMessage; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.invocation.Invocation; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.*; + +import java.util.Map; +import java.util.Collection; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.function.Function; + + +public class TracingTest { + private static final String TRACK_DEPENDENCY_METHOD_NAME = "trackDependency"; + + private static final int DELAY_COMPLETION_MS = 100; + + private static final String SB_QUEUE_NAME = "sbQueueName"; + + private static final String SB_ENDPOINT = "sbEndpoint"; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private @Mock IMessage messageMock; + + private @Mock CompletableFuture futureMock; + + private @Mock Function> functionMock; + + private @Mock Map mapMock; + + private @Mock TelemetryClient telemetryClientMock; + + private @Mock RequestTelemetryContext telemetryContextMock; + + private @InjectMocks ServiceBusTracingImpl tracing; + + private RequestTelemetry requestTelemetryFake; + + @Before + public void setUp() throws Exception { + + MockitoAnnotations.initMocks(this); + when(messageMock.getProperties()).thenReturn(mapMock); + + requestTelemetryFake = new RequestTelemetry(); + requestTelemetryFake.setId("42"); + + when(telemetryContextMock.getHttpRequestTelemetry()).thenReturn(requestTelemetryFake); + } + + @Test + public void trackAndCorrelate_ThenPropagateDiagnoticId() throws Exception { + + // Arrange + ThreadContext.setRequestTelemetryContext(telemetryContextMock); + when(functionMock.apply(eq(messageMock))).thenReturn(futureMock); + + // Act + tracing.trackAndCorrelateServiceBusDependency(SB_ENDPOINT, SB_QUEUE_NAME, messageMock, functionMock); + + // Assert + verify(messageMock, times(1)).getProperties(); + // TODO: validate proper Request-Id format + verify(mapMock, times(1)).put(eq("Diagnostic-Id"), eq("42")); + } + + @Test(timeout = DELAY_COMPLETION_MS * 10) + public void trackAndCorrelate_ThenApplyFunctionAndTrackDependency() throws Exception { + + // Arrange + ThreadContext.setRequestTelemetryContext(telemetryContextMock); + CompletableFuture result = new CompletableFuture<>(); + ArgumentCaptor argument = ArgumentCaptor.forClass(RemoteDependencyTelemetry.class); + // Act + tracing.trackAndCorrelateServiceBusDependency( + SB_ENDPOINT, + SB_QUEUE_NAME, + messageMock, + (m) -> { + try { + Thread.sleep(DELAY_COMPLETION_MS); + result.complete(null); + } catch (InterruptedException e) { + // shallowed exception is ok in here, please expect this test to timeout + } + + return result; + }); + + while (!existsTrackDepedency(mockingDetails(telemetryClientMock) + .getInvocations())){ + Thread.sleep(DELAY_COMPLETION_MS / 10); + } + + // Assert + verify(telemetryClientMock, times(1)) + .trackDependency(any(RemoteDependencyTelemetry.class)); + verify(telemetryClientMock).trackDependency(argument.capture()); + assertTrue(argument.getValue().getDuration().getTotalMilliseconds() >= DELAY_COMPLETION_MS); + assertEquals( + SB_ENDPOINT + " | "+ SB_QUEUE_NAME, + argument.getValue().getTarget()); + assertTrue(argument.getValue().getSuccess()); + } + + @Test(expected = CompletionException.class) + public void trackAndCorrelate_ThenThrownRuntimeExceptionWhenErr() throws Throwable { + // Arrange + ThreadContext.setRequestTelemetryContext(telemetryContextMock); + CompletableFuture result = new CompletableFuture<>(); + ArgumentCaptor argument = ArgumentCaptor.forClass(RemoteDependencyTelemetry.class); + + when(functionMock.apply(eq(messageMock))).thenReturn(result); + + // Act + tracing.trackAndCorrelateServiceBusDependency( + SB_ENDPOINT, + SB_QUEUE_NAME, + messageMock, + functionMock); + + try { + result.completeExceptionally(new RuntimeException()); + result.join(); + } + finally{ + // Assert + verify(functionMock, times(1)) + .apply(eq(messageMock)); + verify(telemetryClientMock, times(1)) + .trackException(any(RuntimeException.class)); + verify(telemetryClientMock, times(1)) + .trackDependency(any(RemoteDependencyTelemetry.class)); + verify(telemetryClientMock).trackDependency(argument.capture()); + assertEquals( + SB_ENDPOINT + " | "+ SB_QUEUE_NAME, + argument.getValue().getTarget()); + assertFalse(argument.getValue().getSuccess()); + } + } + + private static boolean existsTrackDepedency(Collection invcations) + { + for (Invocation invoke : invcations) { + if(invoke.getMethod().getName().equals(TRACK_DEPENDENCY_METHOD_NAME)) + { + return true; + } + } + + return false; + } +} \ No newline at end of file diff --git a/src/shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/configuration/TestAppConfig.java b/src/shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/configuration/TestAppConfig.java new file mode 100644 index 00000000..df27e6e6 --- /dev/null +++ b/src/shipping/ingestion/src/test/java/com/fabrikam/dronedelivery/ingestion/configuration/TestAppConfig.java @@ -0,0 +1,61 @@ +package com.fabrikam.dronedelivery.ingestion.configuration; + +import com.fabrikam.dronedelivery.ingestion.util.ClientPool; +import com.fabrikam.dronedelivery.ingestion.util.ClientPoolImpl; +import com.fabrikam.dronedelivery.ingestion.util.InstrumentedQueueClient; +import com.fabrikam.dronedelivery.ingestion.util.Environment; + +import com.microsoft.applicationinsights.TelemetryClient; +import com.microsoft.azure.servicebus.IMessage; +import com.microsoft.azure.servicebus.primitives.ServiceBusException; + +import static org.mockito.Mockito.*; + +import java.net.URISyntaxException; +import java.util.concurrent.CompletableFuture; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +@Configuration +public class TestAppConfig { + + @Autowired + private ApplicationProperties appProperites; + + @Bean + @Primary + public TelemetryClient getTelemetryClient() { + return mock(TelemetryClient.class); + } + + @Bean + @Primary + public Environment getEnvironment() { + Environment envMock = mock(Environment.class); + + when(envMock.getenv(appProperites.getEnvQueueName())) + .thenReturn("test-queue"); + + return envMock; + } + + @Bean + @Primary + public ClientPool getClientPool() throws InterruptedException, ServiceBusException, URISyntaxException { + ClientPool clientPoolMock = mock(ClientPoolImpl.class); + InstrumentedQueueClient instrumentedQueueClient = + mock(InstrumentedQueueClient.class); + CompletableFuture futureMock = + (CompletableFuture) mock(CompletableFuture.class); + + when(instrumentedQueueClient.sendAsync(any(IMessage.class))) + .thenReturn(futureMock); + when(clientPoolMock.getConnection()) + .thenReturn(instrumentedQueueClient); + + return clientPoolMock; + } +} diff --git a/src/shipping/package/.dockerignore b/src/shipping/package/.dockerignore new file mode 100644 index 00000000..7c21ce5e --- /dev/null +++ b/src/shipping/package/.dockerignore @@ -0,0 +1,16 @@ +# env # +.git +.github +.vscode + +# others +.DS_Store +ehthumbs.db +Icon? +Thumbs.db + +# Node files +node_modules +npm-debug.log +npm-debug.log.* +logs/* diff --git a/src/bc-shipping/package/.gitignore b/src/shipping/package/.gitignore similarity index 100% rename from src/bc-shipping/package/.gitignore rename to src/shipping/package/.gitignore diff --git a/src/shipping/package/Dockerfile b/src/shipping/package/Dockerfile new file mode 100644 index 00000000..787a27bd --- /dev/null +++ b/src/shipping/package/Dockerfile @@ -0,0 +1,34 @@ +FROM node:8.12.0-alpine as base + +WORKDIR /app + +EXPOSE 80 + +# ---- install dependencies ---- +FROM base AS dependencies + +WORKDIR /app +COPY package.json . +COPY gulpfile.js . +RUN npm set progress=false && npm config set depth 0 +RUN npm install --only=production +RUN cp -R node_modules prod_node_modules + +# ---- build ---- +FROM dependencies AS build +WORKDIR /app +RUN npm install +COPY tsconfig.json . +COPY app app/. +RUN npm run build + +# ---- runtime ---- +FROM base AS runtime + +MAINTAINER Fernando Antivero (https://github.com/ferantivero) + +WORKDIR /app +COPY --from=dependencies /app/prod_node_modules ./node_modules +COPY --from=build /app/.bin/app . + +ENTRYPOINT ["node", "main.js"] diff --git a/src/shipping/package/app/api.json b/src/shipping/package/app/api.json new file mode 100644 index 00000000..e22c044a --- /dev/null +++ b/src/shipping/package/app/api.json @@ -0,0 +1,192 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "fabrikam-drone-delivery-package-service", + "description": "Fabrikam Drone Delivery Package Service", + "version": "0.1.0", + "contact": "Microsoft Patterns and Practices", + "termsOfService": "" + }, + "basePath": "/api", + "schemes": [ + "http", + "https" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/packages/{packageId}": { + "get": { + "summary": "Get information about a specific package from the service", + "description": "Returns package by id", + "operationid": "getById", + "parameters": [ + { + "name": "packageId", + "description": "ID of package to return", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Package" + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Package not found" + } + } + }, + "patch": { + "summary": "Update an existing package", + "description": "Update Package by ID", + "operationid": "updateById", + "parameters": [ + { + "name": "packageId", + "description": "ID of package to patch", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "204": { + "description": "successful operation" + }, + "400": { + "description": "invalid id supplied" + }, + "404": { + "description": "package not found" + }, + "405": { + "description": "Validation exception" + } + } + }, + "put": { + "summary": "Create or update a package", + "description": "Creates or updates a package using the data provided in the API", + "operationid": "createOrUpdate", + "parameters": [ + { + "name": "packageId", + "description": "ID of package to patch", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "201": { + "description": "Created new package", + "schema": { + "$ref": "#/definitions/Package" + } + }, + "204": { + "description": "Updated existing package" + } + } + } + }, + "/packages/summary/{ownerId}": { + "get": { + "summary": "Get summary information about packages from a user", + "description": "Get summary information about packages from a user", + "operationid": "getSummary", + "parameters": [ + { + "name": "ownerId", + "description": "ID of the package's owner", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "year", + "description": "Year of the summary requested", + "in": "query", + "required": true, + "type": "integer" + }, + { + "name": "month", + "description": "Month of the summary requested", + "in": "path", + "required": true, + "type": "integer" + } + ], + "responses": { + "201": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/PackageUtilization" + } + }, + "400": { + "description": "Invalid ID, year, or month supplied" + } + } + } + } + }, + "definitions": { + "Package": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "size": { + "type": "string", + "enum": [ + "small", + "medium", + "large" + ] + }, + "weight": { + "type": "number" + }, + "tag": { + "type": "string" + } + } + }, + "PackageUtilization": { + "type": "object", + "properties": { + "totalWeight": { + "type": "number" + } + } + }, + "Error": { + "type": "object", + "properties": { + "code": { + "type": "number" + }, + "message": { + "type": "string" + } + } + } + }, + "components": {}, + "tags": [] +} diff --git a/src/shipping/package/app/app.ts b/src/shipping/package/app/app.ts new file mode 100644 index 00000000..5a5cc1ad --- /dev/null +++ b/src/shipping/package/app/app.ts @@ -0,0 +1,65 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +import Koa from 'koa'; +import bodyParser from "koa-bodyparser"; + +var compress = require('koa-compress'); + +import { apiRouter, healthzRouter, swaggerRouter } from './routes'; +import { logger, ILogger } from './util/logging' + +export class KoaApp { + + static create(logLevel: string) : Koa { + + const app = new Koa(); + + // Configure logging + app.use(logger(logLevel)); + + // Configure global exception handling + // Use: ctx.throw('Error Message', 500); + // in the controller methods to set the status code and exception message + app.use(async (ctx : any, next : any) => { + try { + await next(); + } catch (ex) { + + var logger : ILogger = ctx.state.logger; + if (logger) { + logger.error(ex.message); + } + + ctx.status = ex.status || 500; + // consider api specific codes and localized messages as opposed to internal codes + ctx.body = { + level: "error", + code: ex.code, + message: ex.message + } + ctx.app.emit('error', ex, ctx); + } + }); + + // add compression and body parser to the pipeline + app.use(compress()); + app.use(bodyParser()); + + const packageServiceRouter = apiRouter(); + app.use(packageServiceRouter.routes()); + app.use(packageServiceRouter.allowedMethods()); + + const healthChecksRouter = healthzRouter(); + app.use(healthChecksRouter.routes()); + app.use(healthChecksRouter.allowedMethods()); + + const swaggerSpecRouter = swaggerRouter(); + app.use(swaggerSpecRouter.routes()); + app.use(swaggerSpecRouter.allowedMethods()); + + return app; + } +} diff --git a/src/shipping/package/app/controllers/healthz-controllers.ts b/src/shipping/package/app/controllers/healthz-controllers.ts new file mode 100644 index 00000000..07b7b3ab --- /dev/null +++ b/src/shipping/package/app/controllers/healthz-controllers.ts @@ -0,0 +1,18 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +import { ILogger } from '../util/logging' + +export class HealthzControllers { + + static async getReadinessLiveness(ctx: any) { + var logger : ILogger = ctx.state.logger; + logger.info('Readiness/Liveness Probe Status: %s', "OK"); + + ctx.status = 200; + ctx.body = {status: 'OK'}; + } + +} diff --git a/src/bc-shipping/package/app/controllers/index.ts b/src/shipping/package/app/controllers/index.ts similarity index 80% rename from src/bc-shipping/package/app/controllers/index.ts rename to src/shipping/package/app/controllers/index.ts index 7c380067..fd101adb 100644 --- a/src/bc-shipping/package/app/controllers/index.ts +++ b/src/shipping/package/app/controllers/index.ts @@ -4,3 +4,5 @@ // ------------------------------------------------------------ export * from './package-controllers'; +export * from './healthz-controllers'; +export * from './swagger-controllers'; diff --git a/src/shipping/package/app/controllers/package-controllers.ts b/src/shipping/package/app/controllers/package-controllers.ts new file mode 100644 index 00000000..9ab2949e --- /dev/null +++ b/src/shipping/package/app/controllers/package-controllers.ts @@ -0,0 +1,246 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +import { UpsertStatus } from '../models/repository' +import * as apiModels from '../models/api-models' +import { Package, PackageSize } from '../models/package' +import { ILogger } from '../util/logging' +import { MongoErrors } from '../util/mongo-err' + +export class PackageControllers { + /** + * @swagger + * + * definitions: + * Package: + * type: object + * properties: + * id: + * type: string + * size: + * type: string + * enum: + * - small + * - medium + * - large + * weight: + * type: number + * tag: + * type: string + * PackageUtilization: + * type: object + * properties: + * totalWeight: + * type: number + * Error: + * type: object + * properties: + * code: + * type: number + * message: + * type: string + */ + + /** + * @swagger + * /packages/{packageId}: + * get: + * summary: Get information about a specific package from the service + * description: Returns package by id + * operationid: getById + * parameters: + * - name: packageId + * description: ID of package to return + * in: path + * required: true + * type: string + * responses: + * 200: + * description: successful operation + * schema: + * $ref: '#/definitions/Package' + * 400: + * description: Invalid ID supplied + * 404: + * description: Package not found + */ + static async getById(ctx: any) { + + var logger : ILogger = ctx.state.logger; + var packageId = ctx.params.packageId; + logger.info('Entering getById, packageId = %s', packageId); + + let pkg = await ctx.packageRepository.findPackage(ctx.params.packageId) + + if (pkg == null) { + logger.info(`getById: %s not found`, packageId); + ctx.response.status= 404; + return; + } + + ctx.response.status = 200; + ctx.response.body = ctx.packageRepository.mapPackageDbToApi(pkg); + } + + /** + * @swagger + * /packages/{packageId}: + * patch: + * summary: Update an existing package + * description: Update Package by ID + * operationid: updateById + * parameters: + * - name: packageId + * description: ID of package to patch + * in: path + * required: true + * type: string + * responses: + * 204: + * description: successful operation + * 400: + * description: invalid id supplied + * 404: + * description: package not found + * 405: + * description: Validation exception + */ + static async updateById(ctx: any) { + + var logger : ILogger = ctx.state.logger; + var packageId = ctx.params.packageId; + logger.info('updateById', packageId); + + try { + let apiPkg = ctx.request.body; + let pkg = ctx.packageRepository.mapPackageApiToDb(apiPkg, packageId) + + // the update package should take a dictionary of fields instead of package model + await ctx.packageRepository.updatePackage(pkg); + + ctx.response.status = 204; + } + catch (ex) { + // Need to handle the case were it's 404 vs invalid data + ctx.throw(400, ex.message); + } + + return; + } + + /** + * @swagger + * /packages/{packageId}: + * put: + * summary: Create or update a package + * description: Creates or updates a package using the data provided in the API + * operationid: createOrUpdate + * parameters: + * - name: packageId + * description: ID of package to patch + * in: path + * required: true + * type: string + * responses: + * 201: + * description: Created new package + * schema: + * $ref: '#/definitions/Package' + * 204: + * description: Updated existing package + */ + static async createOrUpdate(ctx: any) { + + var logger : ILogger = ctx.state.logger; + var packageId = ctx.params.packageId; + logger.info('create', ctx.request.body) + + try { + let apiPkg = ctx.request.body; + let pkg = ctx.packageRepository.mapPackageApiToDb(apiPkg, packageId); + + var result = await ctx.packageRepository.addPackage(pkg); + + switch (result) { + case UpsertStatus.Created: + ctx.body = ctx.packageRepository.mapPackageDbToApi(pkg); + ctx.response.status = 201; + break; + case UpsertStatus.Updated: + ctx.response.status = 204; + break; + } + + return; + } + catch (ex) { + switch (ex.code) { + case MongoErrors.ShardKeyNotFound: + logger.error('Missing shard key', ctx.request.body) + ctx.response.status = 400; + ctx.response.message = "Missing shard key"; + break; + + case MongoErrors.TooManyRequests: + logger.error('Too many requests', ctx.request.body) + ctx.response.status = 429; + break; + + default: + { + ctx.throw(500, ex.message); + } + } + } + } + + /** + * @swagger + * /packages/summary/{ownerId}: + * get: + * summary: Get summary information about packages from a user + * description: Get summary information about packages from a user + * operationid: getSummary + * parameters: + * - name: ownerId + * description: ID of the package's owner + * in: path + * required: true + * type: string + * - name: year + * description: Year of the summary requested + * in: query + * required: true + * type: integer + * - name: month + * description: Month of the summary requested + * in: path + * required: true + * type: integer + * responses: + * 201: + * description: successful operation + * schema: + * $ref: '#/definitions/PackageUtilization' + * 400: + * description: Invalid ID, year, or month supplied + */ + static async getSummary(ctx: any) { + + var logger : ILogger = ctx.state.logger; + var ownerId = ctx.params.ownerId; + var year = ctx.params.year; + var month = ctx.params.month; + logger.info('retrieve summary %s %d/%d', ownerId) + + let utilization = new apiModels.PackageUtilization(); + utilization.totalWeight = 400; + + ctx.body = utilization; + ctx.response.status = 200; + + return; + } +} diff --git a/src/shipping/package/app/controllers/swagger-controllers.ts b/src/shipping/package/app/controllers/swagger-controllers.ts new file mode 100644 index 00000000..37c95c19 --- /dev/null +++ b/src/shipping/package/app/controllers/swagger-controllers.ts @@ -0,0 +1,19 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +import * as Spec from '../spec/package-swagger'; +import { ILogger } from '../util/logging' + +export class SwaggerControllers { + + static async getSpec(ctx: any) { + var logger : ILogger = ctx.state.logger; + logger.info('Swagger Spec Request %s', "OK"); + + ctx.status = 200; + ctx.body = Spec.PackageServiceSwaggerApi; + } + +} diff --git a/src/shipping/package/app/initializer.ts b/src/shipping/package/app/initializer.ts new file mode 100644 index 00000000..fc1f741f --- /dev/null +++ b/src/shipping/package/app/initializer.ts @@ -0,0 +1,58 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +import { MongoErrors } from './util/mongo-err' + +let appInsights = require('applicationinsights'); +var MongoClient = require('mongodb').MongoClient; + +export class PackageServiceInitializer +{ + static async initialize(connection: string, collectionName: string, containerName: string) { + try { + PackageServiceInitializer.initAppInsights(containerName); + await PackageServiceInitializer.initMongoDb(connection, + collectionName); + } + catch(ex) { + console.log(ex); + } + } + + private static async initMongoDb(connection: string, collectionName: string) { + try { + var db = (await MongoClient.connect(connection)).db(); + await db.command({ shardCollection: db.databaseName + '.' + collectionName, key: { tag: "hashed" } }); + } + catch (ex) { + if (ex.code != MongoErrors.CommandNotFound && ex.code != 9) { + console.log(ex); + } + } + } + + private static initAppInsights(cloudRole = "package") { + if (!process.env.APPINSIGHTS_INSTRUMENTATIONKEY && + process.env.NODE_ENV === 'development') { + const logger = console; + process.stderr.write('Skipping app insights setup - in development mode with no ikey set\n'); + appInsights. + defaultClient = { + trackEvent: logger.log.bind(console, 'trackEvent'), + trackException: logger.error.bind(console, 'trackException'), + trackMetric: logger.log.bind(console, 'trackMetric'), + }; + } else if (process.env.APPINSIGHTS_INSTRUMENTATIONKEY) { + appInsights.setup(); + appInsights.defaultClient.context.tags[appInsights.defaultClient.context.keys.cloudRole] = cloudRole; + process.stdout.write('App insights setup - configuring client\n'); + appInsights.start(); + process.stdout.write('Application Insights started'); + } else { + throw new Error('No app insights setup. A key must be specified in non-development environments.'); + } + } +} + diff --git a/src/bc-shipping/package/app/main.ts b/src/shipping/package/app/main.ts similarity index 90% rename from src/bc-shipping/package/app/main.ts rename to src/shipping/package/app/main.ts index f6bd7207..e0371bed 100644 --- a/src/bc-shipping/package/app/main.ts +++ b/src/shipping/package/app/main.ts @@ -3,11 +3,11 @@ // Licensed under the MIT License (MIT). See License.txt in the repo root for license information. // ------------------------------------------------------------ -import { PackageService } from './server'; import { PackageServiceInitializer } from './initializer' +import { PackageService } from './server'; import { Settings } from './util/settings'; -PackageServiceInitializer.initialize(Settings.connectionString(), Settings.collectionName()) +PackageServiceInitializer.initialize(Settings.connectionString(), Settings.collectionName(), Settings.containerName()) .then(_ => { PackageService.start(); }); diff --git a/src/bc-shipping/package/app/models/api-models.ts b/src/shipping/package/app/models/api-models.ts similarity index 50% rename from src/bc-shipping/package/app/models/api-models.ts rename to src/shipping/package/app/models/api-models.ts index e9563464..a6f2258c 100644 --- a/src/bc-shipping/package/app/models/api-models.ts +++ b/src/shipping/package/app/models/api-models.ts @@ -8,3 +8,14 @@ export class Package tag:string } +export class PackageUtilization +{ + totalWeight:number +} + +export class Error +{ + code:number + message:string +} + diff --git a/src/bc-shipping/package/app/models/package.ts b/src/shipping/package/app/models/package.ts similarity index 85% rename from src/bc-shipping/package/app/models/package.ts rename to src/shipping/package/app/models/package.ts index 2051aaa0..00195e11 100644 --- a/src/bc-shipping/package/app/models/package.ts +++ b/src/shipping/package/app/models/package.ts @@ -8,12 +8,12 @@ import { ObjectID } from 'mongodb'; export type PackageSize = "small" | "medium" | "large"; export class Package { - readonly _id: ObjectID; + readonly _id: string; tag: string; weight: number; size: PackageSize; constructor(id? : string) { - this._id = id || (new ObjectID(id)).toHexString(); + this._id = id || (new ObjectID()).toHexString(); } -} \ No newline at end of file +} diff --git a/src/bc-shipping/package/app/models/repository.ts b/src/shipping/package/app/models/repository.ts similarity index 63% rename from src/bc-shipping/package/app/models/repository.ts rename to src/shipping/package/app/models/repository.ts index 69d2436a..47150334 100644 --- a/src/bc-shipping/package/app/models/repository.ts +++ b/src/shipping/package/app/models/repository.ts @@ -3,9 +3,10 @@ // Licensed under the MIT License (MIT). See License.txt in the repo root for license information. // ------------------------------------------------------------ -import { Package } from "./package" +import { Package, PackageSize } from './package' +import * as apiModels from './api-models' import { Settings } from '../util/settings'; -import * as Logger from '../util/logging'; +import * as Logger from '../util/logging'; import { MongoErrors } from '../util/mongo-err'; var MongoClient = require('mongodb').MongoClient; @@ -18,11 +19,11 @@ export enum UpsertStatus { export class Repository { static readonly collectionName = Settings.collectionName(); - private static db; + private static db:any; static async initialize(connection: string) { - Repository.db = await MongoClient.connect(connection); + Repository.db = (await MongoClient.connect(connection)).db(); } private collection() { @@ -44,7 +45,7 @@ export class Repository } } } - + async updatePackage(p: Package) { await this.collection().update({_id:p._id}, p); } @@ -53,5 +54,25 @@ export class Repository var collection = this.collection(); return await collection.findOne({_id: id }); // Returns null if not found } + + mapPackageDbToApi(pkg: Package): apiModels.Package { + // consider allowing repository to deal with API types or.. automapping, mapping through convention or config + // we could simpley add/remove/change the model in the api vs database + let apiPkg = new apiModels.Package(); + apiPkg.id = pkg._id; + apiPkg.size = pkg.size ? pkg.size.toString() : null; + apiPkg.tag = pkg.tag; + apiPkg.weight = pkg.weight; + return apiPkg; + } + + mapPackageApiToDb(apiPkg: apiModels.Package, id?: string): Package { + let pkg = new Package(id); + pkg.size = apiPkg.size; + pkg.weight = apiPkg.weight; + pkg.tag = apiPkg.tag; + return pkg; + } + } diff --git a/src/shipping/package/app/routes.ts b/src/shipping/package/app/routes.ts new file mode 100644 index 00000000..0e22ad68 --- /dev/null +++ b/src/shipping/package/app/routes.ts @@ -0,0 +1,46 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +const Router = require('@koa/router'); + +import { PackageControllers, HealthzControllers, SwaggerControllers } from './controllers'; + +export function apiRouter() { + + const router = new Router({ + prefix: '/api' + }); + + router.get('/packages/:packageId', PackageControllers.getById); + router.put('/packages', PackageControllers.createOrUpdate); + router.put('/packages/:packageId', PackageControllers.createOrUpdate); + router.patch('/packages/:packageId', PackageControllers.updateById); + + router.get('/packages/summary/:ownerId', PackageControllers.getSummary); + + return router; +} + +export function healthzRouter() { + + const router = new Router({ + prefix: '/healthz' + }); + + router.get('/', HealthzControllers.getReadinessLiveness); + + return router; +} + +export function swaggerRouter() { + + const router = new Router({ + prefix: '/swagger' + }); + + router.get('/swagger.json', SwaggerControllers.getSpec); + + return router; +} diff --git a/src/shipping/package/app/server.ts b/src/shipping/package/app/server.ts new file mode 100644 index 00000000..885c4829 --- /dev/null +++ b/src/shipping/package/app/server.ts @@ -0,0 +1,34 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +import { KoaApp } from './app'; +import { Repository } from './models/repository'; +import { Settings } from './util/settings'; + +export class PackageService { + + static start() { + + const port = process.env.PORT || 80; + + console.log('Package service starting...') + + // Initialize repository with connection string + Promise.resolve(Repository.initialize(Settings.connectionString())) + .catch((ex) => { + console.error("failed to initialize repository - make sure a connectiong string has been configured"); + console.error(ex.message); + process.exit(1); // Crash the container + }); + + const app = KoaApp.create(Settings.logLevel()); + + // Add package repo to the context + app.context.packageRepository = new Repository(); + + app.listen(port); + console.log('listening on port %s', port); + } +} diff --git a/src/shipping/package/app/spec/package-swagger.ts b/src/shipping/package/app/spec/package-swagger.ts new file mode 100644 index 00000000..bb09aaff --- /dev/null +++ b/src/shipping/package/app/spec/package-swagger.ts @@ -0,0 +1,29 @@ +const path = require('path'); +const swaggerJSDoc = require('swagger-jsdoc'); + +const options = { + definition: { + openapi: '3.0.0', + info: { + title: "fabrikam-drone-delivery-package-service", + description: "Fabrikam Drone Delivery Package Service", + version: "0.1.0", + contact: "Microsoft Patterns and Practices", + termsOfService: '' + }, + basePath: '/api', + "schemes": [ + "http", + "https" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ] + }, + apis: [path.join(__dirname, '../controllers/package-controllers.{ts,js}')], +}; + +export const PackageServiceSwaggerApi = swaggerJSDoc(options); diff --git a/src/shipping/package/app/util/logging.ts b/src/shipping/package/app/util/logging.ts new file mode 100644 index 00000000..cb766552 --- /dev/null +++ b/src/shipping/package/app/util/logging.ts @@ -0,0 +1,51 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +import { createLogger, format, transports } from 'winston'; + +const { combine, timestamp, errors, printf, splat } = format; +const defaultFormat = combine( + timestamp(), + splat(), + errors(), + printf( + ({ + timestamp, + level, + message, + ...rest + }) => { + let restString = JSON.stringify(rest, undefined, 2); + restString = restString === '{}' ? '' : restString; + + return `[${timestamp}] ${level} - ${message} ${restString}`; + }, + ), +); + +export interface ILogger { + log(level: string, msg: string, meta?: any) : any + debug(msg: string, meta?: any) : any + info(msg: string, meta?: any) : any + warn(msg: string, meta?: any) : any + error(msg: string, meta?: any) : any +} + +export function logger(level: string) { + const logger = createLogger({ + level: level, + transports: [ + new transports.Console({ level: level}) + ], + format: defaultFormat + }); + + logger.info('winston logger created'); + + return async function(ctx: any, next: any) { + ctx.state.logger = logger; + await next(); + } +} diff --git a/src/bc-shipping/package/app/util/mongo-err.ts b/src/shipping/package/app/util/mongo-err.ts similarity index 100% rename from src/bc-shipping/package/app/util/mongo-err.ts rename to src/shipping/package/app/util/mongo-err.ts diff --git a/src/bc-shipping/package/app/util/settings.ts b/src/shipping/package/app/util/settings.ts similarity index 82% rename from src/bc-shipping/package/app/util/settings.ts rename to src/shipping/package/app/util/settings.ts index 5695bf71..0dbaaedd 100644 --- a/src/bc-shipping/package/app/util/settings.ts +++ b/src/shipping/package/app/util/settings.ts @@ -3,7 +3,7 @@ // Licensed under the MIT License (MIT). See License.txt in the repo root for license information. // ------------------------------------------------------------ -import process = require("process") +const process = require("process") export class Settings { static collectionName() : string { @@ -14,11 +14,11 @@ export class Settings { return process.env["CONNECTION_STRING"] } - static correlationHeader() : string { - return process.env["CORRELATION_HEADER"] + static containerName() : string { + return process.env["CONTAINER_NAME"] } static logLevel() : string { return process.env["LOG_LEVEL"] || 'debug' } -} \ No newline at end of file +} diff --git a/src/shipping/package/azure-pipelines-cd.json b/src/shipping/package/azure-pipelines-cd.json new file mode 100644 index 00000000..78c31950 --- /dev/null +++ b/src/shipping/package/azure-pipelines-cd.json @@ -0,0 +1,1728 @@ +{ + "source": 1, + "revision": 20, + "description": null, + "isDeleted": false, + "variables": { + "REASON": { + "value": "Azure DevOps CD Pipeline", + "allowOverride": true + }, + "REPO_NAME": { + "value": "package" + } + }, + "variableGroups": [], + "environments": [ + { + "id": 6, + "name": "dev", + "rank": 1, + "owner": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "variables": { + "AI_IKEY": { + "value": "DEV_AI_IKEY_VAR_VAL", + "isSecret": true + }, + "COSMOSDB_COL_NAME": { + "value": "DEV_COSMOSDB_COL_NAME_VAR_VAL" + }, + "COSMOSDB_CONNECTION": { + "value": "DEV_COSMOSDB_CONNECTION_VAR_VAL", + "isSecret": true + }, + "ACR_SERVER": { + "value": "DEV_ACR_SERVER_VAR_VAL" + }, + "ACR_NAME": { + "value": "DEV_ACR_NAME_VAR_VAL" + }, + "CLUSTER_SUBNET_PREFIX": { + "value": "DEV_CLUSTER_SUBNET_PREFIX_VAR_VAL" + } + }, + "variableGroups": [], + "preDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 18 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 1 + } + }, + "deployStep": { + "id": 25 + }, + "postDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 26 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 2 + } + }, + "deployPhases": [ + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [ + { + "alias": "ci-package", + "artifactType": "Build", + "artifactDownloadMode": "All", + "artifactItems": [] + } + ] + }, + "queueId": AZURE_DEVOPS_PACKAGE_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 1, + "phaseType": 1, + "name": "Agent job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "helm upgrade package dev", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend-dev", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)-dev", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),secrets.appinsights.ikey=$(AI_IKEY),networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",secrets.mongo.pwd=$(COSMOSDB_CONNECTION),cosmosDb.collectionName=$(COSMOSDB_COL_NAME),reason=\"$(REASON)\",envs.dev=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + } + ], + "environmentOptions": { + "emailNotificationType": "OnlyOnFailure", + "emailRecipients": "release.environment.owner;release.creator", + "skipArtifactsDownload": false, + "timeoutInMinutes": 0, + "enableAccessToken": false, + "publishDeploymentStatus": true, + "badgeEnabled": false, + "autoLinkWorkItems": false, + "pullRequestDeploymentEnabled": false + }, + "demands": [], + "conditions": [ + { + "name": "ReleaseStarted", + "conditionType": 1, + "value": "" + }, + { + "name": "ci-package", + "conditionType": 4, + "value": "{\"sourceBranch\":\"release/package/v*\",\"tags\":[],\"useBuildDefinitionBranch\":false,\"createReleaseOnBuildTagging\":false}" + } + ], + "executionPolicy": { + "concurrencyCount": 1, + "queueDepthCount": 0 + }, + "schedules": [], + "retentionPolicy": { + "daysToKeep": 30, + "releasesToKeep": 3, + "retainBuild": true + }, + "processParameters": { + "inputs": [ + { + "aliases": [], + "options": { + "Azure Resource Manager": "Azure Resource Manager", + "Kubernetes Service Connection": "Kubernetes Service Connection" + }, + "properties": { + "EditableOptions": "false" + }, + "name": "connectionType", + "label": "Connection Type.", + "defaultValue": "Azure Resource Manager", + "type": "pickList", + "helpMarkDown": "", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "azureSubscriptionEndpoint", + "label": "Azure subscription", + "defaultValue": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "required": true, + "type": "connectedService:AzureRM", + "helpMarkDown": "Select an Azure subscription, which has your Azure Container Registry.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "azureResourceGroup", + "label": "Resource group", + "defaultValue": "RESOURCE_GROUP_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Resource Group.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "kubernetesCluster", + "label": "Kubernetes cluster", + "defaultValue": "CLUSTER_NAME_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Kubernetes Service cluster.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "kubernetesServiceEndpoint", + "label": "Kubernetes Service Connection", + "defaultValue": "", + "required": true, + "type": "connectedService:kubernetes", + "helpMarkDown": "Select a Kubernetes service connection.", + "visibleRule": "connectionType = Kubernetes Service Connection", + "groupName": "" + } + ], + "dataSourceBindings": [ + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "kubernetesCluster", + "resultTemplate": "{{{name}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + }, + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "azureResourceGroup", + "resultTemplate": "{{{ #extractResource id resourcegroups}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + } + ] + }, + "properties": {}, + "preDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "postDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "environmentTriggers": [], + "badgeUrl": null + }, + { + "id": 7, + "name": "QA", + "rank": 2, + "owner": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "variables": { + "AI_IKEY": { + "value": "QA_AI_IKEY_VAR_VAL", + "isSecret": true + }, + "COSMOSDB_COL_NAME": { + "value": "QA_COSMOSDB_COL_NAME_VAR_VAL" + }, + "COSMOSDB_CONNECTION": { + "value": "QA_COSMOSDB_CONNECTION_VAR_VAL", + "isSecret": true + }, + "ACR_SERVER": { + "value": "QA_ACR_SERVER_VAR_VAL" + }, + "ACR_NAME": { + "value": "QA_ACR_NAME_VAR_VAL" + }, + "CLUSTER_SUBNET_PREFIX": { + "value": "QA_CLUSTER_SUBNET_PREFIX_VAR_VAL" + } + }, + "variableGroups": [], + "preDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 19 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 1 + } + }, + "deployStep": { + "id": 24 + }, + "postDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 27 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 2 + } + }, + "deployPhases": [ + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [ + { + "alias": "ci-package", + "artifactType": "Build", + "artifactDownloadMode": "All", + "artifactItems": [] + } + ] + }, + "queueId": AZURE_DEVOPS_PACKAGE_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 1, + "phaseType": 1, + "name": "Agent job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "helm upgrade package staging", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend-qa", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)-qa", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),secrets.appinsights.ikey=$(AI_IKEY),networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",secrets.mongo.pwd=$(COSMOSDB_CONNECTION),cosmosDb.collectionName=$(COSMOSDB_COL_NAME),reason=\"$(REASON)\",envs.qa=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + } + ], + "environmentOptions": { + "emailNotificationType": "OnlyOnFailure", + "emailRecipients": "release.environment.owner;release.creator", + "skipArtifactsDownload": false, + "timeoutInMinutes": 0, + "enableAccessToken": false, + "publishDeploymentStatus": true, + "badgeEnabled": false, + "autoLinkWorkItems": false, + "pullRequestDeploymentEnabled": false + }, + "demands": [], + "conditions": [ + { + "name": "dev", + "conditionType": 2, + "value": "4" + } + ], + "executionPolicy": { + "concurrencyCount": 1, + "queueDepthCount": 0 + }, + "schedules": [], + "retentionPolicy": { + "daysToKeep": 30, + "releasesToKeep": 3, + "retainBuild": true + }, + "processParameters": { + "inputs": [ + { + "aliases": [], + "options": { + "Azure Resource Manager": "Azure Resource Manager", + "Kubernetes Service Connection": "Kubernetes Service Connection" + }, + "properties": { + "EditableOptions": "false" + }, + "name": "connectionType", + "label": "Connection Type.", + "defaultValue": "Azure Resource Manager", + "type": "pickList", + "helpMarkDown": "", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "azureSubscriptionEndpoint", + "label": "Azure subscription", + "defaultValue": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "required": true, + "type": "connectedService:AzureRM", + "helpMarkDown": "Select an Azure subscription, which has your Azure Container Registry.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "azureResourceGroup", + "label": "Resource group", + "defaultValue": "RESOURCE_GROUP_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Resource Group.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "kubernetesCluster", + "label": "Kubernetes cluster", + "defaultValue": "CLUSTER_NAME_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Kubernetes Service cluster.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "kubernetesServiceEndpoint", + "label": "Kubernetes Service Connection", + "defaultValue": "", + "required": true, + "type": "connectedService:kubernetes", + "helpMarkDown": "Select a Kubernetes service connection.", + "visibleRule": "connectionType = Kubernetes Service Connection", + "groupName": "" + } + ], + "dataSourceBindings": [ + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "kubernetesCluster", + "resultTemplate": "{{{name}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + }, + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "azureResourceGroup", + "resultTemplate": "{{{ #extractResource id resourcegroups}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + } + ] + }, + "properties": {}, + "preDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "postDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "environmentTriggers": [], + "badgeUrl": null + }, + { + "id": 8, + "name": "staging", + "rank": 3, + "owner": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "variables": { + "AI_IKEY": { + "value": "STAGING_AI_IKEY_VAR_VAL", + "isSecret": true + }, + "COSMOSDB_COL_NAME": { + "value": "STAGING_COSMOSDB_COL_NAME_VAR_VAL" + }, + "COSMOSDB_CONNECTION": { + "value": "STAGING_COSMOSDB_CONNECTION_VAR_VAL", + "isSecret": true + }, + "ACR_SERVER": { + "value": "STAGING_ACR_SERVER_VAR_VAL" + }, + "ACR_NAME": { + "value": "STAGING_ACR_NAME_VAR_VAL" + }, + "CLUSTER_SUBNET_PREFIX": { + "value": "STAGING_CLUSTER_SUBNET_PREFIX_VAR_VAL" + } + }, + "variableGroups": [], + "preDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 20 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 1 + } + }, + "deployStep": { + "id": 23 + }, + "postDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 28 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 2 + } + }, + "deployPhases": [ + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [ + { + "alias": "ci-package", + "artifactType": "Build", + "artifactDownloadMode": "All", + "artifactItems": [] + } + ] + }, + "queueId": AZURE_DEVOPS_PACKAGE_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 1, + "phaseType": 1, + "name": "Agent job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "helm upgrade package staging", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend-staging", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)-staging", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),secrets.appinsights.ikey=$(AI_IKEY),networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",secrets.mongo.pwd=$(COSMOSDB_CONNECTION),cosmosDb.collectionName=$(COSMOSDB_COL_NAME),reason=\"$(REASON)\",envs.staging=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + } + ], + "environmentOptions": { + "emailNotificationType": "OnlyOnFailure", + "emailRecipients": "release.environment.owner;release.creator", + "skipArtifactsDownload": false, + "timeoutInMinutes": 0, + "enableAccessToken": false, + "publishDeploymentStatus": true, + "badgeEnabled": false, + "autoLinkWorkItems": false, + "pullRequestDeploymentEnabled": false + }, + "demands": [], + "conditions": [ + { + "name": "dev", + "conditionType": 2, + "value": "4" + } + ], + "executionPolicy": { + "concurrencyCount": 1, + "queueDepthCount": 0 + }, + "schedules": [], + "retentionPolicy": { + "daysToKeep": 30, + "releasesToKeep": 3, + "retainBuild": true + }, + "processParameters": { + "inputs": [ + { + "aliases": [], + "options": { + "Azure Resource Manager": "Azure Resource Manager", + "Kubernetes Service Connection": "Kubernetes Service Connection" + }, + "properties": { + "EditableOptions": "false" + }, + "name": "connectionType", + "label": "Connection Type.", + "defaultValue": "Azure Resource Manager", + "type": "pickList", + "helpMarkDown": "", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "azureSubscriptionEndpoint", + "label": "Azure subscription", + "defaultValue": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "required": true, + "type": "connectedService:AzureRM", + "helpMarkDown": "Select an Azure subscription, which has your Azure Container Registry.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "azureResourceGroup", + "label": "Resource group", + "defaultValue": "RESOURCE_GROUP_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Resource Group.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "kubernetesCluster", + "label": "Kubernetes cluster", + "defaultValue": "CLUSTER_NAME_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Kubernetes Service cluster.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "kubernetesServiceEndpoint", + "label": "Kubernetes Service Connection", + "defaultValue": "", + "required": true, + "type": "connectedService:kubernetes", + "helpMarkDown": "Select a Kubernetes service connection.", + "visibleRule": "connectionType = Kubernetes Service Connection", + "groupName": "" + } + ], + "dataSourceBindings": [ + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "kubernetesCluster", + "resultTemplate": "{{{name}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + }, + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "azureResourceGroup", + "resultTemplate": "{{{ #extractResource id resourcegroups}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + } + ] + }, + "properties": {}, + "preDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "postDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "environmentTriggers": [], + "badgeUrl": null + }, + { + "id": 9, + "name": "production", + "rank": 4, + "owner": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "variables": { + "AI_IKEY": { + "value": "PROD_AI_IKEY_VAR_VAL", + "isSecret": true + }, + "COSMOSDB_COL_NAME": { + "value": "PROD_COSMOSDB_COL_NAME_VAR_VAL" + }, + "COSMOSDB_CONNECTION": { + "value": "PROD_COSMOSDB_CONNECTION_VAR_VAL", + "isSecret": true + }, + "SOURCE_ACR_SERVER": { + "value": "SOURCE_ACR_SERVER_VAR_VAL" + }, + "SOURCE_ACR_NAME": { + "value": "SOURCE_ACR_NAME_VAR_VAL" + }, + "ACR_SERVER": { + "value": "PROD_ACR_SERVER_VAR_VAL" + }, + "ACR_NAME": { + "value": "PROD_ACR_NAME_VAR_VAL" + }, + "CLUSTER_SUBNET_PREFIX": { + "value": "PROD_CLUSTER_SUBNET_PREFIX_VAR_VAL" + } + }, + "variableGroups": [], + "preDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": false, + "approver": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "isNotificationOn": false, + "approver": null, + "id": 21 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": true, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 1 + } + }, + "deployStep": { + "id": 22 + }, + "postDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 29 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 2 + } + }, + "deployPhases": [ + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [ + { + "alias": "ci-package", + "artifactType": "Build", + "artifactDownloadMode": "All", + "artifactItems": [] + } + ] + }, + "queueId": AZURE_DEVOPS_PACKAGE_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 1, + "phaseType": 1, + "name": "Agent job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "promote package image to production", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr import --name $(ACR_NAME) --source $(SOURCE_ACR_SERVER)/$(REPO_NAME):$(Build.SourceBranchName) --force", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(SOURCE_ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "install new package version in the green slot", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(SOURCE_ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),secrets.appinsights.ikey=$(AI_IKEY),networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",secrets.mongo.pwd=$(COSMOSDB_CONNECTION),cosmosDb.collectionName=$(COSMOSDB_COL_NAME),reason=\"$(REASON)\",envs.prod=true,package-prod.experimental=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + }, + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 2, + "phaseType": 2, + "name": "Agentless job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "bcb64569-d51a-4af0-9c01-ea5d05b3b622", + "version": "8.*", + "name": "Swap (blue-green)", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 3600, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "instructions": "consider running some canary or just resume for swapping blue and green versions", + "emailRecipients": "", + "onTimeout": "reject" + } + } + ] + }, + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [ + { + "alias": "ci-package", + "artifactType": "Build", + "artifactDownloadMode": "All", + "artifactItems": [] + } + ] + }, + "queueId": AZURE_DEVOPS_PACKAGE_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 3, + "phaseType": 1, + "name": "Agent job (swap)", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "cbc316a2-586f-4def-be79-488a1f503564", + "version": "1.*", + "name": "get current package blue slot version", + "refName": "BlueVersion", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "Azure Resource Manager", + "kubernetesServiceEndpoint": "", + "azureSubscriptionEndpoint": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "azureResourceGroup": "RESOURCE_GROUP_VAR_VAL", + "kubernetesCluster": "CLUSTER_NAME_VAR_VAL", + "useClusterAdmin": "false", + "namespace": "", + "command": "get", + "useConfigurationFile": "false", + "configurationType": "configuration", + "configuration": "", + "inline": "", + "arguments": "-n backend svc/package -o \"jsonpath={.spec.selector['app\\.kubernetes\\.io\\/instance']}\" --ignore-not-found=true", + "secretType": "dockerRegistry", + "secretArguments": "", + "containerRegistryType": "Azure Container Registry", + "dockerRegistryEndpoint": "", + "azureSubscriptionEndpointForSecrets": "", + "azureContainerRegistry": "", + "secretName": "", + "forceUpdate": "true", + "configMapName": "", + "forceUpdateConfigMap": "false", + "useConfigMapFile": "false", + "configMapFile": "", + "configMapArguments": "", + "versionOrLocation": "version", + "versionSpec": "1.12.4", + "checkLatest": "false", + "specifyLocation": "", + "cwd": "$(System.DefaultWorkingDirectory)", + "outputFormat": "" + } + }, + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(SOURCE_ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "cbc316a2-586f-4def-be79-488a1f503564", + "version": "1.*", + "name": "swap blue package version to green slot", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "Azure Resource Manager", + "kubernetesServiceEndpoint": "", + "azureSubscriptionEndpoint": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "azureResourceGroup": "RESOURCE_GROUP_VAR_VAL", + "kubernetesCluster": "CLUSTER_NAME_VAR_VAL", + "useClusterAdmin": "false", + "namespace": "", + "command": "set", + "useConfigurationFile": "false", + "configurationType": "configuration", + "configuration": "", + "inline": "", + "arguments": "selector -n backend svc/package-experimental app.kubernetes.io/name=package,app.kubernetes.io/instance=$(BlueVersion.KubectlOutput)", + "secretType": "dockerRegistry", + "secretArguments": "", + "containerRegistryType": "Azure Container Registry", + "dockerRegistryEndpoint": "", + "azureSubscriptionEndpointForSecrets": "", + "azureContainerRegistry": "", + "secretName": "", + "forceUpdate": "true", + "configMapName": "", + "forceUpdateConfigMap": "false", + "useConfigMapFile": "false", + "configMapFile": "", + "configMapArguments": "", + "versionOrLocation": "version", + "versionSpec": "1.12.4", + "checkLatest": "false", + "specifyLocation": "", + "cwd": "$(System.DefaultWorkingDirectory)", + "outputFormat": "json" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "install new package version in the blue slot", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(SOURCE_ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),secrets.appinsights.ikey=$(AI_IKEY),networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",secrets.mongo.pwd=$(COSMOSDB_CONNECTION),cosmosDb.collectionName=$(COSMOSDB_COL_NAME),reason=\"$(REASON)\",envs.prod=true,current=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + } + ], + "environmentOptions": { + "emailNotificationType": "OnlyOnFailure", + "emailRecipients": "release.environment.owner;release.creator", + "skipArtifactsDownload": false, + "timeoutInMinutes": 0, + "enableAccessToken": false, + "publishDeploymentStatus": true, + "badgeEnabled": false, + "autoLinkWorkItems": false, + "pullRequestDeploymentEnabled": false + }, + "demands": [], + "conditions": [ + { + "name": "QA", + "conditionType": 2, + "value": "4" + }, + { + "name": "staging", + "conditionType": 2, + "value": "4" + } + ], + "executionPolicy": { + "concurrencyCount": 1, + "queueDepthCount": 0 + }, + "schedules": [], + "retentionPolicy": { + "daysToKeep": 30, + "releasesToKeep": 3, + "retainBuild": true + }, + "processParameters": { + "inputs": [ + { + "aliases": [], + "options": { + "Azure Resource Manager": "Azure Resource Manager", + "Kubernetes Service Connection": "Kubernetes Service Connection" + }, + "properties": { + "EditableOptions": "false" + }, + "name": "connectionType", + "label": "Connection Type.", + "defaultValue": "Azure Resource Manager", + "type": "pickList", + "helpMarkDown": "", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "azureSubscriptionEndpoint", + "label": "Azure subscription", + "defaultValue": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "required": true, + "type": "connectedService:AzureRM", + "helpMarkDown": "Select an Azure subscription, which has your Azure Container Registry.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "azureResourceGroup", + "label": "Resource group", + "defaultValue": "RESOURCE_GROUP_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Resource Group.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "kubernetesCluster", + "label": "Kubernetes cluster", + "defaultValue": "CLUSTER_NAME_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Kubernetes Service cluster.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "kubernetesServiceEndpoint", + "label": "Kubernetes Service Connection", + "defaultValue": "", + "required": true, + "type": "connectedService:kubernetes", + "helpMarkDown": "Select a Kubernetes service connection.", + "visibleRule": "connectionType = Kubernetes Service Connection", + "groupName": "" + } + ], + "dataSourceBindings": [ + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "kubernetesCluster", + "resultTemplate": "{{{name}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + }, + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "azureResourceGroup", + "resultTemplate": "{{{ #extractResource id resourcegroups}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + } + ] + }, + "properties": {}, + "preDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "postDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "environmentTriggers": [], + "badgeUrl": null + } + ], + "artifacts": [ + { + "sourceId": "AZURE_DEVOPS_PROJECT_ID_VAR_VAL:AZURE_DEVOPS_PACKAGE_BUILD_ID_VAR_VAL", + "type": "Build", + "alias": "ci-package", + "definitionReference": { + "artifactSourceDefinitionUrl": { + "id": "", + "name": "" + }, + "defaultVersionBranch": { + "id": "", + "name": "" + }, + "defaultVersionSpecific": { + "id": "", + "name": "" + }, + "defaultVersionTags": { + "id": "", + "name": "" + }, + "defaultVersionType": { + "id": "selectDuringReleaseCreationType", + "name": "Specify at the time of release creation" + }, + "definition": { + "id": "AZURE_DEVOPS_PACKAGE_BUILD_ID_VAR_VAL", + "name": "aks-ri-ci-package" + }, + "definitions": { + "id": "", + "name": "" + }, + "IsMultiDefinitionType": { + "id": "False", + "name": "False" + }, + "project": { + "id": "AZURE_DEVOPS_PROJECT_ID_VAR_VAL", + "name": "roadmap" + }, + "repository": { + "id": "AZURE_DEVOPS_REPOS_ID_VAR_VALL", + "name": "roadmap" + } + }, + "isPrimary": true, + "isRetained": false + } + ], + "triggers": [ + { + "artifactAlias": "ci-package", + "triggerConditions": [ + { + "sourceBranch": "release/$(REPO_NAME)/v*", + "tags": [], + "useBuildDefinitionBranch": false, + "createReleaseOnBuildTagging": false + } + ], + "triggerType": 1 + } + ], + "releaseNameFormat": "release-$(rev:r)", + "tags": [], + "pipelineProcess": { + "type": 1 + }, + "properties": { + "DefinitionCreationSource": { + "$type": "System.String", + "$value": "Other" + } + }, + "id": 3, + "name": "package-cd", + "path": null, + "projectReference": null, + "url": null +} diff --git a/src/shipping/package/azure-pipelines.yml b/src/shipping/package/azure-pipelines.yml new file mode 100644 index 00000000..7c9f8ffe --- /dev/null +++ b/src/shipping/package/azure-pipelines.yml @@ -0,0 +1,117 @@ +variables: + repositoryName: package + chartPath: charts/package + dockerFileName: src/shipping/package/Dockerfile + imageName: $(repositoryName):$(Build.SourceBranchName) + azureSubscription: AZURE_PIPELINES_SERVICE_CONN_NAME_VAR_VAL + azureContainerRegistry: ACR_SERVER_VAR_VAL + azureContainerRegistryName: ACR_NAME_VAR_VAL + +name: $(build.sourceBranch)-$(Date:yyyyMMdd)$(Rev:.rr) + +pr: # only valid for GitHub. Using Azure repo it must be configure as a Branch Policy + paths: + include: + - /src/shipping/package/ + + branches: + include: + - master + - release/package/v* # for bug fixes + +trigger: + batch: true + branches: + include: + # for new release to production: release flow strategy + - release/package/v* + - refs/relelase/package/v* + - master + - feature/package/* + - topic/package/* + paths: + include: + - /src/shipping/package/ + +resources: +- repo: self + +jobs: + +# CI +- job: packagejobci + displayName: "Package CI" + pool: + vmImage: 'Ubuntu 16.04' + timeoutInMinutes: 90 + variables: + fullCI: $[ startsWith(variables['build.sourceBranch'], 'refs/heads/release/package/v') ] + buildImage: $[ eq(variables['build.sourceBranch'], 'refs/heads/master') ] + steps: + - task: Docker@1 + condition: or(eq(variables['buildImage'],True),eq(variables['fullCI'],True)) + displayName: 'Build runtime image' + inputs: + + azureSubscriptionEndpoint: $(azureSubscription) + + azureContainerRegistry: $(azureContainerRegistry) + + dockerFile: $(System.DefaultWorkingDirectory)/$(dockerFileName) + + includeLatestTag: true + + imageName: '$(imageName)' + + - task: Docker@1 + condition: eq(variables['fullCI'],True) + displayName: 'Push runtime image' + inputs: + azureSubscriptionEndpoint: $(azureSubscription) + + azureContainerRegistry: $(azureContainerRegistry) + + command: 'Push an image' + + imageName: '$(imageName)' + + includeSourceTags: false + + - task: HelmInstaller@0 + condition: eq(variables['fullCI'],True) + displayName: 'Install Helm 3.0.3' + inputs: + helmVersion: 3.0.3 + + checkLatestHelmVersion: false + + kubectlVersion: 1.12.4 + + checkLatestKubectl: false + + - task: HelmDeploy@0 + condition: eq(variables['fullCI'],True) + displayName: 'helm package' + inputs: + command: package + + chartPath: $(chartPath) + + chartVersion: $(Build.SourceBranchName) + + updateDependency: true + + save: false + + arguments: '--app-version $(Build.SourceBranchName)' + + - task: AzureCLI@1 + condition: eq(variables['fullCI'],True) + displayName: 'Push a helm package' + inputs: + azureSubscription: $(azureSubscription) + + scriptLocation: inlineScript + + inlineScript: | + az acr helm push $(System.ArtifactsDirectory)/$(repositoryName)-$(Build.SourceBranchName).tgz --name $(azureContainerRegistryName) --force; diff --git a/src/shipping/package/babel.config.js b/src/shipping/package/babel.config.js new file mode 100644 index 00000000..694b4a38 --- /dev/null +++ b/src/shipping/package/babel.config.js @@ -0,0 +1,13 @@ +module.exports = { + presets: [ + [ + '@babel/preset-env', + { + targets: { + node: 'current', + }, + }, + ], + '@babel/preset-typescript', + ], +}; diff --git a/src/bc-shipping/package/build/docker-compose.yml b/src/shipping/package/docker-compose.dev.yaml similarity index 61% rename from src/bc-shipping/package/build/docker-compose.yml rename to src/shipping/package/docker-compose.dev.yaml index 6201d572..dae26097 100644 --- a/src/bc-shipping/package/build/docker-compose.yml +++ b/src/shipping/package/docker-compose.dev.yaml @@ -3,20 +3,25 @@ services: app: build: context: . - dockerfile: dev.dockerfile + dockerfile: Dockerfile + depends_on: + - mongo + restart: on-failure command: bash ports: - "7080:80" - "7443:443" - volumes: - - ..:/app tty: true stdin_open: true - working_dir: /app + environment: + - NODE_ENV=development + - CONNECTION_STRING=mongodb://packagedb:27017/local + - COLLECTION_NAME=packages + - LOG_LEVEL = "debug" networks: dronedelivery: aliases: - - packageservice + - package mongo: image: mongo:3.5.6 ports: diff --git a/src/bc-shipping/package/down.sh b/src/shipping/package/down.sh old mode 100644 new mode 100755 similarity index 73% rename from src/bc-shipping/package/down.sh rename to src/shipping/package/down.sh index ec8aafb1..06fbf71b --- a/src/bc-shipping/package/down.sh +++ b/src/shipping/package/down.sh @@ -1,6 +1,6 @@ -docker-compose -p drone-package -f ./build/docker-compose.yml down +docker-compose -p drone-package -f ./docker-compose.dev.yaml down # cleanup the network if there are not containers using it if [ "$(docker network inspect dronedelivery --format "{{range .Containers}}T{{end}}")" == "" ]; then docker network rm dronedelivery -fi \ No newline at end of file +fi diff --git a/src/bc-shipping/package/gulpfile.js b/src/shipping/package/gulpfile.js similarity index 74% rename from src/bc-shipping/package/gulpfile.js rename to src/shipping/package/gulpfile.js index 37e2e334..22659348 100644 --- a/src/bc-shipping/package/gulpfile.js +++ b/src/shipping/package/gulpfile.js @@ -4,17 +4,16 @@ var nodemon = require('gulp-nodemon'); var del = require('del'); var mocha = require('gulp-mocha'); -gulp.task('set-env', function() { - +gulp.task('set-env', gulp.series(function(done) { process.env.CONNECTION_STRING = "mongodb://packagedb:27017/local"; process.env.COLLECTION_NAME = "packages"; process.env.CORRELATION_HEADER = "x-b3-traceid"; process.env.LOG_LEVEL = "debug" - return; -}); + done(); +})); // Generate types that can be used when working with api definitions -gulp.task ('create-api-classes', function() { +gulp.task ('create-api-classes', function(done) { let generated = "// GENERATED FILE - DO NOT EDIT\n\n"; @@ -26,13 +25,13 @@ gulp.task ('create-api-classes', function() { for (var gprop in definition.properties) { let propType = definition.properties[gprop]['type']; - + // If this is a reference to a class we will use that if (propType == null) { propType = definition.properties[gprop]['$ref'].split('/').splice(-1); } - // convert swagger integer to + // convert swagger integer to if (propType == "integer") propType = "number"; @@ -41,16 +40,24 @@ gulp.task ('create-api-classes', function() { } generated += "}\n\n"; } - + var fs = require('fs'); fs.writeFile("./app/models/api-models.ts", generated, function(err) { if(err) { return console.log(err); } - }); + }); + + done(); + }); -gulp.task('build', ['clean'], function () { +// delete all build output files +gulp.task('clean', gulp.series(function (done) { + return del(['.bin/'], done); + })); + +gulp.task('build', gulp.series('clean', function () { gulp.src('**/*.json', {'cwd':'app'}) .pipe(gulp.dest('.bin/app')); @@ -60,34 +67,29 @@ gulp.task('build', ['clean'], function () { .pipe(tsProject()); return tsResult.js.pipe(gulp.dest('.bin/app')); -}); +})); -gulp.task('serve', ['build', 'set-env'], function (cb) { +gulp.task('serve', gulp.series('build', 'set-env', function (cb) { return nodemon({ script: '.bin/app/main.js', ext: 'ts json', watch: 'app', env: { "PORT": 80 }, tasks: ["build"], - env: { 'NODE_ENV': 'dev' } + env: { 'NODE_ENV': 'development' } }) -}); +})); -// delete all build output files -gulp.task('clean', function (done) { - return del(['.bin/'], done); -}); - -gulp.task('build-test', ['build'], function () { +gulp.task('build-int-test', gulp.series('build', function () { var tsProject = ts.createProject('tsconfig.json'); - + var tsResult = gulp.src("test/**/*.ts", {'base':'.'}) .pipe(tsProject()); return tsResult.js.pipe(gulp.dest('.bin')); -}); +})); -gulp.task('test', ['build-test', 'set-env'], function() { - gulp.src("test/**/*.js", { cwd: '.bin' }) - .pipe(mocha({ reporter: 'list' })); -}); \ No newline at end of file +gulp.task('int-test', gulp.series('build-int-test', 'set-env', function() { + return gulp.src("test/**/*.js", { cwd: '.bin' }) + .pipe(mocha({ reporter: 'list', exit: true })); +})); diff --git a/src/shipping/package/jest.config.js b/src/shipping/package/jest.config.js new file mode 100644 index 00000000..2c379d78 --- /dev/null +++ b/src/shipping/package/jest.config.js @@ -0,0 +1,32 @@ +module.exports = { + testEnvironment: 'node', + bail: true, + verbose: true, + setupFilesAfterEnv: [ + 'jest-extended' + ], + testPathIgnorePatterns: [ + '/node_modules/' + ], + "moduleFileExtensions": ["js", "jsx", "json", "ts", "tsx"], + "collectCoverage": true, + "collectCoverageFrom": [ + "**/*.{ts,tsx,js,jsx}", + "!**/tests/models/*.{ts,tsx,js,jsx}", + "!**/node_modules/**", + "!**/build/**", + "!**/coverage/**" + ], + "transform": { + "\\.ts$": "ts-jest" + }, + "coverageThreshold": { + "global": { + "lines": 60, + } + }, + "coverageReporters": [ + "text", + "text-summary" + ] +}; diff --git a/src/shipping/package/package.json b/src/shipping/package/package.json new file mode 100644 index 00000000..d78938af --- /dev/null +++ b/src/shipping/package/package.json @@ -0,0 +1,64 @@ +{ + "name": "fabrikam-drone-delivery-package-service", + "version": "0.1.0", + "description": "Fabrikam Drone Delivery Package Service", + "scripts": { + "start": "gulp serve", + "build": "gulp build", + "clean": "gulp clean", + "test": "jest", + "api": "gulp create-api-classes", + "int-test": "gulp int-test", + "ncu": "ncu", + "ncu:update": "ncu -u" + }, + "repository": { + "type": "git", + "url": "" + }, + "keywords": [ + "Drone", + "Delivery", + "Package" + ], + "author": "Microsoft Patterns and Practices", + "license": "MIT", + "dependencies": { + "@koa/router": "^8.0.8", + "applicationinsights": "^1.2.0", + "koa": "^2.6.1", + "koa-bodyparser": "^4.2.1", + "koa-compress": "^3.0.0", + "mongodb": "3.2.1", + "swagger-jsdoc": "^4.0.0", + "winston": "^3.2.1" + }, + "devDependencies": { + "@babel/core": "^7.9.0", + "@babel/preset-env": "^7.9.0", + "@babel/preset-typescript": "^7.9.0", + "@types/jest": "^25.1.4", + "@types/koa": "^2.11.3", + "@types/koa-bodyparser": "^5.0.1", + "@types/koa-router": "^7.4.0", + "@types/mongodb": "^3.5.4", + "@types/node": "^11.10.5", + "babel-jest": "^25.2.3", + "del": "^4.0.0", + "gulp": "^4.0.0", + "gulp-cli": "^2.0.1", + "gulp-mocha": "^6.0.0", + "gulp-nodemon": "^2.4.2", + "gulp-typescript": "^5.0.0", + "gulp-util": "^3.0.8", + "jest": "^25.2.3", + "jest-extended": "^0.11.5", + "mocha": "^6.0.2", + "nodemon": "^1.19.4", + "npm-check-updates": "^3.0.2", + "supertest": "^4.0.2", + "ts-jest": "^25.2.1", + "ts-node": "^8.8.1", + "typescript": "^3.8.3" + } +} diff --git a/src/shipping/package/readme.md b/src/shipping/package/readme.md new file mode 100644 index 00000000..067aace5 --- /dev/null +++ b/src/shipping/package/readme.md @@ -0,0 +1,11 @@ +# Package service + +## Create local development environment + +1. Install Docker and docker-compose +2. From a bash CLI navigate to the project root and type `./up.sh` +3. try ```curl -X PUT --header 'Accept: application/json' 'http://localhost:7080/api/packages/42'``` + +> Known issue: +> ```'failed to connect to server [packagedb:27017] on first connect [MongoError: connect ECONNREFUSED 172.24.0.2:27017]'``` +> First time the package service starts, it might fail connecting to mongo. Package would automatically restart if needed. diff --git a/src/bc-shipping/package/test/models/test-data-access.ts b/src/shipping/package/tests/models/test-data-access.ts similarity index 100% rename from src/bc-shipping/package/test/models/test-data-access.ts rename to src/shipping/package/tests/models/test-data-access.ts diff --git a/src/shipping/package/tests/unit/healthz-controllers.test.ts b/src/shipping/package/tests/unit/healthz-controllers.test.ts new file mode 100644 index 00000000..caea169e --- /dev/null +++ b/src/shipping/package/tests/unit/healthz-controllers.test.ts @@ -0,0 +1,39 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +const supertest = require('supertest'); + +import { KoaApp } from '../../app/app'; + +const app = KoaApp.create('debug'); + +const server = app.listen(); + +afterAll((done) => { + server.close(done) +}); + +describe('HealthzControllers', () => { + const request = supertest(server); + + describe('GET /', () => { + it('<200> should always return with the package utilization information', async () => { + //Arrange + const expected = ['status']; + + // Act + const res = await request + .get('/healthz') + .expect('Content-Type', /json/) + .expect(200); + const pkg = res.body; + + // Assert + expect(Object.keys(pkg)).toEqual(expect.arrayContaining(expected)); + expect(pkg.status).toBe("OK"); + }); + }); + +}); diff --git a/src/shipping/package/tests/unit/package-controllers.test.ts b/src/shipping/package/tests/unit/package-controllers.test.ts new file mode 100644 index 00000000..f24cef19 --- /dev/null +++ b/src/shipping/package/tests/unit/package-controllers.test.ts @@ -0,0 +1,173 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +const supertest = require('supertest'); + +import * as apiModels from '../../app/models/api-models' +import { Package } from '../../app/models/package' + +import { KoaApp } from '../../app/app'; + +const app = KoaApp.create('debug'); + +// Add mocks to the context +const mockFindPackage = jest.fn(id => (id === "507f1f77bcf86cd799439011") ? new Package(id) : null); +const mockMapPackageDbToApi = jest.fn(pkg => { + if (pkg == null) return null; + + var pkgApi = new apiModels.Package(); + pkgApi.id = "507f1f77bcf86cd799439011"; + pkgApi.size = "small"; + pkgApi.weight = 1; + pkgApi.tag = "test"; + + return pkgApi; +}); + +const mockMapPackageApiToDb = jest.fn((pkg, id) => new Package(id)); +const mockAddPackage = jest.fn(pkg => { + switch (pkg._id) { + case "42": + return 2; + case "43": + throw new Error("mock error"); + default: + return 1; + } +}); + +const mockUpdatePackage = jest.fn(pkg => {return;}); +app.context.packageRepository = { + findPackage: mockFindPackage, + mapPackageDbToApi: mockMapPackageDbToApi, + mapPackageApiToDb: mockMapPackageApiToDb, + addPackage: mockAddPackage, + updatePackage: mockUpdatePackage +}; + +const server = app.listen(); + +afterAll((done) => { + server.close(done) +}); + +describe('PackageControllers', () => { + const request = supertest(server); + + describe('GET /', () => { + it('<404> should always return when the package id does not exist', async () => { + //Arrange + const ramdonId = "507f1f77bcf86cd799439012"; + + // Act + const res = await request + .get('/api/packages/' + ramdonId); + // Assert + expect(res.status).toBe(404); + }); + }); + + describe('GET /', () => { + it('<200> should always return with the package information', async () => { + //Arrange + const ramdonId = "507f1f77bcf86cd799439011"; + const expected = ['id', 'size', 'weight', 'tag']; + + // Act + const res = await request + .get('/api/packages/' + ramdonId) + .expect('Content-Type', /json/) + .expect(200); + const pkg = res.body; + + // Assert + expect(Object.keys(pkg)).toEqual(expect.arrayContaining(expected)); + expect(pkg.id).toBe("507f1f77bcf86cd799439011"); + expect(pkg.size).toBe("small"); + expect(pkg.weight).toBe(1); + expect(pkg.tag).toBe("test"); + }); + }); + + describe('PUT /', () => { + it('<204> should always return if exists', async () => { + //Arrange + const id = "42"; + + // Act + const res = await request + .put('/api/packages/' + id); + + // Assert + expect(res.status).toBe(204); + + }); + }); + + describe('PUT /', () => { + it('<201> should always return if not exist and autogenerate id', async () => { + //Arrange + const id = ""; + + // Act + const res = await request + .put('/api/packages/' + id); + + // Assert + expect(res.status).toBe(201); + + }); + }); + + describe('PATCH /', () => { + it('<204> should always return if updated', async () => { + //Arrange + const id = "42"; + + // Act + const res = await request + .patch('/api/packages/' + id); + + // Assert + expect(res.status).toBe(204); + + }); + }); + + describe('PUT /', () => { + it('<500> should return 500 if something went really wrong', async () => { + //Arrange + const id = "43"; + + // Act + const res = await request + .put('/api/packages/' + id); + + // Assert + expect(res.status).toBe(500); + + }); + }); + + describe('GET /', () => { + it('<200> should always return with the package utilization information', async () => { + //Arrange + const ownerId = "42"; + const expected = ['totalWeight']; + + // Act + const res = await request + .get('/api/packages/summary/' + ownerId) + .expect('Content-Type', /json/) + .expect(200); + const pkg = res.body; + + // Assert + expect(Object.keys(pkg)).toEqual(expect.arrayContaining(expected)); + expect(pkg.totalWeight).toBe(400); + }); + }); + +}); diff --git a/src/shipping/package/tests/unit/swagger-controllers.test.ts b/src/shipping/package/tests/unit/swagger-controllers.test.ts new file mode 100644 index 00000000..4b857eaa --- /dev/null +++ b/src/shipping/package/tests/unit/swagger-controllers.test.ts @@ -0,0 +1,45 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +const pkg = require('../../package.json'); +const supertest = require('supertest'); + +import { KoaApp } from '../../app/app'; + +const app = KoaApp.create('debug'); + +const server = app.listen(); + +afterAll((done) => { + server.close(done) +}); + +describe('SwaggerControllers', () => { + const request = supertest(server); + + describe('GET /', () => { + it('<200> should always return with the openAPI specinformation', async () => { + // Arrange + // N/A + + // Act + const res = await request + .get('/swagger/swagger.json') + .expect('Content-Type', /json/) + .expect(200); + + const spec = res.body; + + // Assert + const expected = ["openapi", "info", "basePath", "schemes", "consumes", "produces", "paths", "definitions", "components", "tags"]; + expect(Object.keys(spec)).toEqual(expect.arrayContaining(expected)); + expect(spec.info.title).toBe(pkg.name); + expect(spec.info.version).toBe(pkg.version); + expect(spec.info.description).toBe(pkg.description); + expect(spec.info.contact).toBe(pkg.author); + }); + }); + +}); diff --git a/src/shipping/package/tests/unit/util-settings.test.ts b/src/shipping/package/tests/unit/util-settings.test.ts new file mode 100644 index 00000000..84936e35 --- /dev/null +++ b/src/shipping/package/tests/unit/util-settings.test.ts @@ -0,0 +1,56 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +const supertest = require('supertest'); + +import { Settings } from '../../app/util/settings'; + +describe('Util Settings', () => { + + describe('Settings default values', () => { + it('settings should return fake values', async () => { + // Arrange + // N/A + + // Act + const colNameReceived = Settings.collectionName(); + const connStrReceived = Settings.connectionString(); + const containerNameReceived = Settings.containerName(); + const logLevelReceived = Settings.logLevel(); + + // Assert + expect(colNameReceived).not.toBeNull(); + expect(connStrReceived).not.toBeNull(); + expect(containerNameReceived).not.toBeNull(); + expect(logLevelReceived).not.toBeNull(); + }); + + }); describe('Settings values', () => { + it('settings should return fake values', async () => { + // Arrange + const colNameExpected = "test-col"; + process.env["COLLECTION_NAME"] = colNameExpected; + const connStrExpected = "test-connstr"; + process.env["CONNECTION_STRING"] = connStrExpected; + const containerNameExpected = "test-container"; + process.env["CONTAINER_NAME"] = containerNameExpected; + const logLevelExpected = "test-loglvl"; + process.env["LOG_LEVEL"] = logLevelExpected; + + // Act + const colNameReceived = Settings.collectionName(); + const connStrReceived = Settings.connectionString(); + const containerNameReceived = Settings.containerName(); + const logLevelReceived = Settings.logLevel(); + + // Assert + expect(colNameReceived).toBe(colNameExpected); + expect(connStrReceived).toBe(connStrExpected); + expect(containerNameReceived).toBe(containerNameExpected); + expect(logLevelReceived).toBe(logLevelExpected); + }); + }); + +}); diff --git a/src/bc-shipping/package/tsconfig.json b/src/shipping/package/tsconfig.json similarity index 56% rename from src/bc-shipping/package/tsconfig.json rename to src/shipping/package/tsconfig.json index 405d81c7..5ea83103 100644 --- a/src/bc-shipping/package/tsconfig.json +++ b/src/shipping/package/tsconfig.json @@ -2,13 +2,15 @@ "compilerOptions": { "outDir": ".bin", "module": "commonjs", - "target": "ES2015", - "noImplicitAny": false, - "sourceMap": true + "target": "es2017", + "noImplicitAny": true, + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true }, "exclude": [ "node_modules" ], "typeRoots": ["node_modules/@types"], "types": [".*"] -} \ No newline at end of file +} diff --git a/src/bc-shipping/package/up.sh b/src/shipping/package/up.sh old mode 100644 new mode 100755 similarity index 80% rename from src/bc-shipping/package/up.sh rename to src/shipping/package/up.sh index d6e969c2..8ba8275f --- a/src/bc-shipping/package/up.sh +++ b/src/shipping/package/up.sh @@ -8,6 +8,6 @@ if ! docker network ls | grep -q dronedelivery; then docker network create dronedelivery fi -docker-compose -p drone-package -f ./build/docker-compose.yml up -d +docker-compose -p drone-package -f ./docker-compose.dev.yaml up -d docker attach dronepackage_app_1 diff --git a/src/shipping/workflow/Dockerfile b/src/shipping/workflow/Dockerfile new file mode 100644 index 00000000..55945e01 --- /dev/null +++ b/src/shipping/workflow/Dockerfile @@ -0,0 +1,28 @@ +FROM mcr.microsoft.com/dotnet/core/runtime:3.1 AS base +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build +WORKDIR /src/Fabrikam.Workflow.Service + +COPY Fabrikam.Workflow.Service/Fabrikam.Workflow.Service.csproj . +RUN dotnet restore Fabrikam.Workflow.Service.csproj + +COPY Fabrikam.Workflow.Service/. . +RUN dotnet build Fabrikam.Workflow.Service.csproj -c release -o /app --no-restore + +FROM build AS testrunner +WORKDIR /src/tests + +COPY Fabrikam.Workflow.Service.Tests/*.csproj . +RUN dotnet restore Fabrikam.Workflow.Service.Tests.csproj + +COPY Fabrikam.Workflow.Service.Tests/. . +ENTRYPOINT ["dotnet", "test", "--logger:trx"] + +FROM build AS publish +RUN dotnet publish Fabrikam.Workflow.Service.csproj -c Release -o /app + +FROM base AS final +WORKDIR /app +COPY --from=publish /app . +ENTRYPOINT ["dotnet", "Fabrikam.Workflow.Service.dll"] \ No newline at end of file diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/DeliveryServiceCallerIntegrationTests.cs b/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/DeliveryServiceCallerIntegrationTests.cs new file mode 100644 index 00000000..92f095f5 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/DeliveryServiceCallerIntegrationTests.cs @@ -0,0 +1,183 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Http; +using Microsoft.Extensions.Logging; +using Fabrikam.Workflow.Service.Models; +using Fabrikam.Workflow.Service.Services; +using Fabrikam.Workflow.Service.Tests.Utils; +using Moq; +using Newtonsoft.Json; +using Xunit; + +namespace Fabrikam.Workflow.Service.Tests +{ + public class DeliveryServiceCallerIntegrationTests : IDisposable, IClassFixture + { + private const string DeliveryHost = "deliveryhost"; + private static readonly string DeliveryUri = $"http://{DeliveryHost}/api/Deliveries/"; + + private readonly TestServer _testServer; + private RequestDelegate _handleHttpRequest = ctx => Task.CompletedTask; + + private readonly IDeliveryServiceCaller _caller; + + public DeliveryServiceCallerIntegrationTests() + { + var context = new HostBuilderContext(new Dictionary()); + context.Configuration = + new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary { ["SERVICE_URI_DELIVERY"] = DeliveryUri }) + .AddEnvironmentVariables() + .Build(); + context.HostingEnvironment = + Mock.Of(e => e.EnvironmentName == "Test"); + + var serviceCollection = new ServiceCollection(); + ServiceStartup.ConfigureServices(context, serviceCollection); + serviceCollection.AddLogging(builder => builder.AddDebug()); + + _testServer = + new TestServer( + new WebHostBuilder() + .UseTestServer() + .Configure(builder => + { + builder.Run(ctx => _handleHttpRequest(ctx)); + }) + .ConfigureServices(builder => + { + builder.AddControllers(); + })); + _testServer.AllowSynchronousIO = true; + + serviceCollection.Replace( + ServiceDescriptor.Transient( + sp => new TestServerMessageHandlerBuilder(_testServer))); + var serviceProvider = serviceCollection.BuildServiceProvider(); + + _caller = serviceProvider.GetService(); + } + + public void Dispose() + { + _testServer.Dispose(); + } + + [Fact] + public async Task WhenSchedulingDelivery_ThenInvokesDeliveryAPI() + { + string actualDeliveryId = null; + DeliverySchedule actualDeliverySchedule = null; + _handleHttpRequest = ctx => + { + if (ctx.Request.Host.Host == DeliveryHost) + { + actualDeliveryId = ctx.Request.Path; + actualDeliverySchedule = + new JsonSerializer().Deserialize(new JsonTextReader(new StreamReader(ctx.Request.Body, Encoding.UTF8))); + ctx.Response.StatusCode = StatusCodes.Status201Created; + } + else + { + ctx.Response.StatusCode = StatusCodes.Status500InternalServerError; + } + + return Task.CompletedTask; + }; + + var delivery = + new Delivery + { + DeliveryId = "someDeliveryId", + PackageInfo = new PackageInfo { PackageId = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d } + }; + await _caller.ScheduleDeliveryAsync(delivery, "someDroneId"); + + Assert.NotNull(actualDeliveryId); + Assert.Equal($"/api/Deliveries/{delivery.DeliveryId}", actualDeliveryId); + + Assert.NotNull(actualDeliverySchedule); + Assert.Equal(delivery.DeliveryId, actualDeliverySchedule.Id); + Assert.Equal((int)delivery.ConfirmationRequired, (int)actualDeliverySchedule.ConfirmationRequired); + Assert.Equal(delivery.Expedited, actualDeliverySchedule.Expedited); + Assert.Equal(delivery.OwnerId, actualDeliverySchedule.Owner.UserId); + Assert.Equal("someDroneId", actualDeliverySchedule.DroneId); + } + + [Fact] + public async Task WhenDeliveryAPIReturnsCreated_ThenReturnsGeneratedSchedule() + { + _handleHttpRequest = async ctx => + { + if (ctx.Request.Host.Host == DeliveryHost) + { + await ctx.WriteResultAsync( + new ObjectResult( + new DeliverySchedule { Id = "someDeliveryId" }) + { + StatusCode = StatusCodes.Status201Created + }); + } + else + { + ctx.Response.StatusCode = StatusCodes.Status500InternalServerError; + } + }; + + var delivery = + new Delivery + { + DeliveryId = "someDeliveryId", + PackageInfo = new PackageInfo { PackageId = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d } + }; + var actualDeliverySchedule = await _caller.ScheduleDeliveryAsync(delivery, "someDroneId"); + + Assert.NotNull(actualDeliverySchedule); + Assert.Equal("someDeliveryId", actualDeliverySchedule.Id); + } + + [Fact] + public async Task WhenPackageAPIDoesNotReturnOK_ThenThrows() + { + _handleHttpRequest = ctx => + { + if (ctx.Request.Host.Host == DeliveryHost) + { + ctx.Response.StatusCode = StatusCodes.Status400BadRequest; + } + else + { + ctx.Response.StatusCode = StatusCodes.Status500InternalServerError; + } + + return Task.CompletedTask; + }; + + var delivery = + new Delivery + { + DeliveryId = "someDeliveryId", + PackageInfo = new PackageInfo { PackageId = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d } + }; + + await Assert.ThrowsAsync(() => _caller.ScheduleDeliveryAsync(delivery, "someDroneId")); + } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/DroneSchedulerServiceCallerIntegrationTests.cs b/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/DroneSchedulerServiceCallerIntegrationTests.cs new file mode 100644 index 00000000..c462c6f4 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/DroneSchedulerServiceCallerIntegrationTests.cs @@ -0,0 +1,175 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Http; +using Microsoft.Extensions.Logging; +using Fabrikam.Workflow.Service.Models; +using Fabrikam.Workflow.Service.Services; +using Fabrikam.Workflow.Service.Tests.Utils; +using Moq; +using Newtonsoft.Json; +using Xunit; + +namespace Fabrikam.Workflow.Service.Tests +{ + public class DroneSchedulerServiceCallerIntegrationTests : IDisposable, IClassFixture + { + private const string DroneSchedulerHost = "dronescheduler"; + private static readonly string DroneSchedulerUri = $"http://{DroneSchedulerHost}/api/DroneDeliveries/"; + + private readonly TestServer _testServer; + private RequestDelegate _handleHttpRequest = ctx => Task.CompletedTask; + + private readonly IDroneSchedulerServiceCaller _caller; + + public DroneSchedulerServiceCallerIntegrationTests() + { + var context = new HostBuilderContext(new Dictionary()); + context.Configuration = + new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary { ["SERVICE_URI_DRONE"] = DroneSchedulerUri }) + .AddEnvironmentVariables() + .Build(); + context.HostingEnvironment = + Mock.Of(e => e.EnvironmentName == "Test"); + + var serviceCollection = new ServiceCollection(); + ServiceStartup.ConfigureServices(context, serviceCollection); + serviceCollection.AddLogging(builder => builder.AddDebug()); + + _testServer = + new TestServer( + new WebHostBuilder() + .UseTestServer() + .Configure(builder => + { + builder.Run(ctx => _handleHttpRequest(ctx)); + }) + .ConfigureServices(builder => + { + builder.AddControllers(); + })); + _testServer.AllowSynchronousIO = true; + + serviceCollection.Replace( + ServiceDescriptor.Transient( + sp => new TestServerMessageHandlerBuilder(_testServer))); + var serviceProvider = serviceCollection.BuildServiceProvider(); + + _caller = serviceProvider.GetService(); + } + + public void Dispose() + { + _testServer.Dispose(); + } + + [Fact] + public async Task WhenGettingDroneId_ThenInvokesDroneSchedulerAPI() + { + string actualDeliveryId = null; + DroneDelivery actualDelivery = null; + _handleHttpRequest = ctx => + { + if (ctx.Request.Host.Host == DroneSchedulerHost) + { + actualDeliveryId = ctx.Request.Path; + actualDelivery = + new JsonSerializer().Deserialize(new JsonTextReader(new StreamReader(ctx.Request.Body, Encoding.UTF8))); + } + else + { + ctx.Response.StatusCode = StatusCodes.Status500InternalServerError; + } + + return Task.CompletedTask; + }; + + var delivery = + new Delivery + { + DeliveryId = "someDeliveryId", + PackageInfo = new PackageInfo { PackageId = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d } + }; + await _caller.GetDroneIdAsync(delivery); + + Assert.NotNull(actualDeliveryId); + Assert.Equal($"/api/DroneDeliveries/{delivery.DeliveryId}", actualDeliveryId); + + Assert.NotNull(actualDelivery); + Assert.Equal(delivery.DeliveryId, actualDelivery.DeliveryId); + Assert.Equal(delivery.PackageInfo.PackageId, actualDelivery.PackageDetail.Id); + Assert.Equal((int)delivery.PackageInfo.Size, (int)actualDelivery.PackageDetail.Size); + } + + [Fact] + public async Task WhenDroneSchedulerAPIReturnsOK_ThenReturnsDroneId() + { + _handleHttpRequest = async ctx => + { + if (ctx.Request.Host.Host == DroneSchedulerHost) + { + await ctx.WriteResultAsync(new ContentResult { Content = "someDroneId", StatusCode = StatusCodes.Status200OK }); + } + else + { + ctx.Response.StatusCode = StatusCodes.Status500InternalServerError; + } + }; + + var delivery = + new Delivery + { + DeliveryId = "someDeliveryId", + PackageInfo = new PackageInfo { PackageId = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d } + }; + var actualDroneId = await _caller.GetDroneIdAsync(delivery); + + Assert.Equal("someDroneId", actualDroneId); + } + + [Fact] + public async Task WhenDroneSchedulerAPIDoesNotReturnOK_ThenThrows() + { + _handleHttpRequest = ctx => + { + if (ctx.Request.Host.Host == DroneSchedulerHost) + { + ctx.Response.StatusCode = StatusCodes.Status400BadRequest; + } + else + { + ctx.Response.StatusCode = StatusCodes.Status500InternalServerError; + } + + return Task.CompletedTask; + }; + + var delivery = + new Delivery + { + DeliveryId = "someDeliveryId", + PackageInfo = new PackageInfo { PackageId = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d } + }; + + await Assert.ThrowsAsync(() => _caller.GetDroneIdAsync(delivery)); + } + } +} + diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/Fabrikam.Workflow.Service.Tests.csproj b/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/Fabrikam.Workflow.Service.Tests.csproj new file mode 100644 index 00000000..7324d1b6 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/Fabrikam.Workflow.Service.Tests.csproj @@ -0,0 +1,25 @@ + + + + netcoreapp3.1 + false + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + \ No newline at end of file diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/PackageServiceCallerIntegrationTests.cs b/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/PackageServiceCallerIntegrationTests.cs new file mode 100644 index 00000000..8bc7cc9f --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/PackageServiceCallerIntegrationTests.cs @@ -0,0 +1,374 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Http; +using Microsoft.Extensions.Logging; +using Fabrikam.Workflow.Service.Models; +using Fabrikam.Workflow.Service.Services; +using Fabrikam.Workflow.Service.Tests.Utils; +using Moq; +using Newtonsoft.Json; +using Xunit; + +namespace Fabrikam.Workflow.Service.Tests +{ + public class PackageServiceCallerIntegrationTests : IDisposable, IClassFixture + { + private const string PackageHost = "packagehost"; + private static readonly string PackageUri = $"http://{PackageHost}/api/packages/"; + + private readonly TestServer _testServer; + private RequestDelegate _handleHttpRequest = ctx => Task.CompletedTask; + + private readonly IPackageServiceCaller _caller; + + public PackageServiceCallerIntegrationTests() + { + var context = new HostBuilderContext(new Dictionary()); + context.Configuration = + new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary { ["SERVICE_URI_PACKAGE"] = PackageUri }) + .AddEnvironmentVariables() + .Build(); + context.HostingEnvironment = + Mock.Of(e => e.EnvironmentName == "Test"); + + var serviceCollection = new ServiceCollection(); + ServiceStartup.ConfigureServices(context, serviceCollection); + serviceCollection.AddLogging(builder => builder.AddDebug()); + + _testServer = + new TestServer( + new WebHostBuilder() + .UseTestServer() + .Configure(builder => + { + builder.Run(ctx => _handleHttpRequest(ctx)); + }) + .ConfigureServices(builder => + { + builder.AddControllers(); + })); + _testServer.AllowSynchronousIO = true; + + serviceCollection.Replace( + ServiceDescriptor.Transient( + sp => new TestServerMessageHandlerBuilder(_testServer))); + var serviceProvider = serviceCollection.BuildServiceProvider(); + + _caller = serviceProvider.GetService(); + } + + public void Dispose() + { + _testServer.Dispose(); + } + + [Fact] + public async Task WhenCreatingPackage_ThenInvokesDroneSchedulerAPI() + { + string actualPackageId = null; + PackageGen actualPackage = null; + _handleHttpRequest = ctx => + { + if (ctx.Request.Host.Host == PackageHost) + { + actualPackageId = ctx.Request.Path; + actualPackage = + new JsonSerializer().Deserialize(new JsonTextReader(new StreamReader(ctx.Request.Body, Encoding.UTF8))); + ctx.Response.StatusCode = StatusCodes.Status201Created; + } + else + { + ctx.Response.StatusCode = StatusCodes.Status500InternalServerError; + } + + return Task.CompletedTask; + }; + + var packageInfo = new PackageInfo { PackageId = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d }; + await _caller.UpsertPackageAsync(packageInfo); + + Assert.NotNull(actualPackageId); + Assert.Equal($"/api/packages/{packageInfo.PackageId}", actualPackageId); + + Assert.NotNull(actualPackage); + Assert.Equal((int)packageInfo.Size, (int)actualPackage.Size); + Assert.Equal(packageInfo.Tag, actualPackage.Tag); + Assert.Equal(packageInfo.Weight, actualPackage.Weight); + } + + [Fact] + public async Task WhenUpdatingPackage_ThenInvokesDroneSchedulerAPI() + { + // Arrange + string actualPackageId = null; + PackageGen actualPackage = null; + _handleHttpRequest = ctx => + { + if (ctx.Request.Host.Host == PackageHost && + ctx.Request.Method.Equals("PUT")) + { + ctx.Response.StatusCode = StatusCodes.Status204NoContent; + actualPackage = + new JsonSerializer() + .Deserialize( + new JsonTextReader( + new StreamReader( + ctx.Request.Body, + Encoding.UTF8))); + + } + else if (ctx.Request.Host.Host == PackageHost && + ctx.Request.Method.Equals("GET")) + { + actualPackageId = ctx.Request.Path; + } + else + { + ctx.Response.StatusCode = StatusCodes.Status500InternalServerError; + } + + return Task.CompletedTask; + }; + + var packageInfo = new PackageInfo { PackageId = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d }; + + // Act + await _caller.UpsertPackageAsync(packageInfo); + + // Assert + Assert.NotNull(actualPackageId); + Assert.Equal($"/api/packages/{packageInfo.PackageId}", actualPackageId); + + Assert.NotNull(actualPackage); + Assert.Equal((int)packageInfo.Size, (int)actualPackage.Size); + Assert.Equal(packageInfo.Tag, actualPackage.Tag); + Assert.Equal(packageInfo.Weight, actualPackage.Weight); + } + + [Fact] + public async Task WhenPackageAPIReturnsOK_ThenReturnsGeneratedPackage() + { + _handleHttpRequest = async ctx => + { + if (ctx.Request.Host.Host == PackageHost) + { + await ctx.WriteResultAsync( + new ObjectResult( + new PackageGen { Id = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d }) + { + StatusCode = StatusCodes.Status201Created + }); + } + else + { + ctx.Response.StatusCode = StatusCodes.Status500InternalServerError; + } + }; + + var packageInfo = new PackageInfo { PackageId = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d }; + var actualPackage = await _caller.UpsertPackageAsync(packageInfo); + + Assert.NotNull(actualPackage); + Assert.Equal((int)packageInfo.Size, (int)actualPackage.Size); + Assert.Equal(packageInfo.Tag, actualPackage.Tag); + Assert.Equal(packageInfo.Weight, actualPackage.Weight); + } + + [Fact] + public async Task WhenPackageAPIReturnsNoContent_ThenReturnsUpdatedPackage() + { + // Arrange + _handleHttpRequest = async ctx => + { + if (ctx.Request.Host.Host == PackageHost && + ctx.Request.Method.Equals("PUT")) + { + ctx.Response.StatusCode = StatusCodes.Status204NoContent; + } + else if (ctx.Request.Host.Host == PackageHost && + ctx.Request.Method.Equals("GET")) + { + await ctx.WriteResultAsync( + new ObjectResult( + new PackageGen { Id = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d }) + { + StatusCode = StatusCodes.Status200OK + }); + + } + else + { + ctx.Response.StatusCode = StatusCodes.Status500InternalServerError; + } + }; + + var packageInfo = new PackageInfo { PackageId = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d }; + + // Act + var actualPackage = await _caller.UpsertPackageAsync(packageInfo); + + // Assert + Assert.NotNull(actualPackage); + Assert.Equal((int)packageInfo.Size, (int)actualPackage.Size); + Assert.Equal(packageInfo.Tag, actualPackage.Tag); + Assert.Equal(packageInfo.Weight, actualPackage.Weight); + } + + [Fact] + public async Task WhenPackageAPIDoesNotReturnOK_ThenThrows() + { + _handleHttpRequest = ctx => + { + if (ctx.Request.Host.Host == PackageHost) + { + ctx.Response.StatusCode = StatusCodes.Status400BadRequest; + } + else + { + ctx.Response.StatusCode = StatusCodes.Status500InternalServerError; + } + + return Task.CompletedTask; + }; + + var packageInfo = new PackageInfo { PackageId = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d }; + + await Assert.ThrowsAsync(() => _caller.UpsertPackageAsync(packageInfo)); + } + + [Fact] + public async Task WhenRequestsFail_TheyAreRetried() + { + var receivedRequests = 0; + + _handleHttpRequest = async ctx => + { + if (ctx.Request.Host.Host == PackageHost) + { + await ctx.WriteResultAsync( + new ObjectResult( + new PackageGen { Id = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d }) + { + StatusCode = Interlocked.Increment(ref receivedRequests) <= 2 ? StatusCodes.Status500InternalServerError : StatusCodes.Status201Created + }); + } + else + { + ctx.Response.StatusCode = StatusCodes.Status500InternalServerError; + } + }; + + var result = + await _caller.UpsertPackageAsync(new PackageInfo { PackageId = "package", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d }); + + Assert.Equal(3, receivedRequests); + } + + [Fact] + public async Task WhenMultipleRequestsAreIssued_ThenBulkheadRestrictsConcurrentAccess() + { + const int totalRequests = 20; + var receivedRequests = 0; + var successfulRequests = 0; + var failedRequests = 0; + + _handleHttpRequest = async ctx => + { + if (ctx.Request.Host.Host == PackageHost) + { + Interlocked.Increment(ref receivedRequests); + await Task.Delay(TimeSpan.FromSeconds(1)); + await ctx.WriteResultAsync( + new ObjectResult( + new PackageGen { Id = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d }) + { + StatusCode = StatusCodes.Status201Created + }); + } + else + { + ctx.Response.StatusCode = StatusCodes.Status500InternalServerError; + } + }; + + var requestTasks = + Enumerable.Range(1, totalRequests) + .Select(async i => + { + try + { + var result = + await _caller.UpsertPackageAsync(new PackageInfo { PackageId = $"package{i}", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d }); + Interlocked.Increment(ref successfulRequests); + } + catch + { + Interlocked.Increment(ref failedRequests); + } + }); + await Task.WhenAll(requestTasks); + + Assert.Equal(8, receivedRequests); + Assert.Equal(8, successfulRequests); + Assert.Equal(12, failedRequests); + } + + [Fact] + public async Task WhenRequestsFailAboveTheThreshold_ThenCircuitBreakerBreaks() + { + const int totalRequests = 100; + var receivedRequests = 0; + var successfulRequests = 0; + var failedRequests = 0; + + _handleHttpRequest = ctx => + { + Interlocked.Increment(ref receivedRequests); + ctx.Response.StatusCode = StatusCodes.Status500InternalServerError; + return Task.CompletedTask; + }; + + var requestTasks = + Enumerable.Range(1, totalRequests) + .Select(async i => + { + try + { + await Task.Delay(TimeSpan.FromSeconds(i / 10)); + var result = + await _caller.UpsertPackageAsync(new PackageInfo { PackageId = $"package{i}", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d }); + Interlocked.Increment(ref successfulRequests); + } + catch + { + Interlocked.Increment(ref failedRequests); + } + }); + await Task.WhenAll(requestTasks); + + Assert.NotEqual(totalRequests * 4, receivedRequests); + Assert.Equal(0, successfulRequests); + Assert.Equal(totalRequests, failedRequests); + } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/ReadinessLivenessPublisherTests.cs b/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/ReadinessLivenessPublisherTests.cs new file mode 100644 index 00000000..37aa7a76 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/ReadinessLivenessPublisherTests.cs @@ -0,0 +1,120 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using Fabrikam.Workflow.Service.Services; +using Moq; +using Xunit; + +namespace Fabrikam.Workflow.Service.Tests +{ + public class ReadinessLivenessPublisherTests + { + private const int DelayCompletionMs = 1000; + + private readonly ReadinessLivenessPublisher _publisher; + + public ReadinessLivenessPublisherTests() + { + var servicesBuilder = new ServiceCollection(); + servicesBuilder.AddLogging(logging => logging.AddDebug()); + var services = servicesBuilder.BuildServiceProvider(); + + _publisher = + new ReadinessLivenessPublisher( + services.GetService>()); + } + + [Fact] + public async Task WhenPublishingAndReportIsHealthy_FileExists() + { + // Arrange + var healthReportEntries = new Dictionary() + { + {"healthy", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null) } + }; + + // Act + await _publisher.PublishAsync( + new HealthReport(healthReportEntries, TimeSpan.MinValue), + new CancellationTokenSource().Token); + + // Arrange + Assert.True(File.Exists(ReadinessLivenessPublisher.FilePath)); + } + + [Fact] + public async Task WhenPublishingAndReportIsUnhealthy_FileDateTimeIsNotModified() + { + // Arrange + var healthReportEntries = new Dictionary() + { + {"healthy", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null) } + }; + + await _publisher.PublishAsync( + new HealthReport( + healthReportEntries, + TimeSpan.MinValue), + new CancellationTokenSource().Token); + + healthReportEntries.Add( + "unhealthy", + new HealthReportEntry( + HealthStatus.Unhealthy, + null,TimeSpan.MinValue, null, null)); + + // Act + DateTime healthyWriteTime = File.GetLastWriteTime(ReadinessLivenessPublisher.FilePath); + await _publisher.PublishAsync( + new HealthReport(healthReportEntries, TimeSpan.MinValue), + new CancellationTokenSource().Token); + + // Arrange + Assert.True(File.Exists(ReadinessLivenessPublisher.FilePath)); + Assert.Equal(healthyWriteTime, File.GetLastWriteTime(ReadinessLivenessPublisher.FilePath)); + } + + [Fact(Timeout = DelayCompletionMs * 3)] + public async Task WhenPublishingAndReportIsHealthyTwice_FileDateTimeIsModified() + { + // Arrange + Func emulatePeriodicHealthCheckAsync = + () => Task.Delay(DelayCompletionMs); + + var healthReportEntries = new Dictionary() + { + {"healthy", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null) } + }; + + // Act + await _publisher.PublishAsync( + new HealthReport( + healthReportEntries, + TimeSpan.MinValue), + new CancellationTokenSource().Token); + + DateTime firstTimehealthyWriteTime = File.GetLastWriteTime(ReadinessLivenessPublisher.FilePath); + + await emulatePeriodicHealthCheckAsync(); + + await _publisher.PublishAsync( + new HealthReport(healthReportEntries, TimeSpan.MinValue), + new CancellationTokenSource().Token); + + DateTime sencondTimehealthyWriteTime = File.GetLastWriteTime(ReadinessLivenessPublisher.FilePath); + + // Arrange + Assert.True(firstTimehealthyWriteTime < sencondTimehealthyWriteTime); + } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/RequestProcessorIntegrationTests.cs b/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/RequestProcessorIntegrationTests.cs new file mode 100644 index 00000000..d4ac23d9 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/RequestProcessorIntegrationTests.cs @@ -0,0 +1,159 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Http; +using Microsoft.Extensions.Logging; +using Fabrikam.Workflow.Service.Models; +using Fabrikam.Workflow.Service.RequestProcessing; +using Fabrikam.Workflow.Service.Tests.Utils; +using Moq; +using Newtonsoft.Json; +using Xunit; + +namespace Fabrikam.Workflow.Service.Tests +{ + public class RequestProcessorIntegrationTests : IDisposable, IClassFixture + { + private const string DeliveryHost = "deliveryhost"; + private static readonly string DeliveryUri = $"http://{DeliveryHost}/api/Deliveries/"; + private const string DroneSchedulerHost = "dronescheduler"; + private static readonly string DroneSchedulerUri = $"http://{DroneSchedulerHost}/api/DroneDeliveries/"; + private const string PackageHost = "packagehost"; + private static readonly string PackageUri = $"http://{PackageHost}/api/packages/"; + + private readonly IRequestProcessor _requestProcessor; + private readonly TestServer _testServer; + private RequestDelegate _handleHttpRequest = ctx => Task.CompletedTask; + + public RequestProcessorIntegrationTests() + { + var context = new HostBuilderContext(new Dictionary()); + context.Configuration = + new ConfigurationBuilder() + .AddInMemoryCollection( + new Dictionary + { + ["SERVICE_URI_DELIVERY"] = DeliveryUri, + ["SERVICE_URI_DRONE"] = DroneSchedulerUri, + ["SERVICE_URI_PACKAGE"] = PackageUri + }) + .AddEnvironmentVariables() + .Build(); + context.HostingEnvironment = + Mock.Of(e => e.EnvironmentName == "Test"); + + var serviceCollection = new ServiceCollection(); + ServiceStartup.ConfigureServices(context, serviceCollection); + serviceCollection.AddLogging(builder => builder.AddDebug()); + + _testServer = + new TestServer( + new WebHostBuilder() + .UseTestServer() + .Configure(builder => + { + builder.Run(ctx => _handleHttpRequest(ctx)); + }) + .ConfigureServices(builder => + { + builder.AddControllers(); + })); + _testServer.AllowSynchronousIO = true; + + serviceCollection.Replace( + ServiceDescriptor.Transient( + sp => new TestServerMessageHandlerBuilder(_testServer))); + var serviceProvider = serviceCollection.BuildServiceProvider(); + + _requestProcessor = serviceProvider.GetService(); + } + + public void Dispose() + { + _testServer.Dispose(); + } + + [Fact] + public async Task ProcessingDelivery_InvokesPackageServiceAndDroneSchedulerService() + { + PackageGen actualPackage = null; + DroneDelivery actualDelivery = null; + DeliverySchedule actualDeliverySchedule = null; + _handleHttpRequest = async ctx => + { + var serializer = new JsonSerializer(); + + if (ctx.Request.Host.Host == PackageHost) + { + actualPackage = serializer.Deserialize(new JsonTextReader(new StreamReader(ctx.Request.Body, Encoding.UTF8))); + + await ctx.WriteResultAsync( + new ObjectResult( + new PackageGen { Id = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d }) + { + StatusCode = StatusCodes.Status201Created + }); + } + else if (ctx.Request.Host.Host == DroneSchedulerHost) + { + actualDelivery = serializer.Deserialize(new JsonTextReader(new StreamReader(ctx.Request.Body, Encoding.UTF8))); + + await ctx.WriteResultAsync(new ContentResult { Content = "someDroneId", StatusCode = StatusCodes.Status200OK }); + } + else if (ctx.Request.Host.Host == DeliveryHost) + { + actualDeliverySchedule = serializer.Deserialize(new JsonTextReader(new StreamReader(ctx.Request.Body, Encoding.UTF8))); + + await ctx.WriteResultAsync( + new ObjectResult(new DeliverySchedule { Id = "someDeliveryId" }) + { + StatusCode = StatusCodes.Status201Created + }); + } + else + { + ctx.Response.StatusCode = StatusCodes.Status500InternalServerError; + } + }; + + var delivery = + new Delivery + { + DeliveryId = "someDeliveryId", + PackageInfo = new PackageInfo { PackageId = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d } + }; + await _requestProcessor.ProcessDeliveryRequestAsync(delivery, new Dictionary()); + + Assert.NotNull(actualPackage); + Assert.Equal((int)delivery.PackageInfo.Size, (int)actualPackage.Size); + Assert.Equal(delivery.PackageInfo.Tag, actualPackage.Tag); + Assert.Equal(delivery.PackageInfo.Weight, actualPackage.Weight); + + Assert.NotNull(actualDelivery); + Assert.Equal(delivery.DeliveryId, actualDelivery.DeliveryId); + Assert.Equal(delivery.PackageInfo.PackageId, actualDelivery.PackageDetail.Id); + Assert.Equal((int)delivery.PackageInfo.Size, (int)actualDelivery.PackageDetail.Size); + + Assert.NotNull(actualDeliverySchedule); + Assert.Equal(delivery.DeliveryId, actualDeliverySchedule.Id); + Assert.Equal("someDroneId", actualDeliverySchedule.DroneId); + } + } +} + diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/RequestProcessorTests.cs b/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/RequestProcessorTests.cs new file mode 100644 index 00000000..bf690381 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/RequestProcessorTests.cs @@ -0,0 +1,215 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Fabrikam.Workflow.Service.Models; +using Fabrikam.Workflow.Service.RequestProcessing; +using Fabrikam.Workflow.Service.Services; +using Moq; +using Xunit; + +namespace Fabrikam.Workflow.Service.Tests +{ + public class RequestProcessorTests + { + private readonly Mock _packageServiceCallerMock; + private readonly Mock _droneSchedulerServiceCallerMock; + private readonly Mock _deliveryServiceCallerMock; + private readonly RequestProcessor _processor; + + public RequestProcessorTests() + { + var servicesBuilder = new ServiceCollection(); + servicesBuilder.AddLogging(logging => logging.AddDebug()); + var services = servicesBuilder.BuildServiceProvider(); + + _packageServiceCallerMock = new Mock(); + _droneSchedulerServiceCallerMock = new Mock(); + _deliveryServiceCallerMock = new Mock(); + + _processor = + new RequestProcessor( + services.GetService>(), + _packageServiceCallerMock.Object, + _droneSchedulerServiceCallerMock.Object, + _deliveryServiceCallerMock.Object); + } + + [Fact] + public async Task WhenInvokingPackageServiceThrows_ProcessingFails() + { + _packageServiceCallerMock + .Setup(c => c.UpsertPackageAsync(It.IsAny())) + .ThrowsAsync(new Exception()).Verifiable(); + + var delivery = + new Delivery + { + DeliveryId = "someDeliveryId", + PackageInfo = new PackageInfo { PackageId = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d } + }; + var success = await _processor.ProcessDeliveryRequestAsync(delivery, new Dictionary()); + + Assert.False(success); + _packageServiceCallerMock.Verify(); + _droneSchedulerServiceCallerMock.VerifyNoOtherCalls(); + _deliveryServiceCallerMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task WhenInvokingPackageServiceFails_ProcessingFails() + { + _packageServiceCallerMock + .Setup(c => c.UpsertPackageAsync(It.IsAny())) + .ReturnsAsync(default(PackageGen)).Verifiable(); + + var delivery = + new Delivery + { + DeliveryId = "someDeliveryId", + PackageInfo = new PackageInfo { PackageId = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d } + }; + var success = await _processor.ProcessDeliveryRequestAsync(delivery, new Dictionary()); + + Assert.False(success); + _packageServiceCallerMock.Verify(); + _droneSchedulerServiceCallerMock.VerifyNoOtherCalls(); + _deliveryServiceCallerMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task WhenInvokingDroneSchedulerThrows_ProcessingFails() + { + _packageServiceCallerMock + .Setup(c => c.UpsertPackageAsync(It.IsAny())) + .ReturnsAsync(new PackageGen { Id = "someid" }).Verifiable(); + _droneSchedulerServiceCallerMock + .Setup(c => c.GetDroneIdAsync(It.IsAny())) + .ThrowsAsync(new Exception()).Verifiable(); + + var delivery = + new Delivery + { + DeliveryId = "someDeliveryId", + PackageInfo = new PackageInfo { PackageId = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d } + }; + var success = await _processor.ProcessDeliveryRequestAsync(delivery, new Dictionary()); + + Assert.False(success); + _packageServiceCallerMock.Verify(); + _droneSchedulerServiceCallerMock.Verify(); + _deliveryServiceCallerMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task WhenInvokingDroneSchedulerFails_ProcessingFails() + { + _packageServiceCallerMock + .Setup(c => c.UpsertPackageAsync(It.IsAny())) + .ReturnsAsync(new PackageGen { Id = "someid" }).Verifiable(); + _droneSchedulerServiceCallerMock + .Setup(c => c.GetDroneIdAsync(It.IsAny())) + .ReturnsAsync(default(string)).Verifiable(); + + var delivery = + new Delivery + { + DeliveryId = "someDeliveryId", + PackageInfo = new PackageInfo { PackageId = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d } + }; + var success = await _processor.ProcessDeliveryRequestAsync(delivery, new Dictionary()); + + Assert.False(success); + _packageServiceCallerMock.Verify(); + _droneSchedulerServiceCallerMock.Verify(); + _deliveryServiceCallerMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task WhenInvokingDeliverySchedulerThrows_ProcessingFails() + { + _packageServiceCallerMock + .Setup(c => c.UpsertPackageAsync(It.IsAny())) + .ReturnsAsync(new PackageGen { Id = "someid" }).Verifiable(); + _droneSchedulerServiceCallerMock + .Setup(c => c.GetDroneIdAsync(It.IsAny())) + .ReturnsAsync("droneId").Verifiable(); + _deliveryServiceCallerMock + .Setup(c => c.ScheduleDeliveryAsync(It.IsAny(), "droneId")) + .ThrowsAsync(new Exception()).Verifiable(); + + var delivery = + new Delivery + { + DeliveryId = "someDeliveryId", + PackageInfo = new PackageInfo { PackageId = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d } + }; + var success = await _processor.ProcessDeliveryRequestAsync(delivery, new Dictionary()); + + Assert.False(success); + _packageServiceCallerMock.Verify(); + _droneSchedulerServiceCallerMock.Verify(); + _deliveryServiceCallerMock.Verify(); + } + + [Fact] + public async Task WhenInvokingDeliverySchedulerFails_ProcessingFails() + { + _packageServiceCallerMock + .Setup(c => c.UpsertPackageAsync(It.IsAny())) + .ReturnsAsync(new PackageGen { Id = "someid" }).Verifiable(); + _droneSchedulerServiceCallerMock + .Setup(c => c.GetDroneIdAsync(It.IsAny())) + .ReturnsAsync("droneId").Verifiable(); + _deliveryServiceCallerMock + .Setup(c => c.ScheduleDeliveryAsync(It.IsAny(), "droneId")) + .ReturnsAsync(default(DeliverySchedule)).Verifiable(); + + var delivery = + new Delivery + { + DeliveryId = "someDeliveryId", + PackageInfo = new PackageInfo { PackageId = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d } + }; + var success = await _processor.ProcessDeliveryRequestAsync(delivery, new Dictionary()); + + Assert.False(success); + _packageServiceCallerMock.Verify(); + _droneSchedulerServiceCallerMock.Verify(); + _deliveryServiceCallerMock.Verify(); + } + + [Fact] + public async Task WhenProcessingAValidDelivery_ProcessingSucceeds() + { + _packageServiceCallerMock + .Setup(c => c.UpsertPackageAsync(It.IsAny())) + .ReturnsAsync(new PackageGen { Id = "someid" }).Verifiable(); + _droneSchedulerServiceCallerMock + .Setup(c => c.GetDroneIdAsync(It.IsAny())) + .ReturnsAsync("droneId").Verifiable(); + _deliveryServiceCallerMock + .Setup(c => c.ScheduleDeliveryAsync(It.IsAny(), "droneId")) + .ReturnsAsync(new DeliverySchedule { Id = "someDeliveryId" }).Verifiable(); + + var delivery = + new Delivery + { + DeliveryId = "someDeliveryId", + PackageInfo = new PackageInfo { PackageId = "somePackageId", Size = ContainerSize.Medium, Tag = "sometag", Weight = 100d } + }; + var success = await _processor.ProcessDeliveryRequestAsync(delivery, new Dictionary()); + + Assert.True(success); + _packageServiceCallerMock.Verify(); + _droneSchedulerServiceCallerMock.Verify(); + _deliveryServiceCallerMock.Verify(); + } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/ResiliencyEnvironmentVariablesFixture.cs b/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/ResiliencyEnvironmentVariablesFixture.cs new file mode 100644 index 00000000..2335cc94 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/ResiliencyEnvironmentVariablesFixture.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; + +namespace Fabrikam.Workflow.Service.Tests +{ + public class ResiliencyEnvironmentVariablesFixture + { + public ResiliencyEnvironmentVariablesFixture() + { + Environment.SetEnvironmentVariable("SERVICEREQUEST__MAXRETRIES", "3"); + Environment.SetEnvironmentVariable("SERVICEREQUEST__CIRCUITBREAKERTHRESHOLD", "0.75"); + Environment.SetEnvironmentVariable("SERVICEREQUEST__CIRCUITBREAKERSAMPLINGPERIODSECONDS", "5"); + Environment.SetEnvironmentVariable("SERVICEREQUEST__CIRCUITBREAKERMINIMUMTHROUGHPUT", "2"); + Environment.SetEnvironmentVariable("SERVICEREQUEST__CIRCUITBREAKERBREAKDURATIONSECONDS", "5"); + Environment.SetEnvironmentVariable("SERVICEREQUEST__MAXBULKHEADSIZE", "5"); + Environment.SetEnvironmentVariable("SERVICEREQUEST__MAXBULKHEADQUEUESIZE", "3"); + } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/Utils/HttpContextTestExtensions.cs b/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/Utils/HttpContextTestExtensions.cs new file mode 100644 index 00000000..fd6e5036 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/Utils/HttpContextTestExtensions.cs @@ -0,0 +1,45 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; + +namespace Fabrikam.Workflow.Service.Tests.Utils +{ + public static class HttpContextTestExtensions + { + private static readonly RouteData EmptyRouteData = new RouteData(); + + private static readonly ActionDescriptor EmptyActionDescriptor = new ActionDescriptor(); + + public static Task WriteResultAsync(this HttpContext context, TResult result) + where TResult : IActionResult + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var executor = context.RequestServices.GetService>(); + + if (executor == null) + { + throw new InvalidOperationException($"No result executor for '{typeof(TResult).FullName}' has been registered."); + } + + var routeData = context.GetRouteData() ?? EmptyRouteData; + + var actionContext = new ActionContext(context, routeData, EmptyActionDescriptor); + + return executor.ExecuteAsync(actionContext, result); + } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/Utils/TestServerMessageHandlerBuilder.cs b/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/Utils/TestServerMessageHandlerBuilder.cs new file mode 100644 index 00000000..dbd19a31 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service.Tests/Utils/TestServerMessageHandlerBuilder.cs @@ -0,0 +1,29 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using System.Net.Http; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Http; + +namespace Fabrikam.Workflow.Service.Tests.Utils +{ + public class TestServerMessageHandlerBuilder : HttpMessageHandlerBuilder + { + public TestServerMessageHandlerBuilder(TestServer testServer) + { + AdditionalHandlers = new List(); + PrimaryHandler = testServer.CreateHandler(); + } + + public override string Name { get; set; } + + public override HttpMessageHandler PrimaryHandler { get; set; } + + public override IList AdditionalHandlers { get; } + + public override HttpMessageHandler Build() => CreateHandlerPipeline(PrimaryHandler, AdditionalHandlers); + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Fabrikam.Workflow.Service.csproj b/src/shipping/workflow/Fabrikam.Workflow.Service/Fabrikam.Workflow.Service.csproj new file mode 100644 index 00000000..ce944744 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Fabrikam.Workflow.Service.csproj @@ -0,0 +1,38 @@ + + + + Exe + netcoreapp3.1 + 7.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Models/ConfirmationRequired.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/ConfirmationRequired.cs new file mode 100644 index 00000000..04cacec5 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/ConfirmationRequired.cs @@ -0,0 +1,15 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +namespace Fabrikam.Workflow.Service.Models +{ + public enum ConfirmationRequired + { + FingerPrint, + Picture, + Voice, + None + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Models/ConfirmationType.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/ConfirmationType.cs new file mode 100644 index 00000000..3c1f2646 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/ConfirmationType.cs @@ -0,0 +1,15 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +namespace Fabrikam.Workflow.Service.Models +{ + public enum ConfirmationType + { + FingerPrint, + Picture, + Voice, + None + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Models/ContainerSize.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/ContainerSize.cs new file mode 100644 index 00000000..27f09ebf --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/ContainerSize.cs @@ -0,0 +1,12 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +namespace Fabrikam.Workflow.Service.Models +{ + public enum ContainerSize + { + Small, Medium, Large + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Models/Delivery.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/Delivery.cs new file mode 100644 index 00000000..af1324dd --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/Delivery.cs @@ -0,0 +1,22 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; + +namespace Fabrikam.Workflow.Service.Models +{ + public class Delivery + { + public string DeliveryId { get; set; } + public string OwnerId { get; set; } + public string PickupLocation { get; set; } + public string DropoffLocation { get; set; } + public string Deadline { get; set; } + public bool Expedited { get; set; } + public ConfirmationRequired ConfirmationRequired { get; set; } + public DateTime PickupTime { get; set; } + public PackageInfo PackageInfo { get; set; } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Models/DeliverySchedule.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/DeliverySchedule.cs new file mode 100644 index 00000000..a22f248c --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/DeliverySchedule.cs @@ -0,0 +1,19 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +namespace Fabrikam.Workflow.Service.Models +{ + public class DeliverySchedule + { + public string Id { get; set; } + public UserAccount Owner { get; set; } + public Location Pickup { get; set; } + public Location Dropoff { get; set; } + public string Deadline { get; set; } + public bool Expedited { get; set; } + public ConfirmationType ConfirmationRequired { get; set; } + public string DroneId { get; set; } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Models/DroneDelivery.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/DroneDelivery.cs new file mode 100644 index 00000000..3ebc8a35 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/DroneDelivery.cs @@ -0,0 +1,16 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +namespace Fabrikam.Workflow.Service.Models +{ + public class DroneDelivery + { + public string DeliveryId { get; set; } + public Location Pickup { get; set; } + public Location Dropoff { get; set; } + public PackageDetail PackageDetail { get; set; } + public bool Expedited { get; set; } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Models/Location.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/Location.cs new file mode 100644 index 00000000..a47d7b1f --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/Location.cs @@ -0,0 +1,14 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +namespace Fabrikam.Workflow.Service.Models +{ + public class Location + { + public double Altitude { get; set; } + public double Latitude { get; set; } + public double Longitude { get; set; } + } +} \ No newline at end of file diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Models/PackageDetail.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/PackageDetail.cs new file mode 100644 index 00000000..56579ddc --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/PackageDetail.cs @@ -0,0 +1,13 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +namespace Fabrikam.Workflow.Service.Models +{ + public class PackageDetail + { + public string Id { get; set; } + public PackageSize Size { get; set; } + } +} \ No newline at end of file diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Models/PackageGen.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/PackageGen.cs new file mode 100644 index 00000000..f9ef2501 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/PackageGen.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Fabrikam.Workflow.Service.Models +{ + public class PackageGen + { + [JsonProperty("id")] + public string Id { get; set; } + [JsonProperty("size")] + [JsonConverter(typeof(StringEnumConverter))] + public ContainerSize Size { get; set; } + [JsonProperty("tag")] + public string Tag { get; set; } + [JsonProperty("weight")] + public double Weight { get; set; } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Models/PackageInfo.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/PackageInfo.cs new file mode 100644 index 00000000..5e3a546d --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/PackageInfo.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Fabrikam.Workflow.Service.Models +{ + public class PackageInfo + { + [JsonProperty("packageId")] + public string PackageId { get; set; } + [JsonProperty("size")] + [JsonConverter(typeof(StringEnumConverter))] + public ContainerSize Size { get; set; } + [JsonProperty("weight")] + public double Weight { get; set; } + [JsonProperty("tag")] + public string Tag { get; set; } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Models/PackageSize.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/PackageSize.cs new file mode 100644 index 00000000..e5799586 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/PackageSize.cs @@ -0,0 +1,12 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +namespace Fabrikam.Workflow.Service.Models +{ + public enum PackageSize + { + Small, Medium, Large + } +} \ No newline at end of file diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Models/UserAccount.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/UserAccount.cs new file mode 100644 index 00000000..7ffa6be2 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Models/UserAccount.cs @@ -0,0 +1,13 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +namespace Fabrikam.Workflow.Service.Models +{ + public class UserAccount + { + public string UserId { get; set; } + public string AccountId { get; set; } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Program.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Program.cs new file mode 100644 index 00000000..312f3b50 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Program.cs @@ -0,0 +1,61 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System.IO; +using System.Threading.Tasks; +using Microsoft.ApplicationInsights; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Serilog; +using Serilog.Formatting.Compact; + +namespace Fabrikam.Workflow.Service +{ + class Program + { + static async Task Main(string[] args) + { + var host = CreateHostBuilder(args).Build(); + + var logger = host.Services.GetRequiredService>(); + logger.LogInformation("Fabrikan Workflow Service is starting."); + + await host.RunAsync(); + } + + private static IHostBuilder CreateHostBuilder(string[] args) + { + return new HostBuilder() + .ConfigureAppConfiguration((context, builder) => + { + builder + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: true) + .AddEnvironmentVariables(); + + var buildConfig = builder.Build(); + if (buildConfig["CONFIGURATION_FOLDER"] is var configurationFolder && !string.IsNullOrEmpty(configurationFolder)) + { + builder.AddKeyPerFile(Path.Combine(context.HostingEnvironment.ContentRootPath, configurationFolder), false); + } + }) + .ConfigureLogging((context, builder) => + { + builder.AddConfiguration(context.Configuration.GetSection("Logging")); + builder.AddApplicationInsights(); + + var serilogBuilder = new LoggerConfiguration() + .ReadFrom.Configuration(context.Configuration) + .WriteTo.Console(new CompactJsonFormatter()); + + builder.AddSerilog(serilogBuilder.CreateLogger(), true); + }) + .ConfigureServices(ServiceStartup.ConfigureServices) + .UseConsoleLifetime(); + } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/RequestProcessing/IRequestProcessor.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/RequestProcessing/IRequestProcessor.cs new file mode 100644 index 00000000..9cc5f378 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/RequestProcessing/IRequestProcessor.cs @@ -0,0 +1,16 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using System.Threading.Tasks; +using Fabrikam.Workflow.Service.Models; + +namespace Fabrikam.Workflow.Service.RequestProcessing +{ + public interface IRequestProcessor + { + Task ProcessDeliveryRequestAsync(Delivery deliveryRequest, IReadOnlyDictionary properties); + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/RequestProcessing/RequestProcessor.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/RequestProcessing/RequestProcessor.cs new file mode 100644 index 00000000..926d3090 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/RequestProcessing/RequestProcessor.cs @@ -0,0 +1,71 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Fabrikam.Workflow.Service.Models; +using Fabrikam.Workflow.Service.Services; + +namespace Fabrikam.Workflow.Service.RequestProcessing +{ + public class RequestProcessor : IRequestProcessor + { + private readonly ILogger _logger; + private readonly IPackageServiceCaller _packageServiceCaller; + private readonly IDroneSchedulerServiceCaller _droneSchedulerServiceCaller; + private readonly IDeliveryServiceCaller _deliveryServiceCaller; + + public RequestProcessor( + ILogger logger, + IPackageServiceCaller packageServiceCaller, + IDroneSchedulerServiceCaller droneSchedulerServiceCaller, + IDeliveryServiceCaller deliveryServiceCaller) + { + _logger = logger; + _packageServiceCaller = packageServiceCaller; + _droneSchedulerServiceCaller = droneSchedulerServiceCaller; + _deliveryServiceCaller = deliveryServiceCaller; + } + + public async Task ProcessDeliveryRequestAsync(Delivery deliveryRequest, IReadOnlyDictionary properties) + { + _logger.LogInformation("Processing delivery request {deliveryId}", deliveryRequest.DeliveryId); + + try + { + var packageGen = await _packageServiceCaller.UpsertPackageAsync(deliveryRequest.PackageInfo).ConfigureAwait(false); + if (packageGen != null) + { + _logger.LogInformation("Generated package {packageId} for delivery {deliveryId}", packageGen.Id, deliveryRequest.DeliveryId); + + var droneId = await _droneSchedulerServiceCaller.GetDroneIdAsync(deliveryRequest).ConfigureAwait(false); + if (droneId != null) + { + _logger.LogInformation("Assigned drone {droneId} for delivery {deliveryId}", droneId, deliveryRequest.DeliveryId); + + var deliverySchedule = await _deliveryServiceCaller.ScheduleDeliveryAsync(deliveryRequest, droneId); + if (deliverySchedule != null) + { + _logger.LogInformation("Completed delivery {deliveryId}", deliveryRequest.DeliveryId); + return true; + } + else + { + _logger.LogError("Failed delivery for request {deliveryId}", deliveryRequest.DeliveryId); + } + } + } + } + catch (Exception e) + { + _logger.LogError(e, "Error processing delivery request {deliveryId}", deliveryRequest.DeliveryId); + } + + return false; + } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/ResiliencyExtensions.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/ResiliencyExtensions.cs new file mode 100644 index 00000000..a80d5fd3 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/ResiliencyExtensions.cs @@ -0,0 +1,47 @@ +using System; +using System.Net.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Polly; + +namespace Fabrikam.Workflow.Service +{ + internal static class ResiliencyExtensions + { + public static IHttpClientBuilder AddResiliencyPolicies(this IHttpClientBuilder builder, IConfiguration configuration) + { + var resiliencyConfiguration = configuration.GetSection("ServiceRequest").Get(); + + builder + .AddPolicyHandler( + Policy.BulkheadAsync(resiliencyConfiguration.MaxBulkheadSize, resiliencyConfiguration.MaxBulkheadQueueSize)) + .AddTransientHttpErrorPolicy(p => + p.AdvancedCircuitBreakerAsync( + resiliencyConfiguration.CircuitBreakerThreshold, + TimeSpan.FromSeconds(resiliencyConfiguration.CircuitBreakerSamplingPeriodSeconds), + resiliencyConfiguration.CircuitBreakerMinimumThroughput, + TimeSpan.FromSeconds(resiliencyConfiguration.CircuitBreakerBreakDurationSeconds))) + .AddTransientHttpErrorPolicy(p => + p.WaitAndRetryAsync(resiliencyConfiguration.MaxRetries, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt) - 2))); + + return builder; + } + + private class ResiliencyConfiguration + { + public int MaxRetries { get; set; } + + public double CircuitBreakerThreshold { get; set; } + + public int CircuitBreakerSamplingPeriodSeconds { get; set; } + + public int CircuitBreakerMinimumThroughput { get; set; } + + public int CircuitBreakerBreakDurationSeconds { get; set; } + + public int MaxBulkheadSize { get; set; } + + public int MaxBulkheadQueueSize { get; set; } + } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/ServiceStartup.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/ServiceStartup.cs new file mode 100644 index 00000000..0c63fd57 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/ServiceStartup.cs @@ -0,0 +1,78 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Hosting; +using Fabrikam.Workflow.Service.RequestProcessing; +using Fabrikam.Workflow.Service.Services; + +namespace Fabrikam.Workflow.Service +{ + public static class ServiceStartup + { + private const string HealthCheckName = "ReadinessLiveness"; + private const string HealthCheckServiceAssembly = "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherHostedService"; + + public static void ConfigureServices(HostBuilderContext context, IServiceCollection services) + { + services.AddOptions(); + + // Configure AppInsights + services.AddApplicationInsightsKubernetesEnricher(); + services.AddApplicationInsightsTelemetry(context.Configuration); + + services.Configure(context.Configuration); + services.AddHostedService(); + + services.AddTransient(); + + // Add health check │ + services.AddHealthChecks().AddCheck( + HealthCheckName, + () => HealthCheckResult.Healthy("OK")); + + if (context.Configuration["HEALTHCHECK_INITIAL_DELAY"] is var configuredDelay && + double.TryParse(configuredDelay, out double delay)) + { + services.Configure(options => + { + options.Delay = TimeSpan.FromMilliseconds(delay); + }); + } + + services + .AddHttpClient(c => + { + c.BaseAddress = new Uri(context.Configuration["SERVICE_URI_PACKAGE"]); + }) + .AddResiliencyPolicies(context.Configuration); + + services + .AddHttpClient(c => + { + c.BaseAddress = new Uri(context.Configuration["SERVICE_URI_DRONE"]); + }) + .AddResiliencyPolicies(context.Configuration); + + services + .AddHttpClient(c => + { + c.BaseAddress = new Uri(context.Configuration["SERVICE_URI_DELIVERY"]); + }) + .AddResiliencyPolicies(context.Configuration); + + // workaround .NET Core 2.2: for more info https://github.com/aspnet/AspNetCore.Docs/blob/master/aspnetcore/host-and-deploy/health-checks/samples/2.x/HealthChecksSample/LivenessProbeStartup.cs#L51 + services.TryAddEnumerable( + ServiceDescriptor.Singleton(typeof(IHostedService), + typeof(HealthCheckPublisherOptions).Assembly + .GetType(HealthCheckServiceAssembly))); + + services.AddSingleton(); + } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Services/BackendServiceCallFailedException.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Services/BackendServiceCallFailedException.cs new file mode 100644 index 00000000..48c71d8c --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Services/BackendServiceCallFailedException.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; + +namespace Fabrikam.Workflow.Service.Services +{ + public class BackendServiceCallFailedException : Exception + { + public BackendServiceCallFailedException() + { + } + + public BackendServiceCallFailedException(string message) : base(message) + { + } + + public BackendServiceCallFailedException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Services/DeliveryServiceCaller.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Services/DeliveryServiceCaller.cs new file mode 100644 index 00000000..7dd4c8be --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Services/DeliveryServiceCaller.cs @@ -0,0 +1,65 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Fabrikam.Workflow.Service.Models; +using Fabrikam.Workflow.Service.Utils; + +namespace Fabrikam.Workflow.Service.Services +{ + public class DeliveryServiceCaller : IDeliveryServiceCaller + { + private readonly HttpClient _httpClient; + + public DeliveryServiceCaller(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public async Task ScheduleDeliveryAsync(Delivery deliveryRequest, string droneId) + { + try + { + var schedule = CreateDeliverySchedule(deliveryRequest, droneId); + + var response = await _httpClient.PutAsJsonAsync(schedule.Id, schedule); + if (response.StatusCode == HttpStatusCode.Created) + { + return await response.Content.ReadAsAsync(); + } + + throw new BackendServiceCallFailedException(response.ReasonPhrase); + } + catch (BackendServiceCallFailedException) + { + throw; + } + catch (Exception e) + { + throw new BackendServiceCallFailedException(e.Message, e); + } + } + + private DeliverySchedule CreateDeliverySchedule(Delivery deliveryRequest, string droneId) + { + DeliverySchedule scheduleDelivery = new DeliverySchedule + { + Id = deliveryRequest.DeliveryId, + Owner = new UserAccount { AccountId = Guid.NewGuid().ToString(), UserId = deliveryRequest.OwnerId }, + Pickup = LocationRandomizer.GetRandomLocation(), + Dropoff = LocationRandomizer.GetRandomLocation(), + Deadline = deliveryRequest.Deadline, + Expedited = deliveryRequest.Expedited, + ConfirmationRequired = (ConfirmationType)deliveryRequest.ConfirmationRequired, + DroneId = droneId, + }; + + return scheduleDelivery; + } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Services/DroneSchedulerServiceCaller.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Services/DroneSchedulerServiceCaller.cs new file mode 100644 index 00000000..cb6124ea --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Services/DroneSchedulerServiceCaller.cs @@ -0,0 +1,62 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Fabrikam.Workflow.Service.Models; +using Fabrikam.Workflow.Service.Utils; + +namespace Fabrikam.Workflow.Service.Services +{ + public class DroneSchedulerServiceCaller : IDroneSchedulerServiceCaller + { + private readonly HttpClient _httpClient; + + public DroneSchedulerServiceCaller(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public async Task GetDroneIdAsync(Delivery deliveryRequest) + { + try + { + var delivery = CreateDroneDelivery(deliveryRequest); + + var response = await _httpClient.PutAsJsonAsync($"{delivery.DeliveryId}", delivery); + if (response.StatusCode == HttpStatusCode.OK) + { + return await response.Content.ReadAsStringAsync(); + } + + throw new BackendServiceCallFailedException(response.ReasonPhrase); + } + catch (BackendServiceCallFailedException) + { + throw; + } + catch (Exception e) + { + throw new BackendServiceCallFailedException(e.Message, e); + } + } + + private DroneDelivery CreateDroneDelivery(Delivery deliveryRequest) + { + DroneDelivery delivery = new DroneDelivery(); + delivery.DeliveryId = deliveryRequest.DeliveryId; + + delivery.Dropoff = LocationRandomizer.GetRandomLocation(); + delivery.Pickup = LocationRandomizer.GetRandomLocation(); + + delivery.Expedited = delivery.Expedited; + delivery.PackageDetail = ModelsConverter.GetPackageDetail(deliveryRequest.PackageInfo); + + return delivery; + } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Services/IDeliveryServiceCaller.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Services/IDeliveryServiceCaller.cs new file mode 100644 index 00000000..754bdfc8 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Services/IDeliveryServiceCaller.cs @@ -0,0 +1,15 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System.Threading.Tasks; +using Fabrikam.Workflow.Service.Models; + +namespace Fabrikam.Workflow.Service.Services +{ + public interface IDeliveryServiceCaller + { + Task ScheduleDeliveryAsync(Delivery deliveryRequest, string droneId); + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Services/IDroneSchedulerServiceCaller.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Services/IDroneSchedulerServiceCaller.cs new file mode 100644 index 00000000..a3eafe77 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Services/IDroneSchedulerServiceCaller.cs @@ -0,0 +1,15 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System.Threading.Tasks; +using Fabrikam.Workflow.Service.Models; + +namespace Fabrikam.Workflow.Service.Services +{ + public interface IDroneSchedulerServiceCaller + { + Task GetDroneIdAsync(Delivery deliveryRequest); + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Services/IPackageServiceCaller.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Services/IPackageServiceCaller.cs new file mode 100644 index 00000000..4fa9eeaa --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Services/IPackageServiceCaller.cs @@ -0,0 +1,15 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System.Threading.Tasks; +using Fabrikam.Workflow.Service.Models; + +namespace Fabrikam.Workflow.Service.Services +{ + public interface IPackageServiceCaller + { + Task UpsertPackageAsync(PackageInfo packageInfo); + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Services/PackageServiceCaller.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Services/PackageServiceCaller.cs new file mode 100644 index 00000000..78a6fbb8 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Services/PackageServiceCaller.cs @@ -0,0 +1,71 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Fabrikam.Workflow.Service.Models; + +namespace Fabrikam.Workflow.Service.Services +{ + public class PackageServiceCaller : IPackageServiceCaller + { + private readonly HttpClient _httpClient; + + public PackageServiceCaller(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public async Task UpsertPackageAsync(PackageInfo packageInfo) + { + try + { + var response = await _httpClient.PutAsJsonAsync($"{packageInfo.PackageId}", packageInfo); + if (response.StatusCode == HttpStatusCode.Created) + { + return await response.Content.ReadAsAsync(); + } + else if (response.StatusCode == HttpStatusCode.NoContent) + { + return await this.GetPackageAsync(packageInfo.PackageId); + } + + throw new BackendServiceCallFailedException(response.ReasonPhrase); + } + catch (BackendServiceCallFailedException) + { + throw; + } + catch (Exception e) + { + throw new BackendServiceCallFailedException(e.Message, e); + } + } + + private async Task GetPackageAsync(string packageId) + { + try + { + var response = await _httpClient.GetAsync($"{packageId}"); + if (response.StatusCode == HttpStatusCode.OK) + { + return await response.Content.ReadAsAsync(); + } + + throw new BackendServiceCallFailedException(response.ReasonPhrase); + } + catch (BackendServiceCallFailedException) + { + throw; + } + catch (Exception e) + { + throw new BackendServiceCallFailedException(e.Message, e); + } + } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Services/ReadinessLivenessPublisher.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Services/ReadinessLivenessPublisher.cs new file mode 100644 index 00000000..f36dc887 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Services/ReadinessLivenessPublisher.cs @@ -0,0 +1,81 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; + +namespace Fabrikam.Workflow.Service.Services +{ + public class ReadinessLivenessPublisher : IHealthCheckPublisher + { + public const string FilePath = "healthz"; + + private readonly ILogger _logger; + + public ReadinessLivenessPublisher(ILogger logger) + { + this._logger = logger; + } + + public Task PublishAsync(HealthReport report, + CancellationToken cancellationToken) + { + switch (report.Status) + { + case HealthStatus.Healthy: + { + this._logger.LogInformation( + "{Timestamp} Readiness/Liveness Probe Status: {Result}", + DateTime.UtcNow, + report.Status); + + CreateOrUpdateHealthz(); + + break; + } + + case HealthStatus.Degraded: + { + this._logger.LogWarning( + "{Timestamp} Readiness/Liveness Probe Status: {Result}", + DateTime.UtcNow, + report.Status); + + break; + } + + case HealthStatus.Unhealthy: + { + this._logger.LogError( + "{Timestamp} Readiness Probe/Liveness Status: {Result}", + DateTime.UtcNow, + report.Status); + + break; + } + } + + cancellationToken.ThrowIfCancellationRequested(); + + return Task.CompletedTask; + } + + private static void CreateOrUpdateHealthz() + { + if (File.Exists(FilePath)) + { + File.SetLastWriteTimeUtc(FilePath, DateTime.UtcNow); + } + else + { + File.AppendText(FilePath).Close(); + } + } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/TracingExtensions.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/TracingExtensions.cs new file mode 100644 index 00000000..fd85d38d --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/TracingExtensions.cs @@ -0,0 +1,205 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.ApplicationInsights; +using Microsoft.ApplicationInsights.AspNetCore.TelemetryInitializers; +using Microsoft.ApplicationInsights.Channel; +using Microsoft.ApplicationInsights.DependencyCollector; +using Microsoft.ApplicationInsights.Extensibility; +using Microsoft.ApplicationInsights.Extensibility.Implementation.ApplicationId; +using Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.QuickPulse; +using Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Fabrikam.Workflow.Service +{ + /// + /// Application Insights setup class based on https://docs.microsoft.com/en-us/azure/azure-monitor/app/console + /// + /// + /// Telemetry Modules initialization as expected based on https://github.com/Microsoft/ApplicationInsights-aspnetcore/blob/04b5485d4a8aa498b2d99c60bdf8ca59bc9103fc/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryConfigurationOptions.cs#L27 + /// + internal static class TracingExtensions + { + public static IServiceCollection AddApplicationInsightsTelemetry( + this IServiceCollection services, + IConfiguration configuration) + { + // add initializers + services.AddSingleton< + ITelemetryInitializer, + DomainNameRoleInstanceTelemetryInitializer>(); + services.AddSingleton< + ITelemetryInitializer, + HttpDependenciesParsingTelemetryInitializer>(); + + // add modules + services.AddSingleton(s => + { + var module = new DependencyTrackingTelemetryModule(); + + var excludedDomains = module.ExcludeComponentCorrelationHttpHeadersOnDomains; + excludedDomains.Add("core.windows.net"); + excludedDomains.Add("core.chinacloudapi.cn"); + excludedDomains.Add("core.cloudapi.de"); + excludedDomains.Add("core.usgovcloudapi.net"); + + if (module.EnableLegacyCorrelationHeadersInjection) + { + excludedDomains.Add("localhost"); + excludedDomains.Add("127.0.0.1"); + } + + var includedActivities = module.IncludeDiagnosticSourceActivities; + includedActivities.Add("Microsoft.Azure.ServiceBus"); + + return module; + }); + + services.AddSingleton< + ITelemetryModule, + QuickPulseTelemetryModule>(); + + // add others + services.TryAddSingleton< + IApplicationIdProvider, + ApplicationInsightsApplicationIdProvider>(); + + services.TryAddSingleton< + ITelemetryChannel, + ServerTelemetryChannel>(); + + services.TryAddSingleton(); + + services.AddSingleton(provider => + provider.GetService>().Value); + + services.AddOptions(); + services.AddSingleton, TelemetryConfigurationOptions>(); + services.AddSingleton, TelemetryConfigurationOptionsSetup>(); + + return services; + } + + public static IServiceCollection AddApplicationInsightsKubernetesEnricher (this IServiceCollection services) + { + services.Configure( + (config) => + config.AddApplicationInsightsKubernetesEnricher( + applyOptions: null) + ); + + return services; + } + + private class TelemetryConfigurationOptionsSetup : IConfigureOptions + { + private const string CustomKeyVaultAppInsightsIKey = "ApplicationInsights-InstrumentationKey"; + private const string AppInsightsDeveloperMode = "ApplicationInsights:DeveloperMode"; + + private readonly IConfiguration _configuration; + private readonly IServiceProvider _serviceProvider; + private readonly IEnumerable _initializers; + private readonly IEnumerable _modules; + private readonly ITelemetryChannel _telemetryChannel; + + public TelemetryConfigurationOptionsSetup( + IServiceProvider serviceProvider, + IEnumerable initializers, + IEnumerable modules, + ITelemetryChannel telemetryChannel, + IConfiguration configuration) + { + this._serviceProvider = serviceProvider; + this._initializers = initializers; + this._modules = modules; + this._telemetryChannel = telemetryChannel; + this._configuration = configuration; + } + + public void Configure(TelemetryConfiguration telemetryConfig) + { + // flex volume + var instrumentationKey = _configuration[CustomKeyVaultAppInsightsIKey]; + + if (!string.IsNullOrWhiteSpace(instrumentationKey)) + { + telemetryConfig.InstrumentationKey = instrumentationKey; + } + + // Fallback to default channel (InMemoryChannel) created by base sdk if no channel is found in DI + telemetryConfig.TelemetryChannel = + this._telemetryChannel + ?? telemetryConfig.TelemetryChannel; + + if(bool.TryParse(_configuration[AppInsightsDeveloperMode], out bool developerMode)) + this._telemetryChannel.DeveloperMode = developerMode; + + (telemetryConfig.TelemetryChannel as ITelemetryModule) + ?.Initialize(telemetryConfig); + + // use processors + telemetryConfig + .DefaultTelemetrySink + .TelemetryProcessorChainBuilder + .Use((next) => + { + var processor = new QuickPulseTelemetryProcessor(next); + + var quickPulseModule = _serviceProvider + .GetServices() + .OfType() + .Single(); + quickPulseModule.RegisterTelemetryProcessor(processor); + + return processor; + }); + + telemetryConfig + .DefaultTelemetrySink + .TelemetryProcessorChainBuilder + .Build(); + + // add initializers: https://github.com/Microsoft/ApplicationInsights-aspnetcore/pull/672 + foreach (var initializers in _initializers) + { + telemetryConfig.TelemetryInitializers.Add(initializers); + } + + // initialize all modules + foreach (var module in _modules) + { + module.Initialize(telemetryConfig); + } + + // other config: https://github.com/Microsoft/ApplicationInsights-aspnetcore/blob/de1af6235a4cc365d64cbc78db9bdd2d579a37ee/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/TelemetryConfigurationOptionsSetup.cs#L129 + telemetryConfig.ApplicationIdProvider = + _serviceProvider.GetRequiredService(); + } + } + + private class TelemetryConfigurationOptions : IOptions + { + public TelemetryConfigurationOptions(IEnumerable> configureOptions) + { + this.Value = TelemetryConfiguration.CreateDefault(); + + var configureOptionsArray = configureOptions.ToArray(); + foreach (var c in configureOptionsArray) + { + c.Configure(this.Value); + } + } + + public TelemetryConfiguration Value { get; } + } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Utils/LocationRandomizer.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Utils/LocationRandomizer.cs new file mode 100644 index 00000000..d1c18c49 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Utils/LocationRandomizer.cs @@ -0,0 +1,28 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using Fabrikam.Workflow.Service.Models; + +namespace Fabrikam.Workflow.Service.Utils +{ + static class LocationRandomizer + { + private static Random Random = new Random(); + + public static Location GetRandomLocation() + { + Location location = new Location(); + lock (Random) + { + location.Altitude = Random.NextDouble(); + location.Latitude = Random.NextDouble(); + location.Longitude = Random.NextDouble(); + } + + return location; + } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/Utils/ModelsConverter.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/Utils/ModelsConverter.cs new file mode 100644 index 00000000..98e9c764 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/Utils/ModelsConverter.cs @@ -0,0 +1,28 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using Fabrikam.Workflow.Service.Models; + +namespace Fabrikam.Workflow.Service.Utils +{ + class ModelsConverter + { + internal static PackageDetail GetPackageDetail(PackageInfo packageInfo) + { + var packageDetail = new PackageDetail + { + Id = packageInfo.PackageId, + Size = GetPackageSize(packageInfo.Size) + }; + + return packageDetail; + } + + private static PackageSize GetPackageSize(ContainerSize containerSize) + { + return (PackageSize)(int)containerSize; + } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/WorkflowService.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/WorkflowService.cs new file mode 100644 index 00000000..0adba19e --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/WorkflowService.cs @@ -0,0 +1,147 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.ObjectModel; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Azure.ServiceBus; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Fabrikam.Workflow.Service.Models; +using Fabrikam.Workflow.Service.RequestProcessing; +using Newtonsoft.Json; + +namespace Fabrikam.Workflow.Service +{ + internal class WorkflowService : IHostedService + { + private readonly JsonSerializer _serializer; + + private readonly ILogger _logger; + private readonly IRequestProcessor _requestProcessor; + private readonly Func, IQueueClient> _createQueueClient; + private readonly IOptions _options; + private IQueueClient _receiveClient; + + public WorkflowService(IOptions options, ILogger logger, IRequestProcessor requestProcessor) + : this(options, logger, requestProcessor, CreateQueueClient) + { } + + public WorkflowService(IOptions options, ILogger logger, IRequestProcessor requestProcessor, Func, IQueueClient> createQueueClient) + { + _options = options; + _logger = logger; + _requestProcessor = requestProcessor; + _createQueueClient = createQueueClient; + + _serializer = new JsonSerializer(); + } + + private static IQueueClient CreateQueueClient(IOptions options) + { + var connectionStringBuilder = new ServiceBusConnectionStringBuilder + { + Endpoint = options.Value.QueueEndpoint, + EntityPath = options.Value.QueueName, + SasKeyName = options.Value.QueueAccessPolicyName, + SasKey = options.Value.QueueAccessPolicyKey, + TransportType = TransportType.Amqp + }; + + return new QueueClient(connectionStringBuilder) + { + PrefetchCount = options.Value.PrefetchCount + }; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _receiveClient = _createQueueClient(_options); + + _receiveClient.RegisterMessageHandler( + ProcessMessageAsync, + new MessageHandlerOptions(ProcessMessageExceptionAsync) + { + AutoComplete = false, + MaxConcurrentCalls = _options.Value.MaxConcurrency + }); + + _logger.LogInformation("Started"); + return Task.CompletedTask; + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + await _receiveClient?.CloseAsync(); + + _logger.LogInformation("Stopped"); + } + + private async Task ProcessMessageAsync(Message message, CancellationToken ct) + { + _logger.LogInformation("Processing message {messageId}", message.MessageId); + + if (TryGetDelivery(message, out var delivery)) + { + try + { + if (await _requestProcessor.ProcessDeliveryRequestAsync(delivery, new ReadOnlyDictionary(message.UserProperties))) + { + await _receiveClient.CompleteAsync(message.SystemProperties.LockToken); + return; + } + } + catch (Exception e) + { + _logger.LogError(e, "Error processing message {messageId}", message.MessageId); + } + } + + try + { + await _receiveClient.DeadLetterAsync(message.SystemProperties.LockToken); + } + catch (Exception e) + { + _logger.LogError(e, "Error moving message {messageId} to dead letter queue", message.MessageId); + } + + return; + } + + private Task ProcessMessageExceptionAsync(ExceptionReceivedEventArgs exceptionEvent) + { + _logger.LogError(exceptionEvent.Exception, "Exception processing message"); + + return Task.CompletedTask; + } + + private bool TryGetDelivery(Message message, out Delivery delivery) + { + try + { + using (var payloadStream = new MemoryStream(message.Body, false)) + using (var streamReader = new StreamReader(payloadStream, Encoding.UTF8)) + using (var jsonReader = new JsonTextReader(streamReader)) + { + delivery = _serializer.Deserialize(jsonReader); + } + + return true; + } + catch (Exception e) + { + _logger.LogError(e, "Cannot parse payload from message {messageId}", message.MessageId); + } + + delivery = null; + return false; + } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/WorkflowServiceOptions.cs b/src/shipping/workflow/Fabrikam.Workflow.Service/WorkflowServiceOptions.cs new file mode 100644 index 00000000..4902a2a5 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/WorkflowServiceOptions.cs @@ -0,0 +1,28 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +namespace Fabrikam.Workflow.Service +{ + internal class WorkflowServiceOptions + { + public WorkflowServiceOptions() + { + MaxConcurrency = 20; + PrefetchCount = 3000; + } + + public string QueueEndpoint { get; set; } + + public string QueueName { get; set; } + + public string QueueAccessPolicyName { get; set; } + + public string QueueAccessPolicyKey { get; set; } + + public int MaxConcurrency { get; internal set; } + + public int PrefetchCount { get; internal set; } + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/appsettings.Development.json b/src/shipping/workflow/Fabrikam.Workflow.Service/appsettings.Development.json new file mode 100644 index 00000000..91dfd50f --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/appsettings.Development.json @@ -0,0 +1,16 @@ +{ + "Logging": { + "IncludeScopes": false, + "ApplicationInsights": { + "LogLevel": { + "Default": "None" + } + }, + "LogLevel": { + "Default": "Information" + } + }, + "ApplicationInsights": { + "DeveloperMode": true + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.Service/appsettings.json b/src/shipping/workflow/Fabrikam.Workflow.Service/appsettings.json new file mode 100644 index 00000000..63622fef --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.Service/appsettings.json @@ -0,0 +1,30 @@ +{ + "Logging": { + "IncludeScopes": false, + "ApplicationInsights": { + "LogLevel": { + "Default": "Error" + } + }, + "LogLevel": { + "Default": "Information" + } + }, + "ApplicationInsights": { + "DeveloperMode": false + }, + "Serilog": { + "MinimumLevel": "Verbose", + "Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ], + "WriteTo": [ + //{ + // "Name": "RollingFile", + // "Args": { + // "pathFormat": "Logs/deliveryservice-{Date}.log", + // "formatter": "Serilog.Formatting.Json.JsonFormatter, Serilog", + // "retainedFileCountLimit": 10 + // } + //} + ] + } +} diff --git a/src/shipping/workflow/Fabrikam.Workflow.sln b/src/shipping/workflow/Fabrikam.Workflow.sln new file mode 100644 index 00000000..924c96e9 --- /dev/null +++ b/src/shipping/workflow/Fabrikam.Workflow.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.136 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fabrikam.Workflow.Service", "Fabrikam.Workflow.Service\Fabrikam.Workflow.Service.csproj", "{2A3140EA-2FB2-4982-AF14-E9DC66D1D1FE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2DB20E67-904A-4146-A38C-E8DB4F458A5C}" + ProjectSection(SolutionItems) = preProject + Dockerfile = Dockerfile + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{0B10A889-9683-4C1B-82BC-141994066C34}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fabrikam.Workflow.Service.Tests", "Fabrikam.Workflow.Service.Tests\Fabrikam.Workflow.Service.Tests.csproj", "{4D0C1CF0-61B4-451E-B3C4-D5AFF308B657}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2A3140EA-2FB2-4982-AF14-E9DC66D1D1FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A3140EA-2FB2-4982-AF14-E9DC66D1D1FE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A3140EA-2FB2-4982-AF14-E9DC66D1D1FE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A3140EA-2FB2-4982-AF14-E9DC66D1D1FE}.Release|Any CPU.Build.0 = Release|Any CPU + {4D0C1CF0-61B4-451E-B3C4-D5AFF308B657}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D0C1CF0-61B4-451E-B3C4-D5AFF308B657}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D0C1CF0-61B4-451E-B3C4-D5AFF308B657}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D0C1CF0-61B4-451E-B3C4-D5AFF308B657}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {4D0C1CF0-61B4-451E-B3C4-D5AFF308B657} = {0B10A889-9683-4C1B-82BC-141994066C34} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2EB1F9E1-9576-495A-AE70-258803ADBE9E} + EndGlobalSection +EndGlobal diff --git a/src/shipping/workflow/azure-pipelines-cd.json b/src/shipping/workflow/azure-pipelines-cd.json new file mode 100644 index 00000000..d2c664c5 --- /dev/null +++ b/src/shipping/workflow/azure-pipelines-cd.json @@ -0,0 +1,1474 @@ +{ + "source": 1, + "revision": 3, + "description": null, + "isDeleted": false, + "variables": { + "REASON": { + "value": "Azure DevOps CD Pipeline", + "allowOverride": true + }, + "REPO_NAME": { + "value": "workflow" + } + }, + "variableGroups": [], + "environments": [ + { + "id": 34, + "name": "dev", + "rank": 1, + "owner": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "variables": { + "WORKFLOW_KEYVAULT_RESOURCE_GROUP": { + "value": "DEV_WORKFLOW_KEYVAULT_RESOURCE_GROUP_VAR_VAL" + }, + "SUBSCRIPTION_ID": { + "value": "DEV_SUBSCRIPTION_ID_VAR_VAL" + }, + "TENANT_ID": { + "value": "DEV_TENANT_ID_VAR_VAL" + }, + "WORKFLOW_KEYVAULT_NAME": { + "value": "DEV_WORKFLOW_KEYVAULT_NAME_VAR_VAL" + }, + "WORKFLOW_PRINCIPAL_CLIENT_ID": { + "value": "DEV_WORKFLOW_PRINCIPAL_CLIENT_ID_VAR_VAL" + }, + "WORKFLOW_PRINCIPAL_RESOURCE_ID": { + "value": "DEV_WORKFLOW_PRINCIPAL_RESOURCE_ID_VAR_VAL" + }, + "ACR_SERVER": { + "value": "DEV_ACR_SERVER_VAR_VAL" + }, + "ACR_NAME": { + "value": "DEV_ACR_NAME_VAR_VAL" + }, + "CLUSTER_SUBNET_PREFIX": { + "value": "DEV_CLUSTER_SUBNET_PREFIX_VAR_VAL" + } + }, + "variableGroups": [], + "preDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 102 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 1 + } + }, + "deployStep": { + "id": 109 + }, + "postDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 110 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 2 + } + }, + "deployPhases": [ + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [] + }, + "queueId": AZURE_DEVOPS_WORKFLOW_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 1, + "phaseType": 1, + "name": "Agent job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "helm upgrade workflow dev", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend-dev", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)-dev", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),identity.clientid=$(WORKFLOW_PRINCIPAL_CLIENT_ID),identity.resourceid=$(WORKFLOW_PRINCIPAL_RESOURCE_ID),networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",keyvault.name=$(WORKFLOW_KEYVAULT_NAME),keyvault.resourcegroup=$(WORKFLOW_KEYVAULT_RESOURCE_GROUP),keyvault.subscriptionid=$(SUBSCRIPTION_ID),keyvault.tenantid=$(TENANT_ID),reason=\"$(REASON)\",envs.dev=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + } + ], + "environmentOptions": { + "emailNotificationType": "OnlyOnFailure", + "emailRecipients": "release.environment.owner;release.creator", + "skipArtifactsDownload": false, + "timeoutInMinutes": 0, + "enableAccessToken": false, + "publishDeploymentStatus": true, + "badgeEnabled": false, + "autoLinkWorkItems": false, + "pullRequestDeploymentEnabled": false + }, + "demands": [], + "conditions": [ + { + "name": "ReleaseStarted", + "conditionType": 1, + "value": "" + } + ], + "executionPolicy": { + "concurrencyCount": 1, + "queueDepthCount": 0 + }, + "schedules": [], + "retentionPolicy": { + "daysToKeep": 30, + "releasesToKeep": 3, + "retainBuild": true + }, + "processParameters": { + "inputs": [ + { + "aliases": [], + "options": { + "Azure Resource Manager": "Azure Resource Manager", + "Kubernetes Service Connection": "Kubernetes Service Connection" + }, + "properties": { + "EditableOptions": "false" + }, + "name": "connectionType", + "label": "Connection Type.", + "defaultValue": "Azure Resource Manager", + "type": "pickList", + "helpMarkDown": "", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "azureSubscriptionEndpoint", + "label": "Azure subscription", + "defaultValue": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "required": true, + "type": "connectedService:AzureRM", + "helpMarkDown": "Select an Azure subscription, which has your Azure Container Registry.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "azureResourceGroup", + "label": "Resource group", + "defaultValue": "CLUSTER_RESOURCE_GROUP_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Resource Group.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "kubernetesCluster", + "label": "Kubernetes cluster", + "defaultValue": "CLUSTER_NAME_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Kubernetes Service cluster.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "kubernetesServiceEndpoint", + "label": "Kubernetes Service Connection", + "defaultValue": "", + "required": true, + "type": "connectedService:kubernetes", + "helpMarkDown": "Select a Kubernetes service connection.", + "visibleRule": "connectionType = Kubernetes Service Connection", + "groupName": "" + } + ], + "dataSourceBindings": [ + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "kubernetesCluster", + "resultTemplate": "{{{name}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + }, + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "azureResourceGroup", + "resultTemplate": "{{{ #extractResource id resourcegroups}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + } + ] + }, + "properties": {}, + "preDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "postDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "environmentTriggers": [], + "badgeUrl": null + }, + { + "id": 35, + "name": "QA", + "rank": 2, + "owner": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "variables": { + "WORKFLOW_KEYVAULT_RESOURCE_GROUP": { + "value": "QA_WORKFLOW_KEYVAULT_RESOURCE_GROUP_VAR_VAL" + }, + "SUBSCRIPTION_ID": { + "value": "QA_SUBSCRIPTION_ID_VAR_VAL" + }, + "TENANT_ID": { + "value": "QA_TENANT_ID_VAR_VAL" + }, + "WORKFLOW_KEYVAULT_NAME": { + "value": "QA_WORKFLOW_KEYVAULT_NAME_VAR_VAL" + }, + "WORKFLOW_PRINCIPAL_CLIENT_ID": { + "value": "QA_WORKFLOW_PRINCIPAL_CLIENT_ID_VAR_VAL" + }, + "WORKFLOW_PRINCIPAL_RESOURCE_ID": { + "value": "QA_WORKFLOW_PRINCIPAL_RESOURCE_ID_VAR_VAL" + }, + "ACR_SERVER": { + "value": "QA_ACR_SERVER_VAR_VAL" + }, + "ACR_NAME": { + "value": "QA_ACR_NAME_VAR_VAL" + }, + "CLUSTER_SUBNET_PREFIX": { + "value": "QA_CLUSTER_SUBNET_PREFIX_VAR_VAL" + } + }, + "variableGroups": [], + "preDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 103 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 1 + } + }, + "deployStep": { + "id": 108 + }, + "postDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 111 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 2 + } + }, + "deployPhases": [ + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [] + }, + "queueId": AZURE_DEVOPS_WORKFLOW_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 1, + "phaseType": 1, + "name": "Agent job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "helm upgrade workflow qa", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend-qa", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)-qa", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),identity.clientid=$(WORKFLOW_PRINCIPAL_CLIENT_ID),identity.resourceid=$(WORKFLOW_PRINCIPAL_RESOURCE_ID),networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",keyvault.name=$(WORKFLOW_KEYVAULT_NAME),keyvault.resourcegroup=$(WORKFLOW_KEYVAULT_RESOURCE_GROUP),keyvault.subscriptionid=$(SUBSCRIPTION_ID),keyvault.tenantid=$(TENANT_ID),reason=\"$(REASON)\",envs.qa=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + } + ], + "environmentOptions": { + "emailNotificationType": "OnlyOnFailure", + "emailRecipients": "release.environment.owner;release.creator", + "skipArtifactsDownload": false, + "timeoutInMinutes": 0, + "enableAccessToken": false, + "publishDeploymentStatus": true, + "badgeEnabled": false, + "autoLinkWorkItems": false, + "pullRequestDeploymentEnabled": false + }, + "demands": [], + "conditions": [ + { + "name": "dev", + "conditionType": 2, + "value": "4" + } + ], + "executionPolicy": { + "concurrencyCount": 1, + "queueDepthCount": 0 + }, + "schedules": [], + "retentionPolicy": { + "daysToKeep": 30, + "releasesToKeep": 3, + "retainBuild": true + }, + "processParameters": { + "inputs": [ + { + "aliases": [], + "options": { + "Azure Resource Manager": "Azure Resource Manager", + "Kubernetes Service Connection": "Kubernetes Service Connection" + }, + "properties": { + "EditableOptions": "false" + }, + "name": "connectionType", + "label": "Connection Type.", + "defaultValue": "Azure Resource Manager", + "type": "pickList", + "helpMarkDown": "", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "azureSubscriptionEndpoint", + "label": "Azure subscription", + "defaultValue": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "required": true, + "type": "connectedService:AzureRM", + "helpMarkDown": "Select an Azure subscription, which has your Azure Container Registry.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "azureResourceGroup", + "label": "Resource group", + "defaultValue": "CLUSTER_RESOURCE_GROUP_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Resource Group.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "kubernetesCluster", + "label": "Kubernetes cluster", + "defaultValue": "CLUSTER_NAME_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Kubernetes Service cluster.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "kubernetesServiceEndpoint", + "label": "Kubernetes Service Connection", + "defaultValue": "", + "required": true, + "type": "connectedService:kubernetes", + "helpMarkDown": "Select a Kubernetes service connection.", + "visibleRule": "connectionType = Kubernetes Service Connection", + "groupName": "" + } + ], + "dataSourceBindings": [ + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "kubernetesCluster", + "resultTemplate": "{{{name}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + }, + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "azureResourceGroup", + "resultTemplate": "{{{ #extractResource id resourcegroups}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + } + ] + }, + "properties": {}, + "preDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "postDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "environmentTriggers": [], + "badgeUrl": null + }, + { + "id": 36, + "name": "staging", + "rank": 3, + "owner": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "variables": { + "WORKFLOW_KEYVAULT_RESOURCE_GROUP": { + "value": "STAGING_WORKFLOW_KEYVAULT_RESOURCE_GROUP_VAR_VAL" + }, + "SUBSCRIPTION_ID": { + "value": "STAGING_SUBSCRIPTION_ID_VAR_VAL" + }, + "TENANT_ID": { + "value": "STAGING_TENANT_ID_VAR_VAL" + }, + "WORKFLOW_KEYVAULT_NAME": { + "value": "STAGING_WORKFLOW_KEYVAULT_NAME_VAR_VAL" + }, + "WORKFLOW_PRINCIPAL_CLIENT_ID": { + "value": "STAGING_WORKFLOW_PRINCIPAL_CLIENT_ID_VAR_VAL" + }, + "WORKFLOW_PRINCIPAL_RESOURCE_ID": { + "value": "STAGING_WORKFLOW_PRINCIPAL_RESOURCE_ID_VAR_VAL" + }, + "ACR_SERVER": { + "value": "STAGING_ACR_SERVER_VAR_VAL" + }, + "ACR_NAME": { + "value": "STAGING_ACR_NAME_VAR_VAL" + }, + "CLUSTER_SUBNET_PREFIX": { + "value": "STAGING_CLUSTER_SUBNET_PREFIX_VAR_VAL" + } + }, + "variableGroups": [], + "preDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 104 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 1 + } + }, + "deployStep": { + "id": 107 + }, + "postDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 112 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 2 + } + }, + "deployPhases": [ + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [] + }, + "queueId": AZURE_DEVOPS_WORKFLOW_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 1, + "phaseType": 1, + "name": "Agent job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "helm upgrade workflow staging", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend-staging", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)-staging", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),identity.clientid=$(WORKFLOW_PRINCIPAL_CLIENT_ID),identity.resourceid=$(WORKFLOW_PRINCIPAL_RESOURCE_ID),networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",keyvault.name=$(WORKFLOW_KEYVAULT_NAME),keyvault.resourcegroup=$(WORKFLOW_KEYVAULT_RESOURCE_GROUP),keyvault.subscriptionid=$(SUBSCRIPTION_ID),keyvault.tenantid=$(TENANT_ID),reason=\"$(REASON)\",envs.staging=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + } + ], + "environmentOptions": { + "emailNotificationType": "OnlyOnFailure", + "emailRecipients": "release.environment.owner;release.creator", + "skipArtifactsDownload": false, + "timeoutInMinutes": 0, + "enableAccessToken": false, + "publishDeploymentStatus": true, + "badgeEnabled": false, + "autoLinkWorkItems": false, + "pullRequestDeploymentEnabled": false + }, + "demands": [], + "conditions": [ + { + "name": "dev", + "conditionType": 2, + "value": "4" + } + ], + "executionPolicy": { + "concurrencyCount": 1, + "queueDepthCount": 0 + }, + "schedules": [], + "retentionPolicy": { + "daysToKeep": 30, + "releasesToKeep": 3, + "retainBuild": true + }, + "processParameters": { + "inputs": [ + { + "aliases": [], + "options": { + "Azure Resource Manager": "Azure Resource Manager", + "Kubernetes Service Connection": "Kubernetes Service Connection" + }, + "properties": { + "EditableOptions": "false" + }, + "name": "connectionType", + "label": "Connection Type.", + "defaultValue": "Azure Resource Manager", + "type": "pickList", + "helpMarkDown": "", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "azureSubscriptionEndpoint", + "label": "Azure subscription", + "defaultValue": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "required": true, + "type": "connectedService:AzureRM", + "helpMarkDown": "Select an Azure subscription, which has your Azure Container Registry.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "azureResourceGroup", + "label": "Resource group", + "defaultValue": "CLUSTER_RESOURCE_GROUP_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Resource Group.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "kubernetesCluster", + "label": "Kubernetes cluster", + "defaultValue": "CLUSTER_NAME_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Kubernetes Service cluster.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "kubernetesServiceEndpoint", + "label": "Kubernetes Service Connection", + "defaultValue": "", + "required": true, + "type": "connectedService:kubernetes", + "helpMarkDown": "Select a Kubernetes service connection.", + "visibleRule": "connectionType = Kubernetes Service Connection", + "groupName": "" + } + ], + "dataSourceBindings": [ + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "kubernetesCluster", + "resultTemplate": "{{{name}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + }, + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "azureResourceGroup", + "resultTemplate": "{{{ #extractResource id resourcegroups}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + } + ] + }, + "properties": {}, + "preDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "postDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "environmentTriggers": [], + "badgeUrl": null + }, + { + "id": 37, + "name": "production", + "rank": 4, + "owner": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "variables": { + "WORKFLOW_KEYVAULT_RESOURCE_GROUP": { + "value": "PROD_WORKFLOW_KEYVAULT_RESOURCE_GROUP_VAR_VAL" + }, + "SUBSCRIPTION_ID": { + "value": "PROD_SUBSCRIPTION_ID_VAR_VAL" + }, + "TENANT_ID": { + "value": "PROD_TENANT_ID_VAR_VAL" + }, + "WORKFLOW_KEYVAULT_NAME": { + "value": "PROD_WORKFLOW_KEYVAULT_NAME_VAR_VAL" + }, + "WORKFLOW_PRINCIPAL_CLIENT_ID": { + "value": "PROD_WORKFLOW_PRINCIPAL_CLIENT_ID_VAR_VAL" + }, + "WORKFLOW_PRINCIPAL_RESOURCE_ID": { + "value": "PROD_WORKFLOW_PRINCIPAL_RESOURCE_ID_VAR_VAL" + }, + "SOURCE_ACR_SERVER": { + "value": "SOURCE_ACR_SERVER_VAR_VAL" + }, + "SOURCE_ACR_NAME": { + "value": "SOURCE_ACR_NAME_VAR_VAL" + }, + "ACR_SERVER": { + "value": "PROD_ACR_SERVER_VAR_VAL" + }, + "ACR_NAME": { + "value": "PROD_ACR_NAME_VAR_VAL" + }, + "CLUSTER_SUBNET_PREFIX": { + "value": "PROD_CLUSTER_SUBNET_PREFIX_VAR_VAL" + } + }, + "variableGroups": [], + "preDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": false, + "isNotificationOn": false, + "approver": { + "id": "AZURE_DEVOPS_USER_ID_VAR_VAL" + }, + "approver": null, + "id": 105 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": true, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 1 + } + }, + "deployStep": { + "id": 106 + }, + "postDeployApprovals": { + "approvals": [ + { + "rank": 1, + "isAutomated": true, + "isNotificationOn": false, + "id": 113 + } + ], + "approvalOptions": { + "requiredApproverCount": null, + "releaseCreatorCanBeApprover": false, + "autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped": false, + "enforceIdentityRevalidation": false, + "timeoutInMinutes": 0, + "executionOrder": 2 + } + }, + "deployPhases": [ + { + "deploymentInput": { + "parallelExecution": { + "parallelExecutionType": 0 + }, + "skipArtifactsDownload": false, + "artifactsDownloadInput": { + "downloadInputs": [ + { + "alias": "ci-workflow", + "artifactType": "Build", + "artifactDownloadMode": "All", + "artifactItems": [] + } + ] + }, + "queueId": AZURE_DEVOPS_WORKFLOW_QUEUE_ID_VAR_VAL, + "demands": [], + "enableAccessToken": false, + "timeoutInMinutes": 0, + "jobCancelTimeoutInMinutes": 1, + "condition": "succeeded()", + "overrideInputs": {} + }, + "rank": 1, + "phaseType": 1, + "name": "Agent job", + "refName": null, + "workflowTasks": [ + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "promote workflow image to production", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr import --name $(ACR_NAME) --source $(SOURCE_ACR_SERVER)/$(REPO_NAME):$(Build.SourceBranchName) --force", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "068d5909-43e6-48c5-9e01-7c8a94816220", + "version": "0.*", + "name": "install helm 3.0.3", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "helmVersion": "3.0.3", + "checkLatestHelmVersion": "false", + "installKubeCtl": "true", + "kubectlVersion": "1.12.4", + "checkLatestKubeCtl": "false" + } + }, + { + "environment": {}, + "taskId": "46e4be58-730b-4389-8a2f-ea10b3e5e815", + "version": "1.*", + "name": "add helm repo", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": "task", + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectedServiceNameARM": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "scriptLocation": "inlineScript", + "scriptPath": "", + "inlineScript": "az acr helm repo add --name $(SOURCE_ACR_NAME)", + "args": "", + "addSpnToEnvironment": "false", + "useGlobalConfig": "false", + "cwd": "", + "failOnStandardError": "false" + } + }, + { + "environment": {}, + "taskId": "afa7d54d-537b-4dc8-b60a-e0eeea2c9a87", + "version": "0.*", + "name": "helm upgrade workflow prod", + "refName": "", + "enabled": true, + "alwaysRun": false, + "continueOnError": false, + "timeoutInMinutes": 0, + "definitionType": null, + "overrideInputs": {}, + "condition": "succeeded()", + "inputs": { + "connectionType": "$(Parameters.connectionType)", + "azureSubscriptionEndpoint": "$(Parameters.azureSubscriptionEndpoint)", + "azureResourceGroup": "$(Parameters.azureResourceGroup)", + "kubernetesCluster": "$(Parameters.kubernetesCluster)", + "kubernetesServiceEndpoint": "$(Parameters.kubernetesServiceEndpoint)", + "namespace": "backend", + "command": "upgrade", + "chartType": "Name", + "chartName": "$(SOURCE_ACR_NAME)/$(REPO_NAME)", + "chartPath": "", + "version": "", + "releaseName": "$(REPO_NAME)-$(Build.SourceBranchName)", + "overrideValues": "image.tag=$(Build.SourceBranchName),image.repository=$(REPO_NAME),dockerregistry=$(ACR_SERVER),identity.clientid=$(WORKFLOW_PRINCIPAL_CLIENT_ID),identity.resourceid=$(WORKFLOW_PRINCIPAL_RESOURCE_ID),networkPolicy.egress.external.enabled=true,networkPolicy.egress.external.clusterSubnetPrefix=\"$(CLUSTER_SUBNET_PREFIX)\",keyvault.name=$(WORKFLOW_KEYVAULT_NAME),keyvault.resourcegroup=$(WORKFLOW_KEYVAULT_RESOURCE_GROUP),keyvault.subscriptionid=$(SUBSCRIPTION_ID),keyvault.tenantid=$(TENANT_ID),reason=\"$(REASON)\",envs.prod=true", + "valueFile": "", + "destination": "", + "canaryimage": "false", + "upgradetiller": "false", + "updatedependency": "false", + "save": "true", + "install": "true", + "recreate": "false", + "resetValues": "false", + "force": "true", + "waitForExecution": "false", + "arguments": "--version $(Build.SourceBranchName)", + "enableTls": "false", + "caCert": "", + "certificate": "", + "privatekey": "", + "tillernamespace": "", + "failOnStderr": "false" + } + } + ] + } + ], + "environmentOptions": { + "emailNotificationType": "OnlyOnFailure", + "emailRecipients": "release.environment.owner;release.creator", + "skipArtifactsDownload": false, + "timeoutInMinutes": 0, + "enableAccessToken": false, + "publishDeploymentStatus": true, + "badgeEnabled": false, + "autoLinkWorkItems": false, + "pullRequestDeploymentEnabled": false + }, + "demands": [], + "conditions": [ + { + "name": "QA", + "conditionType": 2, + "value": "4" + }, + { + "name": "staging", + "conditionType": 2, + "value": "4" + } + ], + "executionPolicy": { + "concurrencyCount": 1, + "queueDepthCount": 0 + }, + "schedules": [], + "retentionPolicy": { + "daysToKeep": 30, + "releasesToKeep": 3, + "retainBuild": true + }, + "processParameters": { + "inputs": [ + { + "aliases": [], + "options": { + "Azure Resource Manager": "Azure Resource Manager", + "Kubernetes Service Connection": "Kubernetes Service Connection" + }, + "properties": { + "EditableOptions": "false" + }, + "name": "connectionType", + "label": "Connection Type.", + "defaultValue": "Azure Resource Manager", + "type": "pickList", + "helpMarkDown": "", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "azureSubscriptionEndpoint", + "label": "Azure subscription", + "defaultValue": "AZURE_DEVOPS_SERVICE_CONN_ID_VAR_VAL", + "required": true, + "type": "connectedService:AzureRM", + "helpMarkDown": "Select an Azure subscription, which has your Azure Container Registry.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "azureResourceGroup", + "label": "Resource group", + "defaultValue": "CLUSTER_RESOURCE_GROUP_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Resource Group.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": { + "EditableOptions": "True" + }, + "name": "kubernetesCluster", + "label": "Kubernetes cluster", + "defaultValue": "CLUSTER_NAME_VAR_VAL", + "required": true, + "type": "pickList", + "helpMarkDown": "Select an Azure Kubernetes Service cluster.", + "visibleRule": "connectionType = Azure Resource Manager", + "groupName": "" + }, + { + "aliases": [], + "options": {}, + "properties": {}, + "name": "kubernetesServiceEndpoint", + "label": "Kubernetes Service Connection", + "defaultValue": "", + "required": true, + "type": "connectedService:kubernetes", + "helpMarkDown": "Select a Kubernetes service connection.", + "visibleRule": "connectionType = Kubernetes Service Connection", + "groupName": "" + } + ], + "dataSourceBindings": [ + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "kubernetesCluster", + "resultTemplate": "{{{name}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/resourceGroups/$(azureResourceGroup)/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + }, + { + "parameters": {}, + "endpointId": "$(azureSubscriptionEndpoint)", + "target": "azureResourceGroup", + "resultTemplate": "{{{ #extractResource id resourcegroups}}}", + "endpointUrl": "{{{endpoint.url}}}/subscriptions/{{{endpoint.subscriptionId}}}/providers/Microsoft.ContainerService/managedClusters?api-version=2017-08-31", + "resultSelector": "jsonpath:$.value[*]" + } + ] + }, + "properties": {}, + "preDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "postDeploymentGates": { + "id": 0, + "gatesOptions": null, + "gates": [] + }, + "environmentTriggers": [], + "badgeUrl": null + } + ], + "artifacts": [ + { + "sourceId": "AZURE_DEVOPS_PROJECT_ID_VAR_VAL:AZURE_DEVOPS_WORKFLOW_BUILD_ID_VAR_VAL", + "type": "Build", + "alias": "ci-workflow", + "definitionReference": { + "artifactSourceDefinitionUrl": { + "id": "", + "name": "" + }, + "defaultVersionBranch": { + "id": "", + "name": "" + }, + "defaultVersionSpecific": { + "id": "", + "name": "" + }, + "defaultVersionTags": { + "id": "", + "name": "" + }, + "defaultVersionType": { + "id": "selectDuringReleaseCreationType", + "name": "Specify at the time of release creation" + }, + "definition": { + "id": "AZURE_DEVOPS_WORKFLOW_BUILD_ID_VAR_VAL", + "name": "aks-ri-ci-workflow" + }, + "definitions": { + "id": "", + "name": "" + }, + "IsMultiDefinitionType": { + "id": "False", + "name": "False" + }, + "project": { + "id": "AZURE_DEVOPS_PROJECT_ID_VAR_VAL", + "name": "roadmap" + }, + "repository": { + "id": "AZURE_DEVOPS_REPOS_ID_VAR_VALL", + "name": "roadmap" + } + }, + "isPrimary": true, + "isRetained": false + } + ], + "triggers": [ + { + "artifactAlias": "ci-workflow", + "triggerConditions": [ + { + "sourceBranch": "release/$(REPO_NAME)/v*", + "tags": [], + "useBuildDefinitionBranch": false, + "createReleaseOnBuildTagging": false + } + ], + "triggerType": 1 + } + ], + "releaseNameFormat": "release-$(rev:r)", + "tags": [], + "pipelineProcess": { + "type": 1 + }, + "properties": { + "DefinitionCreationSource": { + "$type": "System.String", + "$value": "ReleaseImport" + } + }, + "id": 17, + "name": "workflow-cd", + "path": null, + "projectReference": null, + "url": null +} diff --git a/src/shipping/workflow/azure-pipelines.yml b/src/shipping/workflow/azure-pipelines.yml new file mode 100644 index 00000000..5f67d0ac --- /dev/null +++ b/src/shipping/workflow/azure-pipelines.yml @@ -0,0 +1,158 @@ +variables: + repositoryName: workflow + chartPath: charts/workflow + dockerFileName: src/shipping/workflow/Dockerfile + imageName: $(repositoryName):$(Build.SourceBranchName) + azureSubscription: AZURE_PIPELINES_SERVICE_CONN_NAME_VAR_VAL + azureContainerRegistry: ACR_SERVER_VAR_VAL + azureContainerRegistryName: ACR_NAME_VAR_VAL + +name: $(build.sourceBranch)-$(Date:yyyyMMdd)$(Rev:.rr) + +pr: # only valid for GitHub. Using Azure repo it must be configure as a Branch Policy + paths: + include: + - /src/shipping/workflow/ + + branches: + include: + - master + - release/workflow/v* # for bug fixes + +trigger: + batch: true + branches: + include: + # for new release to production: release flow strategy + - release/workflow/v* + - refs/relelase/workflow/v* + - master + - feature/workflow/* + - topic/workflow/* + paths: + include: + - /src/shipping/workflow/ + +resources: +- repo: self + +jobs: + +# CI +- job: workflowjobci + displayName: "Workflow CI" + pool: + vmImage: 'Ubuntu 16.04' + timeoutInMinutes: 90 + variables: + fullCI: $[ startsWith(variables['build.sourceBranch'], 'refs/heads/release/workflow/v') ] + buildImage: $[ eq(variables['build.sourceBranch'], 'refs/heads/master') ] + steps: + - task: Docker@1 + displayName: 'Build testrunner image' + inputs: + azureSubscriptionEndpoint: $(azureSubscription) + + azureContainerRegistry: $(azureContainerRegistry) + + arguments: '--pull --target testrunner' + + dockerFile: $(System.DefaultWorkingDirectory)/$(dockerFileName) + + imageName: '$(imageName)-test' + + - task: Docker@1 + displayName: 'Run tests' + inputs: + azureSubscriptionEndpoint: $(azureSubscription) + + azureContainerRegistry: $(azureContainerRegistry) + + command: 'run' + + containerName: testrunner + + volumes: '$(System.DefaultWorkingDirectory)/TestResults:/src/tests/TestResults' + + imageName: '$(imageName)-test' + + runInBackground: false + + - task: PublishTestResults@2 + displayName: 'Publish test results' + inputs: + testResultsFormat: 'VSTest' # Options: JUnit, NUnit, VSTest, xUnit + + testResultsFiles: 'TestResults/*.trx' + + searchFolder: '$(System.DefaultWorkingDirectory)' + + publishRunAttachments: true + + - task: Docker@1 + condition: or(eq(variables['buildImage'],True),eq(variables['fullCI'],True)) + displayName: 'Build runtime image' + inputs: + + azureSubscriptionEndpoint: $(azureSubscription) + + azureContainerRegistry: $(azureContainerRegistry) + + dockerFile: $(System.DefaultWorkingDirectory)/$(dockerFileName) + + includeLatestTag: false + + imageName: '$(imageName)' + + - task: Docker@1 + condition: eq(variables['fullCI'],True) + displayName: 'Push runtime image' + inputs: + azureSubscriptionEndpoint: $(azureSubscription) + + azureContainerRegistry: $(azureContainerRegistry) + + command: 'Push an image' + + imageName: '$(imageName)' + + includeSourceTags: false + + - task: HelmInstaller@0 + condition: eq(variables['fullCI'],True) + displayName: 'Install Helm 3.0.3' + inputs: + helmVersion: 3.0.3 + + checkLatestHelmVersion: false + + kubectlVersion: 1.12.4 + + checkLatestKubectl: false + + - task: HelmDeploy@0 + condition: eq(variables['fullCI'],True) + displayName: 'helm package' + inputs: + command: package + + chartPath: $(chartPath) + + chartVersion: $(Build.SourceBranchName) + + updateDependency: true + + save: false + + arguments: '--app-version $(Build.SourceBranchName)' + + - task: AzureCLI@1 + condition: eq(variables['fullCI'],True) + displayName: 'Push a helm package' + inputs: + azureSubscription: $(azureSubscription) + + scriptLocation: inlineScript + + inlineScript: | + az acr helm push $(System.ArtifactsDirectory)/$(repositoryName)-$(Build.SourceBranchName).tgz --name $(azureContainerRegistryName) --force;