Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Begin usage of operator-framework APIs #767

Merged
merged 1 commit into from
Aug 26, 2022
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
107 changes: 18 additions & 89 deletions certification/internal/bundle/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package bundle

import (
"context"
"errors"
"fmt"
"io"
"os"
Expand All @@ -12,15 +11,12 @@ import (
rbacv1 "k8s.io/api/rbac/v1"

"github.com/blang/semver"
operatorv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/operator-framework/api/pkg/manifests"
"github.com/redhat-openshift-ecosystem/openshift-preflight/certification/internal/operatorsdk"
log "github.com/sirupsen/logrus"
"sigs.k8s.io/yaml"
)

// versionsKey is the OpenShift versions in annotations.yaml that lists the versions allowed for an operator
const versionsKey = "com.redhat.openshift.versions"

// This table signifies what the NEXT release of OpenShift will
// deprecate, not what it matches up to.
var ocpToKubeVersion = map[string]string{
Expand Down Expand Up @@ -54,19 +50,19 @@ func Validate(ctx context.Context, operatorSdk operatorSdk, imagePath string) (*
if err != nil {
return nil, fmt.Errorf("could not open annotations.yaml: %v", err)
}
annotations, err := GetAnnotations(ctx, annotationsFile)
annotations, err := LoadAnnotations(ctx, annotationsFile)
if err != nil {
return nil, fmt.Errorf("unable to get annotations.yaml from the bundle: %v", err)
}

if versions, ok := annotations[versionsKey]; ok {
if annotations.OpenshiftVersions != "" {
// Check that the label range contains >= 4.9
targetVersion, err := targetVersion(versions)
targetVersion, err := targetVersion(annotations.OpenshiftVersions)
if err != nil {
// Could not parse the version, which probably means the annotation is invalid
return nil, fmt.Errorf("%v", err)
}
if k8sVer, ok := ocpToKubeVersion[targetVersion]; ok {
if k8sVer, found := ocpToKubeVersion[targetVersion]; found {
log.Debugf("OpenShift %s detected in annotations. Running with additional checks enabled.", targetVersion)
opts.OptionalValues = make(map[string]string)
opts.OptionalValues["k8s-version"] = k8sVer
Expand Down Expand Up @@ -138,100 +134,33 @@ func cleanStringToGetTheVersionToParse(value string) string {
return value
}

// GetAnnotations accepts a context, and an io.Reader that is expected to provide
// the annotations.yaml, and parses the annotations from there
func GetAnnotations(ctx context.Context, r io.Reader) (map[string]string, error) {
fileContents, err := io.ReadAll(r)
// LoadAnnotations reads an operator bundle's annotations.yaml from r.
func LoadAnnotations(ctx context.Context, r io.Reader) (*Annotations, error) {
annFile, err := io.ReadAll(r)
if err != nil {
return nil, fmt.Errorf("fail to read metadata/annotation.yaml file in bundle: %v", err)
}

annotations, err := ExtractAnnotationsBytes(ctx, fileContents)
if err != nil {
return nil, fmt.Errorf("metadata/annotations.yaml found but is malformed: %v", err)
}

return annotations, nil
}

// extractAnnotationsBytes reads the annotation data read from a file and returns the expected format for that yaml
// represented as a map[string]string.
func ExtractAnnotationsBytes(ctx context.Context, annotationBytes []byte) (map[string]string, error) {
type metadata struct {
Annotations map[string]string
}

if len(annotationBytes) == 0 {
return nil, errors.New("the annotations file was empty")
}

var bundleMeta metadata
if err := yaml.Unmarshal(annotationBytes, &bundleMeta); err != nil {
return nil, fmt.Errorf("metadata/annotations.yaml found but is malformed: %v", err)
}

return bundleMeta.Annotations, nil
}

func GetCsvFilePathFromBundle(imageDir string) (string, error) {
log.Trace("reading clusterserviceversion file from the bundle")
log.Debug("image directory is ", imageDir)
matches, err := filepath.Glob(filepath.Join(imageDir, "manifests", "*.clusterserviceversion.yaml"))
if err != nil {
return "", fmt.Errorf("glob pattern is malformed: %v", err)
}
if len(matches) == 0 {
return "", fmt.Errorf("unable to find clusterserviceversion file in the bundle image: %v", os.ErrNotExist)
}
if len(matches) > 1 {
return "", fmt.Errorf("more than one CSV file detected in bundle")
}
log.Debugf("The path to csv file is %s", matches[0])
return matches[0], nil
}

func csvFromReader(ctx context.Context, csvReader io.Reader) (*operatorv1alpha1.ClusterServiceVersion, error) {
var csv operatorv1alpha1.ClusterServiceVersion
bts, err := io.ReadAll(csvReader)
if err != nil {
return nil, fmt.Errorf("could not get CSV from reader: %v", err)
}
err = yaml.Unmarshal(bts, &csv)
if err != nil {
return nil, fmt.Errorf("malformed CSV detected: %v", err)
if len(annFile) == 0 {
return nil, fmt.Errorf("annotations file was empty")
}

return &csv, nil
}

func GetSupportedInstallModes(ctx context.Context, csvReader io.Reader) (map[string]bool, error) {
csv, err := csvFromReader(ctx, csvReader)
if err != nil {
return nil, err
var annotationsFile AnnotationsFile
if err := yaml.Unmarshal(annFile, &annotationsFile); err != nil {
return nil, fmt.Errorf("unable to load the annotations file: %v", err)
}

installedModes := make(map[string]bool, len(csv.Spec.InstallModes))
for _, v := range csv.Spec.InstallModes {
if v.Supported {
installedModes[string(v.Type)] = true
}
}
return installedModes, nil
return &annotationsFile.Annotations, nil
}

// GetSecurityContextConstraints returns an string array of SCC resource names requested by the operator as specified
// in the csv
func GetSecurityContextConstraints(ctx context.Context, csvReader io.Reader) ([]string, error) {
var csv operatorv1alpha1.ClusterServiceVersion
bts, err := io.ReadAll(csvReader)
if err != nil {
return nil, fmt.Errorf("could not get CSV from reader: %v", err)
}
err = yaml.Unmarshal(bts, &csv)
func GetSecurityContextConstraints(ctx context.Context, bundlePath string) ([]string, error) {
bundle, err := manifests.GetBundleFromDir(bundlePath)
if err != nil {
return nil, fmt.Errorf("malformed CSV detected: %v", err)
return nil, fmt.Errorf("could not get bundle from dir: %s: %v", bundlePath, err)
komish marked this conversation as resolved.
Show resolved Hide resolved
}
for _, cp := range csv.Spec.InstallStrategy.StrategySpec.ClusterPermissions {
for _, cp := range bundle.CSV.Spec.InstallStrategy.StrategySpec.ClusterPermissions {
for _, rule := range cp.Rules {
if hasSCCApiGroup(rule) && hasSCCResource(rule) {
return rule.ResourceNames, nil
Expand Down
130 changes: 6 additions & 124 deletions certification/internal/bundle/bundle_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package bundle

import (
"bytes"
"context"
"os"
"path/filepath"
"strings"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
Expand Down Expand Up @@ -111,142 +111,24 @@ var _ = Describe("BundleValidateCheck", func() {
Expect(report).ToNot(BeNil())
})
})

Context("the annotations file has a bad OpenShift version", func() {
JustBeforeEach(func() {
err := os.WriteFile(filepath.Join(imageRef.ImageFSPath, metadataDir, annotationFilename), []byte(`annotations:
com.redhat.openshift.versions: "vfoo"`), 0o644)
Expect(err).ToNot(HaveOccurred())
})
It("should fail", func() {
report, err := Validate(context.Background(), fakeEngine, imageRef.ImageFSPath)
Expect(err).To(HaveOccurred())
Expect(report).To(BeNil())
})
})

Context("getting the CSV file from the bundle", func() {
var manifestsPath string

BeforeEach(func() {
manifestsPath = filepath.Join(imageRef.ImageFSPath, manifestsDir)
err := os.WriteFile(filepath.Join(manifestsPath, clusterServiceVersionFilename), []byte(""), 0o644)
Expect(err).ToNot(HaveOccurred())
})
Context("the CSV is malformed", func() {
It("should error", func() {
r := strings.NewReader("badcsv::bad")
images, err := GetSupportedInstallModes(context.TODO(), r)
Expect(err).To(HaveOccurred())
Expect(images).To(BeNil())
})
})
Context("the CSV could not be read", func() {
It("should error", func() {
images, err := GetSupportedInstallModes(context.TODO(), errReader(0))
Expect(err).To(HaveOccurred())
Expect(images).To(BeNil())
})
})
Context("the CSV exists by itself", func() {
It("should return the filename", func() {
filename, err := GetCsvFilePathFromBundle(imageRef.ImageFSPath)
Expect(err).ToNot(HaveOccurred())
Expect(filename).To(Equal(filepath.Join(manifestsPath, clusterServiceVersionFilename)))
})
})
Context("the CSV doesn't exist", func() {
JustBeforeEach(func() {
err := os.Remove(filepath.Join(manifestsPath, clusterServiceVersionFilename))
Expect(err).ToNot(HaveOccurred())
})
It("should return an error", func() {
filename, err := GetCsvFilePathFromBundle(imageRef.ImageFSPath)
Expect(err).To(HaveOccurred())
Expect(filename).To(Equal(""))
})
})
Context("there is more than one CSV", func() {
JustBeforeEach(func() {
err := os.WriteFile(filepath.Join(manifestsPath, "otheroperator.clusterserviceversion.yaml"), []byte(""), 0o664)
Expect(err).ToNot(HaveOccurred())
})
It("should return an error", func() {
filename, err := GetCsvFilePathFromBundle(imageRef.ImageFSPath)
Expect(err).To(HaveOccurred())
Expect(filename).To(Equal(""))
})
})
Context("there is a bad mount dir", func() {
It("should return an error", func() {
filename, err := GetCsvFilePathFromBundle("[]")
Expect(err).To(HaveOccurred())
Expect(filename).To(Equal(""))
})
})
})
})

Describe("Supported Install Modes", func() {
var csv string = `spec:
installModes:
- supported: true
type: OwnNamespace
- supported: true
type: SingleNamespace
- supported: false
type: MultiNamespace
- supported: true
type: AllNamespaces`

Context("CSV is valid", func() {
It("should return a map of 3", func() {
installModes, err := GetSupportedInstallModes(context.Background(), strings.NewReader(csv))
Expect(err).ToNot(HaveOccurred())
Expect(installModes).ToNot(BeNil())
Expect(len(installModes)).To(Equal(3))
Expect("MultiNamespace").ToNot(BeElementOf(installModes))
})
})

Context("reader is not valid", func() {
It("should error", func() {
installModes, err := GetSupportedInstallModes(context.Background(), errReader(0))
Expect(err).To(HaveOccurred())
Expect(installModes).To(BeNil())
})
})

Context("CSV is invalid", func() {
JustBeforeEach(func() {
csv = `invalid`
})
It("should error", func() {
installModes, err := GetSupportedInstallModes(context.Background(), strings.NewReader(csv))
Expect(err).To(HaveOccurred())
Expect(installModes).To(BeNil())
})
})
})

Describe("While ensuring that container util is working", func() {
// tests: extractAnnotationsBytes
Context("with an annotations yaml data read from disk", func() {
Context("with the correct format", func() {
data := []byte("annotations:\n foo: bar")

It("should properly marshal to a map[string]string", func() {
annotations, err := ExtractAnnotationsBytes(context.TODO(), data)
annotations, err := LoadAnnotations(context.TODO(), bytes.NewReader([]byte(annotations)))
Expect(err).ToNot(HaveOccurred())
Expect(annotations["foo"]).To(Equal("bar"))
Expect(annotations.DefaultChannelName).To(Equal("testChannel"))
})
})

Context("containing no data read in from the yaml file", func() {
data := []byte{}

It("should return an error", func() {
_, err := ExtractAnnotationsBytes(context.TODO(), data)
_, err := LoadAnnotations(context.TODO(), bytes.NewReader(data))
Expect(err).To(HaveOccurred())
})
})
Expand All @@ -255,14 +137,14 @@ var _ = Describe("BundleValidateCheck", func() {
data := []byte(`malformed`)

It("should return an error", func() {
_, err := ExtractAnnotationsBytes(context.TODO(), data)
_, err := LoadAnnotations(context.TODO(), bytes.NewReader(data))
Expect(err).To(HaveOccurred())
})
})

Context("a bad reader is sent to GetAnnotations", func() {
It("should return an error", func() {
annotations, err := GetAnnotations(context.Background(), errReader(0))
annotations, err := LoadAnnotations(context.TODO(), errReader(0))
Expect(err).To(HaveOccurred())
Expect(annotations).To(BeNil())
})
Expand Down
13 changes: 13 additions & 0 deletions certification/internal/bundle/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package bundle

import "github.com/operator-framework/api/pkg/manifests"

type AnnotationsFile struct {
Annotations Annotations `json:"annotations" yaml:"annotations"`
}

type Annotations struct {
manifests.Annotations

OpenshiftVersions string `json:"com.redhat.openshift.versions,omitempty" yaml:"com.redhat.openshift.versions,omitempty"`
}
10 changes: 5 additions & 5 deletions certification/internal/policy/operator/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ var (
"registry.access.redhat.com": {},
}

prioritizedInstallModes = []string{
string(operatorv1alpha1.InstallModeTypeOwnNamespace),
string(operatorv1alpha1.InstallModeTypeSingleNamespace),
string(operatorv1alpha1.InstallModeTypeMultiNamespace),
string(operatorv1alpha1.InstallModeTypeAllNamespaces),
prioritizedInstallModes = []operatorv1alpha1.InstallModeType{
operatorv1alpha1.InstallModeTypeOwnNamespace,
operatorv1alpha1.InstallModeTypeSingleNamespace,
operatorv1alpha1.InstallModeTypeMultiNamespace,
operatorv1alpha1.InstallModeTypeAllNamespaces,
}
)
Loading