func()

in cli/azd/pkg/project/framework_service_docker.go [194:340]


func (p *dockerProject) Build(
	ctx context.Context,
	serviceConfig *ServiceConfig,
	restoreOutput *ServiceRestoreResult,
	progress *async.Progress[ServiceProgress],
) (*ServiceBuildResult, error) {
	if serviceConfig.Docker.RemoteBuild || useDotnetPublishForDockerBuild(serviceConfig) {
		return &ServiceBuildResult{Restore: restoreOutput}, nil
	}

	dockerOptions := getDockerOptionsWithDefaults(serviceConfig.Docker)

	resolveParameters := func(source []string) ([]string, error) {
		result := make([]string, len(source))
		for i, arg := range source {
			evaluatedString, err := apphost.EvalString(arg, func(match string) (string, error) {
				path := match
				value, has := p.env.Config.GetString(path)
				if !has {
					return "", fmt.Errorf("parameter %s not found", path)
				}
				return value, nil
			})
			if err != nil {
				return nil, err
			}
			result[i] = evaluatedString
		}
		return result, nil
	}

	dockerBuildArgs := []string{}
	for _, arg := range dockerOptions.BuildArgs {
		buildArgValue, err := arg.Envsubst(p.env.Getenv)
		if err != nil {
			return nil, fmt.Errorf("substituting environment variables in build args: %w", err)
		}

		dockerBuildArgs = append(dockerBuildArgs, buildArgValue)
	}

	// resolve parameters for build args and secrets
	resolvedBuildArgs, err := resolveParameters(dockerBuildArgs)
	if err != nil {
		return nil, err
	}

	resolvedBuildEnv, err := resolveParameters(dockerOptions.BuildEnv)
	if err != nil {
		return nil, err
	}

	dockerOptions.BuildEnv = resolvedBuildEnv

	// For services that do not specify a project path and have not specified a language then
	// there is nothing to build and we can return an empty build result
	// Ex) A container app project that uses an external image path
	if serviceConfig.RelativePath == "" &&
		(serviceConfig.Language == ServiceLanguageNone || serviceConfig.Language == ServiceLanguageDocker) {
		return &ServiceBuildResult{}, nil
	}

	buildArgs := []string{}
	for _, arg := range resolvedBuildArgs {
		buildArgs = append(buildArgs, exec.RedactSensitiveData(arg))
	}

	log.Printf(
		"building image for service %s, cwd: %s, path: %s, context: %s, buildArgs: %s)",
		serviceConfig.Name,
		serviceConfig.Path(),
		dockerOptions.Path,
		dockerOptions.Context,
		buildArgs,
	)

	imageName := fmt.Sprintf(
		"%s-%s",
		strings.ToLower(serviceConfig.Project.Name),
		strings.ToLower(serviceConfig.Name),
	)

	dockerfilePath := dockerOptions.Path
	if !filepath.IsAbs(dockerfilePath) {
		dockerfilePath = filepath.Join(serviceConfig.Path(), dockerfilePath)
	}

	_, err = os.Stat(dockerfilePath)
	if errors.Is(err, os.ErrNotExist) && serviceConfig.Docker.Path == "" {
		// Build the container from source when:
		// 1. No Dockerfile path is specified, and
		// 2. <service directory>/Dockerfile doesn't exist
		progress.SetProgress(NewServiceProgress("Building Docker image from source"))
		res, err := p.packBuild(ctx, serviceConfig, dockerOptions, imageName)
		if err != nil {
			return nil, err
		}

		res.Restore = restoreOutput
		return res, nil
	}

	// Include full environment variables for the docker build including:
	// 1. Environment variables from the host
	// 2. Environment variables from the service configuration
	// 3. Environment variables from the docker configuration
	dockerEnv := []string{}
	dockerEnv = append(dockerEnv, os.Environ()...)
	dockerEnv = append(dockerEnv, p.env.Environ()...)
	dockerEnv = append(dockerEnv, dockerOptions.BuildEnv...)

	// Build the container
	progress.SetProgress(NewServiceProgress("Building Docker image"))
	previewerWriter := p.console.ShowPreviewer(ctx,
		&input.ShowPreviewerOptions{
			Prefix:       "  ",
			MaxLineCount: 8,
			Title:        "Docker Output",
		})
	imageId, err := p.docker.Build(
		ctx,
		serviceConfig.Path(),
		dockerOptions.Path,
		dockerOptions.Platform,
		dockerOptions.Target,
		dockerOptions.Context,
		imageName,
		resolvedBuildArgs,
		dockerOptions.BuildSecrets,
		dockerEnv,
		previewerWriter,
	)
	p.console.StopPreviewer(ctx, false)
	if err != nil {
		return nil, fmt.Errorf("building container: %s at %s: %w", serviceConfig.Name, dockerOptions.Context, err)
	}

	log.Printf("built image %s for %s", imageId, serviceConfig.Name)
	return &ServiceBuildResult{
		Restore:         restoreOutput,
		BuildOutputPath: imageId,
		Details: &dockerBuildResult{
			ImageId:   imageId,
			ImageName: imageName,
		},
	}, nil
}