Clarify pipeline composition envelope handling

This commit is contained in:
2026-09-05 00:31:32 +00:00
parent abd405015b
commit 979d8049b1
5 changed files with 118 additions and 40 deletions

View File

@@ -180,14 +180,24 @@ func splitPipelineCompositionEnvelope(document *compositionDocument) (*compositi
if err := validateCompositionDocument(document, "root pipeline"); err != nil {
return nil, pipelineCompositionEnvelope{}, err
}
root := cloneCompositionNode(document.root)
envelope, fieldIndex, err := decodePipelineCompositionEnvelope(document.root)
if err != nil {
return nil, pipelineCompositionEnvelope{}, err
}
if err := validatePipelineCompositionEnvelope(envelope); err != nil {
return nil, pipelineCompositionEnvelope{}, err
}
return pipelineBodyWithoutComposition(document, fieldIndex), envelope, nil
}
func decodePipelineCompositionEnvelope(root *compositionNode) (pipelineCompositionEnvelope, int, error) {
index := compositionFieldIndex(root.fields, "composition")
if index < 0 {
return &compositionDocument{root: root, sources: append([]string(nil), document.sources...)}, pipelineCompositionEnvelope{}, nil
return pipelineCompositionEnvelope{}, -1, nil
}
envelopeNode := root.fields[index].value
if envelopeNode.kind != yaml.MappingNode {
return nil, pipelineCompositionEnvelope{}, fmt.Errorf(
return pipelineCompositionEnvelope{}, -1, fmt.Errorf(
"configuration source %s at composition: expected a mapping, got %s",
formatCompositionSources(envelopeNode.sources), yamlKindName(envelopeNode.kind),
)
@@ -198,14 +208,14 @@ func splitPipelineCompositionEnvelope(document *compositionDocument) (*compositi
switch field.key {
case "imports":
if field.value.kind != yaml.SequenceNode {
return nil, pipelineCompositionEnvelope{}, fmt.Errorf(
return pipelineCompositionEnvelope{}, -1, fmt.Errorf(
"configuration source %s at composition.imports: expected a list, got %s",
formatCompositionSources(field.value.sources), yamlKindName(field.value.kind),
)
}
for itemIndex, item := range field.value.items {
if item.kind != yaml.ScalarNode || item.tag != "!!str" {
return nil, pipelineCompositionEnvelope{}, fmt.Errorf(
return pipelineCompositionEnvelope{}, -1, fmt.Errorf(
"configuration source %s at composition.imports[%d]: expected a string path",
formatCompositionSources(item.sources), itemIndex,
)
@@ -214,54 +224,62 @@ func splitPipelineCompositionEnvelope(document *compositionDocument) (*compositi
}
case "default_profile":
if field.value.kind != yaml.ScalarNode || field.value.tag != "!!str" {
return nil, pipelineCompositionEnvelope{}, fmt.Errorf(
return pipelineCompositionEnvelope{}, -1, fmt.Errorf(
"configuration source %s at composition.default_profile: expected a string profile name",
formatCompositionSources(field.value.sources),
)
}
name, err := normalizePipelineProfileName(field.value.value, "composition.default_profile")
if err != nil {
return nil, pipelineCompositionEnvelope{}, err
return pipelineCompositionEnvelope{}, -1, err
}
envelope.defaultProfile = &name
case "profiles":
if field.value.kind != yaml.MappingNode {
return nil, pipelineCompositionEnvelope{}, fmt.Errorf(
return pipelineCompositionEnvelope{}, -1, fmt.Errorf(
"configuration source %s at composition.profiles: expected a mapping, got %s",
formatCompositionSources(field.value.sources), yamlKindName(field.value.kind),
)
}
if len(field.value.fields) == 0 {
return nil, pipelineCompositionEnvelope{}, fmt.Errorf("composition.profiles must declare at least one named profile")
return pipelineCompositionEnvelope{}, -1, fmt.Errorf("composition.profiles must declare at least one named profile")
}
for _, profileField := range field.value.fields {
name, err := normalizePipelineProfileName(profileField.key, "composition.profiles profile name")
if err != nil {
return nil, pipelineCompositionEnvelope{}, err
return pipelineCompositionEnvelope{}, -1, err
}
profile, err := parsePipelineProfileDeclaration(name, profileField.value)
if err != nil {
return nil, pipelineCompositionEnvelope{}, err
return pipelineCompositionEnvelope{}, -1, err
}
envelope.profiles = append(envelope.profiles, profile)
}
default:
return nil, pipelineCompositionEnvelope{}, fmt.Errorf(
return pipelineCompositionEnvelope{}, -1, fmt.Errorf(
"configuration source %s at composition.%s: unknown composition field %q",
formatCompositionSources(field.value.sources), field.key, field.key,
)
}
}
return envelope, index, nil
}
func validatePipelineCompositionEnvelope(envelope pipelineCompositionEnvelope) error {
if envelope.defaultProfile != nil && !pipelineProfileDeclared(envelope.profiles, *envelope.defaultProfile) {
return nil, pipelineCompositionEnvelope{}, fmt.Errorf(
return fmt.Errorf(
"composition.default_profile %q does not name a declared profile", *envelope.defaultProfile,
)
}
root.fields = append(root.fields[:index], root.fields[index+1:]...)
for fieldIndex := range root.fields {
root.fields[fieldIndex].order = fieldIndex
return nil
}
func pipelineBodyWithoutComposition(document *compositionDocument, compositionIndex int) *compositionDocument {
root := cloneCompositionNode(document.root)
if compositionIndex >= 0 {
root.fields = append(root.fields[:compositionIndex], root.fields[compositionIndex+1:]...)
}
return &compositionDocument{root: root, sources: append([]string(nil), document.sources...)}, envelope, nil
return &compositionDocument{root: root, sources: append([]string(nil), document.sources...)}
}
func parsePipelineProfileDeclaration(name string, node *compositionNode) (pipelineProfileDeclaration, error) {