func()

in cli/azd/pkg/devcenter/template_source.go [46:214]


func (s *TemplateSource) ListTemplates(ctx context.Context) ([]*templates.Template, error) {
	var devCenterFilter DevCenterFilterPredicate
	var projectFilter ProjectFilterPredicate
	var catalogFilter EnvironmentDefinitionFilterPredicate

	if s.config.Name != "" {
		devCenterFilter = func(dc *devcentersdk.DevCenter) bool {
			return strings.EqualFold(dc.Name, s.config.Name)
		}
	}

	if s.config.Catalog != "" {
		catalogFilter = func(ed *devcentersdk.EnvironmentDefinition) bool {
			return strings.EqualFold(ed.CatalogName, s.config.Catalog)
		}
	}

	if s.config.Project != "" {
		projectFilter = func(p *devcentersdk.Project) bool {
			return strings.EqualFold(p.Name, s.config.Project)
		}
	}

	projects, err := s.manager.WritableProjectsWithFilter(ctx, devCenterFilter, projectFilter)
	if err != nil {
		return nil, fmt.Errorf("failed getting writable projects: %w", err)
	}

	templatesChan := make(chan *templates.Template)
	errorsChan := make(chan error)

	// Perform the lookup and checking for projects in parallel to speed up the process
	var wg sync.WaitGroup

	for _, project := range projects {
		wg.Add(1)

		go func(project *devcentersdk.Project) {
			defer wg.Done()

			// If a project is specified in the config then only consider templates for the specified project
			if s.config.Project != "" && !strings.EqualFold(s.config.Project, project.Name) {
				return
			}

			envDefinitions, err := s.devCenterClient.
				DevCenterByEndpoint(project.DevCenter.ServiceUri).
				ProjectByName(project.Name).
				EnvironmentDefinitions().
				Get(ctx)

			if err != nil {
				errorsChan <- err
				return
			}

			for _, envDefinition := range envDefinitions.Value {
				// Filter out environment definitions that do not match the specified catalog
				if catalogFilter != nil && !catalogFilter(envDefinition) {
					continue
				}

				// We only want to consider environment definitions that have
				// a repo url parameter as valid templates for azd
				var repoUrls []string
				var repoUrlParamId string
				containsRepoUrl := slices.ContainsFunc(envDefinition.Parameters, func(p devcentersdk.Parameter) bool {
					if strings.EqualFold(p.Id, "repourl") {
						repoUrlParamId = p.Id

						// Repo url parameter can support multiple values
						// Values can either have a default or multiple allowed values but not both
						if len(p.Allowed) > 0 {
							repoUrls = append(repoUrls, p.Allowed...)
						} else if p.Default != nil {
							defaultValue, ok := p.Default.(string)
							if ok && defaultValue != "" {
								repoUrls = append(repoUrls, defaultValue)
							}
						}

						return true
					}

					return false
				})

				if !containsRepoUrl {
					continue
				}

				definitionParts := []string{
					project.DevCenter.Name,
					envDefinition.CatalogName,
					envDefinition.Name,
				}
				definitionPath := strings.Join(definitionParts, "/")

				// List an available AZD template for each repo url that is referenced in the template
				for _, url := range repoUrls {
					templatesChan <- &templates.Template{
						Id:             url + definitionPath,
						Name:           envDefinition.Name,
						Source:         fmt.Sprintf("%s/%s", project.DevCenter.Name, envDefinition.CatalogName),
						Description:    envDefinition.Description,
						RepositoryPath: url,

						// Metadata will be used when creating any azd environments that are based on this template
						Metadata: templates.Metadata{
							Project: map[string]string{
								"platform.type":                                     string(PlatformKindDevCenter),
								fmt.Sprintf("%s.name", ConfigPath):                  project.DevCenter.Name,
								fmt.Sprintf("%s.catalog", ConfigPath):               envDefinition.CatalogName,
								fmt.Sprintf("%s.environmentDefinition", ConfigPath): envDefinition.Name,
							},
							Config: map[string]string{
								// Set the repoUrl param so it is not re-prompted by the provision provider
								fmt.Sprintf("provision.parameters.%s", repoUrlParamId): url,
							},
						},
					}
				}
			}
		}(project)
	}

	go func() {
		wg.Wait()
		close(templatesChan)
		close(errorsChan)
	}()

	var doneGroup sync.WaitGroup
	doneGroup.Add(2)

	var allErrors error
	distinctTemplates := []*templates.Template{}

	go func() {
		defer doneGroup.Done()

		for template := range templatesChan {
			contains := slices.ContainsFunc(distinctTemplates, func(t *templates.Template) bool {
				return t.Id == template.Id
			})

			if !contains {
				distinctTemplates = append(distinctTemplates, template)
			}
		}
	}()

	go func() {
		defer doneGroup.Done()

		for err := range errorsChan {
			allErrors = multierr.Append(allErrors, err)
		}
	}()

	// Wait for all the templates and errors to be processed from channels
	doneGroup.Wait()

	if allErrors != nil {
		return nil, allErrors
	}

	return distinctTemplates, nil
}