// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\r
// SPDX-License-Identifier: Apache-2.0
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using AWS.Deploy.Common;
using AWS.Deploy.Common.IO;
using AWS.Deploy.Common.Recipes;
namespace AWS.Deploy.Orchestration.Utilities
{
public interface ICloudApplicationNameGenerator
{
///
/// Generates a valid candidate for based on .
/// Name is checked to ensure it is unique, ie doesn't already have it.
///
///
/// Thrown if can't generate a valid name from .
///
string GenerateValidName(ProjectDefinition target, List existingApplications, DeploymentTypes? deploymentType = null);
///
/// Validates the cloud application name
///
/// User provided cloud application name
/// The deployment type of the selected recommendation
/// List of existing deployed applications
///
CloudApplicationNameValidationResult IsValidName(string name, IList existingApplications, DeploymentTypes? deploymentType = null);
}
///
/// Stores the result from validating the cloud application name.
///
public class CloudApplicationNameValidationResult
{
public readonly bool IsValid;
public readonly string ErrorMessage;
public CloudApplicationNameValidationResult(bool isValid, string errorMessage)
{
IsValid = isValid;
ErrorMessage = errorMessage;
}
}
public class CloudApplicationNameGenerator : ICloudApplicationNameGenerator
{
private readonly IFileManager _fileManager;
private readonly IDirectoryManager _directoryManager;
///
/// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-using-console-create-stack-parameters.html
///
private readonly Regex _validatorRegex = new ("^[a-zA-Z][a-zA-Z0-9-]{0,127}$", RegexOptions.Compiled);
public CloudApplicationNameGenerator(IFileManager fileManager, IDirectoryManager directoryManager)
{
_fileManager = fileManager;
_directoryManager = directoryManager;
}
public string GenerateValidName(ProjectDefinition target, List existingApplications, DeploymentTypes? deploymentType = null)
{
// generate recommendation
var recommendedPrefix = "deployment";
if (_fileManager.Exists(target.ProjectPath))
recommendedPrefix = Path.GetFileNameWithoutExtension(target.ProjectPath) ?? "";
else if (_directoryManager.Exists(target.ProjectPath))
recommendedPrefix = Path.GetDirectoryName(target.ProjectPath) ?? "";
// sanitize recommendation
recommendedPrefix =
new string(
recommendedPrefix
.ToCharArray()
.SkipWhile(c => !char.IsLetter(c) && ((int)c) < 127)
.Where(c =>
char.IsNumber(c) ||
char.IsLetter(c) && ((int)c) < 127 ||
c == '-')
.ToArray());
// make sure the recommendation doesn't exist already in existingApplications
var recommendation = recommendedPrefix;
var suffixString = "";
var recommendationCharArray = recommendation.ToCharArray();
for (var i = recommendationCharArray.Length - 1; i >= 0; i--)
{
if (char.IsDigit(recommendationCharArray[i]))
suffixString = $"{recommendationCharArray[i]}{suffixString}";
else
break;
}
var prefix = !string.IsNullOrEmpty(suffixString) ? recommendation[..^suffixString.Length] : recommendedPrefix;
var suffix = !string.IsNullOrEmpty(suffixString) ? int.Parse(suffixString): 0;
while (suffix < int.MaxValue)
{
var validationResult = IsValidName(recommendation, existingApplications, deploymentType);
if (validationResult.IsValid)
return recommendation;
recommendation = $"{prefix}{++suffix}";
}
throw new ArgumentException("Failed to generate a valid and unique name.");
}
public CloudApplicationNameValidationResult IsValidName(string name, IList existingApplications, DeploymentTypes? deploymentType = null)
{
var errorMessage = string.Empty;
if (!SatisfiesRegex(name))
{
errorMessage += $"The application name can contain only alphanumeric characters (case-sensitive) and hyphens. " +
$"It must start with an alphabetic character and can't be longer than 128 characters.{Environment.NewLine}";
}
if (MatchesExistingDeployment(name, existingApplications, deploymentType))
{
errorMessage += "A cloud application already exists with this name.";
}
if (string.IsNullOrEmpty(errorMessage))
return new CloudApplicationNameValidationResult(true, string.Empty);
return new CloudApplicationNameValidationResult(false, $"Invalid cloud application name: {name}{Environment.NewLine}{errorMessage}");
}
///
/// This method first filters the existing applications by the current deploymentType if the deploymentType is not null
/// It will then check if the current name matches the filtered list of existing applications
///
/// User provided cloud application name
/// The deployment type of the selected recommendation
/// List of existing deployed applications
/// true if found a match. false otherwise
private bool MatchesExistingDeployment(string name, IList existingApplications, DeploymentTypes? deploymentType = null)
{
if (!existingApplications.Any())
return false;
if (deploymentType != null)
existingApplications = existingApplications.Where(x => x.DeploymentType == deploymentType).ToList();
return existingApplications.Any(x => x.Name == name);
}
///
/// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-using-console-create-stack-parameters.html
///
private bool SatisfiesRegex(string name)
{
return _validatorRegex.IsMatch(name);
}
}
}