From b0ae62db9d063bd067049d440c1d91e21b06e328 Mon Sep 17 00:00:00 2001 From: Markus Heiliger Date: Sun, 14 Mar 2021 21:03:57 -0700 Subject: [PATCH 1/3] update permission on project user changes and support for component tagging --- .../Commands/ComponentUpdateCommand.cs | 16 +++ .../Commands/ComponentUpdateCommandResult.cs | 12 +++ .../Handlers/ProjectUserCommandHandler.cs | 36 ++++++- .../Activities/CommandResultActivity.cs | 28 +++--- .../ComponentEnsureTaggingActivity.cs | 99 +++++++++++++++++++ .../ComponentUpdateCommandOrchestration.cs | 59 +++++++++++ .../ComponentPrepareOrchestration.cs | 8 +- .../OrchestratorCommandOrchestration.cs | 6 +- 8 files changed, 240 insertions(+), 24 deletions(-) create mode 100644 src/TeamCloud.Model/Commands/ComponentUpdateCommand.cs create mode 100644 src/TeamCloud.Model/Commands/ComponentUpdateCommandResult.cs create mode 100644 src/TeamCloud.Orchestrator/Operations/Activities/ComponentEnsureTaggingActivity.cs create mode 100644 src/TeamCloud.Orchestrator/Operations/Orchestrations/Commands/ComponentUpdateCommandOrchestration.cs diff --git a/src/TeamCloud.Model/Commands/ComponentUpdateCommand.cs b/src/TeamCloud.Model/Commands/ComponentUpdateCommand.cs new file mode 100644 index 00000000..7778633f --- /dev/null +++ b/src/TeamCloud.Model/Commands/ComponentUpdateCommand.cs @@ -0,0 +1,16 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +using TeamCloud.Model.Commands.Core; +using TeamCloud.Model.Data; + +namespace TeamCloud.Model.Commands +{ + public sealed class ComponentUpdateCommand : UpdateCommand + { + public ComponentUpdateCommand(User user, Component payload) : base(user, payload) + => ProjectId = payload?.ProjectId ?? throw new System.ArgumentNullException(nameof(payload)); + } +} diff --git a/src/TeamCloud.Model/Commands/ComponentUpdateCommandResult.cs b/src/TeamCloud.Model/Commands/ComponentUpdateCommandResult.cs new file mode 100644 index 00000000..3b6702f5 --- /dev/null +++ b/src/TeamCloud.Model/Commands/ComponentUpdateCommandResult.cs @@ -0,0 +1,12 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +using TeamCloud.Model.Commands.Core; +using TeamCloud.Model.Data; + +namespace TeamCloud.Model.Commands +{ + public sealed class ComponentUpdateCommandResult : CommandResult { } +} diff --git a/src/TeamCloud.Orchestrator/Handlers/ProjectUserCommandHandler.cs b/src/TeamCloud.Orchestrator/Handlers/ProjectUserCommandHandler.cs index 25b1709f..7456409e 100644 --- a/src/TeamCloud.Orchestrator/Handlers/ProjectUserCommandHandler.cs +++ b/src/TeamCloud.Orchestrator/Handlers/ProjectUserCommandHandler.cs @@ -3,13 +3,14 @@ * Licensed under the MIT License. */ -using System; -using System.Threading.Tasks; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; +using System; +using System.Threading.Tasks; using TeamCloud.Data; using TeamCloud.Model.Commands; using TeamCloud.Model.Commands.Core; +using TeamCloud.Model.Data; namespace TeamCloud.Orchestrator.Handlers { @@ -19,10 +20,12 @@ public sealed class ProjectUserCommandHandler ICommandHandler { private readonly IUserRepository userRepository; + private readonly IComponentRepository componentRepository; - public ProjectUserCommandHandler(IUserRepository userRepository) + public ProjectUserCommandHandler(IUserRepository userRepository, IComponentRepository componentRepository) { this.userRepository = userRepository ?? throw new ArgumentNullException(nameof(userRepository)); + this.componentRepository = componentRepository ?? throw new ArgumentNullException(nameof(componentRepository)); } public async Task HandleAsync(ProjectUserCreateCommand command, IAsyncCollector commandQueue, IDurableClient durableClient = null) @@ -47,6 +50,11 @@ public async Task HandleAsync(ProjectUserCreateCommand command, { commandResult.Errors.Add(exc); } + finally + { + await UpdateComponentsAsync(command.User, command.ProjectId, commandQueue) + .ConfigureAwait(false); + } return commandResult; } @@ -73,6 +81,11 @@ public async Task HandleAsync(ProjectUserUpdateCommand command, { commandResult.Errors.Add(exc); } + finally + { + await UpdateComponentsAsync(command.User, command.ProjectId, commandQueue) + .ConfigureAwait(false); + } return commandResult; } @@ -99,8 +112,25 @@ public async Task HandleAsync(ProjectUserDeleteCommand command, { commandResult.Errors.Add(exc); } + finally + { + await UpdateComponentsAsync(command.User, command.ProjectId, commandQueue) + .ConfigureAwait(false); + } return commandResult; } + + private async Task UpdateComponentsAsync(User user, string projectId, IAsyncCollector commandQueue) + { + await foreach (var component in componentRepository.ListAsync(projectId)) + { + var command = new ComponentUpdateCommand(user, component); + + await commandQueue + .AddAsync(command) + .ConfigureAwait(false); + } + } } } diff --git a/src/TeamCloud.Orchestrator/Operations/Activities/CommandResultActivity.cs b/src/TeamCloud.Orchestrator/Operations/Activities/CommandResultActivity.cs index fa6ea2f5..8b6f8c25 100644 --- a/src/TeamCloud.Orchestrator/Operations/Activities/CommandResultActivity.cs +++ b/src/TeamCloud.Orchestrator/Operations/Activities/CommandResultActivity.cs @@ -27,34 +27,30 @@ public static async Task RunActivity( if (durableClient is null) throw new ArgumentNullException(nameof(durableClient)); - ICommandResult commandResult; - try { - commandResult = context.GetInput(); - } - catch (Exception exc) - { - log?.LogError(exc, $"Failed deserialize command result from json: {exc.Message}"); - - throw exc.AsSerializable(); - } + var input = context.GetInput(); - try - { var commandStatus = await durableClient - .GetStatusAsync(commandResult.CommandId.ToString()) + .GetStatusAsync(input.CommandResult.CommandId.ToString()) .ConfigureAwait(false); if (commandStatus != null) - commandResult.ApplyStatus(commandStatus); + input.CommandResult.ApplyStatus(commandStatus); + + return input.CommandResult; } catch (Exception exc) { - log?.LogWarning(exc, $"Failed to augment command result with orchestration status {commandResult.CommandId}: {exc.Message}"); + log?.LogWarning(exc, $"Failed to augment command result with orchestration status: {exc.Message}"); + + throw exc.AsSerializable(); } + } - return commandResult; + internal struct Input + { + public ICommandResult CommandResult { get; set; } } } } diff --git a/src/TeamCloud.Orchestrator/Operations/Activities/ComponentEnsureTaggingActivity.cs b/src/TeamCloud.Orchestrator/Operations/Activities/ComponentEnsureTaggingActivity.cs new file mode 100644 index 00000000..18be5950 --- /dev/null +++ b/src/TeamCloud.Orchestrator/Operations/Activities/ComponentEnsureTaggingActivity.cs @@ -0,0 +1,99 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.DurableTask; +using Microsoft.Extensions.Logging; +using System; +using System.Linq; +using System.Threading.Tasks; +using TeamCloud.Azure.Resources; +using TeamCloud.Data; +using TeamCloud.Model.Data; +using TeamCloud.Orchestration; +using TeamCloud.Serialization; + +namespace TeamCloud.Orchestrator.Operations.Activities +{ + public sealed class ComponentEnsureTaggingActivity + { + private readonly IOrganizationRepository organizationRepository; + private readonly IProjectRepository projectRepository; + private readonly IAzureResourceService azureResourceService; + + public ComponentEnsureTaggingActivity(IOrganizationRepository organizationRepository, IProjectRepository projectRepository, IAzureResourceService azureResourceService) + { + this.organizationRepository = organizationRepository ?? throw new ArgumentNullException(nameof(organizationRepository)); + this.projectRepository = projectRepository ?? throw new ArgumentNullException(nameof(projectRepository)); + this.azureResourceService = azureResourceService ?? throw new ArgumentNullException(nameof(azureResourceService)); + } + + [FunctionName(nameof(ComponentEnsureTaggingActivity))] + [RetryOptions(3)] + public async Task 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().Component; + + try + { + if (AzureResourceIdentifier.TryParse(component.ResourceId, out var azureResourceIdentifier)) + { + var tenantId = (await azureResourceService.AzureSessionService.GetIdentityAsync().ConfigureAwait(false)).TenantId; + + 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(azureResourceIdentifier.ResourceGroup)) + { + var subscription = await azureResourceService + .GetSubscriptionAsync(azureResourceIdentifier.SubscriptionId) + .ConfigureAwait(false); + + if (subscription != null) + await subscription.SetTagsAsync(tags, true).ConfigureAwait(false); + } + else + { + var resourceGroup = await azureResourceService + .GetResourceGroupAsync(azureResourceIdentifier.SubscriptionId, azureResourceIdentifier.ResourceGroup) + .ConfigureAwait(false); + + if (resourceGroup != null) + await resourceGroup.SetTagsAsync(tags, true).ConfigureAwait(false); + } + } + } + catch (Exception exc) + { + throw exc.AsSerializable(); + } + + return component; + } + + internal struct Input + { + public Component Component { get; set; } + } + } +} diff --git a/src/TeamCloud.Orchestrator/Operations/Orchestrations/Commands/ComponentUpdateCommandOrchestration.cs b/src/TeamCloud.Orchestrator/Operations/Orchestrations/Commands/ComponentUpdateCommandOrchestration.cs new file mode 100644 index 00000000..f6d700b6 --- /dev/null +++ b/src/TeamCloud.Orchestrator/Operations/Orchestrations/Commands/ComponentUpdateCommandOrchestration.cs @@ -0,0 +1,59 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.DurableTask; +using Microsoft.Extensions.Logging; +using System; +using System.Threading.Tasks; +using TeamCloud.Model.Commands; +using TeamCloud.Model.Commands.Core; +using TeamCloud.Model.Data; +using TeamCloud.Orchestration; +using TeamCloud.Orchestrator.Operations.Activities; +using TeamCloud.Serialization; + +namespace TeamCloud.Orchestrator.Operations.Orchestrations.Commands +{ + public static class ComponentUpdateCommandOrchestration + { + [FunctionName(nameof(ComponentUpdateCommandOrchestration))] + public static async Task Run( + [OrchestrationTrigger] IDurableOrchestrationContext context, + ILogger log) + { + if (context is null) + throw new ArgumentNullException(nameof(context)); + + var command = context.GetInput(); + var commandResult = command.CreateResult(); + + try + { + var component = command.Payload; + + component = await context + .CallActivityWithRetryAsync(nameof(ComponentEnsurePermissionActivity), new ComponentEnsurePermissionActivity.Input() { Component = component }) + .ConfigureAwait(true); + + component = await context + .CallActivityWithRetryAsync(nameof(ComponentEnsureTaggingActivity), new ComponentEnsureTaggingActivity.Input() { Component = component }) + .ConfigureAwait(true); + } + catch (Exception exc) + { + log.LogError(exc, $"{nameof(ComponentUpdateCommandOrchestration)} failed: {exc.Message}"); + + commandResult.Errors.Add(exc); + + throw exc.AsSerializable(); + } + finally + { + context.SetOutput(commandResult); + } + } + } +} diff --git a/src/TeamCloud.Orchestrator/Operations/Orchestrations/Utilities/ComponentPrepareOrchestration.cs b/src/TeamCloud.Orchestrator/Operations/Orchestrations/Utilities/ComponentPrepareOrchestration.cs index dbdc8a02..e510adf4 100644 --- a/src/TeamCloud.Orchestrator/Operations/Orchestrations/Utilities/ComponentPrepareOrchestration.cs +++ b/src/TeamCloud.Orchestrator/Operations/Orchestrations/Utilities/ComponentPrepareOrchestration.cs @@ -3,11 +3,11 @@ * Licensed under the MIT License. */ -using System; -using System.Threading.Tasks; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; using Microsoft.Extensions.Logging; +using System; +using System.Threading.Tasks; using TeamCloud.Azure.Resources; using TeamCloud.Data; using TeamCloud.Model.Common; @@ -94,6 +94,10 @@ await context .CallActivityWithRetryAsync(nameof(ComponentEnsurePermissionActivity), new ComponentEnsurePermissionActivity.Input() { Component = component }) .ConfigureAwait(true); + component = await context + .CallActivityWithRetryAsync(nameof(ComponentEnsureTaggingActivity), new ComponentEnsureTaggingActivity.Input() { Component = component }) + .ConfigureAwait(true); + component = await UpdateComponentAsync(component, ResourceState.Succeeded) .ConfigureAwait(true); } diff --git a/src/TeamCloud.Orchestrator/Operations/Orchestrations/Utilities/OrchestratorCommandOrchestration.cs b/src/TeamCloud.Orchestrator/Operations/Orchestrations/Utilities/OrchestratorCommandOrchestration.cs index 3a692460..0cdaebf6 100644 --- a/src/TeamCloud.Orchestrator/Operations/Orchestrations/Utilities/OrchestratorCommandOrchestration.cs +++ b/src/TeamCloud.Orchestrator/Operations/Orchestrations/Utilities/OrchestratorCommandOrchestration.cs @@ -3,12 +3,12 @@ * Licensed under the MIT License. */ -using System; -using System.Threading.Tasks; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.DurableTask; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using System; +using System.Threading.Tasks; using TeamCloud.Model.Commands.Core; using TeamCloud.Orchestration; using TeamCloud.Orchestrator.Handlers; @@ -56,7 +56,7 @@ await orchestrationContext orchestrationContext.SetCustomStatus("Augmenting command result", log); commandResult = await orchestrationContext - .CallActivityWithRetryAsync(nameof(CommandResultActivity), commandResult) + .CallActivityWithRetryAsync(nameof(CommandResultActivity), new CommandResultActivity.Input() { CommandResult = commandResult }) .ConfigureAwait(true); } catch (Exception exc) From 83f73abe5bf4ebc9b243fec1e642fba936eaf23b Mon Sep 17 00:00:00 2001 From: Markus Heiliger Date: Sun, 14 Mar 2021 21:58:02 -0700 Subject: [PATCH 2/3] support for component tagging based on project --- .../Controllers/ProjectController.cs | 8 +- .../ComponentEnsureStorageActivity.cs | 73 ----------------- .../ComponentEnsureVaultActivity.cs | 73 ----------------- .../ComponentResolveStorageActivity.cs | 81 +++++++++++++++++++ .../ComponentResolveVaultActivity.cs | 81 +++++++++++++++++++ .../ComponentPrepareOrchestration.cs | 4 +- 6 files changed, 169 insertions(+), 151 deletions(-) delete mode 100644 src/TeamCloud.Orchestrator/Operations/Activities/ComponentEnsureStorageActivity.cs delete mode 100644 src/TeamCloud.Orchestrator/Operations/Activities/ComponentEnsureVaultActivity.cs create mode 100644 src/TeamCloud.Orchestrator/Operations/Activities/ComponentResolveStorageActivity.cs create mode 100644 src/TeamCloud.Orchestrator/Operations/Activities/ComponentResolveVaultActivity.cs diff --git a/src/TeamCloud.API/Controllers/ProjectController.cs b/src/TeamCloud.API/Controllers/ProjectController.cs index a587bd6f..de6da240 100644 --- a/src/TeamCloud.API/Controllers/ProjectController.cs +++ b/src/TeamCloud.API/Controllers/ProjectController.cs @@ -150,9 +150,7 @@ public Task Post([FromBody] ProjectDefinition projectDefinition) Id = projectId, Organization = organization.Id, Users = users, - DisplayName = projectDefinition.DisplayName, - // Tags = projectDefinition.Tags, - // Properties = projectDefinition.Properties + DisplayName = projectDefinition.DisplayName }; ProjectTemplate template = null; @@ -191,6 +189,10 @@ public Task Post([FromBody] ProjectDefinition projectDefinition) project.Template = template.Id; project.TemplateInput = projectDefinition.TemplateInput; + project.Tags = input + .ToObject>() + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToString()); + var currentUser = users.FirstOrDefault(u => u.Id == UserService.CurrentUserId); var command = new ProjectCreateCommand(currentUser, project); diff --git a/src/TeamCloud.Orchestrator/Operations/Activities/ComponentEnsureStorageActivity.cs b/src/TeamCloud.Orchestrator/Operations/Activities/ComponentEnsureStorageActivity.cs deleted file mode 100644 index c6724c78..00000000 --- a/src/TeamCloud.Orchestrator/Operations/Activities/ComponentEnsureStorageActivity.cs +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT License. - */ - -using System; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Azure.WebJobs; -using Microsoft.Azure.WebJobs.Extensions.DurableTask; -using Microsoft.Extensions.Logging; -using TeamCloud.Azure.Resources; -using TeamCloud.Data; -using TeamCloud.Model.Data; -using TeamCloud.Orchestration; - -namespace TeamCloud.Orchestrator.Operations.Activities -{ - public sealed class ComponentEnsureStorageActivity - { - private readonly IProjectRepository projectRepository; - private readonly IAzureResourceService azureResourceService; - - public ComponentEnsureStorageActivity(IProjectRepository projectRepository, IAzureResourceService azureResourceService) - { - this.projectRepository = projectRepository ?? throw new ArgumentNullException(nameof(projectRepository)); - this.azureResourceService = azureResourceService ?? throw new ArgumentNullException(nameof(azureResourceService)); - } - - [FunctionName(nameof(ComponentEnsureStorageActivity))] - [RetryOptions(3)] - public async Task 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().Component; - - if (!AzureResourceIdentifier.TryParse(component.StorageId, out var _)) - { - var project = await projectRepository - .GetAsync(component.Organization, component.ProjectId) - .ConfigureAwait(false); - - if (AzureResourceIdentifier.TryParse(project?.ResourceId, out var projectResourceId)) - { - var projectResourceGroup = await azureResourceService - .GetResourceGroupAsync(projectResourceId.SubscriptionId, projectResourceId.ResourceGroup) - .ConfigureAwait(false); - - var projectStorageResource = await projectResourceGroup - .GetResourcesByTypeAsync("Microsoft.Storage/storageAccounts") - .SingleOrDefaultAsync() - .ConfigureAwait(false); - - component.StorageId = projectStorageResource?.ResourceId.ToString(); - } - } - - return component; - } - - internal struct Input - { - public Component Component { get; set; } - } - } -} diff --git a/src/TeamCloud.Orchestrator/Operations/Activities/ComponentEnsureVaultActivity.cs b/src/TeamCloud.Orchestrator/Operations/Activities/ComponentEnsureVaultActivity.cs deleted file mode 100644 index 5395bbd5..00000000 --- a/src/TeamCloud.Orchestrator/Operations/Activities/ComponentEnsureVaultActivity.cs +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT License. - */ - -using System; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Azure.WebJobs; -using Microsoft.Azure.WebJobs.Extensions.DurableTask; -using Microsoft.Extensions.Logging; -using TeamCloud.Azure.Resources; -using TeamCloud.Data; -using TeamCloud.Model.Data; -using TeamCloud.Orchestration; - -namespace TeamCloud.Orchestrator.Operations.Activities -{ - public sealed class ComponentEnsureVaultActivity - { - private readonly IProjectRepository projectRepository; - private readonly IAzureResourceService azureResourceService; - - public ComponentEnsureVaultActivity(IProjectRepository projectRepository, IAzureResourceService azureResourceService) - { - this.projectRepository = projectRepository ?? throw new ArgumentNullException(nameof(projectRepository)); - this.azureResourceService = azureResourceService ?? throw new ArgumentNullException(nameof(azureResourceService)); - } - - [FunctionName(nameof(ComponentEnsureVaultActivity))] - [RetryOptions(3)] - public async Task 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().Component; - - if (!AzureResourceIdentifier.TryParse(component.VaultId, out var _)) - { - var project = await projectRepository - .GetAsync(component.Organization, component.ProjectId) - .ConfigureAwait(false); - - if (AzureResourceIdentifier.TryParse(project?.ResourceId, out var projectResourceId)) - { - var projectResourceGroup = await azureResourceService - .GetResourceGroupAsync(projectResourceId.SubscriptionId, projectResourceId.ResourceGroup) - .ConfigureAwait(false); - - var projectVaultResource = await projectResourceGroup - .GetResourcesByTypeAsync("Microsoft.KeyVault/vaults") - .SingleOrDefaultAsync() - .ConfigureAwait(false); - - component.VaultId = projectVaultResource?.ResourceId.ToString(); - } - } - - return component; - } - - internal struct Input - { - public Component Component { get; set; } - } - } -} diff --git a/src/TeamCloud.Orchestrator/Operations/Activities/ComponentResolveStorageActivity.cs b/src/TeamCloud.Orchestrator/Operations/Activities/ComponentResolveStorageActivity.cs new file mode 100644 index 00000000..9762f739 --- /dev/null +++ b/src/TeamCloud.Orchestrator/Operations/Activities/ComponentResolveStorageActivity.cs @@ -0,0 +1,81 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.DurableTask; +using Microsoft.Extensions.Logging; +using System; +using System.Linq; +using System.Threading.Tasks; +using TeamCloud.Azure.Resources; +using TeamCloud.Data; +using TeamCloud.Model.Data; +using TeamCloud.Orchestration; +using TeamCloud.Serialization; + +namespace TeamCloud.Orchestrator.Operations.Activities +{ + public sealed class ComponentResolveStorageActivity + { + private readonly IProjectRepository projectRepository; + private readonly IAzureResourceService azureResourceService; + + public ComponentResolveStorageActivity(IProjectRepository projectRepository, IAzureResourceService azureResourceService) + { + this.projectRepository = projectRepository ?? throw new ArgumentNullException(nameof(projectRepository)); + this.azureResourceService = azureResourceService ?? throw new ArgumentNullException(nameof(azureResourceService)); + } + + [FunctionName(nameof(ComponentResolveStorageActivity))] + [RetryOptions(3)] + public async Task Run( + [ActivityTrigger] IDurableActivityContext context, + ILogger log) + { + if (context is null) + throw new ArgumentNullException(nameof(context)); + + if (log is null) + throw new ArgumentNullException(nameof(log)); + + try + { + var component = context.GetInput().Component; + + if (!AzureResourceIdentifier.TryParse(component.StorageId, out var _)) + { + var project = await projectRepository + .GetAsync(component.Organization, component.ProjectId) + .ConfigureAwait(false); + + if (AzureResourceIdentifier.TryParse(project?.ResourceId, out var projectResourceId)) + { + var projectResourceGroup = await azureResourceService + .GetResourceGroupAsync(projectResourceId.SubscriptionId, projectResourceId.ResourceGroup) + .ConfigureAwait(false); + + var projectStorageResource = await projectResourceGroup + .GetResourcesByTypeAsync("Microsoft.Storage/storageAccounts") + .SingleAsync() + .ConfigureAwait(false); + + component.StorageId = projectStorageResource.ResourceId.ToString(); + } + } + + return component; + } + catch (Exception exc) + { + throw exc.AsSerializable(); + } + } + + internal struct Input + { + public Component Component { get; set; } + } + } +} diff --git a/src/TeamCloud.Orchestrator/Operations/Activities/ComponentResolveVaultActivity.cs b/src/TeamCloud.Orchestrator/Operations/Activities/ComponentResolveVaultActivity.cs new file mode 100644 index 00000000..48246d40 --- /dev/null +++ b/src/TeamCloud.Orchestrator/Operations/Activities/ComponentResolveVaultActivity.cs @@ -0,0 +1,81 @@ +/** + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.DurableTask; +using Microsoft.Extensions.Logging; +using System; +using System.Linq; +using System.Threading.Tasks; +using TeamCloud.Azure.Resources; +using TeamCloud.Data; +using TeamCloud.Model.Data; +using TeamCloud.Orchestration; +using TeamCloud.Serialization; + +namespace TeamCloud.Orchestrator.Operations.Activities +{ + public sealed class ComponentResolveVaultActivity + { + private readonly IProjectRepository projectRepository; + private readonly IAzureResourceService azureResourceService; + + public ComponentResolveVaultActivity(IProjectRepository projectRepository, IAzureResourceService azureResourceService) + { + this.projectRepository = projectRepository ?? throw new ArgumentNullException(nameof(projectRepository)); + this.azureResourceService = azureResourceService ?? throw new ArgumentNullException(nameof(azureResourceService)); + } + + [FunctionName(nameof(ComponentResolveVaultActivity))] + [RetryOptions(3)] + public async Task Run( + [ActivityTrigger] IDurableActivityContext context, + ILogger log) + { + if (context is null) + throw new ArgumentNullException(nameof(context)); + + if (log is null) + throw new ArgumentNullException(nameof(log)); + + try + { + var component = context.GetInput().Component; + + if (!AzureResourceIdentifier.TryParse(component.VaultId, out var _)) + { + var project = await projectRepository + .GetAsync(component.Organization, component.ProjectId) + .ConfigureAwait(false); + + if (AzureResourceIdentifier.TryParse(project?.ResourceId, out var projectResourceId)) + { + var projectResourceGroup = await azureResourceService + .GetResourceGroupAsync(projectResourceId.SubscriptionId, projectResourceId.ResourceGroup) + .ConfigureAwait(false); + + var projectVaultResource = await projectResourceGroup + .GetResourcesByTypeAsync("Microsoft.KeyVault/vaults") + .SingleAsync() + .ConfigureAwait(false); + + component.VaultId = projectVaultResource.ResourceId.ToString(); + } + } + + return component; + } + catch (Exception exc) + { + throw exc.AsSerializable(); + } + } + + internal struct Input + { + public Component Component { get; set; } + } + } +} diff --git a/src/TeamCloud.Orchestrator/Operations/Orchestrations/Utilities/ComponentPrepareOrchestration.cs b/src/TeamCloud.Orchestrator/Operations/Orchestrations/Utilities/ComponentPrepareOrchestration.cs index e510adf4..e07a499b 100644 --- a/src/TeamCloud.Orchestrator/Operations/Orchestrations/Utilities/ComponentPrepareOrchestration.cs +++ b/src/TeamCloud.Orchestrator/Operations/Orchestrations/Utilities/ComponentPrepareOrchestration.cs @@ -83,11 +83,11 @@ await context .ConfigureAwait(true); component = await context - .CallActivityWithRetryAsync(nameof(ComponentEnsureStorageActivity), new ComponentEnsureStorageActivity.Input() { Component = component }) + .CallActivityWithRetryAsync(nameof(ComponentResolveStorageActivity), new ComponentResolveStorageActivity.Input() { Component = component }) .ConfigureAwait(true); component = await context - .CallActivityWithRetryAsync(nameof(ComponentEnsureVaultActivity), new ComponentEnsureVaultActivity.Input() { Component = component }) + .CallActivityWithRetryAsync(nameof(ComponentResolveVaultActivity), new ComponentResolveVaultActivity.Input() { Component = component }) .ConfigureAwait(true); component = await context From e22cbd04663786497a70c830f32a00c60f1ec969 Mon Sep 17 00:00:00 2001 From: Markus Heiliger Date: Sun, 14 Mar 2021 22:07:07 -0700 Subject: [PATCH 3/3] added missing license headers --- src/TeamCloud.Git/Data/ComponentPermissionYaml.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/TeamCloud.Git/Data/ComponentPermissionYaml.cs b/src/TeamCloud.Git/Data/ComponentPermissionYaml.cs index 05482d5b..dccfc295 100644 --- a/src/TeamCloud.Git/Data/ComponentPermissionYaml.cs +++ b/src/TeamCloud.Git/Data/ComponentPermissionYaml.cs @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ - namespace TeamCloud.Git.Data { public class ComponentPermissionYaml