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
149 changes: 149 additions & 0 deletions pkg/configmap/configmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package configmap

import (
"errors"
"fmt"
libbundle "github.com/operator-framework/operator-registry/pkg/lib/bundle"
"github.com/operator-framework/operator-registry/pkg/registry"
"github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
"strings"
)

func NewBundleLoader() *BundleLoader {
logger := logrus.NewEntry(logrus.New())
return NewBundleLoaderWithLogger(logger)
}

func NewBundleLoaderWithLogger(logger *logrus.Entry) *BundleLoader {
return &BundleLoader{
logger: logger,
}
}

// Manifest contains a bundle and a PackageManifest.
type Manifest struct {
Bundle *registry.Bundle
PackageManifest *registry.PackageManifest
}

type BundleLoader struct {
logger *logrus.Entry
}

// Load accepts a ConfigMap object, iterates through the Data section and
// creates an operator registry Bundle object.
// If the Data section has a PackageManifest resource then it is also
// deserialized and included in the result.
func (l *BundleLoader) Load(cm *corev1.ConfigMap) (manifest *Manifest, err error) {
if cm == nil {
err = errors.New("ConfigMap must not be <nil>")
return
}

logger := l.logger.WithFields(logrus.Fields{
"configmap": fmt.Sprintf("%s/%s", cm.GetNamespace(), cm.GetName()),
})

bundle, _, bundleErr := loadBundle(logger, cm.Data)
if bundleErr != nil {
err = fmt.Errorf("failed to extract bundle from configmap - %v", bundleErr)
return
}

// get package manifest information from required annotations
annotations := cm.GetAnnotations()
if len(annotations) == 0 {
err = fmt.Errorf("missing required annoations on configmap %v", cm.GetName())
return
}

switch mediatype := annotations[libbundle.MediatypeLabel]; mediatype {
case "registry+v1":
// supported, proceed
default:
err = fmt.Errorf("failed to parse annotations due to unsupported media type %v", mediatype)
return
}

var packageChannels []registry.PackageChannel
channels := strings.Split(annotations[libbundle.ChannelsLabel], ",")
for _, channel := range channels {
packageChannels = append(packageChannels, registry.PackageChannel{
Name: channel,
})
}

manifest = &Manifest{
Bundle: bundle,
PackageManifest: &registry.PackageManifest{
PackageName: annotations[libbundle.PackageLabel],
Channels: packageChannels,
DefaultChannelName: annotations[libbundle.ChannelDefaultLabel],
},
}
return
}

func loadBundle(entry *logrus.Entry, data map[string]string) (bundle *registry.Bundle, skipped map[string]string, err error) {
bundle = &registry.Bundle{}
skipped = map[string]string{}

// Add kube resources to the bundle.
for name, content := range data {
reader := strings.NewReader(content)
logger := entry.WithFields(logrus.Fields{
"key": name,
})

resource, decodeErr := registry.DecodeUnstructured(reader)
if decodeErr != nil {
logger.Infof("skipping due to decode error - %v", decodeErr)

// It may not be not a kube resource, let's add it to the skipped
// list so the caller can act on ot.
skipped[name] = content
continue
}

// It's a valid kube resource,
// could be a crd, csv or other raw kube manifest(s).
bundle.Add(resource)
logger.Infof("added to bundle, Kind=%s", resource.GetKind())
}

return
}

func loadPackageManifest(entry *logrus.Entry, resources map[string]string) *registry.PackageManifest {
// Let's inspect if any of the skipped non kube resources is a PackageManifest type.
// The first one we run into will be selected.
for name, content := range resources {
logger := entry.WithFields(logrus.Fields{
"key": name,
})

// Is it a package yaml file?
reader := strings.NewReader(content)
packageManifest, decodeErr := registry.DecodePackageManifest(reader)
if decodeErr != nil {
logger.Infof("skipping, not a PackageManifest type - %v", decodeErr)
continue
}

logger.Infof("found a PackageManifest type resource - packageName=%s", packageManifest.PackageName)

return packageManifest
}

return nil
}

func extract(data map[string]string) []string {
resources := make([]string, 0)
for _, v := range data {
resources = append(resources, v)
}

return resources
}
124 changes: 124 additions & 0 deletions pkg/configmap/configmap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package configmap

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/yaml"
)

func TestLoad(t *testing.T) {
tests := []struct {
name string
source string
assertFunc func(t *testing.T, manifestGot *Manifest)
}{
{
name: "BundleWithCsvAndCrd",
source: "testdata/bundle.cm.yaml",
assertFunc: func(t *testing.T, manifestGot *Manifest) {
assert.NotNil(t, manifestGot.Bundle)
assert.NotNil(t, manifestGot.PackageManifest)

csvGot, errGot := manifestGot.Bundle.ClusterServiceVersion()
assert.NoError(t, errGot)
assert.NotNil(t, csvGot)

crdListGot, errGot := manifestGot.Bundle.CustomResourceDefinitions()
assert.NoError(t, errGot)
assert.Equal(t, 1, len(crdListGot))
},
},
{
name: "BundleWithBuiltInKubeTypes",
source: "testdata/bundle-with-kube-resources.cm.yaml",
assertFunc: func(t *testing.T, manifestGot *Manifest) {
assert.NotNil(t, manifestGot.Bundle)
assert.NotNil(t, manifestGot.Bundle.Objects)

objects := manifestGot.Bundle.Objects
assert.Equal(t, 1, len(objects))
assert.True(t, objects[0].GetKind() == "Foo")
},
},
{
name: "BundleWithMultipleCsvs",
source: "testdata/bundle-with-multiple-csvs.cm.yaml",
assertFunc: func(t *testing.T, manifestGot *Manifest) {
assert.NotNil(t, manifestGot.Bundle)

csvGot, errGot := manifestGot.Bundle.ClusterServiceVersion()
assert.NoError(t, errGot)
assert.NotNil(t, csvGot)
assert.True(t, csvGot.GetName() == "first" || csvGot.GetName() == "second")
},
},
{
name: "BundleWithBadResource",
source: "testdata/bundle-with-bad-resource.cm.yaml",
assertFunc: func(t *testing.T, manifestGot *Manifest) {
assert.NotNil(t, manifestGot.Bundle)

csvGot, errGot := manifestGot.Bundle.ClusterServiceVersion()
assert.NoError(t, errGot)
assert.NotNil(t, csvGot)
},
},
{
name: "BundleWithAll",
source: "testdata/bundle-with-all.yaml",
assertFunc: func(t *testing.T, manifestGot *Manifest) {
assert.NotNil(t, manifestGot.Bundle)
assert.NotNil(t, manifestGot.PackageManifest)

csvGot, errGot := manifestGot.Bundle.ClusterServiceVersion()
assert.NoError(t, errGot)
assert.NotNil(t, csvGot)
assert.True(t, csvGot.GetName() == "kiali-operator.v1.4.2")

crdListGot, errGot := manifestGot.Bundle.CustomResourceDefinitions()
assert.NoError(t, errGot)
assert.Equal(t, 2, len(crdListGot))

providedAPIList, errGot := manifestGot.Bundle.ProvidedAPIs()
assert.NoError(t, errGot)
assert.Equal(t, 2, len(providedAPIList))

requiredAPIList, errGot := manifestGot.Bundle.RequiredAPIs()
assert.NoError(t, errGot)
assert.Equal(t, 0, len(requiredAPIList))
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cm := loadfromFile(t, tt.source)

loader := NewBundleLoader()
manifestGot, errGot := loader.Load(cm)

assert.NoError(t, errGot)
assert.NotNil(t, manifestGot)

if tt.assertFunc != nil {
tt.assertFunc(t, manifestGot)
}
})
}
}

func loadfromFile(t *testing.T, path string) *corev1.ConfigMap {
reader, err := os.Open(path)
require.NoError(t, err, "unable to load from file %s", path)

decoder := yaml.NewYAMLOrJSONDecoder(reader, 30)
bundle := &corev1.ConfigMap{}
err = decoder.Decode(bundle)
require.NoError(t, err, "could not decode into configmap, file=%s", path)

return bundle
}
Loading