Skip to content

Commit bd8520c

Browse files
author
Eric Stroczynski
authored
scorecard (alpha): use bundle labels in pod runner (#3062)
scorecard (alpha): use bundle labels in pod runner, from image or bundle directory if no image is available images/scorecard-test/cmd/test/main.go: use internal `labels/annotations.yaml` to get metadata directory internal/util/registry: operator-registry utilities, mainly label helpers
1 parent f989726 commit bd8520c

File tree

14 files changed

+346
-81
lines changed

14 files changed

+346
-81
lines changed

cmd/operator-sdk/alpha/scorecard/cmd.go

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"k8s.io/apimachinery/pkg/labels"
3131

3232
"github.com/operator-framework/operator-sdk/internal/flags"
33+
registryutil "github.com/operator-framework/operator-sdk/internal/registry"
3334
scorecard "github.com/operator-framework/operator-sdk/internal/scorecard/alpha"
3435
"github.com/operator-framework/operator-sdk/pkg/apis/scorecard/v1alpha3"
3536
)
@@ -62,6 +63,7 @@ If the argument holds an image tag, it must be present remotely.`,
6263
return c.validate(args)
6364
},
6465
RunE: func(cmd *cobra.Command, args []string) (err error) {
66+
c.bundle = args[0]
6567
return c.run()
6668
},
6769
}
@@ -108,27 +110,30 @@ func (c *scorecardCmd) printOutput(output v1alpha3.Test) error {
108110
return fmt.Errorf("invalid output format selected")
109111
}
110112
return nil
111-
112113
}
113114

114-
func (c *scorecardCmd) run() error {
115-
var err error
115+
func (c *scorecardCmd) run() (err error) {
116116
// Extract bundle image contents if bundle is inferred to be an image.
117+
var bundleLabels registryutil.Labels
117118
if _, err = os.Stat(c.bundle); err != nil && errors.Is(err, os.ErrNotExist) {
118-
// Discard bundle extraction logs unless user sets verbose mode.
119-
logger := log.NewEntry(discardLogger())
120-
if viper.GetBool(flags.VerboseOpt) {
121-
logger = log.WithFields(log.Fields{"bundle": c.bundle})
122-
}
123-
// FEAT: enable explicit local image extraction.
124-
if c.bundle, err = scorecard.ExtractBundleImage(context.TODO(), logger, c.bundle, false); err != nil {
119+
if c.bundle, bundleLabels, err = getBundlePathAndLabelsFromImage(c.bundle); err != nil {
125120
log.Fatal(err)
126121
}
127122
defer func() {
128123
if err := os.RemoveAll(c.bundle); err != nil {
129-
logger.Error(err)
124+
log.Error(err)
130125
}
131126
}()
127+
} else {
128+
// Search for the metadata dir since we cannot assume its path, and
129+
// use that metadata as the source of truth when testing.
130+
metadataDir, err := registryutil.FindMetadataDir(c.bundle)
131+
if err != nil {
132+
log.Fatal(err)
133+
}
134+
if bundleLabels, err = registryutil.GetMetadataLabels(metadataDir); err != nil {
135+
log.Fatal(err)
136+
}
132137
}
133138

134139
o := scorecard.Scorecard{
@@ -160,6 +165,7 @@ func (c *scorecardCmd) run() error {
160165
ServiceAccount: c.serviceAccount,
161166
Namespace: c.namespace,
162167
BundlePath: c.bundle,
168+
BundleLabels: bundleLabels,
163169
}
164170

165171
// Only get the client if running tests.
@@ -185,8 +191,6 @@ func (c *scorecardCmd) validate(args []string) error {
185191
if len(args) != 1 {
186192
return fmt.Errorf("a bundle image or directory argument is required")
187193
}
188-
c.bundle = args[0]
189-
190194
return nil
191195
}
192196

@@ -196,3 +200,27 @@ func discardLogger() *log.Logger {
196200
logger.SetOutput(ioutil.Discard)
197201
return logger
198202
}
203+
204+
// getBundlePathAndLabelsFromImage returns bundleImage's path on disk post-
205+
// extraction and image labels.
206+
func getBundlePathAndLabelsFromImage(bundleImage string) (string, registryutil.Labels, error) {
207+
// Discard bundle extraction logs unless user sets verbose mode.
208+
logger := log.NewEntry(discardLogger())
209+
if viper.GetBool(flags.VerboseOpt) {
210+
logger = log.WithFields(log.Fields{"bundle": bundleImage})
211+
}
212+
// FEAT: enable explicit local image extraction.
213+
bundlePath, err := registryutil.ExtractBundleImage(context.TODO(), logger, bundleImage, false)
214+
if err != nil {
215+
return "", nil, err
216+
}
217+
218+
// Get image labels from bundleImage locally, since the bundle extraction
219+
// already pulled the image.
220+
bundleLabels, err := registryutil.GetImageLabels(context.TODO(), logger, bundleImage, true)
221+
if err != nil {
222+
return "", nil, err
223+
}
224+
225+
return bundlePath, bundleLabels, nil
226+
}

cmd/operator-sdk/alpha/scorecard/cmd_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,10 @@ var _ = Describe("Running an alpha scorecard command", func() {
8181
Expect(err).To(HaveOccurred())
8282
})
8383

84-
It("succeeds if exactly one arg is provided and parses the arg", func() {
84+
It("succeeds if exactly one arg is provided", func() {
8585
input := "cherry"
8686
err := cmd.validate([]string{input})
8787
Expect(err).NotTo(HaveOccurred())
88-
Expect(cmd.bundle).To(Equal(input))
8988
})
9089
})
9190
})

images/scorecard-test/cmd/test/main.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
apimanifests "github.com/operator-framework/api/pkg/manifests"
2424

25+
registryutil "github.com/operator-framework/operator-sdk/internal/registry"
2526
scorecard "github.com/operator-framework/operator-sdk/internal/scorecard/alpha"
2627
"github.com/operator-framework/operator-sdk/internal/scorecard/alpha/tests"
2728
scapiv1alpha3 "github.com/operator-framework/operator-sdk/pkg/apis/scorecard/v1alpha3"
@@ -48,11 +49,16 @@ func main() {
4849
log.Fatal(err.Error())
4950
}
5051

52+
labels, err := registryutil.GetMetadataLabels(scorecard.PodLabelsDir)
53+
if err != nil {
54+
log.Fatal(err.Error())
55+
}
56+
5157
var result scapiv1alpha3.TestStatus
5258

5359
switch entrypoint[0] {
5460
case tests.OLMBundleValidationTest:
55-
result = tests.BundleValidationTest(scorecard.PodBundleRoot)
61+
result = tests.BundleValidationTest(scorecard.PodBundleRoot, labels)
5662
case tests.OLMCRDsHaveValidationTest:
5763
result = tests.CRDsHaveValidationTest(cfg)
5864
case tests.OLMCRDsHaveResourcesTest:

internal/scorecard/alpha/registry.go renamed to internal/registry/image.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
package alpha
15+
package registry
1616

1717
import (
1818
"context"

internal/registry/labels.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright 2020 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package registry
16+
17+
import (
18+
"context"
19+
"errors"
20+
"fmt"
21+
"io/ioutil"
22+
"os"
23+
"path/filepath"
24+
25+
registryimage "github.com/operator-framework/operator-registry/pkg/image"
26+
"github.com/operator-framework/operator-registry/pkg/image/containerdregistry"
27+
registrybundle "github.com/operator-framework/operator-registry/pkg/lib/bundle"
28+
log "github.com/sirupsen/logrus"
29+
30+
// TODO: replace `gopkg.in/yaml.v2` with `sigs.k8s.io/yaml` once operator-registry has `json` tags in the
31+
// annotations struct.
32+
yaml "gopkg.in/yaml.v3"
33+
)
34+
35+
// Labels is a set of key:value labels from an operator-registry object.
36+
type Labels map[string]string
37+
38+
// GetManifestsDir returns the manifests directory name in ls using
39+
// a predefined key, or false if it does not exist.
40+
func (ls Labels) GetManifestsDir() (string, bool) {
41+
value, hasLabel := ls.getLabel(registrybundle.ManifestsLabel)
42+
return filepath.Clean(value), hasLabel
43+
}
44+
45+
// getLabel returns the string by key in ls, or an empty string and false
46+
// if key is not found in ls.
47+
func (ls Labels) getLabel(key string) (string, bool) {
48+
value, hasLabel := ls[key]
49+
return value, hasLabel
50+
}
51+
52+
// GetImageLabels returns the set of labels on image.
53+
func GetImageLabels(ctx context.Context, logger *log.Entry, image string, local bool) (Labels, error) {
54+
// Create a containerd registry for socket-less image layer reading.
55+
reg, err := containerdregistry.NewRegistry(containerdregistry.WithLog(logger))
56+
if err != nil {
57+
return nil, fmt.Errorf("error creating new image registry: %v", err)
58+
}
59+
defer func() {
60+
if err := reg.Destroy(); err != nil {
61+
logger.WithError(err).Warn("Error destroying local cache")
62+
}
63+
}()
64+
65+
// Pull the image if it isn't present locally.
66+
if !local {
67+
if err := reg.Pull(ctx, registryimage.SimpleReference(image)); err != nil {
68+
return nil, fmt.Errorf("error pulling image %s: %v", image, err)
69+
}
70+
}
71+
72+
// Query the image reference for its labels.
73+
labels, err := reg.Labels(ctx, registryimage.SimpleReference(image))
74+
if err != nil {
75+
return nil, fmt.Errorf("error reading image %s labels: %v", image, err)
76+
}
77+
78+
return labels, err
79+
}
80+
81+
// FindMetadataDir walks bundleRoot searching for metadata, and returns that directory if found.
82+
// If one is not found, an error is returned.
83+
func FindMetadataDir(bundleRoot string) (metadataDir string, err error) {
84+
err = filepath.Walk(bundleRoot, func(path string, info os.FileInfo, err error) error {
85+
if err != nil || !info.IsDir() {
86+
return err
87+
}
88+
// Already found the first metadata dir, do not overwrite it.
89+
if metadataDir != "" {
90+
return nil
91+
}
92+
// The annotations file is well-defined.
93+
_, err = os.Stat(filepath.Join(path, registrybundle.AnnotationsFile))
94+
if err == nil || errors.Is(err, os.ErrExist) {
95+
metadataDir = path
96+
return nil
97+
}
98+
return err
99+
})
100+
if err != nil {
101+
return "", err
102+
}
103+
if metadataDir == "" {
104+
return "", fmt.Errorf("metadata dir not found in %s", bundleRoot)
105+
}
106+
107+
return metadataDir, nil
108+
}
109+
110+
// GetMetadataLabels reads annotations from file(s) in metadataDir and returns them as Labels.
111+
func GetMetadataLabels(metadataDir string) (Labels, error) {
112+
// The annotations file is well-defined.
113+
annotationsPath := filepath.Join(metadataDir, registrybundle.AnnotationsFile)
114+
b, err := ioutil.ReadFile(annotationsPath)
115+
if err != nil {
116+
return nil, err
117+
}
118+
119+
// Use the arbitrarily-labelled bundle representation of the annotations file
120+
// for forwards and backwards compatibility.
121+
meta := registrybundle.AnnotationMetadata{
122+
Annotations: make(map[string]string),
123+
}
124+
if err = yaml.Unmarshal(b, &meta); err != nil {
125+
return nil, err
126+
}
127+
128+
return meta.Annotations, nil
129+
}

internal/registry/validate.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import (
2727
"gopkg.in/yaml.v2"
2828
k8svalidation "k8s.io/apimachinery/pkg/api/validation"
2929
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30-
3130
"k8s.io/apimachinery/pkg/util/validation/field"
3231
)
3332

0 commit comments

Comments
 (0)