func Parse()

in cli/azd/pkg/project/project.go [41:146]


func Parse(ctx context.Context, yamlContent string) (*ProjectConfig, error) {
	var projectConfig ProjectConfig

	if strings.TrimSpace(yamlContent) == "" {
		return nil, fmt.Errorf("unable to parse azure.yaml file. File is empty.")
	}

	if err := yaml.Unmarshal([]byte(yamlContent), &projectConfig); err != nil {
		return nil, fmt.Errorf(
			"unable to parse azure.yaml file. Check the format of the file, "+
				"and also verify you have the latest version of the CLI: %w",
			err,
		)
	}

	projectConfig.EventDispatcher = ext.NewEventDispatcher[ProjectLifecycleEventArgs]()

	if projectConfig.RequiredVersions != nil && projectConfig.RequiredVersions.Azd != nil {
		supportedRange, err := semver.ParseRange(*projectConfig.RequiredVersions.Azd)
		if err != nil {
			return nil, fmt.Errorf("%s is not a valid semver range (for requiredVersions.azd): %w",
				*projectConfig.RequiredVersions.Azd, err)
		}

		if !internal.IsDevVersion() && !supportedRange(internal.VersionInfo().Version) {
			return nil, fmt.Errorf("this project requires a version of azd within the range '%s', but you have '%s'. "+
				"Visit https://aka.ms/azure-dev/install to install a supported version.",
				*projectConfig.RequiredVersions.Azd,
				internal.VersionInfo().Version.String())
		}
	}

	var err error
	projectConfig.Infra.Provider, err = provisioning.ParseProvider(projectConfig.Infra.Provider)
	if err != nil {
		return nil, fmt.Errorf("parsing project %s: %w", projectConfig.Name, err)
	}

	if projectConfig.Infra.Path == "" {
		projectConfig.Infra.Path = "infra"
	}

	if projectConfig.Infra.Module == "" {
		projectConfig.Infra.Module = DefaultModule
	}

	if strings.Contains(projectConfig.Infra.Path, "\\") && !strings.Contains(projectConfig.Infra.Path, "/") {
		projectConfig.Infra.Path = strings.ReplaceAll(projectConfig.Infra.Path, "\\", "/")
	}

	projectConfig.Infra.Path = filepath.FromSlash(projectConfig.Infra.Path)

	for key, svc := range projectConfig.Services {
		svc.Name = key
		svc.Project = &projectConfig
		svc.EventDispatcher = ext.NewEventDispatcher[ServiceLifecycleEventArgs]()

		var err error
		svc.Language, err = parseServiceLanguage(svc.Language)
		if err != nil {
			return nil, fmt.Errorf("parsing service %s: %w", svc.Name, err)
		}

		svc.Host, err = parseServiceHost(svc.Host)
		if err != nil {
			return nil, fmt.Errorf("parsing service %s: %w", svc.Name, err)
		}

		svc.Infra.Provider, err = provisioning.ParseProvider(svc.Infra.Provider)
		if err != nil {
			return nil, fmt.Errorf("parsing service %s: %w", svc.Name, err)
		}

		if strings.Contains(svc.Infra.Path, "\\") && !strings.Contains(svc.Infra.Path, "/") {
			svc.Infra.Path = strings.ReplaceAll(svc.Infra.Path, "\\", "/")
		}

		svc.Infra.Path = filepath.FromSlash(svc.Infra.Path)

		// TODO: Move parsing/validation requirements for service targets into their respective components.
		// When working within container based applications users may be using external/pre-built images instead of source
		// In this case it is valid to have not specified a language but would be required to specify a source image
		if svc.Host == ContainerAppTarget && svc.Language == ServiceLanguageNone && svc.Image.Empty() {
			return nil, fmt.Errorf("parsing service %s: must specify language or image", svc.Name)
		}

		if strings.ContainsRune(svc.RelativePath, '\\') && !strings.ContainsRune(svc.RelativePath, '/') {
			svc.RelativePath = strings.ReplaceAll(svc.RelativePath, "\\", "/")
		}

		svc.RelativePath = filepath.FromSlash(svc.RelativePath)

		if strings.ContainsRune(svc.OutputPath, '\\') && !strings.ContainsRune(svc.OutputPath, '/') {
			svc.OutputPath = strings.ReplaceAll(svc.OutputPath, "\\", "/")
		}

		svc.OutputPath = filepath.FromSlash(svc.OutputPath)
	}

	for key, svc := range projectConfig.Resources {
		svc.Name = key
		svc.Project = &projectConfig
	}

	return &projectConfig, nil
}