diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1670752 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +################################################################################ +# This .gitignore file was automatically created by Microsoft(R) Visual Studio. +################################################################################ + +/.vs/slnx.sqlite diff --git a/README.md b/README.md index b833886..6797980 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,16 @@ -# Avanade DevOps HOL -These are the hands on labs associated with the Avanade DevOps Practitioners course. This is based to a large part on the [PartsUnlimitedHOL](https://microsoft.github.io/PartsUnlimited/basic/GettingStarted.html) but simplified to use the Visual Studio MVC sample application. - -You can accomplish these labs using Visual Studio 2017 on your local computer, but you may want to consider doing the labs using an Azure VM as the development machine. This not only keeps you from having to make changes to your local environment, but it gives you additional experience using Azure. You can either configure an Azure development environment on your own or an easy way to do this is to use PowerShell ISE and execute the following commands. This will create a new Azure resource group and then configure an Azure VM with Windows 10 and Visual Studio 2017 Community edition. It will also use Chocolatey to install a collection of other tools and applications. **Review and modify the script to suit your own needs before executing such as changing to VS Enterprise and Window Server 2016 (VS-2017-Ent-Latest-WS2016)** - ->**Note:** Sometimes this all works great but other times, the Chocolatey packages do not install when the VM is first created so you may need to run choco install for the individual packages to complete the environment setup. - ->**Note:** Run PowerShell as an administrator. - -```PowerShell -Login-AzureRmAccount -Select-AzureRmSubscription -SubscriptionName andrewmo -$VmName = "DevOpsHOL" -$DnsLabelPrefix = "" -$VmIPName = $VmName+"-ip" -$VmAdminUserName = "" -$VmAdminPassword ="" -$ResourceGroupName = "DevOpsHOL" -$ResourceGroupLocation = "East US 2" -$SecureStringPwd = ConvertTo-SecureString $VmAdminPassword -AsPlainText -Force -New-AzureRmResourceGroup -Name $ResourceGroupName -Location $ResourceGroupLocation -Verbose -Force -New-AzureRmResourceGroupDeployment -ResourceGroupName $ResourceGroupName ` - -TemplateUri "https://raw.githubusercontent.com/nagroma/DevOpsHOL/master/azure-rm/azuredeploy.json" ` - -VmName $VmName ` - -VmSize "Standard_D2s_v3" ` - -VmVisualStudioVersion "VS-2017-Comm-Latest-Win10-N" ` - -VmAdminUserName $VmAdminUserName ` - -VmAdminPassword $SecureStringPwd ` - -DnsLabelPrefix $DnsLabelPrefix ` - -ChocoPackages 'visualstudiocode;notepadplusplus;googlechrome' ` - -Force -Verbose -``` - -Once the script above completes, you can use the following to start the VM and check to see that everything was installed correctly. - -```PowerShell -Start-AzureRmVM -ResourceGroupName $ResourceGroupName -Name $VmName -while((Get-AzureRmVM -ResourceGroupName $ResourceGroupName -Name $VmName -Status | ` - select -ExpandProperty Statuses | ` - ?{ $_.Code -match "PowerState" } | ` - select -ExpandProperty DisplayStatus) -ne "VM running") -{ - Start-Sleep -s 2 -} -Start-Sleep -s 5 ## Give the VM time to come up so it can accept remote requests -$vmip = Get-AzureRmPublicIpAddress -Name $VmIPName -ResourceGroupName $ResourceGroupName -$hostdns = $vmip.IpAddress.ToString() ## $vmip.DnsSettings.Fqdn -cmdkey /generic:TERMSRV/$hostdns /user:"$VmName\$VmAdminUserName" /pass:$VmAdminPassword -Start-Process "mstsc" -ArgumentList "/V:$hostdns /f" ## use /span to use both monitors -``` - -Finally, when you are done, you can use the following scripts to -1. Shut down the VM to minimize Azure charges -```PowerShell -Stop-AzureRmVM -ResourceGroupName $ResourceGroupName -Name $VmName -Force -``` -2. Delete the entire resource group when done with the labs or to start fresh. -```PowerShell -Remove-AzureRmResourceGroup -Name $ResourceGroupName -``` - - -Once you have a development environment set up, dive right in to the first [Getting Started](getting-started/README.md) lab. - +# Avanade DevOps Hands-On Labs +The labs can be completed using an Azure Virtual Machine as development environment. This not only keeps you from having to make changes to your local environment, but it gives you additional experience using Azure and PowerShell. The [Prerequisites](azure-rm/README.md) instructions help you to set up this environment. + +## Course Labs +The course contains the following labs: + +| Lab | Effort | +| :-------- |:--------------------------:| +| [Prerequisites - Set up development environment](azure-rm/README.md) | 30 min | +| [Lab 1 - Pipelines as code with K8s and Terraform](https://dev.azure.com/thx1139/_git/workshop1?path=%2FREADME.md) | 60 min | +| [Lab 2 - UI Testing with Selenium and Azure DevOps](ui-testing/README.md) | 45 min | +| [Lab 3 - Static code analysis with SonarQube](sonarqube/README.md) | 45 min | +| [Lab 4 - Feature Toggles](feature-flag/README.md) | 45 min | +| [Lab 5 - Working with Azure Security Scanner](secure-devops-kit-adoscanner/README.md) | 45 min | +| [Lab 6 - Validating the release with automated Smoke Testing](smoke-testing/README.md) | 45 min | +| [Lab 7 - *(Optional)* - Feature branching and branch protection](feature-branching/README.md) | 45 min | diff --git a/azure-devops-project/README.md b/azure-devops-project/README.md new file mode 100644 index 0000000..c3fd963 --- /dev/null +++ b/azure-devops-project/README.md @@ -0,0 +1,266 @@ +# Continuous Integration with Azure DevOps + +In this lab, we setup our DevOps Project in Azure to create our CI/CD pipeline. This will provide us with a standard code base to work with. We will also generate a YAML file from the existing pipeline, so we can work with Pipeline as Code in future labs. We will use the existing Build steps as a template and expand the coded pipeline with a Deploy stage. + +Based on the following tutorials: +- [Azure Devops Project ASP.NET Core](https://docs.microsoft.com/en-us/azure/devops-project/azure-devops-project-aspnet-core) +- [Adding Continuous Delivery to the YAML definition](https://www.azuredevopslabs.com/labs/azuredevops/yaml/#task-4-adding-continuous-delivery-to-the-yaml-definition) + +## Prerequisites + +- Complete the [Prerequisites](prerequisites.md) lab. + +## Tasks + +### Setting up the code repository on your development machine and Create Azure Storage Account. + +1. Go to your Azure Portal and create a new DevOps Project. Make sure it meets the following demands: + - .NET Runtime + - ASP.NET Core application + - Kubernetes Service + - Linked to your existing Azure DevOps account + +1. When the Azure resources are created, clone your code repository to your development environment using: + - Open file explorer and go to a folder where you want to store the project files. In the address bar type PowerShell and press enter. A new PowerShell window will open with his location inside the folder of your choice. + - Run the next PowerShell command to clone the repository locally: + `git clone ` + +1. Create an Azure Storage account which will be used to store the Terraform state. + - In your browser, go to your Azure Portal and create a new Storage Account. Make sure it meets the following demands: + - Resource group: `` + - Storage account name: `devopsholstorage` + - Location: (Europe) West Europe + - Leave everything else by default settings + +1. Create an Azure Container in the just created Azure Storage Account meeting the following demands: + - Name: `terraform-state` + - Public access level: `Private (no anonymous acces)` + +### Add Terraform templates to your repository +1. In the root folder of your repository, create a new folder called `TerraformTemplates`. Because you can't add an empty folder to git, you also need to create a new file. Name this file `main.tf`. + - Make sure that the file `main.tf` contains the following content: + ``` + provider "azurerm" { + version = "~>1.5" + } + + terraform { + backend "azurerm" {} + } + ``` + - Create a new file `k8s.tf` and add the following content: + ``` + resource "azurerm_resource_group" "k8s" { + name = var.resource_group_name + location = var.location + } + + resource "random_id" "log_analytics_workspace_name_suffix" { + byte_length = 8 + } + + resource "azurerm_log_analytics_workspace" "test" { + # The WorkSpace name has to be unique across the whole of azure, not just the current subscription/tenant. + name = "${var.log_analytics_workspace_name}-${random_id.log_analytics_workspace_name_suffix.dec}" + location = var.log_analytics_workspace_location + resource_group_name = azurerm_resource_group.k8s.name + sku = var.log_analytics_workspace_sku + } + + resource "azurerm_log_analytics_solution" "test" { + solution_name = "ContainerInsights" + location = azurerm_log_analytics_workspace.test.location + resource_group_name = azurerm_resource_group.k8s.name + workspace_resource_id = azurerm_log_analytics_workspace.test.id + workspace_name = azurerm_log_analytics_workspace.test.name + + plan { + publisher = "Microsoft" + product = "OMSGallery/ContainerInsights" + } + } + + resource "azurerm_kubernetes_cluster" "k8s" { + name = var.cluster_name + location = azurerm_resource_group.k8s.location + resource_group_name = azurerm_resource_group.k8s.name + dns_prefix = var.dns_prefix + + linux_profile { + admin_username = "azureuser" + + ssh_key { + key_data = var.ssh_public_key + } + } + + default_node_pool { + name = "agentpool" + node_count = var.agent_count + vm_size = "Standard_DS1_v2" + } + + service_principal { + client_id = var.client_id + client_secret = var.client_secret + } + + addon_profile { + oms_agent { + enabled = true + log_analytics_workspace_id = azurerm_log_analytics_workspace.test.id + } + } + + tags = { + Environment = "Development" + } + } + ``` + - Create a new file `variables.tf` and add the following content: + ``` + variable "client_id" {} + variable "client_secret" {} + + variable "agent_count" { + default = 1 + } + + variable "ssh_public_key" { + default = "~/.ssh/id_rsa.pub" + } + + variable "dns_prefix" { + default = "devopsholk8s" + } + + variable cluster_name { + default = "devopsholk8s" + } + + variable resource_group_name { + default = "devopsholk8s-rg" + } + + variable location { + default = "West Europe" + } + + variable log_analytics_workspace_name { + default = "testLogAnalyticsWorkspaceName" + } + + variable log_analytics_workspace_location { + default = "westeurope" + } + + variable log_analytics_workspace_sku { + default = "PerGB2018" + } + ``` + **!Be aware!** Change the value of the variable `resource_group_name` to the name of your Resource Group. + + - Create a new file `output.tf` and add the following content: + ``` + output "client_key" { + value = azurerm_kubernetes_cluster.k8s.kube_config.0.client_key + } + + output "client_certificate" { + value = azurerm_kubernetes_cluster.k8s.kube_config.0.client_certificate + } + + output "cluster_ca_certificate" { + value = azurerm_kubernetes_cluster.k8s.kube_config.0.cluster_ca_certificate + } + + output "cluster_username" { + value = azurerm_kubernetes_cluster.k8s.kube_config.0.username + } + + output "cluster_password" { + value = azurerm_kubernetes_cluster.k8s.kube_config.0.password + } + + output "kube_config" { + value = azurerm_kubernetes_cluster.k8s.kube_config_raw + } + + output "host" { + value = azurerm_kubernetes_cluster.k8s.kube_config.0.host + } + ``` + + - Save all changes, commit and push them to your repository. + + +### Set up the Pipeline as Code + +1. In the root folder of your repository, create a new file called `azure-pipelines.yaml` + +1. In your browser, go to Azure DevOps Pipelines, Open the Build pipelines and Edit the existing build pipeline: + - Add Terraform Init step by clicking on the **+** sign you see behind Agent job 1. Here you can search for Terraform. + - Terraform is by default not installed in your DevOps project. So you should click on Add Terraform, you will be redirected to the Market Place. Follow the steps to install Terraform and when you are finished you can close the window. + + - After installing Terraform, you should see the following search results: + ![](../images/pipelines-terraform-install.png) + + - Add a Terraform step between `helm package` and `Publish Artifacts: drop` with the following configuration: + - Name: `Terraform: init` + - Command: `init` + - Configuration directory: `TerraformTemplates` + - Azure subscription: `` + - Resource group: `` + - Storage account: `devopsholstorage` + - Container: `terraform-state` + - Key: ` Settings --> Access keys>` + + - Add a Terraform step after the previous created Terraform step with the following configuration: + - Name: `Terraform: validate` + - Command: `validate` + - Configuration directory: `TerraformTemplates` + + - Modify the copy ARM templates step, because we are going to work with Terraform. Make the properties has the followings values: + - Display name: `Copy Terraform templates` + - Source Folder: `TerraformTemplates` + - Contents: `*.tf` + - Target Folder: `$(Build.ArtifactStagingDirectory)/TerraformTemplates` + + - Go to Triggers and disable continuous integration: + ![](../images/pipelines-disable-ci.png) + + - Save the pipeline changes, do not queue + + - Select the Agent Job and click 'View YAML': + ![](../images/pipelines-view-yaml.png) + + - Select all text from the YAML view and copy it + +1. Paste all the contents into your `azure-pipelines.yaml` file + +1. Save all changes and commit + push them to your repository + +### Set up a pipeline in Azure Devops using the YAML file + +1. Go to Azure DevOps Pipelines and click 'New Pipeline' + +1. Create the new pipeline with the following settings: + - Azure Repos Git (YAML) + - Choose your git repository + - Existing Azure Pipelines YAML file + - Choose the YAML file from your repository + +1. Add a new variable BuildConfiguration + - By adding the following yaml code on top: + ``` + variables: + configuration: release + ``` + +1. Click Run to finalize the setup and wait for the build to complete + +## Next steps +Return to [the lab index](../README.md) and continue with the next lab. + +##TODO: +- secrets uit de variables halen en als variabelen in pipeline \ No newline at end of file diff --git a/azure-rm/README.md b/azure-rm/README.md new file mode 100644 index 0000000..8c57afd --- /dev/null +++ b/azure-rm/README.md @@ -0,0 +1,63 @@ +# Prerequisites lab - Set up your development environment +Follow this lab to set up a development environment for the course labs. Creating the environment can take some time, so it is recommended to complete this before attending the course. + +## Prepare Azure DevOps and Azure subscription + +1. Make sure you have an active Azure DevOps account.\ +[Sign up for Azure DevOps](https://dev.azure.com/) + +1. Make sure you have an active Azure subscription.\ +Sign in to the [Azure Portal](https://portal.azure.com) to verify you can log in and create resources.\ +If you used a (Avanade) organization MDSN account, go to the next step. + +1. (only for organization accounts) If you are using your Avanade email to access the Azure portal, then you will need to create a new Active Directory instance. This is because the labs require creating an enterprise application id and individual users do not have permissions to create enterprise applications on the Avanade AD instance. + +- In the [Azure Portal](https://portal.azure.com), *Create a Resource* of *Azure Active Directory* with a meaningful name. This is probably something you will use as a general purpose AD instance (i.e. not just for the class) so you may want to name it appropriately. + +- For more information, you can reference [Create an Azure Active Directory tenant](https://docs.microsoft.com/en-us/power-bi/developer/create-an-azure-active-directory-tenant#create-an-azure-active-directory-tenant). **DO NOT** execute steps in *Create some users in your Azure Active Directory tenant* section. + - Once the AD instance is created, click on the *All services* menu item and search for *Subscriptions*. Choose the subscription that is tied to your MSDN account (this is the one you will use for the class). If you don't see the correct subscription, you might need to switch directories. + - On the Subscriptions' Overview panel, choose the -> Change directory link and select the new AD instance that you just created. + - For more information, you can reference [Associate an existing subscription to your Azure AD directory](https://docs.microsoft.com/en-us/azure/active-directory/active-directory-how-subscriptions-associated-directory#to-associate-an-existing-subscription-to-your-azure-ad-directory) + +## Prepare development environment + +1. Create a DevTest lab\ +Follow instructions from [Create a lab in Azure DevTest Labs](https://docs.microsoft.com/azure/lab-services/devtest-lab-create-lab). Ensure the following settings: + - Lab name: devopslab + - Resource group: devopslab (new) + - Location: West Europe + - Auto-shutdown: default (Enabled, 19:00) + +1. Add a Virtual Machine to your DevTest lab\ +Follow instructions from [Add a VM to a lab in Azure DevTest Labs](https://docs.microsoft.com/azure/lab-services/devtest-lab-add-vm). Ensure the following settings: + - Choose a base: Visual Studio 2019 Community on Windows 10 Enterprise N (x64) + - Virtual machine name: devopsvm + - User name: devopshol + - Password: ADP#2019 + - Virtual machine size: + - Pick any size that fits within your subscription + - OS disk type: Standard SSD + - Artifacts selection: Install Chocolatey Packages, configuration: + - Packages: `git,vscode,microsoft-edge-insider` + - Allow Empty Checksums: true + - Ignore Checksums: true + +1. Verify connection to the Virtual Machine + - Wait untill the virtual machine is fully provisioned and the artifacts are applied.\ + *(this can take up to 20 minutes)* + - Verify you can use the virtual machine by connecting to it:\ + Select 'Connect' in the Virtual machine Overview in the portal and provide the credentials you used in the previous step. + +1. Install .NET Core SDK version 3.1: + - Open a PowerShell window + - Enter this command and press Enter: + - `choco install dotnetcore-sdk` + +1. Install Azure CLI + - Open a PowerShell window + - Enter this command and press Enter: + + `Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi; Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'` + +## Next steps +Return to the [Lab index](../README.md) to continue with the course labs. \ No newline at end of file diff --git a/azure-rm/azuredeploy.json b/azure-rm/azuredeploy.json deleted file mode 100644 index 4c60d44..0000000 --- a/azure-rm/azuredeploy.json +++ /dev/null @@ -1,252 +0,0 @@ -{ - "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.1", - "parameters": { - "storageType": { - "type": "string", - "defaultValue": "Premium_LRS", - "allowedValues": [ - "Premium_LRS", - "Standard_LRS" - ], - "metadata": { - "description": "Which type of storage you want to use" - } - }, - "vmName": { - "type": "string", - "defaultValue": "VSVM-choco", - "metadata": { - "description": "Local name for the VM can be whatever you want" - } - }, - "vmAdminUserName": { - "type": "string", - "defaultValue": "andrewmo", - "metadata": { - "description": "VM admin user name" - } - }, - "vmAdminPassword": { - "type": "securestring", - "metadata": { - "description": "VM admin password. The supplied password must be between 8-123 characters long and must satisfy at least 3 of password complexity requirements from the following: 1) Contains an uppercase character 2) Contains a lowercase character 3) Contains a numeric digit 4) Contains a special character." - } - }, - "vmSize": { - "type": "string", - "metadata": { - "description": "Desired Size of the VM. Any valid option accepted but if you choose premium storage type you must choose a DS class VM size." - }, - "defaultValue": "Standard_D2s_v3" - }, - "vmVisualStudioVersion": { - "type": "string", - "defaultValue": "VS-2017-Ent-Latest-Win10-N", - "allowedValues": [ - "VS-2015-Comm-VSU3-AzureSDK-29-Win10-N", - "VS-2015-Comm-VSU3-AzureSDK-29-WS2012R2", - "VS-2015-Ent-VSU3-AzureSDK-29-Win10-N", - "VS-2015-Ent-VSU3-AzureSDK-29-WS2012R2", - "VS-2017-Comm-Latest-Win10-N", - "VS-2017-Comm-Latest-WS2016", - "VS-2017-Comm-Win10-N", - "VS-2017-Comm-WS2016", - "VS-2017-Ent-Latest-Win10-N", - "VS-2017-Ent-Latest-WS2016", - "VS-2017-Ent-Win10-N", - "VS-2017-Ent-WS2016" - ], - "metadata": { - "description": "Which version of Visual Studio you would like to deploy" - } - }, - "dnsLabelPrefix": { - "type": "string", - "defaultValue": "andrewmo-choco", - "metadata": { - "description": "DNS Label for the unique Public IP. Must be lowercase. It should match with the following regular expression: ^[a-z][a-z0-9-]{1,61}[a-z0-9]$ or it will raise an error." - } - }, - "chocoPackages": { - "type": "string", - "defaultValue": "sysinternals;googlechrome", - "metadata": { - "description": "List of Chocolatey packages to install separated by a semi-colon. Can not be empty. eg. nodejs-lts;python2;visualstudiocode;notepadplusplus;googlechrome;dotnetcore --version 1.1.2" - } - }, - "setupChocolateyScriptFileName": { - "type": "string", - "defaultValue": "SetupChocolatey.ps1", - "metadata": { - "description": "PowerShell script name to execute" - } - }, - "setupChocolatelyScriptLocation": { - "type": "string", - "defaultValue": "https://raw.githubusercontent.com/snagrom/azure-rm-templates/master/dev-chocolatey/scripts/", - "metadata": { - "description": "Public uri location of PowerShell Chocolately setup script" - } - } - }, - "variables": { - "storageName": "[concat('stor', uniquestring(resourceGroup().id))]", - "vnet01Prefix": "10.0.0.0/16", - "vnet01Subnet1Name": "Subnet-1", - "vnet01Subnet1Prefix": "10.0.0.0/24", - "vmImagePublisher": "MicrosoftVisualStudio", - "vmImageOffer": "VisualStudio", - "vmOSDiskName": "VMOSDisk", - "vmVnetName": "[concat(parameters('VMName'), '-net')]", - "vmVnetID": "[resourceId('Microsoft.Network/virtualNetworks', variables('vmVnetName'))]", - "vmSubnetRef": "[concat(variables('vmVnetID'), '/subnets/', variables('Vnet01Subnet1Name'))]", - "vmStorageAccountContainerName": "vhds", - "vmNicName": "[concat(parameters('vmName'), '-nic')]", - "vmIP01Name": "[concat(parameters('vmName'), '-ip')]" - }, - "resources": [ - { - "name": "[variables('storageName')]", - "type": "Microsoft.Storage/storageAccounts", - "location": "[resourceGroup().location]", - "apiVersion": "2015-06-15", - "dependsOn": [ ], - "tags": { - "displayName": "Storage01" - }, - "properties": { - "accountType": "[parameters('storageType')]" - } - }, - { - "name": "[variables('vmVnetName')]", - "type": "Microsoft.Network/virtualNetworks", - "location": "[resourceGroup().location]", - "apiVersion": "2015-06-15", - "dependsOn": [ ], - "properties": { - "addressSpace": { - "addressPrefixes": [ - "[variables('vnet01Prefix')]" - ] - }, - "subnets": [ - { - "name": "[variables('vnet01Subnet1Name')]", - "properties": { - "addressPrefix": "[variables('vnet01Subnet1Prefix')]" - } - } - ] - } - }, - { - "name": "[variables('vmNicName')]", - "type": "Microsoft.Network/networkInterfaces", - "location": "[resourceGroup().location]", - "apiVersion": "2015-06-15", - "dependsOn": [ - "[concat('Microsoft.Network/virtualNetworks/', variables('vmVnetName'))]", - "[concat('Microsoft.Network/publicIPAddresses/', variables('vmIP01Name'))]" - ], - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "privateIPAllocationMethod": "Dynamic", - "subnet": { - "id": "[variables('vmSubnetRef')]" - }, - "publicIPAddress": { - "id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('vmIP01Name'))]" - } - } - } - ] - } - }, - { - "name": "[parameters('vmName')]", - "type": "Microsoft.Compute/virtualMachines", - "location": "[resourceGroup().location]", - "apiVersion": "2015-06-15", - "dependsOn": [ - "[concat('Microsoft.Storage/storageAccounts/', variables('storageName'))]", - "[concat('Microsoft.Network/networkInterfaces/', variables('vmNicName'))]" - ], - "properties": { - "hardwareProfile": { - "vmSize": "[parameters('vmSize')]" - }, - "osProfile": { - "computerName": "[parameters('vmName')]", - "adminUsername": "[parameters('vmAdminUsername')]", - "adminPassword": "[parameters('vmAdminPassword')]" - }, - "storageProfile": { - "imageReference": { - "publisher": "[variables('vmImagePublisher')]", - "offer": "[variables('vmImageOffer')]", - "sku": "[parameters('vmVisualStudioVersion')]", - "version": "latest" - }, - "osDisk": { - "name": "VMOSDisk", - "vhd": { - "uri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('storageName')), '2015-06-15').primaryEndpoints.blob, variables('vmStorageAccountContainerName'), '/', variables('vmOSDiskName'), '.vhd')]" - }, - "caching": "ReadWrite", - "createOption": "FromImage" - } - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('vmNicName'))]" - } - ] - } - }, - "resources": [ - { - "type": "extensions", - "name": "CustomScriptExtension", - "apiVersion": "2015-06-15", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[parameters('vmName')]" - ], - "properties": { - "publisher": "Microsoft.Compute", - "type": "CustomScriptExtension", - "typeHandlerVersion": "1.8", - "autoUpgradeMinorVersion": true, - "settings": { - "fileUris": [ - "[concat(parameters('setupChocolatelyScriptLocation'),parameters('setupChocolateyScriptFileName'))]" - ], - "commandToExecute": "[concat('powershell -ExecutionPolicy Unrestricted -File ',parameters('setupChocolateyScriptFileName'),' -chocoPackages ',parameters('chocoPackages'))]" - } - } - } - ] - }, - { - "name": "[variables('vmIP01Name')]", - "type": "Microsoft.Network/publicIPAddresses", - "location": "[resourceGroup().location]", - "apiVersion": "2015-06-15", - "dependsOn": [ ], - "properties": { - "publicIPAllocationMethod": "Dynamic", - "dnsSettings": { - "domainNameLabel": "[parameters('dnsLabelPrefix')]" - } - } - } - ], - "outputs": { } - } - \ No newline at end of file diff --git a/azure-rm/azuredeploy.parameters.json b/azure-rm/azuredeploy.parameters.json deleted file mode 100644 index 5137947..0000000 --- a/azure-rm/azuredeploy.parameters.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "adminUsername": { - "value": "andrewmo" - }, - "adminPassword": { - "value": "TopSecretPassword123#$" - } - } -} \ No newline at end of file diff --git a/azure-rm/scripts/PostDeploy.ps1 b/azure-rm/scripts/PostDeploy.ps1 deleted file mode 100644 index d9d0dfd..0000000 --- a/azure-rm/scripts/PostDeploy.ps1 +++ /dev/null @@ -1,116 +0,0 @@ -Param( - [string]$ChocoPackages, - [bool]$PartsUnlimited, - [bool]$Extras, - [string]$VmAdminUserName, - [Security.SecureString]$VmAdminPassword -) - -cls -function buildVS -{ - param - ( - [parameter(Mandatory=$true)] - [String] $path, - - [parameter(Mandatory=$false)] - [bool] $nuget = $true, - - [parameter(Mandatory=$false)] - [bool] $clean = $true - ) - process - { - $msBuildExe = 'C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\msbuild.exe' - - if ($nuget) { - Write-Host "Restoring NuGet packages" -foregroundcolor green - nuget restore "$($path)" - } - - if ($clean) { - Write-Host "Cleaning $($path)" -foregroundcolor green - & "$($msBuildExe)" "$($path)" /t:Clean /m - } - - Write-Host "Building $($path)" -foregroundcolor green - & "$($msBuildExe)" "$($path)" /t:Build /m - } -} - -New-Item "c:\choco" -type Directory -force | Out-Null -$LogFile = "c:\choco\Script.log" -$ChocoPackages | Out-File $LogFile -Append -$PartsUnlimited | Out-File $LogFile -Append -$Extras | Out-File $LogFile -Append -$VmAdminUserName | Out-File $LogFile -Append -$VmAdminPassword | Out-File $LogFile -Append - -$secPassword = ConvertTo-SecureString $VmAdminPassword -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential("$env:COMPUTERNAME\$($VmAdminUserName)", $secPassword) - -# Ensure that current process can run scripts. -"Enabling remoting" | Out-File $LogFile -Append -Enable-PSRemoting -Force -SkipNetworkProfileCheck - -"Changing ExecutionPolicy" | Out-File $LogFile -Append -Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force - -#"Install each Chocolatey Package" -if (-not [String]::IsNullOrWhiteSpace($ChocoPackages)){ - $ChocoPackages.Split(";") | ForEach { - $command = "cinst " + $_ + " -y -force" - $command | Out-File $LogFile -Append - $sb = [scriptblock]::Create("$command") - - # Use the current user profile - Invoke-Command -ScriptBlock $sb -ArgumentList $ChocoPackages -ComputerName $env:COMPUTERNAME -Credential $credential | Out-Null - } -} - -if ($PartsUnlimited){ - "Configuring PartsUnlimitedHOL" | Out-File $LogFile -Append - Invoke-Command -ScriptBlock { - $slnPath = "$env:userprofile\Desktop\PartsUnlimitedHOL" - "slnPath: $slnPath" - npm install bower -g - npm install grunt-cli -g - Copy-Item C:\Python27\python.exe C:\Python27\python2.exe - New-Item $slnPath -type directory -force - git clone https://github.com/Microsoft/PartsUnlimited.git $slnPath # The error message "CategoryInfo : NotSpecified: (Switched to branch..." is a documented issues but no workaround. This still works fine. - #Sometimes the \AppData\Roaming\npm gets added to the path and some times it doesn't. This makes sure. - "Adding $env:userprofile\AppData\Roaming\npm to path" - $AddedLocation ="$env:userprofile\AppData\Roaming\npm" - $Reg = "Registry::HKLM\System\CurrentControlSet\Control\Session Manager\Environment" - $OldPath = (Get-ItemProperty -Path "$Reg" -Name PATH).Path - "OldPath: $OldPath" - if(-Not $OldPath.Contains($AddedLocation)) { - $NewPath= $OldPath + ';' + $AddedLocation - Set-ItemProperty -Path "$Reg" -Name PATH -Value $NewPath - $UpdatedPath = (Get-ItemProperty -Path "$Reg" -Name PATH).Path - "UpdatedPath: $UpdatedPath" - } - - refreshenv - buildVS "$slnPath\PartsUnlimited.sln" -nuget $true -clean $false - Remove-Item $slnPath\src\PartsUnlimitedWebsite\node_modules -Force -Recurse - buildVS "$slnPath\PartsUnlimited.sln" -nuget $true -clean $true - } -ComputerName $env:COMPUTERNAME -Credential $credential | Out-File $LogFile -Append -} - -#A few more settings that I like but are not required for the PartsUnlimitedHOL -if ($Extras){ - "Configuring Extras" | Out-File $LogFile -Append - Invoke-Command -ScriptBlock { - # Show file extensions (have to restart Explorer for this to take effect if run maually - Stop-Process -processName: Explorer -force) - Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" -Name HideFileExt -Value "0" - - Set-TimeZone -Name "Eastern Standard Time" - - Enable-WindowsOptionalFeature –online –featurename IIS-WebServerRole - - } -ComputerName $env:COMPUTERNAME -Credential $credential | Out-File $LogFile -Append -} - -Disable-PSRemoting -Force diff --git a/azure-rm/scripts/SetupChocolatey.ps1 b/azure-rm/scripts/SetupChocolatey.ps1 deleted file mode 100644 index 139124e..0000000 --- a/azure-rm/scripts/SetupChocolatey.ps1 +++ /dev/null @@ -1,64 +0,0 @@ -param([Parameter(Mandatory=$true)][string]$chocoPackages) -Write-Output $chocoPackages -cls - -New-Item "c:\choco" -type Directory -force | Write-Output -$LogFile = "c:\choco\Script.log" -$chocoPackages | Out-File $LogFile -Append - -# Get username/password & machine name -$userName = "artifactInstaller" -[Reflection.Assembly]::LoadWithPartialName("System.Web") | Out-Null -$password = $([System.Web.Security.Membership]::GeneratePassword(12,4)) -$cn = [ADSI]"WinNT://$env:ComputerName" - -# Create new user -$user = $cn.Create("User", $userName) -$user.SetPassword($password) -$user.SetInfo() -$user.description = "Choco artifact installer" -$user.SetInfo() - -# Add user to the Administrators group -$group = [ADSI]"WinNT://$env:ComputerName/Administrators,group" -$group.add("WinNT://$env:ComputerName/$userName") - -# Create pwd and new $creds for remoting -$secPassword = ConvertTo-SecureString $password -AsPlainText -Force -$credential = New-Object System.Management.Automation.PSCredential("$env:COMPUTERNAME\$($username)", $secPassword) - -# Ensure that current process can run scripts. -"Enabling remoting" | Write-Output -"Enabling remoting" | Out-File $LogFile -Append -Enable-PSRemoting -Force -SkipNetworkProfileCheck - -#"Changing ExecutionPolicy" | Out-File $LogFile -Append -Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force - -# Install Choco -"Installing Chocolatey" | Out-File $LogFile -Append -$sb = { iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1')) } -Invoke-Command -ScriptBlock $sb -ComputerName $env:COMPUTERNAME -Credential $credential | Out-Null - -"Disabling UAC" | Out-File $LogFile -Append -$sb = { Set-ItemProperty -path HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\System -name EnableLua -value 0 } -Invoke-Command -ScriptBlock $sb -ComputerName $env:COMPUTERNAME -Credential $credential - -#"Install each Chocolatey Package" -$chocoPackages.Split(";") | ForEach { - $command = "cinst " + $_ + " -y -force" - $command | Out-File $LogFile -Append - $sb = [scriptblock]::Create("$command") - - "Installing $_" | Out-File $LogFile -Append - # Use the current user profile - Invoke-Command -ScriptBlock $sb -ArgumentList $chocoPackages -ComputerName $env:COMPUTERNAME -Credential $credential | Out-Null -} - -Disable-PSRemoting -Force - -# Delete the artifactInstaller user -$cn.Delete("User", $userName) - -# Delete the artifactInstaller user profile -gwmi win32_userprofile | where { $_.LocalPath -like "*$userName*" } | foreach { $_.Delete() } \ No newline at end of file diff --git a/continuous-deployment/README.md b/continuous-deployment/README.md index 53a3df9..705f3ed 100644 --- a/continuous-deployment/README.md +++ b/continuous-deployment/README.md @@ -1,11 +1,9 @@ -Avanade DevOps HOL - Continuous Deployment with Visual Studio Release Management -==================================================================================== +# Avanade DevOps HOL - Continuous Deployment with Visual Studio Release Management In this lab, you have an example MVC application, committed to a Git repository in Visual Studio Team Services (VSTS) and a Continuous Integration build that builds the app and runs unit tests whenever code is pushed to the master branch. Now you want to set up Release Management (a feature of Visual Studio Team Services) to be able continuously deploy the application to an Azure Web App. Initially the app will be deployed to a dev deployment slot. The staging slot will require and approver before the app is deployed into it. Once an approver approves the staging slot, the app will be deployed to the production site. ### Pre-requisites: ### - Complete [Continuous Integration](../continuous-integration/README.md) lab. - ### Tasks Overview: ### **1. Add ARM Template to Solution:** In this step, add a deployment project to the solution. This will create an ARM template which will be used to create the App Service and web site in Azure for deploying the solution into. @@ -27,21 +25,21 @@ In this lab, you have an example MVC application, committed to a Git repository 3. This will create a new project in the solution that contains the files for deployment to Azure. The **Website.json** file will be used to create the deployment environments in task III below.
NOTE: There are a number of ways that the ARM template could have been created and added to the solution. However, since a deployment project also automatically creates a PowerShell script for deployment and a template parameters file, this can also be used to manually deploy the solution if desired (but not in this lab). -4. Open the **WebSite.json** file and replace the contents with the content of(https://raw.githubusercontent.com/nagroma/DevOpsHOL/master/source/deploy/azuredeploy.json). This adds additional features to the deployment such as deployment slots which will be used later in the lab. +4. Open the **WebSite.json** file and replace the contents with the content of(https://raw.githubusercontent.com/Avanade/DevOpsHOL/master/source/deploy/azuredeploy.json). This adds additional features to the deployment such as deployment slots which will be used later in the lab. -5. Open the **WebSite.parameters.json** file and replace the contents with the content of(https://raw.githubusercontent.com/nagroma/DevOpsHOL/master/source/deploy/azuredeploy.parameters.json). This matches the parameters with the azuredeploy.json file. +5. Open the **WebSite.parameters.json** file and replace the contents with the content of(https://raw.githubusercontent.com/Avanade/DevOpsHOL/master/source/deploy/azuredeploy.parameters.json). This matches the parameters with the azuredeploy.json file. 4. Check in the solution and push to VSTS (Team Explorer -> Changes -> Commit All and Push). This checks in the ARM template into source control both locally and in the VSTS repository. This will also trigger a new build (check to make sure this happened and was successful). ### II. Create Service Endpoint 1. Go to your **account’s homepage**: - https://\.visualstudio.com + https://dev.azure.com/ 2. Select the DevOpsHOL team project that was created in the [Getting Started](../getting-started/README.md) lab. -This will take you to the project dashboard page. Click on the Setting icon (gear) in the project menu bar and select Services. Choose **New Service Endpoint** and select **Azure Resource Manager** from the drop down. +This will take you to the project dashboard page. Click on the Project Settings (gear) in the bottom left menu bar and select Service connnections. Choose **New service connection** and select **Azure Resource Manager** from the drop down. ![]() -3. This will open the **Add Azure Resource Manager Service Endpoint** dialog. Set the Connection Name to "DevOpsHOLDeployment", choose your subscription and click **OK**.
+3. This will open the **Add Azure Resource Manager Service Endpoint** dialog. Set the Connection Name to "DevOpsHOLDeployment", choose your subscription and click **OK**.
Leaving the Resource Group blank will allow this to work for multiple resource groups so you don't have to worry about setting this up again if you drop and recreate your Azure resource group. The service endpoint won't work even if the new resource group has the same name as the one selected when it was created.
**NOTE:** You will need to allow popups in the browser, otherwise the popup to authenticate to Azure will fail to appear. ![]() @@ -49,21 +47,19 @@ This will take you to the project dashboard page. Click on the Setting icon (ge 4. Next sign in to your Azure account to complete the endpoint creation process. ### III. Create Release -1. In the DevOpsHOL project in VSTS, navigate to **Build and Release** -> **Releases** and choose the + icon to create a new release definition. - -![]() +1. In the DevOpsHOL project in Azure DevOps, navigate to **Pipelines** -> **Releases** and click the **New Pipeline** button to create a new release definition. -2. Near the top of the **Select a Template** panel, choose **Empty process**. As in the previous lab, you normally would choose a template that closely matches the type of deployment you are doing (or import an existing template), but for this lab we are building it from scratch to see more of the process. +2. Near the top of the **Select a Template** panel, choose **Empty job**. As in the previous lab, you normally would choose a template that closely matches the type of deployment you are doing (or import an existing template), but for this lab we are building it from scratch to see more of the process. -3. Near the top of the page next to **New Release Definition**, click on the pencil icon and rename the release to **DevOpsHOL-CI - CD** +3. Near the top of the page next to **New release pipeline**, click on the pencil icon and rename the release to **DevOpsHOL-CD** -4. On the **Environment** panel, rename the environment to Dev (this will be the development integration environment). +4. On the **Stage** panel, rename the stage to Dev (this will be the development integration environment). -5. Click on the Artifacts +Add link and select the DevOpsHOL-CI as the Source. Leave the version at "Latest" so the release will deploy the latest build. Click Add button to add the build to the release artifacts list. +5. Click on the Artifacts +Add link and select the DevOpsHOL-CI as the Source. Set the version to "Latest" so the release will deploy the latest build. Click Add button to add the build to the release artifacts list. -6. In the Environments box, click on the 1 phase, 0 task link under the Dev environment name. This will open a panel to allow adding of tasks to the release. +6. In the Stages box, click on the 1 job, 0 task link under the Dev environment name. This will open a panel to allow adding of tasks to the release. -6. Click on the **Agent phase** accordion item and review the options for this phase of the release. Leave these settings as is for now. Click on the + icon to the right of Agent phase and add the following two tasks: **Azure Resource Group Deployment** and **Azure App Service Deploy**. +6. Click on the **Agent job** accordion item and review the options for this phase of the release. Leave these settings as is for now. Click on the + icon to the right of Agent phase and add the following two tasks: **Azure Resource Group Deployment** and **Azure App Service Deploy**. 7. Click on the **Azure Deployment:Create Or Update Resource Group...** accordion and fill out the settings as follows: >+ Azure Subscription: DevOpsHOLDeployment @@ -71,7 +67,7 @@ This will take you to the project dashboard page. Click on the Setting icon (ge >+ Resource Group: $(ResourceGroupName) >>+ this is a variable set up in the next step >+ Location: $(SiteLocation) ->+ Template: $(System.DefaultWorkingDirectory)/DevOpsHOL-CI/drop/DevOpsHOL.Deployment/WebSite.json +>+ Template: $(System.DefaultWorkingDirectory)/\_DevOpsHOL-CI/drop/DevOpsHOL.Deployment/WebSite.json >+ Override Template Parameters: Click on the ellipsis to the right of the edit box and enter the following names and values:
NOTE: ARM parameters are case sensitive so the name much match the case of the parameters in the ARM template. ```PowerShell @@ -97,21 +93,21 @@ workerSize "0" 9. Click **Variables** link and add the following variables and values. * **AppServicePlan**: DevOpsHOL *(or any name you would like)* * **ResourceGroupName**: DevOpsHOL *(or any name you would like)* - * **SiteLocation**: *Choose a valid Azure location such as "centralus"* + * **SiteLocation**: *Choose a valid Azure location such as "centralus" or "eastus2"* * **WebsiteName**: *Unique name of the website in Azure* > **Note**: Use a unique value for your WebsiteName by adding something custom at the end like your initials. Example for WebsiteName : devopshol-am 10. Click **Save** and then click **Release -> Create Release**, click **Create**. Inside the Green notification bar, click on the *Release-1* link to be taken to a page to show the release progress. If this page doesn't refresh automatically, periodically refresh the page until the release succeeds (if the release fails, review the error message and go back and make adjustments and re-release). -11. Open a browser page and navigate to the website in Azure. The url will depend on the $(WebsiteName). For example, if the $(WebsiteName) was devopshol-am, then navigate to https://devopshol-am-dev.azurewebsites.net/.
+11. Open a browser page and navigate to the website in Azure. The url will depend on the $(WebsiteName). For example, if the $(WebsiteName) was devopshol-batman, then navigate to https://devopshol-batman-dev.azurewebsites.net/.
NOTE: The website address can be seen on the Azure portal on the WebApp ![]() 12. Once the initial deployment has been verified, go back and edit the release to add staging and production releases. >+ Navigate to Build and Release -> Releases ->+ Hover over the **DevOpsHOL-CI - CD** Release Definition and right click on the ellipse (...) and select the Edit menu option. +>+ Hover over the **DevOpsHOL-CD** Release Definition and right click on the ellipse (...) and select the Edit menu option. >+ Hover over the **Dev** environment and select the **Clone** link. ![]() @@ -134,13 +130,17 @@ NOTE: The website address can be seen on the Azure portal on the WebApp 15. Now go back into Visual Studio and make a change to the code that will be visible in the application. Observe the status of the Build and Release and verify that all the items configured in the CI and CD labs complete successfully.
NOTE: If the Release fails with error "ERROR_FILE_IN_USE", execute [these steps](#errors). + +# Shortcut +Just like in the previous lab, you can use the import functionality with the [DevOpsHOL-CD.json](../source/deploy/DevOpsHOL-CD.json) file to import the release definition to keep from having to create this by hand but you miss out on the joy of learning. + +NOTE: Even if you use this import file, you'll have to go through each job and task to make sure the settings are appropriate since the values in the import file may not match what is in your environment. + # Next steps In this lab you created a series of environments using an Azure ARM template and automatically deployed to each of these environments. -1. Next do the [Feature Flag](../feature-flag/README.md) lab - -2. Explore other options. Here are some additional tasks that you may want to try to expand your knowledge further. +1. Explore other options. Here are some additional tasks that you may want to try to expand your knowledge further. >+ By default, the releases automatically move from environment to environment upon successful deployment to the previous deployment. Go back to the release definition and modify the settings to require approvals prior to deploying to the next environment. >+ Modify the ARM template (Website.json) to modify some of the properties (e.g. change the worker size or tier; modify the app settings per slot) @@ -149,8 +149,10 @@ and modify the settings to require approvals prior to deploying to the next envi >+ Delete the DevOpsHOL resource group and re-release the same build to make sure that the environments can be dynamically re-created. >+ Export the build and release definitions. Check them into source control. Delete these definitions and restore from the source files. +2. Next do the [UI Testing](../ui-testing/README.md) lab + # Common errors and solutions - ERROR_FILE_IN_USE: Web Deploy cannot modify the file 'DevOpsHOL.dll' on the destination because it is locked by an external program. > Solution: Open the Release definition Editor and go to the Additional Deployment Options for every environment (Dev/Stage/Prod). Change the values to the following:
- ![]() \ No newline at end of file + ![]() diff --git a/continuous-deployment/media/CD1.png b/continuous-deployment/media/CD1.png deleted file mode 100644 index 5e8e3ba..0000000 Binary files a/continuous-deployment/media/CD1.png and /dev/null differ diff --git a/continuous-deployment/media/CD2.png b/continuous-deployment/media/CD2.png deleted file mode 100644 index 3a82cad..0000000 Binary files a/continuous-deployment/media/CD2.png and /dev/null differ diff --git a/continuous-deployment/media/CD3.png b/continuous-deployment/media/CD3.png index bc3b0ce..d9793bb 100644 Binary files a/continuous-deployment/media/CD3.png and b/continuous-deployment/media/CD3.png differ diff --git a/continuous-deployment/media/CD4.png b/continuous-deployment/media/CD4.png index 555d816..0dad552 100644 Binary files a/continuous-deployment/media/CD4.png and b/continuous-deployment/media/CD4.png differ diff --git a/continuous-deployment/media/CD5.png b/continuous-deployment/media/CD5.png deleted file mode 100644 index 58cb337..0000000 Binary files a/continuous-deployment/media/CD5.png and /dev/null differ diff --git a/continuous-deployment/media/CD6.png b/continuous-deployment/media/CD6.png deleted file mode 100644 index 07ba9a7..0000000 Binary files a/continuous-deployment/media/CD6.png and /dev/null differ diff --git a/continuous-deployment/media/CD7.png b/continuous-deployment/media/CD7.png deleted file mode 100644 index 27a1c84..0000000 Binary files a/continuous-deployment/media/CD7.png and /dev/null differ diff --git a/continuous-deployment/media/CD8.png b/continuous-deployment/media/CD8.png deleted file mode 100644 index a86f73e..0000000 Binary files a/continuous-deployment/media/CD8.png and /dev/null differ diff --git a/continuous-deployment/media/CD_ERROR_FILE_IN_USE.png b/continuous-deployment/media/CD_ERROR_FILE_IN_USE.png deleted file mode 100644 index 098987c..0000000 Binary files a/continuous-deployment/media/CD_ERROR_FILE_IN_USE.png and /dev/null differ diff --git a/continuous-integration/README.md b/continuous-integration/README.md index 00e1436..a263a56 100644 --- a/continuous-integration/README.md +++ b/continuous-integration/README.md @@ -1,5 +1,4 @@ -Avanade DevOps HOL - Continuous Integration with Visual Studio Team Services -==================================================================================== +# Avanade DevOps HOL - Continuous Integration with Visual Studio Team Services In this lab, we use the application created in the Getting Started HOL to set up Visual Studio Team Services to be able continuously integrate code into the master branch of code. This means that whenever code is committed and pushed to the @@ -8,11 +7,10 @@ get fast feedback. To do so, we are going to be setting up a Continuous Integrat will allow us to compile and run unit tests on our code every time a commit is pushed to Visual Studio Team Services. +**NOTE:** Microsoft changes the way things work all the time so you may have to figure some of this out as the screens may not exactly follow but the principles are the same. + ## Pre-requisites: ## - Complete [Getting Started](../getting-started/README.md) hands on lab. -- An active Visual Studio Team Services account.
- [Sign up for Visual Studio Team Services](https://www.visualstudio.com/en-us/docs/setup-admin/team-services/sign-up-for-visual-studio-team-services) - ## Tasks Overview: ## @@ -28,49 +26,46 @@ we checked in can compile and will successfully pass any automated tests that we have created against it. 1. Go to your **account’s homepage**: - https://\.visualstudio.com + https://dev.azure.com/ 2. Select the DevOpsHOL team project that was created in the [Getting Started](../getting-started/README.md) lab. -This will take you to the project dashboard page. Click on "Set up Build". +This will take you to the project dashboard page. From the menu on the left side of the page, choose Pipelines -> Builds. Now click on the **New Pipeline** button. On the "Select your repository" page, make sure everything is correct and then click **Continue**. ![]() -3. Select the **Empty process** link near the top to create a build definition. +3. Select the **Empty job** link near the top to create a build definition. >**Note:** Normally you would just select one of the available templates that is closest to the type of solution you are deploying but for this lab we want to walk through a few extra steps to allow you to become more familiar with the process. +>> "Beware of knowledge you did not earn" -4. After clicking the **Empty process** link you'll need to fill out the build definition starting with the Process task and it's children. +4. After clicking the **Empty job** link you'll need to fill out the build definition starting with the Process task and it's children. >- Process
Name: **DevOpsHOL-CI**
Agent queue: **Hosted VS2017**
> **Note:** This will use a hosted (i.e. built in) build server. For more flexibility in the build (and for a more in depth learning experience), a private agent can be configured and used following the steps in the [Private Agent](../private-agent/README.md) lab. This is not necessary for this lab, but you can do this if you are up for the adventure. >- Process -> Get Sources
- From: This project
+ Azure Repos Git
+ Team Project: DevOpsHOL
Repository: DevOpsHOL -5. To the right of the **Phase 1** item click on the + sign and add the following tasks:
+5. To the right of the **Agent job 1** item click on the + sign and add the following tasks:
For each of the tasks, the settings for that task (if different than the default) are listed below
(*Hint: use the search box to filter in order to find the tasks in the list*) >- NuGet Tool Installer (Use NuGet 4.3.0) >- NuGet (NuGet restore) >- Visual Studio Build
- MSBuild Arguments: /p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactstagingdirectory)\\\\"
+ MSBuild Arguments: /p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactstagingdirectory)"
Platform: $(BuildPlatform)
Configuration: $(BuildConfiguration) >- .NET Core
Display name: Test Assemblies
Command: test
- Project(s): **/\*test\*.csproj
- Arguments: -l "trx"
- Continue on error: Checked
->- Publish Test Results
- Display name: Publish Test Result
- Test result format: VSTest
- Test results files: **\\*.trx
- Search folder: $(System.DefaultWorkingDirectory)
+ Path to project(s): **/\*test\*.csproj
+ Publish test results: Checked
>- Publish Build Artifacts
Path to publish: $(build.artifactstagingdirectory)
Artifact name: drop
+ Artifact publish location: Azure Pipelines/TFS
![]() @@ -99,7 +94,7 @@ We will now test the **Continuous Integration build (CI)** build we created by c ![]() -3. Build and test the solution locally. +3. Build and test the solution locally. NOTE: You may have to **Accept** the cookie policy to see the menu for the **About** page if you made the suggested change above. ![]() @@ -119,4 +114,13 @@ Integration build that runs when new commits are pushed to the master branch. This allows you to get feedback as to whether your changes made breaking syntax changes, or if they broke one or more automated tests, or if your changes are a okay. -Next do the [Continuous Deployment](../continuous-deployment/README.md) lab \ No newline at end of file +Next do the [Continuous Deployment](../continuous-deployment/README.md) lab + +## Shortcut ## +1. Download the [DevOpsHOL-CI.json](../source/build/DevOpsHOL-CI.json) file locally +2. Go to your project home page and select the **Build and Release** menu item. +3. Click the **+Import** button and select the file downloaded in step 1 and click Import. +4. After the import choose the Hosted VS2017 agent and click on the "Get Sources" process step and verify that it is correctly configured as described above. +5. Choose "Save & queue" to verify that the import and build were successful. + +**NOTE: Due to constant changes in Azure DevOps, the import file may not successfully import all the required settings. You'll still need to go through each setting and verify that it is valid. This may not really be much of a shortcut after all but it does illustrate that you can export and import build and release configurations to some extent.** diff --git a/continuous-integration/media/CI1.png b/continuous-integration/media/CI1.png index e96bc09..d8ae2b9 100644 Binary files a/continuous-integration/media/CI1.png and b/continuous-integration/media/CI1.png differ diff --git a/continuous-integration/media/CI2.png b/continuous-integration/media/CI2.png index b4d0d18..01d2795 100644 Binary files a/continuous-integration/media/CI2.png and b/continuous-integration/media/CI2.png differ diff --git a/continuous-integration/media/CI3.png b/continuous-integration/media/CI3.png deleted file mode 100644 index 7f31ddd..0000000 Binary files a/continuous-integration/media/CI3.png and /dev/null differ diff --git a/continuous-integration/media/CI4.png b/continuous-integration/media/CI4.png index 9588adf..25dcc4e 100644 Binary files a/continuous-integration/media/CI4.png and b/continuous-integration/media/CI4.png differ diff --git a/continuous-integration/media/CI5.png b/continuous-integration/media/CI5.png deleted file mode 100644 index e1f9c84..0000000 Binary files a/continuous-integration/media/CI5.png and /dev/null differ diff --git a/continuous-integration/media/CI6.png b/continuous-integration/media/CI6.png deleted file mode 100644 index 8be2eb3..0000000 Binary files a/continuous-integration/media/CI6.png and /dev/null differ diff --git a/continuous-integration/media/CI7.png b/continuous-integration/media/CI7.png deleted file mode 100644 index 03752dd..0000000 Binary files a/continuous-integration/media/CI7.png and /dev/null differ diff --git a/continuous-integration/media/CI8.png b/continuous-integration/media/CI8.png index 3ae5363..a7450d1 100644 Binary files a/continuous-integration/media/CI8.png and b/continuous-integration/media/CI8.png differ diff --git a/demos/README.md b/demos/README.md new file mode 100644 index 0000000..49146c8 --- /dev/null +++ b/demos/README.md @@ -0,0 +1,23 @@ +# Avanade DevOps HOL - Demos +This is an overview of all the demos and how to run them. + +## Dev Test Labs +1. Go to [Azure-rm](../azure-rm) and run the ProvisionEnvironment.ps1 script. +1. Review the result in your Azure Portal + +## Azure Automation DSC +1. Show runbooks with gallery, custom scripts +1. Show how to use Azure automation: + - Create an Azure automation account in Azure + - Import the DSC configuration and compile it + - Create a Windows **SERVER** Virtual Machine + - Register the VM as DSC node to the new DSC configuration + +## Multi-stage deployments +1. Run the steps from [this lab](../multi-stage-deployments/README.md) +1. Walk through the steps with the class + +## SonarQube +1. Run the steps from [this lab](../sonarqube/README.md) +1. Walk through the steps with the class +1. Show the SonarQube dashboard diff --git a/demos/azure-automation-dsc/HelloWorld.ps1 b/demos/azure-automation-dsc/HelloWorld.ps1 new file mode 100644 index 0000000..3c561be --- /dev/null +++ b/demos/azure-automation-dsc/HelloWorld.ps1 @@ -0,0 +1,16 @@ +Configuration HelloWorld { + + # Import the module that contains the File resource. + Import-DscResource -ModuleName PsDesiredStateConfiguration + + # The Node statement specifies which targets to compile MOF files for, when this configuration is executed. + Node 'localhost' { + + # The File resource can ensure the state of files, or copy them from a source to a destination with persistent updates. + File HelloWorld { + DestinationPath = "C:\Temp\HelloWorld.txt" + Ensure = "Present" + Contents = "Hello World from DSC!" + } + } +} \ No newline at end of file diff --git a/demos/azure-automation-dsc/webserver.ps1 b/demos/azure-automation-dsc/webserver.ps1 new file mode 100644 index 0000000..cde290e --- /dev/null +++ b/demos/azure-automation-dsc/webserver.ps1 @@ -0,0 +1,21 @@ +configuration WebServer +{ + Node IsWebServer + { + WindowsFeature IIS + { + Ensure = 'Present' + Name = 'Web-Server' + IncludeAllSubFeature = $true + } + } + + Node NotWebServer + { + WindowsFeature IIS + { + Ensure = 'Absent' + Name = 'Web-Server' + } + } +} \ No newline at end of file diff --git a/demos/azure-rm/ProvisionEnvironment.ps1 b/demos/azure-rm/ProvisionEnvironment.ps1 new file mode 100644 index 0000000..5a7c2d3 Binary files /dev/null and b/demos/azure-rm/ProvisionEnvironment.ps1 differ diff --git a/demos/azure-rm/json/devtestlab.json b/demos/azure-rm/json/devtestlab.json new file mode 100644 index 0000000..3356097 --- /dev/null +++ b/demos/azure-rm/json/devtestlab.json @@ -0,0 +1,239 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "newLabName": { + "type": "string", + "defaultValue": "FabrikamDemoLab", + "metadata": { + "description": "The name of the new lab instance to be created." + } + }, + "timeZoneId": { + "type": "string", + "defaultValue": "UTC", + "metadata": { + "description": "The timezone of the lab." + } + }, + "labVmShutDownTime": { + "type": "string", + "minLength": 5, + "maxLength": 5, + "defaultValue": "21:00", + "metadata": { + "description": "Set 'Auto Shutdown' policy: The UTC time at which the Lab VMs will be automatically shutdown (E.g. 17:30, 20:00, 09:00)." + } + }, + "maxAllowedVmsPerUser": { + "type": "int", + "minValue": 0, + "defaultValue": 10, + "metadata": { + "description": "Set 'max VM allowed per user' policy: The maximum number of VMs allowed per user." + } + }, + "maxAllowedVmsPerLab": { + "type": "int", + "minValue": 0, + "defaultValue": 100, + "metadata": { + "description": "Set 'Total VMs allowed in Lab' policy: The maximum number of VMs allowed per lab." + } + }, + "vmSize": { + "type": "string", + "defaultValue": "Standard_D1", + "metadata": { + "description": "The size of all the VMs to be created in the lab." + } + }, + "vmStorageType": { + "type": "string", + "defaultValue": "Premium", + "metadata": { + "description": "The storage type ('Premium' or 'Standard') of all the VMs to be created in the lab." + } + }, + "vmUsername": { + "type": "string", + "defaultValue": "demolab", + "metadata": { + "description": "The username for the local account that will be created on all the new VMs." + } + }, + "vmPassword": { + "type": "securestring", + "defaultValue": "<3DevTestLabs!", + "metadata": { + "description": "The password for the local account that will be created on all the new VMs." + } + }, + "offer": { + "type": "string", + "defaultValue": "visualstudio2019" + }, + "publisher": { + "type": "string", + "defaultValue": "microsoftvisualstudio" + }, + "sku": { + "type": "string", + "defaultValue": "vs-2019-comm-win10-n" + }, + "osType": { + "type": "string", + "defaultValue": "Windows" + }, + "version": { + "type": "string", + "defaultValue": "latest" + }, + "chocolatey_packages": { + "type": "string", + "defaultValue": "git,vscode,googlechrome", + "metadata": { + "description": "The packages that will be installed by chocolatey on the new vm." + } + }, + "chocolatey_allowEmptyChecksums": { + "type": "bool", + "defaultValue": true + }, + "chocolatey_ignoreChecksums": { + "type": "bool", + "defaultValue": true + } + }, + "variables": { + "newLabId": "[resourceId('Microsoft.DevTestLab/labs', parameters('newLabName'))]", + "labVirtualNetworkName": "[concat('Dtl', parameters('newLabName'))]", + "labSubnetName": "[concat(variables('labVirtualNetworkName'), 'Subnet')]", + "labVirtualNetworkId": "[concat('/virtualnetworks/', variables('labVirtualNetworkName'))]", + "vmFormulaName": "[concat(parameters('newLabName'), '/Developer')]", + "vmFormulaDescription": "[concat('Formula for lab VMs in ', parameters('newLabName'))]" + }, + "resources": [ + { + "apiVersion": "2017-04-26-preview", + "type": "Microsoft.DevTestLab/labs", + "name": "[parameters('newLabName')]", + "location": "[resourceGroup().location]", + "resources": [ + { + "apiVersion": "2017-04-26-preview", + "name": "[variables('labVirtualNetworkName')]", + "type": "virtualnetworks", + "dependsOn": [ + "[variables('newLabId')]" + ] + }, + { + "apiVersion": "2017-04-26-preview", + "name": "LabVmsShutdown", + "type": "schedules", + "dependsOn": [ + "[variables('newLabId')]" + ], + "properties": { + "status": "Enabled", + "timeZoneId": "[parameters('timeZoneId')]", + "taskType": "LabVmsShutdownTask", + "dailyRecurrence": { + "time": "[replace(parameters('labVmShutDownTime'),':','')]" + } + } + }, + { + "apiVersion": "2017-04-26-preview", + "name": "default/MaxVmsAllowedPerUser", + "type": "policysets/policies", + "dependsOn": [ + "[variables('newLabId')]" + ], + "properties": { + "description": "", + "factName": "UserOwnedLabVmCount", + "threshold": "[string(parameters('maxAllowedVmsPerUser'))]", + "evaluatorType": "MaxValuePolicy", + "status": "enabled" + } + }, + { + "apiVersion": "2017-04-26-preview", + "name": "default/MaxVmsAllowedPerLab", + "type": "policysets/policies", + "dependsOn": [ + "[variables('newLabId')]" + ], + "properties": { + "description": "", + "factName": "LabVmCount", + "threshold": "[string(parameters('maxAllowedVmsPerLab'))]", + "evaluatorType": "MaxValuePolicy", + "status": "enabled" + } + } + ] + }, + { + "apiVersion": "2017-04-26-preview", + "type": "Microsoft.DevTestLab/labs/formulas", + "name": "[variables('vmFormulaName')]", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[variables('newLabId')]" + ], + "properties": { + "description": "[variables('vmFormulaDescription')]", + "osType": "[parameters('osType')]", + "formulaContent": { + "properties": { + "size": "[parameters('vmSize')]", + "userName": "[parameters('vmUsername')]", + "password": "[parameters('vmPassword')]", + "labSubnetName": "[variables('labSubnetName')]", + "labVirtualNetworkId": "[variables('labVirtualNetworkId')]", + "allowClaim": true, + "storageType": "[parameters('vmStorageType')]", + "galleryimagereference": { + "Sku": "[parameters('sku')]", + "OsType": "[parameters('osType')]", + "Publisher": "[parameters('publisher')]", + "Offer": "[parameters('offer')]", + "Version": "[parameters('version')]" + }, + "networkInterface": { + "virtualNetworkId": "[variables('labVirtualNetworkId')]" + }, + "artifacts": [ + { + "artifactid": "/artifactsources/public repo/artifacts/windows-chocolatey", + "parameters": [ + { + "name": "packages", + "value": "[parameters('chocolatey_packages')]" + }, + { + "name": "allowEmptyChecksums", + "value": "[parameters('chocolatey_allowEmptyChecksums')]" + }, + { + "name": "ignoreChecksums", + "value": "[parameters('chocolatey_ignoreChecksums')]" + } + ] + } + ] + } + } + } + } + ], + "outputs": { + "labId": { + "type": "string", + "value": "[resourceId('Microsoft.DevTestLab/labs', parameters('newLabName'))]" + } + } +} \ No newline at end of file diff --git a/demos/azure-rm/json/devtestlab.parameters.json b/demos/azure-rm/json/devtestlab.parameters.json new file mode 100644 index 0000000..6c33069 --- /dev/null +++ b/demos/azure-rm/json/devtestlab.parameters.json @@ -0,0 +1,51 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "newLabName": { + "value": "devopslab" + }, + "timeZoneId": { + "value": "W. Europe Standard Time" + }, + "labVmShutDownTime": { + "value": "21:00" + }, + "maxAllowedVmsPerUser": { + "value": 3 + }, + "maxAllowedVmsPerLab": { + "value": 100 + }, + "vmSize": { + "value": "Standard_B8ms" + }, + "vmStorageType": { + "value": "Standard" + }, + "vmUsername": { + "value": "devopshol" + }, + "vmPassword": { + "value": "ADP#2019" + }, + "offer": { + "value": "visualstudio2019" + }, + "publisher": { + "value": "microsoftvisualstudio" + }, + "sku": { + "value": "vs-2019-comm-win10-n" + }, + "osType": { + "value": "Windows" + }, + "version": { + "value": "latest" + }, + "chocolatey_packages": { + "value": "git,vscode,googlechrome" + } + } +} \ No newline at end of file diff --git a/demos/azure-rm/json/vm.json b/demos/azure-rm/json/vm.json new file mode 100644 index 0000000..be3713e --- /dev/null +++ b/demos/azure-rm/json/vm.json @@ -0,0 +1,138 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "newVMName": { + "type": "string", + "metadata": { + "description": "The name of the new vm to be created." + } + }, + "existingLabName": { + "type": "string", + "metadata": { + "description": "The name of an existing lab where the new vm will be created." + } + }, + "offer": { + "type": "string", + "metadata": { + "description": "Offer of the gallery image" + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Publisher of the gallery image" + } + }, + "sku": { + "type": "string", + "metadata": { + "description": "Sku of the gallery image" + } + }, + "osType": { + "type": "string", + "metadata": { + "description": "OsType of the gallery image" + } + }, + "version": { + "type": "string", + "defaultValue": "latest", + "metadata": { + "description": "Version of the gallery image" + } + }, + "newVMSize": { + "type": "string", + "metadata": { + "description": "The size of the new vm to be created." + } + }, + "userName": { + "type": "string", + "metadata": { + "description": "The username for the local account that will be created on the new vm." + } + }, + "password": { + "type": "securestring", + "metadata": { + "description": "The password for the local account that will be created on the new vm." + } + }, + "chocolatey_packages": { + "type": "string", + "metadata": { + "description": "The packages that will be installed by chocolatey on the new vm." + } + }, + "chocolatey_allowEmptyChecksums": { + "type": "bool", + "defaultValue": true + }, + "chocolatey_ignoreChecksums": { + "type": "bool", + "defaultValue": true + } + }, + "variables": { + "labSubnetName": "[concat(variables('labVirtualNetworkName'), 'Subnet')]", + "labVirtualNetworkId": "[resourceId('Microsoft.DevTestLab/labs/virtualnetworks', parameters('existingLabName'), variables('labVirtualNetworkName'))]", + "labVirtualNetworkName": "[concat('Dtl', parameters('existingLabName'))]", + "resourceName": "[concat(parameters('existingLabName'), '/', parameters('newVMName'))]", + "resourceType": "Microsoft.DevTestLab/labs/virtualMachines" + }, + "resources": [ + { + "apiVersion": "2018-10-15-preview", + "type": "Microsoft.DevTestLab/labs/virtualMachines", + "name": "[variables('resourceName')]", + "location": "[resourceGroup().location]", + "properties": { + "size": "[parameters('newVMSize')]", + "isAuthenticationWithSshKey": false, + "userName": "[parameters('userName')]", + "sshKey": "", + "password": "[parameters('password')]", + "labVirtualNetworkId": "[variables('labVirtualNetworkId')]", + "labSubnetName": "[variables('labSubnetName')]", + "artifacts": [ + { + "artifactId": "[resourceId('Microsoft.DevTestLab/labs/artifactSources/artifacts', parameters('existingLabName'), 'public repo', 'windows-chocolatey')]", + "parameters": [ + { + "name": "packages", + "value": "[parameters('chocolatey_packages')]" + }, + { + "name": "allowEmptyChecksums", + "value": "[parameters('chocolatey_allowEmptyChecksums')]" + }, + { + "name": "ignoreChecksums", + "value": "[parameters('chocolatey_ignoreChecksums')]" + } + ] + } + ], + "notes": "Visual Studio 2019 Community on Windows 10 Enterprise N (x64)", + "galleryImageReference": { + "Sku": "[parameters('sku')]", + "OsType": "[parameters('osType')]", + "Publisher": "[parameters('publisher')]", + "Offer": "[parameters('offer')]", + "Version": "[parameters('version')]" + } + } + } + ], + "outputs": { + "vmId": { + "type": "string", + "value": "[resourceId(variables('resourceType'), parameters('existingLabName'), parameters('newVMName'))]" + } + } +} \ No newline at end of file diff --git a/demos/azure-rm/json/vm.parameters.json b/demos/azure-rm/json/vm.parameters.json new file mode 100644 index 0000000..040460f --- /dev/null +++ b/demos/azure-rm/json/vm.parameters.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "newVMName": { + "value": "devopsvm" + }, + "existingLabName": { + "value": "devopslab" + }, + "newVMSize": { + "value": "Standard_B8ms" + }, + "userName": { + "value": "devopshol" + }, + "password": { + "value": "ADP#2019" + }, + "chocolatey_packages": { + "value": "git,vscode,googlechrome" + }, + "offer": { + "value": "visualstudio2019" + }, + "publisher": { + "value": "microsoftvisualstudio" + }, + "sku": { + "value": "vs-2019-comm-win10-n" + }, + "osType": { + "value": "Windows" + }, + "version": { + "value": "latest" + } + } +} \ No newline at end of file diff --git a/extras/notes b/extras/notes deleted file mode 100644 index 5ae4b6d..0000000 --- a/extras/notes +++ /dev/null @@ -1,51 +0,0 @@ -To add Application Settings to a web app in an ARM template add - -To add the ability to change the "Traffic Routing" (used to be called "Testing in Production") percentage of requests, add the following to the Microsoft.Web/sites slotselement - - "properties":{ - "serverFarmId":"[parameters('hostingPlanName')]", - "siteConfig": { - "appSettings": [ - { "name": "Foo", "value": "abc" }, - { "name": "Bar", "value": "122" } - ] - }, - }, - "resources":[ - { - "apiVersion": "2015-04-01", - "name": "slotconfignames", - "type": "config", - "dependsOn": [ - "[resourceId('Microsoft.Web/Sites', parameters('siteName'))]" - ], - "properties": { - "connectionStringNames": [ "ConnString1" ], - "appSettingNames": [ "Foo" ] - } - } - ] - -More information on this can be found at [A/B Testing and Testing In Production with Azure Web Apps](https://www.hanselman.com/blog/ABTestingAndTestingInProductionWithAzureWebApps.aspx) - "resources": [ - { - "apiVersion":"2015-04-01", - "name":"web", - "type":"config", - "dependsOn":[ - "[resourceId('Microsoft.Web/Sites/Slots', parameters('siteName'), 'Staging')]" - ], - "properties":{ - "experiments": { - "rampUpRules": [ - { - "actionHostName": "deploydemo778.azurewebsites.net", - "reroutePercentage": 33, - "name": "production" - } - ] - }, - "autoSwapSlotName": "production" - } - } - ] diff --git a/feature-branching/README.md b/feature-branching/README.md new file mode 100644 index 0000000..b49cfc1 --- /dev/null +++ b/feature-branching/README.md @@ -0,0 +1,69 @@ +# Feature branching and branch protection + +This lab contains instructions to ensure code quality using pull requests and branch protection.\ +The instructions are based on the following documentation: + +- [Review code with pull requests](https://docs.microsoft.com/en-us/azure/devops/repos/git/pull-requests) +- [Improve code quality with branch policies](https://docs.microsoft.com/azure/devops/repos/git/branch-policies) + +## Prerequisites + +- Complete lab: [Continuous Integration with Azure DevOps](../azure-devops-project/README.md) +- Complete lab: [Multi-stage deployments with Azure DevOps](../multi-stage-deployments/README.md) + +## Enable branch protection + +1. Protect the **master** branch of your repository by enabling a **branch policy** using:\ + [Improve code quality with branch policies](https://docs.microsoft.com/azure/devops/repos/git/branch-policies) + Ensure the following policy settings are enabled: + - Require a minimum number of 1 reviewers (Allow users to approve their own changes) + +## Configure pull request build + +1. Create a copy of the **CI Build**, and name it **PR Build** + +1. In the **PR Build**, remove all tasks that are publishing artifacts.\ + Ensure the pull request build only contains build and test tasks. + +1. Edit the **master** branch policy, enable **Build validation** and set it to the **PR Build** + +## Create a feature branch and make a pull request + +1. In your Azure DevOps project, under Repos - Branches, create a new feature branch: + On the **devopshol** repository, create a feature branch named 'feature/slogan' Protect the **master** branch of your repository by enabling a branch policy using:\ + [Review code with pull requests](https://docs.microsoft.com/en-us/azure/devops/repos/git/pull-requests) + +1. Switch to the feature branch in Visual Studio + +1. In the **Website** project, add a slogan to the home page: +
Views\Home\Index.cshtml (expand to view code) + + ```csharp + ... +
+

DevOps HOL application slogan

+ +
+ ... + ``` +
+ +1. Commit the changes to the feature branch, and **push** or **sync** the changes to Azure DevOps + +1. Create a pull request, from the feature branch to master.\ + Inspect the pull request overview page + Approve the pull request, and notice the CI Build is triggered after approval. + +## Stretch goals + +1. Move the shared tasks of the **CI** (Continous Integration) build and the **PR** (Pull Request) to a **Task Group**.\ + Ensure the following setup: + - **PR Build:** + - 'Build application' task group, containing: Build + Test tasks + - **CI Build:** + - 'Build application' task group, containing: Build + Test tasks + - Publish artifact tasks +1. Prefix the Build number with 'feature' + +## Next steps +Return to the [Lab index](../README.md) and continue with the next lab diff --git a/feature-flag/README.md b/feature-flag/README.md index 9e02a07..bbb023d 100644 --- a/feature-flag/README.md +++ b/feature-flag/README.md @@ -1,340 +1,207 @@ -Avanade DevOps HOL - Feature Flag for Web Application -==================================================================================== -Feature flags provide the ability to turn features of your application on and off at a moments notice, no code deployments required. In this lab, we will add code to the project to demonstrate the use of feature flags to allow selectively turning on and off features to expose functionality. +# Feature Toggles -## Pre-requisites: ## -- Complete [Getting Started](../getting-started/README.md) hands on lab. +This lab contains instructions to use feature toggles in an application. +Feature toggles, or flags, provide the ability to selectively turn features on and off to expose functionality. +The instructions are based on the following documentation: -## Tasks Overview ## -**1. Add the backend code required for feature flags** - in this task we will add in the required code to enable feature flags for our application. +- [Simple, Reliable Feature Toggles in .NET](http://jason-roberts.github.io/FeatureToggle.Docs/) -**2. Create a new page to enable/disable features** - in this task we will add a page for users to opt in for specific features. +## Prerequisites -**3. Update the website to use the feature flags** - in this task we will add new features to the website and leverage the feature flags +- Complete lab: [Pipeline as code with K8s and Terraform](https://dev.azure.com/thx1139/_git/workshop1?path=%2FREADME.md) -**4. Try it out!** - in this task we will try out our feature flag to ensure it's working as expected. +## Add a feature toggle to the application -## I: Add the backend code required for feature flags ### +1. In the **mywebapp** project, add the feature toggle NuGet package: + - FeatureToggle (by Jason Roberts) -**1.** Open the DevOpsHOL solution created previously from [Getting Started](../getting-started/README.md) hands on lab. +1. In the **mywebapp** project, add a feature toggle: + - Create a feature toggle class in a 'Feature' folder +
Feature\ShowDate.cs (expand to view code) -**2.** Create a folder for feature flag related classes. Right click **DevOpsHOL project** -> **Add** -> **New Folder**, named **'FeatureFlag'**. + ```csharp + using FeatureToggle; -![]() - -**3.** Create a feature flag class, this will represent a feature flag in the application. Right click on the newly created **FeatureFlag** folder -> **'Add'** -> **'Class...'**, name the new class **'Feature.cs'** - -![]() - -Add the following properties for the Feature.cs class - -```csharp -public class Feature -{ - public string Key { get; set; } - public string Description { get; set; } - public bool Active { get; set; } -} -``` -- **Key** is the unique identifier for that particular feature flag. -- **Description** is a brief description of what the feature flag is for. -- **Active** is the current state for the feature flag. - -**4** Create a simple feature manager class. This will be used later on to toggle features on and off. Under the same **FeatureFlag** folder create a new class called **FeatureManager.cs** + namespace mywebapp.Feature + { + public class ShowDate : SimpleFeatureToggle { } + } + ``` +
-```csharp -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; + - Add the feature toggle configuration to the appsettings +
appsettings.json (expand to view code) -namespace DevOpsHOL.FeatureFlag -{ - public class FeatureFlag - { - public const string ContactMap = "Contact-Map"; - public const string FeatureX = "FeatureX"; - } - public class FeatureManager - { - public static IEnumerable Features = new List + ```json { - new Feature - { - Description = "Hide/Show Map on Contact Page", - Key = FeatureFlag.ContactMap, - Active = true - }, - new Feature - { - Description = "Hide/Show Feature X", - Key = FeatureFlag.FeatureX, - Active = false + "FeatureToggle": { + "ShowDate": false } - }; - - public static bool GetStatusByKey(string key) - { - if (string.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key)); - - Feature feature = Features.First(f => f.Key == key); - - if (feature == null) - throw new MissingFieldException(nameof(key)); - - return feature.Active; + ... } + ``` +
- public static async Task ChangeFeatureToggles(string[] selectedItems) - { - if (selectedItems == null) - throw new ArgumentNullException(nameof(selectedItems)); + - Initialize the feature toggle setting from the configuration during startup + +
Startup.cs (expand to view code) - foreach (Feature feature in Features) + ```csharp + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) { - feature.Active = selectedItems.Contains(feature.Key); - } - } - - } -} -``` -- **Features** is a list of features used with the application. In this case, to Hide/Show Map on Contact Page -- **GetStatusByKey** is a method used to get the current status for a given user for a particular feature key. -- **ChangeFeatureToggles** takes in a list of selected feature toggles. If the key presents that status will be turned on. - -*Note: In a real world implementation, these flags will be stored in a database. For simplicity's sake these are stored in memory. This also gives the ability to extenalize the on/off switch for features* - -## II. Create a new page to enable/disable features ## -Now we need to create an administration view to toggle these features on and off from the site. - -**1.** In order to create a new page on the site, we need to create a ViewModel that will represent the data used on that page. - -Under the **Models** folder, create a new class called **FeaturesViewModel.cs** - -![]() - -```csharp -using DevOpsHOL.FeatureFlag; -using System.Collections.Generic; - -namespace DevOpsHOL.Models -{ - public class FeaturesViewModel - { - public IEnumerable AvailableFeatures { get; set; } - } -} -``` -- **AvailableFeatures** is the list of features used with the application. - -**2.** Right click **Controllers** folder -> click **Add** -> click **Controller...** -> select **MVC Controller - Empty** -> enter Controller Name: **FeaturesController** then click **Add** -```csharp -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using DevOpsHOL.Models; -using DevOpsHOL.FeatureFlag; - -namespace DevOpsHOL.Controllers -{ - public class FeaturesController : Controller - { - public IActionResult Index() - { - return View(new FeaturesViewModel { AvailableFeatures = FeatureManager.Features }); - } - - [ValidateAntiForgeryToken] - [HttpPost] - public async Task SaveFeatureToggles(string[] selectedItems) - { - await FeatureManager.ChangeFeatureToggles(selectedItems); + // Set provider config so file is read from content root path + var provider = new AppSettingsProvider { Configuration = (IConfigurationRoot)Configuration }; - return RedirectToAction("Features", "Home"); - } - } -} -``` + // Add your feature here + services.AddSingleton(new ShowDate { ToggleValueProvider = provider }); + ... + } + ``` +
-**3:** Under **'Views'** folder, create a new folder called **'Features'** + - Add environment variables to the program configuration -![]() +
Program.cs (expand to view code) -**4:** Create a new View under **'Features'** , named **'Index'**, specify Template, Model class, and layout page. + ```csharp + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Hosting; -![]() + namespace mywebapp + { + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + webBuilder.UseUrls("http://*:5000"); + webBuilder.ConfigureAppConfiguration((hostingContext, config) => + { + config.AddEnvironmentVariables(prefix: "ShowDate_"); + }); + }); + } + } + ``` +
-**5.** Update the newly created Views->Features->Index.cshtml file with the following content. +1. In the **mywebapp** project, implement the feature toggle on the Index page: + - Add the FeatureToggle as property to the IndexModel and assigned it with Dependency Injection in the OnGet method. -```csharp -@model DevOpsHOL.Models.FeaturesViewModel +
Pages\Index.cshtml.cs (expand to view code) -@{ - ViewData["Title"] = "Features"; - Layout = "~/Views/Shared/_Layout.cshtml"; -} + ```csharp + using FeatureToggle; + using Microsoft.AspNetCore.Mvc.RazorPages; + using Microsoft.Extensions.Logging; + using mywebapp.Feature; -

Features

+ namespace mywebapp.Pages + { + public class IndexModel : PageModel + { + private readonly ILogger _logger; + public IFeatureToggle ShowDate { get; set; } + + public IndexModel(ILogger logger) + { + _logger = logger; + } + + public void OnGet(ShowDate showDate) + { + ShowDate = showDate; + } + } + } + ``` +
-
+ - Add html to show the current DateTime in the form that will only display if the feature ShowDate is enabled. +
Pages\Index.cshtml (expand to view code) -@using (Html.BeginForm("SaveFeatureToggles", "Features", FormMethod.Post)) -{ - - - - - - - + ```csharp + @page + @model IndexModel + @{ + ViewData["Title"] = "Home page"; + } - @foreach (var item in Model.AvailableFeatures) - { - - - - - - } -
-

Description

-
-

Key

-
-

Active

-
- @item.Description - - @item.Key - +
+

Welcome Avanadi!

+

Learn about building Web apps with ASP.NET Core.

+ @if(Model.ShowDate.FeatureEnabled) + {
- @Html.AntiForgeryToken() - + @Html.Label("Current DateTime is: " + DateTime.Now.ToString())
-
- -
-} - -
- -@section Scripts { - @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} -} -``` - -**6.** Write unit tests to validate the newly added Features functionality. - -**7.** Build and test the solution and verify that it compiles without error.
- - -## III. Update the website to use the feature flags ## -Now that we have the basics of feature management. Let's add a new feature to display a map section on Contact page, and using the feature flag to control the display. - -**1:** Create a ViewModel that will represent the data used on the Contact page. - -Under the **'Models'** folder, create a new class called **'ContactViewModel.cs'** - -![]() - -```csharp -namespace DevOpsHOL.Models -{ - public class ContactViewModel - { - public bool IsMapFeatureActive { get; set; } - } -} -``` -- **IsMapFeatureActive** is the feature flag used to control display of the new feature. - -**2:** Open **HomeController.cs** under **'Controllers'** folder, add a using statement for **DevOpsHOL.FeatureFlag** and update the **Contact()** method to pass **ContactViewModel** to the view - -```csharp -using DevOpsHOL.FeatureFlag; - - - public IActionResult Contact() - { - ViewData["Message"] = "Your contact page."; - - return View(new ContactViewModel { IsMapFeatureActive = FeatureManager.GetStatusByKey(FeatureFlag.FeatureFlag.ContactMap) }); - } -``` - -**3:** Open **'Contact.cshtml'** under folder **'Views'** -> **'Home'**, add new features to the page with flag for toggling. - -```csharp -@model ContactViewModel -@{ - ViewData["Title"] = "Contact"; -} -

@ViewData["Title"]

-

@ViewData["Message"]

- -
- One Microsoft Way
- Redmond, WA 98052-6399
- P: - 425.555.0100 -
-@if (Model.IsMapFeatureActive) -{ -

- Map -

-
- - - - -} -
- Support: Support@example.com
- Marketing: Marketing@example.com -
- - -Toggle Features -``` -- It adds the newly created **ContactViewModel** to make the feature flag accessible in the view -- Uses the **Model.IsMapFeatureActive** to toggle the display of the map -- Adds a link to the Features page for demonstration purpose. In a real world implementation, this is usually part of the admin function. - - -### IV. Try it out! ### - -The scenario demonstrates a broken change introduced by the new map feature. This would often require code rollback in production. Thanks to the feature flag, we can simply go to the features page to turn off the feature, to minimize the impact to end user, and the turn it back once fixed. - -**1:** Now launch the site. You can do this but pressing F5 or hitting the button shown below in Visual Studio. - -![]() - -**2:** Navigate to Contact page, to observe newly added Map feature, and a error in display the map. - -![]() - -**3:** Navigate to Features page by click on the **Toggle Features** link on **Contact** page, and switch off the - -![]() - -Note: This would be part of admin function in real life. - -**4:** Navigate back to Contact page, and refresh the page to observe the map is no longer displayed on the page. - -![]() - - - - - + } + + ``` +
+ +1. Start the website locally and test the feature toggle on the Index page: + - With the toggle set to **false** in config, on the Index page: + * You won't see the current DateTime. + - With the toggle set to **true** in config, on the Index page: + * You will see the current DateTime. + +## Configure the feature toggle in the release pipeline + +1. Configure the feature toggle to be enabled on the **Test** environment: + - Edit the Azure DevOps **app** pipeline + - Go to Variables, and add the variables: + + |Name |Value|Scope| + |:---------------------|:----|:----| + |FeatureToggle.ShowDate|true |test | + |FeatureToggle.ShowDate|false|prod | + + - Add the environment variable to mywebapp.yaml +
deploy\myapp\templates\mywebapp.yaml (expand to view code) + + ``` + ... + - image: {{ .Values.containerregistryurl}}/mywebapp:{{ .Values.build }} + name: mywebapp + env: + - name: "FeatureToggle__ShowDate" + value: {{ index .Values "ftcpn" }} + ... + ``` +
+ + + - In the **Deploy Azure App Service** task, under *Application and Configuration Settings*,\ + ensure the following setting: + - *App settings:* ```-FEATURETOGGLE__SHOWDATE $(FeatureToggle.ShowDate)``` + +1. Edit the **app** pipeline + - Add two new variables: + 1. *ft.showdate.test* with the value *true* + 1. *ft.showdate.prod* with the value *false* + + - Include variable *ft.showdate.test* in the test deployment stage + - location: Release_Test/Deploy_containers/HelmDeploy@0 + - add a new override value: ftcpn=$(ft.showdate.test) + + - Include variable *ft.showdate.prod* in the prod deployment stage + - location: Release_Prod/Deploy_containers/HelmDeploy@0 + - add a new override value: ftcpn=$(ft.showdate.prod) + +1. Commit your code to trigger the **app** pipeline + +1. Approve the release to all environments, and inspect the results:\ +The **test** environment should have the feature enabled, and the **prod** environment not. + +## Next steps +Return to the [Lab index](../README.md) and continue with the next lab \ No newline at end of file diff --git a/feature-flag/media/add-contact-view-model.PNG b/feature-flag/media/add-contact-view-model.PNG deleted file mode 100644 index d53ff1e..0000000 Binary files a/feature-flag/media/add-contact-view-model.PNG and /dev/null differ diff --git a/feature-flag/media/add-feature-class.PNG b/feature-flag/media/add-feature-class.PNG deleted file mode 100644 index 7e93743..0000000 Binary files a/feature-flag/media/add-feature-class.PNG and /dev/null differ diff --git a/feature-flag/media/add-feature-controller.PNG b/feature-flag/media/add-feature-controller.PNG deleted file mode 100644 index 03cf1f8..0000000 Binary files a/feature-flag/media/add-feature-controller.PNG and /dev/null differ diff --git a/feature-flag/media/add-feature-manager-folder.PNG b/feature-flag/media/add-feature-manager-folder.PNG deleted file mode 100644 index 46bd06f..0000000 Binary files a/feature-flag/media/add-feature-manager-folder.PNG and /dev/null differ diff --git a/feature-flag/media/add-features-index-view.PNG b/feature-flag/media/add-features-index-view.PNG deleted file mode 100644 index 8e09080..0000000 Binary files a/feature-flag/media/add-features-index-view.PNG and /dev/null differ diff --git a/feature-flag/media/add-features-view-model-class.PNG b/feature-flag/media/add-features-view-model-class.PNG deleted file mode 100644 index a6bc6d4..0000000 Binary files a/feature-flag/media/add-features-view-model-class.PNG and /dev/null differ diff --git a/feature-flag/media/add-view-features-folder.PNG b/feature-flag/media/add-view-features-folder.PNG deleted file mode 100644 index 367f74d..0000000 Binary files a/feature-flag/media/add-view-features-folder.PNG and /dev/null differ diff --git a/feature-flag/media/contact-page-with-map.PNG b/feature-flag/media/contact-page-with-map.PNG deleted file mode 100644 index cd41419..0000000 Binary files a/feature-flag/media/contact-page-with-map.PNG and /dev/null differ diff --git a/feature-flag/media/contact-page-without-map.PNG b/feature-flag/media/contact-page-without-map.PNG deleted file mode 100644 index 3a1d5bc..0000000 Binary files a/feature-flag/media/contact-page-without-map.PNG and /dev/null differ diff --git a/feature-flag/media/launch-site.PNG b/feature-flag/media/launch-site.PNG deleted file mode 100644 index 1307a1d..0000000 Binary files a/feature-flag/media/launch-site.PNG and /dev/null differ diff --git a/feature-flag/media/switch-feature.png b/feature-flag/media/switch-feature.png deleted file mode 100644 index bf89b70..0000000 Binary files a/feature-flag/media/switch-feature.png and /dev/null differ diff --git a/getting-started/README.md b/getting-started/README.md index 66c5f21..287feaf 100644 --- a/getting-started/README.md +++ b/getting-started/README.md @@ -1,11 +1,11 @@ - # Avanade DevOps HOL - Getting Started -In this lab, we will be installing the required development components and verifying that the solution builds and is able to be pushed to VSTS. +In this lab, we will be installing the required development components and verifying that the solution builds and is able to be pushed to Azure DevOps. + ## Pre-requisites ## 1. An active Azure subscription
[Azure Portal](https://portal.azure.com) -2. An active Visual Studio Team Services account.
- [Sign up for Visual Studio Team Services](https://www.visualstudio.com/en-us/docs/setup-admin/team-services/sign-up-for-visual-studio-team-services) +2. An active Azure DevOps account.
+ [Sign up for Visual Studio Team Services](https://azure.microsoft.com/en-us/services/devops/) ## Set up your machine ## 1. Install [Visual Studio 2017](http://go.microsoft.com/fwlink/?LinkId=517106)
@@ -24,6 +24,7 @@ In this lab, we will be installing the required development components and verif + Create new Git repository: Checked
+ Click OK
+ On the next dialog, choose Web Application (Model-View-Controller) as the application type, No Authentication
+ + Configure for https: UnChecked (unless you are up for the challenge)
+ Click OK
4. Build and run the solution to make sure everything is OK to this point. @@ -32,30 +33,81 @@ In this lab, we will be installing the required development components and verif + Do a quick smoke test to verify that the solution built and runs correctly.
+ Close browser and stop debugging
-5. Choose File -> New -> Project... and add a Unit Test Project (.NET Core) project, to the solution *not the .NET Framework unit test project*. +5. Choose File -> New -> Project... and add a MSTest Test Project (.NET Core) project, to the solution *not the .NET Framework unit test project*. + Name: DevOpsHOL.Tests
+ Solution: Add to solution
-6. Rename **UnitTest.cs** to **HomeControllerTest.cs** and replace the file contents with the content of this file [HomeControllerTest.cs](../source/tests/HomeControllerTest.cs) - -7. Add the "DevOpsHOL" and "Microsoft.AspNetCore.Mvc.ViewFeatures.dll" as references to the DevOpsHOL.Tests project. Tip: Use quick refactoring. - -8. Build, run unit tests and run the solution to make sure everything is OK to this point. +6. Rename **UnitTest.cs** to **HomeControllerTest.cs** and replace the file contents with the content in the details section below. +      
Click here to expand the sample unit test code + + ```csharp + [TestClass] + public class HomeControllerTest + { + [TestMethod] + public void Index() + { + // Arrange + HomeController controller = new HomeController(); + + // Act + ViewResult result = controller.Index() as ViewResult; + + // Assert + Assert.IsNotNull(result); + } + + [TestMethod] + public void About() + { + // Arrange + HomeController controller = new HomeController(); + + // Act + ViewResult result = controller.About() as ViewResult; + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual("Your application description page.", result.ViewData["Message"]); + } + + [TestMethod] + public void Contact() + { + // Arrange + HomeController controller = new HomeController(); + + // Act + ViewResult result = controller.Contact() as ViewResult; + + // Assert + Assert.IsNotNull(result); + } + } + ``` +
+      NOTE: This source is also available here: [HomeControllerTest.cs](../source/tests/HomeControllerTest.cs) + +7. Add the "DevOpsHOL" as a reference to the DevOpsHOL.Tests project. Tip: Use quick refactoring. + +8. Add references to Microsoft.AspNetCore.Mvc.Abstractions and Microsoft.AspNetCore.Mvc.ViewFeatures as well. Use the NuGet package manager to add these libraries to the project. + +9. Build, run unit tests and run the solution to make sure everything is OK to this point. + Test -> Run -> All Tests (Ctrl+R,A)
+ Debug -> Start Debugging (F5)
+ Do another quick smoke test to verify that the solution built and runs correctly.
+ Close browser and stop debugging
-9. Add solution to VSTS project (Team Explorer -> Sync -> Publish Git Repo) +10. Add solution to VSTS project (Team Explorer -> Sync -> Publish Git Repo) + Push to Visual Studio Team Services
+ Repository name: DevOpsHOL
+ Publish repository will create a project in VSTS (NOTE: if you have multiple VSTS accounts, make sure this is published to the correct Team Services Domain).
![]() -9. Create the first commit for your project (Team Explorer -> Changes -> Commit All and Push) +11. Create the first commit for your project (Team Explorer -> Changes -> Commit All and Push). NOTE: This could be automatically staged so choose Commit Staged and Push. -10. Log in to VSTS with browser and verify that DevOpsHOL project was created and source code is uploaded. +12. Log in to VSTS with browser and verify that DevOpsHOL project was created and source code is uploaded. ## Next steps diff --git a/getting-started/media/GS1.png b/getting-started/media/GS1.png deleted file mode 100644 index 3c8d591..0000000 Binary files a/getting-started/media/GS1.png and /dev/null differ diff --git a/images/ado-security-scanner-pipeline-yaml.png b/images/ado-security-scanner-pipeline-yaml.png new file mode 100644 index 0000000..c0b4dfd Binary files /dev/null and b/images/ado-security-scanner-pipeline-yaml.png differ diff --git a/images/lab-3-tutorial.png b/images/lab-3-tutorial.png new file mode 100644 index 0000000..5848d78 Binary files /dev/null and b/images/lab-3-tutorial.png differ diff --git a/images/personal-access-tokens-part2.png b/images/personal-access-tokens-part2.png new file mode 100644 index 0000000..f242fb0 Binary files /dev/null and b/images/personal-access-tokens-part2.png differ diff --git a/images/personal-access-tokens.png b/images/personal-access-tokens.png new file mode 100644 index 0000000..cf68434 Binary files /dev/null and b/images/personal-access-tokens.png differ diff --git a/images/pipelines-authorize.png b/images/pipelines-authorize.png new file mode 100644 index 0000000..b91783f Binary files /dev/null and b/images/pipelines-authorize.png differ diff --git a/images/pipelines-delete-arm-copy.PNG b/images/pipelines-delete-arm-copy.PNG new file mode 100644 index 0000000..4494892 Binary files /dev/null and b/images/pipelines-delete-arm-copy.PNG differ diff --git a/images/pipelines-delete.png b/images/pipelines-delete.png new file mode 100644 index 0000000..77680f4 Binary files /dev/null and b/images/pipelines-delete.png differ diff --git a/images/pipelines-disable-ci.png b/images/pipelines-disable-ci.png new file mode 100644 index 0000000..61a251f Binary files /dev/null and b/images/pipelines-disable-ci.png differ diff --git a/images/pipelines-terraform-install.PNG b/images/pipelines-terraform-install.PNG new file mode 100644 index 0000000..ad7d408 Binary files /dev/null and b/images/pipelines-terraform-install.PNG differ diff --git a/images/pipelines-unlink.png b/images/pipelines-unlink.png new file mode 100644 index 0000000..e2bb707 Binary files /dev/null and b/images/pipelines-unlink.png differ diff --git a/images/pipelines-view-yaml.png b/images/pipelines-view-yaml.png new file mode 100644 index 0000000..57c76d1 Binary files /dev/null and b/images/pipelines-view-yaml.png differ diff --git a/images/secure-devopskit-workspace-summary-azure.png b/images/secure-devopskit-workspace-summary-azure.png new file mode 100644 index 0000000..1d1b358 Binary files /dev/null and b/images/secure-devopskit-workspace-summary-azure.png differ diff --git a/images/terraform-powershell.png b/images/terraform-powershell.png new file mode 100644 index 0000000..70a5a4d Binary files /dev/null and b/images/terraform-powershell.png differ diff --git a/k8s-terraform-deployment/README.md b/k8s-terraform-deployment/README.md new file mode 100644 index 0000000..9e501f3 --- /dev/null +++ b/k8s-terraform-deployment/README.md @@ -0,0 +1,49 @@ +# Avanade DevOps HOL - Define your continuous deployment process with Kubernetes and Terraform +In this lab, we modify the automated generated CD pipeline so it will work with Kubernetes and Terraform. + +## Prerequisites +- Complete lab [Continuous Integration with Azure DevOps](../azure-devops-project/README.md). + +## Tasks + +### Replace ARM templates with Terraform +The automated generated CD pipeline uses ARM templates for setting up the environment. You should probably see some releases that are failed. This happened because the CD pipeline was automatically triggered after every successfull build. The published build artifacts doesn't contain any ARM templates, which are needed in the current version of the CD pipeline. We will remove the old ARM deployment step and replace it with Terraform deployment steps. + +1. Edit the current CD pipeline and open the stage dev. + +1. Remove `Azure Deployment: Create AKS cluster` this is the ARM deployment step. + +1. Add a new Copy Files step after step 1 `Azure CLI: Preserve Cluster Tags`. Make sure it configured as: + - Display name: `Copy Terraform templates` + - Source Folder: `$(System.DefaultWorkingDirectory)/Drop/drop/TerraformTemplates` + - Contents: `*.tf` + - Target Folder: `$(System.DefaultWorkingDirectory)/Terraform` + +1. After the just created copy step, create a new Terraform step with the following settings: + - Display name: `Terraform: init` + - Command: `init` + - Configuration directory: `$(System.DefaultWorkingDirectory)/Terraform` + - Azure subscription: `` + - Resource group: `` + - Storage account: `devopsholstorage` + - Container: `terraform-state` + - Key: ` Settings --> Access keys>` + +1. After the just created Terraform step add a new Terraform step with the following configuration: + - Display name: `Terraform: validate and apply` + - Command: `validate and apply` + - Configuration directory: `$(System.DefaultWorkingDirectory)/Terraform` + - Azure subscription: `` + +1. Now the CD pipeline is adjusted to use Terraform. Save your changes and create a new release. + +### Wat moet er nog bij +- Test application, reach out naar het + + +az aks get-credentials -n cl0020 -g rg0020 +az aks install-cli +kubectl get services --all-namespaces + + +- Stretch: Blue/Green - namespaces diff --git a/mepfielddemo/README.md b/mepfielddemo/README.md new file mode 100644 index 0000000..59922ce --- /dev/null +++ b/mepfielddemo/README.md @@ -0,0 +1,63 @@ +# Avanade DevOps HOL - Modern Engineering Platform Field Demo +These are the hands on labs associated with the Avanade DevOps Practitioners course. This lab installs the Avanade Modern Engineering Platform (MEP) Field Demo as the labs for the second day of the course. + +Once the installation is complete, examples of CI/CD, Telemetry, etc. can be demonstrated. + +The objective of this lab is to prepare the students to be able to demonstrate the documented scenarios and be able to discuss MEP/DevOps with clients and other practioners. + +# Avanade MEP Field Demo Installation +1. Go to the [Avanade InnerSource information page](https://innersource.avanade.com/Home/Asset/MEPFielddemo) and click on the green Download button to download the scripts for configuring the demo to your download location on your laptop. + +1. Extract the contents of the downloaded zip file + +1. Pull together the information you are going to need to configure the MEPFieldDemo + - Configure a password for the Avanade InnerSource source repository + - Click on the orange Access Source button on the above site. (log in with your Avanade credentials) + - Navigate to the Git code files in the Fielddemo repository (not $/MEPFielddemo). Click on the “clone” link.
+ NOTE: You may need to switch from the TFS repository to the Git repository by clicking on the dropdown in the upper + left corner of the repository page.
+ ![TFS-Git](TFS-Git.png) + - Click on the “Generate Git Credentials”. + - Fill out the password fields (this is a new password you make up for InnerSource Git, not your Avanade password) + - Click “Save Git Credentials” (your done now, don’t actually clone the repository) + - Remember the password this is your for later. + - Create a PAT (private access token) + - Log into your VSTS account (your personal VSTS, not InnerSource VSTS) + - On the upper right hand part of the screen under the username/icon, select “Security” from the drop down menu + - Under personal access tokens, choose Add to create a new token. Name the token something like MEPFieldDemo. Choose a expiration time that makes sense. Make sure the All Scope radio button is selected. Copy the generated token and save away for use later as the . + - Run PowerShell as an admin + - Run the Get-Module command to make sure Azure and AzureRM modules are installed. If not use the commands below. + ```PowerShell + Install-Module Azure -scope CurrentUser + Install-Module AzureRM -scope CurrentUser + Import-Module Azure + Import-Module AzureRM + ``` +1. Navigate to the extracted folder + - cd C:\Users\\\Downloads\Configure-MEPVSTSDemo +1. Run the PowerShell script to start the deployment configuration + - .\Start-Deployment.ps1 + - Target VSTS account name: (this is just the first part of the VSTS account you’ve been using in the class; i.e. not the .visualstudio.com part) + - Target project name: This is the name of a new project that will be created in VSTS. Name it something like “MEPFieldDemo” or “customerdemo”. + - UserName: This is the username you use to connect to VSTS; either your Microsoft email or Avanade email, whichever you use for the VSTS you’ve been using in the class. + - VSTS Private Access token: This is the you created and saved earlier. + - Source repository id: This is your Avanade email + - Source repository dedicated password: This is the you created earlier + - Set up a Lite FieldDemo: Y will configure a light version of the demo, N will ask more questions. + - If you choose Y for Lite FieldDemo, you’ll be prompted for your Azure credentials. Type in credentials and skip to step viii below. + - If you choose N to Lit FieldDemo you’ll be prompted for a couple of additional options for additional features such as containers and mobile. In order to do mobile, you’ll need a mobile Private Access Token (left as an exercise for the reader 😊) + - The next prompt will ask for the target Azure subscription. Use the one you’ve been using for the class if you have more than one. + - Globally unique name: Pick a unique name for the demo website; something like billpayui\ (no spaces or special characters unless you want a challenge) + - Passphrase/key: A new password you make up. Remember this, you might need it later 😊. + - The deployment will start. If there are errors, look at the error output and see what might have gone wrong (there should be no errors, debugging the errors is not part of the exercise). If there are errors, it seems that just re-running the installation seems to allow the installation to complete the second or third time through (no idea yet why this is but it seems to work). First try to figure out the error looking at the output but if it isn't pretty obvious what the problem is, try running the configuration again. + - When the deployment script completes without error, log into VSTS account and look at the Build and watch the builds complete (investigate what these builds are doing – this is the DevOps stuff). When the builds complete, take a look at the Releases (investigate these) + - This build/release process can take 45 min to an hour. + - Once the releases are done, log into the Azure portal and investigate the resources that have been deployed. + +>**Note:** When you are done with the class, you will probably want to either turn off (or just delete) the resources created in Azure to keep your Azure subscription from running out of funds. You can re-run this process to re-install the environment when needed. + +# Practice the Scenarios +Navigate to VSTS account and review the new project that has been created. + - Navigate to the website that was created e.g. billpayui\.trafficmanager.net + +**Look at the documentation links at the bottom of the project readme and work through the provided scenarios.** diff --git a/mepfielddemo/TFS-Git.png b/mepfielddemo/TFS-Git.png new file mode 100644 index 0000000..b8de3f9 Binary files /dev/null and b/mepfielddemo/TFS-Git.png differ diff --git a/multi-stage-deployments/README.md b/multi-stage-deployments/README.md new file mode 100644 index 0000000..3cb4366 --- /dev/null +++ b/multi-stage-deployments/README.md @@ -0,0 +1,100 @@ +# Avanade DevOps HOL - Add Staging environment and define your multi-stage continuous deployment process with approvals and gates + +In this lab, we introduce a Staging environment and setup our multi-stage continuous deployment process by adding approvals and gates. + +Based on the following tutorials and VERY useful pages: +- [Set up staging environments in Azure App Service](https://docs.microsoft.com/en-us/azure/app-service/deploy-staging-slots) +- [Deployment Jobs](https://docs.microsoft.com/en-us/azure/devops/pipelines/process/deployment-jobs?view=azure-devops) +- [Approvals and checks](https://docs.microsoft.com/en-us/azure/devops/pipelines/process/approvals?view=azure-devops&tabs=check-pass) +- [Pipeline Variables](https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#pipeline-variables) + +## Prerequisites + +- Complete lab [Continuous Integration with Azure DevOps](../azure-devops-project/README.md). + +## Tasks + +### Add the Staging environment in the Azure App Service + +1. Read the guide on [setting up staging environments in Azure App Service](https://docs.microsoft.com/en-us/azure/app-service/deploy-staging-slots) + +1. Add a Staging Deployment Slot to your App Service in Azure + +### Add a Deployment stage to the pipeline + +1. Follow the [steps from this guide](https://www.azuredevopslabs.com/labs/azuredevops/yaml/#task-4-adding-continuous-delivery-to-the-yaml-definition) to convert your pipeline to use stages. Create your first stage `Build` and then continue with the next step below + +1. Add the following stage to the bottom of your pipeline: + ``` + - stage: DeployStaging + displayName: Deploy to Staging + jobs: + - deployment: DeployStaging + displayName: Deploy to Staging + environment: Staging + strategy: + runOnce: + deploy: + steps: + - task: AzureRmWebAppDeployment@4 + inputs: + ConnectionType: 'AzureRM' + azureSubscription: 'geuze-adp2020 - Azure' + appType: 'webAppLinux' + WebAppName: 'geuze-adp2020' + deployToSlotOrASE: true + ResourceGroupName: 'geuze-adp2020-rg' + SlotName: 'staging' + packageForLinux: '$(Pipeline.Workspace)/drop/*.zip' + ``` + +1. Make sure to configure the `AzureRmWebAppDeployment` task to use your own subscription, app name and resource group name + +1. Finally save and run the pipeline. If you encounter an authorization issue on your first next run, click the Authorize button and then try again: + ![](../images/pipelines-authorize.png) + +### Add a second Deployment stage for Production + +1. Add another stage to the bottom of your pipeline, but this time name it Production: + ``` + - stage: DeployProduction + displayName: Deploy to Production + dependsOn: DeployStaging + jobs: + - deployment: DeployProduction + displayName: Deploy to Production + environment: Production + strategy: + runOnce: + deploy: + steps: + - task: AzureRmWebAppDeployment@4 + inputs: + ConnectionType: 'AzureRM' + azureSubscription: 'geuze-adp2020 - Azure' + appType: 'webAppLinux' + WebAppName: 'geuze-adp2020' + deployToSlotOrASE: true + ResourceGroupName: 'geuze-adp2020-rg' + SlotName: 'production' + packageForLinux: '$(Pipeline.Workspace)/drop/*.zip' + ``` + +1. Make sure to configure the `AzureRmWebAppDeployment` task to use your own subscription, app name and resource group name for Production + +1. Save and run the pipeline again. Verify the pipeline runs through all the stages and ends with a successful Production deployment + +### Add the approval check to the Production Environment + +1. Follow the steps from [Approvals and checks](https://docs.microsoft.com/en-us/azure/devops/pipelines/process/approvals?view=azure-devops&tabs=check-pass) to add a check to the Production environment + +1. Once saved, run the pipeline again and watch the progress + +1. If Staging is deployed succesfully, make sure that the web app is running by visiting the website at \-staging.azurewebsites.net + +1. Go to the pipeline run and click the Review button. Now click Approve to satisfy the check. This will allow the pipeline to continue with the Production Stage + +1. Now check the Production web app at \.azurewebsites.net and verify that it is running + +## Next steps +Return to [the lab index](../README.md) and continue with the next lab. \ No newline at end of file diff --git a/private-agent/README.md b/private-agent/README.md index 3355c3c..a82080a 100644 --- a/private-agent/README.md +++ b/private-agent/README.md @@ -1,26 +1,26 @@ -Avanade DevOps HOL - Private Agent configuration with Visual Studio Team Services -==================================================================================== +# Avanade DevOps HOL - Private Agent configuration with Azure DevOps In this lab we configure a private agent for use in building the solution created in the [Continuous Integration](../continuous-integration/README.md) lab. -For more information on using private agents for VSTS builds, see the [Build and Release Agents](https://docs.microsoft.com/en-us/vsts/build-release/concepts/agents/agents) documentation. +For more information on using private agents for Azure DevOps builds, see the [Build and Release Agents](https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/agents) documentation. -### Pre-requisites: ### +### Pre-requisites: - Complete [Getting Started](../getting-started/README.md) task. -- An active Visual Studio Team Services account.
- [Sign up for Visual Studio Team Services](https://www.visualstudio.com/en-us/docs/setup-admin/team-services/sign-up-for-visual-studio-team-services) +- An active Azure DevOps account.
+ [Sign up for Azure DevOps](https://dev.azure.com/) +- In order to do the SonarQube lab, your Azure DevOps Agent Machine needs Java 8 installed, because the Azure DevOps SonarQube Extension has that requirement. ### I. Configure Private Build Agent When choosing the Agent Queue for the build process (such as from the [Continuous Integration](../continuous-integration/README.md) lab), click on the **Manage** link above the **Agent queue** dropdown. Otherwise, you can click on the Settings (gear) icon and choose **Agent Queues** to get to the Agent Queues page for the project.
->Note: Private build agents need to be accessible from VSTS so it is recommended to install them on a "build" VM within Azure. For the HOL, if you are using an Azure VM for the development environment, then this same VM can be used for running the private build agent. - 1. Open your VSTS portal (https://*\*.visualstudio.com) in another browser tab. +>Note: Private build agents need to be accessible from Azure DevOps so it is recommended to install them on a "build" VM within Azure. For the HOL, if you are using an Azure VM for the development environment, then this same VM can be used for running the private build agent. + 1. Open your Azure DevOps portal (https://dev.azure.com/youralias) in another browser tab. 2. Navigate to the DevOpsHOL project created earlier. - 3. Navigate to Personal access tokens (select your account picture, then Security, or navigate to https://youralias.visualstudio.com/_details/security/tokens) + 3. Navigate to Personal access tokens (select your account picture, then Security, or navigate to https://dev.azure.com/youralias/_details/security/tokens) 4. Create a new token with description **DevOpsTraining**, and copy the personal access token somewhere for use later. 5. Return to the previous Agent Queue browser tab. Click on **Download agent**. Once the Agent zip file is downloaded, follow the instructions on the agent download page to unzip and configure the agent on the target machine using the configuration options below. - >+ Enter server URL > https://*\*.visualstudio.com + >+ Enter server URL > https://dev.azure.com/youralias >+ Enter authentication type (press enter for PAT) > >+ Enter personal access token > *use the access token saved earlier* >+ Enter agent pool (press enter for default) > diff --git a/secure-devops-kit-adoscanner/README.md b/secure-devops-kit-adoscanner/README.md new file mode 100644 index 0000000..4bf65a3 --- /dev/null +++ b/secure-devops-kit-adoscanner/README.md @@ -0,0 +1,129 @@ +# Secure DevOps Kit for Azure (ADO Scanner) + +This instructions are based on the following documentation: +- [Running ADOScanner as pipeline extension](https://github.com/azsk/ADOScanner-docs/tree/master/05-Running%20ADOScanner%20as%20pipeline%20extension) + + +## Azure DevOps (ADO) security scanner + +The ADO security scanner helps you to keep your ADO environment configured securely. You can run the ADO security scanner in 3 modes: +1. Standalone in a PowerShell console. +1. ADO pipeline via a marketplace extension. +1. Azure-based containerized automated scanning solution. + +In this lab we will configure option 2: ADO pipeline via a marketplace extension. + +## Prerequisites +- Have an ADO environment with "Project Collection Administrator" or "Owner" permission. + +## Install ADO security scanner extension +1. Go to https://marketplace.visualstudio.com/items?itemName=azsdktm.ADOSecurityScanner +1. Press on `Get it free` +1. Select your ADO organisation +1. Press `Install` +1. Press `Proceed to organization`, this will take you back to your ADO environment. Which will now have the extension installed. + +## Create Personal Access Token +To be able to run the ADO security scanner, the pipeline needs a Service Connection with the right access rights. To setup the Service Connection we need a Personal Access Token (PAT) +1. Go to your Personal Access Tokens + + ![alt text](../images/personal-access-tokens.png "") + + ![alt text](../images/personal-access-tokens-part2.png "") + +1. In the Personal Access Tokens view, press `New Token`, this will open a new window where you specify the settings of the new PAT. + +1. Fill in the following details: + + - **Name:** ADO scanner Token + - **Organization:** Select your ADO organization. + - **Expiration (UTC):** custom defined, select an period that will cover this training at least. Or you can select a default value. + - **Scopes:** `Custom defined`, `Show more scopes` and then make sure the following things are enabled: + + | Scope | Privilege | + | :--------- | :------------------------| + | Agent Pools | Read | + | Auditing | Read Audit Log | + | Build | Read | + | Entitlements | Read | + | Extension Data | Read & write | + | Extensions | Read | + | Graph | Read | + | Identity | Read | + | Member Entitlement Management | Read | + | Project and Team | Read | + | Release | Read | + | Secure Files | Read | + | Service Connections | Read | + | Task Groups | Read | + | Tokens | Read & manage | + | User Profile | Read | + | Variable Groups | Read | + | Work Items | Read & write | + +1. Click on Create +1. Copy token value and store it some where save (for example notepad). You can't access this after you close the window. If you forgot it you need to recreate the token with all the settings. + +## Create Service Connection +1. Go to `Project settings` +1. Then below `Pipelines` click on `Service connections` +1. Click on `New service connection` +1. Choose `Azure Repos/Team Foundation Server` +1. Fill in the following details: + - **Authentication method:** Url of your ADO environment + - **Connection URL:** ADO Scanner + - **Personal Access Token:** Paste the PAT you just have created + - Press verify, this should be successfull + - **Service connection name:** ADO security scanner + - **Grant access permission to all pipelines:** This should be enabled +1. Click on `Verify and save` and the Service Connection is being created + +## Create ADO security scanner pipeline +1. Create a new build pipeline in ADO by going to `Pipelines` --> `Pipelines` and click on `New pipeline`. +1. Click on Azure Repos Git, select azdotraining1 +1. Select `Starter pipeline`, this will open a yaml file. +1. Change name of the yaml file to `ado-security-scanner-pipeline.yml` by pressing on `azure-pipelines.yml` +1. In the yaml file remove both the script steps. +1. Above trigger add the next piece of code (please make sure that identation is correct) + ```YAML + name: 'ADO Security Scanner' + ``` +1. Change the vmImage towards `windows-latest`. +1. Add the `ADOSecurityScanner@1` task with the following code (please make sure that identation is correct): + ```YAML + steps: + - task: ADOSecurityScanner@1 + displayName: 'ADO Security Scanner' + inputs: + ADOConnectionName: 'ADO security scanner' + ScanFilter: All + BuildNames: '*' + ReleaseNames: '*' + ServiceConnectionNames: '*' + AgentPoolNames: '*' + ``` +1. The end result should look like this: + ![alt text](../images/ado-security-scanner-pipeline-yaml.png "") + +1. Click on `Save and run`, and click on `Save and run` once again. The pipeline will be created and it will automatically start. + +1. Wait till the pipeline is succesfully finished. + +The pipeline will be created and you will get an authorization issue. Click on 'Resources authorized'. + +## Visualize security scan results +After the scan is completed, the results aren't visible yet. To visualize the results we need to create a project dashboard. + +The ADO security scanner extension contains two widgets which can be placed on an ADO dashboard: + +1. **Org Level Security Scan Summary:** Displays org level security control evaluation summary. This Dashboards helps org owners to take actions based on control failures. + +1. **Project Component Security Scan Summary:** Displays project components (Build/Release/Connections) security control evaluation summary. + +To implement these widgets follow the following steps: +1. Inside ADO go to `Overview`, `Dashboards` +1. On this page click on `Add a widget`, this will open a new view with a grid. +1. This grid represents your dashboard and on the right side you see all the widget you can add. +1. First add `ADO Security Org Security View` widget. +1. Then add `ADO Security Project Component` widget. +1. Click on `Done Editing` to save your dashboard. \ No newline at end of file diff --git a/secure-devops-kit-powershell/README.md b/secure-devops-kit-powershell/README.md new file mode 100644 index 0000000..8c53a62 --- /dev/null +++ b/secure-devops-kit-powershell/README.md @@ -0,0 +1,107 @@ +# Secure DevOps Kit for Azure with PowerShell + +This lab contains instructions to explore the possibilities of the Microsoft Secure DevOps kit. This kit contains several tools to scan and monitor security in Azure in the following categories: +- Subscription Security +- Secure Development +- Security in CICD +- Continuous Assurance +- Alerting & Monitoring + +This instructions are based on the following documentation +- [Getting started with the Secure DevOps Kit for Azure!](https://azsk.azurewebsites.net/00b-Getting-Started/Readme.html) + +## Prerequisites +- Complete lab: [Pipeline as code with K8s and Terraform](https://dev.azure.com/thx1139/_git/workshop1?path=%2FREADME.md) + +## Preparations +1. Install Azure CLI on your local machine + 1. Open PowerShell prompt + 1. Execute the command: `Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi; Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'` this will install the Azure CLI on your local machine + +1. Make sure you have the right PowerShell version + - The Secure DevOps Kit requires PowerShell version 5.0 or higher + - You can easily check the current installed PowerShell version by executing the following command in Powershell: `$PSVersionTable` + - If your PowerShell version is not 5.0 or higher, please make sure that your PowerShell will be updated to 5.0 or higher + +1. Install Secure DevOps Kit on your local machine + 1. Open PowerShell prompt and execute the command: `Install-Module AzSK -Scope CurrentUser` + 1. When the installation is finished you can easily check if the Secure DevOps Kit is correctly installed by executing the command: `azsk`. If the Secure DevOps Kit is correctly installed it will show all available commands. + + +## Run the Subscription Security Report (Subscription Security) +The Subscription Security Report will scan your Azure subscription on security issues. + +1. Open a PowerShell prompt +1. The PowerShell prompt must be connected to your Azure environment before you can run the report. To connect the prompt with Azure execute the command: `Add-AzureAccount`. +1. For running the Subscription Security Report you must known your subscription id. By executing the command: `Get-AzureSubscription` you can view your subscription details. +1. Now run the Subscription Security Report with the command: `Get-AzSKSubscriptionSecurityStatus -SubscriptionId ` +1. After the scan is completed a new file explorer will open with the scan results. Open the .csv file and examine the scan results. + +## Run Azure Services Security Report (Subscription Security / Secure Development) +The Azure Services Security Report will scan your Azure resources on security issues. + +1. Open a PowerShell prompt +1. The PowerShell prompt must be connected to your Azure environment before you can run the report. To connect the prompt with Azure execute the command: `Add-AzureAccount`. +1. For running the Azure Services Security Report you must known your subscription id. By executing the command: `Get-AzureSubscription` you can view your subscription details. +1. Now run the Subscription Security Report with the command: `Get-AzSKAzureServicesSecurityStatus -SubscriptionId ` +1. After the scan is completed a new file explorer will open with the scan results. Open the .csv file and examine the scan results. + +## Setup a Log Analytics workspace (Alerting & Monitoring) +A Log Analytics workspace is an Azure resource which can be used to easily anlyze all the Secure DevOps Kit events. + +1. Using the [Azure Portal](https://portal.azure.com) to create a new Log Analytics workspace. +1. Open a PowerShell prompt and make sure that the prompt is connected with Azure +1. Connect your local machine to the Log Analytics workspace by execute the command: `Set-AzSKMonitoringSettings -WorkspaceID -SharedKey `. The workspaceID and SharedKey can be found in Azure at your Log Analytics workspace -> Advanced settings -> Connected Sources -> Windows Servers. +1. Now generate a new event by execute the command: `Get-AzSKAzureServicesSecurityStatus -SubscriptionId ` +1. Go back to the [Azure Portal](https://portal.azure.com) and your Log Analytics workspace. There go to Search and execute the search query: `search * | where type == "AzSK_CL"`. This will show your recently generated event and all the other events as well. + +## Deploy the AzSK Monitoring Solution (Alerting & Monitoring) +The AzSK Monitoring Solution is deployed to a Log Analytics workspace that is used by the dev ops team for monitoring and generating a dashboard for security monitoring and alerting based on AzSK control evaluation events. + +1. Get the details of your Log Analytics workspace + 1. Switch to the Log Analytics subscription by using the PowerShell command `Set-AzContext -SubscriptionId ` + 1. Get info about the Log Analytics workspace by using the PowerShell command `Get-AzOperationalInsightsWorkspace` + +1. Obtain the workspaceId and sharedKey for the Log Analytics workspace you'd like to use for monitoring. Go to the Log Analytics workspace and navigate to "Advanced Settings -> Connected Sources -> Windows Servers". + +1. In the PowerShell script here below replace the variables between '<>'. After replacing the variables execute the PowerShell script to deploy the AzSK Monitoring Solution + ```PowerShell + $lawsSubId ='' #subscription hosting the Log Analytics workspace + $lawsId ='' + $lawsRGName ='' #RG where the Log Analytics workspace is hosted (See 1-a) + $azSkViewName = '' #This will identify the tile for AzSK view in Log Analytics workspace. E.g., MyApp-View-1 + + #This command will deploy the AzSK view in the Log Analytics workspace. Happy monitoring! + Install-AzSKMonitoringSolution -LAWSSubscriptionId $lawsSubId -LAWSResourceGroup -$lawsRGName -WorkspaceId $lawsId -ViewName $azSkViewName + ``` +1. In the [Azure Portal](https://portal.azure.com) go to your Log Analytics workspace to view the logs. Inside the Log Analytics workspace go to the overview page. In the Get started with Log Analytics block you can click on the View Solutions link (located below 2. Configure monitoring solutions). A new blade will something like here below. When you click on the graph it will open the Az SK Security View. + + ![alt text](../images/secure-devopskit-workspace-summary-azure.png "") + +1. The next step should be analyzing the logs and create alerts based on the results of the logs. But that is out of scope for this lab. + +## Setup and configure Continuous Assurance (Continuous Assurance) +1. To setup the Continuous Assurance you need to run the PowerShell command + + ```PowerShell + Install-AzSKContinuousAssurance -SubscriptionId -ResourceGroupNames ‘rgName1, rgName2,…etc.’ -LAWSId -LAWSSharedKey + + # You can also use “*” for the variable ResourceGroupNames to specify all Resource Groups + ``` + +1. Verify if Continuous Assurance has been correctly setup + 1. In the [Azure Portal](https://portal.azure.com) select your subscription and search for Automation Account. You should see an Automation Account with the name `AzSKContinuousAssurance`. Click this Automation Account to go to the resouce blade. + + 1. Inside the blade click on Runbooks and it should contain the runbook `Continuous_Assurance_Runbook` + + 1. Inside the blade click on Schedules and it should contain the schedule `CA_Scan_Schedule` + + 1. Inside the blade click on Run As Accounts and it should contain an account for the `AzSK_CA` + +1. Verify that all required modules are downloaded successfully (this can take a couple hours before it is completed) + 1. Inside the blade click on the Modules tile. The module `AzSK` should be listed there. Status column value for all modules should be `Available` + +1. Once the Continuous setupd and modules download are completed successfully, the runbooks will automatically execute periodically (once a day) and scan the subscription and the specified resource groups for the application(s) for security issues. The outcomes of these scans will get stored in a storage account created by the installation (format : azsk e.g. azsk20170505181008) and follows a similar structure as followed by standalone SVT execution (CSV file, LOG file, etc.). + +## Stretch goals +1. Add the AzSK tasks to your pipeline to scan your Azure subscription and his resources. diff --git a/security-testing/README.md b/security-testing/README.md new file mode 100644 index 0000000..08db065 --- /dev/null +++ b/security-testing/README.md @@ -0,0 +1,18 @@ +# Avanade DevOps HOL - Security Verification Tests in your CD pipeline + +This lab focuses on adding Security Verification Tests to your existing CD pipeline. + +Based on [this](https://github.com/azsk/DevOpsKit-docs/blob/master/03-Security-In-CICD/Readme.md#enable-azsk-extension-for-your-vsts) tutorial. + +## Prerequisites + +- Complete lab [Create a CI/CD pipeline for .NET with the Azure DevOps Project](../azure-devops-project/README.md). + +## Tasks + +1. Follow the tutorial to set up Security Verification Tests. + +1. Try to resolve the security warnings/errors. + +## Next steps +Return to [the lab index](../README.md) and continue with the next lab. \ No newline at end of file diff --git a/smoke-testing/README.md b/smoke-testing/README.md new file mode 100644 index 0000000..814b91a --- /dev/null +++ b/smoke-testing/README.md @@ -0,0 +1,87 @@ +# Validating the release with automated Smoke Testing + +This lab contains instructions to enable automated smoke testing in the release. + +## Prerequisites + +- Complete lab: [Pipeline as code with K8s and Terraform](https://dev.azure.com/thx1139/_git/workshop1?path=%2FREADME.md) +- Complete lab: [UI Testing with Selenium and Azure DevOps](../ui-testing/README.md) + +## Configure local Smoke Testing + +1. In the **FunctionalTests** project, add a smoke test to validate if the website is available: +
SmokeTests.cs (expand to view code) + + ```csharp + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System; + using System.Net; + + namespace aspnet_core_dotnet_core.FunctionalTests + { + [TestClass] + public class SmokeTests + { + public TestContext TestContext { get; set; } + + private string _siteUrl; + + [TestInitialize()] + public void MyTestInitialize() + { + if (TestContext.Properties["siteUrl"] != null) + { + _siteUrl = TestContext.Properties["siteUrl"].ToString(); + } + } + + [TestMethod] + [TestCategory("Smoke")] + public void ValidateSiteIsAvailable() + { + try + { + var request = WebRequest.CreateHttp(_siteUrl); + request.Timeout = 60000; + request.ReadWriteTimeout = 60000; + using (var response = (HttpWebResponse)request.GetResponse()) + { + // Assert + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + } + } + catch (Exception ex) + { + Console.WriteLine("Exception: {0}", ex.Message); + Assert.Fail(ex.Message); + } + } + } + } + ``` +
+ +1. Validate if the test passes:\ +Right-click the *ValidateSiteIsAvailable()* test method and select **Run Test(s)** + +## Configure automated Smoke Testing in the release pipeline + +1. Include Smoke tests in the app pipeline: + - Edit the Azure DevOps **app** pipeline + - In the stage **Release_Test**, edit the deployment **Run_functional_tests** + - Rename the deployment name to **Run_tests** + - Clone the existing **VSTest@2** and place it right below the current task + - Give the first test task the displayname **Run Smoke Tests** and ensure it has this setting: + - *Test filter criteria:* TestCategory=Smoke + - Give the second test task the displayname **Run UI Tests** and ensure it has this setting: + - *Test filter criterai:* TestCategory=UI + +1. Commit your code to trigger the **App pipeline**.\ + Upon completion, inspect the release results and verify the Smoke test passed. + +## Stretch goals + +1. Refactor all the hardcoded timeout values to a variable in the runsettings file. Make them tokenized for replacement during the Release + +## Next steps +Return to the [Lab index](../README.md) and continue with the next lab \ No newline at end of file diff --git a/sonarqube/README.md b/sonarqube/README.md new file mode 100644 index 0000000..81ec0a7 --- /dev/null +++ b/sonarqube/README.md @@ -0,0 +1,110 @@ +# Static code analysis with SonarQube + +This lab contains instructions to enable automated code analysis using SonarQube.\ +The instructions are based on the following documentation: + +- [Analyzing with SonarQube Extension for VSTS-TFS](https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Extension+for+VSTS-TFS) + +## Prerequisites + +- Complete lab: [Pipeline as code with K8s and Terraform](https://dev.azure.com/thx1139/_git/workshop1?path=%2FREADME.md) + +## Create a SonarQube instance and project + +1. Using the [Azure Portal](https://portal.azure.com), create a SonarQube container instance. Follow instructions from +[Quickstart: Deploy a container instance in Azure using the Azure portal](https://docs.microsoft.com/azure/container-instances/container-instances-quickstart-portal)\ +Ensure the following settings: + - *Resource group:* sonarqube-rg (new) + - *Container name:* sonarqube-aci + - *Region:* (Europe) West Europe + - *Image type:* Public + - *Image name:* sonarqube + - *OS type:* Linux + - *Size:* 2vcpus, 4 GiB memory, 0 gpus + - *DNS name label:* sonar-\ + - *Port:* 9000 TCP + +1. When provisioning is complete, create a project in SonarQube: + - **Visit** the **SonarQube** instance at\ +*http:\//sonar-\\.westeurope.azurecontainer.io:9000* + - Login using the default credentials (admin/admin) + - Under Projects, select **Create new project**. Ensure the following settings: + - *Project key:* devopshol + - *Display name:* devopshol + - On the resulting project page, select **Generate** to generate a project token. As token name you can use *devopsholtoken* \ +*NOTE: store the token somewhere, it is needed in the next steps to configure the Release* + +## Configure SonarQube analysis on the Build stage + +1. Install the SonarQube extension, go to the [Marketplace](https://marketplace.visualstudio.com/items?itemName=SonarSource.sonarqube) and press on **Get it free**. Make sure you select the right Azure DevOps organization, the one you are using for this training. + +1. Add SonarQube service connection to your Azure DevOps pipelines + - Go to Project Settings --> Service Connections + - Add a new SonarQube service connection with the following demands: + * Server url: http://sonar-**youruniquealias**.westeurope.azurecontainer.io:9000 + * Token: The just generated token from SonarQube + * Service connection name: sonarqubeconn1 + +1. In the App pipeline add a new job *Run_SonarQube_analysis* as first job in the Build stage with the following steps: + 1. Job pool vmImage = *ubuntu-latest* + + 1. Prepare Analysis Configuration (SonarQube) + * SonarQube Server Enpoint: sonarqubeconn1 + * Integrate with MSBuild + * Project key: devopshol + * Project name: devopshol + * Project version: 1.0 + + 1. Use .NET Core + * PackageType: sdk + * Version: 3.x + + 1. .NET Core + * Command: build + * Projects: **/mywebapp.csproj + + 1. Use .NET Core + * PackageType: sdk + * Version: 2.x + + 1. Run Code Analysis (SonarQube) + + 1. Publish Quality Gate Result (SonarQube) + * Timeout (s): 300 + +1. Save the changes to your pipeline, but don't start it yet. + +1. Add a project guid to the **mywebapp** project.\ +*This is a workaround for the SonarQube runner to work with dotnet core projects.\ +Otherwise, the dotnet build task will have an error similar to:*\ +```No analyzable projects were found. SonarQube analysis will not be performed. Check the build summary report for details.```\ +Edit the project file (mywebapp.csproj), and ensure the *ProjectGuid* property is present: + +
mywebapp.csproj (expand to view code) + + ```xml + + c1182fc3-8c56-4d10-b550-965843e9e9b4 + + ``` +
+ +1. Commit and push the project file changes, to queue a build. + +1. When finished, inspect the SonarQube results for the build: + - Check the build Summary tab + - Inspect the status in the tab **Extensions** + - Open the SonarQube project results by following the **Detailed SonarQube report** link. + - Review the code analysis outcome in the SonarQube project results + +## Stretch goals + +1. Install 'SonarLint for Visual Studio 2019' via 'Extensions' +2. Configure your Sonar server via Team Explorer - SonarQube +3. Build your application and see the Sonar issues appear in the Error List of Visual Studio +4. Mark an issue in SonarQube as false positive and rebuild your application +5. Resolve technical debt or issues reported by SonarQube +6. Set up own SonarQube server to use in this lab using Bitnami + +## Next steps +Return to the [Lab index](../README.md) and continue with the next lab \ No newline at end of file diff --git a/source/build/DevOpsHOL-CI.json b/source/build/DevOpsHOL-CI.json new file mode 100644 index 0000000..479f4e6 --- /dev/null +++ b/source/build/DevOpsHOL-CI.json @@ -0,0 +1 @@ +{"options":[{"enabled":true,"definition":{"id":"5d58cc01-7c75-450c-be18-a388ddb129ec"},"inputs":{"branchFilters":"[\"+refs/heads/*\"]","additionalFields":"{}"}},{"enabled":false,"definition":{"id":"a9db38f9-9fdc-478c-b0f9-464221e58316"},"inputs":{"workItemType":"Task","assignToRequestor":"true","additionalFields":"{}"}}],"triggers":[{"branchFilters":["+refs/heads/master"],"pathFilters":[],"batchChanges":false,"maxConcurrentBuildsPerBranch":1,"pollingInterval":0,"triggerType":2}],"variables":{"BuildConfiguration":{"value":"release"},"BuildPlatform":{"value":"any cpu"},"system.debug":{"value":"false","allowOverride":true}},"retentionRules":[{"branches":["+refs/heads/*"],"artifacts":[],"artifactTypesToDelete":["FilePath","SymbolStore"],"daysToKeep":10,"minimumToKeep":1,"deleteBuildRecord":true,"deleteTestResults":true}],"properties":{},"tags":[],"_links":{"self":{"href":"https://dev.azure.com/nagroma/f4f57479-d07f-457d-9107-6dae04b04aad/_apis/build/Definitions/41?revision=2"},"web":{"href":"https://dev.azure.com/nagroma/f4f57479-d07f-457d-9107-6dae04b04aad/_build/definition?definitionId=41"},"editor":{"href":"https://dev.azure.com/nagroma/f4f57479-d07f-457d-9107-6dae04b04aad/_build/designer?id=41&_a=edit-build-definition"},"badge":{"href":"https://dev.azure.com/nagroma/f4f57479-d07f-457d-9107-6dae04b04aad/_apis/build/status/41"}},"buildNumberFormat":"$(date:yyyyMMdd)$(rev:.r)","jobAuthorizationScope":1,"jobTimeoutInMinutes":60,"jobCancelTimeoutInMinutes":5,"process":{"phases":[{"steps":[{"environment":{},"enabled":true,"continueOnError":false,"alwaysRun":false,"displayName":"Use NuGet 4.3.0","timeoutInMinutes":0,"condition":"succeeded()","task":{"id":"2c65196a-54fd-4a02-9be8-d9d1837b7c5d","versionSpec":"0.*","definitionType":"task"},"inputs":{"versionSpec":"4.3.0","checkLatest":"false"}},{"environment":{},"enabled":true,"continueOnError":false,"alwaysRun":false,"displayName":"NuGet restore","timeoutInMinutes":0,"condition":"succeeded()","task":{"id":"333b11bd-d341-40d9-afcf-b32d5ce6f23b","versionSpec":"2.*","definitionType":"task"},"inputs":{"command":"restore","solution":"**/*.sln","selectOrConfig":"select","feedRestore":"","includeNuGetOrg":"true","nugetConfigPath":"","externalEndpoints":"","noCache":"false","disableParallelProcessing":"false","packagesDirectory":"","verbosityRestore":"Detailed","searchPatternPush":"$(Build.ArtifactStagingDirectory)/**/*.nupkg;!$(Build.ArtifactStagingDirectory)/**/*.symbols.nupkg","nuGetFeedType":"internal","feedPublish":"","allowPackageConflicts":"false","externalEndpoint":"","verbosityPush":"Detailed","searchPatternPack":"**/*.csproj","configurationToPack":"$(BuildConfiguration)","outputDir":"$(Build.ArtifactStagingDirectory)","versioningScheme":"off","includeReferencedProjects":"false","versionEnvVar":"","requestedMajorVersion":"1","requestedMinorVersion":"0","requestedPatchVersion":"0","packTimezone":"utc","includeSymbols":"false","toolPackage":"false","buildProperties":"","basePath":"","verbosityPack":"Detailed","arguments":""}},{"environment":{},"enabled":true,"continueOnError":false,"alwaysRun":false,"displayName":"Build solution **\\*.sln","timeoutInMinutes":0,"condition":"succeeded()","task":{"id":"71a9a2d3-a98a-4caa-96ab-affca411ecda","versionSpec":"1.*","definitionType":"task"},"inputs":{"solution":"**\\*.sln","vsVersion":"latest","msbuildArgs":"/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation=\"$(build.artifactstagingdirectory)\"","platform":"$(BuildPlatform)","configuration":"$(BuildConfiguration)","clean":"false","maximumCpuCount":"false","restoreNugetPackages":"false","msbuildArchitecture":"x86","logProjectEvents":"true","createLogFile":"false"}},{"environment":{},"enabled":true,"continueOnError":false,"alwaysRun":false,"displayName":"Test Assemblies","timeoutInMinutes":0,"condition":"succeeded()","task":{"id":"5541a522-603c-47ad-91fc-a4b1d163081b","versionSpec":"2.*","definitionType":"task"},"inputs":{"command":"test","publishWebProjects":"true","projects":"**/*test*.csproj","custom":"","arguments":"","publishTestResults":"true","zipAfterPublish":"true","modifyOutputPath":"true","selectOrConfig":"select","feedRestore":"","includeNuGetOrg":"true","nugetConfigPath":"","externalEndpoints":"","noCache":"false","packagesDirectory":"","verbosityRestore":"Detailed","searchPatternPush":"$(Build.ArtifactStagingDirectory)/*.nupkg","nuGetFeedType":"internal","feedPublish":"","externalEndpoint":"","searchPatternPack":"**/*.csproj","configurationToPack":"$(BuildConfiguration)","outputDir":"$(Build.ArtifactStagingDirectory)","nobuild":"false","versioningScheme":"off","versionEnvVar":"","requestedMajorVersion":"1","requestedMinorVersion":"0","requestedPatchVersion":"0","buildProperties":"","verbosityPack":"Detailed","workingDirectory":""}},{"environment":{},"enabled":true,"continueOnError":false,"alwaysRun":false,"displayName":"Publish Artifact: drop","timeoutInMinutes":0,"condition":"succeeded()","task":{"id":"2ff763a7-ce83-4e1f-bc89-0ae63477cebe","versionSpec":"1.*","definitionType":"task"},"inputs":{"PathtoPublish":"$(Build.ArtifactStagingDirectory)","ArtifactName":"drop","ArtifactType":"Container","TargetPath":"","Parallel":"false","ParallelCount":"8"}}],"name":"Agent job 1","refName":"Phase_1","condition":"succeeded()","target":{"executionOptions":{"type":0},"allowScriptsAuthAccessOption":false,"type":1},"jobAuthorizationScope":1,"jobCancelTimeoutInMinutes":1}],"type":1},"repository":{"properties":{"cleanOptions":"0","labelSources":"0","labelSourcesFormat":"$(build.buildNumber)","reportBuildStatus":"true","gitLfsSupport":"false","skipSyncSource":"false","checkoutNestedSubmodules":"false","fetchDepth":"0"},"id":"d1840098-053d-433c-ab57-2104c84e3c94","type":"TfsGit","name":"DevOpsHOL","url":"https://nagroma@dev.azure.com/nagroma/DevOpsHOL/_git/DevOpsHOL","defaultBranch":"refs/heads/master","clean":"false","checkoutSubmodules":false},"processParameters":{},"quality":1,"authoredBy":{"displayName":"Andrew Morgan","url":"https://app.vssps.visualstudio.com/A94cbef75-cc36-4962-a500-fe681327dee5/_apis/Identities/652d04a6-da7e-4ce3-b365-8cfed2574a7d","_links":{"avatar":{"href":"https://dev.azure.com/nagroma/_apis/GraphProfile/MemberAvatars/msa.OWZkNDNhYTktZTkxZS03NmZiLWE1YjktNWQ2Mzg0YTQ4OTI3"}},"id":"652d04a6-da7e-4ce3-b365-8cfed2574a7d","uniqueName":"nagroma.NOCONFLICT.652d04a6-da7e-4ce3-b365-8cfed2574a7d@hotmail.com","imageUrl":"https://dev.azure.com/nagroma/_api/_common/identityImage?id=652d04a6-da7e-4ce3-b365-8cfed2574a7d","descriptor":"msa.OWZkNDNhYTktZTkxZS03NmZiLWE1YjktNWQ2Mzg0YTQ4OTI3"},"drafts":[],"queue":{"_links":{"self":{"href":"https://dev.azure.com/nagroma/_apis/build/Queues/139"}},"id":139,"name":"Hosted VS2017","url":"https://dev.azure.com/nagroma/_apis/build/Queues/139","pool":{"id":4,"name":"Hosted VS2017","isHosted":true}},"id":41,"name":"DevOpsHOL-CI","url":"https://dev.azure.com/nagroma/f4f57479-d07f-457d-9107-6dae04b04aad/_apis/build/Definitions/41?revision=2","uri":"vstfs:///Build/Definition/41","path":"\\","type":2,"queueStatus":0,"revision":2,"createdDate":"2018-11-13T18:16:34.057Z","project":{"id":"f4f57479-d07f-457d-9107-6dae04b04aad","name":"DevOpsHOL","url":"https://dev.azure.com/nagroma/_apis/projects/f4f57479-d07f-457d-9107-6dae04b04aad","state":1,"revision":226,"visibility":"private"}} \ No newline at end of file diff --git a/source/deploy/DevOpsHOL-CD.json b/source/deploy/DevOpsHOL-CD.json new file mode 100644 index 0000000..0a7daff --- /dev/null +++ b/source/deploy/DevOpsHOL-CD.json @@ -0,0 +1 @@ +{"source":2,"revision":1,"description":null,"createdBy":{"displayName":"Andrew Morgan","url":"https://app.vssps.visualstudio.com/A94cbef75-cc36-4962-a500-fe681327dee5/_apis/Identities/652d04a6-da7e-4ce3-b365-8cfed2574a7d","_links":{"avatar":{"href":"https://dev.azure.com/nagroma/_apis/GraphProfile/MemberAvatars/msa.OWZkNDNhYTktZTkxZS03NmZiLWE1YjktNWQ2Mzg0YTQ4OTI3"}},"id":"652d04a6-da7e-4ce3-b365-8cfed2574a7d","uniqueName":"nagroma.NOCONFLICT.652d04a6-da7e-4ce3-b365-8cfed2574a7d@hotmail.com","imageUrl":"https://dev.azure.com/nagroma/_api/_common/identityImage?id=652d04a6-da7e-4ce3-b365-8cfed2574a7d","descriptor":"msa.OWZkNDNhYTktZTkxZS03NmZiLWE1YjktNWQ2Mzg0YTQ4OTI3"},"createdOn":"2018-11-13T19:40:56.740Z","modifiedBy":{"displayName":"Andrew Morgan","url":"https://app.vssps.visualstudio.com/A94cbef75-cc36-4962-a500-fe681327dee5/_apis/Identities/652d04a6-da7e-4ce3-b365-8cfed2574a7d","_links":{"avatar":{"href":"https://dev.azure.com/nagroma/_apis/GraphProfile/MemberAvatars/msa.OWZkNDNhYTktZTkxZS03NmZiLWE1YjktNWQ2Mzg0YTQ4OTI3"}},"id":"652d04a6-da7e-4ce3-b365-8cfed2574a7d","uniqueName":"nagroma.NOCONFLICT.652d04a6-da7e-4ce3-b365-8cfed2574a7d@hotmail.com","imageUrl":"https://dev.azure.com/nagroma/_api/_common/identityImage?id=652d04a6-da7e-4ce3-b365-8cfed2574a7d","descriptor":"msa.OWZkNDNhYTktZTkxZS03NmZiLWE1YjktNWQ2Mzg0YTQ4OTI3"},"modifiedOn":"2018-11-13T19:40:56.740Z","isDeleted":false,"variables":{"AppServicePlan":{"value":"DevOpsHOL"},"ResourceGroupName":{"value":"DevOpsHOL"},"SiteLocation":{"value":"eastus2"},"WebsiteName":{"value":"devopshol-am"}},"variableGroups":[],"environments":[{"id":1,"name":"Dev","rank":1,"owner":{"displayName":"Andrew Morgan","url":"https://app.vssps.visualstudio.com/A94cbef75-cc36-4962-a500-fe681327dee5/_apis/Identities/652d04a6-da7e-4ce3-b365-8cfed2574a7d","_links":{"avatar":{"href":"https://dev.azure.com/nagroma/_apis/GraphProfile/MemberAvatars/msa.OWZkNDNhYTktZTkxZS03NmZiLWE1YjktNWQ2Mzg0YTQ4OTI3"}},"id":"652d04a6-da7e-4ce3-b365-8cfed2574a7d","uniqueName":"nagroma.NOCONFLICT.652d04a6-da7e-4ce3-b365-8cfed2574a7d@hotmail.com","imageUrl":"https://dev.azure.com/nagroma/_api/_common/identityImage?id=652d04a6-da7e-4ce3-b365-8cfed2574a7d","descriptor":"msa.OWZkNDNhYTktZTkxZS03NmZiLWE1YjktNWQ2Mzg0YTQ4OTI3"},"variables":{},"variableGroups":[],"preDeployApprovals":{"approvals":[{"rank":1,"isAutomated":true,"isNotificationOn":false,"id":1}],"approvalOptions":{"requiredApproverCount":null,"releaseCreatorCanBeApprover":false,"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped":false,"enforceIdentityRevalidation":false,"timeoutInMinutes":0,"executionOrder":1}},"deployStep":{"id":2},"postDeployApprovals":{"approvals":[{"rank":1,"isAutomated":true,"isNotificationOn":false,"id":3}],"approvalOptions":{"requiredApproverCount":null,"releaseCreatorCanBeApprover":false,"autoTriggeredAndPreviousEnvironmentApprovedCanBeSkipped":false,"enforceIdentityRevalidation":false,"timeoutInMinutes":0,"executionOrder":2}},"deployPhases":[{"deploymentInput":{"parallelExecution":{"parallelExecutionType":0},"skipArtifactsDownload":false,"artifactsDownloadInput":{"downloadInputs":[{"artifactItems":[],"alias":"_DevOpsHOL-CI","artifactType":"Build","artifactDownloadMode":"All"}]},"queueId":139,"demands":[],"enableAccessToken":false,"timeoutInMinutes":0,"jobCancelTimeoutInMinutes":1,"condition":"succeeded()","overrideInputs":{}},"rank":1,"phaseType":1,"name":"Agent job","workflowTasks":[{"environment":{},"taskId":"94a74903-f93f-4075-884f-dc11f34058b4","version":"2.*","name":"Azure Deployment:Create Or Update Resource Group action on $(ResourceGroupName)","refName":"","enabled":true,"alwaysRun":false,"continueOnError":false,"timeoutInMinutes":0,"definitionType":"task","overrideInputs":{},"condition":"succeeded()","inputs":{"ConnectedServiceName":"b973e9df-170f-4334-bd34-7d407bbffacb","action":"Create Or Update Resource Group","resourceGroupName":"$(ResourceGroupName)","location":"$(SiteLocation)","templateLocation":"Linked artifact","csmFileLink":"","csmParametersFileLink":"","csmFile":"$(System.DefaultWorkingDirectory)/_DevOpsHOL-CI/drop/DevOpsHOL.Deployment/WebSite.json","csmParametersFile":"","overrideParameters":"-siteName $(WebSiteName) -appServicePlanName $(AppServicePlan) -siteLocation $(SiteLocation) -workerSize \"0\"","deploymentMode":"Incremental","enableDeploymentPrerequisites":"None","deploymentGroupEndpoint":"","project":"","deploymentGroupName":"","copyAzureVMTags":"true","runAgentServiceAsUser":"false","userName":"","password":"","outputVariable":"","deploymentOutputs":""}},{"environment":{},"taskId":"497d490f-eea7-4f2b-ab94-48d9c1acdcb1","version":"3.*","name":"Azure App Service Deploy: $(WebSiteName)","refName":"","enabled":true,"alwaysRun":false,"continueOnError":false,"timeoutInMinutes":0,"definitionType":"task","overrideInputs":{},"condition":"succeeded()","inputs":{"ConnectedServiceName":"b973e9df-170f-4334-bd34-7d407bbffacb","WebAppKind":"app","WebAppName":"$(WebSiteName)","DeployToSlotFlag":"true","ResourceGroupName":"$(ResourceGroupName)","SlotName":"Dev","ImageSource":"Registry","AzureContainerRegistry":"","AzureContainerRegistryLoginServer":"","AzureContainerRegistryImage":"","AzureContainerRegistryTag":"","DockerRepositoryAccess":"public","RegistryConnectedServiceName":"","PrivateRegistryImage":"","PrivateRegistryTag":"","DockerNamespace":"","DockerRepository":"","DockerImageTag":"","VirtualApplication":"","Package":"$(System.DefaultWorkingDirectory)/**/*.zip","BuiltinLinuxPackage":"$(System.DefaultWorkingDirectory)/**/*.zip","RuntimeStack":"","StartupCommand":"","WebAppUri":"","ScriptType":"","InlineScript":":: You can provide your deployment commands here. One command per line.","ScriptPath":"","GenerateWebConfig":"false","WebConfigParameters":"","AppSettings":"","ConfigurationSettings":"","TakeAppOfflineFlag":"false","UseWebDeploy":"false","SetParametersFile":"","RemoveAdditionalFilesFlag":"false","ExcludeFilesFromAppDataFlag":"false","AdditionalArguments":"","RenameFilesFlag":"false","XmlTransformation":"false","XmlVariableSubstitution":"false","JSONFiles":""}}]}],"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":[],"currentRelease":{"id":1,"url":"https://vsrm.dev.azure.com/nagroma/f4f57479-d07f-457d-9107-6dae04b04aad/_apis/Release/releases/1","_links":{}},"retentionPolicy":{"daysToKeep":30,"releasesToKeep":3,"retainBuild":true},"processParameters":{},"properties":{},"preDeploymentGates":{"id":0,"gatesOptions":null,"gates":[]},"postDeploymentGates":{"id":0,"gatesOptions":null,"gates":[]},"environmentTriggers":[],"badgeUrl":"https://vsrm.dev.azure.com/nagroma/_apis/public/Release/badge/f4f57479-d07f-457d-9107-6dae04b04aad/1/1"}],"artifacts":[{"sourceId":"f4f57479-d07f-457d-9107-6dae04b04aad:41","type":"Build","alias":"_DevOpsHOL-CI","definitionReference":{"defaultVersionBranch":{"id":"","name":""},"defaultVersionSpecific":{"id":"","name":""},"defaultVersionTags":{"id":"","name":""},"defaultVersionType":{"id":"latestType","name":"Latest"},"definition":{"id":"41","name":"DevOpsHOL-CI"},"definitions":{"id":"","name":""},"IsMultiDefinitionType":{"id":"False","name":"False"},"project":{"id":"f4f57479-d07f-457d-9107-6dae04b04aad","name":"DevOpsHOL"},"repository":{"id":"d1840098-053d-433c-ab57-2104c84e3c94","name":"DevOpsHOL"},"artifactSourceDefinitionUrl":{"id":"https://dev.azure.com/nagroma/_permalink/_build/index?collectionId=d3e7f117-361e-4266-8c86-e766ad01a100&projectId=f4f57479-d07f-457d-9107-6dae04b04aad&definitionId=41","name":""}},"isPrimary":true,"isRetained":false}],"triggers":[],"releaseNameFormat":"Release-$(rev:r)","tags":[],"properties":{"DefinitionCreationSource":{"$type":"System.String","$value":"Other"}},"id":1,"name":"DevOpsHOL-CD","path":"\\","projectReference":null,"url":"https://vsrm.dev.azure.com/nagroma/f4f57479-d07f-457d-9107-6dae04b04aad/_apis/Release/definitions/1","_links":{"self":{"href":"https://vsrm.dev.azure.com/nagroma/f4f57479-d07f-457d-9107-6dae04b04aad/_apis/Release/definitions/1"},"web":{"href":"https://dev.azure.com/nagroma/f4f57479-d07f-457d-9107-6dae04b04aad/_release?definitionId=1"}}} \ No newline at end of file diff --git a/source/deploy/azuredeploy.json b/source/deploy/azuredeploy.json deleted file mode 100644 index 847628f..0000000 --- a/source/deploy/azuredeploy.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "siteName": { - "type": "string" - }, - "appServicePlanName": { - "type": "string" - }, - "siteLocation": { - "type": "string" - }, - "workerSize": { - "type": "string", - "allowedValues": [ - "0", - "1", - "2" - ], - "defaultValue": "0" - }, - }, - "resources": [ - { - "apiVersion": "2015-08-01", - "name": "[parameters('appServicePlanName')]", - "type": "Microsoft.Web/serverfarms", - "location": "[parameters('siteLocation')]", - "sku": { - "name": "S1", - "tier": "Standard", - "capacity": 1 - }, - "properties": { - "name": "[parameters('appServicePlanName')]" - } - }, - { - "apiVersion": "2015-08-01", - "name": "[parameters('siteName')]", - "type": "Microsoft.Web/sites", - "location": "[parameters('siteLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]" - ], - "properties": { - "serverFarmId": "[parameters('appServicePlanName')]" - }, - "resources": [ - { - "apiVersion": "2015-08-01", - "name": "web", - "type": "config", - "dependsOn": [ - "[resourceId('Microsoft.Web/Sites', parameters('siteName'))]" - ], - "properties": { - "phpVersion": "5.5", - "netFrameworkVersion": "v4.0", - "use32BitWorkerProcess": false, /* 64-bit platform */ - "webSocketsEnabled": true, - "alwaysOn": true, - "remoteDebuggingEnabled": true, - "remoteDebuggingVersion": "VS2013", - - "defaultDocuments": [ - "index.html", - "hostingstart.html" - ] - } - }, - { - "apiVersion": "2015-08-01", - "name": "appsettings", - "type": "config", - "dependsOn": [ - "[resourceId('Microsoft.Web/Sites', parameters('siteName'))]" - ], - "properties": { - "AppSettingKey1": "Some value" - } - }, - { - "apiVersion": "2015-08-01", - "name": "logs", - "type": "config", - "dependsOn": [ - "[resourceId('Microsoft.Web/Sites', parameters('siteName'))]" - ], - "properties": { - "applicationLogs": { - "fileSystem": { - "level": "Warning" - } - }, - "httpLogs": { - "fileSystem": { - "retentionInMb": 40, - "enabled": true - } - }, - "failedRequestsTracing": { - "enabled": true - }, - "detailedErrorMessages": { - "enabled": true - } - } - }, - { - "apiVersion": "2015-08-01", - "name": "slotconfignames", - "type": "config", - "dependsOn": [ - "[resourceId('Microsoft.Web/Sites', parameters('siteName'))]" - ], - "properties": { - "appSettingNames": [ "AppSettingKey1" ] - } - }, - { - "apiVersion": "2015-08-01", - "name": "Dev", - "type": "slots", - "location": "[parameters('siteLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/Sites', parameters('siteName'))]" - ], - "properties": { - }, - "resources": [ - { - "apiVersion": "2015-08-01", - "name": "appsettings", - "type": "config", - "dependsOn": [ - "[resourceId('Microsoft.Web/Sites/Slots', parameters('siteName'), 'Dev')]" - ], - "properties": { - "AppSettingKey1": "Some development value" - } - } - ] - }, - { - "apiVersion": "2015-08-01", - "name": "Staging", - "type": "slots", - "location": "[parameters('siteLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/Sites', parameters('siteName'))]" - ], - "properties": { - }, - "resources": [ - { - "apiVersion": "2015-08-01", - "name": "appsettings", - "type": "config", - "dependsOn": [ - "[resourceId('Microsoft.Web/Sites/Slots', parameters('siteName'), 'Staging')]" - ], - "properties": { - "AppSettingKey1": "Some staging value" - } - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/source/deploy/azuredeploy.parameters.json b/source/deploy/azuredeploy.parameters.json deleted file mode 100644 index 2173032..0000000 --- a/source/deploy/azuredeploy.parameters.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "siteName": { - "value": "DevOpsHOL-site" - }, - "siteLocation": { - "value": "Central US" - }, - "workerSize": { - "value": "0" - }, - "appServicePlanName": { - "value": "DevOpsHOL-as" - } - } - } \ No newline at end of file diff --git a/source/tests/HomeControllerTest.cs b/source/tests/HomeControllerTest.cs deleted file mode 100644 index cc4a0fb..0000000 --- a/source/tests/HomeControllerTest.cs +++ /dev/null @@ -1,50 +0,0 @@ -using DevOpsHOL.Controllers; -using Microsoft.AspNetCore.Mvc; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace DevOpsHOL.Tests -{ - [TestClass] - public class HomeControllerTest - { - [TestMethod] - public void Index() - { - // Arrange - HomeController controller = new HomeController(); - - // Act - ViewResult result = controller.Index() as ViewResult; - - // Assert - Assert.IsNotNull(result); - } - - [TestMethod] - public void About() - { - // Arrange - HomeController controller = new HomeController(); - - // Act - ViewResult result = controller.About() as ViewResult; - - // Assert - Assert.IsNotNull(result); - Assert.AreEqual("Your application description page.", result.ViewData["Message"]); - } - - [TestMethod] - public void Contact() - { - // Arrange - HomeController controller = new HomeController(); - - // Act - ViewResult result = controller.Contact() as ViewResult; - - // Assert - Assert.IsNotNull(result); - } - } -} diff --git a/source/vsts/DevOpsHOL-CI - CD.json b/source/vsts/DevOpsHOL-CI - CD.json deleted file mode 100644 index 0393194..0000000 --- a/source/vsts/DevOpsHOL-CI - CD.json +++ /dev/null @@ -1,690 +0,0 @@ -{ - "source":2, - "id":6, - "revision":2, - "name":"DevOpsHOL-CI - CD", - "description":null, - "createdBy":{ - "id":"652d04a6-da7e-4ce3-b365-8cfed2574a7d", - "displayName":"Andrew Morgan", - "uniqueName":"nagroma@hotmail.com", - "url":"https://app.vssps.visualstudio.com/A94cbef75-cc36-4962-a500-fe681327dee5/_apis/Identities/652d04a6-da7e-4ce3-b365-8cfed2574a7d", - "imageUrl":"https://nagroma.visualstudio.com/_api/_common/identityImage?id=652d04a6-da7e-4ce3-b365-8cfed2574a7d&t=00000000-0000-0000-5056-df8f442cd508" - }, - "createdOn":"2017-11-15T19:42:25.530Z", - "modifiedBy":{ - "id":"652d04a6-da7e-4ce3-b365-8cfed2574a7d", - "displayName":"Andrew Morgan", - "uniqueName":"nagroma@hotmail.com", - "url":"https://app.vssps.visualstudio.com/A94cbef75-cc36-4962-a500-fe681327dee5/_apis/Identities/652d04a6-da7e-4ce3-b365-8cfed2574a7d", - "imageUrl":"https://nagroma.visualstudio.com/_api/_common/identityImage?id=652d04a6-da7e-4ce3-b365-8cfed2574a7d&t=00000000-0000-0000-5056-df8f442cd508" - }, - "modifiedOn":"2017-11-15T19:45:38.497Z", - "isDeleted":false, - "path":"\\", - "variables":{ - "AppServicePlan":{ - "value":"DevOpsHOL2" - }, - "ResourceGroupName":{ - "value":"DevOpsHOL2" - }, - "SiteLocation":{ - "value":"eastus" - }, - "WebsiteName":{ - "value":"devopshol-am2" - } - }, - "variableGroups":[ - - ], - "environments":[ - { - "id":10, - "name":"Dev", - "rank":1, - "owner":{ - "id":"652d04a6-da7e-4ce3-b365-8cfed2574a7d", - "displayName":"Andrew Morgan", - "uniqueName":"nagroma@hotmail.com", - "url":"https://app.vssps.visualstudio.com/A94cbef75-cc36-4962-a500-fe681327dee5/_apis/Identities/652d04a6-da7e-4ce3-b365-8cfed2574a7d", - "imageUrl":"https://nagroma.visualstudio.com/_api/_common/identityImage?id=652d04a6-da7e-4ce3-b365-8cfed2574a7d&t=00000000-0000-0000-5056-df8f442cd508" - }, - "variables":{ - - }, - "variableGroups":[ - - ], - "preDeployApprovals":{ - "approvals":[ - { - "rank":1, - "isAutomated":true, - "isNotificationOn":false, - "id":28 - } - ] - }, - "deployStep":{ - "tasks":[ - - ], - "id":33 - }, - "postDeployApprovals":{ - "approvals":[ - { - "rank":1, - "isAutomated":true, - "isNotificationOn":false, - "id":34 - } - ] - }, - "deployPhases":[ - { - "deploymentInput":{ - "parallelExecution":{ - "parallelExecutionType":"none" - }, - "skipArtifactsDownload":false, - "artifactsDownloadInput":{ - "downloadInputs":[ - - ] - }, - "queueId":20, - "demands":[ - - ], - "enableAccessToken":false, - "timeoutInMinutes":0, - "jobCancelTimeoutInMinutes":1, - "condition":"succeeded()", - "overrideInputs":{ - - } - }, - "rank":1, - "phaseType":1, - "name":"Agent phase", - "workflowTasks":[ - { - "taskId":"94a74903-f93f-4075-884f-dc11f34058b4", - "version":"2.*", - "name":"Azure Deployment:Create Or Update Resource Group action on $(ResourceGroupName)", - "refName":"AzureResourceGroupDeployment3", - "enabled":true, - "alwaysRun":false, - "continueOnError":false, - "timeoutInMinutes":0, - "definitionType":"task", - "overrideInputs":{ - - }, - "condition":"succeeded()", - "inputs":{ - "ConnectedServiceName":"cdd20c8a-52e8-4775-8fb6-37f0e9c0cae7", - "action":"Create Or Update Resource Group", - "resourceGroupName":"$(ResourceGroupName)", - "location":"$(SiteLocation)", - "templateLocation":"Linked artifact", - "csmFileLink":"", - "csmParametersFileLink":"", - "csmFile":"$(System.DefaultWorkingDirectory)/**/WebSite.json", - "csmParametersFile":"", - "overrideParameters":"-appServicePlanName $(AppServicePlan) -siteName $(WebSiteName) -siteLocation $(SiteLocation) -workerSize \"0\"", - "deploymentMode":"Incremental", - "enableDeploymentPrerequisites":"None", - "deploymentGroupEndpoint":"", - "project":"", - "deploymentGroupName":"", - "copyAzureVMTags":"true", - "outputVariable":"" - } - }, - { - "taskId":"497d490f-eea7-4f2b-ab94-48d9c1acdcb1", - "version":"3.*", - "name":"Azure App Service Deploy: $(WebSiteName)", - "refName":"AzureRmWebAppDeployment2", - "enabled":true, - "alwaysRun":false, - "continueOnError":false, - "timeoutInMinutes":0, - "definitionType":"task", - "overrideInputs":{ - - }, - "condition":"succeeded()", - "inputs":{ - "ConnectedServiceName":"cdd20c8a-52e8-4775-8fb6-37f0e9c0cae7", - "WebAppName":"$(WebSiteName)", - "WebAppKind":"app", - "DeployToSlotFlag":"true", - "ImageSource":"Registry", - "ResourceGroupName":"$(ResourceGroupName)", - "SlotName":"Dev", - "AzureContainerRegistry":"", - "AzureContainerRegistryLoginServer":"", - "AzureContainerRegistryImage":"", - "AzureContainerRegistryTag":"", - "DockerRepositoryAccess":"public", - "RegistryConnectedServiceName":"", - "PrivateRegistryImage":"", - "PrivateRegistryTag":"", - "DockerNamespace":"", - "DockerRepository":"", - "DockerImageTag":"", - "VirtualApplication":"", - "Package":"$(System.DefaultWorkingDirectory)/**/*.zip", - "BuiltinLinuxPackage":"$(System.DefaultWorkingDirectory)/**/*.zip", - "RuntimeStack":"node|4.4", - "StartupCommand":"", - "WebAppUri":"", - "ScriptType":"", - "InlineScript":":: You can provide your deployment commands here. One command per line.", - "ScriptPath":"", - "GenerateWebConfig":"false", - "WebConfigParameters":"", - "AppSettings":"", - "TakeAppOfflineFlag":"false", - "UseWebDeploy":"false", - "SetParametersFile":"", - "RemoveAdditionalFilesFlag":"false", - "ExcludeFilesFromAppDataFlag":"false", - "AdditionalArguments":"", - "RenameFilesFlag":"false", - "XmlTransformation":"false", - "XmlVariableSubstitution":"false", - "JSONFiles":"" - } - } - ] - } - ], - "environmentOptions":{ - "emailNotificationType":"OnlyOnFailure", - "emailRecipients":"release.environment.owner;release.creator", - "skipArtifactsDownload":false, - "timeoutInMinutes":0, - "enableAccessToken":false, - "publishDeploymentStatus":true - }, - "demands":[ - - ], - "conditions":[ - { - "name":"ReleaseStarted", - "conditionType":1, - "value":"" - } - ], - "executionPolicy":{ - "concurrencyCount":0, - "queueDepthCount":0 - }, - "schedules":[ - - ], - "retentionPolicy":{ - "daysToKeep":30, - "releasesToKeep":3, - "retainBuild":true - }, - "processParameters":{ - - }, - "properties":{ - - }, - "preDeploymentGates":{ - "id":0, - "gatesOptions":null, - "gates":[ - - ] - }, - "postDeploymentGates":{ - "id":0, - "gatesOptions":null, - "gates":[ - - ] - } - }, - { - "id":11, - "name":"Stage", - "rank":2, - "owner":{ - "id":"652d04a6-da7e-4ce3-b365-8cfed2574a7d", - "displayName":"Andrew Morgan", - "uniqueName":"nagroma@hotmail.com", - "url":"https://app.vssps.visualstudio.com/A94cbef75-cc36-4962-a500-fe681327dee5/_apis/Identities/652d04a6-da7e-4ce3-b365-8cfed2574a7d", - "imageUrl":"https://nagroma.visualstudio.com/_api/_common/identityImage?id=652d04a6-da7e-4ce3-b365-8cfed2574a7d&t=00000000-0000-0000-5056-df8f442cd508" - }, - "variables":{ - - }, - "variableGroups":[ - - ], - "preDeployApprovals":{ - "approvals":[ - { - "rank":1, - "isAutomated":true, - "isNotificationOn":false, - "id":29 - } - ] - }, - "deployStep":{ - "tasks":[ - - ], - "id":32 - }, - "postDeployApprovals":{ - "approvals":[ - { - "rank":1, - "isAutomated":true, - "isNotificationOn":false, - "id":35 - } - ] - }, - "deployPhases":[ - { - "deploymentInput":{ - "parallelExecution":{ - "parallelExecutionType":"none" - }, - "skipArtifactsDownload":false, - "artifactsDownloadInput":{ - "downloadInputs":[ - - ] - }, - "queueId":20, - "demands":[ - - ], - "enableAccessToken":false, - "timeoutInMinutes":0, - "jobCancelTimeoutInMinutes":1, - "condition":"succeeded()", - "overrideInputs":{ - - } - }, - "rank":1, - "phaseType":1, - "name":"Agent phase", - "workflowTasks":[ - { - "taskId":"497d490f-eea7-4f2b-ab94-48d9c1acdcb1", - "version":"3.*", - "name":"Azure App Service Deploy: $(WebSiteName)", - "refName":"AzureRmWebAppDeployment2", - "enabled":true, - "alwaysRun":false, - "continueOnError":false, - "timeoutInMinutes":0, - "definitionType":"task", - "overrideInputs":{ - - }, - "condition":"succeeded()", - "inputs":{ - "ConnectedServiceName":"cdd20c8a-52e8-4775-8fb6-37f0e9c0cae7", - "WebAppName":"$(WebSiteName)", - "WebAppKind":"app", - "DeployToSlotFlag":"true", - "ImageSource":"Registry", - "ResourceGroupName":"$(ResourceGroupName)", - "SlotName":"Staging", - "AzureContainerRegistry":"", - "AzureContainerRegistryLoginServer":"", - "AzureContainerRegistryImage":"", - "AzureContainerRegistryTag":"", - "DockerRepositoryAccess":"public", - "RegistryConnectedServiceName":"", - "PrivateRegistryImage":"", - "PrivateRegistryTag":"", - "DockerNamespace":"", - "DockerRepository":"", - "DockerImageTag":"", - "VirtualApplication":"", - "Package":"$(System.DefaultWorkingDirectory)/**/*.zip", - "BuiltinLinuxPackage":"$(System.DefaultWorkingDirectory)/**/*.zip", - "RuntimeStack":"node|4.4", - "StartupCommand":"", - "WebAppUri":"", - "ScriptType":"", - "InlineScript":":: You can provide your deployment commands here. One command per line.", - "ScriptPath":"", - "GenerateWebConfig":"false", - "WebConfigParameters":"", - "AppSettings":"", - "TakeAppOfflineFlag":"false", - "UseWebDeploy":"false", - "SetParametersFile":"", - "RemoveAdditionalFilesFlag":"false", - "ExcludeFilesFromAppDataFlag":"false", - "AdditionalArguments":"", - "RenameFilesFlag":"false", - "XmlTransformation":"false", - "XmlVariableSubstitution":"false", - "JSONFiles":"" - } - } - ] - } - ], - "environmentOptions":{ - "emailNotificationType":"OnlyOnFailure", - "emailRecipients":"release.environment.owner;release.creator", - "skipArtifactsDownload":false, - "timeoutInMinutes":0, - "enableAccessToken":false, - "publishDeploymentStatus":true - }, - "demands":[ - - ], - "conditions":[ - { - "name":"Dev", - "conditionType":2, - "value":"4" - } - ], - "executionPolicy":{ - "concurrencyCount":0, - "queueDepthCount":0 - }, - "schedules":[ - - ], - "retentionPolicy":{ - "daysToKeep":30, - "releasesToKeep":3, - "retainBuild":true - }, - "processParameters":{ - - }, - "properties":{ - - }, - "preDeploymentGates":{ - "id":0, - "gatesOptions":null, - "gates":[ - - ] - }, - "postDeploymentGates":{ - "id":0, - "gatesOptions":null, - "gates":[ - - ] - } - }, - { - "id":12, - "name":"Prod", - "rank":3, - "owner":{ - "id":"652d04a6-da7e-4ce3-b365-8cfed2574a7d", - "displayName":"Andrew Morgan", - "uniqueName":"nagroma@hotmail.com", - "url":"https://app.vssps.visualstudio.com/A94cbef75-cc36-4962-a500-fe681327dee5/_apis/Identities/652d04a6-da7e-4ce3-b365-8cfed2574a7d", - "imageUrl":"https://nagroma.visualstudio.com/_api/_common/identityImage?id=652d04a6-da7e-4ce3-b365-8cfed2574a7d&t=00000000-0000-0000-5056-df8f442cd508" - }, - "variables":{ - - }, - "variableGroups":[ - - ], - "preDeployApprovals":{ - "approvals":[ - { - "rank":1, - "isAutomated":true, - "isNotificationOn":false, - "id":30 - } - ] - }, - "deployStep":{ - "tasks":[ - - ], - "id":31 - }, - "postDeployApprovals":{ - "approvals":[ - { - "rank":1, - "isAutomated":true, - "isNotificationOn":false, - "id":36 - } - ] - }, - "deployPhases":[ - { - "deploymentInput":{ - "parallelExecution":{ - "parallelExecutionType":"none" - }, - "skipArtifactsDownload":false, - "artifactsDownloadInput":{ - "downloadInputs":[ - - ] - }, - "queueId":20, - "demands":[ - - ], - "enableAccessToken":false, - "timeoutInMinutes":0, - "jobCancelTimeoutInMinutes":1, - "condition":"succeeded()", - "overrideInputs":{ - - } - }, - "rank":1, - "phaseType":1, - "name":"Agent phase", - "workflowTasks":[ - { - "taskId":"497d490f-eea7-4f2b-ab94-48d9c1acdcb1", - "version":"3.*", - "name":"Azure App Service Deploy: $(WebSiteName)", - "refName":"AzureRmWebAppDeployment2", - "enabled":true, - "alwaysRun":false, - "continueOnError":false, - "timeoutInMinutes":0, - "definitionType":"task", - "overrideInputs":{ - - }, - "condition":"succeeded()", - "inputs":{ - "ConnectedServiceName":"cdd20c8a-52e8-4775-8fb6-37f0e9c0cae7", - "WebAppName":"$(WebSiteName)", - "WebAppKind":"app", - "DeployToSlotFlag":"false", - "ImageSource":"Registry", - "ResourceGroupName":"$(ResourceGroupName)", - "SlotName":"Staging", - "AzureContainerRegistry":"", - "AzureContainerRegistryLoginServer":"", - "AzureContainerRegistryImage":"", - "AzureContainerRegistryTag":"", - "DockerRepositoryAccess":"public", - "RegistryConnectedServiceName":"", - "PrivateRegistryImage":"", - "PrivateRegistryTag":"", - "DockerNamespace":"", - "DockerRepository":"", - "DockerImageTag":"", - "VirtualApplication":"", - "Package":"$(System.DefaultWorkingDirectory)/**/*.zip", - "BuiltinLinuxPackage":"$(System.DefaultWorkingDirectory)/**/*.zip", - "RuntimeStack":"node|4.4", - "StartupCommand":"", - "WebAppUri":"", - "ScriptType":"", - "InlineScript":":: You can provide your deployment commands here. One command per line.", - "ScriptPath":"", - "GenerateWebConfig":"false", - "WebConfigParameters":"", - "AppSettings":"", - "TakeAppOfflineFlag":"false", - "UseWebDeploy":"false", - "SetParametersFile":"", - "RemoveAdditionalFilesFlag":"false", - "ExcludeFilesFromAppDataFlag":"false", - "AdditionalArguments":"", - "RenameFilesFlag":"false", - "XmlTransformation":"false", - "XmlVariableSubstitution":"false", - "JSONFiles":"" - } - } - ] - } - ], - "environmentOptions":{ - "emailNotificationType":"OnlyOnFailure", - "emailRecipients":"release.environment.owner;release.creator", - "skipArtifactsDownload":false, - "timeoutInMinutes":0, - "enableAccessToken":false, - "publishDeploymentStatus":true - }, - "demands":[ - - ], - "conditions":[ - { - "name":"Stage", - "conditionType":2, - "value":"4" - } - ], - "executionPolicy":{ - "concurrencyCount":0, - "queueDepthCount":0 - }, - "schedules":[ - - ], - "retentionPolicy":{ - "daysToKeep":30, - "releasesToKeep":3, - "retainBuild":true - }, - "processParameters":{ - - }, - "properties":{ - - }, - "preDeploymentGates":{ - "id":0, - "gatesOptions":null, - "gates":[ - - ] - }, - "postDeploymentGates":{ - "id":0, - "gatesOptions":null, - "gates":[ - - ] - } - } - ], - "artifacts":[ - { - "sourceId":"e58a4fcf-1085-4dc6-a88c-8d0a3dc5c49a:5", - "type":"Build", - "alias":"DevOpsHOL-CI", - "definitionReference":{ - "artifactSourceDefinitionUrl":{ - "id":"https://nagroma.visualstudio.com/_permalink/_build/index?collectionId=d3e7f117-361e-4266-8c86-e766ad01a100&projectId=e58a4fcf-1085-4dc6-a88c-8d0a3dc5c49a&definitionId=5", - "name":"" - }, - "defaultVersionBranch":{ - "id":"", - "name":"" - }, - "defaultVersionSpecific":{ - "id":"", - "name":"" - }, - "defaultVersionTags":{ - "id":"", - "name":"" - }, - "defaultVersionType":{ - "id":"latestType", - "name":"Latest" - }, - "definition":{ - "id":"5", - "name":"DevOpsHOL-CI" - }, - "project":{ - "id":"e58a4fcf-1085-4dc6-a88c-8d0a3dc5c49a", - "name":"DevOpsHOL" - } - }, - "isPrimary":true - } - ], - "triggers":[ - { - "artifactAlias":"DevOpsHOL-CI", - "triggerConditions":[ - - ], - "triggerType":1 - } - ], - "releaseNameFormat":"Release-$(rev:r)", - "url":"https://nagroma.vsrm.visualstudio.com/e58a4fcf-1085-4dc6-a88c-8d0a3dc5c49a/_apis/Release/definitions/6", - "_links":{ - "self":{ - "href":"https://nagroma.vsrm.visualstudio.com/e58a4fcf-1085-4dc6-a88c-8d0a3dc5c49a/_apis/Release/definitions/6" - }, - "web":{ - "href":"https://nagroma.visualstudio.com/e58a4fcf-1085-4dc6-a88c-8d0a3dc5c49a/_release?definitionId=6" - } - }, - "tags":[ - - ], - "properties":{ - "DefinitionCreationSource":{ - "$type":"System.String", - "$value":"ReleaseImport" - } - } -} \ No newline at end of file diff --git a/source/vsts/DevOpsHOL-CI.json b/source/vsts/DevOpsHOL-CI.json deleted file mode 100644 index 994588f..0000000 --- a/source/vsts/DevOpsHOL-CI.json +++ /dev/null @@ -1,550 +0,0 @@ -{ - "options":[ - { - "enabled":true, - "definition":{ - "id":"5d58cc01-7c75-450c-be18-a388ddb129ec" - }, - "inputs":{ - "branchFilters":"[\"+refs/heads/*\"]", - "additionalFields":"{}" - } - }, - { - "enabled":false, - "definition":{ - "id":"a9db38f9-9fdc-478c-b0f9-464221e58316" - }, - "inputs":{ - "workItemType":"362937", - "assignToRequestor":"true", - "additionalFields":"{}" - } - }, - { - "enabled":false, - "definition":{ - "id":"57578776-4c22-4526-aeb0-86b6da17ee9c" - }, - "inputs":{ - - } - } - ], - "triggers":[ - { - "branchFilters":[ - "+refs/heads/master" - ], - "pathFilters":[ - - ], - "batchChanges":false, - "maxConcurrentBuildsPerBranch":1, - "pollingInterval":0, - "triggerType":2 - } - ], - "variables":{ - "BuildConfiguration":{ - "value":"release", - "allowOverride":true - }, - "BuildPlatform":{ - "value":"any cpu", - "allowOverride":true - }, - "system.debug":{ - "value":"false", - "allowOverride":true - } - }, - "retentionRules":[ - { - "branches":[ - "+refs/heads/*" - ], - "artifacts":[ - - ], - "artifactTypesToDelete":[ - "FilePath", - "SymbolStore" - ], - "daysToKeep":10, - "minimumToKeep":1, - "deleteBuildRecord":true, - "deleteTestResults":true - } - ], - "metrics":[ - { - "name":"CurrentBuildsInQueue", - "scope":"refs/heads/master", - "intValue":0 - }, - { - "name":"CurrentBuildsInProgress", - "scope":"refs/heads/master", - "intValue":0 - }, - { - "name":"CanceledBuilds", - "scope":"refs/heads/master", - "intValue":0, - "date":"2017-11-15T00:00:00.000Z" - }, - { - "name":"FailedBuilds", - "scope":"refs/heads/master", - "intValue":0, - "date":"2017-11-15T00:00:00.000Z" - }, - { - "name":"PartiallySuccessfulBuilds", - "scope":"refs/heads/master", - "intValue":0, - "date":"2017-11-15T00:00:00.000Z" - }, - { - "name":"SuccessfulBuilds", - "scope":"refs/heads/master", - "intValue":3, - "date":"2017-11-15T00:00:00.000Z" - }, - { - "name":"TotalBuilds", - "scope":"refs/heads/master", - "intValue":3, - "date":"2017-11-15T00:00:00.000Z" - }, - { - "name":"CanceledBuilds", - "scope":"refs/heads/master", - "intValue":0, - "date":"2017-11-14T00:00:00.000Z" - }, - { - "name":"FailedBuilds", - "scope":"refs/heads/master", - "intValue":0, - "date":"2017-11-14T00:00:00.000Z" - }, - { - "name":"PartiallySuccessfulBuilds", - "scope":"refs/heads/master", - "intValue":0, - "date":"2017-11-14T00:00:00.000Z" - }, - { - "name":"SuccessfulBuilds", - "scope":"refs/heads/master", - "intValue":5, - "date":"2017-11-14T00:00:00.000Z" - }, - { - "name":"TotalBuilds", - "scope":"refs/heads/master", - "intValue":5, - "date":"2017-11-14T00:00:00.000Z" - }, - { - "name":"CanceledBuilds", - "scope":"refs/heads/master", - "intValue":0, - "date":"2017-11-13T00:00:00.000Z" - }, - { - "name":"FailedBuilds", - "scope":"refs/heads/master", - "intValue":0, - "date":"2017-11-13T00:00:00.000Z" - }, - { - "name":"PartiallySuccessfulBuilds", - "scope":"refs/heads/master", - "intValue":0, - "date":"2017-11-13T00:00:00.000Z" - }, - { - "name":"SuccessfulBuilds", - "scope":"refs/heads/master", - "intValue":1, - "date":"2017-11-13T00:00:00.000Z" - }, - { - "name":"TotalBuilds", - "scope":"refs/heads/master", - "intValue":1, - "date":"2017-11-13T00:00:00.000Z" - }, - { - "name":"CanceledBuilds", - "scope":"refs/heads/master", - "intValue":2, - "date":"2017-11-12T00:00:00.000Z" - }, - { - "name":"FailedBuilds", - "scope":"refs/heads/master", - "intValue":0, - "date":"2017-11-12T00:00:00.000Z" - }, - { - "name":"PartiallySuccessfulBuilds", - "scope":"refs/heads/master", - "intValue":0, - "date":"2017-11-12T00:00:00.000Z" - }, - { - "name":"SuccessfulBuilds", - "scope":"refs/heads/master", - "intValue":3, - "date":"2017-11-12T00:00:00.000Z" - }, - { - "name":"TotalBuilds", - "scope":"refs/heads/master", - "intValue":5, - "date":"2017-11-12T00:00:00.000Z" - } - ], - "_links":{ - "self":{ - "href":"https://nagroma.visualstudio.com/e58a4fcf-1085-4dc6-a88c-8d0a3dc5c49a/_apis/build/Definitions/4" - }, - "web":{ - "href":"https://nagroma.visualstudio.com/_permalink/_build/index?collectionId=d3e7f117-361e-4266-8c86-e766ad01a100&projectId=e58a4fcf-1085-4dc6-a88c-8d0a3dc5c49a&definitionId=4" - }, - "editor":{ - "href":"https://nagroma.visualstudio.com/_permalink/_build/definitionEditor?collectionId=d3e7f117-361e-4266-8c86-e766ad01a100&projectId=e58a4fcf-1085-4dc6-a88c-8d0a3dc5c49a&definitionId=4" - } - }, - "buildNumberFormat":"$(date:yyyyMMdd)$(rev:.r)", - "jobAuthorizationScope":1, - "jobTimeoutInMinutes":60, - "jobCancelTimeoutInMinutes":5, - "process":{ - "phases":[ - { - "dependencies":[ - - ], - "steps":[ - { - "environment":{ - - }, - "enabled":true, - "continueOnError":false, - "alwaysRun":false, - "displayName":"Use NuGet 4.3.0", - "timeoutInMinutes":0, - "refName":"NuGetToolInstaller1", - "task":{ - "id":"2c65196a-54fd-4a02-9be8-d9d1837b7c5d", - "versionSpec":"0.*", - "definitionType":"task" - }, - "inputs":{ - "versionSpec":"4.3.0", - "checkLatest":"false" - } - }, - { - "environment":{ - - }, - "enabled":true, - "continueOnError":false, - "alwaysRun":false, - "displayName":"NuGet restore", - "timeoutInMinutes":0, - "refName":"NuGetCommand2", - "task":{ - "id":"333b11bd-d341-40d9-afcf-b32d5ce6f23b", - "versionSpec":"2.*", - "definitionType":"task" - }, - "inputs":{ - "command":"restore", - "solution":"$(Parameters.solution)", - "selectOrConfig":"select", - "feedRestore":"", - "includeNuGetOrg":"true", - "nugetConfigPath":"", - "externalEndpoints":"", - "noCache":"false", - "packagesDirectory":"", - "verbosityRestore":"Detailed", - "searchPatternPush":"$(Build.ArtifactStagingDirectory)/*.nupkg", - "nuGetFeedType":"internal", - "feedPublish":"", - "allowPackageConflicts":"false", - "externalEndpoint":"", - "verbosityPush":"Detailed", - "searchPatternPack":"**/*.csproj", - "configurationToPack":"$(BuildConfiguration)", - "outputDir":"$(Build.ArtifactStagingDirectory)", - "versioningScheme":"off", - "includeReferencedProjects":"false", - "versionEnvVar":"", - "requestedMajorVersion":"1", - "requestedMinorVersion":"0", - "requestedPatchVersion":"0", - "packTimezone":"utc", - "includeSymbols":"false", - "buildProperties":"", - "verbosityPack":"Detailed", - "arguments":"" - } - }, - { - "environment":{ - - }, - "enabled":true, - "continueOnError":false, - "alwaysRun":false, - "displayName":"Build solution", - "timeoutInMinutes":0, - "refName":"VSBuild3", - "task":{ - "id":"71a9a2d3-a98a-4caa-96ab-affca411ecda", - "versionSpec":"1.*", - "definitionType":"task" - }, - "inputs":{ - "solution":"$(Parameters.solution)", - "vsVersion":"latest", - "msbuildArgs":"/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation=\"$(build.artifactstagingdirectory)\\\\\"", - "platform":"$(BuildPlatform)", - "configuration":"$(BuildConfiguration)", - "clean":"false", - "maximumCpuCount":"false", - "restoreNugetPackages":"false", - "msbuildArchitecture":"x86", - "logProjectEvents":"true", - "createLogFile":"false" - } - }, - { - "environment":{ - - }, - "enabled":true, - "continueOnError":false, - "alwaysRun":false, - "displayName":"Test Assemblies", - "timeoutInMinutes":0, - "refName":"VSTest4", - "task":{ - "id":"ef087383-ee5e-42c7-9a53-ab56c98420f9", - "versionSpec":"2.*", - "definitionType":"task" - }, - "inputs":{ - "testSelector":"testAssemblies", - "testAssemblyVer2":"**\\$(BuildConfiguration)\\*test*.dll\n!**\\obj\\**", - "testPlan":"", - "testSuite":"", - "testConfiguration":"", - "tcmTestRun":"$(test.RunId)", - "searchFolder":"$(System.DefaultWorkingDirectory)", - "testFiltercriteria":"", - "runOnlyImpactedTests":"False", - "runAllTestsAfterXBuilds":"50", - "uiTests":"false", - "vstestLocationMethod":"version", - "vsTestVersion":"latest", - "vstestLocation":"", - "runSettingsFile":"", - "overrideTestrunParameters":"", - "pathtoCustomTestAdapters":"", - "runInParallel":"False", - "runTestsInIsolation":"False", - "codeCoverageEnabled":"False", - "otherConsoleOptions":"", - "distributionBatchType":"basedOnTestCases", - "batchingBasedOnAgentsOption":"autoBatchSize", - "customBatchSizeValue":"10", - "batchingBasedOnExecutionTimeOption":"autoBatchSize", - "customRunTimePerBatchValue":"60", - "dontDistribute":"False", - "testRunTitle":"", - "platform":"$(BuildPlatform)", - "configuration":"$(BuildConfiguration)", - "publishRunAttachments":"true" - } - }, - { - "environment":{ - - }, - "enabled":true, - "continueOnError":true, - "alwaysRun":false, - "displayName":"Publish symbols path", - "timeoutInMinutes":0, - "refName":"PublishSymbols5", - "task":{ - "id":"0675668a-7bba-4ccb-901d-5ad6554ca653", - "versionSpec":"1.*", - "definitionType":"task" - }, - "inputs":{ - "SymbolsPath":"", - "SearchPattern":"**\\bin\\**\\*.pdb", - "SymbolsFolder":"", - "SkipIndexing":"false", - "TreatNotIndexedAsWarning":"false", - "SymbolsMaximumWaitTime":"", - "SymbolsProduct":"", - "SymbolsVersion":"", - "SymbolsArtifactName":"Symbols_$(BuildConfiguration)" - } - }, - { - "environment":{ - - }, - "enabled":true, - "continueOnError":false, - "alwaysRun":false, - "displayName":"Publish Artifact", - "timeoutInMinutes":0, - "refName":"PublishBuildArtifacts6", - "task":{ - "id":"2ff763a7-ce83-4e1f-bc89-0ae63477cebe", - "versionSpec":"1.*", - "definitionType":"task" - }, - "inputs":{ - "PathtoPublish":"$(build.artifactstagingdirectory)", - "ArtifactName":"$(Parameters.ArtifactName)", - "ArtifactType":"Container", - "TargetPath":"\\\\my\\share\\$(Build.DefinitionName)\\$(Build.BuildNumber)", - "Parallel":"false", - "ParallelCount":"8" - } - } - ], - "variables":{ - - }, - "name":"Phase 1", - "target":{ - "demands":[ - - ], - "executionOptions":{ - "type":0 - }, - "allowScriptsAuthAccessOption":false, - "type":1 - }, - "jobAuthorizationScope":"projectCollection", - "jobCancelTimeoutInMinutes":1 - } - ], - "type":1 - }, - "repository":{ - "properties":{ - "cleanOptions":"0", - "labelSources":"0", - "labelSourcesFormat":"$(build.buildNumber)", - "reportBuildStatus":"true", - "gitLfsSupport":"false", - "skipSyncSource":"false", - "checkoutNestedSubmodules":"false", - "fetchDepth":"0" - }, - "id":"b01b3ac1-5cff-4fbd-8f94-73fcad882df9", - "type":"TfsGit", - "name":"DevOpsHOL", - "url":"https://nagroma.visualstudio.com/_git/DevOpsHOL", - "defaultBranch":"refs/heads/master", - "clean":"false", - "checkoutSubmodules":false - }, - "processParameters":{ - "inputs":[ - { - "aliases":[ - - ], - "options":{ - - }, - "properties":{ - - }, - "name":"solution", - "label":"Path to solution or packages.config", - "defaultValue":"**\\*.sln", - "required":true, - "type":"filePath", - "helpMarkDown":"The path to the Visual Studio solution file or NuGet packages.config", - "visibleRule":"", - "groupName":"" - }, - { - "aliases":[ - - ], - "options":{ - - }, - "properties":{ - - }, - "name":"ArtifactName", - "label":"Artifact Name", - "defaultValue":"drop", - "required":true, - "type":"string", - "helpMarkDown":"The name of the artifact to create.", - "visibleRule":"", - "groupName":"" - } - ] - }, - "quality":1, - "authoredBy":{ - "id":"652d04a6-da7e-4ce3-b365-8cfed2574a7d", - "displayName":"Andrew Morgan", - "uniqueName":"nagroma@hotmail.com", - "url":"https://app.vssps.visualstudio.com/A94cbef75-cc36-4962-a500-fe681327dee5/_apis/Identities/652d04a6-da7e-4ce3-b365-8cfed2574a7d", - "imageUrl":"https://nagroma.visualstudio.com/_api/_common/identityImage?id=652d04a6-da7e-4ce3-b365-8cfed2574a7d&t=00000000-0000-0000-5056-df8f442cd508" - }, - "drafts":[ - - ], - "queue":{ - "id":20, - "name":"Hosted VS2017", - "pool":{ - "id":4, - "name":"Hosted VS2017", - "isHosted":true - } - }, - "id":4, - "name":"DevOpsHOL-CI", - "url":"https://nagroma.visualstudio.com/e58a4fcf-1085-4dc6-a88c-8d0a3dc5c49a/_apis/build/Definitions/4", - "uri":"vstfs:///Build/Definition/4", - "path":"\\", - "type":2, - "queueStatus":0, - "revision":4, - "createdDate":"2017-11-15T15:13:09.657Z", - "project":{ - "id":"e58a4fcf-1085-4dc6-a88c-8d0a3dc5c49a", - "name":"DevOpsHOL", - "url":"https://nagroma.visualstudio.com/_apis/projects/e58a4fcf-1085-4dc6-a88c-8d0a3dc5c49a", - "state":"wellFormed", - "revision":48, - "visibility":0 - } -} \ No newline at end of file diff --git a/source/vsts/README.md b/source/vsts/README.md deleted file mode 100644 index 5a77bcf..0000000 --- a/source/vsts/README.md +++ /dev/null @@ -1 +0,0 @@ -These two files are the export from the VSTS build definiton (DevOpsHOL-CI.json) and release definition (). These can be imported into the VSTS project to configure the build and release and skip all the steps in the labs :). \ No newline at end of file diff --git a/terraform/README.md b/terraform/README.md new file mode 100644 index 0000000..209ee71 --- /dev/null +++ b/terraform/README.md @@ -0,0 +1,154 @@ +# Terraform + +This lab contains instructions to learn the basics of Terraform. + +This instructions are based on the following documentation +- [Getting started - Azure](https://learn.hashicorp.com/terraform?track=azure#azure) + +## Prerequisites +- PowerShell installed on local machine + +## Install Terraform +1. [Click here](https://www.terraform.io/downloads.html) to go to the download page of Terraform. Download here the Terraform package for your local machine. + +1. After the download is finished unzip the downloaded package in a directory of your choosing (Documents, Desktop, etc...). Don't unzip the package the program files, because this will give access rights issues. + +1. Modify Environment Variables to include the path of the Terraform directory. + 1. On your local machine go to `Control Panel -> System -> Advanced system settings -> Environment Variables` + 1. Select Variable `Path` and click on Edit + 1. Click on New and provide as input value the location path where the Terraform has been unzipped + +1. Verifying the installation + 1. Open a new PowerShell prompt and execute the command `terraform` + 1. This will trigger the help text of Terraform. When you see the help text Terraform has been correctly installed on your machine. + +1. Close the PowerShell prompt, you will need a new one to continue this lab. + +## Create configuration +1. In the File Explorer navigate to the installation directory of Terraform. Here you will create a new text file named `main.txt` + +1. Open `main.txt` and put the following text in the file": + ```PowerShell + # Configure the Azure provider + provider "azurerm" { + version = "~>1.32.0" + } + + # Create a new resource group + resource "azurerm_resource_group" "rg" { + name = "myTFResourceGroup" + location = "westeurope" + } + ``` + +1. Save and close the file `main.txt` + +1. Change the file extension from `.txt` to `.tf` + +## Build Infrastructure +1. Navigate with the File Explorer to the installation directory of Terraform. +1. In the File Explorer location path type `PowerShell` and press enter. + + ![alt text](../images/terraform-powershell.png "") + +1. A new PowerShell prompt will open with the Terraform directory as his current location path + +1. Execute the PowerShell command `Add-AzureAccount` to login in your Azure account inside the PowerShell prompt + +1. Initialize the Terraform configuration directory by executing the command `terraform init` + +1. Create an Terraform execution plan with the command `terraform plan` this will show all the changes Terraform will do when you apply the configuration. + +1. Now execute the Terraform configuration and actualy create the new Resource Group in the [Azure Portal](https://portal.azure.com) execute the command `terraform apply` + +1. Go to the [Azure Portal](https://portal.azure.com) and there you will see a new empty Resource Group with the name `myTFResourceGroup` + +## Change Infrastructure +1. Open the file `main.tf` (for example with Notepad, VS Code or Notepad++) + +1. Add tags for the Resource Group to the current configuration. The configuration should look like this: + ```PowerShell + resource "azurerm_resource_group" "rg" { + name = "myTFResourceGroup" + location = "westeurope" + + tags = { + environment = "TF sandbox" + } + } + ``` + +1. Save and close the file `main.tf` + +1. Open the PowerShell prompt again and login to Azure with the command `Add-AzureAccount`. This is not needed when you still use the PowerShell prompt of the exercise `Build Infrastructure` + +1. Create a Terraform plan and save it with the command `terraform plan -out=newplan` + +1. The PowerShell prompt will show the changes that Terraform will make when you will execute this plan + - ~ indicates that the object will be updated + - \+ indicates that the object will be created + - \- indicates that the object will be removed + +1. To execute the just created plan use the command `terraform apply "newplan"` + +1. To view the new values associated with the resource group execute the command `terraform show` + +## Destroy Infrastructure +1. You can easily destroy your whole created Infrastructure with the command `terraform destroy`. Execute this command to remove the created resource group + +1. In the [Azure Portal](https://portal.azure.com) you will see that the resource group `myTFResourceGroup` won't exists anymore + +## Dependencies +!! Todo: Explain dependencies + +## Variables +1. !! Todo: Explain terraform variables + +1. Create a terraform template + - The template must contain the variables `resource-prefix` and `location` + - The template must contain configuration for a Resource Group + - The configuration for a Resource Group can be found in the previous exercises + - The template must contain configuration for a Virtual Network + - Example of a Virtual Network configuration: + ```PowerShell + resource "azurerm_virtual_network" "vnet" { + name = "PrefixTFVnet" + address_space = ["10.0.0.0/16"] + location = "westeurope" + resource_group_name = "" + } + ``` + - The template must contain configuration for a Subnet + - Example of a Subnet configuration: + ```Powershell + resource "azurerm_subnet" "subnet" { + name = "PrefixTFSubnet" + resource_group_name = "" + virtual_network_name = "" + address_prefix = "10.0.1.0/24" + } + ``` + +1. Use an external variables.tf file which will contain the two variables `resource-prefix` and `location` + +1. Use an external terraform.tfvars file which will contain the two variables `resource-prefix` and `location` + +1. Use an output variable to display the resource group name + +## Modules +1. !! Todo: explain modules + +1. Modify the template `main.tf` so it will use modules to create a `Network` and a `Virtual Machine` + +## Stretch goals +1. Create a custom template (no modules) which will deploy a Virtual Machine. The template must contain the following parts: + - Resource Group + - Virtual Network + - Subnet + - Public IP + - Network Security Group and rule + - Network interface + - Virtual Machine + +1. Use a provisioner in your custom template for deploying a Virtual Machine + - !! Todo: explain provisioners diff --git a/trainer-prerequisites.md b/trainer-prerequisites.md new file mode 100644 index 0000000..0bb7110 --- /dev/null +++ b/trainer-prerequisites.md @@ -0,0 +1,17 @@ +# Avanade DevOps HOL - Trainer Prerequisites +This document describes the steps trainers should take, in order to be prepared for the training. + +## Prerequisites + +1. Set up your own Demo environment using the prerequisites lab. + +1. Setup SonarQube + - Use the Azure Marketplace template "SonarQube Certified by Bitnami" to set up the SonarQube environment for all the participants. + - Get the administrator password from the boot diagnostics of your SonarQube machine, so you will be able to login. + - Add the Url, Username and Password for SonarQube to your Practitioners 'preparations' email. + +1. Have the participants complete the prerequisites lab. + +1. Set up pairs of participants, where at least one of them has programming/C# knowledge. + +1. Prepare the [Demos](demos/README.md) up front \ No newline at end of file diff --git a/ui-testing/README.md b/ui-testing/README.md new file mode 100644 index 0000000..c9eab89 --- /dev/null +++ b/ui-testing/README.md @@ -0,0 +1,331 @@ +# UI Testing with Selenium and Azure DevOps + +This lab contains instructions to create automated functional UI tests. +The instructions are based on the following documentation: + +- [UI test with Selenium](https://docs.microsoft.com/azure/devops/pipelines/test/continuous-test-selenium) +- [dotnet vstest](https://docs.microsoft.com/dotnet/core/tools/dotnet-vstest) + +## Prerequisites + +- Complete lab: [Pipeline as code with K8s and Terraform](https://dev.azure.com/thx1139/_git/workshop1?path=%2FREADME.md) + +## Configure local UI Testing +1. Open the mywebapp.csproj (TRAIN/azdotraining1/app/mywebapp) in Visual Studio. + +1. Add a new project to the solution with the following settings: + - **Project Type:** xUnit Test Project (.NET Core) Visual C# + - **Name:** FunctionalTests + +1. Save the solution in Visual Studio in the app folder (./TRAIN/azdotraining1/app). + +1. In the **FunctionalTests** project, remove the sample test (UnitTest1.cs) if it exists + +1. On the **FunctionalTests** project, add the following NuGet packages: + - MSTest.TestFramework + - MSTest.TestAdapter + - Selenium.Support + - Selenium.WebDriver + - Selenium.WebDriver.ChromeDriver **Version: (79.0.3945.3600)** + +1. Ensure the Selenium Chrome driver executable is copied to the output during publish. Also make sure that the testproject is using .NET Core 2.2. To do this: double click on the **FunctionalTests** and the FunctionalTests.csproj will be opened: + - Add the following PropertyGroup: + ```xml + + true + + ``` + - Make sure that the project is using .NET Core 2.2 + ```xml + + netcoreapp2.2 + + ``` + + +1. In the **FunctionalTests** project, create the **functionalTests.runsettings** file. And replace the content with the following content: + +
functionalTests.runsettings (expand to view code) + + ```xml + + + + + + + ``` +
+ +1. In the **functionalTests.runsettings** file make sure that the port of the siteUrl is the same as the webapp is using. This port can be found in the **launSettings.json** *(mywebapp\Properties\launchSettings.json)* of the **mywebapp** project. + + +1. Configure Visual Studio to use the .runsettings file you just created. In Visual Studio go to *Test-->Configure Run Settings-->Select Solution Wide runsettings File*. In the explorer window select the **functionalTests.runsettings** file located in the folder *FunctionalTests*. + +1. In the **FunctionalTests** project add functional test classes for all pages. +Add the following classes to it: + +
HomePage.cs (expand to view code) + + ```csharp + using OpenQA.Selenium; + + class HomePage : BasePage + { + public HomePage(IWebDriver driver, string baseUrl) : base(driver, baseUrl) + { + } + + public string Title { get; set; } + + public void GoToPage() + { + Driver.Navigate().GoToUrl($"{BaseUrl}"); + } + } + ``` +
+ +
PrivacyPage.cs (expand to view code) + + ```csharp + using OpenQA.Selenium; + + class PrivacyPage : BasePage + { + public PrivacyPage(IWebDriver driver, string baseUrl) : base(driver, baseUrl) + { + } + + public void GoToPage() + { + Driver.Navigate().GoToUrl($"{BaseUrl}/Privacy"); + } + + } + ``` +
+ +
BasePage.cs (expand to view code) + + ```csharp + using OpenQA.Selenium; + + abstract class BasePage + { + protected readonly IWebDriver Driver; + protected readonly string BaseUrl; + + protected BasePage(IWebDriver driver, string baseUrl) + { + Driver = driver; + BaseUrl = baseUrl; + } + + public HomePage GoToHomePage() + { + var home = Driver.FindElement(By.LinkText("Home")); + home.Click(); + return new HomePage(Driver, BaseUrl); + } + + public PrivacyPage GoToPrivacyPage() + { + var about = Driver.FindElement(By.LinkText("Privacy")); + about.Click(); + return new PrivacyPage(Driver, BaseUrl); + } + } + ``` +
+ +1. In the **FunctionalTests** project, create the following test class for the functional UI tests: +
UITests.cs (expand to view code) + + ```csharp + using Microsoft.VisualStudio.TestTools.UnitTesting; + using OpenQA.Selenium; + using OpenQA.Selenium.Chrome; + using OpenQA.Selenium.Remote; + using System; + using System.Drawing; + using System.IO; + + namespace aspnet_core_dotnet_core.FunctionalTests + { + [TestClass] + public class UITests + { + private static TestContext _testContext; + private RemoteWebDriver _driver; + private string _siteUrl; + + [ClassInitialize] + public static void Initialize(TestContext testContext) + { + _testContext = testContext; + } + + [TestInitialize()] + public void MyTestInitialize() + { + if (_testContext.Properties["siteUrl"] != null) + { + _siteUrl = _testContext.Properties["siteUrl"].ToString(); + } + + // Chrome + var options = new ChromeOptions(); + options.AddArguments("headless"); + _driver = new ChromeDriver(Directory.GetCurrentDirectory(), options); + + // Driver settings + _driver.Manage().Window.Size = new Size(1920, 1080); + _driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(20); + _driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(20); + } + + [TestMethod] + [TestCategory("UI")] + public void Test() + { + try + { + var page = new HomePage(_driver, _siteUrl); + page.GoToPage(); + SaveAsImage(_driver.GetScreenshot(), "Home.png"); + page.GoToPrivacyPage(); + SaveAsImage(_driver.GetScreenshot(), "Privacy.png"); + var containerDiv = _driver.FindElement(By.ClassName("pb-3")); + var header = containerDiv.FindElement(By.TagName("h1")); + Assert.AreEqual("Privacy Policy", header.Text); + } + catch (NoSuchElementException) + { + SaveAsImage(_driver.GetScreenshot(), "Error.png"); + throw; + } + } + + [TestCleanup()] + public void MyTestCleanup() + { + _driver.Close(); + _driver.Quit(); + } + + private void SaveAsImage(Screenshot screenshot, string name) + { + var timestamp = DateTime.UtcNow.ToString("yyyyMMdd-HHmmss.fff"); + var fileName = $"{timestamp} {name}"; + screenshot.SaveAsFile(fileName, ScreenshotImageFormat.Png); + } + } + } + ``` +
+ +1. Start your website without debugging. This will ensure the website will be running in the local IIS Express instance, on the url specified in the .runsettings file. + +1. Run the UI tests you just created in the Test Explorer and make sure that it succeeds. + +1. When the tests are completed go in the File explorer to build folder of the FunctionalTests project *(C://TRAIN/azdotraining1/app/FunctionalTests/bin/Debug/netcoreapp2.2)*. Here you will some screenshots that were made during the test run, which can be used as Test evidence. + +## Configure automated UI Testing +1. Open the **app** pipeline *(Azure DevOps/Pipelines/Pipelines/app)* and press on **Edit**. + +1. The automated Selenium tests will run against the public ip address of our deployed Pods. To assign the right ip address to the pipeline we will use variables. The ip addressess can be found in the PowerShell window you used in lab 1. Add the next two variables: + 1. **Name:** testip **Value:** http://*\* + 1. **Name:** prodip **Value:** http://*\* + +**Dont forget to press on Save!!** + +1. First the build step of the Selenium project needs to be added to the **Build** stage by adding the following code above the job **Build_containers**. Make sure that the identation of the code is correct. + ``` + - job: Build_functional_tests + pool: + vmImage: 'windows-latest' + steps: + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk 2.2.301' + inputs: + packageType: 'sdk' + version: '2.2.301' + - task: DotNetCoreCLI@2 + displayName: 'Restore' + inputs: + command: 'restore' + projects: '**/FunctionalTests.csproj' + feedsToUse: 'select' + - task: DotNetCoreCLI@2 + displayName: 'Publish' + inputs: + command: publish + publishWebProjects: false + projects: '**/FunctionalTests.csproj' + arguments: '--configuration Release -o $(build.artifactstagingdirectory)/SeleniumTests' + zipAfterPublish: false + modifyOutputPath: false + - task: PublishPipelineArtifact@1 + displayName: 'Publish Pipeline Artifact' + inputs: + targetPath: '$(Build.ArtifactStagingDirectory)' + artifact: 'functionaltests' + publishLocation: 'pipeline' + ``` + +1. The next step is to add the automated Selenium tests to the Test deployment pipeline by adding the following code in stage **Release_Test** below the block **- deployment: Deploy_containers**. Make sure that the identation is correct. + ``` + - deployment: Run_functional_tests + dependsOn: "Deploy_containers" + environment: test + pool: + vmImage: 'windows-latest' + strategy: + runOnce: + deploy: + steps: + - task: VSTest@2 + displayName: Run UI Tests + inputs: + testSelector: 'testAssemblies' + testAssemblyVer2: | + **\*FunctionalTests.dll + !**\*TestAdapter.dll + !**\obj\** + searchFolder: '$(Pipeline.Workspace)/functionaltests/SeleniumTests' + overrideTestrunParameters: '-siteUrl "$(testip)"' + ``` + +1. The last step is to add the automated Selenium tests to the Production deployment pipeline by adding the following code in stage **Release_Prod** below the block **- deployment: Deploy_containers**. Make sure that the identation is correct. + ``` + - deployment: Run_functional_tests + dependsOn: "Deploy_containers" + environment: prod + pool: + vmImage: 'windows-latest' + strategy: + runOnce: + deploy: + steps: + - task: VSTest@2 + displayName: Run UI Tests + inputs: + testSelector: 'testAssemblies' + testAssemblyVer2: | + **\*FunctionalTests.dll + !**\*TestAdapter.dll + !**\obj\** + searchFolder: '$(Pipeline.Workspace)/functionaltests/SeleniumTests' + overrideTestrunParameters: '-siteUrl "$(prodip)"' + ``` + +1. Save your pipeline but don't run it yet. Commit the code changes you made in Visual Studio and push those. This will trigger the **app** pipeline. The Selenium will be automatically executed on the Test and Production environment. After the pipeline is completed you can find the test results in the tab **Tests**. + +## Stretch goals + +1. Introduce a failing test and verify that the deployment stops with the failed test. +1. Upload the Test screenshots to Azure DevOps. https://stackoverflow.com/questions/52823650/selenium-screenshots-in-vsts-azure-devops + +## Next steps +Return to the [Lab index](../README.md) and continue with the next lab \ No newline at end of file