Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion demos/Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
DEMOS := $(shell find . -mindepth 2 -name Makefile -exec dirname {} \; | sort)
DEMOS := $(shell find . -mindepth 2 -maxdepth 2 -name Makefile -exec dirname {} \; | sort)
CLEANS := $(addsuffix .clean,$(DEMOS))

.PHONY: all $(DEMOS)
Expand Down
13 changes: 13 additions & 0 deletions demos/bundles/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
DEMOS := $(shell find . -mindepth 2 -maxdepth 2 -name Makefile -exec dirname {} \; | sort)
CLEANS := $(addsuffix .clean,$(DEMOS))

.PHONY: all $(DEMOS)
all: $(DEMOS)
$(DEMOS):
$(MAKE) -C $@


.PHONY: clean $(CLEANS)
clean: $(CLEANS)
$(CLEANS): %.clean:
$(MAKE) -C $* clean
18 changes: 12 additions & 6 deletions demos/bundles/registryv1/Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
.PHONY: all
all:
ls *.kpmspec.yaml | xargs -n1 kpm build
DEMOS := $(shell find . -mindepth 2 -maxdepth 2 -name Makefile -exec dirname {} \; | sort)
CLEANS := $(addsuffix .clean,$(DEMOS))

.PHONY:
clean:
rm -f *.kpm
.PHONY: all $(DEMOS)
all: $(DEMOS)
$(DEMOS):
$(MAKE) -C $@


.PHONY: clean $(CLEANS)
clean: $(CLEANS)
$(CLEANS): %.clean:
$(MAKE) -C $* clean
7 changes: 7 additions & 0 deletions demos/bundles/registryv1/argocd-operator/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.PHONY: all
all:
ls *.kpmspec.yaml | xargs -n1 kpm build

.PHONY:
clean:
rm -f *.kpm
283 changes: 172 additions & 111 deletions go.mod

Large diffs are not rendered by default.

718 changes: 445 additions & 273 deletions go.sum

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions internal/pkg/bundle/registry/internal/do.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package internal

import "errors"

func DoAll(funcs ...func() error) error {
var errs []error
for _, fn := range funcs {
errs = append(errs, fn())
}
return errors.Join(errs...)
}
77 changes: 77 additions & 0 deletions internal/pkg/bundle/registry/internal/supported_kinds.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package internal

import (
consolev1 "github.com/openshift/api/console/v1"
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
autoscalingv1 "k8s.io/api/autoscaling/v1"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
policyv1 "k8s.io/api/policy/v1"
rbacv1 "k8s.io/api/rbac/v1"
schedulingv1 "k8s.io/api/scheduling/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"

ofv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
)

var SupportedKinds = sets.New[string](
// corev1
"ConfigMap",
"Secret",
"Service",
"ServiceAccount",

// apiextensionsv1
"CustomResourceDefinition",

// rbacv1
"ClusterRole",
"ClusterRoleBinding",
"Role",
"RoleBinding",

// ofv1alpha1
ofv1alpha1.ClusterServiceVersionKind,

// schedulingv1
"PriorityClass",

// policyv1
"PodDisruptionBudget",

// autoscalingv1
"VerticalPodAutoscaler",

// monitoringv1
"PrometheusRule",
"ServiceMonitor",

// console
"ConsoleYAMLSample",
"ConsoleQuickStart",
"ConsoleCLIDownload",
"ConsoleLink",
)

func initScheme() *runtime.Scheme {
scheme := runtime.NewScheme()
_ = corev1.AddToScheme(scheme)
_ = apiextensionsv1.AddToScheme(scheme)
_ = rbacv1.AddToScheme(scheme)
_ = ofv1alpha1.AddToScheme(scheme)
_ = schedulingv1.AddToScheme(scheme)
_ = policyv1.AddToScheme(scheme)
_ = autoscalingv1.AddToScheme(scheme)
_ = monitoringv1.AddToScheme(scheme)
_ = networkingv1.AddToScheme(scheme)
_ = consolev1.AddToScheme(scheme)
return scheme
}

var SupportedKindsScheme *runtime.Scheme

func init() {
SupportedKindsScheme = initScheme()
}
187 changes: 121 additions & 66 deletions internal/pkg/bundle/registry/v1/bundle.go
Original file line number Diff line number Diff line change
@@ -1,97 +1,152 @@
package v1

import (
"bytes"
"compress/gzip"
"context"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
"io"
"io/fs"
"path/filepath"
"testing/fstest"

"github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2"
"oras.land/oras-go/v2/content"

"github.com/joelanford/kpm/internal/pkg/util/tar"
)

const MediaType = "registry+v1"
const (
mediaType = "registry+v1"
manifestsDirectory = "manifests/"
metadataDirectory = "metadata/"
)

type Bundle struct {
fsys fs.FS
manifests manifests
metadata metadata
manifests *manifests
metadata *metadata
}

csv v1alpha1.ClusterServiceVersion
type BundleLoader interface {
Load() (*Bundle, error)
}

func LoadFS(fsys fs.FS) (*Bundle, error) {
metadataFsys, err := fs.Sub(fsys, "metadata")
if err != nil {
type bundleFSLoader struct {
fsys fs.FS
}

func NewBundleFSLoader(fsys fs.FS) BundleLoader {
return &bundleFSLoader{fsys: fsys}
}

func (b *bundleFSLoader) Load() (*Bundle, error) {
manifestsFS, manifestsFSErr := fs.Sub(b.fsys, filepath.Clean(manifestsDirectory))
metadataFS, metadataFSErr := fs.Sub(b.fsys, filepath.Clean(metadataDirectory))
if err := errors.Join(manifestsFSErr, metadataFSErr); err != nil {
return nil, err
}
manifestsFsys, err := fs.Sub(fsys, "manifests")
if err != nil {

manifestsLoader := &manifestsFSLoader{manifestsFS}
metadataLoader := &metadataFSLoader{metadataFS}

bundleManifests, manifestsErr := manifestsLoader.Load()
bundleMetadata, metadataErr := metadataLoader.Load()
if err := errors.Join(manifestsErr, metadataErr); err != nil {
return nil, err
}
b := &Bundle{
fsys: fsys,
metadata: metadata{fsys: metadataFsys},
manifests: manifests{fsys: manifestsFsys},
}
for _, fn := range []func() error{
b.load,
b.validate,
b.complete,
} {
if err := fn(); err != nil {
return nil, err
}
}
return b, nil

return &Bundle{manifests: bundleManifests, metadata: bundleMetadata}, nil
}

func (b *Bundle) load() error {
if err := do(
b.metadata.load,
b.manifests.load,
); err != nil {
return fmt.Errorf("failed to load bundle: %v", err)
}
return nil
func (b *Bundle) tag() string {
return b.manifests.CSV().Value().Spec.Version.String()
}

func (b *Bundle) validate() error {
if err := do(
b.metadata.validate,
b.manifests.validate,
); err != nil {
return fmt.Errorf("failed to validate bundle: %v", err)
}
return nil
func (b *Bundle) ID() string {
return fmt.Sprintf("%s.v%s", b.metadata.PackageName(), b.tag())
}

func (b *Bundle) complete() error {
if err := do(
b.extractCSV,
); err != nil {
return fmt.Errorf("failed to complete bundle: %v", err)
}
return nil
func (b *Bundle) imageNameTag() string {
return fmt.Sprintf("%s:%s", b.metadata.PackageName(), b.tag())
}

func (b *Bundle) extractCSV() error {
for _, mf := range b.manifests.manifestFiles {
for _, obj := range mf.objects {
if obj.GetObjectKind().GroupVersionKind().Kind != v1alpha1.ClusterServiceVersionKind {
continue
}
csv := obj.(*v1alpha1.ClusterServiceVersion)
b.csv = *csv
return nil
}
func (b *Bundle) MarshalOCI(ctx context.Context, target oras.Target) (ocispec.Descriptor, error) {
config, layers, err := b.pushConfigAndLayers(ctx, target)
if err != nil {
return ocispec.Descriptor{}, err
}
// this should never happen because the earlier validate step ensures there is exactly one CSV in the manifests.
return fmt.Errorf("failed to find ClusterServiceVersion in bundle")

manifest := ocispec.Manifest{
Versioned: specs.Versioned{SchemaVersion: 2},
MediaType: ocispec.MediaTypeImageManifest,
Config: config,
Layers: layers,
}
manifestData, err := json.Marshal(manifest)
if err != nil {
return ocispec.Descriptor{}, err
}

desc, err := oras.PushBytes(ctx, target, ocispec.MediaTypeImageManifest, manifestData)
if err != nil {
return ocispec.Descriptor{}, fmt.Errorf("failed to push bundle: %v", err)
}
if err := target.Tag(ctx, desc, b.imageNameTag()); err != nil {
return ocispec.Descriptor{}, fmt.Errorf("failed to tag bundle: %v", err)
}
return desc, nil
}

func (b *Bundle) toFS() fs.FS {
fsys := fstest.MapFS{}
b.manifests.addToFS(fsys)
b.metadata.addToFS(fsys)
return fsys
}

func do(funcs ...func() error) error {
var errs []error
for _, fn := range funcs {
errs = append(errs, fn())
func (b *Bundle) pushConfigAndLayers(ctx context.Context, pusher content.Pusher) (ocispec.Descriptor, []ocispec.Descriptor, error) {
var layerData bytes.Buffer
diffIDHash := sha256.New()

bundleFS := b.toFS()
if err := func() error {
gzipWriter := gzip.NewWriter(&layerData)
defer gzipWriter.Close()
mw := io.MultiWriter(diffIDHash, gzipWriter)
return tar.Directory(mw, bundleFS)
}(); err != nil {
return ocispec.Descriptor{}, nil, err
}

annotationsFile := b.metadata.Annotations()
cfg := ocispec.Image{
Config: ocispec.ImageConfig{
Labels: annotationsFile.Value().Annotations,
},
RootFS: ocispec.RootFS{
Type: "layers",
DiffIDs: []digest.Digest{
digest.NewDigest(digest.SHA256, diffIDHash),
},
},
}
cfgData, err := json.Marshal(cfg)
if err != nil {
return ocispec.Descriptor{}, nil, err
}
cfgDesc, err := oras.PushBytes(ctx, pusher, ocispec.MediaTypeImageConfig, cfgData)
if err != nil {
return ocispec.Descriptor{}, nil, err
}
layerDesc, err := oras.PushBytes(ctx, pusher, ocispec.MediaTypeImageLayerGzip, layerData.Bytes())
if err != nil {
return ocispec.Descriptor{}, nil, err
}
return errors.Join(errs...)
return cfgDesc, []ocispec.Descriptor{layerDesc}, nil
}
Loading