Skip to content

Add functionality to clone all deployment slots for a source web app #1619

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 11, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
* New-AzureRmWebAppSSLBinding
* Get-AzureRmWebAppSSLBinding
* Remove-AzureRmWebAppSSLBinding
* Azure Websites: Added AseName and AseResourceGroupName parameters in New-AzureRmWebApp and New-AzureRmAppServicePlan cmdlet
* Added AseName and AseResourceGroupName parameters in New-AzureRmWebApp and New-AzureRmAppServicePlan cmdlet
* Added support for cloning all deployment slots associated with source website
* Azure Stream Analytics: Added new cmdlet support for Functions.
* New-AzureRmStreamAnalyticsFunction
* Get-AzureRmStreamAnalyticsFunction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ private void WriteError(string error)
}
}

private DeploymentExtended ProvisionDeploymentStatus(string resourceGroup, string deploymentName, Deployment deployment)
public DeploymentExtended ProvisionDeploymentStatus(string resourceGroup, string deploymentName, Deployment deployment)
{
operations = new List<DeploymentOperation>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,9 @@
<None Include="SessionRecords\Microsoft.Azure.Commands.Websites.Test.ScenarioTests.WebAppTests\TestCloneNewWebApp.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="SessionRecords\Microsoft.Azure.Commands.Websites.Test.ScenarioTests.WebAppTests\TestCloneNewWebAppAndDeploymentSlots.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="SessionRecords\Microsoft.Azure.Commands.Websites.Test.ScenarioTests.WebAppTests\TestCloneNewWebAppWithNewTrafficManager.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ public void TestCloneNewWebApp()
WebsitesController.NewInstance.RunPsTest("Test-CloneNewWebApp");
}

[Fact]
[Trait(Category.AcceptanceType, Category.CheckIn)]
public void TestCloneNewWebAppAndDeploymentSlots()
{
WebsitesController.NewInstance.RunPsTest("Test-CloneNewWebAppAndDeploymentSlots");
}

[Fact]
[Trait(Category.AcceptanceType, Category.CheckIn)]
public void TestCloneNewWebAppWithNewTrafficManager()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,112 @@ function Test-CloneNewWebApp
}
}

<#
.SYNOPSIS
Tests clone a website.
#>
function Test-CloneNewWebAppAndDeploymentSlots
{
# Setup
$rgname = Get-ResourceGroupName
$appname = Get-WebsiteName
$slot1name = "staging"
$slot2name = "testing"
$location = Get-Location
$planName = Get-WebHostPlanName
$tier = "Premium"
$apiversion = "2015-08-01"
$resourceType = "Microsoft.Web/sites"

# Destination setup
$destPlanName = Get-WebHostPlanName
$destLocation = Get-SecondaryLocation
$destAppName = Get-WebsiteName

try
{
#Setup
New-AzureRmResourceGroup -Name $rgname -Location $location
$serverFarm = New-AzureRmAppServicePlan -ResourceGroupName $rgname -Name $planName -Location $location -Tier $tier

# Create new web app
$webapp = New-AzureRmWebApp -ResourceGroupName $rgname -Name $appname -Location $location -AppServicePlan $planName

# Assert
Assert-AreEqual $appname $webapp.Name
Assert-AreEqual $serverFarm.Id $webapp.ServerFarmId

# Get new web app
$webapp = Get-AzureRmWebApp -ResourceGroupName $rgname -Name $appname

# Assert
Assert-AreEqual $appname $webapp.Name
Assert-AreEqual $serverFarm.Id $webapp.ServerFarmId

# Create deployment slot 1
$slot1 = New-AzureRmWebAppSlot -ResourceGroupName $rgname -Name $appname -Slot $slot1name -AppServicePlan $planName
$appWithSlotName = "$appname/$slot1name"

# Assert
Assert-AreEqual $appWithSlotName $slot1.Name
Assert-AreEqual $serverFarm.Id $slot1.ServerFarmId

# Create deployment slot 2
$slot2 = New-AzureRmWebAppSlot -ResourceGroupName $rgname -Name $appname -Slot $slot2name -AppServicePlan $planName
$appWithSlotName = "$appname/$slot2name"

# Assert
Assert-AreEqual $appWithSlotName $slot2.Name
Assert-AreEqual $serverFarm.Id $slot2.ServerFarmId

# Create new server Farm
$serverFarm2 = New-AzureRmAppServicePlan -ResourceGroupName $rgname -Name $destPlanName -Location $destLocation -Tier $tier

# Clone web app
$webapp2 = New-AzureRmWebApp -ResourceGroupName $rgname -Name $destAppName -Location $destLocation -AppServicePlan $destPlanName -SourceWebApp $webapp -IncludeSourceWebAppSlots

# Assert
Assert-AreEqual $destAppName $webapp2.Name

# Get new web app
$webapp2 = Get-AzureRmWebApp -ResourceGroupName $rgname -Name $destAppName

# Assert
Assert-AreEqual $destAppName $webapp2.Name

# Get new web app slot1
$slot1 = Get-AzureRmWebAppSlot -ResourceGroupName $rgname -Name $destAppName -Slot $slot1name

$appWithSlotName = "$destAppName/$slot1name"

# Assert
Assert-AreEqual $appWithSlotName $slot1.Name
Assert-AreEqual $serverFarm2.Id $slot1.ServerFarmId

# Get new web app slot1
$slot2 = Get-AzureRmWebAppSlot -ResourceGroupName $rgname -Name $destAppName -Slot $slot2name
$appWithSlotName = "$destAppName/$slot2name"

# Assert
Assert-AreEqual $appWithSlotName $slot2.Name
Assert-AreEqual $serverFarm2.Id $slot2.ServerFarmId
}
finally
{
# Cleanup
Remove-AzureRmWebAppSlot -ResourceGroupName $rgname -Name $appname -Slot $slot1name -Force
Remove-AzureRmWebAppSlot -ResourceGroupName $rgname -Name $appname -Slot $slot2name -Force
Remove-AzureRmWebApp -ResourceGroupName $rgname -Name $appname -Force
Remove-AzureRmAppServicePlan -ResourceGroupName $rgname -Name $planName -Force

Remove-AzureRmWebAppSlot -ResourceGroupName $rgname -Name $destAppName -Slot $slot1name -Force
Remove-AzureRmWebAppSlot -ResourceGroupName $rgname -Name $destAppName -Slot $slot2name -Force
Remove-AzureRmWebApp -ResourceGroupName $rgname -Name $destAppName -Force
Remove-AzureRmAppServicePlan -ResourceGroupName $rgname -Name $destPlanName -Force
Remove-AzureRmResourceGroup -Name $rgname -Force
}
}

<#
.SYNOPSIS
Tests clone a website.
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using Microsoft.Azure.Commands.Resources.Models;
using Microsoft.Azure.Commands.WebApps.Models;
using Microsoft.Azure.Commands.WebApps.Models.WebApp;
using Microsoft.Azure.Commands.WebApps.Utilities;
using Microsoft.Azure.Management.Resources;
using Microsoft.Azure.Management.Resources.Models;
using Microsoft.Azure.Management.WebSites.Models;
using Microsoft.PowerShell;
using Microsoft.WindowsAzure.Commands.Common;
Expand Down Expand Up @@ -80,6 +85,10 @@ public class NewAzureWebAppCmdlet : WebAppBaseClientCmdLet
[ValidateNotNullOrEmpty]
public string AseResourceGroupName { get; set; }

[Parameter(Position = 10, Mandatory = false, HelpMessage = "Clones slots associated with source web app")]
[ValidateNotNullOrEmpty]
public SwitchParameter IncludeSourceWebAppSlots { get; set; }

public override void ExecuteCmdlet()
{
CloningInfo cloningInfo = null;
Expand All @@ -97,7 +106,56 @@ public override void ExecuteCmdlet()
};
}

var cloneWebAppSlots = false;
string[] slotNames = null;
string srcResourceGroupName = null;
string srcwebAppName = null;
string srcSlotName = null;
if (IncludeSourceWebAppSlots.IsPresent)
{
CmdletHelpers.TryParseWebAppMetadataFromResourceId(SourceWebApp.Id, out srcResourceGroupName,
out srcwebAppName, out srcSlotName);
var slots = WebsitesClient.ListWebApps(srcResourceGroupName, srcwebAppName);
if (slots != null && slots.Any())
{
slotNames = slots.Select(s => s.Name.Replace(srcwebAppName + "/", string.Empty)).ToArray();
cloneWebAppSlots = true;
}
}

if (cloneWebAppSlots)
{
WriteVerboseWithTimestamp("Cloning source web app '{0}' to destination web app {1}", srcwebAppName, Name);
}

WriteObject(WebsitesClient.CreateWebApp(ResourceGroupName, Name, null, Location, AppServicePlan, cloningInfo, AseName, AseResourceGroupName));

if (cloneWebAppSlots)
{
WriteVerboseWithTimestamp("Cloning all deployment slots of source web app '{0}' to destination web app {1}", srcwebAppName, Name);
CloneSlots(slotNames);
}
}

private void CloneSlots(string[] slotNames)
{
var hostingEnvironmentProfile = WebsitesClient.CreateHostingEnvironmentProfile(ResourceGroupName, AseResourceGroupName, AseName);
var template = DeploymentTemplateHelper.CreateSlotCloneDeploymentTemplate(Location, AppServicePlan, Name, SourceWebApp.Id,
slotNames, hostingEnvironmentProfile, WebsitesClient.WrappedWebsitesClient.ApiVersion);

var deployment = new Management.Resources.Models.Deployment
{
Properties = new DeploymentProperties
{
Mode = DeploymentMode.Incremental,
Template = template
}
};

var deploymentName = string.Format("CloneSlotsFor{0}", Name);
ResourcesClient.ResourceManagementClient.Deployments.CreateOrUpdate(ResourceGroupName, deploymentName, deployment);
var result = ResourcesClient.ProvisionDeploymentStatus(ResourceGroupName, deploymentName, deployment);
WriteObject(result.ToPSResourceGroupDeployment(ResourceGroupName));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@
<Compile Include="Cmdlets\AppServicePlans\NewAzureAppServicePlan.cs" />
<Compile Include="Cmdlets\AppServicePlans\RemoveAppServicePlan.cs" />
<Compile Include="Models.WebApp\AppServicePlanBaseCmdlet.cs" />
<Compile Include="Models.WebApp\DeploymentTemplate.cs" />
<Compile Include="Models.WebApp\WebAppBaseCmdlet.cs" />
<Compile Include="Models.WebApp\WebAppBaseClient.cs" />
<Compile Include="Models.WebApp\WebAppSlotBaseCmdlet.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.Management.WebSites.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Schema;
using Newtonsoft.Json.Serialization;

namespace Microsoft.Azure.Commands.WebApps.Models.WebApp
{
internal class DeploymentTemplate
{
[JsonProperty("$schema")]
public string Schema { get; set; }

public string ContentVersion { get; set; }

public Dictionary<string, ParameterType> Parameters { get; set; }

public Dictionary<string, object> Variables { get; set; }

public WebAppResource[] Resources { get; set; }

}

internal class ParameterType
{
public string Type { get; set; }

public string AllowedValues { get; set; }
}

internal class WebAppResource
{
public string Name { get; set; }

public string ApiVersion { get; set; }

public string Type { get; set; }

public string Location { get; set; }

public string[] DependsOn { get; set; }

public CopyFunction Copy { get; set; }

public WebAppProperties Properties { get; set; }
}

internal class CopyFunction
{
public string Name { get; set; }

public string Count { get; set; }
}

internal class WebAppProperties
{
public string ServerFarmId { get; set; }

public CloningInfo CloningInfo { get; set; }

public HostingEnvironmentProfile HostingEnvironmentProfile { get; set; }
}


internal static class DeploymentTemplateHelper
{
private const string WebAppSlotName = "[concat(variables('webAppName'), '/', variables('slotNames')[copyIndex()])]";
private const string SourceWebAppSlotId = "[concat(variables('sourceWebAppId'), '/slots/', variables('slotNames')[copyIndex()])]";
private const string WebAppSlotResourceType = "Microsoft.Web/sites/slots";
private const string WebAppSlotCount = "[length(variables('slotNames'))]";
private const string ContentVersion = "1.0.0.0";

internal static string ToJsonString(this DeploymentTemplate template)
{
var serializationSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new CamelCasePropertyNamesContractResolver()
};

var serializer = JsonSerializer.Create(serializationSettings);
var textWriter = new StringWriter();
serializer.Serialize(textWriter, template);
return textWriter.ToString();
}

internal static string CreateSlotCloneDeploymentTemplate(string location, string serverFarmId, string destinationWebAppName, string sourceWebAppId, string[] slotNames, HostingEnvironmentProfile hostingProfile, string apiVersion)
{
var template = new DeploymentTemplate
{
ContentVersion = ContentVersion,
Schema = "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
Variables = new Dictionary<string, object>
{
{ "slotNames", slotNames },
{ "webAppName", destinationWebAppName },
{ "sourceWebAppId", sourceWebAppId }
},
Resources = new WebAppResource[]
{
new WebAppResource
{
Type = WebAppSlotResourceType,
ApiVersion = apiVersion,
Location = location,
Name = WebAppSlotName,
Properties = new WebAppProperties
{
CloningInfo = new CloningInfo
{
SourceWebAppId = SourceWebAppSlotId
},
ServerFarmId = serverFarmId,
HostingEnvironmentProfile = hostingProfile
},
Copy = new CopyFunction
{
Name = "SlotCopy",
Count = WebAppSlotCount
}
}
}
};

return template.ToJsonString();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ internal static bool ShouldUseDeploymentSlot(string webSiteName, string slotName
return result;
}

internal static HostingEnvironmentProfile CreateHostingEnvironmentProfile(string subscriptionId, string resourceGroupName, string aseResourceGroupName, string aseName)
{
var rg = string.IsNullOrEmpty(aseResourceGroupName) ? resourceGroupName : aseResourceGroupName;
var aseResourceId = CmdletHelpers.GetApplicationServiceEnvironmentResourceId(subscriptionId, rg, aseName);
return new HostingEnvironmentProfile
{
Id = aseResourceId,
Type = CmdletHelpers.ApplicationServiceEnvironmentResourcesName,
Name = aseName
};
}

internal static string BuildMetricFilter(DateTime? startTime, DateTime? endTime, string timeGrain, IReadOnlyList<string> metricNames)
{
var dateTimeFormat = "yyyy-MM-ddTHH:mm:ssZ";
Expand Down
Loading