@@ -16,6 +16,7 @@ import (
1616
1717 "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
1818 "github.com/azure/azure-dev/cli/azd/cmd/actions"
19+ "github.com/azure/azure-dev/cli/azd/cmd/middleware"
1920 "github.com/azure/azure-dev/cli/azd/internal"
2021 "github.com/azure/azure-dev/cli/azd/internal/tracing"
2122 "github.com/azure/azure-dev/cli/azd/internal/tracing/fields"
@@ -26,9 +27,11 @@ import (
2627 "github.com/azure/azure-dev/cli/azd/pkg/entraid"
2728 "github.com/azure/azure-dev/cli/azd/pkg/environment"
2829 "github.com/azure/azure-dev/cli/azd/pkg/environment/azdcontext"
30+ "github.com/azure/azure-dev/cli/azd/pkg/infra"
2931 "github.com/azure/azure-dev/cli/azd/pkg/infra/provisioning"
3032 "github.com/azure/azure-dev/cli/azd/pkg/infra/provisioning/bicep"
3133 "github.com/azure/azure-dev/cli/azd/pkg/input"
34+ "github.com/azure/azure-dev/cli/azd/pkg/ioc"
3235 "github.com/azure/azure-dev/cli/azd/pkg/keyvault"
3336 "github.com/azure/azure-dev/cli/azd/pkg/output"
3437 "github.com/azure/azure-dev/cli/azd/pkg/output/ux"
@@ -1100,10 +1103,18 @@ func newEnvRefreshCmd() *cobra.Command {
11001103 return cmd
11011104}
11021105
1106+ // provisioningProviderActivator is the narrow slice of *middleware.ExtensionActivator that env
1107+ // refresh consumes; it exists so tests can stub extension activation.
1108+ type provisioningProviderActivator interface {
1109+ EnsureProvisioningProviders (ctx context.Context , providerNames []string , environmentName string ) (func (), error )
1110+ SuggestExtensionForProvider (ctx context.Context , providerName string ) string
1111+ }
1112+
11031113type envRefreshAction struct {
11041114 provisionManager * provisioning.Manager
11051115 projectConfig * project.ProjectConfig
11061116 projectManager project.ProjectManager
1117+ extensionActivator provisioningProviderActivator
11071118 env * environment.Environment
11081119 envManager environment.Manager
11091120 prompters prompt.Prompter
@@ -1119,6 +1130,7 @@ func newEnvRefreshAction(
11191130 provisionManager * provisioning.Manager ,
11201131 projectConfig * project.ProjectConfig ,
11211132 projectManager project.ProjectManager ,
1133+ extensionActivator * middleware.ExtensionActivator ,
11221134 env * environment.Environment ,
11231135 envManager environment.Manager ,
11241136 prompters prompt.Prompter ,
@@ -1132,6 +1144,7 @@ func newEnvRefreshAction(
11321144 return & envRefreshAction {
11331145 provisionManager : provisionManager ,
11341146 projectManager : projectManager ,
1147+ extensionActivator : extensionActivator ,
11351148 env : env ,
11361149 envManager : envManager ,
11371150 prompters : prompters ,
@@ -1145,35 +1158,61 @@ func newEnvRefreshAction(
11451158 }
11461159}
11471160
1161+ // suggestionForUnresolvedProvider returns the id of a registry extension to suggest installing
1162+ // when err is a provider-resolution failure; any other initialization error skips the registry
1163+ // lookup and returns an empty string.
1164+ func suggestionForUnresolvedProvider (
1165+ ctx context.Context ,
1166+ err error ,
1167+ activator provisioningProviderActivator ,
1168+ providerName string ,
1169+ ) string {
1170+ if ! errors .Is (err , ioc .ErrResolveInstance ) {
1171+ return ""
1172+ }
1173+
1174+ return activator .SuggestExtensionForProvider (ctx , providerName )
1175+ }
1176+
11481177func (ef * envRefreshAction ) Run (ctx context.Context ) (* actions.ActionResult , error ) {
11491178 // Command title
11501179 ef .console .MessageUxItem (ctx , & ux.MessageTitle {
11511180 Title : fmt .Sprintf ("Refreshing environment %s (azd env refresh)" , ef .env .Name ()),
11521181 })
11531182
1154- if err := ef .projectManager .Initialize (ctx , ef .projectConfig ); err != nil {
1155- return nil , err
1156- }
1157-
1158- if err := ef .projectManager .EnsureAllTools (ctx , ef .projectConfig , nil ); err != nil {
1159- return nil , err
1160- }
1161-
1162- infra , err := ef .importManager .ProjectInfrastructure (ctx , ef .projectConfig )
1183+ // `env refresh` is read-only: it deliberately skips service-target initialization and tool
1184+ // checks, which would fail for extension-provided hosts (for example `azure.ai.agent`).
1185+ // Framework lifecycle hooks are wired best-effort after the outputs are pulled - see below.
1186+ projectInfra , err := ef .importManager .ProjectInfrastructure (ctx , ef .projectConfig )
11631187 if err != nil {
11641188 return nil , err
11651189 }
1166- defer func () { _ = infra .Cleanup () }()
1190+ defer func () { _ = projectInfra .Cleanup () }()
11671191
1168- layers := infra .Options .GetLayers ()
1192+ layers := projectInfra .Options .GetLayers ()
11691193 if ef .flags .layer != "" {
1170- layerOpt , err := infra .Options .GetLayer (ef .flags .layer )
1194+ layerOpt , err := projectInfra .Options .GetLayer (ef .flags .layer )
11711195 if err != nil {
11721196 return nil , err
11731197 }
11741198 layers = []provisioning.Options {layerOpt }
11751199 }
11761200
1201+ // Extension-provided provisioning providers (for example `microsoft.foundry`) are only
1202+ // resolvable while the owning extension runs, and `env refresh` does not run the extensions
1203+ // middleware. Start just the installed extension(s) declaring the configured provider(s);
1204+ // all other names resolve natively as in every other command.
1205+ providerNames := make ([]string , 0 , len (layers ))
1206+ for _ , layer := range layers {
1207+ providerNames = append (providerNames , string (layer .Provider ))
1208+ }
1209+
1210+ cleanupProviders , err := ef .extensionActivator .EnsureProvisioningProviders (ctx , providerNames , ef .env .Name ())
1211+ if err != nil {
1212+ return nil , fmt .Errorf ("activating provisioning provider extensions: %w" , err )
1213+ }
1214+ defer cleanupProviders ()
1215+
11771216 // If resource group is defined within the project but not in the environment then
11781217 // add it to the environment to support BYOI lookup scenarios like ADE
11791218 // Infra providers do not currently have access to project configuration
@@ -1183,6 +1222,7 @@ func (ef *envRefreshAction) Run(ctx context.Context) (*actions.ActionResult, err
11831222 }
11841223
11851224 var state provisioning.State
1225+ stateRefreshed := false
11861226 for _ , layer := range layers {
11871227 if ef .flags .layer != "" || len (layers ) > 1 {
11881228 ef .console .EnsureBlankLine (ctx )
@@ -1200,20 +1240,62 @@ func (ef *envRefreshAction) Run(ctx context.Context) (*actions.ActionResult, err
12001240 return nil , err
12011241 }
12021242 } else if err != nil {
1203- return nil , fmt .Errorf ("initializing provisioning manager: %w" , err )
1243+ err = fmt .Errorf ("initializing provisioning manager: %w" , err )
1244+
1245+ // A resolution failure for a provider that a registry (non-installed) extension
1246+ // declares is almost certainly the missing extension: suggest installing it.
1247+ if extensionId := suggestionForUnresolvedProvider (
1248+ ctx , err , ef .extensionActivator , string (layer .Provider )); extensionId != "" {
1249+ return nil , & internal.ErrorWithSuggestion {
1250+ Err : err ,
1251+ Suggestion : fmt .Sprintf (
1252+ "Provisioning provider '%s' is supplied by the '%s' extension. To install it, run %s" ,
1253+ layer .Provider ,
1254+ extensionId ,
1255+ output .WithHighLightFormat ("azd extension install %s" , extensionId ),
1256+ ),
1257+ }
1258+ }
1259+
1260+ return nil , err
12041261 }
12051262
12061263 stateOptions := provisioning .NewStateOptions (ef .flags .hint )
12071264 result , err := ef .provisionManager .State (ctx , stateOptions )
12081265 if err != nil {
1266+ // No deployment exists yet (for example, refresh before `azd provision`): this is
1267+ // informational, not an error - continue so any other layers still refresh. An
1268+ // explicit --hint stays a hard error because CompletedDeployments wraps the same
1269+ // sentinel when deployments exist but none matches the hint.
1270+ if errors .Is (err , infra .ErrDeploymentsNotFound ) && ef .flags .hint == "" {
1271+ ef .console .Message (ctx , fmt .Sprintf (
1272+ "No deployment was found for environment '%s'; there are no outputs to refresh yet. " +
1273+ "Run %s to create one." ,
1274+ ef .env .Name (),
1275+ output .WithHighLightFormat ("azd provision" ),
1276+ ))
1277+ continue
1278+ }
1279+
12091280 return nil , fmt .Errorf ("getting deployment: %w" , err )
12101281 }
12111282
1283+ // Extension providers may reply with an empty state result (nothing deployed yet); treat
1284+ // it like the no-deployment case rather than dereferencing a nil state.
1285+ if result == nil || result .State == nil {
1286+ ef .console .Message (ctx , fmt .Sprintf (
1287+ "No deployment state was found for environment '%s'; there are no outputs to refresh yet." ,
1288+ ef .env .Name (),
1289+ ))
1290+ continue
1291+ }
1292+
12121293 if err := provisioning .UpdateEnvironment (ctx , result .State .Outputs , ef .env , ef .envManager ); err != nil {
12131294 return nil , err
12141295 }
12151296
12161297 state .MergeInto (* result .State )
1298+ stateRefreshed = true
12171299 }
12181300
12191301 if ef .formatter .Kind () == output .JsonFormat {
@@ -1223,23 +1305,36 @@ func (ef *envRefreshAction) Run(ctx context.Context) (*actions.ActionResult, err
12231305 }
12241306 }
12251307
1226- servicesStable , err := ef .importManager .ServiceStable (ctx , ef .projectConfig )
1227- if err != nil {
1228- return nil , err
1229- }
1308+ // Wire framework lifecycle hooks (such as the .NET ServiceEventEnvUpdated handler that syncs
1309+ // outputs into user-secrets) best-effort per service, and raise the event only when a layer
1310+ // actually produced state. Services whose framework fails to initialize are reported and
1311+ // skipped rather than failing the refresh.
1312+ if stateRefreshed {
1313+ initializedServices , skipped , err := ef .projectManager .InitializeFrameworks (ctx , ef .projectConfig )
1314+ if err != nil {
1315+ return nil , err
1316+ }
12301317
1231- for _ , svc := range servicesStable {
1232- eventArgs := project.ServiceLifecycleEventArgs {
1233- Project : ef .projectConfig ,
1234- Service : svc ,
1235- ServiceContext : project .NewServiceContext (),
1236- Args : map [string ]any {
1237- "bicepOutput" : state .Outputs ,
1238- },
1318+ for _ , skip := range skipped {
1319+ ef .console .MessageUxItem (ctx , & ux.WarningMessage {
1320+ Description : fmt .Sprintf (
1321+ "Skipping environment update events for service '%s': %v" , skip .Service .Name , skip .Err ),
1322+ })
12391323 }
12401324
1241- if err := svc .RaiseEvent (ctx , project .ServiceEventEnvUpdated , eventArgs ); err != nil {
1242- return nil , err
1325+ for _ , svc := range initializedServices {
1326+ eventArgs := project.ServiceLifecycleEventArgs {
1327+ Project : ef .projectConfig ,
1328+ Service : svc ,
1329+ ServiceContext : project .NewServiceContext (),
1330+ Args : map [string ]any {
1331+ "bicepOutput" : state .Outputs ,
1332+ },
1333+ }
1334+
1335+ if err := svc .RaiseEvent (ctx , project .ServiceEventEnvUpdated , eventArgs ); err != nil {
1336+ return nil , err
1337+ }
12431338 }
12441339 }
12451340
0 commit comments