2023-10-11 21:40:35 +02:00
|
|
|
|
using System.Text;
|
|
|
|
|
using Azure;
|
|
|
|
|
using Azure.Core;
|
|
|
|
|
using Azure.Identity;
|
|
|
|
|
using Azure.ResourceManager;
|
|
|
|
|
using Azure.ResourceManager.ContainerInstance;
|
|
|
|
|
using Azure.ResourceManager.ContainerInstance.Models;
|
|
|
|
|
using Azure.ResourceManager.Resources;
|
|
|
|
|
using Azure.Storage.Files.Shares;
|
2023-11-09 16:03:38 +00:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
2023-10-11 21:40:35 +02:00
|
|
|
|
using Microsoft.Extensions.Options;
|
|
|
|
|
|
|
|
|
|
|
2023-10-26 22:09:18 +02:00
|
|
|
|
namespace Microsoft.AI.DevTeam;
|
2023-10-11 21:40:35 +02:00
|
|
|
|
|
|
|
|
|
public class AzureService : IManageAzure
|
|
|
|
|
{
|
|
|
|
|
private readonly AzureOptions _azSettings;
|
2023-11-09 16:03:38 +00:00
|
|
|
|
private readonly ILogger<AzureService> _logger;
|
2023-10-11 21:40:35 +02:00
|
|
|
|
|
2023-11-09 16:03:38 +00:00
|
|
|
|
public AzureService(IOptions<AzureOptions> azOptions, ILogger<AzureService> logger)
|
2023-10-11 21:40:35 +02:00
|
|
|
|
{
|
|
|
|
|
_azSettings = azOptions.Value;
|
2023-11-09 16:03:38 +00:00
|
|
|
|
_logger = logger;
|
2023-10-11 21:40:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task DeleteSandbox(string sandboxId)
|
|
|
|
|
{
|
2023-11-09 16:03:38 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var client = new ArmClient(new DefaultAzureCredential());
|
|
|
|
|
var resourceGroupResourceId = ResourceGroupResource.CreateResourceIdentifier(_azSettings.SubscriptionId, _azSettings.ContainerInstancesResourceGroup);
|
|
|
|
|
var resourceGroupResource = client.GetResourceGroupResource(resourceGroupResourceId);
|
|
|
|
|
|
|
|
|
|
var collection = resourceGroupResource.GetContainerGroups();
|
|
|
|
|
var containerGroup = await collection.GetAsync(sandboxId);
|
|
|
|
|
await containerGroup.Value.DeleteAsync(WaitUntil.Started);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError(ex, "Error deleting sandbox");
|
|
|
|
|
}
|
2023-10-11 21:40:35 +02:00
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<bool> IsSandboxCompleted(string sandboxId)
|
|
|
|
|
{
|
2023-11-09 16:03:38 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var client = new ArmClient(new DefaultAzureCredential());
|
|
|
|
|
var resourceGroupResourceId = ResourceGroupResource.CreateResourceIdentifier(_azSettings.SubscriptionId, _azSettings.ContainerInstancesResourceGroup);
|
|
|
|
|
var resourceGroupResource = client.GetResourceGroupResource(resourceGroupResourceId);
|
|
|
|
|
|
|
|
|
|
var collection = resourceGroupResource.GetContainerGroups();
|
|
|
|
|
var containerGroup = await collection.GetAsync(sandboxId);
|
|
|
|
|
return containerGroup.Value.Data.ProvisioningState == "Succeeded"
|
|
|
|
|
&& containerGroup.Value.Data.Containers.First().InstanceView.CurrentState.State == "Terminated";
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError(ex, "Error checking sandbox status");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2023-10-11 21:40:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task RunInSandbox(SandboxRequest request)
|
|
|
|
|
{
|
2023-11-09 16:03:38 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var client = string.IsNullOrEmpty(_azSettings.ManagedIdentity) ?
|
2023-10-26 22:09:18 +02:00
|
|
|
|
new ArmClient(new AzureCliCredential())
|
|
|
|
|
: new ArmClient(new ManagedIdentityCredential(_azSettings.ManagedIdentity));
|
|
|
|
|
|
2023-11-09 16:03:38 +00:00
|
|
|
|
var runId = $"sk-sandbox-{request.Org}-{request.Repo}-{request.ParentIssueNumber}-{request.IssueNumber}";
|
|
|
|
|
var resourceGroupResourceId = ResourceGroupResource.CreateResourceIdentifier(_azSettings.SubscriptionId, _azSettings.ContainerInstancesResourceGroup);
|
|
|
|
|
var resourceGroupResource = client.GetResourceGroupResource(resourceGroupResourceId);
|
|
|
|
|
var scriptPath = $"/azfiles/output/{request.Org}-{request.Repo}/{request.ParentIssueNumber}/{request.IssueNumber}/run.sh";
|
|
|
|
|
var collection = resourceGroupResource.GetContainerGroups();
|
|
|
|
|
var data = new ContainerGroupData(new AzureLocation(_azSettings.Location), new ContainerInstanceContainer[]
|
|
|
|
|
{
|
|
|
|
|
new ContainerInstanceContainer(runId,_azSettings.SandboxImage,new ContainerResourceRequirements(new ContainerResourceRequestsContent(1.5,1)))
|
2023-10-11 21:40:35 +02:00
|
|
|
|
{
|
2023-11-09 16:03:38 +00:00
|
|
|
|
Command = { "/bin/bash", $"{scriptPath}" },
|
|
|
|
|
VolumeMounts =
|
2023-10-11 21:40:35 +02:00
|
|
|
|
{
|
2023-11-09 16:03:38 +00:00
|
|
|
|
new ContainerVolumeMount("azfiles","/azfiles/")
|
|
|
|
|
{
|
|
|
|
|
IsReadOnly = false,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
}}, ContainerInstanceOperatingSystemType.Linux)
|
|
|
|
|
{
|
|
|
|
|
Volumes =
|
2023-10-11 21:40:35 +02:00
|
|
|
|
{
|
2023-11-09 16:03:38 +00:00
|
|
|
|
new ContainerVolume("azfiles")
|
2023-10-11 21:40:35 +02:00
|
|
|
|
{
|
2023-11-09 16:03:38 +00:00
|
|
|
|
AzureFile = new ContainerInstanceAzureFileVolume(_azSettings.FilesShareName,_azSettings.FilesAccountName)
|
|
|
|
|
{
|
|
|
|
|
StorageAccountKey = _azSettings.FilesAccountKey
|
|
|
|
|
},
|
2023-10-11 21:40:35 +02:00
|
|
|
|
},
|
|
|
|
|
},
|
2023-11-09 16:03:38 +00:00
|
|
|
|
RestartPolicy = ContainerGroupRestartPolicy.Never,
|
|
|
|
|
Sku = ContainerGroupSku.Standard,
|
|
|
|
|
Priority = ContainerGroupPriority.Regular
|
|
|
|
|
};
|
|
|
|
|
await collection.CreateOrUpdateAsync(WaitUntil.Completed, runId, data);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError(ex, "Error running sandbox");
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-11 21:40:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task Store(SaveOutputRequest request)
|
|
|
|
|
{
|
2023-11-09 16:03:38 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var connectionString = $"DefaultEndpointsProtocol=https;AccountName={_azSettings.FilesAccountName};AccountKey={_azSettings.FilesAccountKey};EndpointSuffix=core.windows.net";
|
|
|
|
|
var parentDirName = $"{request.Directory}/{request.Org}-{request.Repo}";
|
|
|
|
|
|
|
|
|
|
var fileName = $"{request.FileName}.{request.Extension}";
|
|
|
|
|
|
|
|
|
|
var share = new ShareClient(connectionString, _azSettings.FilesShareName);
|
|
|
|
|
await share.CreateIfNotExistsAsync();
|
|
|
|
|
await share.GetDirectoryClient($"{request.Directory}").CreateIfNotExistsAsync(); ;
|
|
|
|
|
|
|
|
|
|
var parentDir = share.GetDirectoryClient(parentDirName);
|
|
|
|
|
await parentDir.CreateIfNotExistsAsync();
|
|
|
|
|
|
|
|
|
|
var parentIssueDir = parentDir.GetSubdirectoryClient($"{request.ParentIssueNumber}");
|
|
|
|
|
await parentIssueDir.CreateIfNotExistsAsync();
|
|
|
|
|
|
|
|
|
|
var directory = parentIssueDir.GetSubdirectoryClient($"{request.IssueNumber}");
|
|
|
|
|
await directory.CreateIfNotExistsAsync();
|
|
|
|
|
|
|
|
|
|
var file = directory.GetFileClient(fileName);
|
|
|
|
|
// hack to enable script to save files in the same directory
|
|
|
|
|
var cwdHack = "#!/bin/bash\n cd $(dirname $0)";
|
|
|
|
|
var output = request.Extension == "sh" ? request.Output.Replace("#!/bin/bash", cwdHack) : request.Output;
|
|
|
|
|
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(output)))
|
|
|
|
|
{
|
|
|
|
|
await file.CreateAsync(stream.Length);
|
|
|
|
|
await file.UploadRangeAsync(
|
|
|
|
|
new HttpRange(0, stream.Length),
|
|
|
|
|
stream);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
2023-10-11 21:40:35 +02:00
|
|
|
|
{
|
2023-11-09 16:03:38 +00:00
|
|
|
|
_logger.LogError(ex, "Error storing output");
|
2023-10-11 21:40:35 +02:00
|
|
|
|
}
|
2023-11-09 16:03:38 +00:00
|
|
|
|
|
2023-10-11 21:40:35 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public interface IManageAzure
|
|
|
|
|
{
|
|
|
|
|
Task Store(SaveOutputRequest request);
|
|
|
|
|
Task RunInSandbox(SandboxRequest request);
|
|
|
|
|
Task<bool> IsSandboxCompleted(string sandboxId);
|
|
|
|
|
Task DeleteSandbox(string sandboxId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class SaveOutputRequest
|
|
|
|
|
{
|
|
|
|
|
public int ParentIssueNumber { get; set; }
|
|
|
|
|
public int IssueNumber { get; set; }
|
|
|
|
|
public string Output { get; set; }
|
|
|
|
|
public string Extension { get; set; }
|
|
|
|
|
public string Directory { get; set; }
|
|
|
|
|
public string FileName { get; set; }
|
|
|
|
|
public string Org { get; set; }
|
|
|
|
|
public string Repo { get; set; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[GenerateSerializer]
|
|
|
|
|
public class SandboxRequest
|
|
|
|
|
{
|
|
|
|
|
[Id(0)]
|
|
|
|
|
public string Org { get; set; }
|
|
|
|
|
[Id(1)]
|
|
|
|
|
public string Repo { get; set; }
|
|
|
|
|
[Id(2)]
|
|
|
|
|
public int IssueNumber { get; set; }
|
|
|
|
|
[Id(3)]
|
|
|
|
|
public int ParentIssueNumber { get; set; }
|
|
|
|
|
}
|