@@ -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,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