зеркало из https://github.com/microsoft/Cluster.git
Added files
This commit is contained in:
Родитель
d018b7f7af
Коммит
bd41e933aa
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"powershell.scriptAnalysis.settingsPath": "PSScriptAnalyzerSettings.psd1",
|
||||
"files.autoGuessEncoding": true
|
||||
}
|
Двоичный файл не отображается.
|
@ -0,0 +1,446 @@
|
|||
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$InformationPreference = "Continue"
|
||||
|
||||
|
||||
class VmssEnvironment {
|
||||
# naming this 'Environment' causes conflicts with System.Environment
|
||||
|
||||
[ValidatePattern("^[A-Z][A-z0-9]+$")]
|
||||
[string]$ServiceName
|
||||
|
||||
[ValidatePattern("^[A-Z]{3,6}$")]
|
||||
[string]$FlightingRing
|
||||
|
||||
[ValidatePattern("^[A-Z][A-z0-9]+$")]
|
||||
[string]$Region
|
||||
|
||||
VmssEnvironment([string]$Id) {
|
||||
($this.ServiceName, $this.FlightingRing, $this.Region) = $Id -split "-"
|
||||
}
|
||||
|
||||
VmssEnvironment([string]$ServiceName, [string]$FlightingRing, [string]$Region) {
|
||||
$this.ServiceName = $ServiceName
|
||||
$this.FlightingRing = $FlightingRing
|
||||
$this.Region = $Region
|
||||
}
|
||||
|
||||
[string] ToString() {
|
||||
return "$($this.ServiceName)-$($this.FlightingRing)-$($this.Region)"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class Cluster {
|
||||
|
||||
[VmssEnvironment]$VmssEnvironment
|
||||
[int]$Index
|
||||
|
||||
Cluster([string]$ServiceName, [string]$FlightingRing, [string]$Region, [int]$Index) {
|
||||
$this.VmssEnvironment = [VmssEnvironment]::new($ServiceName, $FlightingRing, $Region)
|
||||
$this.Index = $Index
|
||||
}
|
||||
|
||||
Cluster([VmssEnvironment]$VmssEnvironment, [int]$Index) {
|
||||
$this.VmssEnvironment = $VmssEnvironment
|
||||
$this.Index = $Index
|
||||
}
|
||||
|
||||
Cluster([string]$Id) {
|
||||
($serviceName, $flightingRing, $region, $this.Index) = $Id -split "-"
|
||||
$this.VmssEnvironment = [VmssEnvironment]::new($serviceName, $flightingRing, $region)
|
||||
}
|
||||
|
||||
[string] ToString() {
|
||||
return "$($this.VmssEnvironment)-$($this.Index)"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Writes formatted execution status messages to the Information stream
|
||||
|
||||
.DESCRIPTION
|
||||
Prepends message lines with execution information and timestamp
|
||||
|
||||
.PARAMETER Message
|
||||
The message(s) logged to the Information stream. Objects are serialized before writing.
|
||||
|
||||
.EXAMPLE
|
||||
"Hello", "World" | Write-Log
|
||||
|
||||
#>
|
||||
function Write-Log {
|
||||
Param(
|
||||
[Parameter(ValueFromPipeline)]
|
||||
$Message
|
||||
)
|
||||
|
||||
begin {
|
||||
# 'Write-Log' seemingly nondeterministically appears in the call stack
|
||||
$stack = Get-PSCallStack | % {$_.Command} | ? {("<ScriptBlock>", "Write-Log") -notcontains $_}
|
||||
if ($stack) {
|
||||
[array]::reverse($stack)
|
||||
$stack = " | $($stack -join " > ")"
|
||||
}
|
||||
$timestamp = Get-Date -Format "T"
|
||||
}
|
||||
|
||||
process {
|
||||
$Message = ($Message | Format-List | Out-String) -split "[\r\n]+" | ? {$_}
|
||||
$Message | % {Write-Information "[$timestamp$stack] $_" -InformationAction Continue}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function Test-Elevation {
|
||||
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
|
||||
return $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
}
|
||||
|
||||
|
||||
function Assert-AzureRmContext {
|
||||
Param([string]$Account)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$contextAccount = (Get-AzureRmContext).Account
|
||||
|
||||
if (-not $contextAccount) {
|
||||
Write-Error "Must be logged into Azure. Run 'Login-AzureRmAccount' before continuing."
|
||||
}
|
||||
|
||||
if ($Account -and $contextAccount -ne $Account) {
|
||||
Write-Error "Must be logged into Azure as '$Account'. Run 'Login-AzureRmAccount' before continuing."
|
||||
}
|
||||
|
||||
Write-Verbose "Logged into Azure account as '$Account'"
|
||||
}
|
||||
|
||||
|
||||
function ConvertTo-HashTable {
|
||||
Param(
|
||||
[Parameter(Mandatory, ValueFromPipeline)]
|
||||
[psobject[]]$InputObject
|
||||
)
|
||||
|
||||
Process {
|
||||
$hash = @{}
|
||||
$_.PSObject.Properties | % {$hash[$_.Name] = $_.Value}
|
||||
Write-Output $hash
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Provisions a new cluster in Azure
|
||||
|
||||
.DESCRIPTION
|
||||
Creates a new resource group and configured storage account, then deploys a template to that resource group
|
||||
|
||||
.PARAMETER ServiceName
|
||||
Parameter See README for terminology
|
||||
|
||||
.PARAMETER FlightingRing
|
||||
Parameter See README for terminology
|
||||
|
||||
.PARAMETER Region
|
||||
Parameter See README for terminology
|
||||
|
||||
.EXAMPLE
|
||||
New-Cluster "OneRF" "PROD" "WestCentralUS"
|
||||
#>
|
||||
function New-Cluster {
|
||||
[CmdletBinding(DefaultParameterSetName = "Components")]
|
||||
Param(
|
||||
[Parameter(Mandatory, ParameterSetName = "Components", Position = 0)]
|
||||
[string]$ServiceName,
|
||||
[Parameter(Mandatory, ParameterSetName = "Components", Position = 1)]
|
||||
[string]$FlightingRing,
|
||||
[Parameter(Mandatory, ParameterSetName = "Components", Position = 2)]
|
||||
[string]$Region,
|
||||
|
||||
[Parameter(Mandatory, ParameterSetName = "VmssEnvironment", Position = 0)]
|
||||
[VmssEnvironment]$Environment,
|
||||
|
||||
[Parameter(ParameterSetName = "Components", Position = 3)]
|
||||
[Parameter(ParameterSetName = "VmssEnvironment", Position = 1)]
|
||||
[ValidateScript( {Test-Path $_ -PathType Container} )]
|
||||
[string]$DefinitionsContainer = "$PSScriptRoot\..\..\Definitions"
|
||||
)
|
||||
|
||||
Write-Verbose "Parameter validation successful"
|
||||
|
||||
if (-not $VmssEnvironment) {
|
||||
Write-Verbose "Generating VmssEnvironment object from components"
|
||||
$VmssEnvironment = [VmssEnvironment]::new($ServiceName, $FlightingRing, $Region)
|
||||
Write-Verbose "Created VmssEnvironment object '$VmssEnvironment'"
|
||||
}
|
||||
|
||||
# get next available VmssEnvironment index
|
||||
Write-Verbose "Finding first unused cluster index"
|
||||
[int[]]$indexes = Select-Cluster -VmssEnvironment $VmssEnvironment | % {$_.Index}
|
||||
for ($index = 0; $indexes -contains $index; $index++) {}
|
||||
Write-Verbose "Using cluster index '$index'"
|
||||
|
||||
# generate identifiers
|
||||
$cluster = [Cluster]::new($VmssEnvironment, $index)
|
||||
$storageAccountName = "s$(New-Guid)".Replace("-", "").Substring(0, 24)
|
||||
|
||||
# create resources
|
||||
Write-Log "Creating resource group '$cluster' and storage account '$storageAccountName'"
|
||||
New-AzureRmResourceGroup -Name $cluster -Location $Region | Out-Null
|
||||
$storageAccount = New-AzureRmStorageAccount `
|
||||
-ResourceGroupName $cluster `
|
||||
-Name $storageAccountName `
|
||||
-Type "Standard_LRS" `
|
||||
-Location $Region `
|
||||
-EnableEncryptionService "blob"
|
||||
New-AzureStorageContainer -Context $storageAccount.Context -Name "configuration" | Out-Null
|
||||
New-AzureStorageContainer -Context $storageAccount.Context -Name "disks" | Out-Null
|
||||
|
||||
# enforce template
|
||||
Write-Log "Deploying to cluster '$cluster'"
|
||||
New-ClusterDeployment -Cluster $cluster -DefinitionsContainer $DefinitionsContainer
|
||||
|
||||
return $cluster
|
||||
}
|
||||
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Starts a template deployment to the specified cluster
|
||||
|
||||
.DESCRIPTION
|
||||
Uses the config selection defined in the README to deploy the most specific template and parameters in \Management\Templates folder to the specified cluster.
|
||||
|
||||
.EXAMPLE
|
||||
New-ClusterDeployment "Contoso" "PROD" "WestCentralUS" 3
|
||||
|
||||
.NOTES
|
||||
Requires the cluster to exist.
|
||||
|
||||
#>
|
||||
function New-ClusterDeployment {
|
||||
[CmdletBinding(DefaultParameterSetName = "Components")]
|
||||
Param(
|
||||
[Parameter(Mandatory, ParameterSetName = "Components", Position = 0)]
|
||||
[string]$ServiceName,
|
||||
[Parameter(Mandatory, ParameterSetName = "Components", Position = 1)]
|
||||
[string]$FlightingRing,
|
||||
[Parameter(Mandatory, ParameterSetName = "Components", Position = 2)]
|
||||
[string]$Region,
|
||||
[Parameter(Mandatory, ParameterSetName = "Components", Position = 3)]
|
||||
[int]$Index,
|
||||
|
||||
[Parameter(Mandatory, ParameterSetName = "Cluster", Position = 0)]
|
||||
[Cluster]$Cluster,
|
||||
|
||||
[Parameter(ParameterSetName = "Components", Position = 4)]
|
||||
[Parameter(ParameterSetName = "Cluster", Position = 1)]
|
||||
[ValidateScript( {Test-Path $_ -PathType Container} )]
|
||||
[string]$DefinitionsContainer = "."
|
||||
)
|
||||
|
||||
Write-Verbose "Parameter validation successful"
|
||||
|
||||
if (-not $Cluster) {
|
||||
$Cluster = [Cluster]::new($ServiceName, $FlightingRing, $Region, $Index)
|
||||
}
|
||||
|
||||
$storageAccount = Get-AzureRmStorageAccount -ResourceGroupName "$Cluster"
|
||||
$storageAccountName = $storageAccount.StorageAccountName
|
||||
Write-Verbose "Connected to Storage Account '$storageAccount'"
|
||||
|
||||
# build url components
|
||||
Write-Log "Generating secure URL for artifacts"
|
||||
$dscUrl = "https://$storageAccountName.blob.core.windows.net/configuration/dsc.zip"
|
||||
$vhdContainer = "https://$storageAccountName.blob.core.windows.net/disks/"
|
||||
$sasToken = New-AzureStorageContainerSASToken `
|
||||
-Context $storageAccount.Context `
|
||||
-Container "configuration" `
|
||||
-Permission "r" `
|
||||
-ExpiryTime ([datetime]::MaxValue)
|
||||
|
||||
# grab the most specific definition of each type
|
||||
$selectConfigParams = @{
|
||||
ServiceName = $Cluster.VmssEnvironment.ServiceName
|
||||
FlightingRing = $Cluster.VmssEnvironment.FlightingRing
|
||||
DefinitionsContainer = $DefinitionsContainer
|
||||
}
|
||||
$dscFile = Select-Config @selectConfigParams -ConfigType "ps1"
|
||||
$templateFile = Select-Config @selectConfigParams -ConfigType "template.json"
|
||||
$templateParameterFile = Select-Config @selectConfigParams -ConfigType "parameters.json"
|
||||
$configDataFile = Select-Config @selectConfigParams -ConfigType "config.json"
|
||||
$dscConfigDataFile = Select-Config @selectConfigParams -ConfigType "psd1"
|
||||
|
||||
# package and upload DSC
|
||||
Write-Log "Uploading 'Configuration' to '$dscUrl'"
|
||||
Publish-AzureRmVMDscConfiguration `
|
||||
-ConfigurationPath $dscFile `
|
||||
-ConfigurationDataPath $dscConfigDataFile `
|
||||
-OutputArchivePath "$env:TEMP\dsc.zip" `
|
||||
-Force
|
||||
Set-AzureStorageBlobContent `
|
||||
-File "$env:TEMP\dsc.zip" `
|
||||
-Container "configuration" `
|
||||
-Blob "dsc.zip" `
|
||||
-Context $storageAccount.Context `
|
||||
-Force `
|
||||
| Out-Null
|
||||
|
||||
# template deployment parameters
|
||||
$deploymentParams = @{
|
||||
# cmdlet parameters
|
||||
ResourceGroupName = $Cluster
|
||||
TemplateFile = $templateFile
|
||||
TemplateParameterFile = $templateParameterFile
|
||||
|
||||
# template parameters
|
||||
ConfigData = Get-Content $configDataFile -Raw | ConvertFrom-Json | ConvertTo-HashTable
|
||||
DscFileName = Split-Path -Path $dscFile -Leaf
|
||||
DscHash = (Get-FileHash "$env:TEMP\dsc.zip").Hash.Substring(0, 50)
|
||||
DscUrl = $dscUrl
|
||||
Environment = $cluster.VmssEnvironment
|
||||
VhdContainer = $vhdContainer
|
||||
SasToken = $sasToken
|
||||
|
||||
}
|
||||
|
||||
Test-AzureRmResourceGroupDeployment `
|
||||
@deploymentParams `
|
||||
-Verbose `
|
||||
| Select *
|
||||
|
||||
# deploy template
|
||||
Write-Log "Starting deployment to '$Cluster'"
|
||||
$deploymentName = (Get-Date -Format "s") -replace "[^\d]"
|
||||
New-AzureRmResourceGroupDeployment `
|
||||
-Name $deploymentName `
|
||||
@deploymentParams `
|
||||
-Verbose `
|
||||
-Force `
|
||||
| Write-Log
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Queries Azure for clusters
|
||||
|
||||
.DESCRIPTION
|
||||
Filters Azure resource groups by the provided parameters and returns the associated Cluster objects. Supports globs in parameter names.
|
||||
|
||||
.PARAMETER ServiceName
|
||||
Parameter See README for terminology
|
||||
|
||||
.PARAMETER FlightingRing
|
||||
Parameter See README for terminology
|
||||
|
||||
.PARAMETER Region
|
||||
Parameter See README for terminology
|
||||
|
||||
.PARAMETER Index
|
||||
Parameter See README for terminology
|
||||
|
||||
.EXAMPLE
|
||||
An example
|
||||
|
||||
#>
|
||||
function Select-Cluster {
|
||||
[CmdletBinding(DefaultParameterSetName = "Query")]
|
||||
Param(
|
||||
[Parameter(ParameterSetName = "VmssEnvironment")]
|
||||
[VmssEnvironment]$VmssEnvironment,
|
||||
|
||||
[Parameter(ParameterSetName = "Query")]
|
||||
[string]$ServiceName = "*",
|
||||
[Parameter(ParameterSetName = "Query")]
|
||||
[string]$FlightingRing = "*",
|
||||
[Parameter(ParameterSetName = "Query")]
|
||||
[string]$Region = "*",
|
||||
[Parameter(ParameterSetName = "Query")]
|
||||
[string]$Index = "*"
|
||||
)
|
||||
|
||||
$query = switch ([bool]$VmssEnvironment) {
|
||||
$true {"$VmssEnvironment-*"}
|
||||
$false {"$ServiceName-$FlightingRing-$Region-$Index"}
|
||||
}
|
||||
|
||||
return Get-AzureRmResourceGroup `
|
||||
| ? {$_.ResourceGroupName -like $query} `
|
||||
| % {[Cluster]::New($_.ResourceGroupName)}
|
||||
|
||||
}
|
||||
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Selects (ServiceName, FlightingRing, )
|
||||
|
||||
.DESCRIPTION
|
||||
Long description
|
||||
|
||||
.PARAMETER ServiceName
|
||||
Parameter See README for terminology
|
||||
|
||||
.PARAMETER FlightingRing
|
||||
Parameter See README for terminology
|
||||
|
||||
.PARAMETER ConfigType
|
||||
Parameter See README for terminology
|
||||
|
||||
.PARAMETER Container
|
||||
Parameter See README for terminology
|
||||
|
||||
.EXAMPLE
|
||||
An example
|
||||
|
||||
.NOTES
|
||||
General notes
|
||||
#>
|
||||
function Select-ClusterConfig {
|
||||
Param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$ServiceName,
|
||||
[Parameter(Mandatory)]
|
||||
[string]$FlightingRing,
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$ConfigType,
|
||||
[ValidateScript( {Test-Path $_ -PathType Container} )]
|
||||
[string]$DefinitionsContainer = "."
|
||||
)
|
||||
|
||||
$config = $ServiceName, "Default" `
|
||||
| % {"$_.$FlightingRing", $_} `
|
||||
| % {"$DefinitionsContainer\$_.$ConfigType"} `
|
||||
| ? {Test-Path $_} `
|
||||
| Select -First 1
|
||||
|
||||
if ($config) {
|
||||
Write-Log "Using $ConfigType '$(Split-Path $config -Leaf)'"
|
||||
return $config
|
||||
} else {
|
||||
Write-Error "No $ConfigType file found"
|
||||
}
|
||||
|
||||
}
|
34
LICENSE
34
LICENSE
|
@ -1,21 +1,21 @@
|
|||
MIT License
|
||||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
Copyright (c) 2017 Chris Kuech
|
||||
|
||||
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:
|
||||
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 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
|
||||
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,63 @@
|
|||
@{
|
||||
Severity = @(
|
||||
'Error',
|
||||
'Warning',
|
||||
'Information'
|
||||
)
|
||||
ExcludeRules = @(
|
||||
'PSUseOutputTypeCorrectly',
|
||||
'PSUseShouldProcessForStateChangingFunctions'
|
||||
)
|
||||
Rules = @{
|
||||
PSAlignAssignmentStatement = @{
|
||||
Enable = $true
|
||||
CheckHashtable = $true
|
||||
}
|
||||
PSAvoidUsingCmdletAliases = @{
|
||||
# only whitelist verbs from *-Object cmdlets
|
||||
Whitelist = @(
|
||||
'%',
|
||||
'?',
|
||||
'compare',
|
||||
'foreach',
|
||||
'group',
|
||||
'measure',
|
||||
'select',
|
||||
'sort',
|
||||
'tee',
|
||||
'where'
|
||||
)
|
||||
}
|
||||
PSPlaceCloseBrace = @{
|
||||
Enable = $true
|
||||
NoEmptyLineBefore = $false
|
||||
IgnoreOneLineBlock = $true
|
||||
NewLineAfter = $false
|
||||
}
|
||||
PSPlaceOpenBrace = @{
|
||||
Enable = $true
|
||||
OnSameLine = $true
|
||||
NewLineAfter = $true
|
||||
IgnoreOneLineBlock = $true
|
||||
}
|
||||
PSProvideCommentHelp = @{
|
||||
Enable = $true
|
||||
ExportedOnly = $true
|
||||
BlockComment = $true
|
||||
VSCodeSnippetCorrection = $true
|
||||
Placement = "before"
|
||||
}
|
||||
PSUseConsistentIndentation = @{
|
||||
Enable = $true
|
||||
IndentationSize = 4
|
||||
Kind = "space"
|
||||
}
|
||||
PSUseConsistentWhitespace = @{
|
||||
Enable = $true
|
||||
CheckOpenBrace = $true
|
||||
CheckOpenParen = $true
|
||||
CheckOperator = $false
|
||||
CheckSeparator = $true
|
||||
}
|
||||
}
|
||||
}
|
25
README.md
25
README.md
|
@ -1,14 +1,19 @@
|
|||
# Cluster
|
||||
PowerShell module for managing multi-region Azure Virtual Machine Scale Set clusters.
|
||||
|
||||
# Contributing
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
||||
the rights to use your contribution. For details, visit https://cla.microsoft.com.
|
||||
# Terminology
|
||||
|
||||
## Cluster
|
||||
A single resource group. The atomic unit of a service.
|
||||
|
||||
## Environment
|
||||
A set of clusters in a common region sharing a configuration.
|
||||
|
||||
## Flighting Ring
|
||||
A set of environments sharing a common configuration.
|
||||
|
||||
## Service
|
||||
A set of flighting rings containing various versions of a common application.
|
||||
|
||||
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
|
||||
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
|
||||
provided by the bot. You will only need to do this once across all repos using our CLA.
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
|
|
Загрузка…
Ссылка в новой задаче