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) enabled() bool {
if a.getFromEnvVars() {
return true
}
return a.getFromAzCli()
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) 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")
func (a *az) enabled() bool {
return a.getAzureProfile() || a.getAzureRmContext()
}
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,
var config AzureConfig
if err := json.Unmarshal([]byte(content), &config); err != nil {
return false
}
for _, subscription := range config.Subscriptions {
if subscription.IsDefault {
a.AzureSubscription = *subscription
return true
}
}
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
}
func (a *az) getFromAzCli() bool {
cmd := "az"
if !a.env.hasCommand(cmd) {
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
return true
}
err := json.Unmarshal([]byte(output), a)
return err == nil
}

View file

@ -1,7 +1,8 @@
package main
import (
"fmt"
"io/ioutil"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@ -9,141 +10,59 @@ import (
func TestAzSegment(t *testing.T) {
cases := []struct {
Case string
ExpectedEnabled bool
ExpectedString string
EnvEnvironmentName string
EnvUserName string
AccountName string
EnvSubscriptionID string
CLIExists bool
CLIEnvironmentname string
CLISubscriptionID string
CLIAccountName string
CLIUserName string
Template string
Case string
ExpectedEnabled bool
ExpectedString 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",
ExpectedEnabled: true,
ExpectedString: "foo$bar",
CLIExists: true,
CLIEnvironmentname: "foo",
CLISubscriptionID: "bar",
Template: "{{.EnvironmentName}}${{.ID}}",
Case: "Az CLI Profile",
ExpectedEnabled: true,
ExpectedString: "AzureCliCloud",
Template: "{{ .EnvironmentName }}",
HasCLI: true,
},
{
Case: "print only environment ame",
ExpectedEnabled: true,
ExpectedString: "foo",
CLIExists: true,
CLIEnvironmentname: "foo",
CLISubscriptionID: "bar",
Template: "{{.EnvironmentName}}",
Case: "Az Pwsh Profile",
ExpectedEnabled: true,
ExpectedString: "AzurePoshCloud",
Template: "{{ .EnvironmentName }}",
HasPowerShell: true,
},
{
Case: "print only id",
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",
Case: "Faulty template",
ExpectedEnabled: true,
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"
}
}`, tc.CLIEnvironmentname, tc.CLISubscriptionID, tc.CLIAccountName, tc.CLIUserName),
nil,
)
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)
}
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": []
}
]
}