diff --git a/cmd/operator-sdk/alpha/scorecard/cmd.go b/cmd/operator-sdk/alpha/scorecard/cmd.go index 70b7787da98..6d587194179 100644 --- a/cmd/operator-sdk/alpha/scorecard/cmd.go +++ b/cmd/operator-sdk/alpha/scorecard/cmd.go @@ -30,6 +30,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "github.com/operator-framework/operator-sdk/internal/flags" + registryutil "github.com/operator-framework/operator-sdk/internal/registry" scorecard "github.com/operator-framework/operator-sdk/internal/scorecard/alpha" "github.com/operator-framework/operator-sdk/pkg/apis/scorecard/v1alpha3" ) @@ -62,6 +63,7 @@ If the argument holds an image tag, it must be present remotely.`, return c.validate(args) }, RunE: func(cmd *cobra.Command, args []string) (err error) { + c.bundle = args[0] return c.run() }, } @@ -108,27 +110,30 @@ func (c *scorecardCmd) printOutput(output v1alpha3.Test) error { return fmt.Errorf("invalid output format selected") } return nil - } -func (c *scorecardCmd) run() error { - var err error +func (c *scorecardCmd) run() (err error) { // Extract bundle image contents if bundle is inferred to be an image. + var bundleLabels registryutil.Labels if _, err = os.Stat(c.bundle); err != nil && errors.Is(err, os.ErrNotExist) { - // Discard bundle extraction logs unless user sets verbose mode. - logger := log.NewEntry(discardLogger()) - if viper.GetBool(flags.VerboseOpt) { - logger = log.WithFields(log.Fields{"bundle": c.bundle}) - } - // FEAT: enable explicit local image extraction. - if c.bundle, err = scorecard.ExtractBundleImage(context.TODO(), logger, c.bundle, false); err != nil { + if c.bundle, bundleLabels, err = getBundlePathAndLabelsFromImage(c.bundle); err != nil { log.Fatal(err) } defer func() { if err := os.RemoveAll(c.bundle); err != nil { - logger.Error(err) + log.Error(err) } }() + } else { + // Search for the metadata dir since we cannot assume its path, and + // use that metadata as the source of truth when testing. + metadataDir, err := registryutil.FindMetadataDir(c.bundle) + if err != nil { + log.Fatal(err) + } + if bundleLabels, err = registryutil.GetMetadataLabels(metadataDir); err != nil { + log.Fatal(err) + } } o := scorecard.Scorecard{ @@ -160,6 +165,7 @@ func (c *scorecardCmd) run() error { ServiceAccount: c.serviceAccount, Namespace: c.namespace, BundlePath: c.bundle, + BundleLabels: bundleLabels, } // Only get the client if running tests. @@ -185,8 +191,6 @@ func (c *scorecardCmd) validate(args []string) error { if len(args) != 1 { return fmt.Errorf("a bundle image or directory argument is required") } - c.bundle = args[0] - return nil } @@ -196,3 +200,27 @@ func discardLogger() *log.Logger { logger.SetOutput(ioutil.Discard) return logger } + +// getBundlePathAndLabelsFromImage returns bundleImage's path on disk post- +// extraction and image labels. +func getBundlePathAndLabelsFromImage(bundleImage string) (string, registryutil.Labels, error) { + // Discard bundle extraction logs unless user sets verbose mode. + logger := log.NewEntry(discardLogger()) + if viper.GetBool(flags.VerboseOpt) { + logger = log.WithFields(log.Fields{"bundle": bundleImage}) + } + // FEAT: enable explicit local image extraction. + bundlePath, err := registryutil.ExtractBundleImage(context.TODO(), logger, bundleImage, false) + if err != nil { + return "", nil, err + } + + // Get image labels from bundleImage locally, since the bundle extraction + // already pulled the image. + bundleLabels, err := registryutil.GetImageLabels(context.TODO(), logger, bundleImage, true) + if err != nil { + return "", nil, err + } + + return bundlePath, bundleLabels, nil +} diff --git a/cmd/operator-sdk/alpha/scorecard/cmd_test.go b/cmd/operator-sdk/alpha/scorecard/cmd_test.go index 1b1e85fe87c..9a0edc64e1b 100644 --- a/cmd/operator-sdk/alpha/scorecard/cmd_test.go +++ b/cmd/operator-sdk/alpha/scorecard/cmd_test.go @@ -81,11 +81,10 @@ var _ = Describe("Running an alpha scorecard command", func() { Expect(err).To(HaveOccurred()) }) - It("succeeds if exactly one arg is provided and parses the arg", func() { + It("succeeds if exactly one arg is provided", func() { input := "cherry" err := cmd.validate([]string{input}) Expect(err).NotTo(HaveOccurred()) - Expect(cmd.bundle).To(Equal(input)) }) }) }) diff --git a/images/scorecard-test/cmd/test/main.go b/images/scorecard-test/cmd/test/main.go index a075231b7b5..a25cfca7f98 100644 --- a/images/scorecard-test/cmd/test/main.go +++ b/images/scorecard-test/cmd/test/main.go @@ -22,6 +22,7 @@ import ( apimanifests "github.com/operator-framework/api/pkg/manifests" + registryutil "github.com/operator-framework/operator-sdk/internal/registry" scorecard "github.com/operator-framework/operator-sdk/internal/scorecard/alpha" "github.com/operator-framework/operator-sdk/internal/scorecard/alpha/tests" scapiv1alpha3 "github.com/operator-framework/operator-sdk/pkg/apis/scorecard/v1alpha3" @@ -48,11 +49,16 @@ func main() { log.Fatal(err.Error()) } + labels, err := registryutil.GetMetadataLabels(scorecard.PodLabelsDir) + if err != nil { + log.Fatal(err.Error()) + } + var result scapiv1alpha3.TestStatus switch entrypoint[0] { case tests.OLMBundleValidationTest: - result = tests.BundleValidationTest(scorecard.PodBundleRoot) + result = tests.BundleValidationTest(scorecard.PodBundleRoot, labels) case tests.OLMCRDsHaveValidationTest: result = tests.CRDsHaveValidationTest(cfg) case tests.OLMCRDsHaveResourcesTest: diff --git a/internal/scorecard/alpha/registry.go b/internal/registry/image.go similarity index 99% rename from internal/scorecard/alpha/registry.go rename to internal/registry/image.go index 1f94a055f55..7031fdfefb3 100644 --- a/internal/scorecard/alpha/registry.go +++ b/internal/registry/image.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package alpha +package registry import ( "context" diff --git a/internal/registry/labels.go b/internal/registry/labels.go new file mode 100644 index 00000000000..0619c3f6b88 --- /dev/null +++ b/internal/registry/labels.go @@ -0,0 +1,129 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package registry + +import ( + "context" + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + registryimage "github.com/operator-framework/operator-registry/pkg/image" + "github.com/operator-framework/operator-registry/pkg/image/containerdregistry" + registrybundle "github.com/operator-framework/operator-registry/pkg/lib/bundle" + log "github.com/sirupsen/logrus" + + // TODO: replace `gopkg.in/yaml.v2` with `sigs.k8s.io/yaml` once operator-registry has `json` tags in the + // annotations struct. + yaml "gopkg.in/yaml.v3" +) + +// Labels is a set of key:value labels from an operator-registry object. +type Labels map[string]string + +// GetManifestsDir returns the manifests directory name in ls using +// a predefined key, or false if it does not exist. +func (ls Labels) GetManifestsDir() (string, bool) { + value, hasLabel := ls.getLabel(registrybundle.ManifestsLabel) + return filepath.Clean(value), hasLabel +} + +// getLabel returns the string by key in ls, or an empty string and false +// if key is not found in ls. +func (ls Labels) getLabel(key string) (string, bool) { + value, hasLabel := ls[key] + return value, hasLabel +} + +// GetImageLabels returns the set of labels on image. +func GetImageLabels(ctx context.Context, logger *log.Entry, image string, local bool) (Labels, error) { + // Create a containerd registry for socket-less image layer reading. + reg, err := containerdregistry.NewRegistry(containerdregistry.WithLog(logger)) + if err != nil { + return nil, fmt.Errorf("error creating new image registry: %v", err) + } + defer func() { + if err := reg.Destroy(); err != nil { + logger.WithError(err).Warn("Error destroying local cache") + } + }() + + // Pull the image if it isn't present locally. + if !local { + if err := reg.Pull(ctx, registryimage.SimpleReference(image)); err != nil { + return nil, fmt.Errorf("error pulling image %s: %v", image, err) + } + } + + // Query the image reference for its labels. + labels, err := reg.Labels(ctx, registryimage.SimpleReference(image)) + if err != nil { + return nil, fmt.Errorf("error reading image %s labels: %v", image, err) + } + + return labels, err +} + +// FindMetadataDir walks bundleRoot searching for metadata, and returns that directory if found. +// If one is not found, an error is returned. +func FindMetadataDir(bundleRoot string) (metadataDir string, err error) { + err = filepath.Walk(bundleRoot, func(path string, info os.FileInfo, err error) error { + if err != nil || !info.IsDir() { + return err + } + // Already found the first metadata dir, do not overwrite it. + if metadataDir != "" { + return nil + } + // The annotations file is well-defined. + _, err = os.Stat(filepath.Join(path, registrybundle.AnnotationsFile)) + if err == nil || errors.Is(err, os.ErrExist) { + metadataDir = path + return nil + } + return err + }) + if err != nil { + return "", err + } + if metadataDir == "" { + return "", fmt.Errorf("metadata dir not found in %s", bundleRoot) + } + + return metadataDir, nil +} + +// GetMetadataLabels reads annotations from file(s) in metadataDir and returns them as Labels. +func GetMetadataLabels(metadataDir string) (Labels, error) { + // The annotations file is well-defined. + annotationsPath := filepath.Join(metadataDir, registrybundle.AnnotationsFile) + b, err := ioutil.ReadFile(annotationsPath) + if err != nil { + return nil, err + } + + // Use the arbitrarily-labelled bundle representation of the annotations file + // for forwards and backwards compatibility. + meta := registrybundle.AnnotationMetadata{ + Annotations: make(map[string]string), + } + if err = yaml.Unmarshal(b, &meta); err != nil { + return nil, err + } + + return meta.Annotations, nil +} diff --git a/internal/registry/validate.go b/internal/registry/validate.go index cf0a67d1476..510f7ee2a29 100644 --- a/internal/registry/validate.go +++ b/internal/registry/validate.go @@ -27,7 +27,6 @@ import ( "gopkg.in/yaml.v2" k8svalidation "k8s.io/apimachinery/pkg/api/validation" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/validation/field" ) diff --git a/internal/scorecard/alpha/bundle.go b/internal/scorecard/alpha/bundle.go index 0b8bfe3f10b..6480201883f 100644 --- a/internal/scorecard/alpha/bundle.go +++ b/internal/scorecard/alpha/bundle.go @@ -15,12 +15,23 @@ package alpha import ( + "archive/tar" + "bytes" + "compress/gzip" "fmt" - "io/ioutil" "os" + "path" "path/filepath" + "strings" - "k8s.io/apimachinery/pkg/util/rand" + "github.com/operator-framework/operator-registry/pkg/lib/bundle" + log "github.com/sirupsen/logrus" + + // TODO: replace `gopkg.in/yaml.v2` with `sigs.k8s.io/yaml` once operator-registry has `json` tags in the + // annotations struct. + yaml "gopkg.in/yaml.v3" + + registryutil "github.com/operator-framework/operator-sdk/internal/registry" ) // getBundleData tars up the contents of a bundle from a path, and returns that tar file in []byte @@ -28,25 +39,75 @@ func (r PodTestRunner) getBundleData() (bundleData []byte, err error) { // make sure the bundle exists on disk _, err = os.Stat(r.BundlePath) - if os.IsNotExist(err) { - return bundleData, fmt.Errorf("bundle path is not valid %w", err) + if err != nil && os.IsNotExist(err) { + return nil, fmt.Errorf("bundle path does not exist: %w", err) } - tempTarFileName := filepath.Join(os.TempDir(), fmt.Sprintf("tempBundle-%s.tar.gz", rand.String(4))) + // Write the tarball in-memory. + buf := &bytes.Buffer{} + gz := gzip.NewWriter(buf) + w := tar.NewWriter(gz) + // Both tar and gzip writer Close() methods write some data that is + // required when reading the result, so we must close these without a defer. + closers := closeFuncs{w.Close, gz.Close} + // Write the bundle itself. paths := []string{r.BundlePath} - err = CreateTarFile(tempTarFileName, paths) - if err != nil { - return bundleData, fmt.Errorf("error creating tar of bundle %w", err) + if err = WritePathsToTar(w, paths); err != nil { + if err := closers.close(); err != nil { + log.Error(err) + } + return nil, fmt.Errorf("error writing bundle tar: %w", err) + } + + // Write the source of truth labels to the expected path within a pod. + labelPath := filepath.Join(PodLabelsDirName, bundle.AnnotationsFile) + if err = writeLabels(w, labelPath, r.BundleLabels); err != nil { + if err := closers.close(); err != nil { + log.Error(err) + } + return nil, fmt.Errorf("error writing image labels to bundle tar: %w", err) + } + + if err := closers.close(); err != nil { + log.Error(err) } - defer os.Remove(tempTarFileName) + return buf.Bytes(), nil +} - var buf []byte - buf, err = ioutil.ReadFile(tempTarFileName) +type closeFuncs []func() error + +func (fs closeFuncs) close() error { + for _, f := range fs { + if err := f(); err != nil { + return err + } + } + return nil +} + +// writeLabels writes labels to w, creating each directory in +func writeLabels(w *tar.Writer, labelPath string, labels registryutil.Labels) error { + annotations := bundle.AnnotationMetadata{ + Annotations: labels, + } + b, err := yaml.Marshal(annotations) if err != nil { - return bundleData, fmt.Errorf("error reading tar of bundle %w", err) + return err + } + + // Create one header per directory in path. + labelPath = path.Clean(labelPath) + pathSplit := strings.Split(labelPath, "/") + for i := 1; i < len(pathSplit); i++ { + hdr := newTarDirHeader(filepath.Join(pathSplit[:i]...)) + if err = WriteToTar(w, &bytes.Buffer{}, hdr); err != nil { + return err + } } - return buf, err + // Write labels to path. + hdr := newTarFileHeader(labelPath, int64(len(b))) + return WriteToTar(w, bytes.NewBuffer(b), hdr) } diff --git a/internal/scorecard/alpha/bundle_test.go b/internal/scorecard/alpha/bundle_test.go index 70479ea0892..30739e74622 100644 --- a/internal/scorecard/alpha/bundle_test.go +++ b/internal/scorecard/alpha/bundle_test.go @@ -24,6 +24,8 @@ import ( "path/filepath" "reflect" "testing" + + "github.com/operator-framework/operator-sdk/internal/registry" ) func TestBundlePath(t *testing.T) { @@ -44,6 +46,15 @@ func TestBundlePath(t *testing.T) { t.Run(c.bundlePathValue, func(t *testing.T) { r := PodTestRunner{} r.BundlePath = c.bundlePathValue + + if c.bundlePathValue != "" { + var err error + r.BundleLabels, err = registry.GetMetadataLabels(filepath.Join(c.bundlePathValue, "metadata")) + if err != nil { + t.Fatalf("Failed to get test bundle labels: %v", err) + } + } + bundleData, err := r.getBundleData() if err != nil && !c.wantError { t.Fatalf("Wanted result but got error: %v", err) diff --git a/internal/scorecard/alpha/scorecard.go b/internal/scorecard/alpha/scorecard.go index 6fe7a5cd1e2..2813fa22824 100644 --- a/internal/scorecard/alpha/scorecard.go +++ b/internal/scorecard/alpha/scorecard.go @@ -25,6 +25,7 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" + registryutil "github.com/operator-framework/operator-sdk/internal/registry" "github.com/operator-framework/operator-sdk/pkg/apis/scorecard/v1alpha3" ) @@ -45,6 +46,7 @@ type PodTestRunner struct { Namespace string ServiceAccount string BundlePath string + BundleLabels registryutil.Labels Client kubernetes.Interface configMapName string diff --git a/internal/scorecard/alpha/tar.go b/internal/scorecard/alpha/tar.go index c4bee3240f3..fbd836dcd19 100644 --- a/internal/scorecard/alpha/tar.go +++ b/internal/scorecard/alpha/tar.go @@ -22,50 +22,27 @@ import ( "os" "path/filepath" "strings" -) + "time" -// CreateTarFile walks paths to create tar file tarName -func CreateTarFile(tarName string, paths []string) (err error) { - tarFile, err := os.Create(tarName) - if err != nil { - return err - } - defer func() { - err = tarFile.Close() - }() + log "github.com/sirupsen/logrus" +) - absTar, err := filepath.Abs(tarName) - if err != nil { +func WriteToTar(tw *tar.Writer, r io.Reader, hdr *tar.Header) error { + if err := tw.WriteHeader(hdr); err != nil { return err } + _, err := io.Copy(tw, r) + return err +} - // enable compression if file ends in .gz - tw := tar.NewWriter(tarFile) - if strings.HasSuffix(tarName, ".gz") || strings.HasSuffix(tarName, ".gzip") { - gz := gzip.NewWriter(tarFile) - defer gz.Close() - tw = tar.NewWriter(gz) - } - defer tw.Close() - +// WritePathsToTar walks paths to create tar file tarName +func WritePathsToTar(tw *tar.Writer, paths []string) (err error) { // walk each specified path and add encountered file to tar for _, path := range paths { - // validate path path = filepath.Clean(path) - absPath, err := filepath.Abs(path) - if err != nil { - fmt.Println(err) - continue - } - if absPath == absTar { - continue - } - if absPath == filepath.Dir(absTar) { - continue - } walker := func(file string, finfo os.FileInfo, err error) error { - if err != nil { + if err != nil || file == path { return err } @@ -83,14 +60,13 @@ func CreateTarFile(tarName string, paths []string) (err error) { } } // ensure header has relative file path - hdr.Name = relFilePath - - hdr.Name = strings.TrimPrefix(relFilePath, path) + hdr.Name = strings.TrimPrefix(relFilePath, path+string(filepath.Separator)) if err := tw.WriteHeader(hdr); err != nil { return err } + // if path is a dir, dont continue - if finfo.Mode().IsDir() { + if finfo.IsDir() { return nil } @@ -99,12 +75,11 @@ func CreateTarFile(tarName string, paths []string) (err error) { if err != nil { return err } - defer srcFile.Close() _, err = io.Copy(tw, srcFile) - if err != nil { - return err + if err := srcFile.Close(); err != nil { + log.Error(err) } - return nil + return err } // build tar @@ -123,7 +98,9 @@ func UntarFile(tarName, target string) (err error) { return err } defer func() { - err = tarFile.Close() + if err := tarFile.Close(); err != nil { + log.Error(err) + } }() absPath, err := filepath.Abs(target) @@ -131,14 +108,20 @@ func UntarFile(tarName, target string) (err error) { return err } - tr := tar.NewReader(tarFile) - if strings.HasSuffix(tarName, ".gz") || strings.HasSuffix(tarName, ".gzip") { + var tr *tar.Reader + if isFileGzipped(tarName) { gz, err := gzip.NewReader(tarFile) if err != nil { return err } - defer gz.Close() + defer func() { + if err := gz.Close(); err != nil { + log.Error(err) + } + }() tr = tar.NewReader(gz) + } else { + tr = tar.NewReader(tarFile) } // untar each segment @@ -188,3 +171,31 @@ func UntarFile(tarName, target string) (err error) { return nil } + +// isFileGzipped returns true if file is compressed with gzip. +func isFileGzipped(file string) bool { + return strings.HasSuffix(file, ".gz") || strings.HasSuffix(file, ".gzip") +} + +func newTarFileHeader(path string, size int64) *tar.Header { + return &tar.Header{ + Typeflag: tar.TypeReg, + Name: filepath.Clean(path), + ModTime: time.Now(), + Mode: 0666, + Size: size, + Uid: os.Getuid(), + Gid: os.Getgid(), + } +} + +func newTarDirHeader(path string) *tar.Header { + return &tar.Header{ + Typeflag: tar.TypeDir, + Name: filepath.Clean(path) + "/", + ModTime: time.Now(), + Mode: 0700, + Uid: os.Getuid(), + Gid: os.Getgid(), + } +} diff --git a/internal/scorecard/alpha/testdata/bundle.tar.gz b/internal/scorecard/alpha/testdata/bundle.tar.gz index 9e5d7ae4b4e..4f569ab3c15 100644 Binary files a/internal/scorecard/alpha/testdata/bundle.tar.gz and b/internal/scorecard/alpha/testdata/bundle.tar.gz differ diff --git a/internal/scorecard/alpha/testpod.go b/internal/scorecard/alpha/testpod.go index 4a9b9e43a8d..89e93d2c177 100644 --- a/internal/scorecard/alpha/testpod.go +++ b/internal/scorecard/alpha/testpod.go @@ -29,6 +29,13 @@ import ( const ( // PodBundleRoot is the directory containing all bundle data within a test pod. PodBundleRoot = "/bundle" + + // PodLabelsDir is the name of the directory containing bundle labels. + PodLabelsDirName = "labels" + // PodLabelsDir is the directory containing an annotations.yaml file that is + // the source of truth for bundle metadata. These labels come from the + // bundle image if applicable. + PodLabelsDir = PodBundleRoot + "/" + PodLabelsDirName ) // getPodDefinition fills out a Pod definition based on diff --git a/internal/scorecard/alpha/tests/bundle_test.go b/internal/scorecard/alpha/tests/bundle_test.go index e17cbe2c859..085beca3905 100644 --- a/internal/scorecard/alpha/tests/bundle_test.go +++ b/internal/scorecard/alpha/tests/bundle_test.go @@ -22,10 +22,12 @@ import ( . "github.com/onsi/gomega" apimanifests "github.com/operator-framework/api/pkg/manifests" operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + registrybundle "github.com/operator-framework/operator-registry/pkg/lib/bundle" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" + registryutil "github.com/operator-framework/operator-sdk/internal/registry" scapiv1alpha3 "github.com/operator-framework/operator-sdk/pkg/apis/scorecard/v1alpha3" ) @@ -116,7 +118,10 @@ var _ = Describe("Basic and OLM tests", func() { Describe("Testing OLM Bundle", func() { It("should pass when test bundle is at the desired location", func() { - status = BundleValidationTest(testBundle) + metadataDir := filepath.Join(testBundle, registrybundle.MetadataDir) + labels, err := registryutil.GetMetadataLabels(metadataDir) + Expect(err).NotTo(HaveOccurred()) + status = BundleValidationTest(testBundle, labels) Expect(status.Results[0].State).To(Equal(scapiv1alpha3.PassState)) }) }) diff --git a/internal/scorecard/alpha/tests/olm.go b/internal/scorecard/alpha/tests/olm.go index f1f5252bd18..0888be0bb90 100644 --- a/internal/scorecard/alpha/tests/olm.go +++ b/internal/scorecard/alpha/tests/olm.go @@ -31,6 +31,7 @@ import ( "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + registryutil "github.com/operator-framework/operator-sdk/internal/registry" "github.com/operator-framework/operator-sdk/internal/util/k8sutil" scapiv1alpha3 "github.com/operator-framework/operator-sdk/pkg/apis/scorecard/v1alpha3" ) @@ -46,7 +47,7 @@ const ( ) // BundleValidationTest validates an on-disk bundle -func BundleValidationTest(dir string) scapiv1alpha3.TestStatus { +func BundleValidationTest(dir string, labels registryutil.Labels) scapiv1alpha3.TestStatus { r := scapiv1alpha3.TestResult{} r.Name = OLMBundleValidationTest r.State = scapiv1alpha3.PassState @@ -81,9 +82,15 @@ func BundleValidationTest(dir string) scapiv1alpha3.TestStatus { r.Errors = append(r.Errors, err.Error()) } + // Since a custom manifests directory may be used, check labels for its base + // path. Use the default base path if that label doesn't exist. + manifestsDir := registrybundle.ManifestsDir + if value, hasLabel := labels.GetManifestsDir(); hasLabel { + manifestsDir = value + } + // Validate bundle content. - manifestsDir := filepath.Join(dir, registrybundle.ManifestsDir) - bundle, err := apimanifests.GetBundleFromDir(manifestsDir) + bundle, err := apimanifests.GetBundleFromDir(filepath.Join(dir, manifestsDir)) if err != nil { r.State = scapiv1alpha3.FailState r.Errors = append(r.Errors, err.Error())