Skip to content
Open
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
9 changes: 9 additions & 0 deletions cli/azd/cmd/down.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
"errors"
"fmt"
"log"
"slices"
"time"

Expand Down Expand Up @@ -68,6 +69,7 @@ type downAction struct {
provisionManager *provisioning.Manager
importManager *project.ImportManager
env *environment.Environment
envManager environment.Manager
console input.Console
projectConfig *project.ProjectConfig
alphaFeatureManager *alpha.FeatureManager
Expand All @@ -78,6 +80,7 @@ func newDownAction(
flags *downFlags,
provisionManager *provisioning.Manager,
env *environment.Environment,
envManager environment.Manager,
projectConfig *project.ProjectConfig,
console input.Console,
alphaFeatureManager *alpha.FeatureManager,
Expand All @@ -87,6 +90,7 @@ func newDownAction(
flags: flags,
provisionManager: provisionManager,
env: env,
envManager: envManager,
console: console,
projectConfig: projectConfig,
importManager: importManager,
Expand Down Expand Up @@ -150,6 +154,11 @@ func (a *downAction) Run(ctx context.Context) (*actions.ActionResult, error) {
}
}

// Invalidate cache after successful down so azd show will refresh
if err := a.envManager.InvalidateEnvCache(ctx, a.env.Name()); err != nil {
log.Printf("warning: failed to invalidate state cache: %v", err)
}

return &actions.ActionResult{
Message: &actions.ResultMessage{
Header: fmt.Sprintf("Your application was removed from Azure in %s.", ux.DurationAsText(since(startTime))),
Expand Down
8 changes: 8 additions & 0 deletions cli/azd/internal/cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ type DeployAction struct {
projectConfig *project.ProjectConfig
azdCtx *azdcontext.AzdContext
env *environment.Environment
envManager environment.Manager
projectManager project.ProjectManager
serviceManager project.ServiceManager
resourceManager project.ResourceManager
Expand All @@ -136,6 +137,7 @@ func NewDeployAction(
resourceManager project.ResourceManager,
azdCtx *azdcontext.AzdContext,
environment *environment.Environment,
envManager environment.Manager,
accountManager account.Manager,
cloud *cloud.Cloud,
azCli *azapi.AzureClient,
Expand All @@ -152,6 +154,7 @@ func NewDeployAction(
projectConfig: projectConfig,
azdCtx: azdCtx,
env: environment,
envManager: envManager,
projectManager: projectManager,
serviceManager: serviceManager,
resourceManager: resourceManager,
Expand Down Expand Up @@ -362,6 +365,11 @@ func (da *DeployAction) Run(ctx context.Context) (*actions.ActionResult, error)
}
}

// Invalidate cache after successful deploy so azd show will refresh
if err := da.envManager.InvalidateEnvCache(ctx, da.env.Name()); err != nil {
log.Printf("warning: failed to invalidate state cache: %v", err)
}

return &actions.ActionResult{
Message: &actions.ResultMessage{
Header: fmt.Sprintf("Your application was deployed to Azure in %s.", ux.DurationAsText(since(startTime))),
Expand Down
5 changes: 5 additions & 0 deletions cli/azd/internal/cmd/provision.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,11 @@ func (p *ProvisionAction) Run(ctx context.Context) (*actions.ActionResult, error
}, nil
}

// Invalidate cache after successful provisioning so next azd show will refresh
if err := p.envManager.InvalidateEnvCache(ctx, p.env.Name()); err != nil {
log.Printf("warning: failed to invalidate state cache: %v", err)
}

return &actions.ActionResult{
Message: &actions.ResultMessage{
Header: fmt.Sprintf(
Expand Down
97 changes: 72 additions & 25 deletions cli/azd/internal/cmd/show/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/output"
"github.com/azure/azure-dev/cli/azd/pkg/output/ux"
"github.com/azure/azure-dev/cli/azd/pkg/project"
"github.com/azure/azure-dev/cli/azd/pkg/state"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
Expand Down Expand Up @@ -92,6 +93,7 @@ type showAction struct {
lazyServiceManager *lazy.Lazy[project.ServiceManager]
lazyResourceManager *lazy.Lazy[project.ResourceManager]
portalUrlBase string
stateCacheManager *state.StateCacheManager
}

func NewShowAction(
Expand Down Expand Up @@ -133,6 +135,7 @@ func NewShowAction(
lazyServiceManager: lazyServiceManager,
lazyResourceManager: lazyResourceManager,
portalUrlBase: cloud.PortalUrlBase,
stateCacheManager: envManager.GetStateCacheManager(),
}
}

Expand Down Expand Up @@ -196,11 +199,6 @@ func (s *showAction) Run(ctx context.Context) (*actions.ActionResult, error) {
if subId = env.GetSubscriptionId(); subId == "" {
log.Printf("provision has not been run, resource ids will not be available")
} else {
resourceManager, err := s.lazyResourceManager.GetValue()
if err != nil {
return nil, err
}

envName := env.Name()

if len(s.args) > 0 {
Expand All @@ -213,32 +211,81 @@ func (s *showAction) Run(ctx context.Context) (*actions.ActionResult, error) {
return nil, nil
}

rgName, err = s.infraResourceManager.FindResourceGroupForEnvironment(ctx, subId, envName)
if err == nil {
for _, serviceConfig := range stableServices {
svcName := serviceConfig.Name
resources, err := resourceManager.GetServiceResources(ctx, subId, rgName, serviceConfig)
if err == nil {
resourceIds := make([]string, len(resources))
for idx, res := range resources {
resourceIds[idx] = res.Id
}
// Try to load from cache first
cachedState, err := s.stateCacheManager.Load(ctx, envName)
if err != nil {
log.Printf("error loading cache: %v, will query Azure directly", err)
}

resSvc := res.Services[svcName]
resSvc.Target = &contracts.ShowTargetArm{
ResourceIds: resourceIds,
if cachedState != nil && cachedState.SubscriptionId == subId {
// Use cached data
rgName = cachedState.ResourceGroupName
for svcName, cachedSvc := range cachedState.ServiceResources {
if resSvc, exists := res.Services[svcName]; exists {
if len(cachedSvc.ResourceIds) > 0 {
resSvc.Target = &contracts.ShowTargetArm{
ResourceIds: cachedSvc.ResourceIds,
}
}
resSvc.IngresUrl = s.serviceEndpoint(ctx, subId, serviceConfig, env)
resSvc.IngresUrl = cachedSvc.IngressUrl
res.Services[svcName] = resSvc
} else {
log.Printf("ignoring error determining resource id for service %s: %v", svcName, err)
}
}
} else {
log.Printf(
"ignoring error determining resource group for environment %s, resource ids will not be available: %v",
env.Name(),
err)
// Cache miss or invalid, query Azure and update cache
resourceManager, err := s.lazyResourceManager.GetValue()
if err != nil {
return nil, err
}

rgName, err = s.infraResourceManager.FindResourceGroupForEnvironment(ctx, subId, envName)
if err == nil {
// Create cache for this query
newCache := &state.StateCache{
SubscriptionId: subId,
ResourceGroupName: rgName,
ServiceResources: make(map[string]state.ServiceResourceCache),
}

for _, serviceConfig := range stableServices {
svcName := serviceConfig.Name
resources, err := resourceManager.GetServiceResources(ctx, subId, rgName, serviceConfig)
if err == nil {
resourceIds := make([]string, len(resources))
for idx, res := range resources {
resourceIds[idx] = res.Id
}

ingressUrl := s.serviceEndpoint(ctx, subId, serviceConfig, env)

resSvc := res.Services[svcName]
resSvc.Target = &contracts.ShowTargetArm{
ResourceIds: resourceIds,
}
resSvc.IngresUrl = ingressUrl
res.Services[svcName] = resSvc

// Add to cache
newCache.ServiceResources[svcName] = state.ServiceResourceCache{
ResourceIds: resourceIds,
IngressUrl: ingressUrl,
}
} else {
log.Printf("ignoring error determining resource id for service %s: %v", svcName, err)
}
}

// Save cache
if err := s.stateCacheManager.Save(ctx, envName, newCache); err != nil {
log.Printf("error saving cache: %v", err)
}
} else {
log.Printf(
"ignoring error determining resource group for environment %s, "+
"resource ids will not be available: %v",
env.Name(),
err)
}
}
}
}
Expand Down
30 changes: 25 additions & 5 deletions cli/azd/pkg/environment/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ type Manager interface {

EnvPath(env *Environment) string
ConfigPath(env *Environment) string

// InvalidateEnvCache invalidates the state cache for the given environment
InvalidateEnvCache(ctx context.Context, envName string) error

// GetStateCacheManager returns the state cache manager for accessing cached state
GetStateCacheManager() *state.StateCacheManager
}

type manager struct {
Expand All @@ -94,6 +100,9 @@ type manager struct {
// across different scopes, enabling shared state mutation (e.g., from extensions)
cacheMu sync.RWMutex
envCache map[string]*Environment

// State cache manager for managing cached Azure resource information
stateCacheManager *state.StateCacheManager
}

// NewManager creates a new Manager instance
Expand Down Expand Up @@ -125,11 +134,12 @@ func NewManager(
}

return &manager{
azdContext: azdContext,
local: local,
remote: remote,
console: console,
envCache: make(map[string]*Environment),
azdContext: azdContext,
local: local,
remote: remote,
console: console,
envCache: make(map[string]*Environment),
stateCacheManager: state.NewStateCacheManager(azdContext.EnvironmentDirectory()),
}, nil
}

Expand Down Expand Up @@ -564,6 +574,16 @@ func (m *manager) ensureValidEnvironmentName(ctx context.Context, spec *Spec) er
return nil
}

// InvalidateEnvCache invalidates the state cache for the given environment
func (m *manager) InvalidateEnvCache(ctx context.Context, envName string) error {
return m.stateCacheManager.Invalidate(ctx, envName)
}

// GetStateCacheManager returns the state cache manager for accessing cached state
func (m *manager) GetStateCacheManager() *state.StateCacheManager {
return m.stateCacheManager
}

func invalidEnvironmentNameMsg(environmentName string) string {
return fmt.Sprintf(
"environment name '%s' is invalid (it should contain only alphanumeric characters and hyphens)\n",
Expand Down
Loading
Loading