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
}