Skip to content

Commit 95c6d74

Browse files
author
Hovsep
committed
Merge pull request Azure#1619 from naveedaz/dev
Add functionality to clone all deployment slots for a source web app
2 parents bd8cecb + aa025bb commit 95c6d74

File tree

11 files changed

+11396
-14
lines changed

11 files changed

+11396
-14
lines changed

ChangeLog.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
* New-AzureRmWebAppSSLBinding
1010
* Get-AzureRmWebAppSSLBinding
1111
* Remove-AzureRmWebAppSSLBinding
12-
* Azure Websites: Added AseName and AseResourceGroupName parameters in New-AzureRmWebApp and New-AzureRmAppServicePlan cmdlet
12+
* Added AseName and AseResourceGroupName parameters in New-AzureRmWebApp and New-AzureRmAppServicePlan cmdlet
13+
* Added support for cloning all deployment slots associated with source website
1314
* Azure Stream Analytics: Added new cmdlet support for Functions.
1415
* New-AzureRmStreamAnalyticsFunction
1516
* Get-AzureRmStreamAnalyticsFunction

src/ResourceManager/Resources/Commands.Resources/Models.ResourceGroups/ResourceClient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ private void WriteError(string error)
207207
}
208208
}
209209

210-
private DeploymentExtended ProvisionDeploymentStatus(string resourceGroup, string deploymentName, Deployment deployment)
210+
public DeploymentExtended ProvisionDeploymentStatus(string resourceGroup, string deploymentName, Deployment deployment)
211211
{
212212
operations = new List<DeploymentOperation>();
213213

src/ResourceManager/Websites/Commands.Websites.Test/Commands.Websites.Test.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,9 @@
231231
<None Include="SessionRecords\Microsoft.Azure.Commands.Websites.Test.ScenarioTests.WebAppTests\TestCloneNewWebApp.json">
232232
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
233233
</None>
234+
<None Include="SessionRecords\Microsoft.Azure.Commands.Websites.Test.ScenarioTests.WebAppTests\TestCloneNewWebAppAndDeploymentSlots.json">
235+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
236+
</None>
234237
<None Include="SessionRecords\Microsoft.Azure.Commands.Websites.Test.ScenarioTests.WebAppTests\TestCloneNewWebAppWithNewTrafficManager.json">
235238
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
236239
</None>

src/ResourceManager/Websites/Commands.Websites.Test/ScenarioTests/WebAppTests.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ public void TestCloneNewWebApp()
6363
WebsitesController.NewInstance.RunPsTest("Test-CloneNewWebApp");
6464
}
6565

66+
[Fact]
67+
[Trait(Category.AcceptanceType, Category.CheckIn)]
68+
public void TestCloneNewWebAppAndDeploymentSlots()
69+
{
70+
WebsitesController.NewInstance.RunPsTest("Test-CloneNewWebAppAndDeploymentSlots");
71+
}
72+
6673
[Fact]
6774
[Trait(Category.AcceptanceType, Category.CheckIn)]
6875
public void TestCloneNewWebAppWithNewTrafficManager()

src/ResourceManager/Websites/Commands.Websites.Test/ScenarioTests/WebAppTests.ps1

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,112 @@ function Test-CloneNewWebApp
297297
}
298298
}
299299

300+
<#
301+
.SYNOPSIS
302+
Tests clone a website.
303+
#>
304+
function Test-CloneNewWebAppAndDeploymentSlots
305+
{
306+
# Setup
307+
$rgname = Get-ResourceGroupName
308+
$appname = Get-WebsiteName
309+
$slot1name = "staging"
310+
$slot2name = "testing"
311+
$location = Get-Location
312+
$planName = Get-WebHostPlanName
313+
$tier = "Premium"
314+
$apiversion = "2015-08-01"
315+
$resourceType = "Microsoft.Web/sites"
316+
317+
# Destination setup
318+
$destPlanName = Get-WebHostPlanName
319+
$destLocation = Get-SecondaryLocation
320+
$destAppName = Get-WebsiteName
321+
322+
try
323+
{
324+
#Setup
325+
New-AzureRmResourceGroup -Name $rgname -Location $location
326+
$serverFarm = New-AzureRmAppServicePlan -ResourceGroupName $rgname -Name $planName -Location $location -Tier $tier
327+
328+
# Create new web app
329+
$webapp = New-AzureRmWebApp -ResourceGroupName $rgname -Name $appname -Location $location -AppServicePlan $planName
330+
331+
# Assert
332+
Assert-AreEqual $appname $webapp.Name
333+
Assert-AreEqual $serverFarm.Id $webapp.ServerFarmId
334+
335+
# Get new web app
336+
$webapp = Get-AzureRmWebApp -ResourceGroupName $rgname -Name $appname
337+
338+
# Assert
339+
Assert-AreEqual $appname $webapp.Name
340+
Assert-AreEqual $serverFarm.Id $webapp.ServerFarmId
341+
342+
# Create deployment slot 1
343+
$slot1 = New-AzureRmWebAppSlot -ResourceGroupName $rgname -Name $appname -Slot $slot1name -AppServicePlan $planName
344+
$appWithSlotName = "$appname/$slot1name"
345+
346+
# Assert
347+
Assert-AreEqual $appWithSlotName $slot1.Name
348+
Assert-AreEqual $serverFarm.Id $slot1.ServerFarmId
349+
350+
# Create deployment slot 2
351+
$slot2 = New-AzureRmWebAppSlot -ResourceGroupName $rgname -Name $appname -Slot $slot2name -AppServicePlan $planName
352+
$appWithSlotName = "$appname/$slot2name"
353+
354+
# Assert
355+
Assert-AreEqual $appWithSlotName $slot2.Name
356+
Assert-AreEqual $serverFarm.Id $slot2.ServerFarmId
357+
358+
# Create new server Farm
359+
$serverFarm2 = New-AzureRmAppServicePlan -ResourceGroupName $rgname -Name $destPlanName -Location $destLocation -Tier $tier
360+
361+
# Clone web app
362+
$webapp2 = New-AzureRmWebApp -ResourceGroupName $rgname -Name $destAppName -Location $destLocation -AppServicePlan $destPlanName -SourceWebApp $webapp -IncludeSourceWebAppSlots
363+
364+
# Assert
365+
Assert-AreEqual $destAppName $webapp2.Name
366+
367+
# Get new web app
368+
$webapp2 = Get-AzureRmWebApp -ResourceGroupName $rgname -Name $destAppName
369+
370+
# Assert
371+
Assert-AreEqual $destAppName $webapp2.Name
372+
373+
# Get new web app slot1
374+
$slot1 = Get-AzureRmWebAppSlot -ResourceGroupName $rgname -Name $destAppName -Slot $slot1name
375+
376+
$appWithSlotName = "$destAppName/$slot1name"
377+
378+
# Assert
379+
Assert-AreEqual $appWithSlotName $slot1.Name
380+
Assert-AreEqual $serverFarm2.Id $slot1.ServerFarmId
381+
382+
# Get new web app slot1
383+
$slot2 = Get-AzureRmWebAppSlot -ResourceGroupName $rgname -Name $destAppName -Slot $slot2name
384+
$appWithSlotName = "$destAppName/$slot2name"
385+
386+
# Assert
387+
Assert-AreEqual $appWithSlotName $slot2.Name
388+
Assert-AreEqual $serverFarm2.Id $slot2.ServerFarmId
389+
}
390+
finally
391+
{
392+
# Cleanup
393+
Remove-AzureRmWebAppSlot -ResourceGroupName $rgname -Name $appname -Slot $slot1name -Force
394+
Remove-AzureRmWebAppSlot -ResourceGroupName $rgname -Name $appname -Slot $slot2name -Force
395+
Remove-AzureRmWebApp -ResourceGroupName $rgname -Name $appname -Force
396+
Remove-AzureRmAppServicePlan -ResourceGroupName $rgname -Name $planName -Force
397+
398+
Remove-AzureRmWebAppSlot -ResourceGroupName $rgname -Name $destAppName -Slot $slot1name -Force
399+
Remove-AzureRmWebAppSlot -ResourceGroupName $rgname -Name $destAppName -Slot $slot2name -Force
400+
Remove-AzureRmWebApp -ResourceGroupName $rgname -Name $destAppName -Force
401+
Remove-AzureRmAppServicePlan -ResourceGroupName $rgname -Name $destPlanName -Force
402+
Remove-AzureRmResourceGroup -Name $rgname -Force
403+
}
404+
}
405+
300406
<#
301407
.SYNOPSIS
302408
Tests clone a website.

src/ResourceManager/Websites/Commands.Websites.Test/SessionRecords/Microsoft.Azure.Commands.Websites.Test.ScenarioTests.WebAppTests/TestCloneNewWebAppAndDeploymentSlots.json

Lines changed: 11059 additions & 0 deletions
Large diffs are not rendered by default.

src/ResourceManager/Websites/Commands.Websites/Cmdlets/WebApps/NewAzureWebApp.cs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@
1818
using System.Collections.Generic;
1919
using System.Linq;
2020
using System.Management.Automation;
21+
using Microsoft.Azure.Commands.Resources.Models;
2122
using Microsoft.Azure.Commands.WebApps.Models;
23+
using Microsoft.Azure.Commands.WebApps.Models.WebApp;
24+
using Microsoft.Azure.Commands.WebApps.Utilities;
25+
using Microsoft.Azure.Management.Resources;
26+
using Microsoft.Azure.Management.Resources.Models;
2227
using Microsoft.Azure.Management.WebSites.Models;
2328
using Microsoft.PowerShell;
2429
using Microsoft.WindowsAzure.Commands.Common;
@@ -80,6 +85,10 @@ public class NewAzureWebAppCmdlet : WebAppBaseClientCmdLet
8085
[ValidateNotNullOrEmpty]
8186
public string AseResourceGroupName { get; set; }
8287

88+
[Parameter(Position = 10, Mandatory = false, HelpMessage = "Clones slots associated with source web app")]
89+
[ValidateNotNullOrEmpty]
90+
public SwitchParameter IncludeSourceWebAppSlots { get; set; }
91+
8392
public override void ExecuteCmdlet()
8493
{
8594
CloningInfo cloningInfo = null;
@@ -97,7 +106,56 @@ public override void ExecuteCmdlet()
97106
};
98107
}
99108

109+
var cloneWebAppSlots = false;
110+
string[] slotNames = null;
111+
string srcResourceGroupName = null;
112+
string srcwebAppName = null;
113+
string srcSlotName = null;
114+
if (IncludeSourceWebAppSlots.IsPresent)
115+
{
116+
CmdletHelpers.TryParseWebAppMetadataFromResourceId(SourceWebApp.Id, out srcResourceGroupName,
117+
out srcwebAppName, out srcSlotName);
118+
var slots = WebsitesClient.ListWebApps(srcResourceGroupName, srcwebAppName);
119+
if (slots != null && slots.Any())
120+
{
121+
slotNames = slots.Select(s => s.Name.Replace(srcwebAppName + "/", string.Empty)).ToArray();
122+
cloneWebAppSlots = true;
123+
}
124+
}
125+
126+
if (cloneWebAppSlots)
127+
{
128+
WriteVerboseWithTimestamp("Cloning source web app '{0}' to destination web app {1}", srcwebAppName, Name);
129+
}
130+
100131
WriteObject(WebsitesClient.CreateWebApp(ResourceGroupName, Name, null, Location, AppServicePlan, cloningInfo, AseName, AseResourceGroupName));
132+
133+
if (cloneWebAppSlots)
134+
{
135+
WriteVerboseWithTimestamp("Cloning all deployment slots of source web app '{0}' to destination web app {1}", srcwebAppName, Name);
136+
CloneSlots(slotNames);
137+
}
138+
}
139+
140+
private void CloneSlots(string[] slotNames)
141+
{
142+
var hostingEnvironmentProfile = WebsitesClient.CreateHostingEnvironmentProfile(ResourceGroupName, AseResourceGroupName, AseName);
143+
var template = DeploymentTemplateHelper.CreateSlotCloneDeploymentTemplate(Location, AppServicePlan, Name, SourceWebApp.Id,
144+
slotNames, hostingEnvironmentProfile, WebsitesClient.WrappedWebsitesClient.ApiVersion);
145+
146+
var deployment = new Management.Resources.Models.Deployment
147+
{
148+
Properties = new DeploymentProperties
149+
{
150+
Mode = DeploymentMode.Incremental,
151+
Template = template
152+
}
153+
};
154+
155+
var deploymentName = string.Format("CloneSlotsFor{0}", Name);
156+
ResourcesClient.ResourceManagementClient.Deployments.CreateOrUpdate(ResourceGroupName, deploymentName, deployment);
157+
var result = ResourcesClient.ProvisionDeploymentStatus(ResourceGroupName, deploymentName, deployment);
158+
WriteObject(result.ToPSResourceGroupDeployment(ResourceGroupName));
101159
}
102160
}
103161
}

src/ResourceManager/Websites/Commands.Websites/Commands.Websites.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@
172172
<Compile Include="Cmdlets\AppServicePlans\NewAzureAppServicePlan.cs" />
173173
<Compile Include="Cmdlets\AppServicePlans\RemoveAppServicePlan.cs" />
174174
<Compile Include="Models.WebApp\AppServicePlanBaseCmdlet.cs" />
175+
<Compile Include="Models.WebApp\DeploymentTemplate.cs" />
175176
<Compile Include="Models.WebApp\WebAppBaseCmdlet.cs" />
176177
<Compile Include="Models.WebApp\WebAppBaseClient.cs" />
177178
<Compile Include="Models.WebApp\WebAppSlotBaseCmdlet.cs" />
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using Microsoft.Azure.Management.WebSites.Models;
8+
using Newtonsoft.Json;
9+
using Newtonsoft.Json.Schema;
10+
using Newtonsoft.Json.Serialization;
11+
12+
namespace Microsoft.Azure.Commands.WebApps.Models.WebApp
13+
{
14+
internal class DeploymentTemplate
15+
{
16+
[JsonProperty("$schema")]
17+
public string Schema { get; set; }
18+
19+
public string ContentVersion { get; set; }
20+
21+
public Dictionary<string, ParameterType> Parameters { get; set; }
22+
23+
public Dictionary<string, object> Variables { get; set; }
24+
25+
public WebAppResource[] Resources { get; set; }
26+
27+
}
28+
29+
internal class ParameterType
30+
{
31+
public string Type { get; set; }
32+
33+
public string AllowedValues { get; set; }
34+
}
35+
36+
internal class WebAppResource
37+
{
38+
public string Name { get; set; }
39+
40+
public string ApiVersion { get; set; }
41+
42+
public string Type { get; set; }
43+
44+
public string Location { get; set; }
45+
46+
public string[] DependsOn { get; set; }
47+
48+
public CopyFunction Copy { get; set; }
49+
50+
public WebAppProperties Properties { get; set; }
51+
}
52+
53+
internal class CopyFunction
54+
{
55+
public string Name { get; set; }
56+
57+
public string Count { get; set; }
58+
}
59+
60+
internal class WebAppProperties
61+
{
62+
public string ServerFarmId { get; set; }
63+
64+
public CloningInfo CloningInfo { get; set; }
65+
66+
public HostingEnvironmentProfile HostingEnvironmentProfile { get; set; }
67+
}
68+
69+
70+
internal static class DeploymentTemplateHelper
71+
{
72+
private const string WebAppSlotName = "[concat(variables('webAppName'), '/', variables('slotNames')[copyIndex()])]";
73+
private const string SourceWebAppSlotId = "[concat(variables('sourceWebAppId'), '/slots/', variables('slotNames')[copyIndex()])]";
74+
private const string WebAppSlotResourceType = "Microsoft.Web/sites/slots";
75+
private const string WebAppSlotCount = "[length(variables('slotNames'))]";
76+
private const string ContentVersion = "1.0.0.0";
77+
78+
internal static string ToJsonString(this DeploymentTemplate template)
79+
{
80+
var serializationSettings = new JsonSerializerSettings
81+
{
82+
NullValueHandling = NullValueHandling.Ignore,
83+
ContractResolver = new CamelCasePropertyNamesContractResolver()
84+
};
85+
86+
var serializer = JsonSerializer.Create(serializationSettings);
87+
var textWriter = new StringWriter();
88+
serializer.Serialize(textWriter, template);
89+
return textWriter.ToString();
90+
}
91+
92+
internal static string CreateSlotCloneDeploymentTemplate(string location, string serverFarmId, string destinationWebAppName, string sourceWebAppId, string[] slotNames, HostingEnvironmentProfile hostingProfile, string apiVersion)
93+
{
94+
var template = new DeploymentTemplate
95+
{
96+
ContentVersion = ContentVersion,
97+
Schema = "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
98+
Variables = new Dictionary<string, object>
99+
{
100+
{ "slotNames", slotNames },
101+
{ "webAppName", destinationWebAppName },
102+
{ "sourceWebAppId", sourceWebAppId }
103+
},
104+
Resources = new WebAppResource[]
105+
{
106+
new WebAppResource
107+
{
108+
Type = WebAppSlotResourceType,
109+
ApiVersion = apiVersion,
110+
Location = location,
111+
Name = WebAppSlotName,
112+
Properties = new WebAppProperties
113+
{
114+
CloningInfo = new CloningInfo
115+
{
116+
SourceWebAppId = SourceWebAppSlotId
117+
},
118+
ServerFarmId = serverFarmId,
119+
HostingEnvironmentProfile = hostingProfile
120+
},
121+
Copy = new CopyFunction
122+
{
123+
Name = "SlotCopy",
124+
Count = WebAppSlotCount
125+
}
126+
}
127+
}
128+
};
129+
130+
return template.ToJsonString();
131+
}
132+
}
133+
}

src/ResourceManager/Websites/Commands.Websites/Utilities/CmdletHelpers.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,18 @@ internal static bool ShouldUseDeploymentSlot(string webSiteName, string slotName
7777
return result;
7878
}
7979

80+
internal static HostingEnvironmentProfile CreateHostingEnvironmentProfile(string subscriptionId, string resourceGroupName, string aseResourceGroupName, string aseName)
81+
{
82+
var rg = string.IsNullOrEmpty(aseResourceGroupName) ? resourceGroupName : aseResourceGroupName;
83+
var aseResourceId = CmdletHelpers.GetApplicationServiceEnvironmentResourceId(subscriptionId, rg, aseName);
84+
return new HostingEnvironmentProfile
85+
{
86+
Id = aseResourceId,
87+
Type = CmdletHelpers.ApplicationServiceEnvironmentResourcesName,
88+
Name = aseName
89+
};
90+
}
91+
8092
internal static string BuildMetricFilter(DateTime? startTime, DateTime? endTime, string timeGrain, IReadOnlyList<string> metricNames)
8193
{
8294
var dateTimeFormat = "yyyy-MM-ddTHH:mm:ssZ";

0 commit comments

Comments
 (0)