Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ message SelectEnvironmentRequest {

// Request to retrieve a specific key-value pair.
message GetEnvRequest {
string env_name = 1; // Name of the environment.
string env_name = 1; // Optional: Name of the environment. If empty, uses default.
string key = 2; // Key to retrieve.
}

// Request to set a key-value pair.
message SetEnvRequest {
string env_name = 1; // Name of the environment.
string env_name = 1; // Optional: Name of the environment. If empty, uses default.
string key = 2; // Key to set.
string value = 3; // Value to set for the key.
}
Expand Down Expand Up @@ -109,6 +109,7 @@ message KeyValue {
// Request message for Get
message GetConfigRequest {
string path = 1;
string env_name = 2; // Optional: Name of the environment. If empty, uses default.
}

// Response message for Get
Expand All @@ -120,6 +121,7 @@ message GetConfigResponse {
// Request message for GetString
message GetConfigStringRequest {
string path = 1;
string env_name = 2; // Optional: Name of the environment. If empty, uses default.
}

// Response message for GetString
Expand All @@ -131,6 +133,7 @@ message GetConfigStringResponse {
// Request message for GetSection
message GetConfigSectionRequest {
string path = 1;
string env_name = 2; // Optional: Name of the environment. If empty, uses default.
}

// Response message for GetSection
Expand All @@ -143,9 +146,11 @@ message GetConfigSectionResponse {
message SetConfigRequest {
string path = 1;
bytes value = 2;
string env_name = 3; // Optional: Name of the environment. If empty, uses default.
}

// Request message for Unset
message UnsetConfigRequest {
string path = 1;
string env_name = 2; // Optional: Name of the environment. If empty, uses default.
}
9 changes: 7 additions & 2 deletions cli/azd/grpc/proto/environment.proto
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ message SelectEnvironmentRequest {

// Request to retrieve a specific key-value pair.
message GetEnvRequest {
string env_name = 1; // Name of the environment.
string env_name = 1; // Optional: Name of the environment. If empty, uses default.
string key = 2; // Key to retrieve.
}

// Request to set a key-value pair.
message SetEnvRequest {
string env_name = 1; // Name of the environment.
string env_name = 1; // Optional: Name of the environment. If empty, uses default.
string key = 2; // Key to set.
string value = 3; // Value to set for the key.
}
Expand Down Expand Up @@ -110,6 +110,7 @@ message KeyValue {
// Request message for Get
message GetConfigRequest {
string path = 1;
string env_name = 2; // Optional: Name of the environment. If empty, uses default.
}

// Response message for Get
Expand All @@ -121,6 +122,7 @@ message GetConfigResponse {
// Request message for GetString
message GetConfigStringRequest {
string path = 1;
string env_name = 2; // Optional: Name of the environment. If empty, uses default.
}

// Response message for GetString
Expand All @@ -132,6 +134,7 @@ message GetConfigStringResponse {
// Request message for GetSection
message GetConfigSectionRequest {
string path = 1;
string env_name = 2; // Optional: Name of the environment. If empty, uses default.
}

// Response message for GetSection
Expand All @@ -144,10 +147,12 @@ message GetConfigSectionResponse {
message SetConfigRequest {
string path = 1;
bytes value = 2;
string env_name = 3; // Optional: Name of the environment. If empty, uses default.
}

// Request message for Unset
message UnsetConfigRequest {
string path = 1;
string env_name = 2; // Optional: Name of the environment. If empty, uses default.
}

40 changes: 22 additions & 18 deletions cli/azd/internal/grpcserver/environment_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,7 @@ func (s *environmentService) GetValues(
ctx context.Context,
req *azdext.GetEnvironmentRequest,
) (*azdext.KeyValueListResponse, error) {
envManager, err := s.lazyEnvManager.GetValue()
if err != nil {
return nil, err
}

env, err := envManager.Get(ctx, req.Name)
env, err := s.resolveEnvironment(ctx, req.Name)
if err != nil {
return nil, err
}
Expand All @@ -157,12 +152,7 @@ func (s *environmentService) GetValues(

// GetValue retrieves the value of a specific key in the specified environment.
func (s *environmentService) GetValue(ctx context.Context, req *azdext.GetEnvRequest) (*azdext.KeyValueResponse, error) {
envManager, err := s.lazyEnvManager.GetValue()
if err != nil {
return nil, err
}

env, err := envManager.Get(ctx, req.EnvName)
env, err := s.resolveEnvironment(ctx, req.EnvName)
if err != nil {
return nil, err
}
Expand All @@ -182,7 +172,7 @@ func (s *environmentService) SetValue(ctx context.Context, req *azdext.SetEnvReq
return nil, err
}

env, err := envManager.Get(ctx, req.EnvName)
env, err := s.resolveEnvironment(ctx, req.EnvName)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -223,12 +213,26 @@ func (s *environmentService) currentEnvironment(ctx context.Context) (*environme
return env, nil
}

// resolveEnvironment resolves the environment by name if provided, otherwise falls back to the default environment.
func (s *environmentService) resolveEnvironment(ctx context.Context, envName string) (*environment.Environment, error) {
if envName == "" {
return s.currentEnvironment(ctx)
}

envManager, err := s.lazyEnvManager.GetValue()
if err != nil {
return nil, err
}

return envManager.Get(ctx, envName)
}

// GetConfig retrieves a config value by path.
func (s *environmentService) GetConfig(
ctx context.Context,
req *azdext.GetConfigRequest,
) (*azdext.GetConfigResponse, error) {
env, err := s.currentEnvironment(ctx)
env, err := s.resolveEnvironment(ctx, req.EnvName)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -256,7 +260,7 @@ func (s *environmentService) GetConfigString(
ctx context.Context,
req *azdext.GetConfigStringRequest,
) (*azdext.GetConfigStringResponse, error) {
env, err := s.currentEnvironment(ctx)
env, err := s.resolveEnvironment(ctx, req.EnvName)
if err != nil {
return nil, err
}
Expand All @@ -274,7 +278,7 @@ func (s *environmentService) GetConfigSection(
ctx context.Context,
req *azdext.GetConfigSectionRequest,
) (*azdext.GetConfigSectionResponse, error) {
env, err := s.currentEnvironment(ctx)
env, err := s.resolveEnvironment(ctx, req.EnvName)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -309,7 +313,7 @@ func (s *environmentService) SetConfig(ctx context.Context, req *azdext.SetConfi
return nil, err
}

env, err := s.currentEnvironment(ctx)
env, err := s.resolveEnvironment(ctx, req.EnvName)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -340,7 +344,7 @@ func (s *environmentService) UnsetConfig(
return nil, err
}

env, err := s.currentEnvironment(ctx)
env, err := s.resolveEnvironment(ctx, req.EnvName)
if err != nil {
return nil, err
}
Expand Down
184 changes: 184 additions & 0 deletions cli/azd/internal/grpcserver/environment_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,187 @@ func Test_EnvironmentService_Flow(t *testing.T) {
require.NoError(t, err)
require.Equal(t, testEnv2.Name(), getCurrentResponse.Environment.Name)
}

// Test_EnvironmentService_ResolveEnvironment validates that methods use the default environment
// when env_name is empty and the specified environment when env_name is provided.
func Test_EnvironmentService_ResolveEnvironment(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
temp := t.TempDir()

azdContext := azdcontext.NewAzdContextWithDirectory(temp)
projectConfig := project.ProjectConfig{Name: "test"}
err := project.Save(*mockContext.Context, &projectConfig, azdContext.ProjectPath())
require.NoError(t, err)

fileConfigManager := config.NewFileConfigManager(config.NewManager())
localDataStore := environment.NewLocalFileDataStore(azdContext, fileConfigManager)
envManager, err := environment.NewManager(mockContext.Container, azdContext, mockContext.Console, localDataStore, nil)
require.NoError(t, err)

// Create two environments with different dotenv and config values.
env1, err := envManager.Create(*mockContext.Context, environment.Spec{Name: "env1"})
require.NoError(t, err)
env1.DotenvSet("key1", "value1")
require.NoError(t, envManager.Save(*mockContext.Context, env1))

env2, err := envManager.Create(*mockContext.Context, environment.Spec{Name: "env2"})
require.NoError(t, err)
env2.DotenvSet("key1", "value2")
require.NoError(t, envManager.Save(*mockContext.Context, env2))

// Set env1 as default.
require.NoError(t, azdContext.SetProjectState(azdcontext.ProjectState{DefaultEnvironment: "env1"}))

service := NewEnvironmentService(lazy.From(azdContext), lazy.From(envManager))
ctx := *mockContext.Context

t.Run("GetValue", func(t *testing.T) {
tests := []struct {
name string
envName string
expected string
}{
{"empty_env_name_uses_default", "", "value1"},
{"explicit_env_name_targets_specified", "env2", "value2"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resp, err := service.GetValue(ctx, &azdext.GetEnvRequest{EnvName: tt.envName, Key: "key1"})
require.NoError(t, err)
require.Equal(t, tt.expected, resp.Value)
})
}
})

t.Run("GetValues", func(t *testing.T) {
tests := []struct {
name string
envName string
expected string
}{
{"empty_name_uses_default", "", "value1"},
{"explicit_name_targets_specified", "env2", "value2"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resp, err := service.GetValues(ctx, &azdext.GetEnvironmentRequest{Name: tt.envName})
require.NoError(t, err)
envValues := map[string]string{}
for _, kv := range resp.KeyValues {
envValues[kv.Key] = kv.Value
}
require.Equal(t, tt.expected, envValues["key1"])
})
}
})

t.Run("SetValue", func(t *testing.T) {
_, err := service.SetValue(ctx, &azdext.SetEnvRequest{Key: "newkey", Value: "newval"})
require.NoError(t, err)

resp, err := service.GetValue(ctx, &azdext.GetEnvRequest{EnvName: "env1", Key: "newkey"})
require.NoError(t, err)
require.Equal(t, "newval", resp.Value)
})

// Config subtests share state: SetConfig writes values that subsequent reads and unset verify.
t.Run("Config", func(t *testing.T) {
// Setup: write config to both environments.
_, err := service.SetConfig(ctx, &azdext.SetConfigRequest{
Path: "test.key",
Value: []byte(`"configval1"`),
})
require.NoError(t, err)

_, err = service.SetConfig(ctx, &azdext.SetConfigRequest{
Path: "test.key",
Value: []byte(`"configval2"`),
EnvName: "env2",
})
require.NoError(t, err)

t.Run("GetConfigString", func(t *testing.T) {
tests := []struct {
name string
envName string
expected string
}{
{"empty_env_name_reads_default", "", "configval1"},
{"explicit_env_name_reads_specified", "env2", "configval2"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resp, err := service.GetConfigString(ctx, &azdext.GetConfigStringRequest{
Path: "test.key",
EnvName: tt.envName,
})
require.NoError(t, err)
require.True(t, resp.Found)
require.Equal(t, tt.expected, resp.Value)
})
}
})

t.Run("GetConfig", func(t *testing.T) {
tests := []struct {
name string
envName string
}{
{"empty_env_name_reads_default", ""},
{"explicit_env_name_reads_specified", "env2"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resp, err := service.GetConfig(ctx, &azdext.GetConfigRequest{
Path: "test.key",
EnvName: tt.envName,
})
require.NoError(t, err)
require.True(t, resp.Found)
})
}
})

t.Run("GetConfigSection", func(t *testing.T) {
tests := []struct {
name string
envName string
}{
{"empty_env_name_reads_default", ""},
{"explicit_env_name_reads_specified", "env2"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resp, err := service.GetConfigSection(ctx, &azdext.GetConfigSectionRequest{
Path: "test",
EnvName: tt.envName,
})
require.NoError(t, err)
require.True(t, resp.Found)
})
}
})

t.Run("UnsetConfig", func(t *testing.T) {
_, err := service.UnsetConfig(ctx, &azdext.UnsetConfigRequest{
Path: "test.key",
EnvName: "env2",
})
require.NoError(t, err)

// Verify config was removed from env2.
resp, err := service.GetConfigString(ctx, &azdext.GetConfigStringRequest{
Path: "test.key",
EnvName: "env2",
})
require.NoError(t, err)
require.False(t, resp.Found)

// Verify config still exists in env1 (default).
resp, err = service.GetConfigString(ctx, &azdext.GetConfigStringRequest{Path: "test.key"})
require.NoError(t, err)
require.True(t, resp.Found)
require.Equal(t, "configval1", resp.Value)
})
})
}
Loading
Loading