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.
:::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
```json
@ -36,10 +31,6 @@ properties below - defaults to `{{.Name}}`
## Template Properties
:::caution
When using the PowerShell module, only `.EnvironmentName`, `.ID`, `.Name` and `.User.Name` are available.
:::
- `.EnvironmentName`: `string` - the account environment name
- `.HomeTenantID`: `string` - the home tenant id
- `.ID`: `string` - the account/subscription id

View file

@ -15,10 +15,6 @@ $env:POWERLINE_COMMAND = "oh-my-posh"
$env:CONDA_PROMPT_MODIFIER = $false
# 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
if ($null -eq $omp_value) {
$env:POSH_GIT_ENABLED = $false
@ -50,24 +46,6 @@ function global:Initialize-ModuleSupport {
$global:GitStatus = Get-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 = {

View file

@ -2,6 +2,7 @@ package main
import (
"encoding/json"
"path/filepath"
"strings"
)
@ -9,32 +10,75 @@ type az struct {
props properties
env environmentInfo
EnvironmentName string `json:"environmentName"`
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"`
AzureSubscription
}
const (
updateConsentNeeded = "Do you want to continue?"
updateMessage = "AZ CLI: Update needed!"
updateForeground = "#ffffff"
updateBackground = "#ff5349"
)
type AzureConfig struct {
Subscriptions []*AzureSubscription `json:"subscriptions"`
InstallationID string `json:"installationId"`
}
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 {
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 {
if a != nil && a.Name == updateMessage {
return updateMessage
}
segmentTemplate := a.props.getString(SegmentTemplate, "{{.Name}}")
segmentTemplate := a.props.getString(SegmentTemplate, "{{ .EnvironmentName }}")
template := &textTemplate{
Template: segmentTemplate,
Context: a,
@ -52,52 +96,53 @@ func (a *az) init(props properties, env environmentInfo) {
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 {
if a.getFromEnvVars() {
return true
}
return a.getFromAzCli()
return a.getAzureProfile() || a.getAzureRmContext()
}
func (a *az) getFromEnvVars() bool {
environmentName := a.env.getenv("AZ_ENVIRONMENT_NAME")
userName := a.env.getenv("AZ_USER_NAME")
id := a.env.getenv("AZ_SUBSCRIPTION_ID")
accountName := a.env.getenv("AZ_ACCOUNT_NAME")
if userName == "" && environmentName == "" {
func (a *az) getAzureProfile() bool {
var content string
if content = a.getFileContentWithoutBom(".azure/azureProfile.json"); len(content) == 0 {
return false
}
a.EnvironmentName = environmentName
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) {
var config AzureConfig
if err := json.Unmarshal([]byte(content), &config); err != nil {
return false
}
output, _ := a.env.runCommand(cmd, "account", "show")
if len(output) == 0 {
return false
}
if strings.Contains(output, updateConsentNeeded) {
a.props[ForegroundOverride] = updateForeground
a.props[BackgroundOverride] = updateBackground
a.Name = updateMessage
for _, subscription := range config.Subscriptions {
if subscription.IsDefault {
a.AzureSubscription = *subscription
return true
}
err := json.Unmarshal([]byte(output), a)
return err == nil
}
return false
}
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
import (
"fmt"
"io/ioutil"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@ -12,138 +13,56 @@ func TestAzSegment(t *testing.T) {
Case string
ExpectedEnabled bool
ExpectedString string
EnvEnvironmentName string
EnvUserName string
AccountName string
EnvSubscriptionID string
CLIExists bool
CLIEnvironmentname string
CLISubscriptionID string
CLIAccountName string
CLIUserName string
HasCLI bool
HasPowerShell bool
Template string
}{
{
Case: "display account name",
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",
Case: "no config files found",
ExpectedEnabled: false,
ExpectedString: "",
CLIExists: false,
},
{
Case: "cli contains data",
Case: "Az CLI Profile",
ExpectedEnabled: true,
ExpectedString: "foo$bar",
CLIExists: true,
CLIEnvironmentname: "foo",
CLISubscriptionID: "bar",
Template: "{{.EnvironmentName}}${{.ID}}",
ExpectedString: "AzureCliCloud",
Template: "{{ .EnvironmentName }}",
HasCLI: true,
},
{
Case: "print only environment ame",
Case: "Az Pwsh Profile",
ExpectedEnabled: true,
ExpectedString: "foo",
CLIExists: true,
CLIEnvironmentname: "foo",
CLISubscriptionID: "bar",
Template: "{{.EnvironmentName}}",
ExpectedString: "AzurePoshCloud",
Template: "{{ .EnvironmentName }}",
HasPowerShell: true,
},
{
Case: "print only id",
Case: "Faulty template",
ExpectedEnabled: true,
ExpectedString: "bar",
CLIExists: true,
CLIEnvironmentname: "foo",
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",
ExpectedString: incorrectTemplate,
Template: "{{ .Burp }}",
HasPowerShell: true,
},
}
for _, tc := range cases {
env := new(MockedEnvironment)
env.On("getenv", "AZ_ENVIRONMENT_NAME").Return(tc.EnvEnvironmentName)
env.On("getenv", "AZ_USER_NAME").Return(tc.EnvUserName)
env.On("getenv", "AZ_SUBSCRIPTION_ID").Return(tc.EnvSubscriptionID)
env.On("getenv", "AZ_ACCOUNT_NAME").Return(tc.AccountName)
env.On("hasCommand", "az").Return(tc.CLIExists)
env.On("runCommand", "az", []string{"account", "show"}).Return(
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"
home := "/Users/posh"
env.On("homeDir", nil).Return(home)
var azureProfile, azureRmContext string
if tc.HasCLI {
content, _ := ioutil.ReadFile("./test/azureProfile.json")
azureProfile = string(content)
}
}`, tc.CLIEnvironmentname, tc.CLISubscriptionID, tc.CLIAccountName, tc.CLIUserName),
nil,
)
if tc.HasPowerShell {
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{}{
SegmentTemplate: tc.Template,
}
az := &az{
env: env,
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": []
}
]
}