Skip to content

Commit 31ef961

Browse files
committed
implement template expansion logic, tests
Signed-off-by: grokspawn <[email protected]> Helped-by: Claude-LLM
1 parent 1eb6993 commit 31ef961

File tree

2 files changed

+1336
-5
lines changed

2 files changed

+1336
-5
lines changed

alpha/template/substitutes/substitutes.go

Lines changed: 105 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ type Template struct {
1616
}
1717

1818
type Substitute struct {
19-
Name string `json:"name"`
20-
Base string `json:"base"`
19+
Name string `json:"name"` // the bundle image pullspec to substitute
20+
Base string `json:"base"` // the bundle name to substitute for
2121
}
2222

2323
type SubstitutesForTemplate struct {
@@ -54,7 +54,107 @@ func (t Template) Render(ctx context.Context, reader io.Reader) (*declcfg.Declar
5454
return nil, fmt.Errorf("render: unable to parse template: %v", err)
5555
}
5656

57-
// TODO: Implement the actual rendering logic using st.Entries and st.Substitutes
58-
_ = st
59-
return nil, nil
57+
// Create DeclarativeConfig from template entries
58+
cfg, err := declcfg.LoadSlice(st.Entries)
59+
if err != nil {
60+
return nil, fmt.Errorf("render: unable to create declarative config from entries: %v", err)
61+
}
62+
63+
_, err = declcfg.ConvertToModel(*cfg)
64+
if err != nil {
65+
return nil, fmt.Errorf("render: entries are not valid FBC: %v", err)
66+
}
67+
68+
// Process each substitution
69+
for _, substitution := range st.Substitutions {
70+
err := t.processSubstitution(ctx, cfg, substitution)
71+
if err != nil {
72+
return nil, fmt.Errorf("render: error processing substitution %s->%s: %v", substitution.Base, substitution.Name, err)
73+
}
74+
}
75+
76+
return cfg, nil
77+
}
78+
79+
// processSubstitution handles the complex logic for processing a single substitution
80+
func (t Template) processSubstitution(ctx context.Context, cfg *declcfg.DeclarativeConfig, substitution Substitute) error {
81+
// Validate substitution fields - all are required
82+
if substitution.Name == "" {
83+
return fmt.Errorf("substitution name cannot be empty")
84+
}
85+
if substitution.Base == "" {
86+
return fmt.Errorf("substitution base cannot be empty")
87+
}
88+
if substitution.Name == substitution.Base {
89+
return fmt.Errorf("substitution name and base cannot be the same")
90+
}
91+
92+
substituteCfg, err := t.RenderBundle(ctx, substitution.Name)
93+
if err != nil {
94+
return fmt.Errorf("failed to render bundle image reference %q: %v", substitution.Name, err)
95+
}
96+
97+
substituteBundle := &substituteCfg.Bundles[0]
98+
99+
// Iterate over all channels
100+
for i := range cfg.Channels {
101+
channel := &cfg.Channels[i]
102+
103+
// First pass: find entries that have substitution.base as their name
104+
// Only process original entries, not substitution entries (they have empty replaces after clearing)
105+
var entriesToSubstitute []int
106+
for j := range channel.Entries {
107+
entry := &channel.Entries[j]
108+
if entry.Name == substitution.Base {
109+
entriesToSubstitute = append(entriesToSubstitute, j)
110+
}
111+
}
112+
113+
// Create new entries for each substitution (process in reverse order to avoid index issues)
114+
for i := len(entriesToSubstitute) - 1; i >= 0; i-- {
115+
entryIndex := entriesToSubstitute[i]
116+
// Create a new channel entry for substitution.name
117+
newEntry := declcfg.ChannelEntry{
118+
Name: substituteBundle.Name,
119+
Replaces: channel.Entries[entryIndex].Replaces,
120+
Skips: channel.Entries[entryIndex].Skips,
121+
SkipRange: channel.Entries[entryIndex].SkipRange,
122+
}
123+
124+
// Add skip relationship to substitution.base
125+
newEntry.Skips = append(newEntry.Skips, substitution.Base)
126+
127+
// Add the new entry to the channel
128+
channel.Entries = append(channel.Entries, newEntry)
129+
130+
// Clear the original entry's replaces/skips/skipRange since they moved to the new entry
131+
channel.Entries[entryIndex].Replaces = ""
132+
channel.Entries[entryIndex].Skips = nil
133+
channel.Entries[entryIndex].SkipRange = ""
134+
}
135+
136+
// Second pass: update all references to substitution.base to point to substitution.name
137+
// Skip the newly created substitution entries (they are at the end)
138+
originalEntryCount := len(channel.Entries) - len(entriesToSubstitute)
139+
for j := 0; j < originalEntryCount; j++ {
140+
entry := &channel.Entries[j]
141+
142+
// If this entry replaces substitution.base, update it to replace substitution.name
143+
if entry.Replaces == substitution.Base {
144+
entry.Replaces = substituteBundle.Name
145+
entry.Skips = append(entry.Skips, substitution.Base)
146+
}
147+
}
148+
}
149+
150+
// Add the substitute bundle to the config (only once)
151+
cfg.Bundles = append(cfg.Bundles, *substituteBundle)
152+
153+
// now validate the resulting config
154+
_, err = declcfg.ConvertToModel(*cfg)
155+
if err != nil {
156+
return fmt.Errorf("resulting config is not valid FBC: %v", err)
157+
}
158+
159+
return nil
60160
}

0 commit comments

Comments
 (0)