// Package atlas builds the CAD Atlas data model, deriving parts from real // sources (the repo's own CI workflow, infra manifests, the live cluster) // so the visualization can't drift from reality. package atlas import ( "fmt" "gopkg.in/yaml.v3" ) // JobsFromWorkflow extracts the job names, in document order, from a Gitea // Actions / GitHub Actions workflow YAML. Used to generate the CI/CD stage of // the atlas from the pipeline that actually runs, rather than hand-authoring it. func JobsFromWorkflow(workflow []byte) ([]string, error) { var doc yaml.Node if err := yaml.Unmarshal(workflow, &doc); err != nil { return nil, fmt.Errorf("parse workflow: %w", err) } if len(doc.Content) == 0 || doc.Content[0].Kind != yaml.MappingNode { return nil, fmt.Errorf("workflow: expected a top-level mapping") } jobs := mappingValue(doc.Content[0], "jobs") if jobs == nil { return nil, fmt.Errorf("workflow: no jobs block") } if jobs.Kind != yaml.MappingNode { return nil, fmt.Errorf("workflow: jobs is not a mapping") } // A mapping node stores keys and values as alternating Content entries; // keys are the even indices, in document order. names := make([]string, 0, len(jobs.Content)/2) for i := 0; i+1 < len(jobs.Content); i += 2 { names = append(names, jobs.Content[i].Value) } return names, nil } // mappingValue returns the value node for key in a YAML mapping node, or nil. func mappingValue(m *yaml.Node, key string) *yaml.Node { for i := 0; i+1 < len(m.Content); i += 2 { if m.Content[i].Value == key { return m.Content[i+1] } } return nil }