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

Updated steps for Visual Studio experience #368

Merged
merged 8 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ FROM mcr.microsoft.com/vscode/devcontainers/dotnet:${VARIANT}

# Install Az module
RUN pwsh -Command "Install-Module -Name Az -Force -AllowClobber -Scope AllUsers"
RUN pwsh -Command "Install-Module -Name SqlServer -Force -AllowClobber -Scope AllUsers"
Binary file removed assets/images/choice-of-abstraction.png
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed assets/images/jumphost-path-setup.png
Binary file not shown.
142 changes: 97 additions & 45 deletions developer-experience.md
Original file line number Diff line number Diff line change
@@ -1,65 +1,117 @@
# Developer Experience

The dev team uses Visual Studio and they integrate directly with Azure resources when building the code. The team chooses this workflow to so they can integration test with Azure before it reaches the QA team.
The dev team uses Visual Studio and they integrate directly with Azure resources when building the code. The team chooses this workflow to so they can integration test with Azure before their code reaches the QA team.

> **WARNING**
> **NOTE**
>
> This developer experience is only supported for dev deployments. Production deployments
> use are network isolated and do not allow devs to connect from their workstation.
> This content provides a jump host VM to help you access network isolated Azure resources.
> This developer experience is only supported for development deployments. Production deployments
> use network isolation and do not allow devs to connect from their workstation.

To connect to the Azure resources the dev team uses connection strings from Key Vault. Devs use the following script to retrieve data and store it as [User Secrets](https://learn.microsoft.com/aspnet/core/security/app-secrets?view=aspnetcore-6.0&tabs=windows) on their workstation.
Most configurations in the project are stored in Azure App Configuration with secrets saved into Azure Key Vault. To connect to these resources from a developer workstation you need to complete the following steps.

Using the `secrets.json` file helps the team keep their credentials secure. The file is stored outside of the source control directory so the data is never accidentally checked-in. And the devs don't share credentials over email or other ways that could compromise their security.
1. Add your identity to the Azure SQL resource
1. Set up front-end web app configuration
1. Set up back-end web app configuration

Managing secrets from Key Vault ensures that only authorized team members can access the data and also centralizes the administration of secrets.
To support this workflow the following steps will store data in [User Secrets](https://learn.microsoft.com/aspnet/core/security/app-secrets?view=aspnetcore-6.0&tabs=windows) because the code is configured so that these values override configurations and secrets saved in Azure.

New team members should setup their environment by following these steps.
> Note that `secrets.json` file is stored relative to the tooling that supports them. Use the Windows Terminal to execute the following commands instead of the Dev Container if you want to use Visual Studio to launch the project. Read [How the Secret Manager tool works](https://learn.microsoft.com/aspnet/core/security/app-secrets?view=aspnetcore-8.0&tabs=linux#how-the-secret-manager-tool-works) to learn more.

1. Open the Visual Studio solution `./src/Relecloud.sln`
1. Setup the **Relecloud.Web.CallCenter** project User Secrets
1. Right-click on the **Relecloud.Web.CallCenter** project
1. From the context menu choose **Manage User Secrets**
1. Validate that the `secrets.json` file is configured with `App:AppConfig:Uri` or set it based on the AZD environment variable.
## Authenticate with Azure

1. Setup the **Relecloud.Web.CallCenter.Api** project User Secrets
1. Right-click on the **Relecloud.Web.CallCenter.Api** project
1. From the context menu choose **Manage User Secrets**
1. Validate that the `secrets.json` file is configured with `App:AppConfig:Uri` or set it based on the AZD environment variable.
1. If you are not using PowerShell 7+, run the following command (you can use [$PSVersionTable.PSVersion](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_powershell_editions) to check your version):

1. Right-click the **Relecloud** solution and pick **Set Startup Projects...**
1. Choose **Multiple startup projects**
1. Change the dropdowns for *Relecloud.Web.CallCenter* and *Relecloud.Web.CallCenter.Api* to the action of **Start**.
1. Click **Ok** to close the popup
```sh
pwsh
```

1. Connect to Azure

```pwsh
Import-Module Az.Resources
```

1. When connecting to Azure SQL database you'll connect with your Microsoft Entra ID account.
Run the following command to give your Microsoft Entra ID account permission to access the database.
```pwsh
Connect-AzAccount
```

1. Set the subscription to the one you want to use (you can use [Get-AzSubscription](https://learn.microsoft.com/powershell/module/az.accounts/get-azsubscription?view=azps-11.3.0) to list available subscriptions):

<table>
<tr>
<td>PowerShell</td>
<td>
```pwsh
$AZURE_SUBSCRIPTION_ID="<your-subscription-id>"
```

```ps1
pwsh -c "Set-ExecutionPolicy Bypass Process; .\infra\localDevScripts\makeSqlUserAccount.ps1 -g '$myEnvironmentName-rg'"
```pwsh
Set-AzContext -SubscriptionId $AZURE_SUBSCRIPTION_ID
```

</td>
</tr>
<tr>
<td>Bash</td>
<td>

```bash
./infra/localDevScripts/makeSqlUserAccount.sh -g "$myEnvironmentName-rg"
## 1. Add your identity to the Azure SQL resource

1. Run the following script to automate the process in docs [Configure and manage Microsoft Entra authentication with Azure SQL](https://learn.microsoft.com/en-us/azure/azure-sql/database/authentication-aad-configure?view=azuresql&tabs=azure-powershell)

```pwsh
./infra/scripts/devexperience/call-make-sql-account.ps1
```

## 2. Set up front-end web app configuration

1. Get the Azure App Configuration URI
```pwsh
$appConfigurationUri = ((azd env get-values --output json | ConvertFrom-Json).APP_CONFIG_SERVICE_URI)
```

1. Switch to the front-end web app directory
```pwsh
cd src/Relecloud.Web.CallCenter
```
1. Clear any existing user secrets
```pwsh
dotnet user-secrets clear
```
1. Set the Relecloud API base URI
```pwsh
dotnet user-secrets set "App:RelecloudApi:BaseUri" "https://localhost:7242"
```

1. Set the Azure App Configuration URI
```pwsh
dotnet user-secrets set "App:AppConfig:Uri" $appConfigurationUri
```

1. Switch back to the root of the repository
```pwsh
cd ../..
```

## 3. Set up back-end web app configuration

```pwsh
cd src/Relecloud.Web.CallCenter.Api
```

```pwsh
dotnet user-secrets clear
```

```pwsh
dotnet user-secrets set "App:AppConfig:Uri" $appConfigurationUri
```

## 4. Launch the project with Visual Studio

1. Open the project in Visual Studio
1. Configure the solution to start both the front-end and back-end web apps
1. Right-click the **Relecloud** solution and pick **Set Startup Projects...**
1. Choose **Multiple startup projects**
1. Change the dropdowns for *Relecloud.Web.CallCenter* and *Relecloud.Web.CallCenter.Api* to the action of **Start**.
1. Click **Ok** to close the popup

![screenshot of Visual Studio solution startup configuration](assets/images/configure-multiple-startup-projects.png)

</td>
</tr>
</table>
1. Run the project (F5)
1. Open the browser and navigate to `https://localhost:7227/`

1. Press F5 to start debugging the website
![screenshot of web app home page](assets/images/WebAppHomePage.png)

> These steps grant access to SQL server in the primary resource group.
> You can use the same commands if you want to test with the secondary resource
> group by changing the ResourceGroup parameter "-g" to "$myEnvironmentName-secondary-rg"
## Next steps
You can learn more about the web app by reading the [Pattern Simulations](demo.md) documentation.
4 changes: 2 additions & 2 deletions infra/modules/application-post-config.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,8 @@ module writeSqlAdminInfoToKeyVault '../core/security/key-vault-secrets.bicep' =
params: {
name: existingKeyVault.name
secrets: [
{ key: 'Relecloud--SqlAdministratorUsername', value: administratorUsername }
{ key: 'Relecloud--SqlAdministratorPassword', value: databasePassword }
{ key: 'Application--SqlAdministratorUsername', value: administratorUsername }
{ key: 'Application--SqlAdministratorPassword', value: databasePassword }
]
}
}
Expand Down
48 changes: 48 additions & 0 deletions infra/scripts/devexperience/call-make-sql-account.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<#
.SYNOPSIS
Calls the make-sql-account.ps1 script to create a SQL account for a given resource group, SQL server, and database.

.DESCRIPTION
This script retrieves the necessary parameters from the AZD environment variables and Key Vault, and then calls the make-sql-account.ps1 script with the appropriate arguments.

.PARAMETER resourceGroupName
The name of the Azure resource group where the SQL server and database are located.

.PARAMETER sqlServerName
The name of the SQL server.

.PARAMETER sqlDatabaseName
The name of the SQL database.

.PARAMETER keyVaultName
The name of the Azure Key Vault where the SQL administrator credentials are stored.

.EXAMPLE
./call-make-sql-account.ps1

This example demonstrates how to call the script to create a SQL account using the default environment variables and Key Vault.

#>

$resourceGroupName = ((azd env get-values --output json | ConvertFrom-Json).AZURE_RESOURCE_GROUP)
$sqlServerName = ((azd env get-values --output json | ConvertFrom-Json).SQL_SERVER_NAME)
$sqlDatabaseName = ((azd env get-values --output json | ConvertFrom-Json).SQL_DATABASE_NAME)
$keyVaultName = ((azd env get-values --output json | ConvertFrom-Json).AZURE_OPS_VAULT_NAME)

$sqlAdmin = (Get-AzKeyVaultSecret -VaultName $keyVaultName -Name "Application--SqlAdministratorUsername" -AsPlainText)
$secureSqlPassword = ConvertTo-SecureString -String (Get-AzKeyVaultSecret -VaultName $keyVaultName -Name "Application--SqlAdministratorPassword" -AsPlainText) -AsPlainText -Force

$accountId = (Get-AzContext).Account.ExtendedProperties["HomeAccountId"].Split(".")[0]
$accountAlias = (Get-AzContext).Account.Id

$Cred = New-Object System.Management.Automation.PSCredential ($sqlAdmin, $secureSqlPassword)

Write-Host "Calling make-sql-account.ps1 for group:'$resourceGroupName'..."

./infra/scripts/devexperience/make-sql-account.ps1 `
-ResourceGroup $resourceGroupName `
-SqlServerName $sqlServerName `
-SqlDatabaseName $sqlDatabaseName `
-AccountAlias $accountAlias `
-AccountId $accountId `
-Credential $Cred
132 changes: 132 additions & 0 deletions infra/scripts/devexperience/make-sql-account.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<#
.SYNOPSIS
This script creates a SQL account for a specified Entra ID account so that the user can connect to Azure SQL.

.PARAMETER ResourceGroup
The name of the resource group where the SQL Server is located.

.PARAMETER SqlServerName
The name of the SQL Server.

.PARAMETER SqlDatabaseName
The name of the SQL database.

.PARAMETER AccountAlias
The account alias of the Entra ID account to be added to Azure SQL.

.PARAMETER AccountId
The ID of the Entra ID account to be added to Azure SQL.

.EXAMPLE
./make-sql-account.ps1 -ResourceGroup "myResourceGroup" -SqlServerName "mySqlServer" -SqlDatabaseName "mySqlDatabase" -AccountId "mySqlAccount" -Credential $Creds
Creates a SQL account with the specified parameters.

#>

Param(
[Parameter(Mandatory=$true)]
[string] $ResourceGroup,

[Parameter(Mandatory=$true)]
[string] $SqlServerName,

[Parameter(Mandatory=$true)]
[string] $SqlDatabaseName,

[Parameter(Mandatory=$true)]
[string] $AccountAlias,

[Parameter(Mandatory=$true)]
[string] $AccountId,

[Parameter(Mandatory=$true)]
[System.Management.Automation.PSCredential]$Credential
)

<#
.SYNOPSIS
Tests to ensure that the Powershell module we need is installed and imported before use.
.PARAMETER ModuleName
The name of the module to test for.
#>
function Test-ModuleImported {
param(
[Parameter(Mandatory=$true)]
[string] $ModuleName
)

if ((Get-Module -ListAvailable -Name $ModuleName) -and (Get-Module -Name $ModuleName -ErrorAction SilentlyContinue)) {
Write-Verbose "The '$($ModuleName)' module is installed and imported."
}
else {
$SavedVerbosePreference = $global:VerbosePreference
try {
Write-Verbose "Importing '$($ModuleName)' module"
$global:VerbosePreference = 'SilentlyContinue'
Import-Module -Name $ModuleName -ErrorAction Stop
$global:VerbosePreference = $SavedVerbosePreference
Write-Verbose "The '$($ModuleName)' module is imported successfully."
}
catch {
Write-Error "Failed to import the '$($ModuleName)' module. Please install the '$($ModuleName)' module before running this script."
exit 12
}
finally {
$global:VerbosePreference = $SavedVerbosePreference
}
}
}

<#
.SYNOPSIS
Checks to ensure that the user is authenticated with Azure before running the script.
#>
function Test-AzureConnected {
if (Get-AzContext -ErrorAction SilentlyContinue) {
Write-Verbose "The user is authenticated with Azure."
}
else {
Write-Error "You are not authenticated with Azure. Please run 'Connect-AzAccount' to authenticate before running this script."
exit 10
}
}

Test-ModuleImported -ModuleName Az.Resources
Test-ModuleImported -ModuleName SqlServer
Test-AzureConnected

# Prompt formatting features

$defaultColor = if ($PSVersionTable.PSVersion.Major -ge 6) { "`e[0m" } else { "" }
$successColor = if ($PSVersionTable.PSVersion.Major -ge 6) { "`e[32m" } else { "" }

[guid]$guid = [System.Guid]::Parse($accountId)

foreach ($byte in $guid.ToByteArray()) {
$byteGuid += [System.String]::Format("{0:X2}", $byte)
}
$Sid = "0x" + $byteGuid

$fullyQualifiedDomainName = (Get-AzSqlServer -ResourceGroupName $ResourceGroup -ServerName $SqlServerName).FullyQualifiedDomainName


# Prepare SQL cmd to CREATE USER
$createUserSQL = "IF NOT EXISTS (SELECT * FROM sys.database_principals WHERE name = N'$AccountAlias') create user [$AccountAlias] with sid = $Sid, type = E;"

# Connect as SQL Admin acct and execute SQL cmd
Invoke-Sqlcmd -ServerInstance $fullyQualifiedDomainName -database $sqlDatabaseName -Credential $Credential -Query $createUserSQL
Write-Host "`tCreated user"

Invoke-Sqlcmd -ServerInstance $fullyQualifiedDomainName -database 'master' -Credential $Credential -Query $createUserSQL
Write-Host "`tCreated for root db"

# Prepare SQL cmd to grant db_owner role
$grantDbOwner = "IF NOT EXISTS (SELECT * FROM sys.database_principals p JOIN sys.database_role_members db_owner_role ON db_owner_role.member_principal_id = p.principal_id JOIN sys.database_principals role_names ON role_names.principal_id = db_owner_role.role_principal_id AND role_names.[name] = 'db_owner' WHERE p.[name]=N'$AccountAlias') ALTER ROLE db_owner ADD MEMBER [$AccountAlias];"

# Connect as SQL Admin acct and execute SQL cmd
Invoke-Sqlcmd -ServerInstance $fullyQualifiedDomainName -database $sqlDatabaseName -Credential $Credential -Query $grantDbOwner

Write-Host "`tGranted db_owner"

Write-Host "`nFinished $($successColor)successfully$($defaultColor)."
Write-Host "An account for the current user was created in Azure SQL"
Loading
Loading