cli/azd/pkg/project/service_target_containerapp.go (145 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package project import ( "context" "fmt" "strconv" "github.com/azure/azure-dev/cli/azd/pkg/async" "github.com/azure/azure-dev/cli/azd/pkg/azapi" "github.com/azure/azure-dev/cli/azd/pkg/azure" "github.com/azure/azure-dev/cli/azd/pkg/containerapps" "github.com/azure/azure-dev/cli/azd/pkg/environment" "github.com/azure/azure-dev/cli/azd/pkg/tools" ) type containerAppTarget struct { env *environment.Environment envManager environment.Manager containerHelper *ContainerHelper containerAppService containerapps.ContainerAppService resourceManager ResourceManager } // NewContainerAppTarget creates the container app service target. // // The target resource can be partially filled with only ResourceGroupName, since container apps // can be provisioned during deployment. func NewContainerAppTarget( env *environment.Environment, envManager environment.Manager, containerHelper *ContainerHelper, containerAppService containerapps.ContainerAppService, resourceManager ResourceManager, ) ServiceTarget { return &containerAppTarget{ env: env, envManager: envManager, containerHelper: containerHelper, containerAppService: containerAppService, resourceManager: resourceManager, } } // Gets the required external tools func (at *containerAppTarget) RequiredExternalTools(ctx context.Context, serviceConfig *ServiceConfig) []tools.ExternalTool { return at.containerHelper.RequiredExternalTools(ctx, serviceConfig) } // Initializes the Container App target func (at *containerAppTarget) Initialize(ctx context.Context, serviceConfig *ServiceConfig) error { if err := at.addPreProvisionChecks(ctx, serviceConfig); err != nil { return fmt.Errorf("initializing container app target: %w", err) } return nil } // Prepares and tags the container image from the build output based on the specified service configuration func (at *containerAppTarget) Package( ctx context.Context, serviceConfig *ServiceConfig, packageOutput *ServicePackageResult, progress *async.Progress[ServiceProgress], ) (*ServicePackageResult, error) { return packageOutput, nil } // Deploys service container images to ACR and provisions the container app service. func (at *containerAppTarget) Deploy( ctx context.Context, serviceConfig *ServiceConfig, packageOutput *ServicePackageResult, targetResource *environment.TargetResource, progress *async.Progress[ServiceProgress], ) (*ServiceDeployResult, error) { if err := at.validateTargetResource(targetResource); err != nil { return nil, fmt.Errorf("validating target resource: %w", err) } // Login, tag & push container image to ACR _, err := at.containerHelper.Deploy(ctx, serviceConfig, packageOutput, targetResource, true, progress) if err != nil { return nil, err } containerAppOptions := containerapps.ContainerAppOptions{ ApiVersion: serviceConfig.ApiVersion, } imageName := at.env.GetServiceProperty(serviceConfig.Name, "IMAGE_NAME") progress.SetProgress(NewServiceProgress("Updating container app revision")) err = at.containerAppService.AddRevision( ctx, targetResource.SubscriptionId(), targetResource.ResourceGroupName(), targetResource.ResourceName(), imageName, &containerAppOptions, ) if err != nil { return nil, fmt.Errorf("updating container app service: %w", err) } progress.SetProgress(NewServiceProgress("Fetching endpoints for container app service")) endpoints, err := at.Endpoints(ctx, serviceConfig, targetResource) if err != nil { return nil, err } return &ServiceDeployResult{ Package: packageOutput, TargetResourceId: azure.ContainerAppRID( targetResource.SubscriptionId(), targetResource.ResourceGroupName(), targetResource.ResourceName(), ), Kind: ContainerAppTarget, Endpoints: endpoints, }, nil } // Gets endpoint for the container app service func (at *containerAppTarget) Endpoints( ctx context.Context, serviceConfig *ServiceConfig, targetResource *environment.TargetResource, ) ([]string, error) { containerAppOptions := containerapps.ContainerAppOptions{ ApiVersion: serviceConfig.ApiVersion, } if ingressConfig, err := at.containerAppService.GetIngressConfiguration( ctx, targetResource.SubscriptionId(), targetResource.ResourceGroupName(), targetResource.ResourceName(), &containerAppOptions, ); err != nil { return nil, fmt.Errorf("fetching service properties: %w", err) } else { endpoints := make([]string, len(ingressConfig.HostNames)) for idx, hostName := range ingressConfig.HostNames { endpoints[idx] = fmt.Sprintf("https://%s/", hostName) } return endpoints, nil } } func (at *containerAppTarget) validateTargetResource( targetResource *environment.TargetResource, ) error { if targetResource.ResourceGroupName() == "" { return fmt.Errorf("missing resource group name: %s", targetResource.ResourceGroupName()) } if targetResource.ResourceType() != "" { if err := checkResourceType(targetResource, azapi.AzureResourceTypeContainerApp); err != nil { return err } } return nil } func (at *containerAppTarget) addPreProvisionChecks(_ context.Context, serviceConfig *ServiceConfig) error { // Attempt to retrieve the target resource for the current service // This allows the resource deployment to detect whether or not to pull existing container image during // provision operation to avoid resetting the container app back to a default image return serviceConfig.Project.AddHandler("preprovision", func(ctx context.Context, args ProjectLifecycleEventArgs) error { exists := false // Check if the target resource already exists targetResource, err := at.resourceManager.GetTargetResource(ctx, at.env.GetSubscriptionId(), serviceConfig) if targetResource != nil && err == nil { exists = true } at.env.SetServiceProperty(serviceConfig.Name, "RESOURCE_EXISTS", strconv.FormatBool(exists)) return at.envManager.Save(ctx, at.env) }) }