feat: parse azure config files

This commit is contained in:
Jan De Dobbeleer 2021-12-28 13:41:07 +01:00 committed by Jan De Dobbeleer
parent 4ecf674e62
commit ad5c77a61b
6 changed files with 238 additions and 208 deletions

View file

@ -8,11 +8,6 @@ sidebar_label: Azure
Display the currently active Azure subscription information. Display the currently active Azure subscription information.
:::caution
PowerShell offers support for the `Az.Accounts` module, but it is disabled by default.
To enable this, set `$env:AZ_ENABLED = $true` in your `$PROFILE`.
:::
## Sample Configuration ## Sample Configuration
```json ```json
@ -36,10 +31,6 @@ properties below - defaults to `{{.Name}}`
## Template Properties ## Template Properties
:::caution
When using the PowerShell module, only `.EnvironmentName`, `.ID`, `.Name` and `.User.Name` are available.
:::
- `.EnvironmentName`: `string` - the account environment name - `.EnvironmentName`: `string` - the account environment name
- `.HomeTenantID`: `string` - the home tenant id - `.HomeTenantID`: `string` - the home tenant id
- `.ID`: `string` - the account/subscription id - `.ID`: `string` - the account/subscription id

View file

@ -15,10 +15,6 @@ $env:POWERLINE_COMMAND = "oh-my-posh"
$env:CONDA_PROMPT_MODIFIER = $false $env:CONDA_PROMPT_MODIFIER = $false
# specific module support (disabled by default) # specific module support (disabled by default)
$omp_value = $env:AZ_ENABLED
if ($null -eq $omp_value) {
$env:AZ_ENABLED = $false
}
$omp_value = $env:POSH_GIT_ENABLED $omp_value = $env:POSH_GIT_ENABLED
if ($null -eq $omp_value) { if ($null -eq $omp_value) {
$env:POSH_GIT_ENABLED = $false $env:POSH_GIT_ENABLED = $false
@ -50,24 +46,6 @@ function global:Initialize-ModuleSupport {
$global:GitStatus = Get-GitStatus $global:GitStatus = Get-GitStatus
$env:POSH_GIT_STATUS = Write-GitStatus -Status $global:GitStatus $env:POSH_GIT_STATUS = Write-GitStatus -Status $global:GitStatus
} }
$env:AZ_ENVIRONMENT_NAME = $null
$env:AZ_USER_NAME = $null
$env:AZ_SUBSCRIPTION_ID = $null
$env:AZ_ACCOUNT_NAME = $null
if ($env:AZ_ENABLED -eq $true) {
try {
$context = Get-AzContext
if ($null -ne $context) {
$env:AZ_ENVIRONMENT_NAME = $context.Environment.Name
$env:AZ_USER_NAME = $context.Account.Id
$env:AZ_SUBSCRIPTION_ID = $context.Subscription.Id
$env:AZ_ACCOUNT_NAME = $context.Subscription.Name
}
}
catch {}
}
} }
[ScriptBlock]$Prompt = { [ScriptBlock]$Prompt = {

View file

@ -2,6 +2,7 @@ package main
import ( import (
"encoding/json" "encoding/json"
"path/filepath"
"strings" "strings"
) )
@ -9,32 +10,75 @@ type az struct {
props properties props properties
env environmentInfo env environmentInfo
EnvironmentName string `json:"environmentName"` AzureSubscription
HomeTenantID string `json:"homeTenantId"`
ID string `json:"id"`
IsDefault bool `json:"isDefault"`
Name string `json:"name"`
State string `json:"state"`
TenantID string `json:"tenantId"`
User *AzureUser `json:"user"`
} }
const ( type AzureConfig struct {
updateConsentNeeded = "Do you want to continue?" Subscriptions []*AzureSubscription `json:"subscriptions"`
updateMessage = "AZ CLI: Update needed!" InstallationID string `json:"installationId"`
updateForeground = "#ffffff" }
updateBackground = "#ff5349"
) type AzureSubscription struct {
ID string `json:"id"`
Name string `json:"name"`
State string `json:"state"`
User *AzureUser `json:"user"`
IsDefault bool `json:"isDefault"`
TenantID string `json:"tenantId"`
EnvironmentName string `json:"environmentName"`
HomeTenantID string `json:"homeTenantId"`
ManagedByTenants []interface{} `json:"managedByTenants"`
}
type AzureUser struct { type AzureUser struct {
Name string `json:"name"` Name string `json:"name"`
} }
type AzurePowerShellConfig struct {
DefaultContextKey string `json:"DefaultContextKey"`
Contexts map[string]*AzurePowerShellSubscription `json:"Contexts"`
}
type AzurePowerShellSubscription struct {
Account struct {
ID string `json:"Id"`
Credential interface{} `json:"Credential"`
Type string `json:"Type"`
TenantMap struct {
} `json:"TenantMap"`
ExtendedProperties struct {
Subscriptions string `json:"Subscriptions"`
Tenants string `json:"Tenants"`
HomeAccountID string `json:"HomeAccountId"`
} `json:"ExtendedProperties"`
} `json:"Account"`
Tenant struct {
ID string `json:"Id"`
Directory interface{} `json:"Directory"`
IsHome bool `json:"IsHome"`
ExtendedProperties struct {
} `json:"ExtendedProperties"`
} `json:"Tenant"`
Subscription struct {
ID string `json:"Id"`
Name string `json:"Name"`
State string `json:"State"`
ExtendedProperties struct {
HomeTenant string `json:"HomeTenant"`
AuthorizationSource string `json:"AuthorizationSource"`
SubscriptionPolices string `json:"SubscriptionPolices"`
Tenants string `json:"Tenants"`
Account string `json:"Account"`
Environment string `json:"Environment"`
} `json:"ExtendedProperties"`
} `json:"Subscription"`
Environment struct {
Name string `json:"Name"`
} `json:"Environment"`
}
func (a *az) string() string { func (a *az) string() string {
if a != nil && a.Name == updateMessage { segmentTemplate := a.props.getString(SegmentTemplate, "{{ .EnvironmentName }}")
return updateMessage
}
segmentTemplate := a.props.getString(SegmentTemplate, "{{.Name}}")
template := &textTemplate{ template := &textTemplate{
Template: segmentTemplate, Template: segmentTemplate,
Context: a, Context: a,
@ -52,52 +96,53 @@ func (a *az) init(props properties, env environmentInfo) {
a.env = env a.env = env
} }
func (a *az) getFileContentWithoutBom(file string) string {
const ByteOrderMark = "\ufeff"
file = filepath.Join(a.env.homeDir(), file)
config := a.env.getFileContent(file)
return strings.TrimLeft(config, ByteOrderMark)
}
func (a *az) enabled() bool { func (a *az) enabled() bool {
if a.getFromEnvVars() { return a.getAzureProfile() || a.getAzureRmContext()
return true
}
return a.getFromAzCli()
} }
func (a *az) getFromEnvVars() bool { func (a *az) getAzureProfile() bool {
environmentName := a.env.getenv("AZ_ENVIRONMENT_NAME") var content string
userName := a.env.getenv("AZ_USER_NAME") if content = a.getFileContentWithoutBom(".azure/azureProfile.json"); len(content) == 0 {
id := a.env.getenv("AZ_SUBSCRIPTION_ID")
accountName := a.env.getenv("AZ_ACCOUNT_NAME")
if userName == "" && environmentName == "" {
return false return false
} }
var config AzureConfig
a.EnvironmentName = environmentName if err := json.Unmarshal([]byte(content), &config); err != nil {
a.Name = accountName
a.ID = id
a.User = &AzureUser{
Name: userName,
}
return true
}
func (a *az) getFromAzCli() bool {
cmd := "az"
if !a.env.hasCommand(cmd) {
return false return false
} }
for _, subscription := range config.Subscriptions {
output, _ := a.env.runCommand(cmd, "account", "show") if subscription.IsDefault {
if len(output) == 0 { a.AzureSubscription = *subscription
return false
}
if strings.Contains(output, updateConsentNeeded) {
a.props[ForegroundOverride] = updateForeground
a.props[BackgroundOverride] = updateBackground
a.Name = updateMessage
return true return true
} }
}
err := json.Unmarshal([]byte(output), a) return false
return err == nil }
func (a *az) getAzureRmContext() bool {
var content string
if content = a.getFileContentWithoutBom(".azure/AzureRmContext.json"); len(content) == 0 {
return false
}
var config AzurePowerShellConfig
if err := json.Unmarshal([]byte(content), &config); err != nil {
return false
}
defaultContext := config.Contexts[config.DefaultContextKey]
if defaultContext == nil {
return false
}
a.IsDefault = true
a.EnvironmentName = defaultContext.Environment.Name
a.TenantID = defaultContext.Tenant.ID
a.ID = defaultContext.Subscription.ID
a.Name = defaultContext.Subscription.Name
a.State = defaultContext.Subscription.State
return true
} }

View file

@ -1,7 +1,8 @@
package main package main
import ( import (
"fmt" "io/ioutil"
"path/filepath"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -12,138 +13,56 @@ func TestAzSegment(t *testing.T) {
Case string Case string
ExpectedEnabled bool ExpectedEnabled bool
ExpectedString string ExpectedString string
EnvEnvironmentName string HasCLI bool
EnvUserName string HasPowerShell bool
AccountName string
EnvSubscriptionID string
CLIExists bool
CLIEnvironmentname string
CLISubscriptionID string
CLIAccountName string
CLIUserName string
Template string Template string
}{ }{
{ {
Case: "display account name", Case: "no config files found",
ExpectedEnabled: true,
ExpectedString: "foobar",
CLIExists: true,
CLIEnvironmentname: "foo",
CLISubscriptionID: "bar",
CLIAccountName: "foobar",
Template: "{{.Name}}",
},
{
Case: "envvars present",
ExpectedEnabled: true,
ExpectedString: "foo$bar",
EnvEnvironmentName: "foo",
EnvUserName: "bar",
CLIExists: false,
Template: "{{.EnvironmentName}}${{.User.Name}}",
},
{
Case: "envvar environment name present",
ExpectedEnabled: true,
ExpectedString: "foo",
EnvEnvironmentName: "foo",
CLIExists: false,
Template: "{{.EnvironmentName}}",
},
{
Case: "envvar user name present",
ExpectedEnabled: true,
ExpectedString: "bar",
EnvUserName: "bar",
CLIExists: false,
Template: "{{.User.Name}}",
},
{
Case: "envvar subscription id",
ExpectedEnabled: true,
ExpectedString: "foobar",
EnvSubscriptionID: "foobar",
EnvUserName: "bar",
CLIExists: false,
Template: "{{.ID}}",
},
{
Case: "cli not found",
ExpectedEnabled: false, ExpectedEnabled: false,
ExpectedString: "",
CLIExists: false,
}, },
{ {
Case: "cli contains data", Case: "Az CLI Profile",
ExpectedEnabled: true, ExpectedEnabled: true,
ExpectedString: "foo$bar", ExpectedString: "AzureCliCloud",
CLIExists: true, Template: "{{ .EnvironmentName }}",
CLIEnvironmentname: "foo", HasCLI: true,
CLISubscriptionID: "bar",
Template: "{{.EnvironmentName}}${{.ID}}",
}, },
{ {
Case: "print only environment ame", Case: "Az Pwsh Profile",
ExpectedEnabled: true, ExpectedEnabled: true,
ExpectedString: "foo", ExpectedString: "AzurePoshCloud",
CLIExists: true, Template: "{{ .EnvironmentName }}",
CLIEnvironmentname: "foo", HasPowerShell: true,
CLISubscriptionID: "bar",
Template: "{{.EnvironmentName}}",
}, },
{ {
Case: "print only id", Case: "Faulty template",
ExpectedEnabled: true, ExpectedEnabled: true,
ExpectedString: "bar", ExpectedString: incorrectTemplate,
CLIExists: true, Template: "{{ .Burp }}",
CLIEnvironmentname: "foo", HasPowerShell: true,
CLISubscriptionID: "bar",
Template: "{{.ID}}",
},
{
Case: "print none",
ExpectedEnabled: true,
CLIExists: true,
CLIEnvironmentname: "foo",
CLISubscriptionID: "bar",
},
{
Case: "update needed",
ExpectedEnabled: true,
ExpectedString: updateMessage,
CLIExists: true,
CLIEnvironmentname: "Do you want to continue? (Y/n): Visual Studio Enterprise",
}, },
} }
for _, tc := range cases { for _, tc := range cases {
env := new(MockedEnvironment) env := new(MockedEnvironment)
env.On("getenv", "AZ_ENVIRONMENT_NAME").Return(tc.EnvEnvironmentName) home := "/Users/posh"
env.On("getenv", "AZ_USER_NAME").Return(tc.EnvUserName) env.On("homeDir", nil).Return(home)
env.On("getenv", "AZ_SUBSCRIPTION_ID").Return(tc.EnvSubscriptionID) var azureProfile, azureRmContext string
env.On("getenv", "AZ_ACCOUNT_NAME").Return(tc.AccountName) if tc.HasCLI {
env.On("hasCommand", "az").Return(tc.CLIExists) content, _ := ioutil.ReadFile("./test/azureProfile.json")
env.On("runCommand", "az", []string{"account", "show"}).Return( azureProfile = string(content)
fmt.Sprintf(`{
"environmentName": "%s",
"homeTenantId": "8d934305-ac9f-46fe-b0e7-50fd32ad2acf",
"id": "%s",
"isDefault": true,
"managedByTenants": [],
"name": "%s",
"state": "Enabled",
"tenantId": "8d934305-ac9f-46fe-b0e7-50fd32ad2acf",
"user": {
"name": "%s",
"type": "user"
} }
}`, tc.CLIEnvironmentname, tc.CLISubscriptionID, tc.CLIAccountName, tc.CLIUserName), if tc.HasPowerShell {
nil, content, _ := ioutil.ReadFile("./test/AzureRmContext.json")
) azureRmContext = string(content)
}
env.On("getFileContent", filepath.Join(home, ".azure/azureProfile.json")).Return(azureProfile)
env.On("getFileContent", filepath.Join(home, ".azure/AzureRmContext.json")).Return(azureRmContext)
var props properties = map[Property]interface{}{ var props properties = map[Property]interface{}{
SegmentTemplate: tc.Template, SegmentTemplate: tc.Template,
} }
az := &az{ az := &az{
env: env, env: env,
props: props, props: props,

View file

@ -0,0 +1,81 @@
{
"DefaultContextKey": "My Azure Subscription (subscription_id) - tenant_id - user_name",
"EnvironmentTable": {},
"Contexts": {
"My Azure Subscription (subscription_id) - tenant_id - user_name": {
"Account": {
"Id": "232-232-232-232",
"Type": "User",
"TenantMap": {},
"ExtendedProperties": {
"Subscriptions": "subscription_ids",
"Tenants": "tenants_ids",
"HomeAccountId": "tenants_ids"
}
},
"Tenant": {
"Id": "4777-4777-4777-4777",
"Directory": null,
"IsHome": true,
"ExtendedProperties": {}
},
"Subscription": {
"Id": "97hkk-97hkk-97hkk-97hkk",
"Name": "AzurePosh",
"State": "Poshfull",
"ExtendedProperties": {
"HomeTenant": "688ab8f8-d8d8-4c8b-b8b8-b8b8b8b8b8b8",
"AuthorizationSource": "Legacy",
"SubscriptionPolices": "{\"locationPlacementId\":\"Public_2014-09-01\",\"quotaId\":\"MSDN_2014-09-01\",\"spendingLimit\":\"On\"}",
"Tenants": "4777-4777-4777-4777",
"Account": "Bill",
"Environment": "AzurePoshCloud"
}
},
"Environment": {
"Name": "AzurePoshCloud",
"Type": "Built-in",
"OnPremise": false,
"ServiceManagementUrl": "https://management.core.windows.net/",
"ResourceManagerUrl": "https://management.azure.com/",
"ManagementPortalUrl": "https://portal.azure.com/",
"PublishSettingsFileUrl": "https://go.microsoft.com/fwlink/?LinkID=301775",
"ActiveDirectoryAuthority": "https://login.microsoftonline.com/",
"GalleryUrl": "https://gallery.azure.com/",
"GraphUrl": "https://graph.windows.net/",
"ActiveDirectoryServiceEndpointResourceId": "https://management.core.windows.net/",
"StorageEndpointSuffix": "core.windows.net",
"SqlDatabaseDnsSuffix": ".database.windows.net",
"TrafficManagerDnsSuffix": "trafficmanager.net",
"AzureKeyVaultDnsSuffix": "vault.azure.net",
"AzureKeyVaultServiceEndpointResourceId": "https://vault.azure.net",
"GraphEndpointResourceId": "https://graph.windows.net/",
"DataLakeEndpointResourceId": "https://datalake.azure.net/",
"BatchEndpointResourceId": "https://batch.core.windows.net/",
"AzureDataLakeAnalyticsCatalogAndJobEndpointSuffix": "azuredatalakeanalytics.net",
"AzureDataLakeStoreFileSystemEndpointSuffix": "azuredatalakestore.net",
"AdTenant": "Common",
"ContainerRegistryEndpointSuffix": "azurecr.io",
"VersionProfiles": [],
"ExtendedProperties": {
"OperationalInsightsEndpoint": "https://api.loganalytics.io/v1",
"OperationalInsightsEndpointResourceId": "https://api.loganalytics.io",
"AzureAnalysisServicesEndpointSuffix": "asazure.windows.net",
"AnalysisServicesEndpointResourceId": "https://region.asazure.windows.net",
"AzureAttestationServiceEndpointSuffix": "attest.azure.net",
"AzureAttestationServiceEndpointResourceId": "https://attest.azure.net",
"AzureSynapseAnalyticsEndpointSuffix": "dev.azuresynapse.net",
"AzureSynapseAnalyticsEndpointResourceId": "https://dev.azuresynapse.net",
"ManagedHsmServiceEndpointResourceId": "https://managedhsm.azure.net",
"ManagedHsmServiceEndpointSuffix": "managedhsm.azure.net",
"MicrosoftGraphEndpointResourceId": "https://graph.microsoft.com/",
"MicrosoftGraphUrl": "https://graph.microsoft.com"
}
},
"VersionProfile": null,
"TokenCache": { "CacheData": null },
"ExtendedProperties": {}
}
},
"ExtendedProperties": {}
}

View file

@ -0,0 +1,16 @@
{
"installationId": "8d2919ac-a393-11eb-9100-acde48001122",
"subscriptions": [
{
"id": "g51ds77-a393-11eb-9100-acde48001122",
"name": "AzureCli",
"state": "Powerfull",
"user": { "name": "Melinda", "type": "user" },
"isDefault": true,
"tenantId": "6js98d-a393-11eb-9100-acde48001122",
"environmentName": "AzureCliCloud",
"homeTenantId": "8d934305-ac9f-46fe-b0e7-50fd32ad2acf",
"managedByTenants": []
}
]
}