private async Task <string> GetEventsAsync(ComponentTask document) { var output = default(string); if (document.TaskState.IsActive() && AzureResourceIdentifier.TryParse(document.ResourceId, out var resourceId)) { try { var containerGroup = await azureResourceService .GetResourceAsync <AzureContainerGroupResource>(resourceId.ToString()) .ConfigureAwait(false); if (containerGroup is not null) { output = await containerGroup .GetEventContentAsync(document.Id) .ConfigureAwait(false); } } catch { // swallow } } return(output); }
private async Task <object[]> GetRegistryCredentialsAsync(Organization organization) { var credentials = new List <object>(); var organizationRegistry = AzureResourceIdentifier.TryParse(organization.RegistryId, out var organizationRegistryId) ? await azureResourceService.GetResourceAsync <AzureContainerRegistryResource>(organizationRegistryId.ToString()).ConfigureAwait(false) : null; if (organizationRegistry is not null) { var registryCredentials = await organizationRegistry .GetCredentialsAsync() .ConfigureAwait(false); if (registryCredentials is not null) { credentials.Add(new { server = registryCredentials.Domain, username = registryCredentials.UserName, password = registryCredentials.Password }); } } return(credentials.ToArray()); }
protected override async Task <Component> CreateComponentAsync(Component component, Organization componentOrganization, DeploymentScope componentDeploymentScope, Project componentProject, User contextUser, IAsyncCollector <ICommand> commandQueue) { if (component is null) { throw new ArgumentNullException(nameof(component)); } if (!AzureResourceIdentifier.TryParse(component.ResourceId, out var resourceId)) { component.ResourceId = await CreateResourceIdAsync(component) .ConfigureAwait(false); resourceId = AzureResourceIdentifier.Parse(component.ResourceId); var sessionIdenity = await azureResourceService.AzureSessionService .GetIdentityAsync() .ConfigureAwait(false); component.ResourceUrl = resourceId.GetPortalUrl(sessionIdenity.TenantId); component = await componentRepository .SetAsync(component) .ConfigureAwait(false); } return(await UpdateComponentAsync(component, contextUser, commandQueue).ConfigureAwait(false)); }
public async Task <Component> Run( [ActivityTrigger] IDurableActivityContext context, ILogger log) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (log is null) { throw new ArgumentNullException(nameof(log)); } var component = context.GetInput <Input>().Component; if (!AzureResourceIdentifier.TryParse(component.IdentityId, out var identityId)) { var deploymentScope = await deploymentScopeRepository .GetAsync(component.Organization, component.DeploymentScopeId) .ConfigureAwait(false); var project = await projectRepository .GetAsync(component.Organization, component.ProjectId) .ConfigureAwait(false); var projectResourceId = AzureResourceIdentifier.Parse(project.ResourceId); var session = await azureResourceService.AzureSessionService .CreateSessionAsync(projectResourceId.SubscriptionId) .ConfigureAwait(false); var identities = await session.Identities .ListByResourceGroupAsync(projectResourceId.ResourceGroup, loadAllPages : true) .ConfigureAwait(false); var identity = identities .SingleOrDefault(i => i.Name.Equals(deploymentScope.Id, StringComparison.OrdinalIgnoreCase)); if (identity is null) { var location = await GetLocationAsync(component) .ConfigureAwait(false); identity = await session.Identities .Define(deploymentScope.Id) .WithRegion(location) .WithExistingResourceGroup(projectResourceId.ResourceGroup) .CreateAsync() .ConfigureAwait(false); } component.IdentityId = identity.Id; } return(component); }
private static string GetResourceGroupId(string resourceId) { if (AzureResourceIdentifier.TryParse(resourceId, out var resourceGroupIdentifier)) { return(resourceGroupIdentifier.ToString(AzureResourceSegment.ResourceGroup)); } return(null); }
public static bool IsAzureResourceId(this string resourceId) { if (resourceId is null) { throw new ArgumentNullException(nameof(resourceId)); } return(AzureResourceIdentifier.TryParse(resourceId, out var _)); }
public async Task <string[]> Run( [ActivityTrigger] IDurableActivityContext context, ILogger log) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (log is null) { throw new ArgumentNullException(nameof(log)); } var component = context.GetInput <Input>().Component; IAsyncEnumerable <AzureResource> resources = null; if (AzureResourceIdentifier.TryParse(component.ResourceId, out var componentResourceId)) { if (string.IsNullOrEmpty(componentResourceId.ResourceGroup)) { var subscription = await azureResourceService .GetSubscriptionAsync(componentResourceId.SubscriptionId) .ConfigureAwait(false); if (subscription is not null) { resources = subscription.GetResourceGroupsAsync() .SelectMany(rg => rg.GetResourcesAsync()); } } else { var resourceGroup = await azureResourceService .GetResourceGroupAsync(componentResourceId.SubscriptionId, componentResourceId.ResourceGroup) .ConfigureAwait(false); if (resourceGroup is not null) { resources = resourceGroup.GetResourcesAsync(); } } } if (resources is null) { return(Array.Empty <string>()); } return(await resources .Select(r => r.ResourceId.ToString()) .ToArrayAsync() .ConfigureAwait(false)); }
public async Task <ILogger> CreateLoggerAsync(ComponentTask componentTask, ILogger logger) { if (componentTask is null) { throw new ArgumentNullException(nameof(componentTask)); } logger ??= NullLogger.Instance; try { var project = await projectRepository .GetAsync(componentTask.Organization, componentTask.ProjectId) .ConfigureAwait(false); if (AzureResourceIdentifier.TryParse(project?.StorageId, out var storageId)) { var storageAccount = await azureResourceService .GetResourceAsync <AzureStorageAccountResource>(storageId.ToString(), false) .ConfigureAwait(false); if (storageAccount is not null) { var shareClient = await storageAccount .CreateShareClientAsync(componentTask.ComponentId) .ConfigureAwait(false); var directoryClient = shareClient .GetDirectoryClient(".output"); await directoryClient .CreateIfNotExistsAsync() .ConfigureAwait(false); var fileClient = directoryClient .GetFileClient($"{componentTask.Id}"); var fileStream = await fileClient .OpenWriteAsync(true, 0) .ConfigureAwait(false); return(new AdapterInitializationLogger(logger, fileStream)); } } } catch { // swallow } return(logger); }
public static async Task RunOrchestrator( [OrchestrationTrigger] IDurableOrchestrationContext functionContext, ILogger log) { if (functionContext is null) { throw new ArgumentNullException(nameof(functionContext)); } var functionLog = functionContext.CreateReplaySafeLogger(log ?? NullLogger.Instance); var resourceIds = functionContext .GetInput <IEnumerable <string> >() .ToHashSet(StringComparer.OrdinalIgnoreCase); await Task.WhenAll(resourceIds.Select(resourceId => { if (AzureResourceIdentifier.TryParse(resourceId, out var resourceIdentifier)) { if (IsResourceGroup(resourceIdentifier)) { functionLog.LogInformation($"Resetting resource group: {resourceId}"); return(functionContext.CallDeploymentAsync(nameof(ResourceGroupResetActivity), resourceId)); } else { functionLog.LogInformation($"Deleting resource: {resourceId}"); return(functionContext.CallActivityWithRetryAsync(nameof(ResourceDeleteActivity), resourceId)); } } return(Task.CompletedTask); })).ConfigureAwait(true); await Task.WhenAll(resourceIds.Select(resourceId => { if (AzureResourceIdentifier.TryParse(resourceId, out var resourceIdentifier) && IsResourceGroup(resourceIdentifier)) { functionLog.LogInformation($"Deleting resource group: {resourceId}"); return(functionContext.CallActivityWithRetryAsync(nameof(ResourceGroupDeleteActivity), resourceId)); } return(Task.CompletedTask); })).ConfigureAwait(true); bool IsResourceGroup(AzureResourceIdentifier azureResourceIdentifier) => !(azureResourceIdentifier.ResourceTypes?.Any() ?? false); }
protected override async Task <Component> DeleteComponentAsync(Component component, Organization componentOrganization, DeploymentScope componentDeploymentScope, Project componentProject, User contextUser, IAsyncCollector <ICommand> commandQueue) { if (component is null) { throw new ArgumentNullException(nameof(component)); } if (AzureResourceIdentifier.TryParse(component.ResourceId, out var componentResourceId)) { var resourceGroup = await azureResourceService .GetResourceGroupAsync(componentResourceId.SubscriptionId, componentResourceId.ResourceGroup) .ConfigureAwait(false); if (resourceGroup is not null) { await resourceGroup .DeleteAsync(true) .ConfigureAwait(false); } // remove resource related informations component.ResourceId = null; component.ResourceUrl = null; // ensure resource state is deleted component.ResourceState = Model.Common.ResourceState.Deprovisioned; // update entity to ensure we have it's state updated in case the delete fails component = await componentRepository .SetAsync(component) .ConfigureAwait(false); } return(component); }
public async Task Run( [ActivityTrigger] IDurableActivityContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } var project = context.GetInput <Input>().Project; if (AzureResourceIdentifier.TryParse(project.ResourceId, out var resourceId)) { var resourceGroup = await azureResourceService .GetResourceGroupAsync(resourceId.SubscriptionId, resourceId.ResourceGroup) .ConfigureAwait(false); if (resourceGroup is not null) { await resourceGroup .DeleteAsync(true) .ConfigureAwait(false); } } }
private async Task <IEnumerable <Guid> > GetSubscriptionIdsAsync(Component component) { var deploymentScope = await deploymentScopeRepository .GetAsync(component.Organization, component.DeploymentScopeId, true) .ConfigureAwait(false); IEnumerable <Guid> subscriptionIds; if (AzureResourceIdentifier.TryParse(deploymentScope.ManagementGroupId, out var managementGroupResourceId)) { try { var client = await azureResourceService.AzureSessionService .CreateClientAsync <ManagementGroupsAPIClient>() .ConfigureAwait(false); var group = await client.ManagementGroups .GetAsync(managementGroupResourceId.ResourceTypes.Last().Value, expand : "children") .ConfigureAwait(false); subscriptionIds = group.Children .Where(child => child.Type.Equals("/subscriptions", StringComparison.OrdinalIgnoreCase)) .Select(child => Guid.Parse(child.Name)); } catch { subscriptionIds = Enumerable.Empty <Guid>(); } } else { try { var session = await azureResourceService.AzureSessionService .CreateSessionAsync() .ConfigureAwait(false); var subscriptions = await session.Subscriptions .ListAsync(loadAllPages : true) .ConfigureAwait(false); subscriptionIds = subscriptions .Where(subscription => deploymentScope.SubscriptionIds.Contains(Guid.Parse(subscription.SubscriptionId))) .Select(subscription => Guid.Parse(subscription.SubscriptionId)); } catch { subscriptionIds = Enumerable.Empty <Guid>(); } } var identity = await azureResourceService.AzureSessionService .GetIdentityAsync() .ConfigureAwait(false); var subscriptionIdsValidated = await Task .WhenAll(subscriptionIds.Select(subscriptionId => ProveOwnershipAsync(subscriptionId, identity.ObjectId))) .ConfigureAwait(false); return(subscriptionIdsValidated .Where(subscriptionId => subscriptionId != Guid.Empty)); async Task <Guid> ProveOwnershipAsync(Guid subscriptionId, Guid userObjectId) { var subscription = await azureResourceService .GetSubscriptionAsync(subscriptionId) .ConfigureAwait(false); var hasOwnership = await subscription .HasRoleAssignmentAsync(userObjectId.ToString(), AzureRoleDefinition.Owner, true) .ConfigureAwait(false); return(hasOwnership ? subscriptionId : Guid.Empty); } }
protected override async Task <Component> UpdateComponentAsync(Component component, Organization componentOrganization, DeploymentScope componentDeploymentScope, Project componentProject, User contextUser, IAsyncCollector <ICommand> commandQueue) { if (component is null) { throw new ArgumentNullException(nameof(component)); } if (AzureResourceIdentifier.TryParse(component.ResourceId, out var componentResourceId)) { var tasks = new Task[] { UpdateComponentRoleAssignmentsAsync(), UpdateComponentTagsAsync() }; await Task.WhenAll(tasks).ConfigureAwait(false); } return(component); async Task UpdateComponentRoleAssignmentsAsync() { var roleAssignmentMap = await GetRoleAssignmentsAsync(component) .ConfigureAwait(false); if (AzureResourceIdentifier.TryParse(component.IdentityId, out var identityResourceId)) { var session = await azureResourceService.AzureSessionService .CreateSessionAsync(identityResourceId.SubscriptionId) .ConfigureAwait(false); var identity = await session.Identities .GetByIdAsync(identityResourceId.ToString()) .ConfigureAwait(false); roleAssignmentMap .Add(identity.PrincipalId, Enumerable.Repeat(AzureRoleDefinition.Contributor, 1)); } if (string.IsNullOrEmpty(componentResourceId.ResourceGroup)) { var subscription = await azureResourceService .GetSubscriptionAsync(componentResourceId.SubscriptionId, throwIfNotExists : true) .ConfigureAwait(false); if (subscription is not null) { await subscription.SetRoleAssignmentsAsync(roleAssignmentMap).ConfigureAwait(false); } } else { var resourceGroup = await azureResourceService .GetResourceGroupAsync(componentResourceId.SubscriptionId, componentResourceId.ResourceGroup, throwIfNotExists : true) .ConfigureAwait(false); if (resourceGroup is not null) { await resourceGroup.SetRoleAssignmentsAsync(roleAssignmentMap).ConfigureAwait(false); } } } async Task UpdateComponentTagsAsync() { var tenantId = await azureResourceService.AzureSessionService .GetTenantIdAsync() .ConfigureAwait(false); var organization = await organizationRepository .GetAsync(tenantId.ToString(), component.Organization, true) .ConfigureAwait(false); var project = await projectRepository .GetAsync(component.Organization, component.ProjectId, true) .ConfigureAwait(false); var tags = organization.Tags .Union(project.Tags) .GroupBy(kvp => kvp.Key) .ToDictionary(g => g.Key, g => g.First().Value); if (string.IsNullOrEmpty(componentResourceId.ResourceGroup)) { var subscription = await azureResourceService .GetSubscriptionAsync(componentResourceId.SubscriptionId) .ConfigureAwait(false); if (subscription is not null) { await subscription.SetTagsAsync(tags, true).ConfigureAwait(false); } } else { var resourceGroup = await azureResourceService .GetResourceGroupAsync(componentResourceId.SubscriptionId, componentResourceId.ResourceGroup) .ConfigureAwait(false); if (resourceGroup is not null) { await resourceGroup.SetTagsAsync(tags, true).ConfigureAwait(false); } } } }
public async Task <ComponentTask> Run( [ActivityTrigger] IDurableActivityContext context, ILogger log) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (log is null) { throw new ArgumentNullException(nameof(log)); } var componentTask = context.GetInput <Input>().ComponentTask; try { if (AzureResourceIdentifier.TryParse(componentTask.ResourceId, out var resourceId)) { if (!componentTask.TaskState.IsFinal()) { componentTask.TaskState = TaskState.Failed; componentTask = await componentTaskRepository .SetAsync(componentTask) .ConfigureAwait(false); } var containerGroup = await azureResourceService .GetResourceAsync <AzureContainerGroupResource>(resourceId.ToString()) .ConfigureAwait(false); if (containerGroup is not null) { try { await containerGroup .StopAsync() .ConfigureAwait(false); } catch { // swallow } finally { var location = await containerGroup .GetLocationAsync() .ConfigureAwait(false); var usageData = await AzureContainerGroupResource .GetUsageAsync(azureResourceService, containerGroup.ResourceId.SubscriptionId, location) .ConfigureAwait(false); var usage = usageData .SingleOrDefault(u => u.Unit.Equals("Count") && u.Name.Value.Equals("ContainerGroups")); var limit = usage?.Limit.GetValueOrDefault() ?? 0; var current = usage?.CurrentValue.GetValueOrDefault() ?? 0; if (current >= limit) { await containerGroup .DeleteAsync() .ConfigureAwait(false); } } } } } catch (Exception exc) { throw exc.AsSerializable(); } return(componentTask); }
public override async Task <ICommandResult> HandleAsync(ComponentTaskRunCommand command, IAsyncCollector <ICommand> commandQueue, IDurableOrchestrationContext orchestrationContext, ILogger log) { if (command is null) { throw new ArgumentNullException(nameof(command)); } if (commandQueue is null) { throw new ArgumentNullException(nameof(commandQueue)); } if (orchestrationContext is null) { throw new ArgumentNullException(nameof(orchestrationContext)); } if (log is null) { throw new ArgumentNullException(nameof(log)); } var commandResult = command.CreateResult(); var organization = await WaitForOrganizationInitAsync(orchestrationContext, command) .ConfigureAwait(true); var project = await WaitForProjectInitAsync(orchestrationContext, command) .ConfigureAwait(true); var component = await orchestrationContext .CallActivityWithRetryAsync <Component>(nameof(ComponentGetActivity), new ComponentGetActivity.Input() { ComponentId = command.Payload.ComponentId, ProjectId = command.Payload.ProjectId }) .ConfigureAwait(true); using (await orchestrationContext.LockContainerDocumentAsync(component).ConfigureAwait(true)) { commandResult.Result = await orchestrationContext .CallActivityWithRetryAsync <ComponentTask>(nameof(ComponentTaskGetActivity), new ComponentTaskGetActivity.Input() { ComponentTaskId = command.Payload.Id, ComponentId = command.Payload.ComponentId }) .ConfigureAwait(true) ?? command.Payload; if (commandResult.Result.TaskState != TaskState.Canceled) { try { commandResult.Result = await UpdateComponentTaskAsync(orchestrationContext, commandResult.Result, TaskState.Initializing) .ConfigureAwait(true); if (!AzureResourceIdentifier.TryParse(component.IdentityId, out var _)) { // ensure every component has an identity assigned that can be used // as the identity of the task runner container to access azure or // call back into teamcloud using the azure cli extension component = await orchestrationContext .CallActivityWithRetryAsync <Component>(nameof(ComponentIdentityActivity), new ComponentIdentityActivity.Input() { Component = component }) .ConfigureAwait(true); } commandResult.Result = await UpdateComponentTaskAsync(orchestrationContext, commandResult.Result, TaskState.Processing) .ConfigureAwait(true); await(command.Payload.Type switch { ComponentTaskType.Create => ProcessCreateCommandAsync(), ComponentTaskType.Delete => ProcessDeleteCommandAsync(), ComponentTaskType.Custom => ProcessCustomCommandAsync(), _ => throw new NotSupportedException($"The command type '{command.Payload.Type}' is not supported") }).ConfigureAwait(true); commandResult.Result = await UpdateComponentTaskAsync(orchestrationContext, commandResult.Result, TaskState.Succeeded) .ConfigureAwait(true); }
public async Task ExpandAsync(Component document) { if (document is null) { throw new ArgumentNullException(nameof(document)); } if (string.IsNullOrEmpty(document.ValueJson)) { var project = await projectRepository .GetAsync(document.Organization, document.ProjectId) .ConfigureAwait(false); if (AzureResourceIdentifier.TryParse(project?.StorageId, out var storageId)) { var cacheKey = $"{GetType()}|{document.GetType()}|{document.Id}"; try { var sasUrl = await cache .GetOrCreateAsync(cacheKey, AcquireSasUrl) .ConfigureAwait(false); if (sasUrl is null) { cache.Remove(cacheKey); } else { using var stream = await sasUrl.ToString() .WithHeader("responsecontent-disposition", "file; attachment") .WithHeader("responsecontent-type", "binary") .GetStreamAsync() .ConfigureAwait(false); if ((stream?.Length ?? 0) > 0) { using var reader = new StreamReader(stream); document.ValueJson = await reader .ReadToEndAsync() .ConfigureAwait(false); } } } catch { // swallow } } async Task <Uri> AcquireSasUrl(ICacheEntry entry) { var storageAccount = await azureResourceService .GetResourceAsync <AzureStorageAccountResource>(storageId.ToString(), false) .ConfigureAwait(false); if (storageAccount is null) { return(null); } entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddDays(1); return(await storageAccount .CreateShareFileSasUriAsync(document.Id, "value.json", ShareFileSasPermissions.Read, entry.AbsoluteExpiration.Value.AddMinutes(5)) .ConfigureAwait(false)); } } }
private async Task <string> GetContainerImageAsync(Organization organization, ComponentTemplate componentTemplate) { if (runnerOptions.ImportDockerHub && AzureContainerRegistryResource.TryResolveFullyQualifiedContainerImageName(componentTemplate.TaskRunner.Id, out var sourceContainerImage) && AzureContainerRegistryResource.IsDockerHubContainerImage(sourceContainerImage) && sourceContainerImage.Contains("/teamcloud/", StringComparison.OrdinalIgnoreCase)) { var organizationRegistry = AzureResourceIdentifier.TryParse(organization.RegistryId, out var organizationRegistryId) ? await azureResourceService.GetResourceAsync <AzureContainerRegistryResource>(organizationRegistryId.ToString()).ConfigureAwait(false) : null; if (organizationRegistry is null) { // the current organization has now container registry // assigned so we use the orginal container image origion return(sourceContainerImage); } else { var sourceContainerImport = false; // by default we are not going to import the docker hub container image into the organization container registry var targetContainerImage = $"{organizationRegistry.Hostname}{sourceContainerImage.Substring(sourceContainerImage.IndexOf('/', StringComparison.OrdinalIgnoreCase))}"; if (AzureContainerRegistryResource.IsContainerImageNameTagBased(sourceContainerImage)) { var sourceContainerDigest = await GetDockerHubContainerImageDigestAsync(sourceContainerImage).ConfigureAwait(false); if (string.IsNullOrEmpty(sourceContainerDigest)) { // the container image is part of a private registry // on docker hub or just doesn't exist. return the // fully qualified container image name of the source // and let the caller handle a potential error return(sourceContainerImage); } var targetContainerDigest = await organizationRegistry.GetContainerImageDigestAsync(targetContainerImage).ConfigureAwait(false); if (sourceContainerDigest?.Equals(targetContainerDigest, StringComparison.Ordinal) ?? false) { // our organization registry contains the requested image // let's use this one to shorten the image pull duration return(targetContainerImage); } // we go with the source container image but mark this // image for import into the organization registry sourceContainerImport = true; } else if (await organizationRegistry.ContainesContainerImageAsync(targetContainerImage).ConfigureAwait(false)) { // our organization registry contains the requested image // let's use this one to shorten the image pull duration return(targetContainerImage); } else { // we go with the source container image but mark this // image for import into the organization registry sourceContainerImport = true; } if (sourceContainerImport) { var sourceContainerTags = await GetDockerHubContainerImageTagsAsync(targetContainerImage) .ToArrayAsync() .ConfigureAwait(false); await organizationRegistry .ImportContainerImageAsync(sourceContainerImage, sourceContainerTags, force: true) .ConfigureAwait(false); } return(sourceContainerImage); } } // this is our fallback if something bad happend // during the container image evaluation return(componentTemplate.TaskRunner.Id); async Task <string> GetDockerHubContainerImageDigestAsync(string containerImageName) { if (!AzureContainerRegistryResource.IsContainerImageNameTagBased(containerImageName)) { throw new ArgumentException($"'{nameof(containerImageName)}' contain a tag based reference.", nameof(containerImageName)); } containerImageName = AzureContainerRegistryResource.ResolveFullyQualifiedContainerImageName(containerImageName); var containerName = AzureContainerRegistryResource.GetContainerName(containerImageName); var containerReference = AzureContainerRegistryResource.GetContainerReference(containerImageName); var response = await $"https://hub.docker.com/v2/repositories/{containerName}/tags/{containerReference}" .AllowHttpStatus(HttpStatusCode.NotFound) .GetAsync() .ConfigureAwait(false); if (response.IsSuccessStatusCode()) { var json = await response .GetJsonAsync <JObject>() .ConfigureAwait(false); return(json.SelectToken("images[?(@.status == 'active')].digest")?.ToString()); } return(null); } async IAsyncEnumerable <string> GetDockerHubContainerImageTagsAsync(string containerImageName) { if (AzureContainerRegistryResource.IsContainerImageNameTagBased(containerImageName)) { var digest = await GetDockerHubContainerImageDigestAsync(containerImageName).ConfigureAwait(false); if (string.IsNullOrWhiteSpace(digest)) { throw new ArgumentException($"Failed to resolve digest for argument '{nameof(containerImageName)}'.", nameof(containerImageName)); } containerImageName = AzureContainerRegistryResource.ChangeContainerReference(containerImageName, digest); } var containerName = AzureContainerRegistryResource.GetContainerName(containerImageName); var containerDigest = AzureContainerRegistryResource.GetContainerReference(containerImageName); var registryUrl = $"https://hub.docker.com/v2/repositories/{containerName}/tags/"; while (!string.IsNullOrEmpty(registryUrl)) { var json = await registryUrl .GetJObjectAsync() .ConfigureAwait(false); foreach (var tag in json.SelectTokens($"results[?(@.images[?(@.status == 'active' && @.digest == '{containerDigest}')])].name")) { yield return(tag.ToString()); } registryUrl = json .SelectToken("next")? .ToString(); } } }
private async Task <string> GetOutputAsync(ComponentTask document) { var output = default(string); var outputKey = $"{GetType()}|{document.GetType()}|{document.Id}"; var outputUrl = await cache .GetOrCreateAsync(outputKey, GetOutputUrlAsync) .ConfigureAwait(false); if (outputUrl is null) { cache.Remove(outputKey); } else { if (document.TaskState.IsActive()) { output = await GetOutputLogAsync(null) .ConfigureAwait(false); } else { output = await cache .GetOrCreateAsync(outputUrl, GetOutputLogAsync) .ConfigureAwait(false); if (string.IsNullOrEmpty(output)) { cache.Remove(outputUrl); } } } return(output); async Task <string> GetOutputLogAsync(ICacheEntry entry) { try { using var stream = await outputUrl.ToString() .WithHeader("responsecontent-disposition", "file; attachment") .WithHeader("responsecontent-type", "binary") .GetStreamAsync() .ConfigureAwait(false); if ((stream?.Length ?? 0) > 0) { using var reader = new StreamReader(stream); if (entry is not null) { entry.AbsoluteExpiration = DateTime.UtcNow.AddDays(1); entry.Value = await reader .ReadToEndAsync() .ConfigureAwait(false); } else { return(await reader .ReadToEndAsync() .ConfigureAwait(false)); } } } catch { // swallow } return(entry?.Value as string); } async Task <Uri> GetOutputUrlAsync(ICacheEntry entry) { try { var project = await projectRepository .GetAsync(document.Organization, document.ProjectId) .ConfigureAwait(false); if (AzureResourceIdentifier.TryParse(project?.StorageId, out var storageId)) { var storageAccount = await azureResourceService .GetResourceAsync <AzureStorageAccountResource>(storageId.ToString(), false) .ConfigureAwait(false); if (storageAccount is not null) { entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddDays(1); entry.Value = await storageAccount // create a shared access token with an additional expiration offset to avoid time sync issues when fetching the output .CreateShareFileSasUriAsync(document.ComponentId, $".output/{document.Id}", ShareFileSasPermissions.Read, entry.AbsoluteExpiration.Value.AddMinutes(5)) .ConfigureAwait(false); } } } catch { // swallow } return(entry?.Value as Uri); } }
private async Task <object[]> GetContainersAsync(Organization organization, Component component, ComponentTemplate componentTemplate, ComponentTask componentTask, string runnerHost, int runnerCpu, int runnerMemory) { var taskToken = CreateUniqueString(30); var containers = new List <object>() { new { name = componentTask.Id, properties = new { image = await GetContainerImageAsync(organization, componentTemplate).ConfigureAwait(false), resources = new { requests = new { cpu = runnerCpu, memoryInGB = runnerMemory } }, environmentVariables = GetRunnerEnvironmentVariables(), volumeMounts = new [] { new { name = "templates", mountPath = "/mnt/templates", readOnly = false }, new { name = "storage", mountPath = "/mnt/storage", readOnly = false }, new { name = "secrets", mountPath = "/mnt/secrets", readOnly = false }, new { name = "credentials", mountPath = "/mnt/credentials", readOnly = false }, new { name = "temporary", mountPath = "/mnt/temporary", readOnly = false } } } } }; if (componentTemplate.TaskRunner?.WebServer ?? false) { containers.Add(new { name = "webserver", properties = new { image = runnerOptions.WebServerImage, ports = new[] { new { protocol = "tcp", port = 80 }, new { protocol = "tcp", port = 443 } }, resources = new { requests = new { cpu = 1, memoryInGB = 1 } }, //readinessProbe = new //{ // httpGet = new // { // scheme = "http", // port = 80, // path = "/" // }, // initialDelaySeconds = 2, // periodSeconds = 2, // timeoutSeconds = 300 //}, environmentVariables = new object[] { new { name = "TaskHost", value = runnerHost }, new { name = "TaskToken", value = taskToken } }, volumeMounts = new[] { new { name = "templates", mountPath = "/mnt/templates", readOnly = true } } } }); } return(containers.ToArray()); object[] GetRunnerEnvironmentVariables() { var envVariables = componentTemplate.TaskRunner?.With ?? new Dictionary <string, string>(); envVariables["TaskId"] = componentTask.Id; envVariables["TaskHost"] = runnerHost; envVariables["TaskToken"] = taskToken; envVariables["TaskType"] = componentTask.TypeName ?? componentTask.Type.ToString(); envVariables["WebServerEnabled"] = ((componentTemplate.TaskRunner?.WebServer ?? false) ? 1 : 0).ToString(); envVariables["ComponentLocation"] = organization.Location; envVariables["ComponentTemplateBaseUrl"] = $"http://{runnerHost}/{componentTemplate.Folder.Trim().TrimStart('/')}"; envVariables["ComponentTemplateFolder"] = $"file:///mnt/templates/{componentTemplate.Folder.Trim().TrimStart('/')}"; envVariables["ComponentTemplateParameters"] = string.IsNullOrWhiteSpace(component.InputJson) ? "{}" : component.InputJson; envVariables["ComponentResourceId"] = component.ResourceId; if (AzureResourceIdentifier.TryParse(component.ResourceId, out var componentResourceId)) { envVariables["ComponentResourceGroup"] = componentResourceId.ResourceGroup; envVariables["ComponentSubscription"] = componentResourceId.SubscriptionId.ToString(); } return(envVariables .Where(kvp => kvp.Value is not null) .Select(kvp => new { name = kvp.Key, value = kvp.Value }) .ToArray()); } }
public override async Task <ICommandResult> HandleAsync(ComponentTaskCancelCommand command, IAsyncCollector <ICommand> commandQueue, IDurableOrchestrationContext orchestrationContext, ILogger log) { if (command is null) { throw new ArgumentNullException(nameof(command)); } if (commandQueue is null) { throw new ArgumentNullException(nameof(commandQueue)); } var commandResult = command.CreateResult(); try { commandResult.Result = (await componentTaskRepository .GetAsync(command.Payload.ComponentId, command.Payload.Id) .ConfigureAwait(Orchestration)) ?? command.Payload; if (commandResult.Result.Type != ComponentTaskType.Custom) { throw new Exception($"Component tasks of type '{commandResult.Result.TypeName}' cannot be canceled!"); } else if (commandResult.Result.TaskState.IsFinal()) { throw new Exception($"Component tasks in state '{commandResult.Result.TaskState}' cannot be canceled!"); } else { var status = await orchestrationContext .GetCommandStatusAsync(commandResult.Result, showInput : false) .ConfigureAwait(Orchestration); if (status is not null && status.RuntimeStatus.IsActive()) { await orchestrationContext .TerminateCommandAsync(commandResult.Result, $"Canceled by user {command.User.DisplayName}") .ConfigureAwait(Orchestration); } if (AzureResourceIdentifier.TryParse(commandResult.Result.ResourceId, out var resourceId)) { var containerGroup = await azureResourceService .GetResourceAsync <AzureContainerGroupResource>(resourceId.ToString()) .ConfigureAwait(false); if (containerGroup is not null) { await containerGroup .DeleteAsync(true) .ConfigureAwait(false); } } commandResult.Result.TaskState = TaskState.Canceled; commandResult.Result.Finished = DateTime.UtcNow; commandResult.Result.ResourceId = null; commandResult.Result = await componentTaskRepository .SetAsync(commandResult.Result) .ConfigureAwait(Orchestration); } commandResult.RuntimeStatus = CommandRuntimeStatus.Completed; } catch (Exception exc) { commandResult.Errors.Add(exc); } finally { await orchestrationContext .CleanupResourceLocksAsync() .ConfigureAwait(Orchestration); } return(commandResult); }
public async Task <ComponentTask> Run( [ActivityTrigger] IDurableActivityContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } var componentTask = context.GetInput <Input>().ComponentTask; try { if (AzureResourceIdentifier.TryParse(componentTask.ResourceId, out var resourceId) && await azureResourceService.ExistsResourceAsync(resourceId.ToString()).ConfigureAwait(false)) { var session = await azureResourceService.AzureSessionService .CreateSessionAsync(resourceId.SubscriptionId) .ConfigureAwait(false); var group = await session.ContainerGroups .GetByIdAsync(resourceId.ToString()) .ConfigureAwait(false); var runner = group.Containers .SingleOrDefault(c => c.Key.Equals(componentTask.Id, StringComparison.OrdinalIgnoreCase)) .Value; if (runner?.InstanceView is null) { componentTask.TaskState = TaskState.Initializing; } else if (runner.InstanceView.CurrentState is not null) { componentTask.TaskState = TaskState.Processing; componentTask.ExitCode = runner.InstanceView.CurrentState.ExitCode; componentTask.Started = runner.InstanceView.CurrentState.StartTime; componentTask.Finished = runner.InstanceView.CurrentState.FinishTime; if (componentTask.ExitCode.HasValue) { componentTask.TaskState = componentTask.ExitCode == 0 ? TaskState.Succeeded // ExitCode indicates successful provisioning : TaskState.Failed; // ExitCode indicates failed provisioning } else if (runner.InstanceView.CurrentState.State?.Equals("Terminated", StringComparison.OrdinalIgnoreCase) ?? false) { // container instance was terminated without exit code componentTask.TaskState = TaskState.Failed; } if (componentTask.TaskState == TaskState.Failed && !componentTask.ExitCode.HasValue) { var output = new StringBuilder(); output.AppendLine($"Creating runner {group.Id} ended in state {group.State} !!! {Environment.NewLine}"); output.AppendLine(await group.GetLogContentAsync(runner.Name).ConfigureAwait(false)); var project = await projectRepository .GetAsync(componentTask.Organization, componentTask.ProjectId) .ConfigureAwait(false); if (AzureResourceIdentifier.TryParse(project?.StorageId, out var storageId)) { var storage = await azureResourceService .GetResourceAsync <AzureStorageAccountResource>(storageId.ToString()) .ConfigureAwait(false); if (storage is not null) { var outputDirectory = ".output"; var fileClient = await storage .CreateShareFileClientAsync(componentTask.ComponentId, $"{outputDirectory}/{componentTask.Id}") .ConfigureAwait(false); if (!await fileClient.ExistsAsync().ConfigureAwait(false)) { await storage .EnsureDirectoryPathAsync(componentTask.ComponentId, outputDirectory) .ConfigureAwait(false); var logBuffer = Encoding.Default.GetBytes(output.ToString()); using var fileStream = await fileClient .OpenWriteAsync(true, 0, options : new ShareFileOpenWriteOptions() { MaxSize = logBuffer.Length }) .ConfigureAwait(false); await fileStream .WriteAsync(logBuffer, 0, logBuffer.Length) .ConfigureAwait(false); } } } } } componentTask = await componentTaskRepository .SetAsync(componentTask) .ConfigureAwait(false); } } catch (Exception exc) { throw exc.AsSerializable(); } return(componentTask); }
private async Task <object[]> GetVolumesAsync(Project project, Component component, ComponentTemplate componentTemplate) { return(new object[] { new { name = "templates", gitRepo = new { directory = ".", repository = componentTemplate.Repository.Url, revision = componentTemplate.Repository.Ref } }, new { name = "storage", azureFile = await GetShareAsync().ConfigureAwait(false) }, new { name = "secrets", secret = await GetSecretsAsync().ConfigureAwait(false) }, new { name = "credentials", secret = await GetCredentialsAsync().ConfigureAwait(false) }, new { name = "temporary", emptyDir = new { } } }); async Task <object> GetShareAsync() { if (!AzureResourceIdentifier.TryParse(project.StorageId, out var _)) { throw new NullReferenceException($"Missing storage id for project {project.Id}"); } var componentStorage = await azureResourceService .GetResourceAsync <AzureStorageAccountResource>(project.StorageId, true) .ConfigureAwait(false); var componentShare = await componentStorage .CreateShareClientAsync(component.Id) .ConfigureAwait(false); await componentShare .CreateIfNotExistsAsync() .ConfigureAwait(false); var componentStorageKeys = await componentStorage .GetKeysAsync() .ConfigureAwait(false); return(new { shareName = componentShare.Name, storageAccountName = componentShare.AccountName, storageAccountKey = componentStorageKeys.First() }); } async Task <object> GetSecretsAsync() { if (!AzureResourceIdentifier.TryParse(project.SharedVaultId, out var _)) { throw new NullReferenceException($"Missing vault id for project {project.Id}"); } var componentVault = await azureResourceService .GetResourceAsync <AzureKeyVaultResource>(project.SharedVaultId, true) .ConfigureAwait(false); var identity = await azureResourceService.AzureSessionService .GetIdentityAsync() .ConfigureAwait(false); await componentVault .SetAllSecretPermissionsAsync(identity.ObjectId) .ConfigureAwait(false); dynamic secretsObject = new ExpandoObject(); // the secrets container var secretsProperties = secretsObject as IDictionary <string, object>; await foreach (var secret in componentVault.GetSecretsAsync()) { if (secret.Value is null) { continue; } var secretNameSafe = Regex.Replace(secret.Key, "[^A-Za-z0-9_]", string.Empty); var secretValueSafe = Convert.ToBase64String(Encoding.UTF8.GetBytes(secret.Value)); secretsProperties.Add(new KeyValuePair <string, object>(secretNameSafe, secretValueSafe)); } var count = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{secretsProperties.Count}")); secretsProperties.Add(new KeyValuePair <string, object>($"_{nameof(count)}", count)); return(secretsObject); } async Task <object> GetCredentialsAsync() { dynamic credentialObject = new ExpandoObject(); // the credentials container var credentialProperties = credentialObject as IDictionary <string, object>; var deploymentScope = await deploymentScopeRepository .GetAsync(component.Organization, component.DeploymentScopeId) .ConfigureAwait(false); var adapter = adapterProvider.GetAdapter(deploymentScope.Type); if (adapter is not null) { var credential = await adapter .GetServiceCredentialAsync(component) .ConfigureAwait(false); if (credential is not null) { credentialProperties.Add(new KeyValuePair <string, object>("domain", EncodeValue(credential.Domain))); credentialProperties.Add(new KeyValuePair <string, object>("username", EncodeValue(credential.UserName))); credentialProperties.Add(new KeyValuePair <string, object>("password", EncodeValue(credential.Password))); string EncodeValue(string value) => Convert.ToBase64String(Encoding.UTF8.GetBytes(value)); } } var count = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{credentialProperties.Count}")); credentialProperties.Add(new KeyValuePair <string, object>($"_{nameof(count)}", count)); return(credentialObject); } }