@@ -16,8 +16,8 @@ type Template struct {
1616}
1717
1818type 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
2323type 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