1414package api
1515
1616import (
17+ "errors"
1718 "fmt"
1819 "log"
20+ "os"
21+ "path/filepath"
1922 "reflect"
2023 "regexp"
24+ "sort"
2125 "strings"
2226 "unicode"
2327
@@ -34,6 +38,9 @@ type Product struct {
3438 // Example inputs: "Compute", "AccessContextManager"
3539 Name string
3640
41+ // This is the name of the package path relative to mmv1 root repo
42+ PackagePath string
43+
3744 // original value of :name before the provider override happens
3845 // same as :name if not overridden in provider
3946 ApiName string `yaml:"api_name,omitempty"`
@@ -77,6 +84,164 @@ type Product struct {
7784 Compiler string `yaml:"-"`
7885}
7986
87+ // Load compiles a product with all its resources from the given path and optional overrides
88+ // This loads the product configuration and all its resources into memory without generating any files
89+ func (p * Product ) Load (productName string , version string , overrideDirectory string ) error {
90+ productYamlPath := filepath .Join (productName , "product.yaml" )
91+
92+ var productOverridePath string
93+ if overrideDirectory != "" {
94+ productOverridePath = filepath .Join (overrideDirectory , productName , "product.yaml" )
95+ }
96+
97+ _ , baseProductErr := os .Stat (productYamlPath )
98+ baseProductExists := ! errors .Is (baseProductErr , os .ErrNotExist )
99+
100+ _ , overrideProductErr := os .Stat (productOverridePath )
101+ overrideProductExists := ! errors .Is (overrideProductErr , os .ErrNotExist )
102+
103+ if ! (baseProductExists || overrideProductExists ) {
104+ return fmt .Errorf ("%s does not contain a product.yaml file" , productName )
105+ }
106+
107+ // Compile the product configuration
108+ if overrideProductExists {
109+ if baseProductExists {
110+ Compile (productYamlPath , p , overrideDirectory )
111+ overrideApiProduct := & Product {}
112+ Compile (productOverridePath , overrideApiProduct , overrideDirectory )
113+ Merge (reflect .ValueOf (p ).Elem (), reflect .ValueOf (* overrideApiProduct ), version )
114+ } else {
115+ Compile (productOverridePath , p , overrideDirectory )
116+ }
117+ } else {
118+ Compile (productYamlPath , p , overrideDirectory )
119+ }
120+
121+ // Check if product exists at the requested version
122+ if ! p .ExistsAtVersionOrLower (version ) {
123+ return & ErrProductVersionNotFound {ProductName : productName , Version : version }
124+ }
125+
126+ // Compile all resources
127+ resources , err := p .loadResources (productName , version , overrideDirectory )
128+ if err != nil {
129+ return err
130+ }
131+
132+ p .Objects = resources
133+ p .PackagePath = productName
134+ p .Validate ()
135+
136+ return nil
137+ }
138+
139+ // loadResources loads all resources for a product
140+ func (p * Product ) loadResources (productName string , version string , overrideDirectory string ) ([]* Resource , error ) {
141+ var resources []* Resource = make ([]* Resource , 0 )
142+
143+ // Get base resource files
144+ resourceFiles , err := filepath .Glob (fmt .Sprintf ("%s/*" , productName ))
145+ if err != nil {
146+ return nil , fmt .Errorf ("cannot get resource files: %v" , err )
147+ }
148+
149+ // Compile base resources (skip those that will be merged with overrides)
150+ for _ , resourceYamlPath := range resourceFiles {
151+ if filepath .Base (resourceYamlPath ) == "product.yaml" || filepath .Ext (resourceYamlPath ) != ".yaml" {
152+ continue
153+ }
154+
155+ // Skip if resource will be merged in the override loop
156+ if overrideDirectory != "" {
157+ resourceOverridePath := filepath .Join (overrideDirectory , resourceYamlPath )
158+ _ , overrideResourceErr := os .Stat (resourceOverridePath )
159+ if ! errors .Is (overrideResourceErr , os .ErrNotExist ) {
160+ continue
161+ }
162+ }
163+
164+ resource := p .loadResource (resourceYamlPath , "" , version , overrideDirectory )
165+ resources = append (resources , resource )
166+ }
167+
168+ // Compile override resources
169+ if overrideDirectory != "" {
170+ resources , err = p .reconcileOverrideResources (productName , version , overrideDirectory , resources )
171+ if err != nil {
172+ return nil , err
173+ }
174+ }
175+
176+ return resources , nil
177+ }
178+
179+ // reconcileOverrideResources handles resolution of override resources
180+ func (p * Product ) reconcileOverrideResources (productName string , version string , overrideDirectory string , resources []* Resource ) ([]* Resource , error ) {
181+ productOverridePath := filepath .Join (overrideDirectory , productName , "product.yaml" )
182+ productOverrideDir := filepath .Dir (productOverridePath )
183+
184+ overrideFiles , err := filepath .Glob (fmt .Sprintf ("%s/*" , productOverrideDir ))
185+ if err != nil {
186+ return nil , fmt .Errorf ("cannot get override files: %v" , err )
187+ }
188+
189+ for _ , overrideYamlPath := range overrideFiles {
190+ if filepath .Base (overrideYamlPath ) == "product.yaml" || filepath .Ext (overrideYamlPath ) != ".yaml" {
191+ continue
192+ }
193+
194+ baseResourcePath := filepath .Join (productName , filepath .Base (overrideYamlPath ))
195+ resource := p .loadResource (baseResourcePath , overrideYamlPath , version , overrideDirectory )
196+ resources = append (resources , resource )
197+ }
198+
199+ // Sort resources by name for consistent output
200+ sort .Slice (resources , func (i , j int ) bool {
201+ return resources [i ].Name < resources [j ].Name
202+ })
203+
204+ return resources , nil
205+ }
206+
207+ // loadResource loads a single resource with optional override
208+ func (p * Product ) loadResource (baseResourcePath string , overrideResourcePath string , version string , overrideDirectory string ) * Resource {
209+ resource := & Resource {}
210+
211+ // Check if base resource exists
212+ _ , baseResourceErr := os .Stat (baseResourcePath )
213+ baseResourceExists := ! errors .Is (baseResourceErr , os .ErrNotExist )
214+
215+ if overrideResourcePath != "" {
216+ if baseResourceExists {
217+ // Merge base and override
218+ Compile (baseResourcePath , resource , overrideDirectory )
219+ overrideResource := & Resource {}
220+ Compile (overrideResourcePath , overrideResource , overrideDirectory )
221+ Merge (reflect .ValueOf (resource ).Elem (), reflect .ValueOf (* overrideResource ), version )
222+ resource .SourceYamlFile = baseResourcePath
223+ } else {
224+ // Override only
225+ Compile (overrideResourcePath , resource , overrideDirectory )
226+ }
227+ } else {
228+ // Base only
229+ Compile (baseResourcePath , resource , overrideDirectory )
230+ resource .SourceYamlFile = baseResourcePath
231+ }
232+
233+ // Set resource defaults and validate
234+ resource .TargetVersionName = version
235+ // SetDefault before AddExtraFields to ensure relevant metadata is available on existing fields
236+ resource .SetDefault (p )
237+ resource .Properties = resource .AddExtraFields (resource .PropertiesWithExcluded (), nil )
238+ // SetDefault after AddExtraFields to ensure relevant metadata is available for the newly generated fields
239+ resource .SetDefault (p )
240+ resource .Validate ()
241+
242+ return resource
243+ }
244+
80245func (p * Product ) UnmarshalYAML (unmarshal func (any ) error ) error {
81246 type productAlias Product
82247 aliasObj := (* productAlias )(p )
0 commit comments