Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
4 changes: 2 additions & 2 deletions cli/azd/cmd/middleware/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func (m *HooksMiddleware) registerCommandHooks(
m.console,
projectConfig.Path,
projectConfig.Hooks,
env.Environ(),
env,
)

var actionResult *actions.ActionResult
Expand Down Expand Up @@ -142,7 +142,7 @@ func (m *HooksMiddleware) registerServiceHooks(
m.console,
service.Path(),
service.Hooks,
env.Environ(),
env,
)

for hookName, hookConfig := range service.Hooks {
Expand Down
34 changes: 27 additions & 7 deletions cli/azd/pkg/environment/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,8 @@ func FromRoot(root string) (*Environment, error) {
Root: root,
}

envPath := filepath.Join(root, azdcontext.DotEnvFileName)
if e, err := godotenv.Read(envPath); errors.Is(err, os.ErrNotExist) {
env.Values = make(map[string]string)
} else if err != nil {
return EmptyWithRoot(root), fmt.Errorf("loading .env: %w", err)
} else {
env.Values = e
if err := env.Reload(); err != nil {
return EmptyWithRoot(root), err
}

cfgPath := filepath.Join(root, azdcontext.ConfigFileName)
Expand Down Expand Up @@ -144,13 +139,38 @@ func (e *Environment) Getenv(key string) string {
return os.Getenv(key)
}

// Reloads environment variables from .env file
func (e *Environment) Reload() error {
Comment thread
wbreza marked this conversation as resolved.
Outdated
envPath := filepath.Join(e.Root, azdcontext.DotEnvFileName)
Comment thread
wbreza marked this conversation as resolved.
if envMap, err := godotenv.Read(envPath); errors.Is(err, os.ErrNotExist) {
e.Values = make(map[string]string)
} else if err != nil {
return fmt.Errorf("loading .env: %w", err)
} else {
e.Values = envMap
}
Comment thread
wbreza marked this conversation as resolved.

return nil
}

// If `Root` is set, Save writes the current contents of the environment to
// the given directory, creating it and any intermediate directories as needed.
func (e *Environment) Save() error {
if e.Root == "" {
return nil
}

// Cache current values & reload to get any new env vars
currentValues := e.Values
if err := e.Reload(); err != nil {
return fmt.Errorf("failed reloading env vars, %w", err)
}

// Overlay current values before saving
for key, value := range currentValues {
e.Values[key] = value
}
Comment thread
wbreza marked this conversation as resolved.

err := os.MkdirAll(e.Root, osutil.PermissionDirectory)
if err != nil {
return fmt.Errorf("failed to create a directory: %w", err)
Expand Down
41 changes: 41 additions & 0 deletions cli/azd/pkg/environment/environment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import (
"path/filepath"
"testing"

"github.com/azure/azure-dev/cli/azd/pkg/environment/azdcontext"
"github.com/azure/azure-dev/cli/azd/pkg/osutil"
"github.com/azure/azure-dev/cli/azd/test/ostest"
"github.com/joho/godotenv"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -106,3 +109,41 @@ func TestFromRoot(t *testing.T) {
require.True(t, e.Config.IsEmpty())
})
}

func Test_SaveAndReload(t *testing.T) {
tempDir := t.TempDir()
ostest.Chdir(t, tempDir)

env, err := FromRoot(tempDir)
require.NotNil(t, env)
require.NoError(t, err)

env.SetLocation("eastus2")
env.SetSubscriptionId("SUBSCRIPTION_ID")

err = env.Save()
require.NoError(t, err)

// Simulate another process updating the .env file
envPath := filepath.Join(tempDir, azdcontext.DotEnvFileName)
envMap, err := godotenv.Read(envPath)
require.NotNil(t, envMap)
require.NoError(t, err)

// This entry does not exist in the current env state but is added as part of the reload process
envMap["SERVICE_API_ENDPOINT_URL"] = "http://api.example.com"
err = godotenv.Write(envMap, envPath)
require.NoError(t, err)

// Set a new property in the env
env.SetServiceProperty("web", "ENDPOINT_URL", "http://web.example.com")
err = env.Save()
require.NoError(t, err)

// Verify all values exist with expected values
// All values now exist whether or not they were in the env state to start with
require.Equal(t, env.Values["SERVICE_WEB_ENDPOINT_URL"], "http://web.example.com")
require.Equal(t, env.Values["SERVICE_API_ENDPOINT_URL"], "http://api.example.com")
require.Equal(t, "SUBSCRIPTION_ID", env.GetSubscriptionId())
require.Equal(t, "eastus2", env.GetLocation())
}
16 changes: 11 additions & 5 deletions cli/azd/pkg/ext/hooks_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"strings"

"github.com/azure/azure-dev/cli/azd/pkg/environment"
Comment thread
wbreza marked this conversation as resolved.
"github.com/azure/azure-dev/cli/azd/pkg/exec"
"github.com/azure/azure-dev/cli/azd/pkg/input"
"github.com/azure/azure-dev/cli/azd/pkg/output"
Expand All @@ -23,7 +24,7 @@ type HooksRunner struct {
console input.Console
cwd string
hooks map[string]*HookConfig
envVars []string
env *environment.Environment
}

// NewHooks creates a new instance of CommandHooks
Expand All @@ -34,7 +35,7 @@ func NewHooksRunner(
console input.Console,
cwd string,
hooks map[string]*HookConfig,
envVars []string,
env *environment.Environment,
) *HooksRunner {
if cwd == "" {
osWd, err := os.Getwd()
Expand All @@ -51,7 +52,7 @@ func NewHooksRunner(
console: console,
cwd: cwd,
hooks: hooks,
envVars: envVars,
env: env,
}
}

Expand Down Expand Up @@ -82,6 +83,11 @@ func (h *HooksRunner) RunHooks(ctx context.Context, hookType HookType, commands
return fmt.Errorf("failed running scripts for hooks '%s', %w", strings.Join(commands, ","), err)
}

// Reload env vars before execution to enable support for hooks to generate new env vars between commands
if err := h.env.Reload(); err != nil {
return fmt.Errorf("failed reloading env values, %w", err)
}

for _, hookConfig := range hooks {
err := h.execHook(ctx, hookConfig)
if err != nil {
Expand All @@ -101,9 +107,9 @@ func (h *HooksRunner) GetScript(hookConfig *HookConfig) (tools.Script, error) {

switch hookConfig.Shell {
case ShellTypeBash:
return bash.NewBashScript(h.commandRunner, h.cwd, h.envVars), nil
return bash.NewBashScript(h.commandRunner, h.cwd, h.env.Environ()), nil
case ShellTypePowershell:
return powershell.NewPowershellScript(h.commandRunner, h.cwd, h.envVars), nil
return powershell.NewPowershellScript(h.commandRunner, h.cwd, h.env.Environ()), nil
default:
return nil, fmt.Errorf(
"shell type '%s' is not a valid option. Only 'sh' and 'pwsh' are supported",
Expand Down
31 changes: 19 additions & 12 deletions cli/azd/pkg/ext/hooks_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"
"testing"

"github.com/azure/azure-dev/cli/azd/pkg/environment"
"github.com/azure/azure-dev/cli/azd/pkg/exec"
"github.com/azure/azure-dev/cli/azd/pkg/osutil"
"github.com/azure/azure-dev/cli/azd/test/mocks"
Expand All @@ -18,10 +19,13 @@ func Test_Hooks_Execute(t *testing.T) {
cwd := t.TempDir()
ostest.Chdir(t, cwd)

env := []string{
"a=apple",
"b=banana",
}
env := environment.EphemeralWithValues(
"test",
map[string]string{
"a": "apple",
"b": "banana",
},
)

hooks := map[string]*HookConfig{
"preinline": {
Expand Down Expand Up @@ -56,7 +60,7 @@ func Test_Hooks_Execute(t *testing.T) {
ranPreHook = true
require.Equal(t, "scripts/precommand.sh", args.Args[0])
require.Equal(t, cwd, args.Cwd)
require.Equal(t, env, args.Env)
require.Equal(t, env.Environ(), args.Env)
require.Equal(t, false, args.Interactive)

return exec.NewRunResult(0, "", ""), nil
Expand All @@ -82,7 +86,7 @@ func Test_Hooks_Execute(t *testing.T) {
ranPostHook = true
require.Equal(t, "scripts/postcommand.sh", args.Args[0])
require.Equal(t, cwd, args.Cwd)
require.Equal(t, env, args.Env)
require.Equal(t, env.Environ(), args.Env)
require.Equal(t, false, args.Interactive)

return exec.NewRunResult(0, "", ""), nil
Expand All @@ -108,7 +112,7 @@ func Test_Hooks_Execute(t *testing.T) {
ranPostHook = true
require.Equal(t, "scripts/preinteractive.sh", args.Args[0])
require.Equal(t, cwd, args.Cwd)
require.Equal(t, env, args.Env)
require.Equal(t, env.Environ(), args.Env)
require.Equal(t, true, args.Interactive)

return exec.NewRunResult(0, "", ""), nil
Expand Down Expand Up @@ -200,10 +204,13 @@ func Test_Hooks_GetScript(t *testing.T) {
cwd := t.TempDir()
ostest.Chdir(t, cwd)

env := []string{
"a=apple",
"b=banana",
}
env := environment.EphemeralWithValues(
"test",
map[string]string{
"a": "apple",
"b": "banana",
},
)

hooks := map[string]*HookConfig{
"bash": {
Expand Down Expand Up @@ -286,7 +293,7 @@ func Test_GetScript_Validation(t *testing.T) {
err := os.WriteFile("my-script.ps1", nil, osutil.PermissionFile)
require.NoError(t, err)

env := []string{}
env := environment.Ephemeral()

mockContext := mocks.NewMockContext(context.Background())
hooksManager := NewHooksManager(tempDir)
Expand Down
17 changes: 14 additions & 3 deletions cli/azd/pkg/project/service_target_aks.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,17 @@ func (t *aksTarget) Deploy(
return ServiceDeploymentResult{}, err
}

if len(endpoints) > 0 {
// The AKS endpoints contain some additional identifying information
// Split on common to pull out the URL as the first segment
// The last endpoint in the array will be the most publicly exposed
endpointParts := strings.Split(endpoints[len(endpoints)-1], ",")
t.env.SetServiceProperty(t.config.Name, "ENDPOINT_URL", endpointParts[0])
if err := t.env.Save(); err != nil {
Comment thread
wbreza marked this conversation as resolved.
return ServiceDeploymentResult{}, fmt.Errorf("failed updating environment with endpoint url, %w", err)
}
}

return ServiceDeploymentResult{
TargetResourceId: azure.KubernetesServiceRID(
t.env.GetSubscriptionId(),
Expand Down Expand Up @@ -455,11 +466,11 @@ func (t *aksTarget) getServiceEndpoints(ctx context.Context, namespace string, s
var endpoints []string
if service.Spec.Type == kubectl.ServiceTypeLoadBalancer {
for _, resource := range service.Status.LoadBalancer.Ingress {
endpoints = append(endpoints, fmt.Sprintf("http://%s (Service, Type: LoadBalancer)", resource.Ip))
endpoints = append(endpoints, fmt.Sprintf("http://%s, (Service, Type: LoadBalancer)", resource.Ip))
}
} else if service.Spec.Type == kubectl.ServiceTypeClusterIp {
for index, ip := range service.Spec.ClusterIps {
endpoints = append(endpoints, fmt.Sprintf("http://%s:%d (Service, Type: ClusterIP)", ip, service.Spec.Ports[index].Port))
endpoints = append(endpoints, fmt.Sprintf("http://%s:%d, (Service, Type: ClusterIP)", ip, service.Spec.Ports[index].Port))
}
}

Expand Down Expand Up @@ -495,7 +506,7 @@ func (t *aksTarget) getIngressEndpoints(ctx context.Context, namespace string, r
return nil, fmt.Errorf("failed constructing service endpoints, %w", err)
}

endpoints = append(endpoints, fmt.Sprintf("%s (Ingress, Type: LoadBalancer)", endpointUrl))
endpoints = append(endpoints, fmt.Sprintf("%s, (Ingress, Type: LoadBalancer)", endpointUrl))
}

return endpoints, nil
Expand Down