Skip to content

Commit 7dd8b56

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

File tree

2 files changed

+1356
-5
lines changed

2 files changed

+1356
-5
lines changed

alpha/template/substitutes/substitutes.go

Lines changed: 106 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,108 @@ 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+
116+
entryIndex := entriesToSubstitute[i]
117+
// Create a new channel entry for substitution.name
118+
newEntry := declcfg.ChannelEntry{
119+
Name: substituteBundle.Name,
120+
Replaces: channel.Entries[entryIndex].Replaces,
121+
Skips: channel.Entries[entryIndex].Skips,
122+
SkipRange: channel.Entries[entryIndex].SkipRange,
123+
}
124+
125+
// Add skip relationship to substitution.base
126+
newEntry.Skips = append(newEntry.Skips, substitution.Base)
127+
128+
// Add the new entry to the channel
129+
channel.Entries = append(channel.Entries, newEntry)
130+
131+
// Clear the original entry's replaces/skips/skipRange since they moved to the new entry
132+
channel.Entries[entryIndex].Replaces = ""
133+
channel.Entries[entryIndex].Skips = nil
134+
channel.Entries[entryIndex].SkipRange = ""
135+
}
136+
137+
// Second pass: update all references to substitution.base to point to substitution.name
138+
// Skip the newly created substitution entries (they are at the end)
139+
originalEntryCount := len(channel.Entries) - len(entriesToSubstitute)
140+
for j := 0; j < originalEntryCount; j++ {
141+
entry := &channel.Entries[j]
142+
143+
// If this entry replaces substitution.base, update it to replace substitution.name
144+
if entry.Replaces == substitution.Base {
145+
entry.Replaces = substituteBundle.Name
146+
entry.Skips = append(entry.Skips, substitution.Base)
147+
}
148+
}
149+
}
150+
151+
// Add the substitute bundle to the config (only once)
152+
cfg.Bundles = append(cfg.Bundles, *substituteBundle)
153+
154+
// now validate the resulting config
155+
_, err = declcfg.ConvertToModel(*cfg)
156+
if err != nil {
157+
return fmt.Errorf("resulting config is not valid FBC: %v", err)
158+
}
159+
160+
return nil
60161
}

0 commit comments

Comments
 (0)