Skip to content
Draft
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
81 changes: 58 additions & 23 deletions cli/azd/internal/vsrpc/environment_service_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ import (
"path/filepath"
"strings"

"github.com/azure/azure-dev/cli/azd/internal/names"
"github.com/azure/azure-dev/cli/azd/pkg/apphost"
"github.com/azure/azure-dev/cli/azd/pkg/environment"
"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/pkg/project"
"github.com/azure/azure-dev/cli/azd/pkg/tools/dotnet"
)

Expand Down Expand Up @@ -64,32 +66,65 @@ func (s *environmentService) CreateEnvironmentAsync(
// If an azure.yaml doesn't already exist, we need to create one. Creating an environment implies initializing the
// azd project if it does not already exist.
if _, err := os.Stat(c.azdContext.ProjectPath()); errors.Is(err, fs.ErrNotExist) {
_ = observer.OnNext(ctx, newImportantProgressMessage("Analyzing Aspire Application (this might take a moment...)"))

manifest, err := apphost.ManifestFromAppHost(ctx, rc.HostProjectPath, c.dotnetCli, dotnetEnv)
if err != nil {
return false, fmt.Errorf("reading app host manifest: %w", err)
}

projectName := azdcontext.ProjectName(strings.TrimSuffix(c.azdContext.ProjectDirectory(), ".AppHost"))

// Write an azure.yaml file to the project.
files, err := apphost.GenerateProjectArtifacts(
ctx,
c.azdContext.ProjectDirectory(),
projectName,
manifest,
rc.HostProjectPath,
)
isAspire, err := c.dotnetCli.IsAspireHostProject(ctx, rc.HostProjectPath)
if err != nil {
return false, fmt.Errorf("generating project artifacts: %w", err)
return false, fmt.Errorf("checking if %s is an app host project: %w", rc.HostProjectPath, err)
}

file := files["azure.yaml"]
projectFilePath := filepath.Join(c.azdContext.ProjectDirectory(), "azure.yaml")

if err := os.WriteFile(projectFilePath, []byte(file.Contents), osutil.PermissionFile); err != nil {
return false, fmt.Errorf("writing azure.yaml: %w", err)
if isAspire {
_ = observer.OnNext(ctx,
newImportantProgressMessage("Analyzing Aspire Application (this might take a moment...)"))

manifest, err := apphost.ManifestFromAppHost(ctx, rc.HostProjectPath, c.dotnetCli, dotnetEnv)
if err != nil {
return false, fmt.Errorf("reading app host manifest: %w", err)
}

projectName := azdcontext.ProjectName(strings.TrimSuffix(c.azdContext.ProjectDirectory(), ".AppHost"))

// Write an azure.yaml file to the project.
files, err := apphost.GenerateProjectArtifacts(
ctx,
c.azdContext.ProjectDirectory(),
projectName,
manifest,
rc.HostProjectPath,
)
if err != nil {
return false, fmt.Errorf("generating project artifacts: %w", err)
}

file := files["azure.yaml"]
projectFilePath := filepath.Join(c.azdContext.ProjectDirectory(), "azure.yaml")

if err := os.WriteFile(projectFilePath, []byte(file.Contents), osutil.PermissionFile); err != nil {
return false, fmt.Errorf("writing azure.yaml: %w", err)
}
} else {
rel, err := filepath.Rel(c.azdContext.ProjectDirectory(), rc.HostProjectPath)
if err != nil {
return false, fmt.Errorf("determining relative path: %w", err)
}

projectName := azdcontext.ProjectName(c.azdContext.ProjectDirectory())

ext := filepath.Ext(rc.HostProjectPath)
serviceName := names.LabelName(strings.TrimSuffix(filepath.Base(rc.HostProjectPath), ext))

prjConfig := project.ProjectConfig{
Name: projectName,
Services: map[string]*project.ServiceConfig{
serviceName: {
Name: serviceName,
RelativePath: fmt.Sprintf("./%s", filepath.ToSlash(rel)),
},
},
}

err = project.Save(ctx, &prjConfig, c.azdContext.ProjectPath())
if err != nil {
return false, fmt.Errorf("saving project config: %w", err)
}
}
} else if err != nil {
return false, fmt.Errorf("checking for project: %w", err)
Expand Down
15 changes: 9 additions & 6 deletions cli/azd/internal/vsrpc/environment_service_load.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,17 @@ func (s *environmentService) loadEnvironmentAsync(
appHost, err := appHostForProject(ctx, c.projectConfig, c.dotnetCli)
if err != nil {
return nil, fmt.Errorf("failed to find Aspire app host: %w", err)
}
} else if appHost != nil {
manifest, err := c.dotnetImporter.ReadManifest(ctx, appHost)
if err != nil {
return nil, fmt.Errorf("reading app host manifest: %w", err)
}

manifest, err := c.dotnetImporter.ReadManifest(ctx, appHost)
if err != nil {
return nil, fmt.Errorf("reading app host manifest: %w", err)
}
ret.Services = servicesFromManifest(manifest)

ret.Services = servicesFromManifest(manifest)
return ret, nil
}

ret.Services = servicesFromProjectConfig(ctx, c.projectConfig)
return ret, nil
}
34 changes: 25 additions & 9 deletions cli/azd/internal/vsrpc/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,25 @@ import (
)

// appHostServiceForProject returns the ServiceConfig of the service for the AppHost project for the given azd project.
//
// If the project does not have an AppHost project, nil is returned.
func appHostForProject(
ctx context.Context, pc *project.ProjectConfig, dotnetCli *dotnet.Cli,
) (*project.ServiceConfig, error) {
for _, service := range pc.Services {
if service.Language == project.ServiceLanguageDotNet {
isAppHost, err := dotnetCli.IsAspireHostProject(ctx, service.Path())
if err != nil {
log.Printf("error checking if %s is an app host project: %v", service.Path(), err)
return nil, fmt.Errorf("error checking if %s is an app host project: %w", service.Path(), err)
}

if isAppHost {
return service, nil
}
}
}

return nil, fmt.Errorf("no app host project found for project: %s", pc.Name)
return nil, nil
}

func servicesFromManifest(manifest *apphost.Manifest) []*Service {
Expand All @@ -50,6 +53,19 @@ func servicesFromManifest(manifest *apphost.Manifest) []*Service {
return services
}

func servicesFromProjectConfig(ctx context.Context, pc *project.ProjectConfig) []*Service {
var services []*Service

for _, service := range pc.Services {
services = append(services, &Service{
Name: service.Name,
Path: service.Path(),
})
}

return services
}

// azdContext resolves the azd context directory to use.
//
// - If the host project directory contains azure.yaml, the host project directory is used.
Expand Down Expand Up @@ -77,16 +93,16 @@ func azdContext(hostProjectPath string) (*azdcontext.AzdContext, error) {
return nil, err
}

found := false
for _, svc := range prjConfig.Services {
if svc.Language == project.ServiceLanguageDotNet && svc.Host == project.ContainerAppTarget {
if svc.Path() != hostProjectPath {
log.Printf("ignoring %s due to mismatch, using app host directory", azdCtx.ProjectPath())
return azdcontext.NewAzdContextWithDirectory(hostProjectDir), nil
}
if svc.Path() == hostProjectPath {
found = true
}
}

// there can only be one app host project
break
if !found {
log.Printf("ignoring %s due to non-matching project found, using app host directory", azdCtx.ProjectPath())
return azdcontext.NewAzdContextWithDirectory(hostProjectDir), nil
}

log.Printf("use nearest directory: %s", azdCtx.ProjectDirectory())
Expand Down
Loading