diff --git a/src/AWS.Deploy.CLI/Commands/CommandFactory.cs b/src/AWS.Deploy.CLI/Commands/CommandFactory.cs index 60d11511a..ef8dddc2c 100644 --- a/src/AWS.Deploy.CLI/Commands/CommandFactory.cs +++ b/src/AWS.Deploy.CLI/Commands/CommandFactory.cs @@ -211,7 +211,8 @@ private Command BuildDeployCommand() _customRecipeLocator, _systemCapabilityEvaluator, session, - _directoryManager); + _directoryManager, + _fileManager); var deploymentProjectPath = input.DeploymentProject ?? string.Empty; if (!string.IsNullOrEmpty(deploymentProjectPath)) diff --git a/src/AWS.Deploy.CLI/Commands/DeployCommand.cs b/src/AWS.Deploy.CLI/Commands/DeployCommand.cs index 5d3f1d569..cc784a662 100644 --- a/src/AWS.Deploy.CLI/Commands/DeployCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/DeployCommand.cs @@ -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 { @@ -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( @@ -66,7 +68,8 @@ public DeployCommand( ICustomRecipeLocator customRecipeLocator, ISystemCapabilityEvaluator systemCapabilityEvaluator, OrchestratorSession session, - IDirectoryManager directoryManager) + IDirectoryManager directoryManager, + IFileManager fileManager) { _toolInteractiveService = toolInteractiveService; _orchestratorInteractiveService = orchestratorInteractiveService; @@ -83,6 +86,7 @@ public DeployCommand( _consoleUtilities = consoleUtilities; _session = session; _directoryManager = directoryManager; + _fileManager = fileManager; _cdkVersionDetector = cdkVersionDetector; _cdkManager = cdkManager; _customRecipeLocator = customRecipeLocator; @@ -174,12 +178,12 @@ private void DisplayOutputResources(List 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 { @@ -264,15 +268,14 @@ private async Task> GenerateDeploymentRecommendations(Orche return recommendations; } - private async Task GetSelectedRecommendationFromPreviousDeployment(List recommendations, CloudApplication deployedApplication, UserDeploymentSettings? userDeploymentSettings) + private async Task GetSelectedRecommendationFromPreviousDeployment(List 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)) @@ -317,6 +320,51 @@ private async Task GetSelectedRecommendationFromPreviousDeployme return selectedRecommendation; } + private async Task GetRecommendationForRedeployment(List 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 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(recipeBody); + return recipe.Id; + } + catch (Exception ex) + { + throw new FailedToFindDeploymentProjectRecipeIdException($"Failed to find a recipe ID for the deployment project located at {deploymentProjectPath}", ex); + } + } + /// /// This method is used to set the values for Option Setting Items when a deployment is being performed using a user specifed config file. /// diff --git a/src/AWS.Deploy.CLI/Commands/GenerateDeploymentProjectCommand.cs b/src/AWS.Deploy.CLI/Commands/GenerateDeploymentProjectCommand.cs index 1b68391f5..6e159b05c 100644 --- a/src/AWS.Deploy.CLI/Commands/GenerateDeploymentProjectCommand.cs +++ b/src/AWS.Deploy.CLI/Commands/GenerateDeploymentProjectCommand.cs @@ -132,17 +132,17 @@ private async Task> 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. /// - /// The defaukt save directory path. + /// The default save directory path. 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; } @@ -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. /// /// Relative or absolute path of the directory at which the CDK deployment project will be saved. - /// A tuple containaing a boolean that indicates if the directory is valid and a corresponding string error message. + /// A tuple containing a boolean that indicates if the directory is valid and a corresponding string error message. private Tuple ValidateSaveCdkDirectory(string saveCdkDirectoryPath) { var errorMessage = string.Empty; @@ -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 { diff --git a/src/AWS.Deploy.CLI/Exceptions.cs b/src/AWS.Deploy.CLI/Exceptions.cs index 16c38aa1e..88f9df242 100644 --- a/src/AWS.Deploy.CLI/Exceptions.cs +++ b/src/AWS.Deploy.CLI/Exceptions.cs @@ -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) { } + } } diff --git a/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs b/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs index 23b8ab220..e79d42727 100644 --- a/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs +++ b/src/AWS.Deploy.CLI/ServerMode/Controllers/DeploymentController.cs @@ -81,7 +81,21 @@ await _projectParserUtility.Parse(input.ProjectPath) _stateServer.Save(output.SessionId, state); - output.DefaultDeploymentName = _cloudApplicationNameGenerator.GenerateValidName(state.ProjectDefinition, new List()); + var deployedApplicationQueryer = serviceProvider.GetRequiredService(); + 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); } @@ -116,7 +130,8 @@ public async Task 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( @@ -171,6 +186,8 @@ private List 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) }; @@ -246,7 +263,9 @@ public async Task GetExistingDeployments(string sessionId) var deployedApplicationQueryer = serviceProvider.GetRequiredService(); 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) { diff --git a/src/AWS.Deploy.CLI/ServerMode/Models/OptionSettingItemSummary.cs b/src/AWS.Deploy.CLI/ServerMode/Models/OptionSettingItemSummary.cs index b3a86ac68..efc974fff 100644 --- a/src/AWS.Deploy.CLI/ServerMode/Models/OptionSettingItemSummary.cs +++ b/src/AWS.Deploy.CLI/ServerMode/Models/OptionSettingItemSummary.cs @@ -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 @@ -23,6 +24,10 @@ public class OptionSettingItemSummary public bool Updatable { get; set; } + public IList AllowedValues { get; set; } = new List(); + + public IDictionary ValueMapping { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + public List ChildOptionSettings { get; set; } = new(); public OptionSettingItemSummary(string id, string name, string description, string type) diff --git a/src/AWS.Deploy.Common/Recipes/RecipeDefinition.cs b/src/AWS.Deploy.Common/Recipes/RecipeDefinition.cs index 873449b49..aead8a80a 100644 --- a/src/AWS.Deploy.Common/Recipes/RecipeDefinition.cs +++ b/src/AWS.Deploy.Common/Recipes/RecipeDefinition.cs @@ -108,6 +108,11 @@ public class RecipeDefinition /// public bool PersistedDeploymentProject { get; set; } + /// + /// The recipe ID of the parent recipe which was used to create the CDK deployment project. + /// + public string? BaseRecipeId { get; set; } + public RecipeDefinition( string id, string version, diff --git a/src/AWS.Deploy.DockerEngine/Templates/Dockerfile.template b/src/AWS.Deploy.DockerEngine/Templates/Dockerfile.template index 1d6cbfa07..d30b1fc81 100644 --- a/src/AWS.Deploy.DockerEngine/Templates/Dockerfile.template +++ b/src/AWS.Deploy.DockerEngine/Templates/Dockerfile.template @@ -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 diff --git a/src/AWS.Deploy.Orchestration/Utilities/DeployedApplicationQueryer.cs b/src/AWS.Deploy.Orchestration/Utilities/DeployedApplicationQueryer.cs index 7976bd52e..7c0be5b17 100644 --- a/src/AWS.Deploy.Orchestration/Utilities/DeployedApplicationQueryer.cs +++ b/src/AWS.Deploy.Orchestration/Utilities/DeployedApplicationQueryer.cs @@ -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. /// Task> GetCompatibleApplications(List recommendations, List? allDeployedApplications = null, OrchestratorSession? session = null); + + /// + /// Checks if the given recommendation can be used for a redeployment to an existing cloudformation stack. + /// + bool IsCompatible(CloudApplication application, Recommendation recommendation); } public class DeployedApplicationQueryer : IDeployedApplicationQueryer @@ -97,10 +102,12 @@ public async Task> GetCompatibleApplications(List 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) @@ -137,5 +144,17 @@ public async Task> GetCompatibleApplications(List x.LastUpdatedTime) .ToList(); } + + /// + /// Checks if the given recommendation can be used for a redeployment to an existing cloudformation stack. + /// + 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); + } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/.template.config/template.json b/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/.template.config/template.json index 92fd3e3bd..834c9cadd 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/.template.config/template.json +++ b/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/.template.config/template.json @@ -11,7 +11,7 @@ "language": "C#", "type": "project" }, - "sourceName": "ConsoleAppECSFargateScheduleTask", + "sourceName": "BlazorWasm", "preferNameDirectory": true, "symbols": { "AWSDeployRecipesCDKCommonVersion": { diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/AppStack.cs b/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/AppStack.cs index 5fd5a5621..a07df2f7b 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/AppStack.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/AppStack.cs @@ -44,13 +44,13 @@ private void CustomizeCDKProps(CustomizePropsEventArgs 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"); + // } + //} } } } diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/Generated/Configurations/AccessLoggingConfiguration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/Generated/Configurations/AccessLoggingConfiguration.cs index fe714c90d..141cee841 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/Generated/Configurations/AccessLoggingConfiguration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/Generated/Configurations/AccessLoggingConfiguration.cs @@ -21,7 +21,7 @@ public partial class AccessLoggingConfiguration /// /// Enable CloudFront Access Logging. /// - public bool EnableAccessLogging { get; set; } = false; + public bool Enable { get; set; } = false; /// /// Include cookies in access logs. diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/Generated/Configurations/BackendRestApiConfiguration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/Generated/Configurations/BackendRestApiConfiguration.cs new file mode 100644 index 000000000..4937acd78 --- /dev/null +++ b/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/Generated/Configurations/BackendRestApiConfiguration.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Text; + +// This is a generated file from the original deployment recipe. It contains properties for +// all of the settings defined in the recipe file. It is recommended to not modify this file in order +// to allow easy updates to the file when the original recipe that this project was created from has updates. +// This class is marked as a partial class. If you add new settings to the recipe file, those settings should be +// added to partial versions of this class outside of the Generated folder for example in the Configuration folder. + +namespace BlazorWasm.Configurations +{ + public partial class BackendRestApiConfiguration + { + /// + /// Enable Backend rest api + /// + public bool Enable { get; set; } + + /// + /// Uri to the backend rest api + /// + public string Uri { get; set; } + + /// + /// The resource path pattern to determine which request to go to backend rest api. (i.e. "/api/*") + /// + public string ResourcePathPattern { get; set; } + + /// A parameterless constructor is needed for + /// or the classes will fail to initialize. + /// The warnings are disabled since a parameterless constructor will allow non-nullable properties to be initialized with null values. +#nullable disable warnings + public BackendRestApiConfiguration() + { + + } +#nullable restore warnings + + public BackendRestApiConfiguration( + string uri, + string resourcePathPattern + ) + { + Uri = uri; + ResourcePathPattern = resourcePathPattern; + } + } +} diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/Generated/Configurations/Configuration.cs b/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/Generated/Configurations/Configuration.cs index e0881bf0e..9e6e807d4 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/Generated/Configurations/Configuration.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/Generated/Configurations/Configuration.cs @@ -53,6 +53,11 @@ public partial class Configuration /// public Amazon.CDK.AWS.CloudFront.HttpVersion MaxHttpVersion { get; set; } = Amazon.CDK.AWS.CloudFront.HttpVersion.HTTP2; + /// + /// The backend rest api to be added as a origin to the CloudFront distribution + /// + public BackendRestApiConfiguration? BackendApi { get; set; } + /// A parameterless constructor is needed for /// or the classes will fail to initialize. /// The warnings are disabled since a parameterless constructor will allow non-nullable properties to be initialized with null values. diff --git a/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/Generated/Recipe.cs b/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/Generated/Recipe.cs index 5bf7b441f..cd19a3c2f 100644 --- a/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/Generated/Recipe.cs +++ b/src/AWS.Deploy.Recipes/CdkTemplates/BlazorWasm/Generated/Recipe.cs @@ -118,7 +118,7 @@ private void ConfigureCloudFrontDistribution(Configuration settings) distributionProps.ErrorResponses = errorResponses.ToArray(); } - if(settings.AccessLogging?.EnableAccessLogging == true) + if(settings.AccessLogging?.Enable == true) { distributionProps.EnableLogging = true; @@ -157,6 +157,25 @@ private void ConfigureCloudFrontDistribution(Configuration settings) CloudFrontDistribution = new Distribution(this, nameof(CloudFrontDistribution), InvokeCustomizeCDKPropsEvent(nameof(CloudFrontDistribution), this, distributionProps)); + if (settings.BackendApi?.Enable == true) + { + var backendApiUri = new Uri(settings.BackendApi.Uri); + + var httpOrigin = new HttpOrigin(backendApiUri.Host, new HttpOriginProps + { + OriginPath = backendApiUri.PathAndQuery + }); + + // Since this is a backend API where the business logic for the Blazor app caching must be disabled. + var addBehavorOptions = new AddBehaviorOptions + { + AllowedMethods = AllowedMethods.ALLOW_ALL, + CachePolicy = CachePolicy.CACHING_DISABLED + }; + + CloudFrontDistribution.AddBehavior(settings.BackendApi.ResourcePathPattern, httpOrigin, addBehavorOptions); + } + new CfnOutput(this, "EndpointURL", new CfnOutputProps { Description = "Endpoint to access application", diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppAppRunner.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppAppRunner.recipe index 50c057bab..5ea9e8775 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppAppRunner.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppAppRunner.recipe @@ -8,7 +8,7 @@ "CdkProjectTemplate": "../CdkTemplates/AspNetAppAppRunner", "CdkProjectTemplateId": "netdeploy.AspNetAppAppRunner", "ShortDescription": "ASP.NET Core application deployed to AWS App Runner", - "Description": "ASP.NET Core applications built as a container image and deployed to AWS App Runner, a fully management environment as a container image. If your project does not contain a Dockerfile, one will be generated for the project.", + "Description": "This ASP.NET Core application will be built as a container image and deployed to AWS App Runner. If your project does not contain a Dockerfile, it will be automatically generated. Recommended if you want to deploy your application as a container image on a fully managed environment.", "TargetService": "AWS App Runner", "DisplayedResources": [ diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppECSFargate.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppECSFargate.recipe index c8b6e28b7..9da29bf12 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppECSFargate.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppECSFargate.recipe @@ -7,7 +7,7 @@ "DeploymentBundle": "Container", "CdkProjectTemplate": "../CdkTemplates/AspNetAppEcsFargate", "CdkProjectTemplateId": "netdeploy.AspNetAppEcsFargate", - "Description": "ASP.NET Core application built as a container and deployed to Amazon Elastic Container Service (Amazon ECS) with compute power managed by AWS Fargate compute engine. Recommended for applications that can be deployed as a container image. If your project does not contain a Dockerfile, one will be generated for the project.", + "Description": "This ASP.NET Core application will be deployed to Amazon Elastic Container Service (Amazon ECS) with compute power managed by AWS Fargate compute engine. If your project does not contain a Dockerfile, it will be automatically generated. Recommended if you want to deploy your application as a container image on Linux.", "ShortDescription": "ASP.NET Core application deployed to Amazon Elastic Container Service (Amazon ECS).", "TargetService": "Amazon Elastic Container Service", diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe index 03e66c06f..06bb1b421 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ASP.NETAppElasticBeanstalk.recipe @@ -7,7 +7,7 @@ "DeploymentBundle": "DotnetPublishZipFile", "CdkProjectTemplate": "../CdkTemplates/AspNetAppElasticBeanstalkLinux", "CdkProjectTemplateId": "netdeploy.AspNetAppElasticBeanstalkLinux", - "Description": "ASP.NET Core application deployed to AWS Elastic Beanstalk on Linux. Recommended for applications that are not set up to be deployed as containers.", + "Description": "This ASP.NET Core application will be built and deployed to AWS Elastic Beanstalk on Linux. Recommended if you do not want to deploy your application as a container image.", "ShortDescription": "ASP.NET Core application deployed to AWS Elastic Beanstalk on Linux.", "TargetService": "AWS Elastic Beanstalk", diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/BlazorWasm.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/BlazorWasm.recipe index 2b6763076..a4df8ed4d 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/BlazorWasm.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/BlazorWasm.recipe @@ -7,7 +7,7 @@ "DeploymentBundle": "DotnetPublishZipFile", "CdkProjectTemplate": "../CdkTemplates/BlazorWasm", "CdkProjectTemplateId": "netdeploy.BlazorWasm", - "Description": "Blazor WebAssembly application hosted in an Amazon S3 bucket. The Amazon S3 bucket created during deployment will be configured for web hosting and its contents will be open to the public with read access.", + "Description": "This Blazor WebAssembly application will be built and hosted in the new Amazon Simple Storage Service (Amazon S3) bucket. The created Amazon S3 bucket will be configured for web hosting and open to the public with read access.", "ShortDescription": "Blazor WebAssembly application hosted in an Amazon S3 bucket.", "TargetService": "Amazon S3", @@ -120,6 +120,56 @@ "AdvancedSetting": false, "Updatable": true }, + { + "Id": "BackendApi", + "Name": "Backend REST API", + "Description": "URI to a backend rest api that will be added as an origin to the CloudFront distribution. For example an API Gateway endpoint.", + "Type": "Object", + "DefaultValue": "", + "AdvancedSetting": false, + "Updatable": true, + "ChildOptionSettings": [ + { + "Id": "Enable", + "Name": "Enable", + "Description": "Enable adding backend rest api", + "Type": "Bool", + "DefaultValue": false, + "AdvancedSetting": false, + "Updatable": true + }, + { + "Id": "Uri", + "Name": "Uri", + "Description": "Uri to the backend rest api", + "Type": "String", + "DefaultValue": "", + "AdvancedSetting": false, + "Updatable": true, + "DependsOn": [ + { + "Id": "BackendApi.Enable", + "Value": true + } + ] + }, + { + "Id": "ResourcePathPattern", + "Name": "Resource Path Pattern", + "Description": "The resource path pattern to determine which request go to backend rest api. (i.e. \"/api/*\") ", + "Type": "String", + "DefaultValue": "", + "AdvancedSetting": false, + "Updatable": true, + "DependsOn": [ + { + "Id": "BackendApi.Enable", + "Value": true + } + ] + } + ] + }, { "Id": "AccessLogging", "Name": "CloudFront Access Logging", @@ -129,7 +179,7 @@ "Updatable": true, "ChildOptionSettings": [ { - "Id": "EnableAccessLogging", + "Id": "Enable", "Name": "Enable", "Description": "Enable CloudFront Access Logging", "Type": "Bool", @@ -147,7 +197,7 @@ "Updatable": true, "DependsOn": [ { - "Id": "AccessLogging.EnableAccessLogging", + "Id": "AccessLogging.Enable", "Value": true } ] @@ -162,7 +212,7 @@ "Updatable": true, "DependsOn": [ { - "Id": "AccessLogging.EnableAccessLogging", + "Id": "AccessLogging.Enable", "Value": true } ] @@ -177,7 +227,7 @@ "Updatable": true, "DependsOn": [ { - "Id": "AccessLogging.EnableAccessLogging", + "Id": "AccessLogging.Enable", "Value": true }, { @@ -196,7 +246,7 @@ "Updatable": true, "DependsOn": [ { - "Id": "AccessLogging.EnableAccessLogging", + "Id": "AccessLogging.Enable", "Value": true } ] diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateScheduleTask.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateScheduleTask.recipe index 2fe9dc730..af6696556 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateScheduleTask.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateScheduleTask.recipe @@ -7,7 +7,7 @@ "DeploymentBundle": "Container", "CdkProjectTemplate": "../CdkTemplates/ConsoleAppECSFargateScheduleTask", "CdkProjectTemplateId": "netdeploy.ConsoleAppECSFargateScheduleTask", - "Description": "Console application deployed as a scheduled task to Amazon Elastic Container Service (Amazon ECS). Recommended for applications that can be deployed as a container image. If your project does not contain a Dockerfile, one will be generated for the project.", + "Description": "This .NET Console application will be built using a Docker file and deployed as a scheduled task to Amazon Elastic Container Service (Amazon ECS) with compute power managed by AWS Fargate compute engine. If your project does not contain a Dockerfile, it will be automatically generated. Recommended if you want to deploy a scheduled task as a container image on Linux.", "ShortDescription": "Console application deployed as a scheduled task to Amazon Elastic Container Service (Amazon ECS).", "TargetService": "Amazon Elastic Container Service", diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateService.recipe b/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateService.recipe index 8d7e32839..fd6f75f29 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateService.recipe +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/ConsoleAppECSFargateService.recipe @@ -7,7 +7,7 @@ "DeploymentBundle": "Container", "CdkProjectTemplate": "../CdkTemplates/ConsoleAppECSFargateService", "CdkProjectTemplateId": "netdeploy.ConsoleAppEcsFargateService", - "Description": "Console application deployed as a service to Amazon Elastic Container Service (Amazon ECS). Recommended for applications that can be deployed as a container image. If your project does not contain a Dockerfile, one will be generated for the project.", + "Description": "This .NET Console application will be built using a Docker file and deployed as a service to Amazon Elastic Container Service (Amazon ECS) with compute power managed by AWS Fargate compute engine. If your project does not contain a Dockerfile, it will be automatically generated. Recommended if you want to deploy a service as a container image on Linux.", "ShortDescription": "Console application deployed as a service to Amazon Elastic Container Service (Amazon ECS).", "TargetService": "Amazon Elastic Container Service", diff --git a/src/AWS.Deploy.Recipes/RecipeDefinitions/aws-deploy-recipe-schema.json b/src/AWS.Deploy.Recipes/RecipeDefinitions/aws-deploy-recipe-schema.json index eac3d3e32..43ea4479e 100644 --- a/src/AWS.Deploy.Recipes/RecipeDefinitions/aws-deploy-recipe-schema.json +++ b/src/AWS.Deploy.Recipes/RecipeDefinitions/aws-deploy-recipe-schema.json @@ -100,6 +100,16 @@ }, "Validators": { "$ref": "#/definitions/Validators" + }, + "PersistedDeploymentProject": { + "type": "boolean", + "title": "Persisted Deployment Project", + "description": "A boolean value that indicates if this recipe is generated from a saved CDK deployment project." + }, + "BaseRecipeId": { + "type": "string", + "title": "Base Recipe Id", + "description": "The parent recipe Id which was used to create the saved CDK deployment project." } }, "definitions": { diff --git a/src/AWS.Deploy.ServerMode.Client/RestAPI.cs b/src/AWS.Deploy.ServerMode.Client/RestAPI.cs index 576396fda..9f74d9834 100644 --- a/src/AWS.Deploy.ServerMode.Client/RestAPI.cs +++ b/src/AWS.Deploy.ServerMode.Client/RestAPI.cs @@ -1521,6 +1521,12 @@ public partial class OptionSettingItemSummary [Newtonsoft.Json.JsonProperty("updatable", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public bool Updatable { get; set; } + [Newtonsoft.Json.JsonProperty("allowedValues", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection AllowedValues { get; set; } + + [Newtonsoft.Json.JsonProperty("valueMapping", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.IDictionary ValueMapping { get; set; } + [Newtonsoft.Json.JsonProperty("childOptionSettings", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public System.Collections.Generic.ICollection ChildOptionSettings { get; set; } diff --git a/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/RedeploymentTests.cs b/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/RedeploymentTests.cs new file mode 100644 index 000000000..e33e2b4dd --- /dev/null +++ b/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/RedeploymentTests.cs @@ -0,0 +1,172 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.IO; +using System.Linq; +using Amazon.CloudFormation; +using Amazon.ECS; +using AWS.Deploy.CLI.Common.UnitTests.IO; +using AWS.Deploy.CLI.Extensions; +using AWS.Deploy.CLI.IntegrationTests.Extensions; +using AWS.Deploy.CLI.IntegrationTests.Helpers; +using AWS.Deploy.CLI.IntegrationTests.Services; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using Task = System.Threading.Tasks.Task; + +namespace AWS.Deploy.CLI.IntegrationTests.SaveCdkDeploymentProject +{ + public class RedeploymentTests : IDisposable + { + private readonly HttpHelper _httpHelper; + private readonly CloudFormationHelper _cloudFormationHelper; + private readonly ECSHelper _ecsHelper; + private readonly App _app; + private readonly InMemoryInteractiveService _interactiveService; + private bool _isDisposed; + private string _stackName; + + public RedeploymentTests() + { + var serviceCollection = new ServiceCollection(); + + serviceCollection.AddCustomServices(); + serviceCollection.AddTestServices(); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + + _app = serviceProvider.GetService(); + Assert.NotNull(_app); + + _interactiveService = serviceProvider.GetService(); + Assert.NotNull(_interactiveService); + + _httpHelper = new HttpHelper(_interactiveService); + + var cloudFormationClient = new AmazonCloudFormationClient(); + _cloudFormationHelper = new CloudFormationHelper(cloudFormationClient); + + var ecsClient = new AmazonECSClient(); + _ecsHelper = new ECSHelper(ecsClient); + } + + [Fact] + public async Task AttemptWorkFlow() + { + _stackName = $"WebAppWithDockerFile{Guid.NewGuid().ToString().Split('-').Last()}"; + var tempDirectoryPath = new TestAppManager().GetProjectPath(string.Empty); + var projectPath = Path.Combine(tempDirectoryPath, "testapps", "WebAppWithDockerFile"); + var compatibleDeploymentProjectPath = Path.Combine(tempDirectoryPath, "DeploymentProjects", "CompatibleCdkApp"); + var incompatibleDeploymentProjectPath = Path.Combine(tempDirectoryPath, "DeploymentProjects", "IncompatibleCdkApp"); + + // perform inital deployment using ECS Fargate recipe + await PerformInitialDeployment(projectPath); + + // Create a compatible CDK deployment project using the ECS recipe + await Utilities.CreateCDKDeploymentProjectWithRecipeName(projectPath, "Custom ECS Fargate Recipe", "1", compatibleDeploymentProjectPath, underSourceControl: false); + + // Create an incompatible CDK deployment project using the App runner recipe + await Utilities.CreateCDKDeploymentProjectWithRecipeName(projectPath, "Custom App Runner Recipe", "2", incompatibleDeploymentProjectPath, underSourceControl: false); + + // attempt re-deployment using incompatible CDK project + var deployArgs = new[] { "deploy", "--project-path", projectPath, "--deployment-project", incompatibleDeploymentProjectPath, "--stack-name", _stackName, "--diagnostics" }; + var returnCode = await _app.Run(deployArgs); + Assert.Equal(CommandReturnCodes.USER_ERROR, returnCode); + + // attempt re-deployment using compatible CDK project + await _interactiveService.StdInWriter.WriteAsync(Environment.NewLine); // Select default option settings + await _interactiveService.StdInWriter.FlushAsync(); + deployArgs = new[] { "deploy", "--project-path", projectPath, "--deployment-project", compatibleDeploymentProjectPath, "--stack-name", _stackName, "--diagnostics" }; + returnCode = await _app.Run(deployArgs); + Assert.Equal(CommandReturnCodes.SUCCESS, returnCode); + Assert.Equal(StackStatus.UPDATE_COMPLETE, await _cloudFormationHelper.GetStackStatus(_stackName)); + var cluster = await _ecsHelper.GetCluster(_stackName); + Assert.Equal(TaskDefinitionStatus.ACTIVE, cluster.Status); + + // Delete stack + await DeleteStack(); + } + + private async Task PerformInitialDeployment(string projectPath) + { + // Arrange input for deploy + await _interactiveService.StdInWriter.WriteLineAsync("1"); // Select ECS Fargate recommendation + await _interactiveService.StdInWriter.WriteAsync(Environment.NewLine); // Select default option settings + await _interactiveService.StdInWriter.FlushAsync(); + + // Deploy + var deployArgs = new[] { "deploy", "--project-path", projectPath, "--stack-name", _stackName, "--diagnostics" }; + var returnCode = await _app.Run(deployArgs); + Assert.Equal(CommandReturnCodes.SUCCESS, returnCode); + + // Verify application is deployed and running + Assert.Equal(StackStatus.CREATE_COMPLETE, await _cloudFormationHelper.GetStackStatus(_stackName)); + + var cluster = await _ecsHelper.GetCluster(_stackName); + Assert.Equal(TaskDefinitionStatus.ACTIVE, cluster.Status); + + var deployStdOut = _interactiveService.StdOutReader.ReadAllLines(); + + var applicationUrl = deployStdOut.First(line => line.Trim().StartsWith("Endpoint:")) + .Split(" ")[1] + .Trim(); + + // URL could take few more minutes to come live, therefore, we want to wait and keep trying for a specified timeout + await _httpHelper.WaitUntilSuccessStatusCode(applicationUrl, TimeSpan.FromSeconds(5), TimeSpan.FromMinutes(5)); + + // list + var listArgs = new[] { "list-deployments", "--diagnostics" }; + returnCode = await _app.Run(listArgs); + Assert.Equal(CommandReturnCodes.SUCCESS, returnCode); + + // Verify stack exists in list of deployments + var listDeployStdOut = _interactiveService.StdOutReader.ReadAllLines(); + Assert.Contains(listDeployStdOut, (deployment) => _stackName.Equals(deployment)); + } + + private async Task DeleteStack() + { + // Arrange input for delete + await _interactiveService.StdInWriter.WriteAsync("y"); // Confirm delete + await _interactiveService.StdInWriter.FlushAsync(); + var deleteArgs = new[] { "delete-deployment", _stackName, "--diagnostics" }; + + // Delete + var returnCode = await _app.Run(deleteArgs); + Assert.Equal(CommandReturnCodes.SUCCESS, returnCode); + + // Verify application is deleted + Assert.True(await _cloudFormationHelper.IsStackDeleted(_stackName), $"{_stackName} still exists."); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_isDisposed) return; + + if (disposing) + { + var isStackDeleted = _cloudFormationHelper.IsStackDeleted(_stackName).GetAwaiter().GetResult(); + if (!isStackDeleted) + { + _cloudFormationHelper.DeleteStack(_stackName).GetAwaiter().GetResult(); + } + + _interactiveService.ReadStdOutStartToEnd(); + } + + _isDisposed = true; + } + + ~RedeploymentTests() + { + Dispose(false); + } + } +} diff --git a/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/SaveCdkDeploymentProjectTests.cs b/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/SaveCdkDeploymentProjectTests.cs index e8c0b9e37..f8104c926 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/SaveCdkDeploymentProjectTests.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/SaveCdkDeploymentProjectTests.cs @@ -35,7 +35,7 @@ public async Task DefaultSaveDirectory() await Utilities.CreateCDKDeploymentProject(targetApplicationProjectPath); // Verify a bug fix that the IDictionary for TypeHintData was not getting serialized. - var recipeFilePath = Directory.GetFiles(targetApplicationProjectPath + "CDK", "*.recipe", SearchOption.TopDirectoryOnly).FirstOrDefault(); + var recipeFilePath = Directory.GetFiles(targetApplicationProjectPath + ".Deployment", "*.recipe", SearchOption.TopDirectoryOnly).FirstOrDefault(); Assert.True(File.Exists(recipeFilePath)); var recipeRoot = JsonConvert.DeserializeObject(File.ReadAllText(recipeFilePath)); var applicationIAMRoleSetting = recipeRoot.OptionSettings.FirstOrDefault(x => string.Equals(x.Id, "ApplicationIAMRole")); diff --git a/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/Utilities.cs b/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/Utilities.cs index a53e52992..47362996a 100644 --- a/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/Utilities.cs +++ b/test/AWS.Deploy.CLI.IntegrationTests/SaveCdkDeploymentProject/Utilities.cs @@ -32,12 +32,12 @@ public static async Task CreateCDKDeploymentProject(string targetApplicationPath // default save directory if (string.IsNullOrEmpty(saveDirectoryPath)) { - saveDirectoryPath = targetApplicationPath + "CDK"; - deployArgs = new[] { "deployment-project", "generate", "--project-path", targetApplicationPath }; + saveDirectoryPath = targetApplicationPath + ".Deployment"; + deployArgs = new[] { "deployment-project", "generate", "--project-path", targetApplicationPath, "--diagnostics" }; } else { - deployArgs = new[] { "deployment-project", "generate", "--project-path", targetApplicationPath, "--output", saveDirectoryPath }; + deployArgs = new[] { "deployment-project", "generate", "--project-path", targetApplicationPath, "--output", saveDirectoryPath, "--diagnostics" }; } @@ -59,14 +59,18 @@ public static async Task CreateCDKDeploymentProject(string targetApplicationPath VerifyCreatedArtifacts(targetApplicationPath, saveDirectoryPath); } - public static async Task CreateCDKDeploymentProjectWithRecipeName(string targetApplicationPath, string recipeName, string option, string saveDirectoryPath = null, bool isValid = true) + public static async Task CreateCDKDeploymentProjectWithRecipeName(string targetApplicationPath, string recipeName, string option, string saveDirectoryPath = null, bool isValid = true, bool underSourceControl = true) { var (app, interactiveService) = GetAppServiceProvider(); Assert.NotNull(app); Assert.NotNull(interactiveService); // Arrange input for saving the CDK deployment project - await interactiveService.StdInWriter.WriteAsync(option); // select recipe to save the CDK deployment project + await interactiveService.StdInWriter.WriteLineAsync(option); // select recipe to save the CDK deployment project + if (!underSourceControl) + { + await interactiveService.StdInWriter.WriteAsync("y"); // proceed to save without source control. + } await interactiveService.StdInWriter.FlushAsync(); @@ -74,12 +78,12 @@ public static async Task CreateCDKDeploymentProjectWithRecipeName(string targetA // default save directory if (string.IsNullOrEmpty(saveDirectoryPath)) { - saveDirectoryPath = targetApplicationPath + "CDK"; - deployArgs = new[] { "deployment-project", "generate", "--project-path", targetApplicationPath, "--project-display-name", recipeName}; + saveDirectoryPath = targetApplicationPath + ".Deployment"; + deployArgs = new[] { "deployment-project", "generate", "--project-path", targetApplicationPath, "--project-display-name", recipeName, "--diagnostics"}; } else { - deployArgs = new[] { "deployment-project", "generate", "--project-path", targetApplicationPath, "--output", saveDirectoryPath, "--project-display-name", recipeName }; + deployArgs = new[] { "deployment-project", "generate", "--project-path", targetApplicationPath, "--output", saveDirectoryPath, "--project-display-name", recipeName, "--diagnostics" }; } diff --git a/version.json b/version.json index 16e61c6b1..fde578e20 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "0.20", + "version": "0.21", "publicReleaseRefSpec": [ ".*" ],