Skip to content

Commit 96994f9

Browse files
kevinrizzadinhxuanvu
authored andcommitted
Update bundle validation lib for sdk consumption
Updated the bundle validation lib functions to expose separate functions for pulling and validating bundles -- this allows the operator sdk to run this validation against locally created bundle directories Condensed some of the bundle image parsing code to use the common containertool functions already defined elsewhere in the project Additionally, updated the logging and validation error collection to be more configurable
1 parent 5a9bc68 commit 96994f9

39 files changed

+17343
-358
lines changed

cmd/opm/alpha/bundle/validate.go

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package bundle
22

33
import (
4+
"io/ioutil"
5+
"os"
6+
47
"github.com/operator-framework/operator-registry/pkg/lib/bundle"
58
log "github.com/sirupsen/logrus"
69
"github.com/spf13/cobra"
@@ -11,12 +14,10 @@ func newBundleValidateCmd() *cobra.Command {
1114
Use: "validate",
1215
Short: "Validate bundle image",
1316
Long: `The "opm alpha bundle validate" command will validate bundle image
14-
from a remote source to determine if its format and content information are
15-
accurate.
16-
17-
$ opm alpha bundle validate --tag quay.io/test/test-operator:latest \
18-
--image-builder docker`,
19-
RunE: validateFunc,
17+
from a remote source to determine if its format and content information are
18+
accurate.`,
19+
Example: `$ opm alpha bundle validate --tag quay.io/test/test-operator:latest --image-builder docker`,
20+
RunE: validateFunc,
2021
}
2122

2223
bundleValidateCmd.Flags().StringVarP(&tagBuildArgs, "tag", "t", "",
@@ -31,10 +32,36 @@ func newBundleValidateCmd() *cobra.Command {
3132
}
3233

3334
func validateFunc(cmd *cobra.Command, args []string) error {
34-
err := bundle.ValidateFunc(tagBuildArgs, imageBuilderArgs)
35+
logger := log.WithFields(log.Fields{"container-tool": imageBuilderArgs})
36+
log.SetLevel(log.DebugLevel)
37+
38+
imageValidator := bundle.NewImageValidator(imageBuilderArgs, logger)
39+
40+
dir, err := ioutil.TempDir("", "bundle-")
41+
logger.Infof("Create a temp directory at %s", dir)
42+
if err != nil {
43+
return err
44+
}
45+
defer func() {
46+
err := os.RemoveAll(dir)
47+
if err != nil {
48+
logger.Error(err.Error())
49+
}
50+
}()
51+
52+
err = imageValidator.PullBundleImage(tagBuildArgs, dir)
3553
if err != nil {
3654
return err
3755
}
3856

57+
logger.Info("Unpacked image layers, validating bundle image contents")
58+
59+
err = imageValidator.ValidateBundle(dir)
60+
if err != nil {
61+
return err
62+
}
63+
64+
logger.Info("All validation tests have been completed successfully")
65+
3966
return nil
4067
}

pkg/containertools/imagereader.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,17 @@ func (b ImageLayerReader) GetImageData(image, outputDir string, opts ...GetImage
5858
return err
5959
}
6060

61-
workingDir := options.WorkingDir
62-
if workingDir == "" {
61+
rootTarfile := filepath.Join(options.WorkingDir, "bundle.tar")
62+
63+
if options.WorkingDir == "" {
6364
workingDir, err := ioutil.TempDir("./", "bundle_staging_")
6465
if err != nil {
6566
return err
6667
}
6768
defer os.RemoveAll(workingDir)
68-
}
6969

70-
rootTarfile := filepath.Join(workingDir, "bundle.tar")
70+
rootTarfile = filepath.Join(workingDir, "bundle.tar")
71+
}
7172

7273
err = b.Cmd.Save(image, rootTarfile)
7374
if err != nil {

pkg/lib/bundle/errors.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package bundle
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
8+
// ValidationError is an imlementation of the Error type
9+
// that defines a set of errors when validating the bundle
10+
type ValidationError struct {
11+
AnnotationErrors []error
12+
FormatErrors []error
13+
}
14+
15+
func (v ValidationError) Error() string {
16+
var errs []string
17+
for _, err := range v.AnnotationErrors {
18+
errs = append(errs, err.Error())
19+
}
20+
for _, err := range v.FormatErrors {
21+
errs = append(errs, err.Error())
22+
}
23+
return fmt.Sprintf("Bundle validation errors: %s",
24+
strings.Join(errs, ","))
25+
}
26+
27+
func NewValidationError(annotationErrs, formatErrs []error) ValidationError {
28+
return ValidationError{
29+
AnnotationErrors: annotationErrs,
30+
FormatErrors: formatErrs,
31+
}
32+
}

pkg/lib/bundle/generate.go

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
log "github.com/sirupsen/logrus"
1111

1212
"gopkg.in/yaml.v2"
13+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
14+
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
1315
)
1416

1517
const (
@@ -93,34 +95,53 @@ func GenerateFunc(directory, packageName, channels, channelDefault string, overw
9395
return nil
9496
}
9597

96-
// GenerateFunc determines mediatype from files (yaml) in given directory
98+
// GetMediaType determines mediatype from files (yaml) in given directory
9799
// Currently able to detect helm chart, registry+v1 (CSV) and plain k8s resources
98100
// such as CRD.
99101
func GetMediaType(directory string) (string, error) {
100102
var files []string
103+
k8sFiles := make(map[string]*unstructured.Unstructured)
101104

102105
// Read all file names in directory
103106
items, _ := ioutil.ReadDir(directory)
104107
for _, item := range items {
105108
if item.IsDir() {
106109
continue
107-
} else if filepath.Ext(item.Name()) == ".yaml" {
108-
files = append(files, item.Name())
110+
}
111+
112+
files = append(files, item.Name())
113+
114+
fileWithPath := filepath.Join(directory, item.Name())
115+
fileBlob, err := ioutil.ReadFile(fileWithPath)
116+
if err != nil {
117+
return "", fmt.Errorf("Unable to read file %s in bundle", fileWithPath)
118+
}
119+
120+
dec := k8syaml.NewYAMLOrJSONDecoder(strings.NewReader(string(fileBlob)), 10)
121+
unst := &unstructured.Unstructured{}
122+
if err := dec.Decode(unst); err == nil {
123+
k8sFiles[item.Name()] = unst
109124
}
110125
}
111126

112127
if len(files) == 0 {
113128
return "", fmt.Errorf("The directory %s contains no yaml files", directory)
114129
}
115130

116-
// Validate the file names to determine media type
117-
for _, file := range files {
118-
if file == "Chart.yaml" {
131+
// Validate the files to determine media type
132+
for _, fileName := range files {
133+
// TODO: be more robust here, we should validate the format of helm charts
134+
// instead of file name
135+
fmt.Println(fileName)
136+
if fileName == "Chart.yaml" {
119137
return HelmType, nil
120-
} else if strings.HasSuffix(file, "clusterserviceversion.yaml") {
121-
return RegistryV1Type, nil
122-
} else {
123-
continue
138+
}
139+
140+
// Check if one of the k8s files is a CSV
141+
if k8sFile, ok := k8sFiles[fileName]; ok {
142+
if k8sFile.GetObjectKind().GroupVersionKind().Kind == "ClusterServiceVersion" {
143+
return RegistryV1Type, nil
144+
}
124145
}
125146
}
126147

@@ -167,7 +188,7 @@ func ValidateAnnotations(existing, expected []byte) error {
167188
return nil
168189
}
169190

170-
// ValidateAnnotations validates provided default channel to ensure it exists in
191+
// ValidateChannelDefault validates provided default channel to ensure it exists in
171192
// provided channel list.
172193
func ValidateChannelDefault(channels, channelDefault string) (string, error) {
173194
var chanDefault string

pkg/lib/bundle/generate_test.go

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,46 +10,40 @@ import (
1010
)
1111

1212
func TestGetMediaType(t *testing.T) {
13-
setup("")
14-
defer cleanup()
15-
16-
testDir := getTestDir()
1713
tests := []struct {
1814
directory string
1915
mediaType string
2016
errorMsg string
2117
}{
2218
{
23-
testDir,
19+
"./testdata/get_mediatype/registry_v1_bundle",
2420
RegistryV1Type,
2521
"",
2622
},
2723
{
28-
testDir,
24+
"./testdata/get_mediatype/helm_bundle",
2925
HelmType,
3026
"",
3127
},
3228
{
33-
testDir,
29+
"./testdata/get_mediatype/plain_bundle",
3430
PlainType,
3531
"",
3632
},
3733
{
38-
testDir,
34+
"./testdata/get_mediatype/empty_bundle",
3935
"",
40-
fmt.Sprintf("The directory %s contains no yaml files", testDir),
36+
fmt.Sprintf("The directory contains no files"),
4137
},
4238
}
4339

4440
for _, item := range tests {
45-
createFiles(testDir, item.mediaType)
4641
manifestType, err := GetMediaType(item.directory)
4742
if item.errorMsg == "" {
4843
require.Equal(t, item.mediaType, manifestType)
4944
} else {
50-
require.Equal(t, item.errorMsg, err.Error())
45+
require.Error(t, err)
5146
}
52-
clearDir(testDir)
5347
}
5448
}
5549

pkg/lib/bundle/interfaces.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package bundle
2+
3+
import (
4+
"github.com/operator-framework/operator-registry/pkg/containertools"
5+
6+
"github.com/sirupsen/logrus"
7+
)
8+
9+
// BundleImageValidator provides a toolset for pulling and then validating
10+
// bundle container images
11+
type BundleImageValidator interface {
12+
// PullBundleImage takes an imageTag to pull and a directory to push
13+
// the contents of the image to
14+
PullBundleImage(imageTag string, directory string) error
15+
// Validate bundle takes a directory containing the contents of a bundle image
16+
// and validates that the format is correct
17+
ValidateBundle(directory string) error
18+
}
19+
20+
// NewImageValidator is a constructor that returns an ImageValidator
21+
func NewImageValidator(containerTool string, logger *logrus.Entry) BundleImageValidator {
22+
return imageValidator{
23+
imageReader: containertools.NewImageReader(containerTool, logger),
24+
logger: logger,
25+
}
26+
}
-366 KB
Binary file not shown.

pkg/lib/bundle/testdata/expectedBundle/b7e63f6a13273f125c08ce9681e049d2946476f15bda3c556c93ffd3d3574bcd.json

Lines changed: 0 additions & 1 deletion
This file was deleted.
-366 KB
Binary file not shown.

pkg/lib/bundle/testdata/expectedBundle/cba8efc9fb725f08c9956e87bfb84e92037b81264ba0a5a0633f546cb4f7d966/VERSION

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)