Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aspire: Support Azure Functions Prototype #4252

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
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
2 changes: 2 additions & 0 deletions cli/azd/cmd/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/account"
"github.com/azure/azure-dev/cli/azd/pkg/ai"
"github.com/azure/azure-dev/cli/azd/pkg/alpha"
"github.com/azure/azure-dev/cli/azd/pkg/appservice"
"github.com/azure/azure-dev/cli/azd/pkg/auth"
"github.com/azure/azure-dev/cli/azd/pkg/azapi"
"github.com/azure/azure-dev/cli/azd/pkg/azd"
Expand Down Expand Up @@ -571,6 +572,7 @@ func registerCommonDependencies(container *ioc.NestedContainer) {
container.MustRegisterSingleton(entraid.NewEntraIdService)
container.MustRegisterSingleton(azcli.NewContainerRegistryService)
container.MustRegisterSingleton(containerapps.NewContainerAppService)
container.MustRegisterSingleton(appservice.NewFunctionAppService)
container.MustRegisterSingleton(containerregistry.NewRemoteBuildManager)
container.MustRegisterSingleton(keyvault.NewKeyVaultService)
container.MustRegisterSingleton(storage.NewFileShareService)
Expand Down
105 changes: 104 additions & 1 deletion cli/azd/pkg/apphost/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,24 @@ func Dockerfiles(manifest *Manifest) map[string]genDockerfile {
return res
}

// Functions returns information about all function.v0 resources from a manifest.
func Functions(manifest *Manifest) map[string]genFunction {
res := make(map[string]genFunction)

for name, comp := range manifest.Resources {
switch comp.Type {
case "function.v0":
res[name] = genFunction{
Path: *comp.Path,
Env: comp.Env,
Bindings: comp.Bindings,
}
}
}

return res
}

// Containers returns information about all container.v0 resources from a manifest.
func Containers(manifest *Manifest) map[string]genContainer {
res := make(map[string]genContainer)
Expand Down Expand Up @@ -226,6 +244,32 @@ func ContainerAppManifestTemplateForProject(
return buf.String(), nil
}

// FunctionAppManifestTemplateForProject returns the function app manifest template for a given project.
// It can be used (after evaluation) to deploy the service to a container app environment.
func FunctionAppManifestTemplateForProject(
manifest *Manifest, projectName string, options AppHostOptions) (string, error) {
generator := newInfraGenerator()

if err := generator.LoadManifest(manifest); err != nil {
return "", err
}

if err := generator.Compile(); err != nil {
return "", err
}

var buf bytes.Buffer

tmplCtx := generator.containerAppTemplateContexts[projectName]

err := genTemplates.ExecuteTemplate(&buf, "funcApp.tmpl.yaml", tmplCtx)
if err != nil {
return "", fmt.Errorf("executing template: %w", err)
}

return buf.String(), nil
}

// BicepTemplate returns a filesystem containing the generated bicep files for the given manifest. These files represent
// the shared infrastructure that would normally be under the `infra/` folder for the given manifest.
func BicepTemplate(name string, manifest *Manifest, options AppHostOptions) (*memfs.FS, error) {
Expand Down Expand Up @@ -433,6 +477,7 @@ type infraGenerator struct {
containers map[string]genContainer
dapr map[string]genDapr
dockerfiles map[string]genDockerfile
functions map[string]genFunction
projects map[string]genProject
connectionStrings map[string]string
// keeps the value from value.v0 resources if provided.
Expand All @@ -449,6 +494,7 @@ func newInfraGenerator() *infraGenerator {
return &infraGenerator{
bicepContext: genBicepTemplateContext{
ContainerAppEnvironmentServices: make(map[string]genContainerAppEnvironmentServices),
Functions: make([]string, 0),
KeyVaults: make(map[string]genKeyVault),
ContainerApps: make(map[string]genContainerApp),
DaprComponents: make(map[string]genDaprComponent),
Expand All @@ -460,6 +506,7 @@ func newInfraGenerator() *infraGenerator {
containers: make(map[string]genContainer),
dapr: make(map[string]genDapr),
dockerfiles: make(map[string]genDockerfile),
functions: make(map[string]genFunction),
projects: make(map[string]genProject),
connectionStrings: make(map[string]string),
resourceTypes: make(map[string]string),
Expand Down Expand Up @@ -560,6 +607,8 @@ func (b *infraGenerator) LoadManifest(m *Manifest) error {
if err != nil {
return err
}
case "function.v0":
b.addFunctionApp(name, *comp.Path, comp.Env, comp.Bindings)
case "dockerfile.v0":
b.addDockerfile(name, *comp.Path, *comp.Context, comp.Env, comp.Bindings, comp.BuildArgs, comp.Args)
case "parameter.v0":
Expand Down Expand Up @@ -603,6 +652,11 @@ func (b *infraGenerator) requireContainerRegistry() {
b.bicepContext.HasContainerRegistry = true
}

func (b *infraGenerator) requireAppInsights() {
b.requireLogAnalyticsWorkspace()
b.bicepContext.HasAppInsights = true
}

func (b *infraGenerator) requireDaprStore() string {
daprStoreName := "daprStore"

Expand Down Expand Up @@ -1053,6 +1107,22 @@ func (b *infraGenerator) addDockerfile(
}
}

func (b *infraGenerator) addFunctionApp(name string, path string, env map[string]string,
bindings custommaps.WithOrder[Binding]) {
b.requireCluster()
b.requireContainerRegistry()
b.requireAppInsights()

app := genFunction{
Path: path,
Env: env,
Bindings: bindings,
}

b.bicepContext.Functions = append(b.bicepContext.Functions, name)
b.functions[name] = app
}

// singleQuotedStringRegex is a regular expression pattern used to match single-quoted strings.
var singleQuotedStringRegex = regexp.MustCompile(`'[^']*'`)
var propertyNameRegex = regexp.MustCompile(`'([^']*)':`)
Expand Down Expand Up @@ -1087,6 +1157,16 @@ func (b *infraGenerator) compileIngress() error {
ingressBindings: bindingsFromIngress,
}
}
for name, function := range b.functions {
ingress, bindingsFromIngress, err := buildAcaIngress(function.Bindings, 80)
if err != nil {
return fmt.Errorf("configuring ingress for resource %s: %w", name, err)
}
result[name] = ingressDetails{
ingress: ingress,
ingressBindings: bindingsFromIngress,
}
}
for name, project := range b.projects {
ingress, bindingsFromIngress, err := buildAcaIngress(project.Bindings, 8080)
if err != nil {
Expand Down Expand Up @@ -1214,6 +1294,22 @@ func (b *infraGenerator) Compile() error {
b.containerAppTemplateContexts[resourceName] = projectTemplateCtx
}

for resourceName, funcapp := range b.functions {
projectTemplateCtx := genContainerAppManifestTemplateContext{
Name: resourceName,
Env: make(map[string]string),
Secrets: make(map[string]string),
KeyVaultSecrets: make(map[string]string),
Ingress: b.allServicesIngress[resourceName].ingress,
}

if err := b.buildEnvBlock(funcapp.Env, &projectTemplateCtx); err != nil {
return fmt.Errorf("configuring environment for resource %s: %w", resourceName, err)
}

b.containerAppTemplateContexts[resourceName] = projectTemplateCtx
}

for resourceName, project := range b.projects {
projectTemplateCtx := genContainerAppManifestTemplateContext{
Name: resourceName,
Expand Down Expand Up @@ -1373,7 +1469,11 @@ func (b infraGenerator) evalBindingRef(v string, emitType inputEmitType) (string
}

switch {
case targetType == "project.v0" || targetType == "container.v0" || targetType == "dockerfile.v0":
case targetType == "project.v0" ||
targetType == "container.v0" ||
targetType == "dockerfile.v0" ||
targetType == "function.v0":

if !strings.HasPrefix(prop, "bindings.") {
return "", fmt.Errorf("unsupported property referenced in binding expression: %s for %s", prop, targetType)
}
Expand All @@ -1399,6 +1499,9 @@ func (b infraGenerator) evalBindingRef(v string, emitType inputEmitType) (string
} else if targetType == "dockerfile.v0" {
bindings := b.dockerfiles[resource].Bindings
binding, has = bindings.Get(bindingName)
} else if targetType == "function.v0" {
bindings := b.functions[resource].Bindings
binding, has = bindings.Get(bindingName)
}

if !has {
Expand Down
8 changes: 8 additions & 0 deletions cli/azd/pkg/apphost/generate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ type genBuildContainerDetails struct {
Secrets map[string]ContainerV1BuildSecrets
}

type genFunction struct {
Path string
Env map[string]string
Bindings custommaps.WithOrder[Binding]
}

type genProject struct {
Path string
Env map[string]string
Expand Down Expand Up @@ -122,9 +128,11 @@ type genBicepTemplateContext struct {
HasContainerEnvironment bool
HasDaprStore bool
HasLogAnalyticsWorkspace bool
HasAppInsights bool
RequiresPrincipalId bool
RequiresStorageVolume bool
HasBindMounts bool
Functions []string
KeyVaults map[string]genKeyVault
ContainerAppEnvironmentServices map[string]genContainerAppEnvironmentServices
ContainerApps map[string]genContainerApp
Expand Down
Loading
Loading