Skip to content

Commit 40436e0

Browse files
committed
render: support rendering FBC from bundle and packagemanifest directories
In order to generate expected olm.bundles, this commit also adds a new --image-ref-template flag to the `render` subcommand, which callers can use to generate image references from source data based on package name, bundle name, and bundle version. Signed-off-by: Joe Lanford <[email protected]>
1 parent d74ce59 commit 40436e0

File tree

8 files changed

+501
-42
lines changed

8 files changed

+501
-42
lines changed

alpha/action/render.go

Lines changed: 209 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"sort"
1313
"strings"
1414
"sync"
15+
"text/template"
1516

1617
"github.com/h2non/filetype"
1718
"github.com/h2non/filetype/matchers"
@@ -40,6 +41,9 @@ const (
4041
RefDCImage
4142
RefDCDir
4243

44+
RefBundleDir
45+
RefPackageManifestDir
46+
4347
RefAll = 0
4448
)
4549

@@ -50,10 +54,11 @@ func (r RefType) Allowed(refType RefType) bool {
5054
var ErrNotAllowed = errors.New("not allowed")
5155

5256
type Render struct {
53-
Refs []string
54-
Registry image.Registry
55-
AllowedRefMask RefType
56-
Migrate bool
57+
Refs []string
58+
Registry image.Registry
59+
AllowedRefMask RefType
60+
Migrate bool
61+
ImageRefTemplate *template.Template
5762

5863
skipSqliteDeprecationLog bool
5964
}
@@ -125,25 +130,50 @@ func (r Render) createRegistry() (*containerdregistry.Registry, error) {
125130
}
126131

127132
func (r Render) renderReference(ctx context.Context, ref string) (*declcfg.DeclarativeConfig, error) {
128-
if stat, serr := os.Stat(ref); serr == nil {
129-
if stat.IsDir() {
130-
if !r.AllowedRefMask.Allowed(RefDCDir) {
131-
return nil, fmt.Errorf("cannot render declarative config directory: %w", ErrNotAllowed)
132-
}
133-
return declcfg.LoadFS(ctx, os.DirFS(ref))
134-
} else {
135-
// The only supported file type is an sqlite DB file,
136-
// since declarative configs will be in a directory.
137-
if err := checkDBFile(ref); err != nil {
138-
return nil, err
133+
stat, err := os.Stat(ref)
134+
if err != nil {
135+
return r.imageToDeclcfg(ctx, ref)
136+
}
137+
if stat.IsDir() {
138+
dirEntries, err := os.ReadDir(ref)
139+
if err != nil {
140+
return nil, err
141+
}
142+
if isBundle(dirEntries) {
143+
// Looks like a bundle directory
144+
if !r.AllowedRefMask.Allowed(RefBundleDir) {
145+
return nil, fmt.Errorf("cannot render bundle directory %q: %w", ref, ErrNotAllowed)
139146
}
140-
if !r.AllowedRefMask.Allowed(RefSqliteFile) {
141-
return nil, fmt.Errorf("cannot render sqlite file: %w", ErrNotAllowed)
147+
return r.renderBundleDirectory(ref)
148+
} else if isPackageManifest(dirEntries) {
149+
// Looks like a package manifest directory
150+
if !r.AllowedRefMask.Allowed(RefPackageManifestDir) {
151+
return nil, fmt.Errorf("cannot render package manifest directory: %w", ErrNotAllowed)
142152
}
143-
return sqliteToDeclcfg(ctx, ref)
153+
return r.renderPackageManifest(ctx, ref)
154+
}
155+
156+
// Otherwise, assume it is a declarative config root directory.
157+
if !r.AllowedRefMask.Allowed(RefDCDir) {
158+
return nil, fmt.Errorf("cannot render declarative config directory: %w", ErrNotAllowed)
144159
}
160+
return declcfg.LoadFS(ctx, os.DirFS(ref))
161+
}
162+
// The only supported file type is an sqlite DB file,
163+
// since declarative configs will be in a directory.
164+
if err := checkDBFile(ref); err != nil {
165+
return nil, err
166+
}
167+
if !r.AllowedRefMask.Allowed(RefSqliteFile) {
168+
return nil, fmt.Errorf("cannot render sqlite file: %w", ErrNotAllowed)
145169
}
146-
return r.imageToDeclcfg(ctx, ref)
170+
171+
db, err := sqlite.Open(ref)
172+
if err != nil {
173+
return nil, err
174+
}
175+
defer db.Close()
176+
return sqliteToDeclcfg(ctx, db)
147177
}
148178

149179
func (r Render) imageToDeclcfg(ctx context.Context, imageRef string) (*declcfg.DeclarativeConfig, error) {
@@ -169,7 +199,12 @@ func (r Render) imageToDeclcfg(ctx context.Context, imageRef string) (*declcfg.D
169199
if !r.AllowedRefMask.Allowed(RefSqliteImage) {
170200
return nil, fmt.Errorf("cannot render sqlite image: %w", ErrNotAllowed)
171201
}
172-
cfg, err = sqliteToDeclcfg(ctx, filepath.Join(tmpDir, dbFile))
202+
db, err := sqlite.Open(filepath.Join(tmpDir, dbFile))
203+
if err != nil {
204+
return nil, err
205+
}
206+
defer db.Close()
207+
cfg, err = sqliteToDeclcfg(ctx, db)
173208
if err != nil {
174209
return nil, err
175210
}
@@ -190,10 +225,11 @@ func (r Render) imageToDeclcfg(ctx context.Context, imageRef string) (*declcfg.D
190225
return nil, err
191226
}
192227

193-
cfg, err = bundleToDeclcfg(img.Bundle)
228+
bundle, err := bundleToDeclcfg(img.Bundle)
194229
if err != nil {
195230
return nil, err
196231
}
232+
cfg = &declcfg.DeclarativeConfig{Bundles: []declcfg.Bundle{*bundle}}
197233
} else {
198234
labelKeys := sets.StringKeySet(labels)
199235
labelVals := []string{}
@@ -221,17 +257,11 @@ func checkDBFile(ref string) error {
221257
return nil
222258
}
223259

224-
func sqliteToDeclcfg(ctx context.Context, dbFile string) (*declcfg.DeclarativeConfig, error) {
260+
func sqliteToDeclcfg(ctx context.Context, db *sql.DB) (*declcfg.DeclarativeConfig, error) {
225261
logDeprecationMessage.Do(func() {
226262
sqlite.LogSqliteDeprecation()
227263
})
228264

229-
db, err := sqlite.Open(dbFile)
230-
if err != nil {
231-
return nil, err
232-
}
233-
defer db.Close()
234-
235265
migrator, err := sqlite.NewSQLLiteMigrator(db)
236266
if err != nil {
237267
return nil, err
@@ -303,7 +333,7 @@ func populateDBRelatedImages(ctx context.Context, cfg *declcfg.DeclarativeConfig
303333
return nil
304334
}
305335

306-
func bundleToDeclcfg(bundle *registry.Bundle) (*declcfg.DeclarativeConfig, error) {
336+
func bundleToDeclcfg(bundle *registry.Bundle) (*declcfg.Bundle, error) {
307337
objs, props, err := registry.ObjectsAndPropertiesFromBundle(bundle)
308338
if err != nil {
309339
return nil, fmt.Errorf("get properties for bundle %q: %v", bundle.Name, err)
@@ -323,7 +353,7 @@ func bundleToDeclcfg(bundle *registry.Bundle) (*declcfg.DeclarativeConfig, error
323353
}
324354
}
325355

326-
dBundle := declcfg.Bundle{
356+
return &declcfg.Bundle{
327357
Schema: "olm.bundle",
328358
Name: bundle.Name,
329359
Package: bundle.Package,
@@ -332,9 +362,7 @@ func bundleToDeclcfg(bundle *registry.Bundle) (*declcfg.DeclarativeConfig, error
332362
RelatedImages: relatedImages,
333363
Objects: objs,
334364
CsvJSON: string(csvJson),
335-
}
336-
337-
return &declcfg.DeclarativeConfig{Bundles: []declcfg.Bundle{dBundle}}, nil
365+
}, nil
338366
}
339367

340368
func getRelatedImages(b *registry.Bundle) ([]declcfg.RelatedImage, error) {
@@ -363,7 +391,7 @@ func getRelatedImages(b *registry.Bundle) ([]declcfg.RelatedImage, error) {
363391
allImages = allImages.Insert(ri.Image)
364392
}
365393

366-
if !allImages.Has(b.BundleImage) {
394+
if b.BundleImage != "" && !allImages.Has(b.BundleImage) {
367395
relatedImages = append(relatedImages, declcfg.RelatedImage{
368396
Image: b.BundleImage,
369397
})
@@ -454,3 +482,150 @@ func combineConfigs(cfgs []declcfg.DeclarativeConfig) *declcfg.DeclarativeConfig
454482
}
455483
return out
456484
}
485+
486+
func isBundle(entries []os.DirEntry) bool {
487+
foundManifests := false
488+
foundMetadata := false
489+
for _, e := range entries {
490+
if e.IsDir() {
491+
switch e.Name() {
492+
case "manifests":
493+
foundManifests = true
494+
case "metadata":
495+
foundMetadata = true
496+
}
497+
}
498+
if foundMetadata && foundManifests {
499+
return true
500+
}
501+
}
502+
return false
503+
}
504+
505+
func isPackageManifest(entries []os.DirEntry) bool {
506+
for _, e := range entries {
507+
if strings.HasSuffix(e.Name(), ".package.yaml") || strings.HasSuffix(e.Name(), ".package.yml") {
508+
return true
509+
}
510+
}
511+
return false
512+
}
513+
514+
type imageReferenceTemplateData struct {
515+
Package string
516+
Name string
517+
Version string
518+
}
519+
520+
func (r *Render) renderPackageManifest(ctx context.Context, ref string) (*declcfg.DeclarativeConfig, error) {
521+
db, err := sql.Open("sqlite3", fmt.Sprintf("file:%s?mode=memory&cache=shared&_foreign_keys=on", ref))
522+
if err != nil {
523+
return nil, err
524+
}
525+
defer db.Close()
526+
527+
dbLoader, err := sqlite.NewSQLLiteLoader(db)
528+
if err != nil {
529+
return nil, err
530+
}
531+
if err := dbLoader.Migrate(ctx); err != nil {
532+
return nil, err
533+
}
534+
535+
loader := sqlite.NewSQLLoaderForDirectory(dbLoader, ref)
536+
if err := loader.Populate(); err != nil {
537+
return nil, fmt.Errorf("error loading manifests from directory: %s", err)
538+
}
539+
540+
if err := r.templateDBImageRefs(ctx, db); err != nil {
541+
return nil, fmt.Errorf("error templating image references: %v", err)
542+
}
543+
544+
return sqliteToDeclcfg(ctx, db)
545+
}
546+
547+
func (r *Render) templateDBImageRefs(ctx context.Context, db *sql.DB) error {
548+
if r.ImageRefTemplate == nil {
549+
return nil
550+
}
551+
tx, err := db.BeginTx(ctx, nil)
552+
rows, err := tx.QueryContext(ctx, "SELECT DISTINCT en.package_name, op.name, op.version FROM operatorbundle op JOIN channel_entry en ON op.name = en.operatorbundle_name;")
553+
if err != nil {
554+
return err
555+
}
556+
defer rows.Close()
557+
558+
for rows.Next() {
559+
var (
560+
pkgName sql.NullString
561+
name sql.NullString
562+
version sql.NullString
563+
)
564+
if err := rows.Scan(&pkgName, &name, &version); err != nil {
565+
return err
566+
}
567+
if !pkgName.Valid || !name.Valid || !version.Valid {
568+
continue
569+
}
570+
var buf strings.Builder
571+
tmplInput := imageReferenceTemplateData{
572+
Package: pkgName.String,
573+
Name: name.String,
574+
Version: version.String,
575+
}
576+
if err := r.ImageRefTemplate.Execute(&buf, tmplInput); err != nil {
577+
return err
578+
}
579+
if _, err := tx.ExecContext(ctx, "UPDATE operatorbundle SET bundlePath = ? WHERE name = ?;", buf.String(), name.String); err != nil {
580+
if rollbackErr := tx.Rollback(); rollbackErr != nil {
581+
return fmt.Errorf("failed to rollback transaction: %v; original error: %v", rollbackErr, err)
582+
}
583+
return err
584+
}
585+
}
586+
return tx.Commit()
587+
}
588+
589+
func (r *Render) renderBundleDirectory(ref string) (*declcfg.DeclarativeConfig, error) {
590+
img, err := registry.NewImageInput(image.SimpleReference(""), ref)
591+
if err != nil {
592+
return nil, err
593+
}
594+
if err := r.templateBundleImageRef(img.Bundle); err != nil {
595+
return nil, fmt.Errorf("failed templating image reference from bundle for %q: %v", ref, err)
596+
}
597+
fbcBundle, err := bundleToDeclcfg(img.Bundle)
598+
if err != nil {
599+
return nil, err
600+
}
601+
return &declcfg.DeclarativeConfig{Bundles: []declcfg.Bundle{*fbcBundle}}, nil
602+
}
603+
604+
func (r *Render) templateBundleImageRef(bundle *registry.Bundle) error {
605+
if r.ImageRefTemplate == nil {
606+
return nil
607+
}
608+
609+
var pkgProp property.Package
610+
for _, p := range bundle.Properties {
611+
if p.Type != property.TypePackage {
612+
continue
613+
}
614+
if err := json.Unmarshal(p.Value, &pkgProp); err != nil {
615+
return err
616+
}
617+
break
618+
}
619+
620+
var buf strings.Builder
621+
tmplInput := imageReferenceTemplateData{
622+
Package: bundle.Package,
623+
Name: bundle.Name,
624+
Version: pkgProp.Version,
625+
}
626+
if err := r.ImageRefTemplate.Execute(&buf, tmplInput); err != nil {
627+
return err
628+
}
629+
bundle.BundleImage = buf.String()
630+
return nil
631+
}

0 commit comments

Comments
 (0)