Skip to content

Commit f1bac3b

Browse files
Merge pull request #143 from kevinrizza/bundle-validate
Bundle validate lib functions
2 parents b1b0a41 + 8e21f5e commit f1bac3b

File tree

342 files changed

+57817
-3670
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

342 files changed

+57817
-3670
lines changed

cmd/opm/alpha/bundle/cmd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ func NewCmd() *cobra.Command {
1313

1414
runCmd.AddCommand(newBundleGenerateCmd())
1515
runCmd.AddCommand(newBundleBuildCmd())
16+
runCmd.AddCommand(newBundleValidateCmd())
1617
runCmd.AddCommand(extractCmd)
1718
return runCmd
1819
}

cmd/opm/alpha/bundle/validate.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package bundle
2+
3+
import (
4+
"io/ioutil"
5+
"os"
6+
7+
"github.com/operator-framework/operator-registry/pkg/lib/bundle"
8+
log "github.com/sirupsen/logrus"
9+
"github.com/spf13/cobra"
10+
)
11+
12+
func newBundleValidateCmd() *cobra.Command {
13+
bundleValidateCmd := &cobra.Command{
14+
Use: "validate",
15+
Short: "Validate bundle image",
16+
Long: `The "opm alpha bundle validate" command will validate bundle image
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,
21+
}
22+
23+
bundleValidateCmd.Flags().StringVarP(&tagBuildArgs, "tag", "t", "",
24+
"The path of a registry to pull from, image name and its tag that present the bundle image (e.g. quay.io/test/test-operator:latest)")
25+
if err := bundleValidateCmd.MarkFlagRequired("tag"); err != nil {
26+
log.Fatalf("Failed to mark `tag` flag for `validate` subcommand as required")
27+
}
28+
29+
bundleValidateCmd.Flags().StringVarP(&imageBuilderArgs, "image-builder", "b", "docker", "Tool to build container images. One of: [docker, podman]")
30+
31+
return bundleValidateCmd
32+
}
33+
34+
func validateFunc(cmd *cobra.Command, args []string) error {
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)
53+
if err != nil {
54+
return err
55+
}
56+
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+
66+
return nil
67+
}

go.mod

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ require (
77
github.com/golang-migrate/migrate/v4 v4.6.2
88
github.com/golang/mock v1.2.0
99
github.com/golang/protobuf v1.3.2
10-
github.com/googleapis/gnostic v0.2.0 // indirect
1110
github.com/grpc-ecosystem/grpc-health-probe v0.2.1-0.20181220223928-2bf0a5b182db
12-
github.com/imdario/mergo v0.3.7 // indirect
1311
github.com/mattn/go-sqlite3 v1.10.0
1412
github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2
1513
github.com/onsi/ginkgo v1.10.1
@@ -20,16 +18,18 @@ require (
2018
github.com/sirupsen/logrus v1.4.2
2119
github.com/spf13/cobra v0.0.5
2220
github.com/stretchr/testify v1.4.0
23-
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9
21+
golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271
2422
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
25-
google.golang.org/grpc v1.23.0
23+
google.golang.org/grpc v1.24.0
2624
gopkg.in/yaml.v2 v2.2.4
27-
k8s.io/api v0.17.0
28-
k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783
29-
k8s.io/apimachinery v0.17.0
30-
k8s.io/client-go v0.17.0
25+
helm.sh/helm/v3 v3.0.1
26+
k8s.io/api v0.0.0-20191016110408-35e52d86657a
27+
k8s.io/apiextensions-apiserver v0.0.0-20191016113550-5357c4baaf65
28+
k8s.io/apimachinery v0.0.0-20191004115801-a2eda9f80ab8
29+
k8s.io/client-go v0.0.0-20191016111102-bec269661e48
3130
k8s.io/klog v1.0.0
32-
k8s.io/kubectl v0.17.0
3331
)
3432

33+
replace github.com/docker/docker => github.com/moby/moby v0.7.3-0.20190826074503-38ab9da00309 // Required by Helm
34+
3535
go 1.13

go.sum

Lines changed: 126 additions & 27 deletions
Large diffs are not rendered by default.

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: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ import (
1010
log "github.com/sirupsen/logrus"
1111

1212
"gopkg.in/yaml.v2"
13+
"helm.sh/helm/v3/pkg/chartutil"
14+
15+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
16+
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
1317
)
1418

1519
const (
@@ -93,34 +97,51 @@ func GenerateFunc(directory, packageName, channels, channelDefault string, overw
9397
return nil
9498
}
9599

96-
// GenerateFunc determines mediatype from files (yaml) in given directory
100+
// GetMediaType determines mediatype from files (yaml) in given directory
97101
// Currently able to detect helm chart, registry+v1 (CSV) and plain k8s resources
98102
// such as CRD.
99103
func GetMediaType(directory string) (string, error) {
100104
var files []string
105+
k8sFiles := make(map[string]*unstructured.Unstructured)
101106

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

112129
if len(files) == 0 {
113130
return "", fmt.Errorf("The directory %s contains no yaml files", directory)
114131
}
115132

116-
// Validate the file names to determine media type
117-
for _, file := range files {
118-
if file == "Chart.yaml" {
119-
return HelmType, nil
120-
} else if strings.HasSuffix(file, "clusterserviceversion.yaml") {
121-
return RegistryV1Type, nil
122-
} else {
123-
continue
133+
// Validate if bundle is helm chart type
134+
if _, err := chartutil.IsChartDir(directory); err == nil {
135+
return HelmType, nil
136+
}
137+
138+
// Validate the files to determine media type
139+
for _, fileName := range files {
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+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
apiVersion: v1
2+
description: A Helm chart for Kubernetes
3+
name: olm

0 commit comments

Comments
 (0)