Skip to content

Commit 94d8034

Browse files
authored
Merge pull request #3 from estroz/validator-interface
add initial validation library
2 parents 801ef1d + 02bfec5 commit 94d8034

File tree

22 files changed

+2694
-1
lines changed

22 files changed

+2694
-1
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,10 @@
11
# api
2-
Contains the API definitions used by OLM and Marketplace
2+
3+
Contains the API definitions used by [Operator Lifecycle Manager][olm] (OLM) and [Marketplace Operator][marketplace]
4+
5+
## `pkg/validation`: Operator Manifest Validation
6+
7+
`pkg/validation` exposes a convenient set of interfaces to validate Kubernetes object manifests, primarily for use in an Operator project.
8+
9+
[olm]:https://github.com/operator-framework/operator-lifecycle-manager
10+
[marketplace]:https://github.com/operator-framework/operator-marketplace

cmd/operator-verify/main.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
manifests "github.com/operator-framework/api/cmd/operator-verify/manifests"
8+
9+
"github.com/spf13/cobra"
10+
)
11+
12+
func main() {
13+
rootCmd := &cobra.Command{
14+
Use: "operator-verify",
15+
Short: "Operator manifest validation tool",
16+
Long: `operator-verify is a CLI tool that calls functions in pkg/validation.`,
17+
}
18+
19+
rootCmd.AddCommand(manifests.NewCmd())
20+
if err := rootCmd.Execute(); err != nil {
21+
fmt.Println(err)
22+
os.Exit(1)
23+
}
24+
}

cmd/operator-verify/manifests/cmd.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package manifests
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/operator-framework/api/pkg/manifests"
8+
"github.com/operator-framework/api/pkg/validation/errors"
9+
10+
log "github.com/sirupsen/logrus"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
func NewCmd() *cobra.Command {
15+
return &cobra.Command{
16+
Use: "manifests",
17+
Short: "Validates all manifests in a directory",
18+
Long: `'operator-verify manifests' validates all manifests in the supplied directory
19+
and prints errors and warnings corresponding to each manifest found to be
20+
invalid. Manifests are only validated if a validator for that manifest
21+
type/kind, ex. CustomResourceDefinition, is implemented in the Operator
22+
validation library.`,
23+
Run: func(cmd *cobra.Command, args []string) {
24+
if len(args) != 1 {
25+
log.Fatalf("command %s requires exactly one argument", cmd.CommandPath())
26+
}
27+
_, _, results := manifests.GetManifestsDir(args[0])
28+
nonEmptyResults := []errors.ManifestResult{}
29+
for _, result := range results {
30+
if result.HasError() || result.HasWarn() {
31+
nonEmptyResults = append(nonEmptyResults, result)
32+
}
33+
}
34+
if len(nonEmptyResults) != 0 {
35+
fmt.Println(nonEmptyResults)
36+
os.Exit(1)
37+
}
38+
},
39+
}
40+
}

go.mod

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
module github.com/operator-framework/api
2+
3+
go 1.12
4+
5+
replace (
6+
// Pin openshift version to 4.2 (uses kube 1.14)
7+
github.com/openshift/api => github.com/openshift/api v3.9.1-0.20190717200738-0390d1e77d64+incompatible
8+
github.com/openshift/client-go => github.com/openshift/client-go v0.0.0-20190627172412-c44a8b61b9f4
9+
10+
// Pin kube version to 1.14
11+
k8s.io/api => k8s.io/api v0.0.0-20190704095032-f4ca3d3bdf1d
12+
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.0.0-20190704104557-6209bbe9f7a9
13+
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190704094733-8f6ac2502e51
14+
k8s.io/apiserver => k8s.io/apiserver v0.0.0-20190704101451-e5f5c6e528cd
15+
k8s.io/client-go => k8s.io/client-go v11.0.1-0.20190521191137-11646d1007e0+incompatible
16+
k8s.io/code-generator => k8s.io/code-generator v0.0.0-20190704094409-6c2a4329ac29
17+
k8s.io/component-base => k8s.io/component-base v0.0.0-20190704100636-f0322db00a10
18+
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.0.0-20190704101955-e796fd6d55e0
19+
k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30
20+
k8s.io/kubernetes => k8s.io/kubernetes v1.14.5-beta.0.0.20190708100021-7936da50c68f
21+
sigs.k8s.io/structured-merge-diff => sigs.k8s.io/structured-merge-diff v0.0.0-20190302045857-e85c7b244fd2
22+
)
23+
24+
require (
25+
github.com/blang/semver v3.5.1+incompatible
26+
github.com/ghodss/yaml v1.0.0
27+
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect
28+
github.com/hashicorp/golang-lru v0.5.3 // indirect
29+
github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190926160646-a61144936680
30+
github.com/operator-framework/operator-registry v1.5.3
31+
github.com/pkg/errors v0.8.1
32+
github.com/sirupsen/logrus v1.4.2
33+
github.com/spf13/cobra v0.0.5
34+
github.com/stretchr/testify v1.3.0
35+
k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783
36+
k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655
37+
k8s.io/client-go v8.0.0+incompatible
38+
)

go.sum

Lines changed: 560 additions & 0 deletions
Large diffs are not rendered by default.

pkg/internal/bundle.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package manifests
2+
3+
import (
4+
"encoding/json"
5+
6+
"github.com/blang/semver"
7+
operatorsv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
8+
"github.com/operator-framework/operator-registry/pkg/registry"
9+
"github.com/operator-framework/operator-registry/pkg/sqlite"
10+
"github.com/pkg/errors"
11+
)
12+
13+
// TODO: use internal version of registry.Bundle/registry.PackageManifest so
14+
// operator-registry can use validation library.
15+
16+
// manifestsLoad loads a manifests directory from disk.
17+
type manifestsLoad struct {
18+
dir string
19+
pkg registry.PackageManifest
20+
bundles map[string]*registry.Bundle
21+
}
22+
23+
// Ensure manifestsLoad implements registry.Load.
24+
var _ registry.Load = &manifestsLoad{}
25+
26+
// populate uses operator-registry's sqlite.NewSQLLoaderForDirectory to load
27+
// l.dir's manifests. Note that this method does not call any functions that
28+
// use SQL drivers.
29+
func (l *manifestsLoad) populate() error {
30+
loader := sqlite.NewSQLLoaderForDirectory(l, l.dir)
31+
if err := loader.Populate(); err != nil {
32+
return errors.Wrapf(err, "error getting bundles from manifests dir %q", l.dir)
33+
}
34+
return nil
35+
}
36+
37+
// AddOperatorBundle adds a bundle to l.
38+
func (l *manifestsLoad) AddOperatorBundle(bundle *registry.Bundle) error {
39+
csvRaw, err := bundle.ClusterServiceVersion()
40+
if err != nil {
41+
return errors.Wrap(err, "error getting bundle CSV")
42+
}
43+
csvSpec := operatorsv1alpha1.ClusterServiceVersionSpec{}
44+
if err := json.Unmarshal(csvRaw.Spec, &csvSpec); err != nil {
45+
return errors.Wrap(err, "error unmarshaling CSV spec")
46+
}
47+
bundle.Name = csvSpec.Version.String()
48+
l.bundles[csvSpec.Version.String()] = bundle
49+
return nil
50+
}
51+
52+
// AddOperatorBundle adds the package manifest to l.
53+
func (l *manifestsLoad) AddPackageChannels(pkg registry.PackageManifest) error {
54+
l.pkg = pkg
55+
return nil
56+
}
57+
58+
// AddBundlePackageChannels is a no-op to implement the registry.Load interface.
59+
func (l *manifestsLoad) AddBundlePackageChannels(manifest registry.PackageManifest, bundle registry.Bundle) error {
60+
return nil
61+
}
62+
63+
// RmPackageName is a no-op to implement the registry.Load interface.
64+
func (l *manifestsLoad) RmPackageName(packageName string) error {
65+
return nil
66+
}
67+
68+
// ClearNonDefaultBundles is a no-op to implement the registry.Load interface.
69+
func (l *manifestsLoad) ClearNonDefaultBundles(packageName string) error {
70+
return nil
71+
}
72+
73+
// ManifestsStore knows how to query for an operator's package manifest and
74+
// related bundles.
75+
type ManifestsStore interface {
76+
// GetPackageManifest returns the ManifestsStore's registry.PackageManifest.
77+
// The returned object is assumed to be valid.
78+
GetPackageManifest() registry.PackageManifest
79+
// GetBundles returns the ManifestsStore's set of registry.Bundle. These
80+
// bundles are unique by CSV version, since only one operator type should
81+
// exist in one manifests dir.
82+
// The returned objects are assumed to be valid.
83+
GetBundles() []*registry.Bundle
84+
// GetBundleForVersion returns the ManifestsStore's registry.Bundle for a
85+
// given version string. An error should be returned if the passed version
86+
// does not exist in the store.
87+
// The returned object is assumed to be valid.
88+
GetBundleForVersion(string) (*registry.Bundle, error)
89+
}
90+
91+
// manifests implements ManifestsStore
92+
type manifests struct {
93+
pkg registry.PackageManifest
94+
bundles map[string]*registry.Bundle
95+
}
96+
97+
// ManifestsStoreForDir populates a ManifestsStore from the metadata in dir.
98+
// Each bundle and the package manifest are statically validated, and will
99+
// return an error if any are not valid.
100+
func ManifestsStoreForDir(dir string) (ManifestsStore, error) {
101+
load := &manifestsLoad{
102+
dir: dir,
103+
bundles: map[string]*registry.Bundle{},
104+
}
105+
if err := load.populate(); err != nil {
106+
return nil, err
107+
}
108+
return &manifests{
109+
pkg: load.pkg,
110+
bundles: load.bundles,
111+
}, nil
112+
}
113+
114+
func (l manifests) GetPackageManifest() registry.PackageManifest {
115+
return l.pkg
116+
}
117+
118+
func (l manifests) GetBundles() (bundles []*registry.Bundle) {
119+
for _, bundle := range l.bundles {
120+
bundles = append(bundles, bundle)
121+
}
122+
return bundles
123+
}
124+
125+
func (l manifests) GetBundleForVersion(version string) (*registry.Bundle, error) {
126+
if _, err := semver.Parse(version); err != nil {
127+
return nil, errors.Wrapf(err, "error getting bundle for version %q", version)
128+
}
129+
bundle, ok := l.bundles[version]
130+
if !ok {
131+
return nil, errors.Errorf("bundle for version %q does not exist", version)
132+
}
133+
return bundle, nil
134+
}

pkg/manifests/directory.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package manifests
2+
3+
import (
4+
"fmt"
5+
6+
internal "github.com/operator-framework/api/pkg/internal"
7+
"github.com/operator-framework/api/pkg/validation"
8+
"github.com/operator-framework/api/pkg/validation/errors"
9+
10+
"github.com/operator-framework/operator-registry/pkg/registry"
11+
)
12+
13+
// GetManifestsDir parses all bundles and a package manifest from dir, which
14+
// are returned if found along with any errors or warnings encountered while
15+
// parsing/validating found manifests.
16+
func GetManifestsDir(dir string) (registry.PackageManifest, []*registry.Bundle, []errors.ManifestResult) {
17+
manifests, err := internal.ManifestsStoreForDir(dir)
18+
if err != nil {
19+
result := errors.ManifestResult{}
20+
result.Add(errors.ErrInvalidParse(fmt.Sprintf("parse manifests from %q", dir), err))
21+
return registry.PackageManifest{}, nil, []errors.ManifestResult{result}
22+
}
23+
pkg := manifests.GetPackageManifest()
24+
bundles := manifests.GetBundles()
25+
objs := []interface{}{}
26+
for _, obj := range bundles {
27+
objs = append(objs, obj)
28+
}
29+
results := validation.AllValidators.Validate(objs...)
30+
return pkg, bundles, results
31+
}

pkg/validation/doc.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// This package defines the valid Operator manifests directory format
2+
// by exposing a set of Validator's to verify a directory and
3+
// its constituent manifests. A manifests directory consists of a
4+
// package manifest and a set of versioned Bundles. Each Bundle contains a
5+
// ClusterServiceVersion and one or more CustomResourceDefinition's.
6+
//
7+
// Errors and warnings, both represented by the Error type, are returned
8+
// by exported functions for missing mandatory and optional fields,
9+
// respectively. Each Error implements the error interface.
10+
//
11+
// Manifest and Bundle format: https://github.com/operator-framework/operator-registry/#manifest-format
12+
// ClusterServiceVersion documentation: https://github.com/operator-framework/operator-lifecycle-manager/blob/master/Documentation/design/building-your-csv.md
13+
// Package manifest documentation: https://github.com/operator-framework/operator-lifecycle-manager#discovery-catalogs-and-automated-upgrades
14+
// CustomResourceDefinition documentation: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/
15+
package validation

0 commit comments

Comments
 (0)