Skip to content

Commit

Permalink
Merge pull request #349 from aws/dev
Browse files Browse the repository at this point in the history
chore: release 0.21
  • Loading branch information
96malhar authored Oct 7, 2021
2 parents 869be21 + eda4d65 commit 9a4d9d2
Show file tree
Hide file tree
Showing 27 changed files with 472 additions and 50 deletions.
3 changes: 2 additions & 1 deletion src/AWS.Deploy.CLI/Commands/CommandFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,8 @@ private Command BuildDeployCommand()
_customRecipeLocator,
_systemCapabilityEvaluator,
session,
_directoryManager);
_directoryManager,
_fileManager);
var deploymentProjectPath = input.DeploymentProject ?? string.Empty;
if (!string.IsNullOrEmpty(deploymentProjectPath))
Expand Down
62 changes: 55 additions & 7 deletions src/AWS.Deploy.CLI/Commands/DeployCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using AWS.Deploy.Orchestration.DisplayedResources;
using AWS.Deploy.Common.IO;
using AWS.Deploy.Orchestration.LocalUserSettings;
using Newtonsoft.Json;

namespace AWS.Deploy.CLI.Commands
{
Expand All @@ -45,6 +46,7 @@ public class DeployCommand
private readonly ISystemCapabilityEvaluator _systemCapabilityEvaluator;
private readonly OrchestratorSession _session;
private readonly IDirectoryManager _directoryManager;
private readonly IFileManager _fileManager;
private readonly ICDKVersionDetector _cdkVersionDetector;

public DeployCommand(
Expand All @@ -66,7 +68,8 @@ public DeployCommand(
ICustomRecipeLocator customRecipeLocator,
ISystemCapabilityEvaluator systemCapabilityEvaluator,
OrchestratorSession session,
IDirectoryManager directoryManager)
IDirectoryManager directoryManager,
IFileManager fileManager)
{
_toolInteractiveService = toolInteractiveService;
_orchestratorInteractiveService = orchestratorInteractiveService;
Expand All @@ -83,6 +86,7 @@ public DeployCommand(
_consoleUtilities = consoleUtilities;
_session = session;
_directoryManager = directoryManager;
_fileManager = fileManager;
_cdkVersionDetector = cdkVersionDetector;
_cdkManager = cdkManager;
_customRecipeLocator = customRecipeLocator;
Expand Down Expand Up @@ -174,12 +178,12 @@ private void DisplayOutputResources(List<DisplayedResourceItem> displayedResourc
// Verify that the target application can be deployed using the current set of recommendations
if (!compatibleApplications.Any(app => app.StackName.Equals(deployedApplication.StackName, StringComparison.Ordinal)))
{
var errorMessage = $"{deployedApplication.StackName} already exists as a Cloudformation stack but a compatible recommendation to perform a redeployment was no found";
var errorMessage = $"{deployedApplication.StackName} already exists as a Cloudformation stack but a compatible recommendation to perform a redeployment was not found";
throw new FailedToFindCompatibleRecipeException(errorMessage);
}

// preset settings for deployment based on last deployment.
selectedRecommendation = await GetSelectedRecommendationFromPreviousDeployment(recommendations, deployedApplication, userDeploymentSettings);
selectedRecommendation = await GetSelectedRecommendationFromPreviousDeployment(recommendations, deployedApplication, userDeploymentSettings, deploymentProjectPath);
}
else
{
Expand Down Expand Up @@ -264,15 +268,14 @@ private async Task<List<Recommendation>> GenerateDeploymentRecommendations(Orche
return recommendations;
}

private async Task<Recommendation> GetSelectedRecommendationFromPreviousDeployment(List<Recommendation> recommendations, CloudApplication deployedApplication, UserDeploymentSettings? userDeploymentSettings)
private async Task<Recommendation> GetSelectedRecommendationFromPreviousDeployment(List<Recommendation> recommendations, CloudApplication deployedApplication, UserDeploymentSettings? userDeploymentSettings, string deploymentProjectPath)
{
var existingCloudApplicationMetadata = await _templateMetadataReader.LoadCloudApplicationMetadata(deployedApplication.Name);
var deploymentSettingRecipeId = userDeploymentSettings?.RecipeId;
var selectedRecommendation = recommendations.FirstOrDefault(x => string.Equals(x.Recipe.Id, deployedApplication.RecipeId, StringComparison.InvariantCultureIgnoreCase));

var selectedRecommendation = await GetRecommendationForRedeployment(recommendations, deployedApplication, deploymentProjectPath);
if (selectedRecommendation == null)
{
var errorMessage = $"{deployedApplication.StackName} already exists as a Cloudformation stack but the recommendation used to deploy to the stack was not found.";
var errorMessage = $"{deployedApplication.StackName} already exists as a Cloudformation stack but a compatible recommendation used to perform a re-deployment was not found.";
throw new FailedToFindCompatibleRecipeException(errorMessage);
}
if (!string.IsNullOrEmpty(deploymentSettingRecipeId) && !string.Equals(deploymentSettingRecipeId, selectedRecommendation.Recipe.Id, StringComparison.InvariantCultureIgnoreCase))
Expand Down Expand Up @@ -317,6 +320,51 @@ private async Task<Recommendation> GetSelectedRecommendationFromPreviousDeployme
return selectedRecommendation;
}

private async Task<Recommendation?> GetRecommendationForRedeployment(List<Recommendation> recommendations, CloudApplication deployedApplication, string deploymentProjectPath)
{
var targetRecipeId = !string.IsNullOrEmpty(deploymentProjectPath) ?
await GetDeploymentProjectRecipeId(deploymentProjectPath) : deployedApplication.RecipeId;

foreach (var recommendation in recommendations)
{
if (string.Equals(recommendation.Recipe.Id, targetRecipeId) && _deployedApplicationQueryer.IsCompatible(deployedApplication, recommendation))
{
return recommendation;
}
}
return null;
}

private async Task<string> GetDeploymentProjectRecipeId(string deploymentProjectPath)
{
if (!_directoryManager.Exists(deploymentProjectPath))
{
throw new InvalidOperationException($"Invalid deployment project path. {deploymentProjectPath} does not exist on the file system.");
}

try
{
var recipeFiles = _directoryManager.GetFiles(deploymentProjectPath, "*.recipe");
if (recipeFiles.Length == 0)
{
throw new InvalidOperationException($"Failed to find a recipe file at {deploymentProjectPath}");
}
if (recipeFiles.Length > 1)
{
throw new InvalidOperationException($"Found more than one recipe files at {deploymentProjectPath}. Only one recipe file per deployment project is supported.");
}

var recipeFilePath = recipeFiles.First();
var recipeBody = await _fileManager.ReadAllTextAsync(recipeFilePath);
var recipe = JsonConvert.DeserializeObject<RecipeDefinition>(recipeBody);
return recipe.Id;
}
catch (Exception ex)
{
throw new FailedToFindDeploymentProjectRecipeIdException($"Failed to find a recipe ID for the deployment project located at {deploymentProjectPath}", ex);
}
}

/// <summary>
/// This method is used to set the values for Option Setting Items when a deployment is being performed using a user specifed config file.
/// </summary>
Expand Down
11 changes: 6 additions & 5 deletions src/AWS.Deploy.CLI/Commands/GenerateDeploymentProjectCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,17 +132,17 @@ private async Task<List<Recommendation>> GenerateRecommendationsToSaveDeployment
/// a default save directory inside the parent folder of the current directory.
/// For example:
/// Target project directory - C:\Codebase\MyWebApp
/// Generated default save directory - C:\Codebase\MyWebAppCDK If the save directory already exists, then a suffix number is attached.
/// Generated default save directory - C:\Codebase\MyWebApp.Deployment If the save directory already exists, then a suffix number is attached.
/// </summary>
/// <returns>The defaukt save directory path.</returns>
/// <returns>The default save directory path.</returns>
private string GenerateDefaultSaveDirectoryPath()
{
var applicatonDirectoryFullPath = _directoryManager.GetDirectoryInfo(_targetApplicationFullPath).Parent.FullName;
var saveCdkDirectoryFullPath = applicatonDirectoryFullPath + "CDK";
var saveCdkDirectoryFullPath = applicatonDirectoryFullPath + ".Deployment";

var suffixNumber = 0;
while (_directoryManager.Exists(saveCdkDirectoryFullPath))
saveCdkDirectoryFullPath = applicatonDirectoryFullPath + $"CDK{++suffixNumber}";
saveCdkDirectoryFullPath = applicatonDirectoryFullPath + $".Deployment{++suffixNumber}";

return saveCdkDirectoryFullPath;
}
Expand All @@ -167,7 +167,7 @@ private bool CreateSaveCdkDirectory(string saveCdkDirectoryPath)
/// This method takes the path to the intended location of the CDK deployment project and performs validations on it.
/// </summary>
/// <param name="saveCdkDirectoryPath">Relative or absolute path of the directory at which the CDK deployment project will be saved.</param>
/// <returns>A tuple containaing a boolean that indicates if the directory is valid and a corresponding string error message.</returns>
/// <returns>A tuple containing a boolean that indicates if the directory is valid and a corresponding string error message.</returns>
private Tuple<bool, string> ValidateSaveCdkDirectory(string saveCdkDirectoryPath)
{
var errorMessage = string.Empty;
Expand Down Expand Up @@ -234,6 +234,7 @@ private async Task GenerateDeploymentRecipeSnapShot(Recommendation recommendatio
recipe.CdkProjectTemplate = null;
recipe.PersistedDeploymentProject = true;
recipe.RecipePriority = DEFAULT_PERSISTED_RECIPE_PRIORITY;
recipe.BaseRecipeId = recommendation.Recipe.Id;

var recipeSnapshotBody = JsonConvert.SerializeObject(recipe, new JsonSerializerSettings
{
Expand Down
5 changes: 5 additions & 0 deletions src/AWS.Deploy.CLI/Exceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,9 @@ public class InvalidSaveDirectoryForCdkProject : Exception
{
public InvalidSaveDirectoryForCdkProject(string message, Exception? innerException = null) : base(message, innerException) { }
}

public class FailedToFindDeploymentProjectRecipeIdException : Exception
{
public FailedToFindDeploymentProjectRecipeIdException(string message, Exception? innerException = null) : base(message, innerException) { }
}
}
25 changes: 22 additions & 3 deletions src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,21 @@ await _projectParserUtility.Parse(input.ProjectPath)

_stateServer.Save(output.SessionId, state);

output.DefaultDeploymentName = _cloudApplicationNameGenerator.GenerateValidName(state.ProjectDefinition, new List<CloudApplication>());
var deployedApplicationQueryer = serviceProvider.GetRequiredService<IDeployedApplicationQueryer>();
var session = CreateOrchestratorSession(state);
var orchestrator = CreateOrchestrator(state);

// Determine what recommendations are possible for the project.
var recommendations = await orchestrator.GenerateDeploymentRecommendations();
state.NewRecommendations = recommendations;

// Get all existing applications that were previously deployed using our deploy tool.
var allDeployedApplications = await deployedApplicationQueryer.GetExistingDeployedApplications();

var existingApplications = await deployedApplicationQueryer.GetCompatibleApplications(recommendations, allDeployedApplications, session);
state.ExistingDeployments = existingApplications;

output.DefaultDeploymentName = _cloudApplicationNameGenerator.GenerateValidName(state.ProjectDefinition, existingApplications);
return Ok(output);
}

Expand Down Expand Up @@ -116,7 +130,8 @@ public async Task<IActionResult> GetRecommendations(string sessionId)

var output = new GetRecommendationsOutput();

state.NewRecommendations = await orchestrator.GenerateDeploymentRecommendations();
//NewRecommendations is set during StartDeploymentSession API. It is only updated here if NewRecommendations was null.
state.NewRecommendations ??= await orchestrator.GenerateDeploymentRecommendations();
foreach (var recommendation in state.NewRecommendations)
{
output.Recommendations.Add(new RecommendationSummary(
Expand Down Expand Up @@ -171,6 +186,8 @@ private List<OptionSettingItemSummary> ListOptionSettingSummary(Recommendation r
Value = recommendation.GetOptionSettingValue(setting),
Advanced = setting.AdvancedSetting,
Updatable = (!recommendation.IsExistingCloudApplication || setting.Updatable) && recommendation.IsOptionSettingDisplayable(setting),
AllowedValues = setting.AllowedValues,
ValueMapping = setting.ValueMapping,
ChildOptionSettings = ListOptionSettingSummary(recommendation, setting.ChildOptionSettings)
};

Expand Down Expand Up @@ -246,7 +263,9 @@ public async Task<IActionResult> GetExistingDeployments(string sessionId)

var deployedApplicationQueryer = serviceProvider.GetRequiredService<IDeployedApplicationQueryer>();
var session = CreateOrchestratorSession(state);
state.ExistingDeployments = await deployedApplicationQueryer.GetCompatibleApplications(state.NewRecommendations.ToList(), session: session);

//ExistingDeployments is set during StartDeploymentSession API. It is only updated here if ExistingDeployments was null.
state.ExistingDeployments ??= await deployedApplicationQueryer.GetCompatibleApplications(state.NewRecommendations.ToList(), session: session);

foreach(var deployment in state.ExistingDeployments)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\r
// SPDX-License-Identifier: Apache-2.0

using System;
using System.Collections.Generic;

namespace AWS.Deploy.CLI.ServerMode.Models
Expand All @@ -23,6 +24,10 @@ public class OptionSettingItemSummary

public bool Updatable { get; set; }

public IList<string> AllowedValues { get; set; } = new List<string>();

public IDictionary<string, string> ValueMapping { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

public List<OptionSettingItemSummary> ChildOptionSettings { get; set; } = new();

public OptionSettingItemSummary(string id, string name, string description, string type)
Expand Down
5 changes: 5 additions & 0 deletions src/AWS.Deploy.Common/Recipes/RecipeDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ public class RecipeDefinition
/// </summary>
public bool PersistedDeploymentProject { get; set; }

/// <summary>
/// The recipe ID of the parent recipe which was used to create the CDK deployment project.
/// </summary>
public string? BaseRecipeId { get; set; }

public RecipeDefinition(
string id,
string version,
Expand Down
4 changes: 4 additions & 0 deletions src/AWS.Deploy.DockerEngine/Templates/Dockerfile.template
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ WORKDIR "/src/{project-folder}"
RUN dotnet build "{project-name}" -c Release -o /app/build

FROM build AS publish
RUN apt-get update -yq \
&& apt-get install curl gnupg -yq \
&& curl -sL https://deb.nodesource.com/setup_10.x | bash \
&& apt-get install nodejs -yq
RUN dotnet publish "{project-name}" -c Release -o /app/publish

FROM base AS final
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ public interface IDeployedApplicationQueryer
/// Get the list of compatible applications based on the matching elements of the deployed stack and recommendation, such as Recipe Id.
/// </summary>
Task<List<CloudApplication>> GetCompatibleApplications(List<Recommendation> recommendations, List<CloudApplication>? allDeployedApplications = null, OrchestratorSession? session = null);

/// <summary>
/// Checks if the given recommendation can be used for a redeployment to an existing cloudformation stack.
/// </summary>
bool IsCompatible(CloudApplication application, Recommendation recommendation);
}

public class DeployedApplicationQueryer : IDeployedApplicationQueryer
Expand Down Expand Up @@ -97,10 +102,12 @@ public async Task<List<CloudApplication>> GetCompatibleApplications(List<Recomme
if (allDeployedApplications == null)
allDeployedApplications = await GetExistingDeployedApplications();

foreach (var app in allDeployedApplications)
foreach (var application in allDeployedApplications)
{
if (recommendations.Any(rec => string.Equals(rec.Recipe.Id, app.RecipeId, StringComparison.Ordinal)))
compatibleApplications.Add(app);
if (recommendations.Any(rec => IsCompatible(application, rec)))
{
compatibleApplications.Add(application);
}
}

if (session != null)
Expand Down Expand Up @@ -137,5 +144,17 @@ public async Task<List<CloudApplication>> GetCompatibleApplications(List<Recomme
.OrderByDescending(x => x.LastUpdatedTime)
.ToList();
}

/// <summary>
/// Checks if the given recommendation can be used for a redeployment to an existing cloudformation stack.
/// </summary>
public bool IsCompatible(CloudApplication application, Recommendation recommendation)
{
if (recommendation.Recipe.PersistedDeploymentProject)
{
return string.Equals(recommendation.Recipe.BaseRecipeId, application.RecipeId, StringComparison.Ordinal);
}
return string.Equals(recommendation.Recipe.Id, application.RecipeId, StringComparison.Ordinal);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"language": "C#",
"type": "project"
},
"sourceName": "ConsoleAppECSFargateScheduleTask",
"sourceName": "BlazorWasm",
"preferNameDirectory": true,
"symbols": {
"AWSDeployRecipesCDKCommonVersion": {
Expand Down
14 changes: 7 additions & 7 deletions src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/AppStack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ private void CustomizeCDKProps(CustomizePropsEventArgs<Recipe> evnt)
{
// Example of how to customize the container image definition to include environment variables to the running applications.
//
if (string.Equals(evnt.ResourceLogicalName, nameof(evnt.Construct.CloudFrontDistribution)))
{
if (evnt.Props is DistributionProps props)
{
Console.WriteLine("Customizing CloudFront Distribution");
}
}
//if (string.Equals(evnt.ResourceLogicalName, nameof(evnt.Construct.CloudFrontDistribution)))
//{
// if (evnt.Props is DistributionProps props)
// {
// Console.WriteLine("Customizing CloudFront Distribution");
// }
//}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public partial class AccessLoggingConfiguration
/// <summary>
/// Enable CloudFront Access Logging.
/// </summary>
public bool EnableAccessLogging { get; set; } = false;
public bool Enable { get; set; } = false;

/// <summary>
/// Include cookies in access logs.
Expand Down
Loading

0 comments on commit 9a4d9d2

Please sign in to comment.