Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions api/v1/composition.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package v1

import (
"context"
"errors"
"fmt"
"strconv"

Expand Down Expand Up @@ -58,6 +60,82 @@ type CompositionSpec struct {
SynthesisEnv []EnvVar `json:"synthesisEnv,omitempty"`
}

// Sentinel errors for synthesizer resolution.
var (
// ErrNoMatchingSelector is returned when no synthesizers match the label selector.
ErrNoMatchingSelector = errors.New("no synthesizers match the label selector")

// ErrMultipleMatches is returned when more than one synthesizer matches the label selector.
ErrMultipleMatches = errors.New("multiple synthesizers match the label selector")
)

// ResolveSynthesizer resolves the Composition's SynthesizerRef to a concrete Synthesizer.
//
// Precedence behavior: When both Name and LabelSelector are set in the ref,
// LabelSelector takes precedence and Name is ignored. This allows for more
// flexible matching when needed while maintaining backwards compatibility
// with name-based resolution.
//
// If the ref has a labelSelector, it lists all synthesizers matching the selector.
// Exactly one synthesizer must match; if zero match, ErrNoMatchingSelector is returned,
// and if more than one match, ErrMultipleMatches is returned.
//
// If labelSelector is not set, it uses the name field to get the synthesizer directly.
//
// Returns:
// - The resolved Synthesizer if found
// - nil, ErrNoMatchingSelector if no synthesizers match the label selector
// - nil, ErrMultipleMatches if more than one synthesizer matches the label selector
// - nil, error if there was an error during resolution
func (c *Composition) ResolveSynthesizer(ctx context.Context, cl client.Reader) (*Synthesizer, error) {
ref := &c.Spec.Synthesizer
// LabelSelector takes precedence over name
if ref.LabelSelector != nil {
return c.resolveSynthesizerByLabel(ctx, cl)
}

// Fallback to name-based resolution
synth := &Synthesizer{}
synth.Name = ref.Name

return synth, cl.Get(ctx, client.ObjectKeyFromObject(synth), synth)
}

// resolveSynthesizerByLabel resolves a Synthesizer using a label selector.
// It lists all synthesizers matching the selector and returns the matching one.
// Exactly one synthesizer must match the selector.
//
// Returns:
// - The resolved Synthesizer if exactly one matches
// - nil, ErrNoMatchingSelector if no synthesizers match the selector
// - nil, ErrMultipleMatches if more than one synthesizer matches the selector
// - nil, error if there was an error during resolution
func (c *Composition) resolveSynthesizerByLabel(ctx context.Context, cl client.Reader) (*Synthesizer, error) {
ref := &c.Spec.Synthesizer
// Convert metav1.LabelSelector to labels.Selector
selector, err := metav1.LabelSelectorAsSelector(ref.LabelSelector)
if err != nil {
return nil, fmt.Errorf("converting label selector: %w", err)
}

// List all synthesizers matching the selector
synthList := &SynthesizerList{}
err = cl.List(ctx, synthList, client.MatchingLabelsSelector{Selector: selector})
if err != nil {
return nil, fmt.Errorf("listing synthesizers by label selector: %w", err)
}

// Handle results based on match count
switch len(synthList.Items) {
case 0:
return nil, ErrNoMatchingSelector
case 1:
return &synthList.Items[0], nil
default:
return nil, ErrMultipleMatches
}
}

type CompositionStatus struct {
Simplified *SimplifiedStatus `json:"simplified,omitempty"`
InFlightSynthesis *Synthesis `json:"inFlightSynthesis,omitempty"`
Expand Down
1 change: 1 addition & 0 deletions api/v1/synthesizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,4 @@ type SynthesizerRef struct {
Name string `json:"name,omitempty"`
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
}

Loading
Loading