Skip to content

Commit

Permalink
WIP: aspire: Control bicep vs yaml via alpha flag
Browse files Browse the repository at this point in the history
This change moves using bicep instead of yaml for container apps for
aspire projects behind the alpha flag
`aspire.useBicepForContainerApps`. To enable this, run `azd config set
alpha.aspire.useBicepForContainerApps on`.

Also adds support for using bicep when `infra synth` had not yet been run.
  • Loading branch information
ellismg committed Sep 6, 2024
1 parent 248d943 commit e20da6e
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 428 deletions.
5 changes: 4 additions & 1 deletion cli/azd/cmd/infra_synth.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func newInfraSynthAction(
}

var infraSynthFeature = alpha.MustFeatureKey("infraSynth")
var bicepForContainerAppFeature = alpha.MustFeatureKey("aspire.useBicepForContainerApps")

func (a *infraSynthAction) Run(ctx context.Context) (*actions.ActionResult, error) {
if !a.alphaManager.IsEnabled(infraSynthFeature) {
Expand All @@ -93,7 +94,9 @@ func (a *infraSynthAction) Run(ctx context.Context) (*actions.ActionResult, erro
spinnerMessage := "Synthesizing infrastructure"

a.console.ShowSpinner(ctx, spinnerMessage, input.Step)
synthFS, err := a.importManager.SynthAllInfrastructure(ctx, a.projectConfig)
synthFS, err := a.importManager.SynthAllInfrastructure(ctx, a.projectConfig, &project.SynthOptions{
UseBicepForContainerApps: a.alphaManager.IsEnabled(bicepForContainerAppFeature),
})
if err != nil {
a.console.StopSpinner(ctx, spinnerMessage, input.StepFailed)
return nil, err
Expand Down
54 changes: 33 additions & 21 deletions cli/azd/pkg/project/dotnet_importer.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ func evaluateSingleExpressionMatch(
}

func (ai *DotNetImporter) SynthAllInfrastructure(
ctx context.Context, p *ProjectConfig, svcConfig *ServiceConfig,
ctx context.Context, p *ProjectConfig, svcConfig *ServiceConfig, useBicepForContainerApps bool,
) (fs.FS, error) {
manifest, err := ai.ReadManifest(ctx, svcConfig)
if err != nil {
Expand Down Expand Up @@ -511,17 +511,6 @@ func (ai *DotNetImporter) SynthAllInfrastructure(
// manifest is written to a file name "containerApp.tmpl.yaml" in the same directory as the project that produces the
// container we will deploy.
writeManifestForResource := func(name string) error {
containerAppManifest, err := apphost.ContainerAppManifestTemplateForProject(
manifest, name, apphost.AppHostOptions{})
if err != nil {
return fmt.Errorf("generating containerApp.tmpl.yaml for resource %s: %w", name, err)
}

bicepManifest, err := apphost.BicepModuleForProject(manifest, name, apphost.AppHostOptions{})
if err != nil {
return fmt.Errorf("generating bicep module for resource %s: %w", name, err)
}

normalPath, err := filepath.EvalSymlinks(svcConfig.Path())
if err != nil {
return err
Expand All @@ -532,19 +521,42 @@ func (ai *DotNetImporter) SynthAllInfrastructure(
return err
}

manifestPath := filepath.Join(filepath.Dir(projectRelPath), "infra", fmt.Sprintf("%s.tmpl.yaml", name))
bicepPath := filepath.Join(filepath.Dir(projectRelPath), "infra", fmt.Sprintf("%s.bicep", name))
if useBicepForContainerApps {
bicepManifest, err := apphost.BicepModuleForProject(manifest, name, apphost.AppHostOptions{})
if err != nil {
return fmt.Errorf("generating bicep module for resource %s: %w", name, err)
}

if err := generatedFS.MkdirAll(filepath.Dir(manifestPath), osutil.PermissionDirectoryOwnerOnly); err != nil {
return err
}
bicepPath := filepath.Join(filepath.Dir(projectRelPath), "infra", fmt.Sprintf("%s.bicep", name))

err = generatedFS.WriteFile(manifestPath, []byte(containerAppManifest), osutil.PermissionFileOwnerOnly)
if err != nil {
return err
if err := generatedFS.MkdirAll(filepath.Dir(bicepPath), osutil.PermissionDirectoryOwnerOnly); err != nil {
return err
}

err = generatedFS.WriteFile(bicepPath, []byte(bicepManifest), osutil.PermissionFileOwnerOnly)
if err != nil {
return err
}
} else {
containerAppManifest, err := apphost.ContainerAppManifestTemplateForProject(
manifest, name, apphost.AppHostOptions{})
if err != nil {
return fmt.Errorf("generating containerApp.tmpl.yaml for resource %s: %w", name, err)
}

manifestPath := filepath.Join(filepath.Dir(projectRelPath), "infra", fmt.Sprintf("%s.tmpl.yaml", name))

if err := generatedFS.MkdirAll(filepath.Dir(manifestPath), osutil.PermissionDirectoryOwnerOnly); err != nil {
return err
}

err = generatedFS.WriteFile(manifestPath, []byte(containerAppManifest), osutil.PermissionFileOwnerOnly)
if err != nil {
return err
}
}

return generatedFS.WriteFile(bicepPath, []byte(bicepManifest), osutil.PermissionFileOwnerOnly)
return nil
}

for name := range apphost.ProjectPaths(manifest) {
Expand Down
14 changes: 12 additions & 2 deletions cli/azd/pkg/project/importer.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ type ImportManager struct {
dotNetImporter *DotNetImporter
}

type SynthOptions struct {
UseBicepForContainerApps bool
}

func NewImportManager(dotNetImporter *DotNetImporter) *ImportManager {
return &ImportManager{
dotNetImporter: dotNetImporter,
Expand Down Expand Up @@ -180,14 +184,20 @@ func pathHasModule(path, module string) (bool, error) {

}

func (im *ImportManager) SynthAllInfrastructure(ctx context.Context, projectConfig *ProjectConfig) (fs.FS, error) {
func (im *ImportManager) SynthAllInfrastructure(
ctx context.Context, projectConfig *ProjectConfig, options *SynthOptions,
) (fs.FS, error) {
if options == nil {
options = &SynthOptions{}
}

for _, svcConfig := range projectConfig.Services {
if svcConfig.Language == ServiceLanguageDotNet {
if len(projectConfig.Services) != 1 {
return nil, errNoMultipleServicesWithAppHost
}

return im.dotNetImporter.SynthAllInfrastructure(ctx, projectConfig, svcConfig)
return im.dotNetImporter.SynthAllInfrastructure(ctx, projectConfig, svcConfig, options.UseBicepForContainerApps)
}
}

Expand Down
60 changes: 52 additions & 8 deletions cli/azd/pkg/project/service_target_dotnet_containerapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/url"
"os"
Expand All @@ -31,6 +32,8 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/tools/dotnet"
)

var bicepForContainerAppFeature = alpha.MustFeatureKey("aspire.useBicepForContainerApps")

type dotnetContainerAppTarget struct {
env *environment.Environment
containerHelper *ContainerHelper
Expand Down Expand Up @@ -174,8 +177,10 @@ func (at *dotnetContainerAppTarget) Deploy(

progress.SetProgress(NewServiceProgress("Updating container app"))

useBicepForContainerApps := at.alphaFeatureManager.IsEnabled(bicepForContainerAppFeature)

var manifest string
var bicepTemplate *azure.RawArmTemplate
var armTemplate *azure.RawArmTemplate

appHostRoot := serviceConfig.DotNetContainerApp.AppHostPath
if f, err := os.Stat(appHostRoot); err == nil && !f.IsDir() {
Expand All @@ -187,22 +192,22 @@ func (at *dotnetContainerAppTarget) Deploy(

bicepPath := filepath.Join(appHostRoot, "infra", fmt.Sprintf("%s.bicep", serviceConfig.DotNetContainerApp.ProjectName))

if _, err := os.Stat(bicepPath); err == nil {
if _, err := os.Stat(bicepPath); err == nil && useBicepForContainerApps {
log.Printf("using container app manifest from %s", bicepPath)
res, err := at.bicepCli.Build(ctx, bicepPath)
if err != nil {
return nil, fmt.Errorf("building container app bicep: %w", err)
}
bicepTemplate = to.Ptr(azure.RawArmTemplate(res.Compiled))
} else if _, err := os.Stat(manifestPath); err == nil {
armTemplate = to.Ptr(azure.RawArmTemplate(res.Compiled))
} else if _, err := os.Stat(manifestPath); err == nil && !useBicepForContainerApps {
log.Printf("using container app manifest from %s", manifestPath)

contents, err := os.ReadFile(manifestPath)
if err != nil {
return nil, fmt.Errorf("reading container app manifest: %w", err)
}
manifest = string(contents)
} else {
} else if !useBicepForContainerApps {
log.Printf(
"generating container app manifest from %s for project %s",
serviceConfig.DotNetContainerApp.AppHostPath,
Expand All @@ -217,6 +222,45 @@ func (at *dotnetContainerAppTarget) Deploy(
return nil, fmt.Errorf("generating container app manifest: %w", err)
}
manifest = generatedManifest
} else {
log.Printf(
"generating container app bicep from %s for project %s",
serviceConfig.DotNetContainerApp.AppHostPath,
serviceConfig.DotNetContainerApp.ProjectName)

generatedBicep, err := apphost.BicepModuleForProject(
serviceConfig.DotNetContainerApp.Manifest,
serviceConfig.DotNetContainerApp.ProjectName,
apphost.AppHostOptions{},
)
if err != nil {
return nil, fmt.Errorf("generating container app manifest: %w", err)
}

compiled, err := func() (azure.RawArmTemplate, error) {
f, err := os.CreateTemp("", fmt.Sprintf("azd-bicep-%s.bicep", serviceConfig.DotNetContainerApp.ProjectName))
if err != nil {
return azure.RawArmTemplate{}, fmt.Errorf("creating temporary file: %w", err)
}
defer func() {
_ = f.Close()
_ = os.Remove(f.Name())
}()
_, err = io.Copy(f, strings.NewReader(generatedBicep))
if err != nil {
return azure.RawArmTemplate{}, fmt.Errorf("writing bicep file: %w", err)
}

res, err := at.bicepCli.Build(ctx, f.Name())
if err != nil {
return azure.RawArmTemplate{}, fmt.Errorf("building container app bicep: %w", err)
}
return azure.RawArmTemplate(res.Compiled), nil
}()
if err != nil {
return nil, err
}
armTemplate = &compiled
}

fns := &containerAppTemplateManifestFuncs{
Expand Down Expand Up @@ -255,9 +299,9 @@ func (at *dotnetContainerAppTarget) Deploy(
inputs = make(map[string]any)
}

if bicepTemplate != nil {
if armTemplate != nil {
var parsed *azure.ArmTemplate
if err := json.Unmarshal(*bicepTemplate, &parsed); err != nil {
if err := json.Unmarshal(*armTemplate, &parsed); err != nil {
return nil, fmt.Errorf("parsing arm template: %w", err)
}

Expand Down Expand Up @@ -313,7 +357,7 @@ func (at *dotnetContainerAppTarget) Deploy(
at.env.GetSubscriptionId(),
targetResource.ResourceGroupName(),
at.deploymentService.GenerateDeploymentName(serviceConfig.Name),
*bicepTemplate,
*armTemplate,
params,
nil)
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions cli/azd/resources/alpha_features.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@
description: "Do not change Ingress Session Affinity when deploying Azure Container Apps."
- id: deployment.stacks
description: "Enables Azure deployment stacks for ARM/Bicep based deployments."
- id: aspire.useBicepForContainerApps
description: "Use bicep for container app deployments instead of yaml."

This file was deleted.

Loading

0 comments on commit e20da6e

Please sign in to comment.