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
69 changes: 41 additions & 28 deletions cli/azd/cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,44 +162,57 @@ func (ba *buildAction) Run(ctx context.Context) (*actions.ActionResult, error) {
return nil, err
}

buildResults := map[string]*project.ServiceBuildResult{}
stableServices, err := ba.importManager.ServiceStable(ctx, ba.projectConfig)
if err != nil {
return nil, err
}

for _, svc := range stableServices {
stepMessage := fmt.Sprintf("Building service %s", svc.Name)
ba.console.ShowSpinner(ctx, stepMessage, input.Step)

// Skip this service if both cases are true:
// 1. The user specified a service name
// 2. This service is not the one the user specified
if targetServiceName != "" && targetServiceName != svc.Name {
ba.console.StopSpinner(ctx, stepMessage, input.StepSkipped)
continue
}
projectEventArgs := project.ProjectLifecycleEventArgs{
Project: ba.projectConfig,
}

buildResult, err := async.RunWithProgress(
func(buildProgress project.ServiceProgress) {
progressMessage := fmt.Sprintf("Building service %s (%s)", svc.Name, buildProgress.Message)
ba.console.ShowSpinner(ctx, progressMessage, input.Step)
},
func(progress *async.Progress[project.ServiceProgress]) (*project.ServiceBuildResult, error) {
return ba.serviceManager.Build(ctx, svc, nil, progress)
},
)
buildResults := map[string]*project.ServiceBuildResult{}

if err != nil {
ba.console.StopSpinner(ctx, stepMessage, input.StepFailed)
return nil, err
err = ba.projectConfig.Invoke(ctx, project.ProjectEventBuild, projectEventArgs, func() error {
for _, svc := range stableServices {
stepMessage := fmt.Sprintf("Building service %s", svc.Name)
ba.console.ShowSpinner(ctx, stepMessage, input.Step)

// Skip this service if both cases are true:
// 1. The user specified a service name
// 2. This service is not the one the user specified
if targetServiceName != "" && targetServiceName != svc.Name {
ba.console.StopSpinner(ctx, stepMessage, input.StepSkipped)
continue
}

buildResult, err := async.RunWithProgress(
func(buildProgress project.ServiceProgress) {
progressMessage := fmt.Sprintf("Building service %s (%s)", svc.Name, buildProgress.Message)
ba.console.ShowSpinner(ctx, progressMessage, input.Step)
},
func(progress *async.Progress[project.ServiceProgress]) (*project.ServiceBuildResult, error) {
return ba.serviceManager.Build(ctx, svc, nil, progress)
},
)

if err != nil {
ba.console.StopSpinner(ctx, stepMessage, input.StepFailed)
return err
}

ba.console.StopSpinner(ctx, stepMessage, input.StepDone)
buildResults[svc.Name] = buildResult

// report build outputs
ba.console.MessageUxItem(ctx, buildResult.Artifacts)
}

ba.console.StopSpinner(ctx, stepMessage, input.StepDone)
buildResults[svc.Name] = buildResult
return nil
})

// report build outputs
ba.console.MessageUxItem(ctx, buildResult.Artifacts)
if err != nil {
return nil, err
}

if ba.formatter.Kind() == output.JsonFormat {
Expand Down
144 changes: 79 additions & 65 deletions cli/azd/cmd/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,81 +142,95 @@ func (pa *packageAction) Run(ctx context.Context) (*actions.ActionResult, error)
return nil, err
}

packageResults := map[string]*project.ServicePackageResult{}

serviceTable, err := pa.importManager.ServiceStable(ctx, pa.projectConfig)
if err != nil {
return nil, err
}

serviceCount := len(serviceTable)
for index, svc := range serviceTable {
// TODO(ellismg): We need to figure out what packaging an containerized dotnet app means. For now, just skip it.
// We "package" the app during deploy when we call `dotnet publish /p:PublishProfile=DefaultContainer` to build
// and push the container image.
//
// Doing this skip here means that during `azd up` we don't show output like:
// /* cSpell:disable */
//
// Packaging services (azd package)
//
// (✓) Done: Packaging service basketservice
// - Package Output: /var/folders/6n/sxbj12js5ksg6ztn0kslqp400000gn/T/azd472091284
//
// (✓) Done: Packaging service catalogservice
// - Package Output: /var/folders/6n/sxbj12js5ksg6ztn0kslqp400000gn/T/azd2265185954
//
// (✓) Done: Packaging service frontend
// - Package Output: /var/folders/6n/sxbj12js5ksg6ztn0kslqp400000gn/T/azd2956031596
//
// /* cSpell:enable */
// Which is nice - since the above is not the package that we publish (instead it's the raw output of
// `dotnet publish`, as if you were going to run on App Service.).
//
// With .NET 8, we'll be able to build just the container image, by setting ContainerArchiveOutputPath
// as a property when we run `dotnet publish`. If we set this to the filepath of a tgz (doesn't need to exist)
// the the action will just produce a container image and save it to that tgz, as `docker save` would have. It will
// not push the container image.
//
// It's probably right for us to think about "package" for a containerized application as meaning "produce the tgz"
// of the image, as would be done by `docker save` and then do this for both DotNetContainerAppTargets and
// ContainerAppTargets.
if svc.Host == project.DotNetContainerAppTarget {
continue
}

stepMessage := fmt.Sprintf("Packaging service %s", svc.Name)
pa.console.ShowSpinner(ctx, stepMessage, input.Step)
projectEventArgs := project.ProjectLifecycleEventArgs{
Project: pa.projectConfig,
}

// Skip this service if both cases are true:
// 1. The user specified a service name
// 2. This service is not the one the user specified
if targetServiceName != "" && targetServiceName != svc.Name {
pa.console.StopSpinner(ctx, stepMessage, input.StepSkipped)
continue
}
packageResults := map[string]*project.ServicePackageResult{}

options := &project.PackageOptions{OutputPath: pa.flags.outputPath}
packageResult, err := async.RunWithProgress(
func(packageProgress project.ServiceProgress) {
progressMessage := fmt.Sprintf("Packaging service %s (%s)", svc.Name, packageProgress.Message)
pa.console.ShowSpinner(ctx, progressMessage, input.Step)
},
func(progress *async.Progress[project.ServiceProgress]) (*project.ServicePackageResult, error) {
return pa.serviceManager.Package(ctx, svc, nil, progress, options)
},
)
pa.console.StopSpinner(ctx, stepMessage, input.GetStepResultFormat(err))

if err != nil {
return nil, err
}
packageResults[svc.Name] = packageResult
err = pa.projectConfig.Invoke(ctx, project.ProjectEventPackage, projectEventArgs, func() error {
for index, svc := range serviceTable {
// TODO(ellismg): We need to figure out what packaging an containerized dotnet app means. For now, just skip it.
// We "package" the app during deploy when we call `dotnet publish /p:PublishProfile=DefaultContainer` to build
// and push the container image.
//
// Doing this skip here means that during `azd up` we don't show output like:
// /* cSpell:disable */
//
// Packaging services (azd package)
//
// (✓) Done: Packaging service basketservice
// - Package Output: /var/folders/6n/sxbj12js5ksg6ztn0kslqp400000gn/T/azd472091284
//
// (✓) Done: Packaging service catalogservice
// - Package Output: /var/folders/6n/sxbj12js5ksg6ztn0kslqp400000gn/T/azd2265185954
//
// (✓) Done: Packaging service frontend
// - Package Output: /var/folders/6n/sxbj12js5ksg6ztn0kslqp400000gn/T/azd2956031596
//
// /* cSpell:enable */
// Which is nice - since the above is not the package that we publish (instead it's the raw output of
// `dotnet publish`, as if you were going to run on App Service.).
//
// With .NET 8, we'll be able to build just the container image, by setting ContainerArchiveOutputPath
// as a property when we run `dotnet publish`. If we set this to the filepath of a tgz (doesn't need to exist)
// the the action will just produce a container image and save it to that tgz, as `docker save` would have.
// It will not push the container image.
//
// It's probably right for us to think about "package" for a containerized application as meaning
// "produce the tgz" of the image, as would be done by `docker save` and then do this for both
// DotNetContainerAppTargets and ContainerAppTargets.
if svc.Host == project.DotNetContainerAppTarget {
continue
}

stepMessage := fmt.Sprintf("Packaging service %s", svc.Name)
pa.console.ShowSpinner(ctx, stepMessage, input.Step)

// report package output
pa.console.MessageUxItem(ctx, packageResult.Artifacts)
if index < serviceCount-1 {
pa.console.Message(ctx, "")
// Skip this service if both cases are true:
// 1. The user specified a service name
// 2. This service is not the one the user specified
if targetServiceName != "" && targetServiceName != svc.Name {
pa.console.StopSpinner(ctx, stepMessage, input.StepSkipped)
continue
}

options := &project.PackageOptions{OutputPath: pa.flags.outputPath}
packageResult, err := async.RunWithProgress(
func(packageProgress project.ServiceProgress) {
progressMessage := fmt.Sprintf("Packaging service %s (%s)", svc.Name, packageProgress.Message)
pa.console.ShowSpinner(ctx, progressMessage, input.Step)
},
func(progress *async.Progress[project.ServiceProgress]) (*project.ServicePackageResult, error) {
return pa.serviceManager.Package(ctx, svc, nil, progress, options)
},
)
pa.console.StopSpinner(ctx, stepMessage, input.GetStepResultFormat(err))

if err != nil {
return err
}
packageResults[svc.Name] = packageResult

// report package output
pa.console.MessageUxItem(ctx, packageResult.Artifacts)
if index < serviceCount-1 {
pa.console.Message(ctx, "")
}
}

return nil
})

if err != nil {
return nil, err
}

if pa.formatter.Kind() == output.JsonFormat {
Expand Down
74 changes: 45 additions & 29 deletions cli/azd/cmd/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,44 +154,60 @@ func (ra *restoreAction) Run(ctx context.Context) (*actions.ActionResult, error)
return nil, err
}

restoreResults := map[string]*project.ServiceRestoreResult{}
stableServices, err := ra.importManager.ServiceStable(ctx, ra.projectConfig)
if err != nil {
return nil, err
}

for _, svc := range stableServices {
stepMessage := fmt.Sprintf("Restoring service %s", svc.Name)
ra.console.ShowSpinner(ctx, stepMessage, input.Step)
projectEventArgs := project.ProjectLifecycleEventArgs{
Project: ra.projectConfig,
}

// Skip this service if both cases are true:
// 1. The user specified a service name
// 2. This service is not the one the user specified
if targetServiceName != "" && targetServiceName != svc.Name {
ra.console.StopSpinner(ctx, stepMessage, input.StepSkipped)
continue
}
restoreResults := map[string]*project.ServiceRestoreResult{}

// Initialize service context for restore operation
serviceContext := &project.ServiceContext{}

restoreResult, err := async.RunWithProgress(
func(buildProgress project.ServiceProgress) {
progressMessage := fmt.Sprintf("Building service %s (%s)", svc.Name, buildProgress.Message)
ra.console.ShowSpinner(ctx, progressMessage, input.Step)
},
func(progress *async.Progress[project.ServiceProgress]) (*project.ServiceRestoreResult, error) {
return ra.serviceManager.Restore(ctx, svc, serviceContext, progress)
},
)

if err != nil {
ra.console.StopSpinner(ctx, stepMessage, input.StepFailed)
return nil, err
err = ra.projectConfig.Invoke(ctx, project.ProjectEventRestore, projectEventArgs, func() error {
for _, svc := range stableServices {
stepMessage := fmt.Sprintf("Restoring service %s", svc.Name)
ra.console.ShowSpinner(ctx, stepMessage, input.Step)

// Skip this service if both cases are true:
// 1. The user specified a service name
// 2. This service is not the one the user specified
if targetServiceName != "" && targetServiceName != svc.Name {
ra.console.StopSpinner(ctx, stepMessage, input.StepSkipped)
continue
}

// Initialize service context for restore operation
serviceContext := &project.ServiceContext{}

restoreResult, err := async.RunWithProgress(
func(buildProgress project.ServiceProgress) {
progressMessage := fmt.Sprintf("Building service %s (%s)", svc.Name, buildProgress.Message)
ra.console.ShowSpinner(ctx, progressMessage, input.Step)
},
func(progress *async.Progress[project.ServiceProgress]) (*project.ServiceRestoreResult, error) {
return ra.serviceManager.Restore(ctx, svc, serviceContext, progress)
},
)

if err != nil {
ra.console.StopSpinner(ctx, stepMessage, input.StepFailed)
return err
}

ra.console.StopSpinner(ctx, stepMessage, input.StepDone)
restoreResults[svc.Name] = restoreResult

// report restore output
ra.console.MessageUxItem(ctx, restoreResult.Artifacts)
}

ra.console.StopSpinner(ctx, stepMessage, input.StepDone)
restoreResults[svc.Name] = restoreResult
return nil
})

if err != nil {
return nil, err
}

if ra.formatter.Kind() == output.JsonFormat {
Expand Down
32 changes: 29 additions & 3 deletions cli/azd/extensions/microsoft.azd.demo/internal/cmd/listen.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,43 @@ func newListenCommand() *cobra.Command {

return nil
}).
WithProjectEventHandler("predeploy", func(ctx context.Context, args *azdext.ProjectEventArgs) error {
for i := 1; i <= 20; i++ {
fmt.Printf("%d. Doing important predeploy project work in extension...\n", i)
time.Sleep(250 * time.Millisecond)
}

return nil
}).
WithProjectEventHandler("postdeploy", func(ctx context.Context, args *azdext.ProjectEventArgs) error {
for i := 1; i <= 20; i++ {
fmt.Printf("%d. Doing important postdeploy project work in extension...\n", i)
time.Sleep(250 * time.Millisecond)
}

return nil
}).
WithServiceEventHandler("prepackage", func(ctx context.Context, args *azdext.ServiceEventArgs) error {
for i := 1; i <= 20; i++ {
fmt.Printf("%d. Doing important work in extension...\n", i)
fmt.Printf("%d. Doing important prepackage service work in extension...\n", i)
time.Sleep(250 * time.Millisecond)
}

return nil
}, &azdext.ServerEventOptions{
// Optionally filter your subscription by service host and/or language
Host: "containerapp",
}).
WithServiceEventHandler("postpackage", func(ctx context.Context, args *azdext.ServiceEventArgs) error {
for i := 1; i <= 20; i++ {
fmt.Printf("%d. Doing important postpackage service work in extension...\n", i)
time.Sleep(250 * time.Millisecond)
}

return nil
}, &azdext.ServerEventOptions{
// Optionally filter your subscription by service host and/or language
Host: "containerapp",
Language: "python",
Host: "containerapp",
})

// Start listening for events
Expand Down
Loading
Loading