collectEnvCheckFindings walks every compose file scheduled for publication (top-level files plus any local extends parents discovered along the way) and aggregates per-service and per-config findings. The walk mirrors processExtends so coverage matches what is actually serialized into the OCI artifa
(ctx context.Context, project *types.Project)
| 471 | // processExtends so coverage matches what is actually serialized into the OCI |
| 472 | // artifact. |
| 473 | func collectEnvCheckFindings(ctx context.Context, project *types.Project) (*envCheckFindings, error) { |
| 474 | findings := &envCheckFindings{services: map[string]*serviceEnvFindings{}} |
| 475 | literalCfgs := map[string]struct{}{} |
| 476 | keywordDetector := keyword.NewDetector("0") |
| 477 | |
| 478 | seen := map[string]struct{}{} |
| 479 | queue := slices.Clone(project.ComposeFiles) |
| 480 | for len(queue) > 0 { |
| 481 | file := queue[0] |
| 482 | queue = queue[1:] |
| 483 | if _, ok := seen[file]; ok { |
| 484 | continue |
| 485 | } |
| 486 | seen[file] = struct{}{} |
| 487 | |
| 488 | unresolved, err := loadUnresolvedFile(ctx, project, file) |
| 489 | if err != nil { |
| 490 | return nil, fmt.Errorf("failed to load compose file %s: %w", file, err) |
| 491 | } |
| 492 | |
| 493 | for _, service := range unresolved.Services { |
| 494 | recordServiceEnvFindings(findings.services, keywordDetector, service) |
| 495 | if parent := localExtendsParent(service); parent != "" { |
| 496 | queue = append(queue, parent) |
| 497 | } |
| 498 | } |
| 499 | for name, config := range unresolved.Configs { |
| 500 | // config.Environment is a variable *name* (only the name is |
| 501 | // published, not its resolved value) so it is not a leak. Inline |
| 502 | // config.Content is what ends up in the artifact. compose-go |
| 503 | // enforces that file, environment, and content are mutually |
| 504 | // exclusive. The map key is the name as written in the compose |
| 505 | // file; config.Name is the project-namespaced version, which is |
| 506 | // less helpful when surfaced to the user. |
| 507 | if config.Content != "" && configContentLooksLiteral(config.Content, keywordDetector) { |
| 508 | literalCfgs[name] = struct{}{} |
| 509 | } |
| 510 | } |
| 511 | } |
| 512 | |
| 513 | if len(literalCfgs) > 0 { |
| 514 | findings.configsLiteralContent = sortedMapKeys(literalCfgs) |
| 515 | } |
| 516 | return findings, nil |
| 517 | } |
| 518 | |
| 519 | func recordServiceEnvFindings(services map[string]*serviceEnvFindings, detector secrets.Detector, service types.ServiceConfig) { |
| 520 | envValues := map[string]string{} |