예제 #1
0
    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);
        }
예제 #6
0
    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);
    }
예제 #11
0
    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);
                }
예제 #16
0
    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();
            }
        }
    }
예제 #18
0
    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);
        }
    }