66 "os"
77 "path"
88 "path/filepath"
9+ "strings"
910
1011 dct "github.com/compose-spec/compose-go/v2/types"
1112 "github.com/pkg/errors"
@@ -73,24 +74,24 @@ type StagingCaches struct {
7374}
7475
7576func StagePallet (
76- indent int , pallet * forklift.FSPallet , stageStore * forklift.FSStageStore , caches StagingCaches ,
77+ indent int , merged * forklift.FSPallet , stageStore * forklift.FSStageStore , caches StagingCaches ,
7778 exportPath string , versions StagingVersions ,
7879 skipImageCaching , parallel , ignoreToolVersion bool ,
7980) (index int , err error ) {
80- if _ , isMerged := pallet .FS .(* forklift.MergeFS ); isMerged {
81+ if _ , isMerged := merged .FS .(* forklift.MergeFS ); isMerged {
8182 return 0 , errors .Errorf ("the pallet provided for staging should not be a merged pallet!" )
8283 }
8384
84- pallet , repoCacheWithMerged , err := CacheStagingReqs (
85- 0 , pallet , caches .Mirrors , caches .Pallets , caches .Repos , caches .Downloads , false , parallel ,
85+ merged , repoCacheWithMerged , err := CacheStagingReqs (
86+ 0 , merged , caches .Mirrors , caches .Pallets , caches .Repos , caches .Downloads , false , parallel ,
8687 )
8788 if err != nil {
8889 return 0 , errors .Wrap (err , "couldn't cache requirements for staging the pallet" )
8990 }
9091 // Note: we must have all requirements in the cache before we can check their compatibility with
9192 // the Forklift tool version
9293 if err = CheckDeepCompat (
93- pallet , caches .Pallets , repoCacheWithMerged , versions .Core , ignoreToolVersion ,
94+ merged , caches .Pallets , repoCacheWithMerged , versions .Core , ignoreToolVersion ,
9495 ); err != nil {
9596 return 0 , err
9697 }
@@ -102,10 +103,10 @@ func StagePallet(
102103 }
103104 fmt .Printf ("Bundling pallet as stage %d for staged application...\n " , index )
104105 if err = buildBundle (
105- pallet , repoCacheWithMerged , caches .Downloads ,
106+ merged , caches . Pallets , repoCacheWithMerged , caches .Downloads ,
106107 versions .NewBundle , path .Join (stageStore .FS .Path (), fmt .Sprintf ("%d" , index )),
107108 ); err != nil {
108- return index , errors .Wrapf (err , "couldn't bundle pallet %s as stage %d" , pallet .Path (), index )
109+ return index , errors .Wrapf (err , "couldn't bundle pallet %s as stage %d" , merged .Path (), index )
109110 }
110111 if err = SetNextStagedBundle (
111112 indent , stageStore , index , exportPath , versions .Core .Tool , versions .MinSupportedBundle ,
@@ -119,19 +120,18 @@ func StagePallet(
119120}
120121
121122func buildBundle (
122- pallet * forklift.FSPallet ,
123- repoCache forklift.PathedRepoCache , dlCache * forklift.FSDownloadCache ,
123+ merged * forklift.FSPallet ,
124+ palletCache forklift.PathedPalletCache , repoCache forklift.PathedRepoCache ,
125+ dlCache * forklift.FSDownloadCache ,
124126 forkliftVersion , outputPath string ,
125127) (err error ) {
126128 outputBundle := forklift .NewFSBundle (outputPath )
127- // TODO: once we can overlay pallets, save the result of overlaying the pallets to a `overlay`
128- // subdir
129- outputBundle .Manifest , err = newBundleManifest (pallet , repoCache , forkliftVersion )
129+ outputBundle .Manifest , err = newBundleManifest (merged , palletCache , repoCache , forkliftVersion )
130130 if err != nil {
131131 return errors .Wrapf (err , "couldn't create bundle manifest for %s" , outputBundle .FS .Path ())
132132 }
133133
134- depls , _ , err := Check (0 , pallet , repoCache )
134+ depls , _ , err := Check (0 , merged , repoCache )
135135 if err != nil {
136136 return errors .Wrap (err , "couldn't ensure pallet validity" )
137137 }
@@ -141,8 +141,8 @@ func buildBundle(
141141 }
142142 }
143143
144- if err := outputBundle .SetBundledPallet (pallet ); err != nil {
145- return errors .Wrapf (err , "couldn't write pallet %s into bundle" , pallet .Def .Pallet .Path )
144+ if err := outputBundle .SetBundledPallet (merged ); err != nil {
145+ return errors .Wrapf (err , "couldn't write pallet %s into bundle" , merged .Def .Pallet .Path )
146146 }
147147 if err = outputBundle .WriteRepoDefFile (); err != nil {
148148 return errors .Wrap (err , "couldn't write repo declaration into bundle" )
@@ -157,13 +157,15 @@ func buildBundle(
157157}
158158
159159func newBundleManifest (
160- pallet * forklift.FSPallet , repoCache forklift.PathedRepoCache , forkliftVersion string ,
160+ merged * forklift.FSPallet ,
161+ palletCache forklift.PathedPalletCache , repoCache forklift.PathedRepoCache ,
162+ forkliftVersion string ,
161163) (forklift.BundleManifest , error ) {
162164 desc := forklift.BundleManifest {
163165 ForkliftVersion : forkliftVersion ,
164166 Pallet : forklift.BundlePallet {
165- Path : pallet .Path (),
166- Description : pallet .Def .Pallet .Description ,
167+ Path : merged .Path (),
168+ Description : merged .Def .Pallet .Description ,
167169 },
168170 Includes : forklift.BundleInclusions {
169171 Pallets : make (map [string ]forklift.BundlePalletInclusion ),
@@ -173,30 +175,45 @@ func newBundleManifest(
173175 Downloads : make (map [string ][]string ),
174176 Exports : make (map [string ][]string ),
175177 }
176- desc .Pallet .Version , desc .Pallet .Clean = checkGitRepoVersion ( pallet .FS .Path ())
177- palletReqs , err := pallet .LoadFSPalletReqs ("**" )
178+ desc .Pallet .Version , desc .Pallet .Clean = CheckGitRepoVersion ( merged .FS .Path ())
179+ palletReqs , err := merged .LoadFSPalletReqs ("**" )
178180 if err != nil {
179- return desc , errors .Wrapf (err , "couldn't determine pallets required by pallet %s" , pallet .Path ())
181+ return desc , errors .Wrapf (err , "couldn't determine pallets required by pallet %s" , merged .Path ())
180182 }
181- // TODO: once we can overlay pallets, the description of pallet & repo inclusions should probably
182- // be made from the result of overlaying. We could also describe pre-overlay requirements from the
183- // bundled pallet, in desc.Pallet.Requires.
184183 for _ , req := range palletReqs {
185- inclusion := forklift.BundlePalletInclusion {Req : req .PalletReq }
186- // TODO: also check for overridden pallets
187- desc .Includes .Pallets [req .RequiredPath ] = inclusion
184+ if desc .Includes .Pallets [req .RequiredPath ], err = newBundlePalletInclusion (
185+ merged , req , palletCache , true ,
186+ ); err != nil {
187+ return desc , errors .Wrapf (
188+ err , "couldn't generate description of requirement for pallet %s" , req .RequiredPath ,
189+ )
190+ }
188191 }
189- repoReqs , err := pallet .LoadFSRepoReqs ("**" )
192+ repoReqs , err := merged .LoadFSRepoReqs ("**" )
190193 if err != nil {
191- return desc , errors .Wrapf (err , "couldn't determine repos required by pallet %s" , pallet .Path ())
194+ return desc , errors .Wrapf (err , "couldn't determine repos required by pallet %s" , merged .Path ())
192195 }
193196 for _ , req := range repoReqs {
194197 desc .Includes .Repos [req .RequiredPath ] = newBundleRepoInclusion (req , repoCache )
195198 }
199+ if mergeFS , ok := merged .FS .(* forklift.MergeFS ); ok {
200+ imports , err := mergeFS .ListImports ()
201+ if err != nil {
202+ return desc , errors .Wrapf (err , "couldn't list pallet file import groups" )
203+ }
204+ desc .Imports = make (map [string ][]string )
205+ for target , sourceRef := range imports {
206+ sources := make ([]string , 0 , len (sourceRef .Sources ))
207+ for _ , source := range sourceRef .Sources {
208+ sources = append (sources , path .Join (source , sourceRef .Path ))
209+ }
210+ desc .Imports [target ] = sources
211+ }
212+ }
196213 return desc , nil
197214}
198215
199- func checkGitRepoVersion (palletPath string ) (version string , clean bool ) {
216+ func CheckGitRepoVersion (palletPath string ) (version string , clean bool ) {
200217 gitRepo , err := git .Open (filepath .FromSlash (palletPath ))
201218 if err != nil {
202219 return "" , false
@@ -220,6 +237,102 @@ func checkGitRepoVersion(palletPath string) (version string, clean bool) {
220237 return versionString , status .IsClean ()
221238}
222239
240+ func newBundlePalletInclusion (
241+ pallet * forklift.FSPallet , req * forklift.FSPalletReq , palletCache forklift.PathedPalletCache ,
242+ describeImports bool ,
243+ ) (inclusion forklift.BundlePalletInclusion , err error ) {
244+ inclusion = forklift.BundlePalletInclusion {
245+ Req : req .PalletReq ,
246+ Includes : make (map [string ]forklift.BundlePalletInclusion ),
247+ }
248+ for {
249+ if palletCache == nil {
250+ break
251+ }
252+ layeredCache , ok := palletCache .(* forklift.LayeredPalletCache )
253+ if ! ok {
254+ break
255+ }
256+ overlay := layeredCache .Overlay
257+ if overlay == nil {
258+ palletCache = layeredCache .Underlay
259+ continue
260+ }
261+
262+ if loaded , err := overlay .LoadFSPallet (req .RequiredPath , req .VersionLock .Version ); err == nil {
263+ // i.e. the pallet was overridden
264+ inclusion .Override .Path = loaded .FS .Path ()
265+ inclusion .Override .Version , inclusion .Override .Clean = CheckGitRepoVersion (loaded .FS .Path ())
266+ break
267+ }
268+ palletCache = layeredCache .Underlay
269+ }
270+
271+ loaded , err := palletCache .LoadFSPallet (req .RequiredPath , req .VersionLock .Version )
272+ if err != nil {
273+ return inclusion , errors .Wrapf (err , "couldn't load pallet %s" , req .RequiredPath )
274+ }
275+ palletReqs , err := loaded .LoadFSPalletReqs ("**" )
276+ if err != nil {
277+ return inclusion , errors .Wrapf (
278+ err , "couldn't determine pallets required by pallet %s" , loaded .Path (),
279+ )
280+ }
281+ for _ , req := range palletReqs {
282+ if inclusion .Includes [req .RequiredPath ], err = newBundlePalletInclusion (
283+ loaded , req , palletCache , false ,
284+ ); err != nil {
285+ return inclusion , errors .Wrapf (
286+ err , "couldn't generate description of transitive requirement for pallet %s" , loaded .Path (),
287+ )
288+ }
289+ }
290+
291+ if ! describeImports {
292+ return inclusion , nil
293+ }
294+ if inclusion .Imports , err = describePalletImports (pallet , req , palletCache ); err != nil {
295+ return inclusion , errors .Wrapf (err , "couldn't describe file imports for %s" , req .RequiredPath )
296+ }
297+ return inclusion , nil
298+ }
299+
300+ func describePalletImports (
301+ pallet * forklift.FSPallet , req * forklift.FSPalletReq , palletCache forklift.PathedPalletCache ,
302+ ) (fileMappings map [string ]map [string ]string , err error ) {
303+ imports , err := pallet .LoadImports (path .Join (req .RequiredPath , "**/*" ))
304+ if err != nil {
305+ return nil , errors .Wrap (err , "couldn't load file import groups" )
306+ }
307+ allResolved , err := forklift .ResolveImports (pallet , palletCache , imports )
308+ if err != nil {
309+ return nil , errors .Wrap (err , "couldn't resolve file import groups" )
310+ }
311+ requiredPallets := make (map [string ]* forklift.FSPallet ) // pallet path -> pallet
312+ for _ , resolved := range allResolved {
313+ requiredPallets [resolved .Pallet .Path ()] = resolved .Pallet
314+ }
315+ for palletPath , requiredPallet := range requiredPallets {
316+ if requiredPallets [palletPath ], err = forklift .MergeFSPallet (
317+ requiredPallet , palletCache , nil ,
318+ ); err != nil {
319+ return nil , errors .Wrapf (
320+ err , "couldn't compute merged pallet for required pallet %s" , palletPath ,
321+ )
322+ }
323+ }
324+
325+ fileMappings = make (map [string ]map [string ]string )
326+ for _ , resolved := range allResolved {
327+ resolved .Pallet = requiredPallets [req .RequiredPath ]
328+ importName := strings .TrimPrefix (resolved .Name , req .RequiredPath + "/" )
329+ if fileMappings [importName ], err = resolved .Evaluate (palletCache ); err != nil {
330+ return nil , errors .Wrapf (err , "couldn't evaluate file import group %s" , importName )
331+ }
332+ }
333+ return fileMappings , nil
334+ }
335+
223336func newBundleRepoInclusion (
224337 req * forklift.FSRepoReq , repoCache forklift.PathedRepoCache ,
225338) forklift.BundleRepoInclusion {
@@ -238,11 +351,10 @@ func newBundleRepoInclusion(
238351 continue
239352 }
240353
241- if repo , err := overlay .LoadFSRepo (
242- req .RequiredPath , req .VersionLock .Version ,
243- ); err == nil { // i.e. the repo was overridden
244- inclusion .Override .Path = repo .FS .Path ()
245- inclusion .Override .Version , inclusion .Override .Clean = checkGitRepoVersion (repo .FS .Path ())
354+ if loaded , err := overlay .LoadFSRepo (req .RequiredPath , req .VersionLock .Version ); err == nil {
355+ // i.e. the repo was overridden
356+ inclusion .Override .Path = loaded .FS .Path ()
357+ inclusion .Override .Version , inclusion .Override .Clean = CheckGitRepoVersion (loaded .FS .Path ())
246358 return inclusion
247359 }
248360 repoCache = layeredCache .Underlay
0 commit comments