This commit is contained in:
Victor Viriya-ampanond 2021-02-24 10:35:38 +13:00
Родитель 5e9276cba5
Коммит 78c05a7c7d
69 изменённых файлов: 41040 добавлений и 8 удалений

9
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,9 @@
**/*copy.*
**/bin
**/obj
**/publish
**/.vscode
**/appsettings.*.json
**/*.azcli
**/demo
**script.**

Просмотреть файл

@ -1,14 +1,91 @@
# Project
---
services: ARM and Azure Pipeline
author: wviriya
level: 300
---
> This repo has been populated by an initial template to help get you started. Please
> make sure to update the content to build a great experience for community-building.
# FastTrack for Azure Live - How to deploy Azure services and infrastructure. Leverage Azure Pipeline for CI/CD.
As the maintainer of this project, please make a few updates:
## Synopsis:
"Zero to Hero with ARM and IaC" How to create an ARM Template using Bicep and deploy using Azure Pipelines.
- Improving this README.MD file to provide a great experience
- Updating SUPPORT.MD with content about this project's support experience
- Understanding the security reporting process in SECURITY.MD
- Remove this section from the README
## Who should attend:
- I want to do Infrastructure as Code but don't know where to start.
- My team keeps asking me to deploy infrastructure and various environments to Azure, and I do it manually.
- I want to automate my infrastructure.
## At the end of the session you should:
- Understand the basic structure of an ARM Template
- Be aware of the tools available to create and manage ARM Templates
- Understand basic how to create and use simple Azure Pipelines
## What are prerequisites:
- Azure Subscription
- Azure DevOps account
- JSON (JavaScript Object Notation)
- YAML Syntax
- Git repository
- Desire to learn
## Where are your team
![Automation Maturity](contents/automation_maturity.png)
## About this sample
This sample is a guide for learning basic ARM Template usage. The links in this document can you help better understand these services and tools.
## [What is Infrastructure as Code?](https://docs.microsoft.com/en-us/azure/devops/learn/what-is-infrastructure-as-code)
## [Why I should adopt Infrastructure as Code Practice? (Read John Downs' Blog)](https://techcommunity.microsoft.com/t5/fasttrack-for-azure/the-benefits-of-infrastructure-as-code/ba-p/2069350)
## Azure Resource Manager (ARM) Template
A native infra-as-code solution for managing Azure resources using a declarative programming paradigm.
### Concepts
- [What is ARM?](https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/overview)
- [What are ARM Templates?](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/overview)
- [Bicep (Experimental)](https://github.com/azure/bicep)
- [Nested and Linked templates](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/linked-templates)
- [Deployment modes](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/deployment-modes)
- [Best practices](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-best-practices)
### Authoring tools and helps
- [ARM QuickStart Templates](https://azure.microsoft.com/en-us/resources/templates/)
- [Azure Portal](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/quickstart-create-templates-use-the-portal)
- [VS Code](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/quickstart-create-templates-use-visual-studio-code?tabs=CLI)
- [Visual Studio](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/create-visual-studio-deployment-project)
- [Template structure and syntax](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-syntax)
- [Template references](https://docs.microsoft.com/en-us/azure/templates/)
- [ARM template functions](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-functions)
- [ARM Template Toolkit for analyzing and testing](https://github.com/Azure/arm-ttk)
### CI/CD
- [Deploy with Azure Pipelines](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/add-template-to-azure-pipelines)
- [Deploy with GitHub Actions](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/deploy-github-actions)
### Learning resources
- [Microsoft Learn path on ARM template](https://docs.microsoft.com/en-us/learn/paths/deploy-manage-resource-manager-templates)
### New and preview features
- [Deployment scripts (GA - DEC 2020)](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-tutorial-deployment-script)
- [What-If deployment (GA - DEC 2020)](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-deploy-what-if?tabs=azure-powershell)
- [Template Specs (Preview)](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-specs?tabs=azure-powershell)
- [Azure Deployment Manager (Preview)](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-specs?tabs=azure-powershell)
## Other orchestrators
### Terraform with Azure
- [Overview](https://docs.microsoft.com/en-us/azure/developer/terraform/overview)
- [Terraform QuickStart](https://docs.microsoft.com/en-us/azure/developer/terraform/install-configure)
- [Terraform Configuration Language](https://www.terraform.io/docs/configuration/syntax.html)
- [Store Terraform state in Azure Storage](https://docs.microsoft.com/en-us/azure/developer/terraform/store-state-in-azure-storage)
- [Automating infrastructure deployments in the Cloud with Terraform and Azure Pipelines](https://www.azuredevopslabs.com/labs/vstsextend/terraform/)
### Ansible wiht Azure
- [Overview](https://docs.microsoft.com/en-us/azure/developer/ansible/overview)
- [Ansible QuickStart](https://docs.microsoft.com/en-us/azure/developer/ansible/install-on-linux-vm)
- [Ansible Playbooks](https://docs.ansible.com/ansible/latest/user_guide/playbooks_intro.html)
- [Automating infrastructure deployments in the Cloud with Ansible and Azure Pipelines](https://www.azuredevopslabs.com/labs/vstsextend/ansible/)
## [FAQ](./faq.md)
## Contributing

Просмотреть файл

@ -0,0 +1,269 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"name": {
"type": "string",
"metadata": {
"description": "Name of the application. Will be used by default as resource name prefix."
}
},
"environmentName": {
"type": "string",
"defaultValue": "Dev",
"allowedValues": [
"Dev",
"Test",
"QA",
"Stage",
"Beta",
"Prod",
"dev",
"test",
"qa",
"stage",
"beta",
"prod"
]
},
"runtime": {
"type": "string",
"defaultValue": "dotnet",
"allowedValues": [
"node",
"dotnet",
"java"
],
"metadata": {
"description": "The language worker runtime to load in the function app."
}
},
"identity": {
"type": "string",
"metadata": {
"description": "MSI to execute AZ CLI"
}
}
},
"variables": {
"appNameWithEnv": "[toLower(concat(parameters('name'),'-',parameters('environmentName')))]",
"hostingPlanName": "[concat(variables('appNameWithEnv'),'-asp')]",
"functionAppName": "[variables('appNameWithEnv')]",
"appInsightsName": "[concat(variables('appNameWithEnv'),'-ai')]",
"storageAccountName": "[toLower(replace(concat(variables('functionAppName'),'app'),'-',''))]",
"location": "[resourceGroup().location]"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2019-06-01",
"name": "[variables('storageAccountName')]",
"location": "[variables('location')]",
"kind": "StorageV2",
"sku": {
"name": "Standard_LRS",
"tier": "Standard"
},
"properties": {
"accessTier": "Hot"
}
},
{
"type": "Microsoft.Resources/deploymentScripts",
"apiVersion": "2019-10-01-preview",
"name": "configStaticWeb",
"location": "[variables('location')]",
"dependsOn": [
"[variables('storageAccountName')]"
],
"kind": "AzureCLI",
"identity": {
"type": "UserAssigned",
"userAssignedIdentities": {
"[parameters('identity')]": {}
}
},
"properties": {
"AzCliVersion": "2.0.80",
"timeout": "PT30M",
"arguments": "[variables('storageAccountName')]",
"scriptContent": "az storage blob service-properties update --account-name $1 --static-website --404-document notfound.html --index-document index.html",
"cleanupPreference": "Always",
"retentionInterval": "P1D"
}
},
{
"type": "Microsoft.Resources/deploymentScripts",
"apiVersion": "2019-10-01-preview",
"name": "getStaticWebURL",
"dependsOn": [
"configStaticWeb"
],
"location": "[variables('location')]",
"kind": "AzurePowerShell",
"identity": {
"type": "userAssigned",
"userAssignedIdentities": {
"[parameters('identity')]": {
}
}
},
"properties": {
"azPowerShellVersion": "3.0",
"timeout": "PT30M",
"arguments": "[concat('-storageAccount ',variables('storageAccountName'),' -resourceGroup ',resourceGroup().name)]",
"scriptContent": "
param([string] $storageAccount, [string] $resourceGroup)
$storage = Get-AzStorageAccount -ResourceGroupName $resourceGroup -Name $storageAccount
$output = $storage.PrimaryEndpoints.Web
$output = $output.TrimEnd('/')
Write-Output $output
$DeploymentScriptOutputs = @{}
$DeploymentScriptOutputs['text'] = $output
",
"cleanupPreference": "Always",
"retentionInterval": "P1D"
}
},
{
"apiVersion": "2020-06-01",
"name": "[variables('hostingPlanName')]",
"type": "Microsoft.Web/serverfarms",
"location": "[variables('location')]",
"tags": {
"displayName": "HostingPlan"
},
"sku": {
"name": "Y1",
"tier": "Dynamic"
},
"properties": {
"name": "[variables('hostingPlanName')]",
"computeMode": "Dynamic"
}
},
{
"type": "Microsoft.Web/sites",
"apiVersion": "2020-06-01",
"name": "[variables('functionAppName')]",
"location": "[variables('location')]",
"kind": "functionapp",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
"[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]",
"[resourceId('Microsoft.Insights/components', variables('appInsightsName'))]",
"getStaticWebURL"
],
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
"siteConfig": {
"appSettings": [
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
},
{
"name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
},
{
"name": "WEBSITE_CONTENTSHARE",
"value": "[toLower(variables('functionAppName'))]"
},
{
"name": "FUNCTIONS_EXTENSION_VERSION",
"value": "~2"
},
{
"name": "WEBSITE_NODE_DEFAULT_VERSION",
"value": "~10"
},
{
"name": "APPINSIGHTS_INSTRUMENTATIONKEY",
"value": "[reference(resourceId('microsoft.insights/components', variables('appInsightsName')), '2020-02-02-preview').InstrumentationKey]"
},
{
"name": "FUNCTIONS_WORKER_RUNTIME",
"value": "[parameters('runtime')]"
}
],
"cors": {
"allowedOrigins": [
"[reference('getStaticWebURL').outputs.text]"
]
}
// ,
// "ipSecurityRestrictions": [
// {
// "ipAddress": "119.224.22.180/32",
// "action": "Allow",
// "tag": "Default",
// "priority": 100,
// "name": "client",
// "description": "client"
// },
// {
// "ipAddress": "Any",
// "action": "Deny",
// "priority": 2147483647,
// "name": "Deny all",
// "description": "Deny all access"
// }
// ]
}
}
},
{
"type": "Microsoft.Resources/deploymentScripts",
"apiVersion": "2019-10-01-preview",
"name": "configureAuthen",
"location": "[variables('location')]",
"dependsOn": [
"[variables('storageAccountName')]"
],
"kind": "AzureCLI",
"identity": {
"type": "UserAssigned",
"userAssignedIdentities": {
"[parameters('identity')]": {}
}
},
"properties": {
"AzCliVersion": "2.0.80",
"timeout": "PT30M",
"arguments": "[concat(variables('storageAccountName'),' ',variables('functionAppName'),' ',reference('getStaticWebURL').outputs.text,' ',subscription().subscriptionId,' ',resourceGroup().name)]",
"primaryScriptUri": "https://raw.githubusercontent.com/wviriya/Zero-to-Hero-with-ARM-and-IaC/master/scripts/app_registration.sh",
"cleanupPreference": "Always",
"retentionInterval": "P1D"
}
},
{
"type": "Microsoft.Insights/components",
"apiVersion": "2020-02-02-preview",
"name": "[variables('appInsightsName')]",
"location": "[variables('location')]",
"tags": {
"[concat('hidden-link:', resourceId('Microsoft.Web/sites', variables('appInsightsName')))]": "Resource"
},
"properties": {
"ApplicationId": "[variables('appInsightsName')]",
"Request_Source": "IbizaWebAppExtensionCreate"
}
}
],
"outputs": {
"appName": {
"type": "string",
"value": "[variables('functionAppName')]"
},
"appFqdn": {
"type": "string",
"value": "[reference(concat('Microsoft.Web/sites/', variables('functionAppName'))).hostnames[0]]"
},
"staticWebURL": {
"type": "string",
"value": "[reference('getStaticWebURL').outputs.text]"
}
}
}

Просмотреть файл

@ -0,0 +1,12 @@
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"name": {
"value": "__webappname__"
},
"identity": {
"value": "/subscriptions/__subscription__/resourceGroups/__resourceGroup__/providers/Microsoft.ManagedIdentity/userAssignedIdentities/__msi__"
}
}
}

Просмотреть файл

@ -0,0 +1,145 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"name": {
"type": "string",
"metadata": {
"description": "VM Name"
}
},
"size": {
"type": "string",
"defaultValue": "Standard_B1ms",
"metadata": {
"description": "VM size"
}
},
"username": {
"type": "string",
"metadata": {
"description": "VM user name"
}
},
"password": {
"type": "securestring",
"metadata": {
"description": "VM password"
}
},
"storage": {
"type": "string",
"metadata": {
"description": "Storage Account Name"
}
},
"vnet": {
"type": "string",
"metadata": {
"description": "VNet Name"
}
},
"templateBaseUrl": {
"type": "string",
"defaultValue": "https://raw.githubusercontent.com/wviriya/Zero-to-Hero-with-ARM-and-IaC/master/arm_linked_templates/",
"metadata": {
"description": "he base URI where artifacts required by this template are located. When the template is deployed using the accompanying scripts, a private location in the subscription will be used and this value will be automatically generated."
}
}
},
"functions": [],
"variables": {},
"resources": [
{
"name": "network",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2019-10-01",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri":"[uri(parameters('templateBaseUrl'),'network-linked-template.json')]",
"contentVersion":"1.0.0.0"
},
"parameters": {
"vnet":{"value": "[parameters('vnet')]"}
}
}
},
{
"name": "frontendvms",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2019-10-01",
"dependsOn": [
"network"
],
"properties": {
"mode": "Incremental",
"templateLink": {
"uri":"[uri(parameters('templateBaseUrl'),'vm-linked-template.json')]",
"contentVersion":"1.0.0.0"
},
"parameters": {
"vm":{"value": "[concat('web-',parameters('name'))]"},
"size":{"value": "[parameters('size')]"},
"instance":{"value": 1},
"username":{"value": "[parameters('username')]"},
"password":{"value": "[parameters('password')]"},
"storage":{"value": "[parameters('storage')]"},
"vnet":{"value": "[parameters('vnet')]"},
"subnet":{"value": "frontend"}
}
}
},
{
"name": "appvms",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2019-10-01",
"dependsOn": [
"network"
],
"properties": {
"mode": "Incremental",
"templateLink": {
"uri":"[uri(parameters('templateBaseUrl'),'vm-linked-template.json')]",
"contentVersion":"1.0.0.0"
},
"parameters": {
"vm":{"value": "[concat('app-',parameters('name'))]"},
"size":{"value": "[parameters('size')]"},
"instance":{"value": 1},
"username":{"value": "[parameters('username')]"},
"password":{"value": "[parameters('password')]"},
"storage":{"value": "[parameters('storage')]"},
"vnet":{"value": "[parameters('vnet')]"},
"subnet":{"value": "app"}
}
}
},
{
"name": "datavms",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2019-10-01",
"dependsOn": [
"network"
],
"properties": {
"mode": "Incremental",
"templateLink": {
"uri":"[uri(parameters('templateBaseUrl'),'vm-linked-template.json')]",
"contentVersion":"1.0.0.0"
},
"parameters": {
"vm":{"value": "[concat('db',parameters('name'))]"},
"size":{"value": "[parameters('size')]"},
"instance":{"value": 1},
"username":{"value": "[parameters('username')]"},
"password":{"value": "[parameters('password')]"},
"storage":{"value": "[parameters('storage')]"},
"vnet":{"value": "[parameters('vnet')]"},
"subnet":{"value": "data"}
}
}
}
],
"outputs": {}
}

Просмотреть файл

@ -0,0 +1,32 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"name": {
"value": "azninja-vm" // TODO: Fill in parameter value
},
"size": {
"value": "Standard_B1ms"
},
"username": {
"value": "azninja" // TODO: Fill in parameter value
},
"password": {
"reference": {
"keyVault": {
"id": "/subscriptions/c1a37f4e-c839-41b3-a627-5ea61b973cd1/resourceGroups/azninja-rg/providers/Microsoft.KeyVault/vaults/azninja-kv"
},
"secretName": "vmPassword"
}
},
"storage": {
"value": "azninja" // TODO: Fill in parameter value
},
"vnet": {
"value": "azninja-vms-vnet" // TODO: Fill in parameter value
},
"templateBaseUrl": {
"value": "https://raw.githubusercontent.com/wviriya/Zero-to-Hero-with-ARM-and-IaC/master/arm_templates/"
}
}
}

Просмотреть файл

@ -0,0 +1,105 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"vnet": {
"type": "string",
"metadata": {
"description": "VNet Name"
}
},
"addressSpace": {
"type": "string",
"defaultValue": "10.0.0.0/16",
"metadata": {
"description": "VNet Address Space"
}
},
"subnetAddresses": {
"type": "array",
"defaultValue": ["10.0.0.0/24","10.0.1.0/24","10.0.2.0/24"],
"metadata": {
"description": "Array of Subnet"
}
},
"subnetNames": {
"type": "array",
"defaultValue": ["frontend","app","data"],
"metadata": {
"description": "Array of Subnet"
}
},
"nsg": {
"type": "string",
"defaultValue": "default-nsg",
"metadata": {
"description": "NSG Rules"
}
}
},
"functions": [],
"variables": {},
"resources": [
{
"name": "[parameters('nsg')]",
"type": "Microsoft.Network/networkSecurityGroups",
"apiVersion": "2018-08-01",
"location": "[resourceGroup().location]",
"properties": {
"securityRules": [
{
"name": "rdp",
"properties": {
"description": "description",
"protocol": "Tcp",
"sourcePortRange": "*",
"destinationPortRange": "3389",
"sourceAddressPrefix": "*",
"destinationAddressPrefix": "*",
"access": "Allow",
"priority": 100,
"direction": "Inbound"
}
}
]
}
},
{
"name": "[parameters('vnet')]",
"type": "Microsoft.Network/virtualNetworks",
"apiVersion": "2019-11-01",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "[parameters('vnet')]"
},
"properties": {
"addressSpace": {
"addressPrefixes": [
"[parameters('addressSpace')]"
]
}
}
},
{
"name": "[concat(parameters('vnet'),'/',parameters('subnetNames')[copyIndex()])]",
"type": "Microsoft.Network/virtualNetworks/subnets",
"apiVersion": "2019-11-01",
"dependsOn": [
"[resourceId('Microsoft.Network/virtualNetworks',parameters('vnet'))]",
"[resourceId('Microsoft.Network/networkSecurityGroups', parameters('nsg'))]"
],
"properties": {
"addressPrefix": "[parameters('subnetAddresses')[copyIndex()]]",
"networkSecurityGroup": {
"id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('nsg'))]"
}
},
"copy": {
"name": "subnetcopy",
"mode": "Serial",
"count": "[length(parameters('subnetAddresses'))]"
}
}
],
"outputs": {}
}

Просмотреть файл

@ -0,0 +1,183 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"vm": {
"type": "string",
"metadata": {
"description": "VM Name"
}
},
"size": {
"type": "string",
"defaultValue": "Standard_B1ms",
"metadata": {
"description": "VM size"
}
},
"instance": {
"type": "int",
"defaultValue": 1,
"metadata": {
"description": "Number of VM"
}
},
"username": {
"type": "string",
"metadata": {
"description": "VM user name"
}
},
"password": {
"type": "securestring",
"metadata": {
"description": "VM password"
}
},
"storage": {
"type": "string",
"metadata": {
"description": "Storage Account Name"
}
},
"vnet": {
"type": "string",
"metadata": {
"description": "VNet Name"
}
},
"subnet": {
"type": "string",
"metadata": {
"description": "Subnet name to put put VM"
}
}
},
"functions": [],
"variables": {
"storage": "[toLower(concat(parameters('storage'),'vmdiagblobstorage'))]",
"nic": "[concat(parameters('vm'),'-nic')]",
"pip": "[concat(parameters('vm'),'-pip')]"
},
"resources": [
{
"name": "[variables('storage')]",
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2019-06-01",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "[concat(variables('storage'),' storage account')]"
},
"sku": {
"name": "Standard_LRS"
},
"kind": "Storage"
},
{
"name": "[concat(variables('pip'),copyIndex())]",
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "2019-11-01",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "PublicIPAddress"
},
"properties": {
"publicIPAllocationMethod": "Dynamic",
"dnsSettings": {
"domainNameLabel": "[toLower(concat(parameters('vm'),copyIndex()))]"
}
},
"copy": {
"name": "pipcopy",
"count": "[parameters('instance')]"
}
},
{
"name": "[concat(variables('nic'),copyIndex())]",
"type": "Microsoft.Network/networkInterfaces",
"apiVersion": "2019-11-01",
"location": "[resourceGroup().location]",
"dependsOn": [
"[resourceId('Microsoft.Network/publicIPAddresses', concat(variables('pip'),copyIndex()))]"
],
"tags": {
"displayName": "[concat(variables('nic'),copyIndex(),' Network Interface')]"
},
"properties": {
"ipConfigurations": [
{
"name": "ipConfig1",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', concat(variables('pip'),copyIndex()))]"
},
"subnet": {
"id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnet'), parameters('subnet'))]"
}
}
}
]
},
"copy": {
"name": "niccopy",
"count": "[parameters('instance')]"
}
},
{
"name": "[concat(parameters('vm'),copyIndex())]",
"type": "Microsoft.Compute/virtualMachines",
"apiVersion": "2019-07-01",
"location": "[resourceGroup().location]",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts', variables('storage'))]",
"[resourceId('Microsoft.Network/networkInterfaces', concat(variables('nic'),copyIndex()))]"
],
"tags": {
"displayName": "[concat(parameters('vm'),copyIndex())]"
},
"properties": {
"hardwareProfile": {
"vmSize": "[parameters('size')]"
},
"osProfile": {
"computerName": "[concat(parameters('vm'),copyIndex())]",
"adminUsername": "[parameters('username')]",
"adminPassword": "[parameters('password')]"
},
"storageProfile": {
"imageReference": {
"publisher": "MicrosoftWindowsServer",
"offer": "WindowsServer",
"sku": "2012-R2-Datacenter",
"version": "latest"
},
"osDisk": {
"name": "[concat(parameters('vm'),copyIndex(),'osdisk')]",
"caching": "ReadWrite",
"createOption": "FromImage"
}
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', concat(variables('nic'),copyIndex()))]"
}
]
},
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": true,
"storageUri": "[reference(resourceId('Microsoft.Storage/storageAccounts/', variables('storage'))).primaryEndpoints.blob]"
}
}
},
"copy": {
"name": "vmcopy",
"count": "[parameters('instance')]",
"mode": "Serial"
}
}
],
"outputs": {}
}

Просмотреть файл

@ -0,0 +1,138 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"name": {
"type": "string",
"metadata": {
"description": "Web App Name"
}
},
"sku": {
"type": "string",
"defaultValue": "S1",
"allowedValues": [
"B1", "B2", "B3", "D1", "F1", "FREE", "I1", "I2", "I3", "P1V2", "P2V2", "P3V2", "PC2", "PC3", "PC4", "S1", "S2", "S3", "SHARED"
],
"metadata": {
"description": "App Service Plan SKU/Size"
}
},
"os": {
"type": "string",
"defaultValue": "Windows",
"allowedValues": [
"Windows","Linux"
],
"metadata": {
"description": "App Service Plan SKU/Size"
}
},
"instanceCount": {
"type": "int",
"defaultValue": 1,
"metadata": {
"description": "Number of running App Service Plan instance"
}
},
"slots": {
"type": "array",
"defaultValue":
[
{
"name": "production",
"connectionString": "database connection string",
"connectionType": "Custom" // allowed values : MySql, SQLServer, SQLAzure, Custom, NotificationHub, ServiceBus, EventHub, ApiHub, DocDb, RedisCache, PostgreSQL
}
],
"metadata": {
"description": "Number of running App Service Plan instance"
}
},
"region": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Region to deploy resources"
}
}
},
"functions": [],
"variables": {
"appServicePlanName": "[concat(parameters('name'),'-asp')]",
"os": "[if(equals(toLower(parameters('os')),'linux'),'linux','app')]"
},
"resources": [
{
"name": "[variables('appServicePlanName')]",
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2019-08-01",
"kind": "[variables('os')]",
"location": "[parameters('region')]",
"sku": {
"name": "[parameters('sku')]",
"capacity": "[parameters('instanceCount')]"
},
"tags": {
"displayName": "[variables('appServicePlanName')]"
},
"properties": {
"name": "[variables('appServicePlanName')]"
}
},
{
"name": "[parameters('name')]",
"type": "Microsoft.Web/sites",
"apiVersion": "2019-08-01",
"location": "[parameters('region')]",
"tags": {
"[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/',variables('appServicePlanName'))]": "Resource",
"displayName": "[parameters('name')]"
},
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]"
],
"properties": {
"name": "[parameters('name')]",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]",
"siteConfig": {
"connectionStrings": [
{
"name": "[concat(parameters('slots')[add(length(parameters('slots')),-1)].name,'connstr')]",
"connectionString": "[parameters('slots')[add(length(parameters('slots')),-1)].connectionString]",
"type": "[parameters('slots')[add(length(parameters('slots')),-1)].connectionType]"
}
]
}
}
},
{
"name": "[concat(parameters('name'),'/', parameters('slots')[copyIndex()].name)]",
"type": "Microsoft.Web/sites/slots",
"apiVersion": "2019-08-01",
"location": "[parameters('region')]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', parameters('name'))]"
],
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]",
"siteConfig": {
"connectionStrings": [
{
"name": "[concat(parameters('slots')[copyIndex()].name,'connstr')]",
"connectionString": "[parameters('slots')[copyIndex()].connectionString]",
"type": "[parameters('slots')[copyIndex()].connectionType]"
}
]
}
},
"copy": {
"name": "webPortalSlot",
"count": "[length(parameters('slots'))]"
}
}
],
"outputs": {}
}

Просмотреть файл

@ -0,0 +1,32 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"name": {
"value": "__webappname__"
},
"sku": {
"value": "__sku__"
},
"slots": {
"value":
[
{
"name":"dev",
"connectionString": "__devconnstr__",
"connectionType":"Custom"
},
{
"name":"test",
"connectionString": "__testconnstr__",
"connectionType":"Custom"
},
{
"name":"production",
"connectionString":"__prodconnstr__",
"connectionType":"Custom"
}
]
}
}
}

176
azure-pipelines.yml Normal file
Просмотреть файл

@ -0,0 +1,176 @@
# ASP.NET Core
# Build and test ASP.NET Core projects targeting .NET Core.
# Add steps that run tests, create a NuGet package, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core
trigger: none
pool:
vmImage: 'windows-latest'
variables:
- group: connectionStrings
- name: 'buildConfiguration'
value: 'Release'
- name: 'resource_group'
value: 'arm-demo-rg'
- name: 'location'
value: 'Australia East'
- name: 'app_service_name'
value: 'vwvarmdemo-ado'
- name: 'deploy_app'
value: true
- name: 'deploy_infra'
value: true
- name: 'slots'
value: |
[
{
"name": "dev",
"connectionString": "__devconnstr__",
"connectionType": "Custom"
},
{
"name": "test",
"connectionString": "__testconnstr__",
"connectionType": "Custom"
},
{
"name": "production",
"connectionString": "__prodconnstr__",
"connectionType": "Custom"
}
]
stages:
- stage: Build
displayName: Build
jobs:
- job: BuildCode
displayName: Build Code
condition: eq(variables.deploy_app,true)
steps:
- task: DotNetCoreCLI@2
displayName: Restore
inputs:
command: restore
projects: '**/*.csproj'
- task: DotNetCoreCLI@2
displayName: Build
inputs:
command: 'build'
projects: '**/*.csproj'
arguments: '--configuration $(buildConfiguration)'
- task: DotNetCoreCLI@2
displayName: publish Code
inputs:
command: publish
publishWebProjects: True
arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
zipAfterPublish: True
- task: PublishBuildArtifacts@1
displayName: Publish artifacts
inputs:
pathtoPublish: '$(Build.ArtifactStagingDirectory)'
artifactName: 'artifacts'
- job: BuildInfra
displayName: Build Infrastructure
condition: eq(variables.deploy_infra,true)
steps:
- task: AzureResourceManagerTemplateDeployment@3
inputs:
deploymentScope: 'Resource Group'
azureResourceManagerConnection: '$(azure_service_connection)'
subscriptionId: '$(subscription_id)'
action: 'Create Or Update Resource Group'
resourceGroupName: '$(resource_group)'
location: '$(location)'
templateLocation: 'Linked artifact'
csmFile: '$(System.DefaultWorkingDirectory)/arm_templates/webapp/deployment.json'
csmParametersFile: '$(System.DefaultWorkingDirectory)/arm_templates/webapp/deployment.parameters.json'
overrideParameters: '-name $(app_service_name) -sku S1 -os Windows -instanceCount 1 -slots $(slots)'
deploymentMode: 'Incremental'
- stage: DevRelease
displayName: Release to Dev
condition: eq(variables.deploy_app,true)
dependsOn: Build
jobs:
- deployment: dev
displayName: deploy to dev
environment: dev
strategy:
runOnce:
deploy:
steps:
- template: release-steps.yml
parameters:
azure_service_connection: $(azure_service_connection)
app_service_name: $(app_service_name)
resource_group: $(resource_group)
environment: $(Environment.Name)
connection_string: $(testconnstr)
- stage: TestRelease
displayName: Release to Test
condition: eq(variables.deploy_app,true)
dependsOn: DevRelease
jobs:
- deployment: test
displayName: deploy to test
environment: test
strategy:
runOnce:
deploy:
steps:
- template: release-steps.yml
parameters:
azure_service_connection: $(azure_service_connection)
app_service_name: $(app_service_name)
resource_group: $(resource_group)
environment: $(Environment.Name)
connection_string: $(testconnstr)
- stage: ProdRelease
displayName: Release to Production
condition: eq(variables.deploy_app,true)
dependsOn: TestRelease
jobs:
- job: manual_intervention
steps:
- task: ManualValidation@0
inputs:
notifyUsers: 'wviriya@microsoft.com'
instructions: 'Please review App Service Test Slot before proceed.'
- deployment: prod
displayName: swap production slot
environment: prod
strategy:
runOnce:
deploy:
steps:
- task: AzureAppServiceSettings@1
inputs:
azureSubscription: '$(azure_service_connection)'
appName: '$(app_service_name)'
resourceGroupName: '$(resource_group)'
slotName: 'production'
connectionStrings: |
[
{
"name": "$(Environment.Name)connstr",
"value": "$(prodconnstr)",
"type": "Custom",
"slotSetting": true
}
]
- task: AzureAppServiceManage@0
inputs:
azureSubscription: '$(azure_service_connection)'
Action: 'Swap Slots'
WebAppName: '$(app_service_name)'
ResourceGroupName: '$(resource_group)'
SourceSlot: 'test'

Двоичные данные
contents/automation_maturity.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 34 KiB

72
faq.md Normal file
Просмотреть файл

@ -0,0 +1,72 @@
# ARM FAQ
## Can I export a template from existing resources?
Yes, you can export a template form existing resources through the Azure portal, see [Export template in Azure portal](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/export-template-portal).
## Can I reuse templates?
Yes, the ARM template is designed to be reusable. You may deploy ARM templates through [portal](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/deploy-portal), [CLI](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/deploy-cli), [PowerShell](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/deploy-powershell), [REST API](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/deploy-rest) or [from GitHub](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/deploy-to-azure-button).
## Is it good practice to deploy multiple different templates to the same resource group?
Good practice is to have a single template or a set of main and [linked templates](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/linked-templates) to deploy resources into a resource group. This allows for change tracking and single source of truth.
## Which API Version should I use?
It depends on features of the resource that you want to use. Each resource provider has its own set of API versions. The latest version will have latest features available.
## If Microsoft makes changes to resources will it break my template?
Microsoft keeps previous versions of resource providers and makes available via versioning of REST API.
## Do you recommend complete or incremental mode?
For most situations incremental mode is recommended. It is important to understand differences between two [deployment modes](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/deployment-modes).
## How do I manage secrets in a template?
We recommend [integrate Azure Key Vault](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-tutorial-use-key-vault) in your ARM template deployment. You can store secrets in Azure Key Vault and retrieve them as deployment template parameters.
## How can I run non ARM actions as part of my ARM deployment e.g run a powershell or upload a blob?
Yes, there is a feature, [deployment scripts in template](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/deployment-script-template?tabs=CLI) (preview), which allows execution of PowerShell or CLI scripts in template deployments and reviewing of execution results.
## Should I build using kudu or my pipeline?
Generally building in a pipeline makes more sense. This allows you to adopt a Build once deploy many approach and the only change between environments is some configuration. Building using kudu can be very useful for demo templates where you want to enable others to use your template in a one click deployment.
## Should I handle complex logic in my template using nested templates and logic operator or should i move it up to powershell or an orchestrator?
It depends on your preference. ARM template is capable of complex logic and operations.
## What the best tool for authoring templates?
This really comes down to personal preference, We commonly use Visual Studio or VSCode as they both have great extensions for navigating, generating and validating templates.
## Where should I start? Create in the portal and export, QuickStart templates and modify, or using snippets
Everyone learns in different ways but we find starting with a combination of QuickStart templates and exporting resources from the portal until you become familiar is a good way to go
## How do I manage dependencies against existing resources that are not defined in my template?
You can refer to existing resource(s) by Resource Id. You may use build-in [resourceId](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-functions-resource#resourceid) function for ARM template. You will need subscription id and resource group name of dependent resource.
## Can I create resource groups with an ARM template?
Yes, But there requires a deployment to the subscription and not to the resource group. This approach is used in blueprints for example. For less complex deployments you would typically deploy all your resources into one resource group which would be pre provisioned using PowerShell, CLI or Azure Pipelines
## Can I assign permissions in an arm template
No. However, by using [deployment scripts in template](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/deployment-script-template?tabs=CLI) (preview). You can manage users and create objects in Azure AD.
## How do I share common templates with my team or end users
There are several options. The portal has a builtin template gallery to share templates across your tenant. You could manage deployments entirely thorough Azure DevOps or another orchestrator. You could build you custom deployment solution using a private repository. Alternatively, Github is a great way for sharing templates with external users.
## When should I use Azure Blueprints vs standalone ARM templates?
You should start with the standalone ARM templates and once you feel comfortable with them, investigate to Azure Blueprints. Azure Blueprints combine role and policy assignments together with ARM templates and Resource Groups to define a repeatable set of Azure resources that implements and adheres to an organization's standards, patterns, and requirements.
## Any Anti-Patterns I should be aware of?
DO NOT right click to deploy.
Ensure traceability.
# IaC FAQ
## Should I automate everything including resources I only provision once?
You need to evaluate effort required to create deployment template. If the resource(s) are meant to be provisioned once, the portal can be the quickest way to deploy.
## How do I manage changes that cant be automated?
By utilizing appropriate tooling, most changes can be automated. Otherwise, limit number of staff who can manage these changes.
## How do I choose my tooling? ARM, TerraForm, Ansible, Puppet, Chef, DSC, git etc
It depends. By using pipelines tool such as Azure Pipelines, many of these tools can work together where its purpose fits.
## How do I manage shared resources when working with teams of teams, e.g Application Gateway or API Management
Typically, these shared resources should be managed with a single template/script by an appropriate team.
## How do I encourage my team to start using declarative methods, source control and be test driven?
Show your team the benefit of this approach and build DevOps culture among team's members.

Просмотреть файл

@ -0,0 +1,16 @@
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build-env
WORKDIR /app
# Copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore
# Copy everything else and build
COPY . ./
RUN dotnet publish -c Release -o out
# Build runtime image
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "hello_fasttrack_azure.dll"]

Просмотреть файл

@ -0,0 +1,26 @@
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>

Просмотреть файл

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace hello_fasttrack_azure.Pages
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class ErrorModel : PageModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
private readonly ILogger<ErrorModel> _logger;
public ErrorModel(ILogger<ErrorModel> logger)
{
_logger = logger;
}
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
}

Просмотреть файл

@ -0,0 +1,12 @@
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<h1 class="display-4">Welcome to FTA Live - DevOps and IaC</h1>
<h2>Monday, 18 January 2021</h2>
<h2>Hello! from FastTrack for Azure team.</h2>
<p>We love all things Azure.</p>
</div>

Просмотреть файл

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace hello_fasttrack_azure.Pages
{
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}

Просмотреть файл

@ -0,0 +1,8 @@
@page
@model PrivacyModel
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>

Просмотреть файл

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace hello_fasttrack_azure.Pages
{
public class PrivacyModel : PageModel
{
private readonly ILogger<PrivacyModel> _logger;
public PrivacyModel(ILogger<PrivacyModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}

Просмотреть файл

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Zero to Hero with ARM and IaC</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asp-page="/Index">Zero to Hero with ARM and IaC</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
&copy; 2020 - Zero to Hero with ARM and IaC - <a asp-area="" asp-page="/Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@RenderSection("Scripts", required: false)
</body>
</html>

Просмотреть файл

@ -0,0 +1,2 @@
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>

Просмотреть файл

@ -0,0 +1,3 @@
@using hello_fasttrack_azure
@namespace hello_fasttrack_azure.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Просмотреть файл

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

Просмотреть файл

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace hello_fasttrack_azure
{
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<Startup>();
});
}
}

Просмотреть файл

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:45513",
"sslPort": 44339
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"hello_fasttrack_azure": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

Просмотреть файл

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace hello_fasttrack_azure
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
}
}

Просмотреть файл

@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

Просмотреть файл

@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
</Project>

Просмотреть файл

@ -0,0 +1,71 @@
/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
for details on configuring this project to bundle and minify static web assets. */
a.navbar-brand {
white-space: normal;
text-align: center;
word-break: break-all;
}
/* Provide sufficient contrast against white background */
a {
color: #0366d6;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
/* Sticky footer styles
-------------------------------------------------- */
html {
font-size: 14px;
}
@media (min-width: 768px) {
html {
font-size: 16px;
}
}
.border-top {
border-top: 1px solid #e5e5e5;
}
.border-bottom {
border-bottom: 1px solid #e5e5e5;
}
.box-shadow {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}
button.accept-policy {
font-size: 1rem;
line-height: inherit;
}
/* Sticky footer styles
-------------------------------------------------- */
html {
position: relative;
min-height: 100%;
}
body {
/* Margin bottom by footer height */
margin-bottom: 60px;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
white-space: nowrap;
line-height: 60px; /* Vertically center the text there */
}

Двоичные данные
hello_fasttrack_azure/wwwroot/favicon.ico Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 31 KiB

Просмотреть файл

@ -0,0 +1,4 @@
// Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
// for details on configuring this project to bundle and minify static web assets.
// Write your Javascript code.

Просмотреть файл

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2011-2018 Twitter, Inc.
Copyright (c) 2011-2018 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -0,0 +1,331 @@
/*!
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
line-height: 1.15;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
display: block;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
text-align: left;
background-color: #fff;
}
[tabindex="-1"]:focus {
outline: 0 !important;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 0.5rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-original-title] {
text-decoration: underline;
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
border-bottom: 0;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: .5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 80%;
}
sub,
sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -.25em;
}
sup {
top: -.5em;
}
a {
color: #007bff;
text-decoration: none;
background-color: transparent;
}
a:hover {
color: #0056b3;
text-decoration: underline;
}
a:not([href]):not([tabindex]) {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):focus {
outline: 0;
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
}
pre {
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
}
figure {
margin: 0 0 1rem;
}
img {
vertical-align: middle;
border-style: none;
}
svg {
overflow: hidden;
vertical-align: middle;
}
table {
border-collapse: collapse;
}
caption {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
color: #6c757d;
text-align: left;
caption-side: bottom;
}
th {
text-align: inherit;
}
label {
display: inline-block;
margin-bottom: 0.5rem;
}
button {
border-radius: 0;
}
button:focus {
outline: 1px dotted;
outline: 5px auto -webkit-focus-ring-color;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
select {
word-wrap: normal;
}
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
button:not(:disabled),
[type="button"]:not(:disabled),
[type="reset"]:not(:disabled),
[type="submit"]:not(:disabled) {
cursor: pointer;
}
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
padding: 0;
border-style: none;
}
input[type="radio"],
input[type="checkbox"] {
box-sizing: border-box;
padding: 0;
}
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"] {
-webkit-appearance: listbox;
}
textarea {
overflow: auto;
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
display: block;
width: 100%;
max-width: 100%;
padding: 0;
margin-bottom: .5rem;
font-size: 1.5rem;
line-height: inherit;
color: inherit;
white-space: normal;
}
progress {
vertical-align: baseline;
}
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
outline-offset: -2px;
-webkit-appearance: none;
}
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
summary {
display: list-item;
cursor: pointer;
}
template {
display: none;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -0,0 +1,8 @@
/*!
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.min.css.map */

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

10038
hello_fasttrack_azure/wwwroot/lib/bootstrap/dist/css/bootstrap.css поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

4435
hello_fasttrack_azure/wwwroot/lib/bootstrap/dist/js/bootstrap.js поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -0,0 +1,12 @@
Copyright (c) .NET Foundation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
these files except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.

Просмотреть файл

@ -0,0 +1,432 @@
// Unobtrusive validation support library for jQuery and jQuery Validate
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// @version v3.2.11
/*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: false */
/*global document: false, jQuery: false */
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define("jquery.validate.unobtrusive", ['jquery-validation'], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS-like environments that support module.exports
module.exports = factory(require('jquery-validation'));
} else {
// Browser global
jQuery.validator.unobtrusive = factory(jQuery);
}
}(function ($) {
var $jQval = $.validator,
adapters,
data_validation = "unobtrusiveValidation";
function setValidationValues(options, ruleName, value) {
options.rules[ruleName] = value;
if (options.message) {
options.messages[ruleName] = options.message;
}
}
function splitAndTrim(value) {
return value.replace(/^\s+|\s+$/g, "").split(/\s*,\s*/g);
}
function escapeAttributeValue(value) {
// As mentioned on http://api.jquery.com/category/selectors/
return value.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g, "\\$1");
}
function getModelPrefix(fieldName) {
return fieldName.substr(0, fieldName.lastIndexOf(".") + 1);
}
function appendModelPrefix(value, prefix) {
if (value.indexOf("*.") === 0) {
value = value.replace("*.", prefix);
}
return value;
}
function onError(error, inputElement) { // 'this' is the form element
var container = $(this).find("[data-valmsg-for='" + escapeAttributeValue(inputElement[0].name) + "']"),
replaceAttrValue = container.attr("data-valmsg-replace"),
replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) !== false : null;
container.removeClass("field-validation-valid").addClass("field-validation-error");
error.data("unobtrusiveContainer", container);
if (replace) {
container.empty();
error.removeClass("input-validation-error").appendTo(container);
}
else {
error.hide();
}
}
function onErrors(event, validator) { // 'this' is the form element
var container = $(this).find("[data-valmsg-summary=true]"),
list = container.find("ul");
if (list && list.length && validator.errorList.length) {
list.empty();
container.addClass("validation-summary-errors").removeClass("validation-summary-valid");
$.each(validator.errorList, function () {
$("<li />").html(this.message).appendTo(list);
});
}
}
function onSuccess(error) { // 'this' is the form element
var container = error.data("unobtrusiveContainer");
if (container) {
var replaceAttrValue = container.attr("data-valmsg-replace"),
replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) : null;
container.addClass("field-validation-valid").removeClass("field-validation-error");
error.removeData("unobtrusiveContainer");
if (replace) {
container.empty();
}
}
}
function onReset(event) { // 'this' is the form element
var $form = $(this),
key = '__jquery_unobtrusive_validation_form_reset';
if ($form.data(key)) {
return;
}
// Set a flag that indicates we're currently resetting the form.
$form.data(key, true);
try {
$form.data("validator").resetForm();
} finally {
$form.removeData(key);
}
$form.find(".validation-summary-errors")
.addClass("validation-summary-valid")
.removeClass("validation-summary-errors");
$form.find(".field-validation-error")
.addClass("field-validation-valid")
.removeClass("field-validation-error")
.removeData("unobtrusiveContainer")
.find(">*") // If we were using valmsg-replace, get the underlying error
.removeData("unobtrusiveContainer");
}
function validationInfo(form) {
var $form = $(form),
result = $form.data(data_validation),
onResetProxy = $.proxy(onReset, form),
defaultOptions = $jQval.unobtrusive.options || {},
execInContext = function (name, args) {
var func = defaultOptions[name];
func && $.isFunction(func) && func.apply(form, args);
};
if (!result) {
result = {
options: { // options structure passed to jQuery Validate's validate() method
errorClass: defaultOptions.errorClass || "input-validation-error",
errorElement: defaultOptions.errorElement || "span",
errorPlacement: function () {
onError.apply(form, arguments);
execInContext("errorPlacement", arguments);
},
invalidHandler: function () {
onErrors.apply(form, arguments);
execInContext("invalidHandler", arguments);
},
messages: {},
rules: {},
success: function () {
onSuccess.apply(form, arguments);
execInContext("success", arguments);
}
},
attachValidation: function () {
$form
.off("reset." + data_validation, onResetProxy)
.on("reset." + data_validation, onResetProxy)
.validate(this.options);
},
validate: function () { // a validation function that is called by unobtrusive Ajax
$form.validate();
return $form.valid();
}
};
$form.data(data_validation, result);
}
return result;
}
$jQval.unobtrusive = {
adapters: [],
parseElement: function (element, skipAttach) {
/// <summary>
/// Parses a single HTML element for unobtrusive validation attributes.
/// </summary>
/// <param name="element" domElement="true">The HTML element to be parsed.</param>
/// <param name="skipAttach" type="Boolean">[Optional] true to skip attaching the
/// validation to the form. If parsing just this single element, you should specify true.
/// If parsing several elements, you should specify false, and manually attach the validation
/// to the form when you are finished. The default is false.</param>
var $element = $(element),
form = $element.parents("form")[0],
valInfo, rules, messages;
if (!form) { // Cannot do client-side validation without a form
return;
}
valInfo = validationInfo(form);
valInfo.options.rules[element.name] = rules = {};
valInfo.options.messages[element.name] = messages = {};
$.each(this.adapters, function () {
var prefix = "data-val-" + this.name,
message = $element.attr(prefix),
paramValues = {};
if (message !== undefined) { // Compare against undefined, because an empty message is legal (and falsy)
prefix += "-";
$.each(this.params, function () {
paramValues[this] = $element.attr(prefix + this);
});
this.adapt({
element: element,
form: form,
message: message,
params: paramValues,
rules: rules,
messages: messages
});
}
});
$.extend(rules, { "__dummy__": true });
if (!skipAttach) {
valInfo.attachValidation();
}
},
parse: function (selector) {
/// <summary>
/// Parses all the HTML elements in the specified selector. It looks for input elements decorated
/// with the [data-val=true] attribute value and enables validation according to the data-val-*
/// attribute values.
/// </summary>
/// <param name="selector" type="String">Any valid jQuery selector.</param>
// $forms includes all forms in selector's DOM hierarchy (parent, children and self) that have at least one
// element with data-val=true
var $selector = $(selector),
$forms = $selector.parents()
.addBack()
.filter("form")
.add($selector.find("form"))
.has("[data-val=true]");
$selector.find("[data-val=true]").each(function () {
$jQval.unobtrusive.parseElement(this, true);
});
$forms.each(function () {
var info = validationInfo(this);
if (info) {
info.attachValidation();
}
});
}
};
adapters = $jQval.unobtrusive.adapters;
adapters.add = function (adapterName, params, fn) {
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation.</summary>
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
/// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param>
/// <param name="params" type="Array" optional="true">[Optional] An array of parameter names (strings) that will
/// be extracted from the data-val-nnnn-mmmm HTML attributes (where nnnn is the adapter name, and
/// mmmm is the parameter name).</param>
/// <param name="fn" type="Function">The function to call, which adapts the values from the HTML
/// attributes into jQuery Validate rules and/or messages.</param>
/// <returns type="jQuery.validator.unobtrusive.adapters" />
if (!fn) { // Called with no params, just a function
fn = params;
params = [];
}
this.push({ name: adapterName, params: params, adapt: fn });
return this;
};
adapters.addBool = function (adapterName, ruleName) {
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where
/// the jQuery Validate validation rule has no parameter values.</summary>
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
/// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param>
/// <param name="ruleName" type="String" optional="true">[Optional] The name of the jQuery Validate rule. If not provided, the value
/// of adapterName will be used instead.</param>
/// <returns type="jQuery.validator.unobtrusive.adapters" />
return this.add(adapterName, function (options) {
setValidationValues(options, ruleName || adapterName, true);
});
};
adapters.addMinMax = function (adapterName, minRuleName, maxRuleName, minMaxRuleName, minAttribute, maxAttribute) {
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where
/// the jQuery Validate validation has three potential rules (one for min-only, one for max-only, and
/// one for min-and-max). The HTML parameters are expected to be named -min and -max.</summary>
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
/// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param>
/// <param name="minRuleName" type="String">The name of the jQuery Validate rule to be used when you only
/// have a minimum value.</param>
/// <param name="maxRuleName" type="String">The name of the jQuery Validate rule to be used when you only
/// have a maximum value.</param>
/// <param name="minMaxRuleName" type="String">The name of the jQuery Validate rule to be used when you
/// have both a minimum and maximum value.</param>
/// <param name="minAttribute" type="String" optional="true">[Optional] The name of the HTML attribute that
/// contains the minimum value. The default is "min".</param>
/// <param name="maxAttribute" type="String" optional="true">[Optional] The name of the HTML attribute that
/// contains the maximum value. The default is "max".</param>
/// <returns type="jQuery.validator.unobtrusive.adapters" />
return this.add(adapterName, [minAttribute || "min", maxAttribute || "max"], function (options) {
var min = options.params.min,
max = options.params.max;
if (min && max) {
setValidationValues(options, minMaxRuleName, [min, max]);
}
else if (min) {
setValidationValues(options, minRuleName, min);
}
else if (max) {
setValidationValues(options, maxRuleName, max);
}
});
};
adapters.addSingleVal = function (adapterName, attribute, ruleName) {
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where
/// the jQuery Validate validation rule has a single value.</summary>
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
/// in the data-val-nnnn HTML attribute(where nnnn is the adapter name).</param>
/// <param name="attribute" type="String">[Optional] The name of the HTML attribute that contains the value.
/// The default is "val".</param>
/// <param name="ruleName" type="String" optional="true">[Optional] The name of the jQuery Validate rule. If not provided, the value
/// of adapterName will be used instead.</param>
/// <returns type="jQuery.validator.unobtrusive.adapters" />
return this.add(adapterName, [attribute || "val"], function (options) {
setValidationValues(options, ruleName || adapterName, options.params[attribute]);
});
};
$jQval.addMethod("__dummy__", function (value, element, params) {
return true;
});
$jQval.addMethod("regex", function (value, element, params) {
var match;
if (this.optional(element)) {
return true;
}
match = new RegExp(params).exec(value);
return (match && (match.index === 0) && (match[0].length === value.length));
});
$jQval.addMethod("nonalphamin", function (value, element, nonalphamin) {
var match;
if (nonalphamin) {
match = value.match(/\W/g);
match = match && match.length >= nonalphamin;
}
return match;
});
if ($jQval.methods.extension) {
adapters.addSingleVal("accept", "mimtype");
adapters.addSingleVal("extension", "extension");
} else {
// for backward compatibility, when the 'extension' validation method does not exist, such as with versions
// of JQuery Validation plugin prior to 1.10, we should use the 'accept' method for
// validating the extension, and ignore mime-type validations as they are not supported.
adapters.addSingleVal("extension", "extension", "accept");
}
adapters.addSingleVal("regex", "pattern");
adapters.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url");
adapters.addMinMax("length", "minlength", "maxlength", "rangelength").addMinMax("range", "min", "max", "range");
adapters.addMinMax("minlength", "minlength").addMinMax("maxlength", "minlength", "maxlength");
adapters.add("equalto", ["other"], function (options) {
var prefix = getModelPrefix(options.element.name),
other = options.params.other,
fullOtherName = appendModelPrefix(other, prefix),
element = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(fullOtherName) + "']")[0];
setValidationValues(options, "equalTo", element);
});
adapters.add("required", function (options) {
// jQuery Validate equates "required" with "mandatory" for checkbox elements
if (options.element.tagName.toUpperCase() !== "INPUT" || options.element.type.toUpperCase() !== "CHECKBOX") {
setValidationValues(options, "required", true);
}
});
adapters.add("remote", ["url", "type", "additionalfields"], function (options) {
var value = {
url: options.params.url,
type: options.params.type || "GET",
data: {}
},
prefix = getModelPrefix(options.element.name);
$.each(splitAndTrim(options.params.additionalfields || options.element.name), function (i, fieldName) {
var paramName = appendModelPrefix(fieldName, prefix);
value.data[paramName] = function () {
var field = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(paramName) + "']");
// For checkboxes and radio buttons, only pick up values from checked fields.
if (field.is(":checkbox")) {
return field.filter(":checked").val() || field.filter(":hidden").val() || '';
}
else if (field.is(":radio")) {
return field.filter(":checked").val() || '';
}
return field.val();
};
});
setValidationValues(options, "remote", value);
});
adapters.add("password", ["min", "nonalphamin", "regex"], function (options) {
if (options.params.min) {
setValidationValues(options, "minlength", options.params.min);
}
if (options.params.nonalphamin) {
setValidationValues(options, "nonalphamin", options.params.nonalphamin);
}
if (options.params.regex) {
setValidationValues(options, "regex", options.params.regex);
}
});
adapters.add("fileextensions", ["extensions"], function (options) {
setValidationValues(options, "extension", options.params.extensions);
});
$(function () {
$jQval.unobtrusive.parse(document);
});
return $jQval.unobtrusive;
}));

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -0,0 +1,22 @@
The MIT License (MIT)
=====================
Copyright Jörn Zaefferer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -0,0 +1,36 @@
Copyright JS Foundation and other contributors, https://js.foundation/
This software consists of voluntary contributions made by many
individuals. For exact contribution history, see the revision history
available at https://github.com/jquery/jquery
The following license applies to all parts of this software except as
documented below:
====
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
====
All files located in the node_modules and external directories are
externally maintained libraries used by this software which have their
own licenses; we recommend you read them, as their terms may differ from
the terms above.

10364
hello_fasttrack_azure/wwwroot/lib/jquery/dist/jquery.js поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

2
hello_fasttrack_azure/wwwroot/lib/jquery/dist/jquery.min.js поставляемый Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

1
hello_fasttrack_azure/wwwroot/lib/jquery/dist/jquery.min.map поставляемый Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

51
manifests/deployment.yaml Normal file
Просмотреть файл

@ -0,0 +1,51 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web
name: web
spec:
replicas: 1
selector:
matchLabels:
app: web
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
labels:
app: web
name: web
spec:
containers:
- image: azninja.azurecr.io/hellofta
livenessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 30
periodSeconds: 20
timeoutSeconds: 10
failureThreshold: 3
imagePullPolicy: Always
name: web
ports:
- containerPort: 80
hostPort: 80
protocol: TCP
resources:
requests:
cpu: 100m
memory: 128Mi
securityContext:
privileged: false
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30

16
manifests/service.yaml Normal file
Просмотреть файл

@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: web
name: web
spec:
ports:
- name: web-traffic
port: 80
protocol: TCP
targetPort: 80
selector:
app: web
sessionAffinity: None
type: LoadBalancer

37
release-steps.yml Normal file
Просмотреть файл

@ -0,0 +1,37 @@
parameters:
- name: azure_service_connection
type: string
- name: app_service_name
type: string
- name: resource_group
type: string
- name: environment
type: string
- name: connection_string
type: string
steps:
- task: AzureAppServiceSettings@1
inputs:
azureSubscription: ${{parameters.azure_service_connection}}
appName: ${{parameters.app_service_name}}
resourceGroupName: ${{parameters.resource_group}}
slotName: ${{parameters.environment}}
connectionStrings: |
[
{
"name": "${{parameters.environment}}connstr",
"value": "${{parameters.connection_string}}",
"type": "Custom",
"slotSetting": true
}
]
- task: AzureWebApp@1
inputs:
azureSubscription: ${{parameters.azure_service_connection}}
appType: 'webApp'
appName: ${{parameters.app_service_name}}
deployToSlotOrASE: true
ResourceGroupName: ${{parameters.resource_group}}
SlotName: ${{parameters.environment}}
package: '$(Pipeline.Workspace)/artifacts/**/*.zip'
deploymentMethod: 'auto'

Просмотреть файл

@ -0,0 +1,32 @@
#!/bin/bash
# Create Client App
CLIENT_APP=$(az ad app create --display-name "${1}" --identifier-uris "http://${1}")
# Get App Id and Object Id
CLIENT_ID=$(echo $CLIENT_APP | jq -r '.appId')
CLIENT_OID=$(echo $CLIENT_APP | jq -r '.objectId')
JSON="{\"spa\": {\"redirectUris\": [\"${URL}\"]}, \"web\": {\"implicitGrantSettings\": {\"enableAccessTokenIssuance\": true,\"enableIdTokenIssuance\": true}}}"
sleep 30
# Setup Implicit Grant Flow and Reply URLs
az rest -m patch -u "https://graph.microsoft.com/beta/applications/${CLIENT_OID}" --headers Content-Type=application/json -b "$JSON"
# Create API App
API_APP=$(az ad app create --display-name "${2}" --identifier-uris "http://${2}")
# Get App Id and Object Id
PERMISSION_ID=$(echo $API_APP | jq -r '.oauth2Permissions | .[].id')
API_ID=$(echo $API_APP | jq -r '.appId')
API_OID=$(echo $API_APP | jq -r '.objectId')
JSON="{\"api\": {\"preAuthorizedApplications\": [{\"appId\": \"${CLIENT_ID}\",\"permissionIds\": [\"${PERMISSION_ID}\"]}]}}"
sleep 30
# Allow client app above to access API as a trusted service
az rest -m patch -u "https://graph.microsoft.com/beta/applications/${API_OID}" --headers Content-Type=application/json -b "$JSON"
SECRET=$(az ad app credential reset --id $API_OID --append | jq -r '.password')
az webapp auth update -g $4 -n $API --enabled true \
--action LoginWithAzureActiveDirectory \
--aad-allowed-token-audiences $3 \
--aad-client-id $API_ID --aad-client-secret $SECRET \
--aad-token-issuer-url $3