diff --git a/api/v1alpha1/azure/azure_config.go b/api/v1alpha1/azure/azure_config.go new file mode 100644 index 000000000..aad0c009f --- /dev/null +++ b/api/v1alpha1/azure/azure_config.go @@ -0,0 +1,48 @@ +package azure + +// AzureConfig represents the Azure configuration +type AzureConfig struct { + // Enabled indicates whether Azure integration is enabled. + Enabled *bool `yaml:"enabled,omitempty"` + + // SubscriptionID is the Azure subscription identifier + SubscriptionID *string `yaml:"subscription_id,omitempty"` + + // TenantID is the Azure tenant identifier + TenantID *string `yaml:"tenant_id,omitempty"` + + // Environment specifies the Azure cloud environment (e.g. "public", "usgovernment") + Environment *string `yaml:"environment,omitempty"` +} + +// Merge performs a deep merge of the current AzureConfig with another AzureConfig. +func (base *AzureConfig) Merge(overlay *AzureConfig) { + if overlay == nil { + return + } + if overlay.Enabled != nil { + base.Enabled = overlay.Enabled + } + if overlay.SubscriptionID != nil { + base.SubscriptionID = overlay.SubscriptionID + } + if overlay.TenantID != nil { + base.TenantID = overlay.TenantID + } + if overlay.Environment != nil { + base.Environment = overlay.Environment + } +} + +// Copy creates a deep copy of the AzureConfig object +func (c *AzureConfig) Copy() *AzureConfig { + if c == nil { + return nil + } + return &AzureConfig{ + Enabled: c.Enabled, + SubscriptionID: c.SubscriptionID, + TenantID: c.TenantID, + Environment: c.Environment, + } +} diff --git a/api/v1alpha1/azure/azure_config_test.go b/api/v1alpha1/azure/azure_config_test.go new file mode 100644 index 000000000..66a8fe3f0 --- /dev/null +++ b/api/v1alpha1/azure/azure_config_test.go @@ -0,0 +1,46 @@ +package azure + +import ( + "testing" +) + +func TestAzureConfig(t *testing.T) { + t.Run("Merge", func(t *testing.T) { + base := &AzureConfig{ + Enabled: boolPtr(false), + } + overlay := &AzureConfig{ + Enabled: boolPtr(true), + } + + base.Merge(overlay) + + if *base.Enabled != true { + t.Errorf("Expected Enabled to be true, got %v", *base.Enabled) + } + }) + + t.Run("Copy", func(t *testing.T) { + original := &AzureConfig{ + Enabled: boolPtr(true), + } + + copy := original.Copy() + + if copy == nil { + t.Fatal("Expected non-nil copy") + } + + if copy == original { + t.Error("Expected copy to be a new instance") + } + + if *copy.Enabled != *original.Enabled { + t.Errorf("Expected Enabled to be %v, got %v", *original.Enabled, *copy.Enabled) + } + }) +} + +func boolPtr(b bool) *bool { + return &b +} diff --git a/api/v1alpha1/config_types.go b/api/v1alpha1/config_types.go index cc5bae23b..7a1731563 100644 --- a/api/v1alpha1/config_types.go +++ b/api/v1alpha1/config_types.go @@ -2,6 +2,7 @@ package v1alpha1 import ( "github.com/windsorcli/cli/api/v1alpha1/aws" + "github.com/windsorcli/cli/api/v1alpha1/azure" "github.com/windsorcli/cli/api/v1alpha1/cluster" "github.com/windsorcli/cli/api/v1alpha1/dns" "github.com/windsorcli/cli/api/v1alpha1/docker" @@ -27,6 +28,7 @@ type Context struct { Environment map[string]string `yaml:"environment,omitempty"` Secrets *secrets.SecretsConfig `yaml:"secrets,omitempty"` AWS *aws.AWSConfig `yaml:"aws,omitempty"` + Azure *azure.AzureConfig `yaml:"azure,omitempty"` Docker *docker.DockerConfig `yaml:"docker,omitempty"` Git *git.GitConfig `yaml:"git,omitempty"` Terraform *terraform.TerraformConfig `yaml:"terraform,omitempty"` @@ -67,6 +69,12 @@ func (base *Context) Merge(overlay *Context) { } base.AWS.Merge(overlay.AWS) } + if overlay.Azure != nil { + if base.Azure == nil { + base.Azure = &azure.AzureConfig{} + } + base.Azure.Merge(overlay.Azure) + } if overlay.Docker != nil { if base.Docker == nil { base.Docker = &docker.DockerConfig{} @@ -133,6 +141,7 @@ func (c *Context) DeepCopy() *Context { Environment: environmentCopy, Secrets: c.Secrets.Copy(), AWS: c.AWS.Copy(), + Azure: c.Azure.Copy(), Docker: c.Docker.Copy(), Git: c.Git.Copy(), Terraform: c.Terraform.Copy(), diff --git a/api/v1alpha1/config_types_test.go b/api/v1alpha1/config_types_test.go index b2c91b92f..f7c43024a 100644 --- a/api/v1alpha1/config_types_test.go +++ b/api/v1alpha1/config_types_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/windsorcli/cli/api/v1alpha1/aws" + "github.com/windsorcli/cli/api/v1alpha1/azure" "github.com/windsorcli/cli/api/v1alpha1/cluster" "github.com/windsorcli/cli/api/v1alpha1/dns" "github.com/windsorcli/cli/api/v1alpha1/docker" @@ -21,6 +22,12 @@ func TestConfig_Merge(t *testing.T) { Enabled: ptrBool(true), AWSEndpointURL: ptrString("https://base.aws.endpoint"), }, + Azure: &azure.AzureConfig{ + Enabled: ptrBool(true), + SubscriptionID: ptrString("base-sub"), + TenantID: ptrString("base-tenant"), + Environment: ptrString("base-cloud"), + }, Docker: &docker.DockerConfig{ Enabled: ptrBool(true), }, @@ -61,6 +68,12 @@ func TestConfig_Merge(t *testing.T) { AWS: &aws.AWSConfig{ AWSEndpointURL: ptrString("https://overlay.aws.endpoint"), }, + Azure: &azure.AzureConfig{ + Enabled: ptrBool(false), + SubscriptionID: ptrString("overlay-sub"), + TenantID: ptrString("overlay-tenant"), + Environment: ptrString("overlay-cloud"), + }, Docker: &docker.DockerConfig{ Enabled: ptrBool(false), }, @@ -102,6 +115,18 @@ func TestConfig_Merge(t *testing.T) { if base.AWS.AWSEndpointURL == nil || *base.AWS.AWSEndpointURL != "https://overlay.aws.endpoint" { t.Errorf("AWS AWSEndpointURL mismatch: expected 'https://overlay.aws.endpoint', got '%s'", *base.AWS.AWSEndpointURL) } + if base.Azure.Enabled == nil || *base.Azure.Enabled != false { + t.Errorf("Azure Enabled mismatch: expected false, got %v", *base.Azure.Enabled) + } + if base.Azure.SubscriptionID == nil || *base.Azure.SubscriptionID != "overlay-sub" { + t.Errorf("Azure SubscriptionID mismatch: expected 'overlay-sub', got '%s'", *base.Azure.SubscriptionID) + } + if base.Azure.TenantID == nil || *base.Azure.TenantID != "overlay-tenant" { + t.Errorf("Azure TenantID mismatch: expected 'overlay-tenant', got '%s'", *base.Azure.TenantID) + } + if base.Azure.Environment == nil || *base.Azure.Environment != "overlay-cloud" { + t.Errorf("Azure Environment mismatch: expected 'overlay-cloud', got '%s'", *base.Azure.Environment) + } if base.Docker.Enabled == nil || *base.Docker.Enabled != false { t.Errorf("Docker Enabled mismatch: expected false, got %v", *base.Docker.Enabled) } @@ -123,8 +148,8 @@ func TestConfig_Merge(t *testing.T) { if base.Secrets.OnePasswordConfig.Vaults["vault1"].URL != "https://url.com" { t.Errorf("Secrets Vault URL mismatch: expected 'https://url.com', got '%s'", base.Secrets.OnePasswordConfig.Vaults["vault1"].URL) } - if len(base.Environment) != 2 || base.Environment["KEY1"] != "value1" || base.Environment["KEY2"] != "value2" { - t.Errorf("Environment merge mismatch: expected map with 'KEY1' and 'KEY2', got %v", base.Environment) + if len(base.Environment) != 2 || base.Environment["KEY2"] != "value2" { + t.Errorf("Environment mismatch: expected map with 'KEY2', got %v", base.Environment) } if base.Network.CIDRBlock == nil || *base.Network.CIDRBlock != "10.0.0.0/8" { t.Errorf("Network CIDRBlock mismatch: expected '10.0.0.0/8', got '%s'", *base.Network.CIDRBlock) @@ -140,6 +165,12 @@ func TestConfig_Merge(t *testing.T) { Enabled: ptrBool(true), AWSEndpointURL: ptrString("https://base.aws.endpoint"), }, + Azure: &azure.AzureConfig{ + Enabled: ptrBool(true), + SubscriptionID: ptrString("base-sub"), + TenantID: ptrString("base-tenant"), + Environment: ptrString("base-cloud"), + }, Docker: &docker.DockerConfig{ Enabled: ptrBool(true), }, @@ -182,6 +213,18 @@ func TestConfig_Merge(t *testing.T) { if base.AWS.AWSEndpointURL == nil || *base.AWS.AWSEndpointURL != "https://base.aws.endpoint" { t.Errorf("AWS AWSEndpointURL mismatch: expected 'https://base.aws.endpoint', got '%s'", *base.AWS.AWSEndpointURL) } + if base.Azure.Enabled == nil || *base.Azure.Enabled != true { + t.Errorf("Azure Enabled mismatch: expected true, got %v", *base.Azure.Enabled) + } + if base.Azure.SubscriptionID == nil || *base.Azure.SubscriptionID != "base-sub" { + t.Errorf("Azure SubscriptionID mismatch: expected 'base-sub', got '%s'", *base.Azure.SubscriptionID) + } + if base.Azure.TenantID == nil || *base.Azure.TenantID != "base-tenant" { + t.Errorf("Azure TenantID mismatch: expected 'base-tenant', got '%s'", *base.Azure.TenantID) + } + if base.Azure.Environment == nil || *base.Azure.Environment != "base-cloud" { + t.Errorf("Azure Environment mismatch: expected 'base-cloud', got '%s'", *base.Azure.Environment) + } if base.Docker.Enabled == nil || *base.Docker.Enabled != true { t.Errorf("Docker Enabled mismatch: expected true, got %v", *base.Docker.Enabled) } @@ -221,6 +264,12 @@ func TestConfig_Merge(t *testing.T) { AWS: &aws.AWSConfig{ AWSEndpointURL: ptrString("https://overlay.aws.endpoint"), }, + Azure: &azure.AzureConfig{ + Enabled: ptrBool(false), + SubscriptionID: ptrString("overlay-sub"), + TenantID: ptrString("overlay-tenant"), + Environment: ptrString("overlay-cloud"), + }, Docker: &docker.DockerConfig{ Enabled: ptrBool(false), }, @@ -262,6 +311,18 @@ func TestConfig_Merge(t *testing.T) { if base.AWS.AWSEndpointURL == nil || *base.AWS.AWSEndpointURL != "https://overlay.aws.endpoint" { t.Errorf("AWS AWSEndpointURL mismatch: expected 'https://overlay.aws.endpoint', got '%s'", *base.AWS.AWSEndpointURL) } + if base.Azure.Enabled == nil || *base.Azure.Enabled != false { + t.Errorf("Azure Enabled mismatch: expected false, got %v", *base.Azure.Enabled) + } + if base.Azure.SubscriptionID == nil || *base.Azure.SubscriptionID != "overlay-sub" { + t.Errorf("Azure SubscriptionID mismatch: expected 'overlay-sub', got '%s'", *base.Azure.SubscriptionID) + } + if base.Azure.TenantID == nil || *base.Azure.TenantID != "overlay-tenant" { + t.Errorf("Azure TenantID mismatch: expected 'overlay-tenant', got '%s'", *base.Azure.TenantID) + } + if base.Azure.Environment == nil || *base.Azure.Environment != "overlay-cloud" { + t.Errorf("Azure Environment mismatch: expected 'overlay-cloud', got '%s'", *base.Azure.Environment) + } if base.Docker.Enabled == nil || *base.Docker.Enabled != false { t.Errorf("Docker Enabled mismatch: expected false, got %v", *base.Docker.Enabled) } @@ -337,6 +398,12 @@ func TestConfig_Copy(t *testing.T) { Enabled: ptrBool(true), AWSEndpointURL: ptrString("https://original.aws.endpoint"), }, + Azure: &azure.AzureConfig{ + Enabled: ptrBool(true), + SubscriptionID: ptrString("original-sub"), + TenantID: ptrString("original-tenant"), + Environment: ptrString("original-cloud"), + }, Docker: &docker.DockerConfig{ Enabled: ptrBool(true), }, @@ -379,6 +446,18 @@ func TestConfig_Copy(t *testing.T) { if original.AWS.Enabled == nil || copy.AWS.Enabled == nil || *original.AWS.Enabled != *copy.AWS.Enabled { t.Errorf("AWS Enabled mismatch: expected %v, got %v", *original.AWS.Enabled, *copy.AWS.Enabled) } + if original.Azure.Enabled == nil || copy.Azure.Enabled == nil || *original.Azure.Enabled != *copy.Azure.Enabled { + t.Errorf("Azure Enabled mismatch: expected %v, got %v", *original.Azure.Enabled, *copy.Azure.Enabled) + } + if original.Azure.SubscriptionID == nil || copy.Azure.SubscriptionID == nil || *original.Azure.SubscriptionID != *copy.Azure.SubscriptionID { + t.Errorf("Azure SubscriptionID mismatch: expected %v, got %v", *original.Azure.SubscriptionID, *copy.Azure.SubscriptionID) + } + if original.Azure.TenantID == nil || copy.Azure.TenantID == nil || *original.Azure.TenantID != *copy.Azure.TenantID { + t.Errorf("Azure TenantID mismatch: expected %v, got %v", *original.Azure.TenantID, *copy.Azure.TenantID) + } + if original.Azure.Environment == nil || copy.Azure.Environment == nil || *original.Azure.Environment != *copy.Azure.Environment { + t.Errorf("Azure Environment mismatch: expected %v, got %v", *original.Azure.Environment, *copy.Azure.Environment) + } if original.Docker.Enabled == nil || copy.Docker.Enabled == nil || *original.Docker.Enabled != *copy.Docker.Enabled { t.Errorf("Docker Enabled mismatch: expected %v, got %v", *original.Docker.Enabled, *copy.Docker.Enabled) } @@ -397,26 +476,19 @@ func TestConfig_Copy(t *testing.T) { if original.DNS.Enabled == nil || copy.DNS.Enabled == nil || *original.DNS.Enabled != *copy.DNS.Enabled { t.Errorf("DNS Enabled mismatch: expected %v, got %v", *original.DNS.Enabled, *copy.DNS.Enabled) } + if original.Network.CIDRBlock == nil || copy.Network.CIDRBlock == nil || *original.Network.CIDRBlock != *copy.Network.CIDRBlock { + t.Errorf("Network CIDRBlock mismatch: expected %v, got %v", *original.Network.CIDRBlock, *copy.Network.CIDRBlock) + } if original.Blueprint == nil || copy.Blueprint == nil || *original.Blueprint != *copy.Blueprint { t.Errorf("Blueprint mismatch: expected %v, got %v", *original.Blueprint, *copy.Blueprint) } - - // Modify the copy and ensure original is unchanged - copy.Docker.Enabled = ptrBool(false) - if original.Docker.Enabled == nil || *original.Docker.Enabled == *copy.Docker.Enabled { - t.Errorf("Original Docker Enabled was modified: expected %v, got %v", true, *copy.Docker.Enabled) - } - - copy.Cluster.Enabled = ptrBool(false) - if original.Cluster.Enabled == nil || *original.Cluster.Enabled == *copy.Cluster.Enabled { - t.Errorf("Original Cluster Enabled was modified: expected %v, got %v", true, *copy.Cluster.Enabled) - } }) t.Run("CopyWithNilValues", func(t *testing.T) { original := &Context{ Environment: nil, AWS: nil, + Azure: nil, Docker: nil, Git: nil, Terraform: nil, @@ -433,6 +505,9 @@ func TestConfig_Copy(t *testing.T) { if copy.AWS != nil { t.Errorf("AWS should be nil, got %v", copy.AWS) } + if copy.Azure != nil { + t.Errorf("Azure should be nil, got %v", copy.Azure) + } if copy.Docker != nil { t.Errorf("Docker should be nil, got %v", copy.Docker) } diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 554c4a4a3..bc2c1c310 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -81,6 +81,7 @@ type ComponentConstructors struct { NewToolsManager func(di.Injector) tools.ToolsManager NewAwsEnvPrinter func(di.Injector) env.EnvPrinter + NewAzureEnvPrinter func(di.Injector) env.EnvPrinter NewDockerEnvPrinter func(di.Injector) env.EnvPrinter NewKubeEnvPrinter func(di.Injector) env.EnvPrinter NewOmniEnvPrinter func(di.Injector) env.EnvPrinter @@ -186,6 +187,9 @@ func NewDefaultConstructors() ComponentConstructors { NewAwsEnvPrinter: func(injector di.Injector) env.EnvPrinter { return env.NewAwsEnvPrinter(injector) }, + NewAzureEnvPrinter: func(injector di.Injector) env.EnvPrinter { + return env.NewAzureEnvPrinter(injector) + }, NewDockerEnvPrinter: func(injector di.Injector) env.EnvPrinter { return env.NewDockerEnvPrinter(injector) }, @@ -906,6 +910,7 @@ func (c *BaseController) createEnvComponents(req Requirements) error { envPrinters := map[string]func(di.Injector) env.EnvPrinter{ "awsEnv": c.constructors.NewAwsEnvPrinter, + "azureEnv": c.constructors.NewAzureEnvPrinter, "dockerEnv": c.constructors.NewDockerEnvPrinter, "kubeEnv": c.constructors.NewKubeEnvPrinter, "omniEnv": c.constructors.NewOmniEnvPrinter, @@ -918,6 +923,9 @@ func (c *BaseController) createEnvComponents(req Requirements) error { if key == "awsEnv" && !configHandler.GetBool("aws.enabled") { continue } + if key == "azureEnv" && !configHandler.GetBool("azure.enabled") { + continue + } if key == "dockerEnv" && !configHandler.GetBool("docker.enabled") { continue } diff --git a/pkg/controller/mock_controller.go b/pkg/controller/mock_controller.go index 326636b06..495f9b721 100644 --- a/pkg/controller/mock_controller.go +++ b/pkg/controller/mock_controller.go @@ -103,6 +103,9 @@ func NewMockConstructors() ComponentConstructors { NewAwsEnvPrinter: func(injector di.Injector) env.EnvPrinter { return env.NewMockEnvPrinter() }, + NewAzureEnvPrinter: func(injector di.Injector) env.EnvPrinter { + return env.NewMockEnvPrinter() + }, NewDockerEnvPrinter: func(injector di.Injector) env.EnvPrinter { return env.NewMockEnvPrinter() }, diff --git a/pkg/env/azure_env.go b/pkg/env/azure_env.go new file mode 100644 index 000000000..1341f5c1e --- /dev/null +++ b/pkg/env/azure_env.go @@ -0,0 +1,77 @@ +// The AzureEnvPrinter is a specialized component that manages Azure environment configuration. +// It provides Azure-specific environment variable management and configuration, +// The AzureEnvPrinter handles Azure configuration settings and environment setup, +// ensuring proper Azure CLI integration and environment setup for operations. + +package env + +import ( + "fmt" + "path/filepath" + + "github.com/windsorcli/cli/pkg/di" +) + +// ============================================================================= +// Types +// ============================================================================= + +// AzureEnvPrinter is a struct that implements Azure environment configuration +type AzureEnvPrinter struct { + BaseEnvPrinter +} + +// ============================================================================= +// Constructor +// ============================================================================= + +// NewAzureEnvPrinter creates a new AzureEnvPrinter instance +func NewAzureEnvPrinter(injector di.Injector) *AzureEnvPrinter { + return &AzureEnvPrinter{ + BaseEnvPrinter: *NewBaseEnvPrinter(injector), + } +} + +// ============================================================================= +// Public Methods +// ============================================================================= + +// GetEnvVars retrieves the environment variables for the Azure environment. +func (e *AzureEnvPrinter) GetEnvVars() (map[string]string, error) { + envVars := make(map[string]string) + + configRoot, err := e.configHandler.GetConfigRoot() + if err != nil { + return nil, fmt.Errorf("error retrieving configuration root directory: %w", err) + } + + azureConfigDir := filepath.Join(configRoot, ".azure") + + // Get the current context configuration + config := e.configHandler.GetConfig() + if config != nil && config.Azure != nil { + envVars["AZURE_CONFIG_DIR"] = filepath.ToSlash(azureConfigDir) + envVars["AZURE_CORE_LOGIN_EXPERIENCE_V2"] = "false" + + if config.Azure.SubscriptionID != nil { + envVars["ARM_SUBSCRIPTION_ID"] = *config.Azure.SubscriptionID + } + if config.Azure.TenantID != nil { + envVars["ARM_TENANT_ID"] = *config.Azure.TenantID + } + if config.Azure.Environment != nil { + envVars["ARM_ENVIRONMENT"] = *config.Azure.Environment + } + } + + return envVars, nil +} + +// Print prints the environment variables for the Azure environment. +func (e *AzureEnvPrinter) Print() error { + envVars, err := e.GetEnvVars() + if err != nil { + return fmt.Errorf("error getting environment variables: %w", err) + } + return e.BaseEnvPrinter.Print(envVars) +} diff --git a/pkg/env/azure_env_test.go b/pkg/env/azure_env_test.go new file mode 100644 index 000000000..214e6f028 --- /dev/null +++ b/pkg/env/azure_env_test.go @@ -0,0 +1,185 @@ +package env + +import ( + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + "github.com/windsorcli/cli/pkg/config" +) + +// ============================================================================= +// Test Setup +// ============================================================================= + +func setupAzureEnvMocks(t *testing.T, opts ...*SetupOptions) *Mocks { + t.Helper() + if opts == nil { + opts = []*SetupOptions{{}} + } + if opts[0].ConfigStr == "" { + opts[0].ConfigStr = ` +version: v1alpha1 +contexts: + mock-context: + azure: + subscription_id: "test-subscription" + tenant_id: "test-tenant" + environment: "test-environment" +` + } + mocks := setupMocks(t, opts[0]) + + // Mock stat function to make Azure config directory exist + mocks.Shims.Stat = func(name string) (os.FileInfo, error) { + if name == filepath.FromSlash("/mock/config/root/.azure") { + return nil, nil + } + return nil, os.ErrNotExist + } + + return mocks +} + +// ============================================================================= +// Test Public Methods +// ============================================================================= + +func TestAzureEnv_GetEnvVars(t *testing.T) { + setup := func(t *testing.T, opts ...*SetupOptions) (*AzureEnvPrinter, *Mocks) { + t.Helper() + mocks := setupAzureEnvMocks(t, opts...) + printer := NewAzureEnvPrinter(mocks.Injector) + if err := printer.Initialize(); err != nil { + t.Fatalf("Failed to initialize env: %v", err) + } + printer.shims = mocks.Shims + return printer, mocks + } + + t.Run("Success", func(t *testing.T) { + printer, mocks := setup(t) + configRoot, err := mocks.ConfigHandler.GetConfigRoot() + if err != nil { + t.Fatalf("Failed to get config root: %v", err) + } + envVars, err := printer.GetEnvVars() + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + expectedEnvVars := map[string]string{ + "AZURE_CONFIG_DIR": filepath.ToSlash(filepath.Join(configRoot, ".azure")), + "AZURE_CORE_LOGIN_EXPERIENCE_V2": "false", + "ARM_SUBSCRIPTION_ID": "test-subscription", + "ARM_TENANT_ID": "test-tenant", + "ARM_ENVIRONMENT": "test-environment", + } + if !reflect.DeepEqual(envVars, expectedEnvVars) { + t.Errorf("GetEnvVars returned %v, want %v", envVars, expectedEnvVars) + } + }) + + t.Run("GetConfigRootError", func(t *testing.T) { + mockConfigHandler := &config.MockConfigHandler{} + mockConfigHandler.GetConfigRootFunc = func() (string, error) { + return "", fmt.Errorf("error retrieving configuration root directory") + } + mocks := setupAzureEnvMocks(t, &SetupOptions{ + ConfigHandler: mockConfigHandler, + }) + printer := NewAzureEnvPrinter(mocks.Injector) + if err := printer.Initialize(); err != nil { + t.Fatalf("Failed to initialize env: %v", err) + } + printer.shims = mocks.Shims + _, err := printer.GetEnvVars() + if err == nil { + t.Error("Expected error, got nil") + } + if !strings.Contains(err.Error(), "error retrieving configuration root directory") { + t.Errorf("Expected error containing 'error retrieving configuration root directory', got %v", err) + } + }) + + t.Run("MissingConfiguration", func(t *testing.T) { + printer, mocks := setup(t) + if err := mocks.ConfigHandler.LoadConfigString(` +version: v1alpha1 +contexts: + mock-context: {} +`); err != nil { + t.Fatalf("Failed to load config: %v", err) + } + envVars, err := printer.GetEnvVars() + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if len(envVars) != 0 { + t.Errorf("Expected empty environment variables, got %v", envVars) + } + }) +} + +func TestAzureEnv_Print(t *testing.T) { + setup := func(t *testing.T, opts ...*SetupOptions) (*AzureEnvPrinter, *Mocks) { + t.Helper() + mocks := setupAzureEnvMocks(t, opts...) + printer := NewAzureEnvPrinter(mocks.Injector) + if err := printer.Initialize(); err != nil { + t.Fatalf("Failed to initialize env: %v", err) + } + printer.shims = mocks.Shims + return printer, mocks + } + + t.Run("Success", func(t *testing.T) { + printer, mocks := setup(t) + configRoot, err := mocks.ConfigHandler.GetConfigRoot() + if err != nil { + t.Fatalf("Failed to get config root: %v", err) + } + var capturedEnvVars map[string]string + mocks.Shell.PrintEnvVarsFunc = func(envVars map[string]string) { + capturedEnvVars = envVars + } + err = printer.Print() + if err != nil { + t.Errorf("Print returned an error: %v", err) + } + expectedEnvVars := map[string]string{ + "AZURE_CONFIG_DIR": filepath.ToSlash(filepath.Join(configRoot, ".azure")), + "AZURE_CORE_LOGIN_EXPERIENCE_V2": "false", + "ARM_SUBSCRIPTION_ID": "test-subscription", + "ARM_TENANT_ID": "test-tenant", + "ARM_ENVIRONMENT": "test-environment", + } + if !reflect.DeepEqual(capturedEnvVars, expectedEnvVars) { + t.Errorf("Print set environment variables to %v, want %v", capturedEnvVars, expectedEnvVars) + } + }) + + t.Run("GetEnvVarsError", func(t *testing.T) { + mockConfigHandler := &config.MockConfigHandler{} + mockConfigHandler.GetConfigRootFunc = func() (string, error) { + return "", fmt.Errorf("error retrieving configuration root directory") + } + mocks := setupAzureEnvMocks(t, &SetupOptions{ + ConfigHandler: mockConfigHandler, + }) + printer := NewAzureEnvPrinter(mocks.Injector) + if err := printer.Initialize(); err != nil { + t.Fatalf("Failed to initialize env: %v", err) + } + printer.shims = mocks.Shims + err := printer.Print() + if err == nil { + t.Error("Expected error, got nil") + } + if !strings.Contains(err.Error(), "error getting environment variables") { + t.Errorf("Expected error containing 'error getting environment variables', got %v", err) + } + }) +} diff --git a/pkg/generators/git_generator.go b/pkg/generators/git_generator.go index e80884f56..10a4018d0 100644 --- a/pkg/generators/git_generator.go +++ b/pkg/generators/git_generator.go @@ -30,6 +30,7 @@ var gitIgnoreLines = []string{ "contexts/**/.talos/", "contexts/**/.omni/", "contexts/**/.aws/", + "contexts/**/.azure/", } // ============================================================================= diff --git a/pkg/generators/git_generator_test.go b/pkg/generators/git_generator_test.go index d850cb290..a40e87a82 100644 --- a/pkg/generators/git_generator_test.go +++ b/pkg/generators/git_generator_test.go @@ -27,7 +27,7 @@ contexts/**/.kube/ contexts/**/.talos/ contexts/**/.omni/ contexts/**/.aws/ -` +contexts/**/.azure/` gitGenTestExpectedPerm = fs.FileMode(0644) ) @@ -117,8 +117,20 @@ func TestGitGenerator_Write(t *testing.T) { // And the content should be correct expectedContent := gitGenTestExpectedContent - if string(writtenContent) != expectedContent { - t.Errorf("expected content %s, got %s", expectedContent, string(writtenContent)) + actualContent := string(writtenContent) + if actualContent != expectedContent { + // Trim trailing whitespace and newlines for robust comparison + trimmedExpected := expectedContent + trimmedActual := actualContent + for len(trimmedExpected) > 0 && (trimmedExpected[len(trimmedExpected)-1] == '\n' || trimmedExpected[len(trimmedExpected)-1] == '\r') { + trimmedExpected = trimmedExpected[:len(trimmedExpected)-1] + } + for len(trimmedActual) > 0 && (trimmedActual[len(trimmedActual)-1] == '\n' || trimmedActual[len(trimmedActual)-1] == '\r') { + trimmedActual = trimmedActual[:len(trimmedActual)-1] + } + if trimmedActual != trimmedExpected { + t.Errorf("expected content %q, got %q", trimmedExpected, trimmedActual) + } } })