Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bug in configure-continuous-deployments command and maintain developer CLI #684

2 changes: 1 addition & 1 deletion developer-cli/Commands/CodeCoverageCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ private int Execute(string? solutionName)
);

var codeCoverageReport = Path.Combine(Configuration.ApplicationFolder, "coverage", "dotCover.html");
AnsiConsole.MarkupLine($"[green]Code Coverage Report[/] {codeCoverageReport}");
AnsiConsole.MarkupLine($"[green]Code Coverage Report.[/] {codeCoverageReport}");
ProcessHelper.StartProcess($"open {codeCoverageReport}", Configuration.ApplicationFolder);

return 0;
Expand Down
163 changes: 83 additions & 80 deletions developer-cli/Commands/ConfigureContinuousDeploymentsCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ public class ConfigureContinuousDeploymentsCommand : Command

private static readonly Dictionary<string, string> AzureLocations = GetAzureLocations();

private static List<ConfigureContinuousDeployments>? _configureContinuousDeploymentsExtensions;
private List<ConfigureContinuousDeployments>? _configureContinuousDeploymentsExtensions;

public ConfigureContinuousDeploymentsCommand() : base(
"configure-continuous-deployments",
"Set up trust between Azure and GitHub for passwordless deployments using OpenID Connect."
"Set up trust between Azure and GitHub for passwordless deployments using OpenID Connect"
)
{
AddOption(new Option<bool>(["--verbose-logging"], "Print Azure and GitHub CLI commands and output"));
Expand Down Expand Up @@ -242,9 +242,9 @@ private void SelectAzureSubscriptions()
Config.StagingSubscription = SelectSubscription("Staging");
Config.ProductionSubscription = SelectSubscription("Production");

if(Config.StagingSubscription.TenantId != Config.ProductionSubscription.TenantId)
if (Config.StagingSubscription.TenantId != Config.ProductionSubscription.TenantId)
{
AnsiConsole.MarkupLine($"[red]ERROR:[/] Please select two subscriptions from the same tenant, and try again.");
AnsiConsole.MarkupLine("[red]ERROR:[/] Please select two subscriptions from the same tenant, and try again.");
Environment.Exit(1);
}

Expand Down Expand Up @@ -468,55 +468,55 @@ private void ConfirmChangesPrompt()
[bold]Please review planned changes before continuing.[/]

1. The following will be created or updated in Azure:

[bold]Active Directory App Registrations/Service Principals:[/]
* [blue]{Config.StagingSubscription.AppRegistration.Name}[/] with access to the [blue]{Config.StagingSubscription.Name}[/] subscription.
* [blue]{Config.ProductionSubscription.AppRegistration.Name}[/] with access to the [blue]{Config.ProductionSubscription.Name}[/] subscription.

[yellow]** The Service Principals will get 'Contributor' and 'User Access Administrator' role on the Azure Subscriptions.[/]

[bold]Active Directory Security Groups:[/]
* [blue]{Config.StagingSubscription.SqlAdminsGroup.Name}[/]
* [blue]{Config.ProductionSubscription.SqlAdminsGroup.Name}[/]

[yellow]** The SQL Admins Security Groups are used to grant Managed Identities and CI/CD permissions to SQL Databases.[/]

2. The following GitHub environments will be created if not exists:
* [blue]staging[/]
* [blue]production[/]

[yellow]** Environments are used to require approval when infrastructure is deployed. In private GitHub repositories, this requires a paid plan.[/]

3. The following GitHub repository variables will be created:

[bold]Shared Variables:[/]
* TENANT_ID: [blue]{Config.TenantId}[/]
* UNIQUE_PREFIX: [blue]{Config.UniquePrefix}[/]

[bold]Staging Shared Variables:[/]
* STAGING_SUBSCRIPTION_ID: [blue]{Config.StagingSubscription.Id}[/]
* STAGING_SHARED_LOCATION: [blue]{Config.StagingLocation.SharedLocation}[/]
* STAGING_SERVICE_PRINCIPAL_ID: [blue]{stagingServicePrincipal}[/]
* STAGING_SQL_ADMIN_OBJECT_ID: [blue]{stagingSqlAdminObject}[/]
* STAGING_DOMAIN_NAME: [blue]-[/] ([yellow]Manually changed this and triggered deployment to set up the domain[/])

[bold]Staging Cluster Variables:[/]
* STAGING_CLUSTER_ENABLED: [blue]true[/]
* STAGING_CLUSTER_LOCATION: [blue]{Config.StagingLocation.ClusterLocation}[/]
* STAGING_CLUSTER_LOCATION_ACRONYM: [blue]{Config.StagingLocation.ClusterLocationAcronym}[/]

[bold]Production Shared Variables:[/]
* PRODUCTION_SUBSCRIPTION_ID: [blue]{Config.ProductionSubscription.Id}[/]
* PRODUCTION_SHARED_LOCATION: [blue]{Config.ProductionLocation.SharedLocation}[/]
* PRODUCTION_SERVICE_PRINCIPAL_ID: [blue]{productionServicePrincipal}[/]
* PRODUCTION_SQL_ADMIN_OBJECT_ID: [blue]{productionSqlAdminObject}[/]
* PRODUCTION_DOMAIN_NAME: [blue]-[/] ([yellow]Manually changed this and triggered deployment to set up the domain[/])

[bold]Production Cluster 1 Variables:[/]
* PRODUCTION_CLUSTER1_ENABLED: [blue]false[/] ([yellow]Change this to 'true' when ready to deploy to production[/])
* PRODUCTION_CLUSTER1_LOCATION: [blue]{Config.ProductionLocation.ClusterLocation}[/]
* PRODUCTION_CLUSTER1_LOCATION_ACRONYM: [blue]{Config.ProductionLocation.ClusterLocationAcronym}[/]

[yellow]** All variables can be changed on the GitHub Settings page. For example, if you want to deploy production or staging to different locations.[/]

4. Disable the reusable GitHub workflows [blue]Deploy Container[/] and [blue]Plan and Deploy Infrastructure[/].
Expand Down Expand Up @@ -619,22 +619,24 @@ void CreateFederatedCredential(string appRegistrationId, string displayName, str
{
var parameters = JsonSerializer.Serialize(new
{
name = displayName,
issuer = "https://token.actions.githubusercontent.com",
subject = $"""repo:{Config.GithubInfo!.Path}:{refRefsHeadsMain}""",
audiences = new[] { "api://AzureADTokenExchange" }
}
name = displayName,
issuer = "https://token.actions.githubusercontent.com",
subject = $"""repo:{Config.GithubInfo!.Path}:{refRefsHeadsMain}""",
audiences = new[] { "api://AzureADTokenExchange" }
}
);

ProcessHelper.StartProcess(new ProcessStartInfo
{
FileName = Configuration.IsWindows ? "cmd.exe" : "az",
Arguments =
$"{(Configuration.IsWindows ? "/C az" : string.Empty)} ad app federated-credential create --id {appRegistrationId} --parameters @-",
RedirectStandardInput = true,
RedirectStandardOutput = !Configuration.VerboseLogging,
RedirectStandardError = !Configuration.VerboseLogging
}, parameters
FileName = Configuration.IsWindows ? "cmd.exe" : "az",
Arguments =
$"{(Configuration.IsWindows ? "/C az" : string.Empty)} ad app federated-credential create --id {appRegistrationId} --parameters @-",
RedirectStandardInput = true,
RedirectStandardOutput = !Configuration.VerboseLogging,
RedirectStandardError = !Configuration.VerboseLogging
},
parameters,
exitOnError: false
);
}
}
Expand Down Expand Up @@ -773,6 +775,7 @@ void DisableActiveWorkflow(string workflowName)
}
}

// ReSharper disable once MemberCanBeMadeStatic.Local
private void TriggerAndMonitorWorkflows()
{
AnsiConsole.Status().Start("Begin deployment.", ctx =>
Expand Down Expand Up @@ -872,9 +875,9 @@ private void ShowSuccessMessage()
- To add a step for manual approval during infrastructure deployment to the staging and production environments, set up required reviewers on GitHub environments. Visit [blue]{Config.GithubInfo!.Url}/settings/environments[/] and enable [blue]Required reviewers[/] for the [bold]staging[/] and [bold]production[/] environments. Requires a paid GitHub plan for private repositories.

- Configure the Domain Name for the staging and production environments. This involves two steps:

a. Go to [blue]{Config.GithubInfo!.Url}/settings/variables/actions[/] to set the [blue]DOMAIN_NAME_STAGING[/] and [blue]DOMAIN_NAME_PRODUCTION[/] variables. E.g. [blue]staging.your-saas-company.com[/] and [blue]your-saas-company.com[/].

b. Run the [blue]Cloud Infrastructure - Deployment[/] workflow again. Note that it might fail with an error message to set up a DNS TXT and CNAME record. Once done, re-run the failed jobs.

- Set up SonarCloud for code quality and security analysis. This service is free for public repositories. Visit [blue]https://sonarcloud.io[/] to connect your GitHub account. Add the [blue]SONAR_TOKEN[/] secret, and the [blue]SONAR_ORGANIZATION[/] and [blue]SONAR_PROJECT_KEY[/] variables to the GitHub repository. The workflows are already configured for SonarCloud analysis.
Expand Down Expand Up @@ -905,7 +908,7 @@ private string RunAzureCliCommand(string arguments, bool redirectOutput = true)
{
var azureCliCommand = Configuration.IsWindows ? "cmd.exe /C az" : "az";

return ProcessHelper.StartProcess($"{azureCliCommand} {arguments}", redirectOutput: redirectOutput);
return ProcessHelper.StartProcess($"{azureCliCommand} {arguments}", redirectOutput: redirectOutput, exitOnError: false);
}

private static Dictionary<string, string> GetAzureLocations()
Expand All @@ -915,51 +918,51 @@ private static Dictionary<string, string> GetAzureLocations()
// Location Acronyms are taken from here https://learn.microsoft.com/en-us/azure/backup/scripts/geo-code-list
return new Dictionary<string, string>
{
{ "Australia Central", "acl" },
{ "Australia Central 2", "acl2" },
{ "Australia East", "ae" },
{ "Australia Southeast", "ase" },
{ "Brazil South", "brs" },
{ "Brazil Southeast", "bse" },
{ "Canada Central", "cnc" },
{ "Canada East", "cne" },
{ "Central India", "inc" },
{ "Central US", "cus" },
{ "East Asia", "ea" },
{ "East US", "eus" },
{ "East US 2", "eus2" },
{ "France Central", "frc" },
{ "France South", "frs" },
{ "Germany North", "gn" },
{ "Germany West Central", "gwc" },
{ "Japan East", "jpe" },
{ "Japan West", "jpw" },
{ "Jio India Central", "jic" },
{ "Jio India West", "jiw" },
{ "Korea Central", "krc" },
{ "Korea South", "krs" },
{ "North Central US", "ncus" },
{ "North Europe", "ne" },
{ "Norway East", "nwe" },
{ "Norway West", "nww" },
{ "South Africa North", "san" },
{ "South Africa West", "saw" },
{ "South Central US", "scus" },
{ "South India", "ins" },
{ "Southeast Asia", "sea" },
{ "Sweden Central", "sdc" },
{ "Switzerland North", "szn" },
{ "Switzerland West", "szw" },
{ "UAE Central", "uac" },
{ "UAE North", "uan" },
{ "UK South", "uks" },
{ "UK West", "ukw" },
{ "West Central US", "wcus" },
{ "West Europe", "we" },
{ "West India", "inw" },
{ "West US", "wus" },
{ "West US 2", "wus2" },
{ "West US 3", "wus3" }
{ "Australia Central", "au" },
{ "Australia Central 2", "au" },
{ "Australia East", "au" },
{ "Australia Southeast", "au" },
{ "Brazil South", "br" },
{ "Brazil Southeast", "br" },
{ "Canada Central", "ca" },
{ "Canada East", "ca" },
{ "Central India", "in" },
{ "Central US", "us" },
{ "East Asia", "as" },
{ "East US", "us" },
{ "East US 2", "us" },
{ "France Central", "eu" },
{ "France South", "eu" },
{ "Germany North", "eu" },
{ "Germany West Central", "eu" },
{ "Japan East", "jp" },
{ "Japan West", "jp" },
{ "Jio India Central", "in" },
{ "Jio India West", "in" },
{ "Korea Central", "kr" },
{ "Korea South", "kr" },
{ "North Central US", "us" },
{ "North Europe", "eu" },
{ "Norway East", "no" },
{ "Norway West", "no" },
{ "South Africa North", "za" },
{ "South Africa West", "za" },
{ "South Central US", "us" },
{ "South India", "in" },
{ "Southeast Asia", "as" },
{ "Sweden Central", "eu" },
{ "Switzerland North", "ch" },
{ "Switzerland West", "ch" },
{ "UAE Central", "ae" },
{ "UAE North", "ae" },
{ "UK South", "uk" },
{ "UK West", "uk" },
{ "West Central US", "us" },
{ "West Europe", "eu" },
{ "West India", "in" },
{ "West US", "us" },
{ "West US 2", "us" },
{ "West US 3", "us" }
};
}
}
Expand All @@ -968,17 +971,17 @@ public class Config
{
public string TenantId => StagingSubscription.TenantId;

public string UniquePrefix { get; set; } = default!;
public string UniquePrefix { get; set; } = null!;

public GithubInfo? GithubInfo { get; private set; }

public Subscription StagingSubscription { get; set; } = default!;
public Subscription StagingSubscription { get; set; } = null!;

public Location StagingLocation { get; set; } = default!;
public Location StagingLocation { get; set; } = null!;

public Subscription ProductionSubscription { get; set; } = default!;
public Subscription ProductionSubscription { get; set; } = null!;

public Location ProductionLocation { get; set; } = default!;
public Location ProductionLocation { get; set; } = null!;

public Dictionary<string, string> GithubVariables { get; set; } = new();

Expand Down
5 changes: 3 additions & 2 deletions developer-cli/Commands/InstallCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Each command is one C# class that can be customized to automate your own workflo

public InstallCommand() : base(
"install",
$"This will register the alias {Configuration.AliasName} so it will be available everywhere."
$"This will register the alias {Configuration.AliasName} so it will be available everywhere"
)
{
Handler = CommandHandler.Create(Execute);
Expand All @@ -53,7 +53,8 @@ private void Execute()
var installedAliasPath = Configuration.GetConfigurationSetting().CliSourceCodeFolder!;
AnsiConsole.MarkupLine(Environment.ProcessPath!.StartsWith(installedAliasPath)
? $"[yellow]The CLI is already installed please run {Configuration.AliasName} to use it.[/]"
: $"[yellow]There is already a CLI with the alias '{Configuration.AliasName}' installed in {installedAliasPath}.[/]");
: $"[yellow]There is already a CLI with the alias '{Configuration.AliasName}' installed in {installedAliasPath}.[/]"
);

return;
}
Expand Down
16 changes: 8 additions & 8 deletions developer-cli/Commands/PullPlatformPlatformChangesCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ private static int Execute(bool verboseLogging, bool autoConfirm, bool resume, b

if (pullRequestCommits.Length == 0 && newCommits.Length == 0)
{
AnsiConsole.MarkupLine("[yellow]Everything look up to date.[/]");
AnsiConsole.MarkupLine("[yellow]Everything looks up to date.[/]");
Environment.Exit(0);
}

Expand Down Expand Up @@ -296,7 +296,7 @@ private static void BuildTestAndCleanupCode(bool runCodeCleanup)

void BuildSolution()
{
AnsiConsole.MarkupLine("[green]Building backend and frontend backend[/]");
AnsiConsole.MarkupLine("[green]Building backend and frontend backend.[/]");
while (true)
{
try
Expand Down Expand Up @@ -325,7 +325,7 @@ void RunTests()
{
try
{
AnsiConsole.MarkupLine("[green]Running tests[/]");
AnsiConsole.MarkupLine("[green]Running tests.[/]");
ProcessHelper.StartProcess("dotnet test", Configuration.ApplicationFolder, throwOnError: true);
break;
}
Expand All @@ -347,7 +347,7 @@ void CleanupBackendCode()
{
try
{
AnsiConsole.MarkupLine("[green]Running cleanup[/]");
AnsiConsole.MarkupLine("[green]Running cleanup.[/]");

var solutionFile = Directory.GetFiles(Configuration.ApplicationFolder, "*.sln", SearchOption.TopDirectoryOnly).Single();
ProcessHelper.StartProcess(
Expand All @@ -374,7 +374,7 @@ void CheckFrontendCode()
{
try
{
AnsiConsole.MarkupLine("[green]Validating frontend code[/]");
AnsiConsole.MarkupLine("[green]Validating frontend code.[/]");
ProcessHelper.StartProcess("npm run format", Configuration.ApplicationFolder, throwOnError: true);
ProcessHelper.StartProcess("npm run lint", Configuration.ApplicationFolder, throwOnError: true);
ProcessHelper.StartProcess("npm run check", Configuration.ApplicationFolder, throwOnError: true);
Expand Down Expand Up @@ -413,7 +413,7 @@ private static void ValidateGitStatus()

void CreateChangeCommit(string message)
{
AnsiConsole.MarkupLine("[green]Adding changes[/]");
AnsiConsole.MarkupLine("[green]Adding changes.[/]");
ProcessHelper.StartProcess("git add .", Configuration.SourceCodeFolder);
ProcessHelper.StartProcess($"git commit -m \"{message}\"", Configuration.SourceCodeFolder);
}
Expand Down Expand Up @@ -542,7 +542,7 @@ private static bool HasOriginRemote()

private static void AmendCommit()
{
AnsiConsole.MarkupLine("[green]Amending changes[/]");
AnsiConsole.MarkupLine("[green]Amending changes.[/]");
ProcessHelper.StartProcess("git add .", Configuration.SourceCodeFolder);
ProcessHelper.StartProcess("git commit --amend --no-edit", Configuration.SourceCodeFolder);
}
Expand Down Expand Up @@ -591,7 +591,7 @@ private static void EnsureBranchIsUpToDate()

if (localCommits.Contains(latestOriginCommit)) return;

AnsiConsole.MarkupLine($"[red]Branch is not up to date with '{DefaultRemote}/{activeBranchName}'[/]");
AnsiConsole.MarkupLine($"[red]Branch is not up to date with '{DefaultRemote}/{activeBranchName}'.[/]");
Environment.Exit(0);
}

Expand Down
2 changes: 1 addition & 1 deletion developer-cli/Commands/UninstallCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class UninstallCommand : Command
{
public UninstallCommand() : base(
"uninstall",
$"Will remove the {Configuration.AliasName} CLI alias."
$"Will remove the {Configuration.AliasName} CLI alias"
)
{
Handler = CommandHandler.Create(Execute);
Expand Down
Loading