Skip to content
Draft
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
167 changes: 164 additions & 3 deletions source/Calamari.AzureAppService.Tests/AppServiceBehaviourFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
using Azure.ResourceManager.Storage;
using Azure.ResourceManager.Storage.Models;
using Calamari.AzureAppService.Azure;
using Calamari.Common.FeatureToggles;
using Calamari.Common.Plumbing.FileSystem;
using Calamari.Common.Plumbing.Variables;
using Calamari.Testing;
Expand Down Expand Up @@ -496,7 +495,7 @@ await AssertContent(WebSiteResource.Data.DefaultHostName,
},
secondsBetweenRetries: 10);
}

[Test]
public async Task CanDeployZip_ToLinuxFunctionApp_WithRunFromPackageFlag()
{
Expand Down Expand Up @@ -633,5 +632,167 @@ private void AddVariables(CommandTestBuilderContext context)
context.Variables[SpecialVariables.Action.Azure.AppSettings] = settings.json;
}
}

[TestFixture]
public class WhenUsingAFlexConsumptionLinuxAppService : AppServiceIntegrationTest
{
static readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource();
readonly CancellationToken cancellationToken = CancellationTokenSource.Token;
AppServicePlanResource appServicePlanResource;

protected override async Task ConfigureTestResources(ResourceGroupResource resourceGroup)
{
//just generate a completely unique name
var storageAccountName = AzureTestResourceHelpers.RandomName(length: 24);

var storageAccountResponse = await ResourceGroupResource
.GetStorageAccounts()
.CreateOrUpdateAsync(WaitUntil.Completed,
storageAccountName,
new StorageAccountCreateOrUpdateContent(
new StorageSku(StorageSkuName.StandardLrs),
StorageKind.StorageV2,
ResourceGroupResource.Data.Location)
);

var blobService = storageAccountResponse.Value.GetBlobService();
var containerName = $"app-package-{resourceGroup.Data.Name}-linux";
await blobService.GetBlobContainers()
.CreateOrUpdateAsync(WaitUntil.Completed, containerName, new BlobContainerData());

var keys = await storageAccountResponse
.Value
.GetKeysAsync()
.ToListAsync();

var linuxAppServicePlan = await resourceGroup.GetAppServicePlans()
.CreateOrUpdateAsync(WaitUntil.Completed,
$"{resourceGroup.Data.Name}-linux-asp",
new AppServicePlanData(resourceGroup.Data.Location)
{
Sku = new AppServiceSkuDescription
{
Name = "FC1",
Tier = "FlexConsumption"
},
Kind = "linux",
IsReserved = true
});

await linuxAppServicePlan.WaitForCompletionAsync(cancellationToken);

appServicePlanResource = linuxAppServicePlan.Value;

var storageAccountConnectionString = $"DefaultEndpointsProtocol=https;AccountName={storageAccountName};AccountKey={keys.First().Value};EndpointSuffix=core.windows.net";
var linuxWebSiteResponse = await resourceGroup.GetWebSites()
.CreateOrUpdateAsync(WaitUntil.Completed,
$"{resourceGroup.Data.Name}-linux",
new WebSiteData(resourceGroup.Data.Location)
{
AppServicePlanId = linuxAppServicePlan.Value.Id,
Kind = "functionapp,linux",
FunctionAppConfig = new FunctionAppConfig
{
DeploymentStorage = new FunctionAppStorage
{
StorageType = FunctionAppStorageType.BlobContainer,
Authentication = new FunctionAppStorageAuthentication
{
AuthenticationType = FunctionAppStorageAccountAuthenticationType.StorageAccountConnectionString,
StorageAccountConnectionStringName = "AzureWebJobsStorage"
},
Value = new Uri($"https://{storageAccountName}.blob.core.windows.net/app-package-{resourceGroup.Data.Name}-linux")
},
Runtime = new FunctionAppRuntime()
{
Name = "dotnet-isolated",
Version = "10.0"
},
ScaleAndConcurrency = new FunctionAppScaleAndConcurrency
{
InstanceMemoryMB = 512,
MaximumInstanceCount = 1,
}
},
IsReserved = true,
SiteConfig = new SiteConfigProperties
{
Use32BitWorkerProcess = false,
AppSettings = new List<AppServiceNameValuePair>
{
new() { Name = "FUNCTIONS_EXTENSION_VERSION", Value = "~4" },
new() { Name = "AzureWebJobsStorage", Value = storageAccountConnectionString },
new() { Name = "WEBSITES_CONTAINER_START_TIME_LIMIT", Value = "460" },
new() { Name = "WEBSITE_SCM_ALWAYS_ON_ENABLED", Value = "true" }
}
}
});

await linuxWebSiteResponse.WaitForCompletionAsync(cancellationToken);

WebSiteResource = linuxWebSiteResponse.Value;
}

[Test]
public async Task CanDeployZip_ToLinuxFunctionApp()
{
// Arrange
var packageInfo = PrepareZipPackage();

// Act
await CommandTestBuilder.CreateAsync<DeployAzureAppServiceCommand, Program>()
.WithArrange(context =>
{
context.WithPackage(packageInfo.packagePath, packageInfo.packageName, packageInfo.packageVersion);
AddVariables(context);
})
.Execute();

// Assert
await DoWithRetries(2,
async () =>
{
await AssertContent(WebSiteResource.Data.DefaultHostName,
rootPath: $"api/HttpExample?name={greeting}",
actualText: $"Hello, {greeting}");
},
secondsBetweenRetries: 10);
}

private static (string packagePath, string packageName, string packageVersion) PrepareZipPackage()
{
// Looks like there's some file locking issues if multiple tests try to copy from the same file when running in parallel.
// For each test that needs one, create a temporary copy.
(string packagePath, string packageName, string packageVersion) packageInfo;

var tempPath = TemporaryDirectory.Create();
new DirectoryInfo(tempPath.DirectoryPath).CreateSubdirectory("AzureZipDeployPackage");

var testAssemblyLocation = new FileInfo(Assembly.GetExecutingAssembly().Location);
var sourceZip = Path.Combine(testAssemblyLocation.Directory.FullName, "functionapp.8.0.0.zip");
var temporaryZipLocationForTest = $"{tempPath.DirectoryPath}/functionapp.8.0.0.zip";
File.Copy(sourceZip, temporaryZipLocationForTest);

packageInfo.packagePath = temporaryZipLocationForTest;
packageInfo.packageVersion = "8.0.0";
packageInfo.packageName = "functionapp";

return packageInfo;
}

private void AddVariables(CommandTestBuilderContext context)
{
AddAzureVariables(context);
context.Variables.Add(SpecialVariables.Action.Azure.DeploymentType, "ZipDeploy");

var settings = BuildAppSettingsJson(new[]
{
("WEBSITES_CONTAINER_START_TIME_LIMIT", "460", false),
("WEBSITE_SCM_ALWAYS_ON_ENABLED", "true", false)
});

context.Variables[SpecialVariables.Action.Azure.AppSettings] = settings.json;
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
using Calamari.Testing;
using Calamari.Testing.Azure;
using FluentAssertions;
using JetBrains.TeamCity.ServiceMessages.Write.Special.Impl.Writer;
using Newtonsoft.Json;
using NUnit.Framework;
using Octostache;
Expand All @@ -42,11 +41,11 @@ public abstract class AppServiceIntegrationTest
protected ResourceGroupResource ResourceGroupResource { get; private set; }
protected WebSiteResource WebSiteResource { get; private protected set; }

private readonly HttpClient client = new HttpClient();
private readonly HttpClient client = new();

protected virtual string DefaultResourceGroupLocation => RandomAzureRegion.GetRandomRegionWithExclusions();

static readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource();
static readonly CancellationTokenSource CancellationTokenSource = new();
readonly CancellationToken cancellationToken = CancellationTokenSource.Token;

[OneTimeSetUp]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,8 @@
<None Update="functionapp.1.0.0.zip">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="functionapp.8.0.0.zip">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
</Project>
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,11 @@ public interface IPackageProvider
Task<FileInfo> PackageArchive(string sourceDirectory, string targetDirectory);

Task<FileInfo> ConvertToAzureSupportedFile(FileInfo sourceFile);

string ContentType { get; }

string AdditionalParameters { get; }

string PublishingProfileMethod { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Calamari.Common.Features.Packages;
using Calamari.Common.Features.Packages.Java;
using Calamari.Common.Features.Processes;
using Calamari.Common.Plumbing.Extensions;
using Calamari.Common.Plumbing.FileSystem;
using Calamari.Common.Plumbing.Logging;
using Calamari.Common.Plumbing.Variables;
Expand Down Expand Up @@ -56,6 +55,8 @@ await Task.Run(() =>
}

public async Task<FileInfo> ConvertToAzureSupportedFile(FileInfo sourceFile) => await Task.Run(() => sourceFile);

public string ContentType => "application/octet-stream";
public string AdditionalParameters => string.Empty;
public string PublishingProfileMethod => "MSDeploy";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,9 @@ public async Task<FileInfo> ConvertToAzureSupportedFile(FileInfo sourceFile)
await Task.Run(() => File.Copy(sourceFile.FullName, newFilePath));
return new FileInfo(newFilePath);
}

public string ContentType => "application/octet-stream";
public string AdditionalParameters => string.Empty;
public string PublishingProfileMethod => "MSDeploy";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.IO;
using System.Threading.Tasks;
using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
using SharpCompress.Common;

namespace Calamari.AzureAppService;

public abstract class BaseZipPackageProvider : IPackageProvider
{
public abstract string UploadUrlPath { get; }
public abstract bool SupportsAsynchronousDeployment { get; }

public async Task<FileInfo> PackageArchive(string sourceDirectory, string targetDirectory)
{
await Task.Run(() =>
{
using var archive = ZipArchive.Create();
archive.AddAllFromDirectory(
$"{sourceDirectory}");
archive.SaveTo($"{targetDirectory}/app.zip", CompressionType.Deflate);
});
return new FileInfo($"{targetDirectory}/app.zip");
}

public async Task<FileInfo> ConvertToAzureSupportedFile(FileInfo sourceFile) => await Task.Run(() => sourceFile);

public abstract string ContentType { get; }
public abstract string AdditionalParameters { get; }
public string PublishingProfileMethod => "ZipDeploy";
}

public class OneDeployZipPackageProvider : BaseZipPackageProvider
{
public override string UploadUrlPath => @"/api/publish";
public override bool SupportsAsynchronousDeployment => false;
public override string ContentType => @"application/zip";
public override string AdditionalParameters => string.Empty; //"?deployer=Octopus&isAsync=true";
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,11 @@

namespace Calamari.AzureAppService
{
public class ZipPackageProvider : IPackageProvider
public class ZipPackageProvider : BaseZipPackageProvider
{
public string UploadUrlPath => @"/api/zipdeploy";
public bool SupportsAsynchronousDeployment => true;

public async Task<FileInfo> PackageArchive(string sourceDirectory, string targetDirectory)
{
await Task.Run(() =>
{
using var archive = ZipArchive.Create();
archive.AddAllFromDirectory(
$"{sourceDirectory}");
archive.SaveTo($"{targetDirectory}/app.zip", CompressionType.Deflate);
});
return new FileInfo($"{targetDirectory}/app.zip");
}

public async Task<FileInfo> ConvertToAzureSupportedFile(FileInfo sourceFile) => await Task.Run(() => sourceFile);
public override bool SupportsAsynchronousDeployment => true;
public override string UploadUrlPath => @"/api/zipdeploy";
public override string ContentType => "application/octet-stream";
public override string AdditionalParameters => "?isAsync=true";
}
}
Loading