diff --git a/cmd/operator-sdk/bundle/cmd.go b/cmd/operator-sdk/bundle/cmd.go index 3fc3a05861..48f5473c56 100644 --- a/cmd/operator-sdk/bundle/cmd.go +++ b/cmd/operator-sdk/bundle/cmd.go @@ -29,7 +29,7 @@ type bundleCmd struct { generateOnly bool } -func NewCmd() *cobra.Command { +func newCmd() *cobra.Command { cmd := &cobra.Command{ Use: "bundle", Short: "Manage operator bundle metadata", @@ -44,10 +44,22 @@ Operator Lifecycle Manager: https://github.com/operator-framework/operator-lifecycle-manager `, } + return cmd +} +func NewCmdLegacy() *cobra.Command { + cmd := newCmd() cmd.AddCommand( newCreateCmd(), newValidateCmd(), ) return cmd } + +func NewCmd() *cobra.Command { + cmd := newCmd() + cmd.AddCommand( + newValidateCmd(), + ) + return cmd +} diff --git a/cmd/operator-sdk/cli/cli.go b/cmd/operator-sdk/cli/cli.go index 1bb19dc6ba..dc35b24dc7 100644 --- a/cmd/operator-sdk/cli/cli.go +++ b/cmd/operator-sdk/cli/cli.go @@ -20,6 +20,7 @@ import ( "github.com/operator-framework/operator-sdk/cmd/operator-sdk/bundle" "github.com/operator-framework/operator-sdk/cmd/operator-sdk/cleanup" "github.com/operator-framework/operator-sdk/cmd/operator-sdk/completion" + "github.com/operator-framework/operator-sdk/cmd/operator-sdk/generate" "github.com/operator-framework/operator-sdk/cmd/operator-sdk/olm" "github.com/operator-framework/operator-sdk/cmd/operator-sdk/run" "github.com/operator-framework/operator-sdk/cmd/operator-sdk/scorecard" @@ -41,17 +42,16 @@ var commands = []*cobra.Command{ // new.NewCmd() alpha.NewCmd(), + build.NewCmd(), bundle.NewCmd(), cleanup.NewCmd(), completion.NewCmd(), + generate.NewCmd(), olm.NewCmd(), run.NewCmd(), scorecard.NewCmd(), test.NewCmd(), version.NewCmd(), - build.NewCmd(), - - // TODO(hasbro17): add generate csv command after aligning it for kubebuilder layout } func Run() error { diff --git a/cmd/operator-sdk/cli/legacy.go b/cmd/operator-sdk/cli/legacy.go index 9f008511a2..14f2b13eb0 100644 --- a/cmd/operator-sdk/cli/legacy.go +++ b/cmd/operator-sdk/cli/legacy.go @@ -73,11 +73,11 @@ func GetCLIRoot() *cobra.Command { add.NewCmd(), alpha.NewCmd(), build.NewCmd(), - bundle.NewCmd(), + bundle.NewCmdLegacy(), cleanup.NewCmd(), completion.NewCmd(), execentrypoint.NewCmd(), - generate.NewCmd(), + generate.NewCmdLegacy(), migrate.NewCmd(), new.NewCmd(), olm.NewCmd(), diff --git a/cmd/operator-sdk/generate/bundle/bundle.go b/cmd/operator-sdk/generate/bundle/bundle.go new file mode 100644 index 0000000000..55be4b333a --- /dev/null +++ b/cmd/operator-sdk/generate/bundle/bundle.go @@ -0,0 +1,215 @@ +// 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 bundle + +import ( + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/operator-framework/operator-registry/pkg/lib/bundle" + "sigs.k8s.io/kubebuilder/pkg/model/config" + + genutil "github.com/operator-framework/operator-sdk/cmd/operator-sdk/generate/internal" + gencsv "github.com/operator-framework/operator-sdk/internal/generate/clusterserviceversion" + "github.com/operator-framework/operator-sdk/internal/generate/collector" + "github.com/operator-framework/operator-sdk/internal/scaffold/kustomize" +) + +// TODO: make these paths relative to inputDir +const kubebuilderKustomization = `resources: +- ../default +- ../samples +` + +func (c *bundleCmd) setDefaults(cfg *config.Config) { + if c.operatorName == "" { + c.operatorName = filepath.Base(cfg.Repo) + } +} + +func (c bundleCmd) runKustomize(cfg *config.Config) error { + + if !c.quiet { + fmt.Println("Generating bundle manifest kustomize bases") + } + + defaultDir := filepath.Join("config", "bundle") + if c.inputDir == "" { + c.inputDir = defaultDir + } + if c.outputDir == "" { + c.outputDir = defaultDir + } + if c.apisDir == "" { + if cfg.MultiGroup { + c.apisDir = "apis" + } else { + c.apisDir = "api" + } + } + + csvGen := gencsv.Generator{ + OperatorName: c.operatorName, + OperatorType: genutil.PluginKeyToOperatorType(cfg.Layout), + } + opts := []gencsv.Option{ + gencsv.WithBase(c.inputDir, c.apisDir), + gencsv.WithBaseWriter(c.outputDir), + } + if err := csvGen.Generate(cfg, opts...); err != nil { + return fmt.Errorf("error generating ClusterServiceVersion: %v", err) + } + + if err := kustomize.WriteIfNotExist(c.outputDir, kubebuilderKustomization); err != nil { + return err + } + + if !c.quiet { + fmt.Println("Bases generated successfully") + } + + return nil +} + +func (c bundleCmd) validateManifests(*config.Config) (err error) { + if c.version != "" { + if err := genutil.ValidateVersion(c.version); err != nil { + return err + } + } + + if !genutil.IsPipeReader() { + if c.manifestRoot == "" { + return errors.New("--manifest-root must be set if not reading from stdin") + } + if c.crdsDir == "" { + return errors.New("--crd-dir must be set if not reading from stdin") + } + } + + if c.stdout { + if c.outputDir != "" { + return errors.New("--output-dir cannot be set if writing to stdout") + } + } + + return nil +} + +func (c bundleCmd) runManifests(cfg *config.Config) (err error) { + + if !c.quiet && !c.stdout { + if c.version == "" { + fmt.Println("Generating bundle manifests") + } else { + fmt.Println("Generating bundle manifests version", c.version) + } + } + + defaultBundleDir := filepath.Join("config", "bundle") + if c.inputDir == "" { + c.inputDir = defaultBundleDir + } + if !c.stdout { + if c.outputDir == "" { + c.outputDir = defaultBundleDir + } + } + // Only regenerate API definitions once. + if c.apisDir == "" && !c.kustomize { + if cfg.MultiGroup { + c.apisDir = "apis" + } else { + c.apisDir = "api" + } + } + + col := &collector.Manifests{} + if genutil.IsPipeReader() { + if err := col.UpdateFromReader(os.Stdin); err != nil { + return err + } + } + if c.manifestRoot != "" { + if err := col.UpdateFromDirs(c.manifestRoot, c.crdsDir); err != nil { + return err + } + } + + csvGen := gencsv.Generator{ + OperatorName: c.operatorName, + OperatorType: genutil.PluginKeyToOperatorType(cfg.Layout), + Version: c.version, + Collector: col, + } + + stdout := genutil.NewMultiManifestWriter(os.Stdout) + opts := []gencsv.Option{ + gencsv.WithBase(c.inputDir, c.apisDir), + } + if c.stdout { + opts = append(opts, gencsv.WithWriter(stdout)) + } else { + opts = append(opts, gencsv.WithBundleWriter(c.outputDir)) + } + + if err := csvGen.Generate(cfg, opts...); err != nil { + return fmt.Errorf("error generating ClusterServiceVersion: %v", err) + } + + if c.stdout { + if err := genutil.WriteCRDs(stdout, col.CustomResourceDefinitions...); err != nil { + return err + } + } else { + dir := filepath.Join(c.outputDir, bundle.ManifestsDir) + if err := genutil.WriteCRDFiles(dir, col.CustomResourceDefinitions...); err != nil { + return err + } + } + + if !c.quiet && !c.stdout { + fmt.Println("Bundle manifests generated successfully") + } + + return nil +} + +// runMetadata generates a bundle.Dockerfile and metadata. +func (c bundleCmd) runMetadata() error { + + directory := c.inputDir + if directory == "" { + directory = filepath.Join("config", "bundle", bundle.ManifestsDir) + } else { + directory = filepath.Join(directory, bundle.ManifestsDir) + } + outputDir := c.outputDir + if filepath.Clean(outputDir) == filepath.Clean(directory) { + outputDir = "" + } + + return c.generateMetadata(directory, outputDir) +} + +func (c bundleCmd) generateMetadata(manifestsDir, outputDir string) error { + err := bundle.GenerateFunc(manifestsDir, outputDir, c.operatorName, c.channels, c.defaultChannel, c.overwrite) + if err != nil { + return fmt.Errorf("error generating bundle metadata: %v", err) + } + return nil +} diff --git a/cmd/operator-sdk/generate/bundle/bundle_legacy.go b/cmd/operator-sdk/generate/bundle/bundle_legacy.go new file mode 100644 index 0000000000..aaecefe347 --- /dev/null +++ b/cmd/operator-sdk/generate/bundle/bundle_legacy.go @@ -0,0 +1,119 @@ +// 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 bundle + +import ( + "fmt" + "path/filepath" + + "github.com/operator-framework/operator-registry/pkg/lib/bundle" + log "github.com/sirupsen/logrus" + + genutil "github.com/operator-framework/operator-sdk/cmd/operator-sdk/generate/internal" + gencsv "github.com/operator-framework/operator-sdk/internal/generate/clusterserviceversion" + "github.com/operator-framework/operator-sdk/internal/generate/collector" + "github.com/operator-framework/operator-sdk/internal/util/projutil" +) + +func (c *bundleCmd) setDefaultsLegacy() { + if c.operatorName == "" { + c.operatorName = filepath.Base(projutil.MustGetwd()) + } +} + +func (c bundleCmd) validateManifestsLegacy() error { + if c.version != "" { + if err := genutil.ValidateVersion(c.version); err != nil { + return err + } + } + return nil +} + +func (c bundleCmd) runManifestsLegacy() (err error) { + + if !c.quiet { + if c.version == "" { + log.Info("Generating bundle manifests") + } else { + log.Info("Generating bundle manifests version", c.version) + } + } + + if c.apisDir == "" { + c.apisDir = filepath.Join("pkg", "apis") + } + if c.manifestRoot == "" { + c.manifestRoot = "deploy" + } + if c.crdsDir == "" { + c.crdsDir = filepath.Join(c.manifestRoot, "crds") + } + defaultBundleDir := filepath.Join(c.manifestRoot, "bundle", c.operatorName) + if c.inputDir == "" { + c.inputDir = defaultBundleDir + } + if c.outputDir == "" { + c.outputDir = defaultBundleDir + } + + col := &collector.Manifests{} + if err := col.UpdateFromDirs(c.manifestRoot, c.crdsDir); err != nil { + return err + } + + csvGen := gencsv.Generator{ + OperatorName: c.operatorName, + OperatorType: projutil.GetOperatorType(), + Version: c.version, + Collector: col, + } + + opts := []gencsv.LegacyOption{ + gencsv.WithBundleBase(c.inputDir, c.apisDir), + gencsv.LegacyOption(gencsv.WithBundleWriter(c.outputDir)), + } + if err := csvGen.GenerateLegacy(opts...); err != nil { + return fmt.Errorf("error generating ClusterServiceVersion: %v", err) + } + + dir := filepath.Join(c.outputDir, bundle.ManifestsDir) + if err := genutil.WriteCRDFiles(dir, col.CustomResourceDefinitions...); err != nil { + return err + } + + if !c.quiet { + log.Info("Bundle manifests generated successfully") + } + + return nil +} + +// runMetadataLegacy generates a bundle.Dockerfile and metadata. +func (c bundleCmd) runMetadataLegacy() error { + + directory := c.inputDir + if directory == "" { + directory = filepath.Join("deploy", "bundle", c.operatorName, bundle.ManifestsDir) + } else { + directory = filepath.Join(directory, bundle.ManifestsDir) + } + outputDir := c.outputDir + if filepath.Clean(outputDir) == filepath.Clean(directory) { + outputDir = "" + } + + return c.generateMetadata(directory, outputDir) +} diff --git a/cmd/operator-sdk/generate/bundle/cmd.go b/cmd/operator-sdk/generate/bundle/cmd.go new file mode 100644 index 0000000000..ea39cc25bd --- /dev/null +++ b/cmd/operator-sdk/generate/bundle/cmd.go @@ -0,0 +1,167 @@ +// 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 bundle + +import ( + "fmt" + + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + kbutil "github.com/operator-framework/operator-sdk/internal/util/kubebuilder" +) + +//nolint:maligned +type bundleCmd struct { + // Options to turn on different parts of bundling. + kustomize bool + manifests bool + metadata bool + + // Common options. + operatorName string + version string + inputDir string + outputDir string + manifestRoot string + apisDir string + crdsDir string + stdout bool + quiet bool + + // Metadata options. + channels string + defaultChannel string + overwrite bool +} + +//nolint:lll +func NewCmd() *cobra.Command { + c := &bundleCmd{} + cmd := &cobra.Command{ + Use: "bundle", + Short: "Generates bundle data for the operator", + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) != 0 { + return fmt.Errorf("command %s doesn't accept any arguments", cmd.CommandPath()) + } + + // Turn them all on by default here to simplify individual flag usage. + fs := cmd.Flags() + if !fs.Changed("kustomize") && !fs.Changed("metadata") && !fs.Changed("manifests") { + c.kustomize = true + c.manifests = true + c.metadata = true + } + + cfg, err := kbutil.ReadConfig() + if err != nil { + return fmt.Errorf("error reading configuration: %v", err) + } + c.setDefaults(cfg) + + if c.kustomize { + if err = c.runKustomize(cfg); err != nil { + log.Fatalf("Error generating bundle bases: %v", err) + } + } + if c.manifests { + if err = c.validateManifests(cfg); err != nil { + return fmt.Errorf("invalid command options: %v", err) + } + if err = c.runManifests(cfg); err != nil { + log.Fatalf("Error generating bundle manifests: %v", err) + } + } + if c.metadata { + if err = c.runMetadata(); err != nil { + log.Fatalf("Error generating bundle metadata: %v", err) + } + } + + return nil + }, + } + + cmd.Flags().BoolVar(&c.kustomize, "kustomize", false, "Generate kustomize bases") + cmd.Flags().BoolVar(&c.manifests, "manifests", false, "Generate bundle manifests") + cmd.Flags().BoolVar(&c.metadata, "metadata", false, "Generate bundle metadata and Dockerfile") + cmd.Flags().BoolVar(&c.stdout, "stdout", false, "Write bundle manifest to stdout") + + c.addCommonFlagsTo(cmd.Flags()) + + return cmd +} + +func NewCmdLegacy() *cobra.Command { + c := &bundleCmd{} + cmd := &cobra.Command{ + Use: "bundle", + Short: "Generates bundle data for the operator", + RunE: func(cmd *cobra.Command, args []string) (err error) { + if len(args) != 0 { + return fmt.Errorf("command %s doesn't accept any arguments", cmd.CommandPath()) + } + + // Turn them all on by default here to simplify individual flag usage. + fs := cmd.Flags() + if !fs.Changed("metadata") && !fs.Changed("manifests") { + c.metadata = true + c.manifests = true + } + + c.setDefaultsLegacy() + + if c.manifests { + if err = c.validateManifestsLegacy(); err != nil { + return fmt.Errorf("invalid command options: %v", err) + } + if err = c.runManifestsLegacy(); err != nil { + log.Fatalf("Error generating bundle manifests: %v", err) + } + } + if c.metadata { + if err = c.runMetadataLegacy(); err != nil { + log.Fatalf("Error generating bundle metadata: %v", err) + } + } + + return nil + }, + } + + cmd.Flags().BoolVar(&c.manifests, "manifests", false, "Generate bundle manifests") + cmd.Flags().BoolVar(&c.metadata, "metadata", false, "Generate bundle metadata and Dockerfile") + + c.addCommonFlagsTo(cmd.Flags()) + + return cmd +} + +func (c *bundleCmd) addCommonFlagsTo(fs *pflag.FlagSet) { + fs.StringVar(&c.operatorName, "operator-name", "", "Name of the bundle's operator") + fs.StringVarP(&c.version, "version", "v", "", "Semantic version of the operator in the generated bundle. "+ + "Only set if creating a new bundle or upgrading your operator") + fs.StringVar(&c.inputDir, "input-dir", "", "Directory to read an existing bundle format from") + fs.StringVar(&c.outputDir, "output-dir", "", "Directory to write the bundle format to") + fs.StringVar(&c.manifestRoot, "manifest-root", "", "Root directory for operator manifests, ex. Deployment and RBAC") + fs.StringVar(&c.apisDir, "apis-dir", "", "Root directory for API type defintions") + fs.StringVar(&c.crdsDir, "crds-dir", "", "Root directory for CustomResoureDefinition and Custom Resource manifests") + fs.StringVar(&c.channels, "channels", "alpha", "A comma-separated list of channels the bundle belongs to") + fs.StringVar(&c.defaultChannel, "default-channel", "", "The default channel for the bundle") + fs.BoolVar(&c.overwrite, "overwrite", false, "Overwrite the bundle's metadata and Dockerfile if they exist") + fs.BoolVarP(&c.quiet, "quiet", "q", false, "Run in quiet mode") +} diff --git a/cmd/operator-sdk/generate/cmd.go b/cmd/operator-sdk/generate/cmd.go index 1059ce582a..089e285cfc 100644 --- a/cmd/operator-sdk/generate/cmd.go +++ b/cmd/operator-sdk/generate/cmd.go @@ -16,19 +16,37 @@ package generate import ( "github.com/spf13/cobra" + + "github.com/operator-framework/operator-sdk/cmd/operator-sdk/generate/bundle" + "github.com/operator-framework/operator-sdk/cmd/operator-sdk/generate/packagemanifests" ) -func NewCmd() *cobra.Command { - cmd := &cobra.Command{ +func newCmd() *cobra.Command { + return &cobra.Command{ Use: "generate ", Short: "Invokes a specific generator", Long: `The 'operator-sdk generate' command invokes a specific generator to generate -code or manifests on disk.`, +code or manifests.`, } +} + +func NewCmd() *cobra.Command { + cmd := newCmd() + cmd.AddCommand( + bundle.NewCmd(), + packagemanifests.NewCmd(), + ) + return cmd +} + +func NewCmdLegacy() *cobra.Command { + cmd := newCmd() cmd.AddCommand( newGenerateK8SCmd(), newGenerateCRDsCmd(), newGenerateCSVCmd(), + bundle.NewCmdLegacy(), + packagemanifests.NewCmdLegacy(), ) return cmd } diff --git a/cmd/operator-sdk/generate/internal/genutil.go b/cmd/operator-sdk/generate/internal/genutil.go new file mode 100644 index 0000000000..8cf733048a --- /dev/null +++ b/cmd/operator-sdk/generate/internal/genutil.go @@ -0,0 +1,111 @@ +// 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 genutil + +import ( + "bytes" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/blang/semver" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "sigs.k8s.io/yaml" + + "github.com/operator-framework/operator-sdk/internal/util/projutil" +) + +func ValidateVersion(version string) error { + v, err := semver.Parse(version) + if err != nil { + return fmt.Errorf("%s is not a valid semantic version: %v", version, err) + } + // Ensures numerical values composing csvVersion don't contain leading 0's, + // ex. 01.01.01 + if v.String() != version { + return fmt.Errorf("provided CSV version %s contains bad values (parses to %s)", version, v) + } + return nil +} + +func IsPipeReader() bool { + info, err := os.Stdin.Stat() + if err != nil { + return false + } + return info.Mode()&os.ModeNamedPipe != 0 +} + +func PluginKeyToOperatorType(pluginKey string) projutil.OperatorType { + switch { + case strings.HasPrefix(pluginKey, "go"): + return projutil.OperatorTypeGo + } + return "" +} + +func WriteCRDs(w io.Writer, crds ...v1beta1.CustomResourceDefinition) error { + for _, crd := range crds { + if err := writeCRD(w, crd); err != nil { + return err + } + } + return nil +} + +func WriteCRDFiles(dir string, crds ...v1beta1.CustomResourceDefinition) error { + if err := os.MkdirAll(dir, 0700); err != nil { + return err + } + for _, crd := range crds { + if err := writeCRDFile(dir, crd); err != nil { + return err + } + } + return nil +} + +func writeCRDFile(dir string, crd v1beta1.CustomResourceDefinition) error { + file := fmt.Sprintf("%s_%s.yaml", crd.Spec.Group, crd.Spec.Names.Plural) + f, err := os.Create(filepath.Join(dir, file)) + if err != nil { + return err + } + defer f.Close() + return writeCRD(f, crd) +} + +func writeCRD(w io.Writer, crd v1beta1.CustomResourceDefinition) error { + b, err := yaml.Marshal(crd) + if err != nil { + return err + } + _, err = w.Write(b) + return err +} + +type multiManifestWriter struct { + io.Writer +} + +func (w *multiManifestWriter) Write(b []byte) (int, error) { + return w.Writer.Write(append([]byte("\n---\n"), bytes.TrimSpace(b)...)) +} + +func NewMultiManifestWriter(w io.Writer) io.Writer { + return &multiManifestWriter{w} +} diff --git a/cmd/operator-sdk/generate/packagemanifests/cmd.go b/cmd/operator-sdk/generate/packagemanifests/cmd.go new file mode 100644 index 0000000000..4d69ceeed8 --- /dev/null +++ b/cmd/operator-sdk/generate/packagemanifests/cmd.go @@ -0,0 +1,148 @@ +// 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 packagemanifests + +import ( + "fmt" + + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + kbutil "github.com/operator-framework/operator-sdk/internal/util/kubebuilder" +) + +//nolint:maligned +type packagemanifestsCmd struct { + // Options to turn on different parts of packaging. + kustomize bool + manifests bool + + // Common options. + operatorName string + version string + fromVersion string + inputDir string + outputDir string + manifestRoot string + apisDir string + crdsDir string + updateCRDs bool + stdout bool + quiet bool + + // Package manifest options. + channelName string + isDefaultChannel bool +} + +//nolint:lll +func NewCmd() *cobra.Command { + c := &packagemanifestsCmd{} + + cmd := &cobra.Command{ + Use: "packagemanifests", + Short: "Generates a package manifests format", + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) != 0 { + return fmt.Errorf("command %s doesn't accept any arguments", cmd.CommandPath()) + } + + // Turn them all on by default here to simplify individual flag usage. + fs := cmd.Flags() + if !fs.Changed("kustomize") && !fs.Changed("manifests") { + c.kustomize = true + c.manifests = true + } + + cfg, err := kbutil.ReadConfig() + if err != nil { + log.Fatal(fmt.Errorf("error reading configuration: %v", err)) + } + c.setDefaults(cfg) + + if c.kustomize { + if err = c.runKustomize(cfg); err != nil { + log.Fatalf("Error generating package bases: %v", err) + } + } + if c.manifests { + if err = c.validateManifests(); err != nil { + return fmt.Errorf("invalid command options: %v", err) + } + if err = c.runManifests(cfg); err != nil { + log.Fatalf("Error generating package manifests: %v", err) + } + } + + return nil + }, + } + + cmd.Flags().BoolVar(&c.kustomize, "kustomize", false, "Generate kustomize bases") + cmd.Flags().BoolVar(&c.manifests, "manifests", false, "Generate a package") + cmd.Flags().BoolVar(&c.stdout, "stdout", false, "Write package to stdout") + + c.addCommonFlagsTo(cmd.Flags()) + + return cmd +} + +//nolint:lll +func NewCmdLegacy() *cobra.Command { + c := &packagemanifestsCmd{} + + cmd := &cobra.Command{ + Use: "packagemanifests", + Short: "Generates a package manifests format", + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) != 0 { + return fmt.Errorf("command %s doesn't accept any arguments", cmd.CommandPath()) + } + + c.setDefaultsLegacy() + if err := c.validateLegacy(); err != nil { + return fmt.Errorf("invalid command options: %v", err) + } + if err := c.runLegacy(); err != nil { + log.Fatalf("Error generating package manifests format: %v", err) + } + + return nil + }, + } + + cmd.Flags().StringVar(&c.fromVersion, "from-version", "", "Semantic version of the existing ClusterServiceVersion to update") + + c.addCommonFlagsTo(cmd.Flags()) + + return cmd +} + +func (c *packagemanifestsCmd) addCommonFlagsTo(fs *pflag.FlagSet) { + fs.StringVar(&c.operatorName, "operator-name", "", "Name of the operator to generate the package for") + fs.StringVarP(&c.version, "version", "v", "", "Semantic version of the generated package") + fs.StringVar(&c.inputDir, "input-dir", "", "Directory to read an existing package manifests format from") + fs.StringVar(&c.outputDir, "output-dir", "", "Directory in which to write the package manifests format") + fs.StringVar(&c.manifestRoot, "manifest-root", "", "Root directory for operator manifests, ex. Deployment and RBAC") + fs.StringVar(&c.apisDir, "apis-dir", "", "Root directory for API type defintions") + fs.StringVar(&c.crdsDir, "crds-dir", "", "Root directory for CustomResoureDefinition and Custom Resource manifests") + fs.StringVar(&c.channelName, "channel", "", "Channel name for the generated package") + fs.BoolVar(&c.isDefaultChannel, "default-channel", false, "Use the channel passed to --channel "+ + "as the package manifests' default channel") + fs.BoolVar(&c.updateCRDs, "update-crds", false, "Update CustomResoureDefinition manifests "+ + "in the package for this version") + fs.BoolVarP(&c.quiet, "quiet", "q", false, "Run in quiet mode") +} diff --git a/cmd/operator-sdk/generate/packagemanifests/packagemanifests.go b/cmd/operator-sdk/generate/packagemanifests/packagemanifests.go new file mode 100644 index 0000000000..9137273a6e --- /dev/null +++ b/cmd/operator-sdk/generate/packagemanifests/packagemanifests.go @@ -0,0 +1,213 @@ +// 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 packagemanifests + +import ( + "errors" + "fmt" + "os" + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/config" + + genutil "github.com/operator-framework/operator-sdk/cmd/operator-sdk/generate/internal" + gencsv "github.com/operator-framework/operator-sdk/internal/generate/clusterserviceversion" + "github.com/operator-framework/operator-sdk/internal/generate/collector" + genpkg "github.com/operator-framework/operator-sdk/internal/generate/packagemanifest" + "github.com/operator-framework/operator-sdk/internal/scaffold/kustomize" +) + +// TODO: make these paths relative to inputDir +const kubebuilderKustomization = `resources: +- ../default +- ../samples +` + +func (c *packagemanifestsCmd) setDefaults(cfg *config.Config) { + if c.operatorName == "" { + c.operatorName = filepath.Base(cfg.Repo) + } +} + +func (c packagemanifestsCmd) runKustomize(cfg *config.Config) error { + + if !c.quiet { + fmt.Println("Generating package manifests format kustomize bases") + } + + defaultDir := filepath.Join("config", "packages") + if c.inputDir == "" { + c.inputDir = defaultDir + } + if c.outputDir == "" { + c.outputDir = defaultDir + } + if c.apisDir == "" { + if cfg.MultiGroup { + c.apisDir = "apis" + } else { + c.apisDir = "api" + } + } + + csvGen := gencsv.Generator{ + OperatorName: c.operatorName, + OperatorType: genutil.PluginKeyToOperatorType(cfg.Layout), + } + opts := []gencsv.Option{ + gencsv.WithBase(c.inputDir, c.apisDir), + gencsv.WithBaseWriter(c.outputDir), + } + if err := csvGen.Generate(cfg, opts...); err != nil { + return fmt.Errorf("error generating ClusterServiceVersion: %v", err) + } + + if err := kustomize.WriteIfNotExist(c.outputDir, kubebuilderKustomization); err != nil { + return err + } + + if !c.quiet { + fmt.Println("Bases generated successfully") + } + + return nil +} + +func (c packagemanifestsCmd) validateManifests() error { + + if err := genutil.ValidateVersion(c.version); err != nil { + return err + } + + if c.fromVersion != "" { + return errors.New("--from-version cannot be set for PROJECT configured projects") + } + + if !genutil.IsPipeReader() { + if c.manifestRoot == "" { + return errors.New("--manifest-root must be set if not reading from stdin") + } + if c.crdsDir == "" { + return errors.New("--crd-dir must be set if not reading from stdin") + } + } + + if c.stdout { + if c.outputDir != "" { + return errors.New("--output-dir cannot be set if writing to stdout") + } + } + + if c.isDefaultChannel && c.channelName == "" { + return fmt.Errorf("--default-channel can only be set if --channel is set") + } + + return nil +} + +func (c packagemanifestsCmd) runManifests(cfg *config.Config) error { + + if !c.quiet { + fmt.Printf("Generating package version %s\n", c.version) + } + + defaultDir := filepath.Join("config", "packages") + if c.inputDir == "" { + c.inputDir = defaultDir + } + if !c.stdout { + if c.outputDir == "" { + c.outputDir = defaultDir + } + } + if c.apisDir == "" && !c.kustomize { + if cfg.MultiGroup { + c.apisDir = "apis" + } else { + c.apisDir = "api" + } + } + + if err := c.generatePackageManifest(); err != nil { + return err + } + + col := &collector.Manifests{} + if genutil.IsPipeReader() { + if err := col.UpdateFromReader(os.Stdin); err != nil { + return err + } + } + if c.manifestRoot != "" { + if err := col.UpdateFromDirs(c.manifestRoot, c.crdsDir); err != nil { + return err + } + } + + csvGen := gencsv.Generator{ + OperatorName: c.operatorName, + OperatorType: genutil.PluginKeyToOperatorType(cfg.Layout), + Version: c.version, + Collector: col, + } + + stdout := genutil.NewMultiManifestWriter(os.Stdout) + opts := []gencsv.Option{ + gencsv.WithBase(c.inputDir, c.apisDir), + } + if c.stdout { + opts = append(opts, gencsv.WithWriter(stdout)) + } else { + opts = append(opts, gencsv.WithPackageWriter(c.outputDir)) + } + + if err := csvGen.Generate(cfg, opts...); err != nil { + return fmt.Errorf("error generating ClusterServiceVersion: %v", err) + } + + if c.stdout { + if err := genutil.WriteCRDs(stdout, col.CustomResourceDefinitions...); err != nil { + return err + } + } else if c.updateCRDs { + dir := filepath.Join(c.outputDir, c.version) + if err := genutil.WriteCRDFiles(dir, col.CustomResourceDefinitions...); err != nil { + return err + } + } + + if !c.quiet { + fmt.Println("Package generated successfully") + } + + return nil +} + +func (c packagemanifestsCmd) generatePackageManifest() error { + pkgGen := genpkg.Generator{ + OperatorName: c.operatorName, + Version: c.version, + ChannelName: c.channelName, + IsDefaultChannel: c.isDefaultChannel, + } + opts := []genpkg.Option{ + genpkg.WithGetBase(c.inputDir), + genpkg.WithFileWriter(c.outputDir), + } + if err := pkgGen.Generate(opts...); err != nil { + return err + } + return nil +} diff --git a/cmd/operator-sdk/generate/packagemanifests/packagemanifests_legacy.go b/cmd/operator-sdk/generate/packagemanifests/packagemanifests_legacy.go new file mode 100644 index 0000000000..b45e6e3fa3 --- /dev/null +++ b/cmd/operator-sdk/generate/packagemanifests/packagemanifests_legacy.go @@ -0,0 +1,110 @@ +// 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 packagemanifests + +import ( + "fmt" + "path/filepath" + + log "github.com/sirupsen/logrus" + + genutil "github.com/operator-framework/operator-sdk/cmd/operator-sdk/generate/internal" + gencsv "github.com/operator-framework/operator-sdk/internal/generate/clusterserviceversion" + "github.com/operator-framework/operator-sdk/internal/generate/collector" + "github.com/operator-framework/operator-sdk/internal/util/projutil" +) + +func (c *packagemanifestsCmd) setDefaultsLegacy() { + if c.operatorName == "" { + c.operatorName = filepath.Base(projutil.MustGetwd()) + } +} + +func (c packagemanifestsCmd) validateLegacy() error { + if err := genutil.ValidateVersion(c.version); err != nil { + return err + } + if c.fromVersion != "" { + if err := genutil.ValidateVersion(c.fromVersion); err != nil { + return err + } + if c.version == c.fromVersion { + return fmt.Errorf("--from-version (%s) cannot equal --version; set --version instead", c.fromVersion) + } + } + + if c.isDefaultChannel && c.channelName == "" { + return fmt.Errorf("--default-channel can only be set if --channel is set") + } + + return nil +} + +func (c packagemanifestsCmd) runLegacy() error { + log.Infof("Generating package version %s", c.version) + + if c.manifestRoot == "" { + c.manifestRoot = "deploy" + } + if c.crdsDir == "" { + c.crdsDir = filepath.Join(c.manifestRoot, "crds") + } + if c.apisDir == "" { + c.apisDir = filepath.Join("pkg", "apis") + } + defaultDir := filepath.Join(c.manifestRoot, "olm-catalog", c.operatorName) + if c.inputDir == "" { + c.inputDir = defaultDir + } + if c.outputDir == "" { + c.outputDir = defaultDir + } + + if err := c.generatePackageManifest(); err != nil { + return err + } + + col := &collector.Manifests{} + if err := col.UpdateFromDirs(c.manifestRoot, c.crdsDir); err != nil { + return err + } + + csvGen := gencsv.Generator{ + OperatorName: c.operatorName, + OperatorType: projutil.GetOperatorType(), + Version: c.version, + FromVersion: c.fromVersion, + Collector: col, + } + + opts := []gencsv.LegacyOption{ + gencsv.WithPackageBase(c.inputDir, c.apisDir), + gencsv.WithPackageWriterLegacy(c.outputDir), + } + if err := csvGen.GenerateLegacy(opts...); err != nil { + return err + } + + if c.updateCRDs { + dir := filepath.Join(c.outputDir, c.version) + if err := genutil.WriteCRDFiles(dir, col.CustomResourceDefinitions...); err != nil { + return err + } + } + + log.Info("Package generated successfully") + + return nil +} diff --git a/cmd/operator-sdk/test/local.go b/cmd/operator-sdk/test/local.go index bb1a57454e..cc1594e334 100644 --- a/cmd/operator-sdk/test/local.go +++ b/cmd/operator-sdk/test/local.go @@ -15,6 +15,7 @@ package test import ( + "bytes" "errors" "fmt" "io/ioutil" @@ -307,7 +308,7 @@ func replaceImage(manifestPath, image string) error { } foundDeployment := false newManifest := []byte{} - scanner := internalk8sutil.NewYAMLScanner(yamlFile) + scanner := internalk8sutil.NewYAMLScanner(bytes.NewBuffer(yamlFile)) for scanner.Scan() { yamlSpec := scanner.Bytes() diff --git a/internal/generate/clusterserviceversion/bases/clusterserviceversion.go b/internal/generate/clusterserviceversion/bases/clusterserviceversion.go new file mode 100644 index 0000000000..ee0ec5f21d --- /dev/null +++ b/internal/generate/clusterserviceversion/bases/clusterserviceversion.go @@ -0,0 +1,184 @@ +// 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 bases + +import ( + "bytes" + "fmt" + "io/ioutil" + + operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + log "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/yaml" + + "github.com/operator-framework/operator-sdk/internal/util/k8sutil" + "github.com/operator-framework/operator-sdk/internal/util/projutil" +) + +type ClusterServiceVersion struct { + // OperatorName is the operator's name, ex. app-operator + OperatorName string + // OperatorType + OperatorType projutil.OperatorType + // APIsDir contains project API definition files. + APIsDir string + // GVKs are all GroupVersionKinds in the project. + GVKs []schema.GroupVersionKind + // BasePath is the path to the base being read. + BasePath string + + // Fields for input to the base. + DisplayName string + Description string + Maturity string + Capabilities string + Keywords []string + Provider operatorsv1alpha1.AppLink + Links []operatorsv1alpha1.AppLink + Maintainers []operatorsv1alpha1.Maintainer + // TODO(estroz): read icon bytes from files. + Icon []operatorsv1alpha1.Icon +} + +func (b ClusterServiceVersion) GetBase() (base *operatorsv1alpha1.ClusterServiceVersion, err error) { + if b.BasePath != "" { + if base, err = readClusterServiceVersionBase(b.BasePath); err != nil { + return nil, fmt.Errorf("error reading existing ClusterServiceVersion base %s: %v", b.BasePath, err) + } + } else { + b.setDefaults() + base = b.makeNewBase() + } + + if b.APIsDir != "" { + switch b.OperatorType { + case projutil.OperatorTypeGo: + if err := updateDescriptionsForGVKs(base, b.APIsDir, b.GVKs); err != nil { + return nil, fmt.Errorf("error generating ClusterServiceVersion base metadata: %w", err) + } + } + } + + return base, nil +} + +func (b *ClusterServiceVersion) setDefaults() { + if b.DisplayName == "" { + b.DisplayName = k8sutil.GetDisplayName(b.OperatorName) + } + if b.Description == "" { + b.Description = b.DisplayName + " description. Fill me in." + } + if b.Maturity == "" { + b.Maturity = "alpha" + } + if b.Capabilities == "" { + b.Capabilities = "Basic Install" + } + if len(b.Keywords) == 0 || b.Keywords[0] == "" { + b.Keywords = []string{b.OperatorName} + } + if len(b.Links) == 0 || b.Links[0] == (operatorsv1alpha1.AppLink{}) { + b.Links = []operatorsv1alpha1.AppLink{ + { + Name: b.DisplayName, + URL: fmt.Sprintf("https://%s.domain", b.OperatorName), + }, + } + } + if len(b.Maintainers) == 0 || b.Maintainers[0] == (operatorsv1alpha1.Maintainer{}) { + b.Maintainers = []operatorsv1alpha1.Maintainer{ + { + Name: "Maintainer Name", + Email: "your@email.com", + }, + } + } + if b.Provider == (operatorsv1alpha1.AppLink{}) { + b.Provider = operatorsv1alpha1.AppLink{ + Name: "Provider Name", + URL: "https://your.domain", + } + } + if len(b.Icon) == 0 || b.Icon[0] == (operatorsv1alpha1.Icon{}) { + b.Icon = make([]operatorsv1alpha1.Icon, 1) + } +} + +func (b ClusterServiceVersion) makeNewBase() *operatorsv1alpha1.ClusterServiceVersion { + return &operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: metav1.TypeMeta{ + APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, + Kind: operatorsv1alpha1.ClusterServiceVersionKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: b.OperatorName + ".vX.Y.Z", + Namespace: "placeholder", + Annotations: map[string]string{ + "capabilities": b.Capabilities, + "alm-examples": "[]", + }, + }, + Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ + DisplayName: b.DisplayName, + Description: b.Description, + Provider: b.Provider, + Maintainers: b.Maintainers, + Links: b.Links, + Maturity: b.Maturity, + Keywords: b.Keywords, + Icon: b.Icon, + InstallModes: []operatorsv1alpha1.InstallMode{ + {Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, Supported: true}, + {Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, Supported: true}, + {Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, Supported: false}, + {Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, Supported: false}, + }, + }, + } +} + +// readClusterServiceVersionBase returns the ClusterServiceVersion base at path. +// If no base is found, readClusterServiceVersionBase returns an error. +func readClusterServiceVersionBase(path string) (*operatorsv1alpha1.ClusterServiceVersion, error) { + b, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + scanner := k8sutil.NewYAMLScanner(bytes.NewBuffer(b)) + for scanner.Scan() { + manifest := scanner.Bytes() + typeMeta, err := k8sutil.GetTypeMetaFromBytes(manifest) + if err != nil { + log.Debugf("Skipping non-Object manifest %s: %v", path, err) + continue + } + if typeMeta.Kind == operatorsv1alpha1.ClusterServiceVersionKind { + csv := &operatorsv1alpha1.ClusterServiceVersion{} + if err := yaml.Unmarshal(manifest, csv); err != nil { + return nil, fmt.Errorf("error unmarshalling ClusterServiceVersion from manifest %s: %w", path, err) + } + return csv, nil + } + } + if err = scanner.Err(); err != nil { + return nil, fmt.Errorf("error scanning manifest %s: %w", path, err) + } + + return nil, fmt.Errorf("no ClusterServiceVersion manifest in %s", path) +} diff --git a/internal/generate/clusterserviceversion/bases/markers.go b/internal/generate/clusterserviceversion/bases/markers.go new file mode 100644 index 0000000000..b567723e4a --- /dev/null +++ b/internal/generate/clusterserviceversion/bases/markers.go @@ -0,0 +1,73 @@ +// 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 bases + +import ( + "errors" + "fmt" + "strings" + + "github.com/markbates/inflect" + operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + log "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/operator-framework/operator-sdk/internal/generate/olm-catalog/descriptor" +) + +func updateDescriptionsForGVKs(csv *operatorsv1alpha1.ClusterServiceVersion, apisDir string, + gvks []schema.GroupVersionKind) error { + + gvkMap := make(map[schema.GroupVersionKind]operatorsv1alpha1.CRDDescription) + for _, desc := range csv.Spec.CustomResourceDefinitions.Owned { + group := desc.Name + if split := strings.Split(desc.Name, "."); len(split) > 1 { + group = strings.Join(split[1:], ".") + } + // Parse CRD descriptors from source code comments and annotations. + gvk := schema.GroupVersionKind{ + Group: group, + Version: desc.Version, + Kind: desc.Kind, + } + gvkMap[gvk] = desc + } + + descriptions := []operatorsv1alpha1.CRDDescription{} + for _, gvk := range gvks { + newDescription, err := descriptor.GetCRDDescriptionForGVK(apisDir, gvk) + if err != nil { + if errors.Is(err, descriptor.ErrAPIDirNotExist) { + log.Debugf("Directory for API %s does not exist. Skipping CSV annotation parsing for API.", gvk) + } else if errors.Is(err, descriptor.ErrAPITypeNotFound) { + log.Debugf("No kind type found for API %s. Skipping CSV annotation parsing for API.", gvk) + } else { + // TODO: Should we ignore all CSV annotation parsing errors and simply log the error + // like we do for the above cases. + return fmt.Errorf("failed to set CRD descriptors for %s: %v", gvk, err) + } + // Keep the existing description and don't update on error + if desc, hasDesc := gvkMap[gvk]; hasDesc { + descriptions = append(descriptions, desc) + } + } else { + // Replace the existing description with the newly parsed one + newDescription.Name = inflect.Pluralize(strings.ToLower(gvk.Kind)) + "." + gvk.Group + descriptions = append(descriptions, newDescription) + } + } + csv.Spec.CustomResourceDefinitions.Owned = descriptions + return nil +} diff --git a/internal/generate/clusterserviceversion/clusterserviceversion.go b/internal/generate/clusterserviceversion/clusterserviceversion.go new file mode 100644 index 0000000000..4333307ff1 --- /dev/null +++ b/internal/generate/clusterserviceversion/clusterserviceversion.go @@ -0,0 +1,310 @@ +// 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 clusterserviceversion + +import ( + "fmt" + "io" + "path/filepath" + "regexp" + "strings" + + "github.com/blang/semver" + operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/operator-framework/operator-registry/pkg/lib/bundle" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/kubebuilder/pkg/model/config" + "sigs.k8s.io/yaml" + + "github.com/operator-framework/operator-sdk/internal/generate/clusterserviceversion/bases" + "github.com/operator-framework/operator-sdk/internal/generate/collector" + genutil "github.com/operator-framework/operator-sdk/internal/generate/internal" + "github.com/operator-framework/operator-sdk/internal/util/projutil" +) + +const ( + // OLMCatalogDir is the named directory for OLM catalog manifests. + OLMCatalogDir = "olm-catalog" + + csvYamlFileExt = ".clusterserviceversion.yaml" +) + +type Generator struct { + // OperatorName is the operator's name, ex. app-operator + OperatorName string + // + OperatorType projutil.OperatorType + // Version is the CSV current version. + Version string + // + FromVersion string + // + Collector *collector.Manifests + + // + config *config.Config + // + getBase getBaseFunc + // + getWriter func() (io.Writer, error) +} + +type getBaseFunc func() (*operatorsv1alpha1.ClusterServiceVersion, error) + +type Option func(*Generator) error + +func WithBase(inputDir, apisDir string) Option { + return func(g *Generator) error { + g.getBase = g.makeBaseGetterKustomize(inputDir, apisDir) + return nil + } +} + +func WithWriter(w io.Writer) Option { + return func(g *Generator) error { + g.getWriter = func() (io.Writer, error) { + return w, nil + } + return nil + } +} + +func WithBaseWriter(dir string) Option { + return func(g *Generator) error { + fileName := getCSVFile(g.OperatorName) + g.getWriter = func() (io.Writer, error) { + return genutil.Open(filepath.Join(dir, "bases"), fileName) + } + return nil + } +} + +func WithBundleWriter(dir string) Option { + return func(g *Generator) error { + fileName := getCSVFile(g.OperatorName) + g.getWriter = func() (io.Writer, error) { + return genutil.Open(filepath.Join(dir, bundle.ManifestsDir), fileName) + } + return nil + } +} + +func WithPackageWriter(dir string) Option { + return func(g *Generator) error { + fileName := getCSVFile(g.OperatorName) + g.getWriter = func() (io.Writer, error) { + return genutil.Open(filepath.Join(dir, g.Version), fileName) + } + return nil + } +} + +func (g *Generator) Generate(cfg *config.Config, opts ...Option) (err error) { + g.config = cfg + for _, opt := range opts { + if err = opt(g); err != nil { + return err + } + } + + return g.generate() +} + +type LegacyOption Option + +func WithPackageWriterLegacy(dir string) LegacyOption { + return func(g *Generator) error { + fileName := getCSVFileLegacy(g.OperatorName, g.Version) + g.getWriter = func() (io.Writer, error) { + return genutil.Open(filepath.Join(dir, g.Version), fileName) + } + return nil + } +} + +func WithBundleBase(inputDir, apisDir string) LegacyOption { + return func(g *Generator) error { + g.getBase = g.makeBaseGetterBundleLegacy(inputDir, apisDir) + return nil + } +} + +func WithPackageBase(inputDir, apisDir string) LegacyOption { + return func(g *Generator) error { + g.getBase = g.makeBaseGetterPackageLegacy(inputDir, apisDir) + return nil + } +} + +func (g *Generator) GenerateLegacy(opts ...LegacyOption) (err error) { + for _, opt := range opts { + if err = opt(g); err != nil { + return err + } + } + + return g.generate() +} + +func (g *Generator) generate() (err error) { + if g.getBase == nil { + return genutil.InternalError("getBase must be set") + } + if g.getWriter == nil { + return genutil.InternalError("getWriter must be set") + } + + base, err := g.getBase() + if err != nil { + return fmt.Errorf("error getting ClusterServiceVersion base: %v", err) + } + + if err = g.updateVersions(base); err != nil { + return err + } + + if g.Collector != nil { + if err := applyTo(g.Collector, base); err != nil { + return err + } + } + + w, err := g.getWriter() + if err != nil { + return err + } + return genutil.WriteObject(w, base) +} + +func getCSVFile(name string) string { + return strings.ToLower(name) + csvYamlFileExt +} + +func getCSVFileLegacy(name, version string) string { + return fmt.Sprintf("%s.v%s%s", strings.ToLower(name), version, csvYamlFileExt) +} + +func (g Generator) makeBaseGetterKustomize(inputDir, apisDir string) getBaseFunc { + basePath := filepath.Join(inputDir, "bases", getCSVFile(g.OperatorName)) + if genutil.IsNotExist(basePath) { + basePath = "" + } + + return g.makeBaseGetter(basePath, apisDir) +} + +func (g Generator) makeBaseGetter(basePath, apisDir string) getBaseFunc { + gvks := make([]schema.GroupVersionKind, len(g.config.Resources)) + for i, gvk := range g.config.Resources { + gvks[i].Group = fmt.Sprintf("%s.%s", gvk.Group, g.config.Domain) + gvks[i].Version = gvk.Version + gvks[i].Kind = gvk.Kind + } + + return func() (*operatorsv1alpha1.ClusterServiceVersion, error) { + b := bases.ClusterServiceVersion{ + OperatorName: g.OperatorName, + OperatorType: g.OperatorType, + BasePath: basePath, + APIsDir: apisDir, + GVKs: gvks, + } + return b.GetBase() + } +} + +func (g Generator) makeBaseGetterBundleLegacy(inputDir, apisDir string) getBaseFunc { + basePath := filepath.Join(inputDir, bundle.ManifestsDir, getCSVFile(g.OperatorName)) + if genutil.IsNotExist(basePath) { + basePath = "" + } + return g.makeBaseGetterLegacy(basePath, apisDir) +} + +func (g Generator) makeBaseGetterPackageLegacy(inputDir, apisDir string) getBaseFunc { + version := g.FromVersion + if version == "" { + version = g.Version + } + basePath := filepath.Join(inputDir, version, getCSVFileLegacy(g.OperatorName, version)) + if genutil.IsNotExist(basePath) { + basePath = "" + } + return g.makeBaseGetterLegacy(basePath, apisDir) +} + +func (g Generator) makeBaseGetterLegacy(basePath, apisDir string) getBaseFunc { + var gvks []schema.GroupVersionKind + if g.Collector != nil { + for _, crd := range g.Collector.CustomResourceDefinitions { + for _, version := range crd.Spec.Versions { + gvks = append(gvks, schema.GroupVersionKind{ + Group: crd.Spec.Group, + Version: version.Name, + Kind: crd.Spec.Names.Kind, + }) + } + } + } + + return func() (*operatorsv1alpha1.ClusterServiceVersion, error) { + b := bases.ClusterServiceVersion{ + OperatorName: g.OperatorName, + OperatorType: g.OperatorType, + BasePath: basePath, + APIsDir: apisDir, + GVKs: gvks, + } + return b.GetBase() + } +} + +// updateVersions updates csv's version and data involving the version, +// ex. ObjectMeta.Name, and place the old version in the `replaces` object, +// if there is an old version to replace. +func (g Generator) updateVersions(csv *operatorsv1alpha1.ClusterServiceVersion) (err error) { + + oldVer, newVer := csv.Spec.Version.String(), g.Version + newCSVName := genutil.GetCSVName(g.OperatorName, newVer) + oldCSVName := genutil.GetCSVName(g.OperatorName, oldVer) + + // If the new version is empty, either because a CSV is only being updated or + // a base was generated, no update is needed. + if newVer == "0.0.0" || newVer == "" || newVer == oldVer { + return nil + } + + if oldVer != "0.0.0" { + // Replace all references to the old operator name. + oldRe, err := regexp.Compile(fmt.Sprintf("\\b%s\\b", regexp.QuoteMeta(oldCSVName))) + if err != nil { + return fmt.Errorf("error compiling CSV name regexp %s: %v", oldRe, err) + } + b, err := yaml.Marshal(csv) + if err != nil { + return err + } + b = oldRe.ReplaceAll(b, []byte(newCSVName)) + *csv = operatorsv1alpha1.ClusterServiceVersion{} + if err = yaml.Unmarshal(b, csv); err != nil { + return fmt.Errorf("error unmarshalling CSV %s after replacing old CSV name: %v", csv.GetName(), err) + } + csv.Spec.Replaces = oldCSVName + } + + csv.SetName(newCSVName) + csv.Spec.Version.Version, err = semver.Parse(newVer) + return err +} diff --git a/internal/generate/clusterserviceversion/clusterserviceversion_go_test.go b/internal/generate/clusterserviceversion/clusterserviceversion_go_test.go new file mode 100644 index 0000000000..d51b5f5c4b --- /dev/null +++ b/internal/generate/clusterserviceversion/clusterserviceversion_go_test.go @@ -0,0 +1,345 @@ +// 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 clusterserviceversion + +import ( + "bytes" + "io" + "io/ioutil" + "path/filepath" + "testing" + + "github.com/blang/semver" + operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/yaml" + + "github.com/operator-framework/operator-sdk/internal/generate/clusterserviceversion/bases" + "github.com/operator-framework/operator-sdk/internal/generate/collector" + genutil "github.com/operator-framework/operator-sdk/internal/generate/internal" + "github.com/operator-framework/operator-sdk/internal/util/k8sutil" + "github.com/operator-framework/operator-sdk/internal/util/projutil" +) + +const ( + testProjectName = "memcached-operator" + + // Dir names/CSV versions + newVersion = "0.0.3" + fromVersion = "0.0.2" + notExistVersion = "1.0.0" +) + +var ( + testGoDataDir = filepath.Join("..", "testdata", "go") + testNonStandardLayoutDataDir = filepath.Join("..", "testdata", "non-standard-layout") +) + +func makeNoUpdateBaseGetter(opName, apisDir string, gvks []schema.GroupVersionKind) getBaseFunc { + + b := bases.ClusterServiceVersion{ + OperatorName: opName, + OperatorType: projutil.OperatorTypeGo, + APIsDir: apisDir, + GVKs: gvks, + } + return b.GetBase +} + +func readFile(t *testing.T, path string) []byte { + b, err := ioutil.ReadFile(path) + if err != nil { + t.Fatalf("Failed to read testdata file: %v", err) + } + return b +} + +// TODO: Change to table driven subtests to test out different Inputs/Output for the generator +func TestGoCSVNewNewLayout(t *testing.T) { + + manifestRoot := filepath.Join(testNonStandardLayoutDataDir, "config") + crdsDir := filepath.Join(manifestRoot, "crds") + col := &collector.Manifests{} + if err := col.UpdateFromDirs(manifestRoot, crdsDir); err != nil { + t.Fatalf("Error updating collector from manifest root %s: %v", manifestRoot, err) + } + + gvks := []schema.GroupVersionKind{ + {Group: "cache.example.com", Version: "v1alpha1", Kind: "Memcached"}, + } + inputDir := filepath.Join(testNonStandardLayoutDataDir, "expected-catalog", OLMCatalogDir, testProjectName) + apisDir := filepath.Join(testNonStandardLayoutDataDir, "api") + buf := &bytes.Buffer{} + newVersion := "0.0.1" + g := Generator{ + OperatorName: testProjectName, + Version: newVersion, + FromVersion: "", + Collector: col, + getWriter: func() (io.Writer, error) { return buf, nil }, + getBase: makeNoUpdateBaseGetter(testProjectName, apisDir, gvks), + } + err := g.GenerateLegacy() + if err != nil { + t.Fatalf("Failed to execute CSV generator: %v", err) + } + + // Read expected CSV + csvFileName := getCSVFileLegacy(g.OperatorName, g.Version) + csvExp := string(readFile(t, filepath.Join(inputDir, csvFileName))) + + assert.Equal(t, csvExp, buf.String()) +} + +func TestGoCSVUpgradeNewLayout(t *testing.T) { + + manifestRoot := filepath.Join(testNonStandardLayoutDataDir, "config") + crdsDir := filepath.Join(manifestRoot, "crds") + col := &collector.Manifests{} + if err := col.UpdateFromDirs(manifestRoot, crdsDir); err != nil { + t.Fatalf("Error updating collector from manifest root %s: %v", manifestRoot, err) + } + + fromVersion := "0.0.3" + newVersion := "0.0.4" + inputDir := filepath.Join(testNonStandardLayoutDataDir, "expected-catalog", OLMCatalogDir, testProjectName) + apisDir := filepath.Join(testNonStandardLayoutDataDir, "api") + buf := &bytes.Buffer{} + g := Generator{ + OperatorName: testProjectName, + Version: newVersion, + FromVersion: fromVersion, + getWriter: func() (io.Writer, error) { return buf, nil }, + } + err := g.GenerateLegacy(WithPackageBase(inputDir, apisDir)) + if err != nil { + t.Fatalf("Failed to execute CSV generator: %v", err) + } + + // Read expected CSV + csvFileName := getCSVFileLegacy(g.OperatorName, g.Version) + csvExp := string(readFile(t, filepath.Join(inputDir, csvFileName))) + + assert.Equal(t, csvExp, buf.String()) +} + +func TestGoCSVNew(t *testing.T) { + + manifestRoot := filepath.Join(testGoDataDir, "deploy") + crdsDir := filepath.Join(manifestRoot, "crds_v1beta1") + col := &collector.Manifests{} + if err := col.UpdateFromDirs(manifestRoot, crdsDir); err != nil { + t.Fatalf("Error updating collector from manifest root %s: %v", manifestRoot, err) + } + + gvks := []schema.GroupVersionKind{ + {Group: "cache.example.com", Version: "v1alpha1", Kind: "Memcached"}, + } + inputDir := filepath.Join(manifestRoot, OLMCatalogDir, testProjectName) + apisDir := filepath.Join(testGoDataDir, "pkg", "apis") + buf := &bytes.Buffer{} + g := Generator{ + OperatorName: testProjectName, + Version: newVersion, + FromVersion: "", + getWriter: func() (io.Writer, error) { return buf, nil }, + getBase: makeNoUpdateBaseGetter(testProjectName, apisDir, gvks), + } + err := g.GenerateLegacy() + if err != nil { + t.Fatalf("Failed to execute CSV generator: %v", err) + } + + // Read expected CSV + csvFileName := getCSVFileLegacy(g.OperatorName, g.Version) + csvExp := string(readFile(t, filepath.Join(inputDir, csvFileName))) + + assert.Equal(t, csvExp, buf.String()) +} + +func TestGoCSVUpdate(t *testing.T) { + + manifestRoot := filepath.Join(testGoDataDir, "deploy") + crdsDir := filepath.Join(manifestRoot, "crds_v1beta1") + col := &collector.Manifests{} + if err := col.UpdateFromDirs(manifestRoot, crdsDir); err != nil { + t.Fatalf("Error updating collector from manifest root %s: %v", manifestRoot, err) + } + + inputDir := filepath.Join(manifestRoot, OLMCatalogDir, testProjectName) + apisDir := filepath.Join(testGoDataDir, "pkg", "apis") + buf := &bytes.Buffer{} + g := Generator{ + OperatorName: testProjectName, + Version: newVersion, + FromVersion: "", + getWriter: func() (io.Writer, error) { return buf, nil }, + } + err := g.GenerateLegacy(WithPackageBase(inputDir, apisDir)) + if err != nil { + t.Fatalf("Failed to execute CSV generator: %v", err) + } + + // Read expected CSV + csvFileName := getCSVFileLegacy(g.OperatorName, g.Version) + csvExp := string(readFile(t, filepath.Join(inputDir, csvFileName))) + + assert.Equal(t, csvExp, buf.String()) +} + +func TestGoCSVUpgrade(t *testing.T) { + + manifestRoot := filepath.Join(testGoDataDir, "deploy") + crdsDir := filepath.Join(manifestRoot, "crds_v1beta1") + col := &collector.Manifests{} + if err := col.UpdateFromDirs(manifestRoot, crdsDir); err != nil { + t.Fatalf("Error updating collector from manifest root %s: %v", manifestRoot, err) + } + + inputDir := filepath.Join(manifestRoot, OLMCatalogDir, testProjectName) + apisDir := filepath.Join(testGoDataDir, "pkg", "apis") + buf := &bytes.Buffer{} + g := Generator{ + OperatorName: testProjectName, + Version: newVersion, + FromVersion: fromVersion, + getWriter: func() (io.Writer, error) { return buf, nil }, + } + err := g.GenerateLegacy(WithPackageBase(inputDir, apisDir)) + if err != nil { + t.Fatalf("Failed to execute CSV generator: %v", err) + } + + // Read expected CSV + csvFileName := getCSVFileLegacy(g.OperatorName, g.Version) + csvExp := string(readFile(t, filepath.Join(inputDir, csvFileName))) + + assert.Equal(t, csvExp, buf.String()) +} + +func TestGoCSVNewWithInvalidDeployDir(t *testing.T) { + + manifestRoot := filepath.Join(testGoDataDir, "notexist") + inputDir := filepath.Join(manifestRoot, OLMCatalogDir, testProjectName) + apisDir := filepath.Join(testGoDataDir, "pkg", "apis") + buf := &bytes.Buffer{} + g := Generator{ + OperatorName: testProjectName, + Version: newVersion, + FromVersion: "", + getWriter: func() (io.Writer, error) { return buf, nil }, + } + err := g.GenerateLegacy(WithPackageBase(inputDir, apisDir)) + if err == nil { + t.Fatalf("Failed to get error for running CSV generatoron non-existent manifests directory: %s", + manifestRoot) + } +} + +func TestGoCSVNewWithEmptyDeployDir(t *testing.T) { + + manifestRoot := filepath.Join(testGoDataDir, "emptydir") + crdsDir := filepath.Join(manifestRoot, "crds_v1beta1") + col := &collector.Manifests{} + if err := col.UpdateFromDirs(manifestRoot, crdsDir); err != nil { + t.Fatalf("Error updating collector from manifest root %s: %v", manifestRoot, err) + } + + inputDir := filepath.Join(manifestRoot, OLMCatalogDir, testProjectName) + apisDir := filepath.Join(testGoDataDir, "pkg", "apis") + buf := &bytes.Buffer{} + g := Generator{ + OperatorName: testProjectName, + Version: notExistVersion, + FromVersion: "", + getWriter: func() (io.Writer, error) { return buf, nil }, + } + err := g.GenerateLegacy(WithPackageBase(inputDir, apisDir)) + if err != nil { + t.Fatalf("Failed to execute CSV generator: %v", err) + } + + // Create a new CSV base. + b := bases.ClusterServiceVersion{ + OperatorName: g.OperatorName, + } + csv, err := b.GetBase() + if err != nil { + t.Fatal(err) + } + csv.SetName(genutil.GetCSVName(g.OperatorName, notExistVersion)) + csvExpBytes, err := k8sutil.GetObjectBytes(csv, yaml.Marshal) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, string(csvExpBytes), buf.String()) +} + +func TestUpdateCSVVersion(t *testing.T) { + + b := bases.ClusterServiceVersion{ + OperatorName: testProjectName, + } + csv, err := b.GetBase() + if err != nil { + t.Fatal(err) + } + csv.SetName(genutil.GetCSVName(testProjectName, notExistVersion)) + depSpecs := make([]operatorsv1alpha1.StrategyDeploymentSpec, 1) + csv.Spec.InstallStrategy = operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: operatorsv1alpha1.StrategyDetailsDeployment{ + DeploymentSpecs: depSpecs, + }, + } + depSpecs[0].Spec.Template.Spec.Containers = make([]corev1.Container, 1) + depSpecs[0].Spec.Template.Spec.Containers[0].Image = "quay.io/example/memcached-operator:v" + fromVersion + + g := Generator{ + OperatorName: testProjectName, + Version: newVersion, + FromVersion: fromVersion, + } + if err := g.updateVersions(csv); err != nil { + t.Fatalf("Failed to update csv with version %s: (%v)", newVersion, err) + } + + wantedSemver, err := semver.Parse(newVersion) + if err != nil { + t.Errorf("Failed to parse %s: %v", newVersion, err) + } + if !csv.Spec.Version.Equals(wantedSemver) { + t.Errorf("Wanted csv version %v, got %v", wantedSemver, csv.Spec.Version) + } + wantedName := genutil.GetCSVName(testProjectName, newVersion) + if csv.GetName() != wantedName { + t.Errorf("Wanted csv name %s, got %s", wantedName, csv.ObjectMeta.Name) + } + + csvPodImage := depSpecs[0].Spec.Template.Spec.Containers[0].Image + // updateCSVVersions should not update podspec image. + wantedImage := "quay.io/example/memcached-operator:v" + fromVersion + if csvPodImage != wantedImage { + t.Errorf("Podspec image changed from %s to %s", wantedImage, csvPodImage) + } + + wantedReplaces := genutil.GetCSVName(testProjectName, fromVersion) + if csv.Spec.Replaces != wantedReplaces { + t.Errorf("Wanted csv replaces %s, got %s", wantedReplaces, csv.Spec.Replaces) + } +} diff --git a/internal/generate/clusterserviceversion/clusterserviceversion_updaters.go b/internal/generate/clusterserviceversion/clusterserviceversion_updaters.go new file mode 100644 index 0000000000..27524442e1 --- /dev/null +++ b/internal/generate/clusterserviceversion/clusterserviceversion_updaters.go @@ -0,0 +1,222 @@ +// 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 clusterserviceversion + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "sort" + + operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/operator-framework/api/pkg/validation" + log "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/version" + + "github.com/operator-framework/operator-registry/pkg/registry" + "github.com/operator-framework/operator-sdk/internal/generate/collector" +) + +func applyTo(c *collector.Manifests, csv *operatorsv1alpha1.ClusterServiceVersion) error { + // Apply manifests to the CSV object. + if err := apply(c, csv); err != nil { + return fmt.Errorf("error updating ClusterServiceVersion: %v", err) + } + + // Finally sort all updated fields. + sortUpdates(csv) + + return validateClusterServiceVersion(csv) +} + +// apply applies the manifests in the collection to csv. +func apply(c *collector.Manifests, csv *operatorsv1alpha1.ClusterServiceVersion) error { + strategy := getCSVInstallStrategy(csv) + switch strategy.StrategyName { + case operatorsv1alpha1.InstallStrategyNameDeployment: + applyRoles(c, &strategy.StrategySpec) + applyClusterRoles(c, &strategy.StrategySpec) + applyDeployments(c, &strategy.StrategySpec) + } + csv.Spec.InstallStrategy = strategy + + applyCustomResourceDefinitions(c, csv) + if err := applyCustomResources(c, csv); err != nil { + return fmt.Errorf("error applying Custom Resource: %v", err) + } + return nil +} + +// Get install strategy from csv. +func getCSVInstallStrategy(csv *operatorsv1alpha1.ClusterServiceVersion) operatorsv1alpha1.NamedInstallStrategy { + // Default to a deployment strategy if none found. + if csv.Spec.InstallStrategy.StrategyName == "" { + csv.Spec.InstallStrategy.StrategyName = operatorsv1alpha1.InstallStrategyNameDeployment + } + return csv.Spec.InstallStrategy +} + +// applyRoles updates strategy's permissions with the Roles in the collection. +func applyRoles(c *collector.Manifests, strategy *operatorsv1alpha1.StrategyDetailsDeployment) { + perms := []operatorsv1alpha1.StrategyDeploymentPermissions{} + for _, role := range c.Roles { + perms = append(perms, operatorsv1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: role.GetName(), + Rules: role.Rules, + }) + } + strategy.Permissions = perms +} + +// applyClusterRoles updates strategy's cluserPermissions with the ClusterRoles +// in the collection. +func applyClusterRoles(c *collector.Manifests, strategy *operatorsv1alpha1.StrategyDetailsDeployment) { + perms := []operatorsv1alpha1.StrategyDeploymentPermissions{} + for _, role := range c.ClusterRoles { + perms = append(perms, operatorsv1alpha1.StrategyDeploymentPermissions{ + ServiceAccountName: role.GetName(), + Rules: role.Rules, + }) + } + strategy.ClusterPermissions = perms +} + +// applyDeployments updates strategy's deployments with the Deployments +// in the collection. +func applyDeployments(c *collector.Manifests, strategy *operatorsv1alpha1.StrategyDetailsDeployment) { + depSpecs := []operatorsv1alpha1.StrategyDeploymentSpec{} + for _, dep := range c.Deployments { + depSpecs = append(depSpecs, operatorsv1alpha1.StrategyDeploymentSpec{ + Name: dep.GetName(), + Spec: dep.Spec, + }) + } + strategy.DeploymentSpecs = depSpecs +} + +// applyCustomResourceDefinitions updates csv's customresourcedefinitions.owned +// with CustomResourceDefinitions in the collection. +// customresourcedefinitions.required are left as-is, since they are +// manually-defined values. +func applyCustomResourceDefinitions(c *collector.Manifests, csv *operatorsv1alpha1.ClusterServiceVersion) { + ownedDescs := []operatorsv1alpha1.CRDDescription{} + descMap := map[registry.DefinitionKey]operatorsv1alpha1.CRDDescription{} + for _, owned := range csv.Spec.CustomResourceDefinitions.Owned { + defKey := registry.DefinitionKey{ + Name: owned.Name, + Version: owned.Version, + Kind: owned.Kind, + } + descMap[defKey] = owned + } + for _, crd := range c.CustomResourceDefinitions { + for _, ver := range crd.Spec.Versions { + defKey := registry.DefinitionKey{ + Name: crd.GetName(), + Version: ver.Name, + Kind: crd.Spec.Names.Kind, + } + if owned, ownedExists := descMap[defKey]; ownedExists { + ownedDescs = append(ownedDescs, owned) + } else { + ownedDescs = append(ownedDescs, operatorsv1alpha1.CRDDescription{ + Name: defKey.Name, + Version: defKey.Version, + Kind: defKey.Kind, + }) + } + } + } + csv.Spec.CustomResourceDefinitions.Owned = ownedDescs +} + +// applyCustomResources updates csv's "alm-examples" annotation with the +// Custom Resources in the collection. +func applyCustomResources(c *collector.Manifests, csv *operatorsv1alpha1.ClusterServiceVersion) error { + examples := []json.RawMessage{} + for _, cr := range c.CustomResources { + crBytes, err := cr.MarshalJSON() + if err != nil { + return err + } + examples = append(examples, json.RawMessage(crBytes)) + } + examplesJSON, err := json.Marshal(examples) + if err != nil { + return err + } + examplesJSON, err = prettifyJSON(examplesJSON) + if err != nil { + return err + } + if csv.GetAnnotations() == nil { + csv.SetAnnotations(make(map[string]string)) + } + csv.GetAnnotations()["alm-examples"] = string(examplesJSON) + return nil +} + +// prettifyJSON returns a JSON in a pretty format +func prettifyJSON(b []byte) ([]byte, error) { + var out bytes.Buffer + err := json.Indent(&out, b, "", " ") + return out.Bytes(), err +} + +// sortUpdates sorts all fields updated in csv. +// TODO(estroz): sort other modified fields. +func sortUpdates(csv *operatorsv1alpha1.ClusterServiceVersion) { + sort.Sort(descSorter(csv.Spec.CustomResourceDefinitions.Owned)) + sort.Sort(descSorter(csv.Spec.CustomResourceDefinitions.Required)) +} + +// descSorter sorts a set of crdDescriptions. +type descSorter []operatorsv1alpha1.CRDDescription + +var _ sort.Interface = descSorter{} + +func (descs descSorter) Len() int { return len(descs) } +func (descs descSorter) Less(i, j int) bool { + if descs[i].Name == descs[j].Name { + if descs[i].Kind == descs[j].Kind { + return version.CompareKubeAwareVersionStrings(descs[i].Version, descs[j].Version) > 0 + } + return descs[i].Kind < descs[j].Kind + } + return descs[i].Name < descs[j].Name +} +func (descs descSorter) Swap(i, j int) { descs[i], descs[j] = descs[j], descs[i] } + +// validateClusterServiceVersion will validate csv using the api validation library. +// More info: https://github.com/operator-framework/api +func validateClusterServiceVersion(csv *operatorsv1alpha1.ClusterServiceVersion) error { + if csv == nil { + return errors.New("empty ClusterServiceVersion") + } + results := validation.ClusterServiceVersionValidator.Validate(csv) + for _, r := range results { + if r.HasError() { + for _, e := range r.Errors { + log.Errorf("ClusterServiceVersion validation: [%s] %s", e.Type, e.Detail) + } + return errors.New("got ClusterServiceVersion validation errors") + } + for _, w := range r.Warnings { + log.Warnf("ClusterServiceVersion validation: [%s] %s", w.Type, w.Detail) + } + } + return nil +} diff --git a/internal/generate/collector/collect.go b/internal/generate/collector/collect.go new file mode 100644 index 0000000000..9818ecd5d1 --- /dev/null +++ b/internal/generate/collector/collect.go @@ -0,0 +1,210 @@ +// 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 collector + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + + log "github.com/sirupsen/logrus" + appsv1 "k8s.io/api/apps/v1" + rbacv1 "k8s.io/api/rbac/v1" + apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/yaml" + + "github.com/operator-framework/operator-sdk/internal/util/k8sutil" +) + +// Manifests holds a collection of all manifests relevant to CSV updates. +type Manifests struct { + Roles []rbacv1.Role + ClusterRoles []rbacv1.ClusterRole + Deployments []appsv1.Deployment + CustomResourceDefinitions []apiextv1beta1.CustomResourceDefinition + CustomResources []unstructured.Unstructured + Others []unstructured.Unstructured +} + +func (c *Manifests) UpdateFromDirs(manifestRoot, crdsDir string) error { + // Collect all manifests in paths. + err := filepath.Walk(manifestRoot, func(path string, info os.FileInfo, err error) error { + if err != nil || info.IsDir() { + return err + } + + b, err := ioutil.ReadFile(path) + if err != nil { + return err + } + scanner := k8sutil.NewYAMLScanner(bytes.NewBuffer(b)) + for scanner.Scan() { + manifest := scanner.Bytes() + typeMeta, err := k8sutil.GetTypeMetaFromBytes(manifest) + if err != nil { + log.Debugf("No TypeMeta in %s, skipping file", path) + continue + } + switch typeMeta.GroupVersionKind().Kind { + case "Role": + err = c.addRoles(manifest) + case "ClusterRole": + err = c.addClusterRoles(manifest) + case "Deployment": + err = c.addDeployments(manifest) + case "CustomResourceDefinition": + // Skip for now and add explicitly from CRDsDir input. + default: + err = c.addOthers(manifest) + } + if err != nil { + return err + } + } + return scanner.Err() + }) + if err != nil { + return fmt.Errorf("error collecting manifests from directory %s: %v", manifestRoot, err) + } + + // Add CRDs from input. + if isDirExist(crdsDir) { + c.CustomResourceDefinitions, err = k8sutil.GetCustomResourceDefinitions(crdsDir) + if err != nil { + return err + } + } + + // Remove duplicate manifests. + if err := c.deduplicate(); err != nil { + return fmt.Errorf("error removing duplicate manifests: %v", err) + } + + // Filter the collection based on data collected. + c.filter() + + return nil +} + +// collectFromReader collects manifests relevant to a bundle from r. +func (c *Manifests) UpdateFromReader(r io.Reader) error { + // Bundle contents. + scanner := k8sutil.NewYAMLScanner(r) + for scanner.Scan() { + manifest := scanner.Bytes() + typeMeta, err := k8sutil.GetTypeMetaFromBytes(manifest) + if err != nil { + log.Debug("No TypeMeta found, skipping manifest") + continue + } + switch typeMeta.GroupVersionKind().Kind { + case "Role": + err = c.addRoles(manifest) + case "ClusterRole": + err = c.addClusterRoles(manifest) + case "Deployment": + err = c.addDeployments(manifest) + case "CustomResourceDefinition": + err = c.addCustomResourceDefinitions(manifest) + default: + err = c.addOthers(manifest) + } + if err != nil { + return err + } + } + if err := scanner.Err(); err != nil { + return fmt.Errorf("error collecting manifests from reader: %v", err) + } + + // Remove duplicate manifests. + if err := c.deduplicate(); err != nil { + return fmt.Errorf("error removing duplicate manifests: %v", err) + } + + // Filter the collection based on data collected. + c.filter() + + return nil +} + +// addRoles assumes add manifest data in rawManifests are Roles and adds them +// to the collection. +func (c *Manifests) addRoles(rawManifests ...[]byte) error { + for _, rawManifest := range rawManifests { + role := rbacv1.Role{} + if err := yaml.Unmarshal(rawManifest, &role); err != nil { + return fmt.Errorf("error adding Role to manifest collection: %v", err) + } + c.Roles = append(c.Roles, role) + } + return nil +} + +// addClusterRoles assumes add manifest data in rawManifests are ClusterRoles +// and adds them to the collection. +func (c *Manifests) addClusterRoles(rawManifests ...[]byte) error { + for _, rawManifest := range rawManifests { + role := rbacv1.ClusterRole{} + if err := yaml.Unmarshal(rawManifest, &role); err != nil { + return fmt.Errorf("error adding ClusterRole to manifest collection: %v", err) + } + c.ClusterRoles = append(c.ClusterRoles, role) + } + return nil +} + +// addDeployments assumes add manifest data in rawManifests are Deployments +// and adds them to the collection. +func (c *Manifests) addDeployments(rawManifests ...[]byte) error { + for _, rawManifest := range rawManifests { + dep := appsv1.Deployment{} + if err := yaml.Unmarshal(rawManifest, &dep); err != nil { + return fmt.Errorf("error adding Deployment to manifest collection: %v", err) + } + c.Deployments = append(c.Deployments, dep) + } + return nil +} + +// addCustomResourceDefinitions assumes add manifest data in rawManifests are +// CustomResourceDefinitions and adds them to the collection. +func (c *Manifests) addCustomResourceDefinitions(rawManifests ...[]byte) error { + for _, rawManifest := range rawManifests { + crd := apiextv1beta1.CustomResourceDefinition{} + if err := yaml.Unmarshal(rawManifest, &crd); err != nil { + return fmt.Errorf("error adding Deployment to manifest collection: %v", err) + } + c.CustomResourceDefinitions = append(c.CustomResourceDefinitions, crd) + } + return nil +} + +// addOthers assumes add manifest data in rawManifests are able to be +// unmarshalled into an Unstructured object and adds them to the collection. +func (c *Manifests) addOthers(rawManifests ...[]byte) error { + for _, rawManifest := range rawManifests { + u := unstructured.Unstructured{} + if err := yaml.Unmarshal(rawManifest, &u); err != nil { + return fmt.Errorf("error adding manifest collection: %v", err) + } + c.Others = append(c.Others, u) + } + return nil +} diff --git a/internal/generate/collector/filter.go b/internal/generate/collector/filter.go new file mode 100644 index 0000000000..91bd7970c9 --- /dev/null +++ b/internal/generate/collector/filter.go @@ -0,0 +1,152 @@ +// 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 collector + +import ( + "crypto/sha256" + + appsv1 "k8s.io/api/apps/v1" + rbacv1 "k8s.io/api/rbac/v1" + apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// filter applies filtering rules to certain manifest types in a collection. +func (c *Manifests) filter() { + c.filterCustomResources() +} + +// filterCustomResources filters "other" objects, which contain likely +// Custom Resources corresponding to a CustomResourceDefinition, by GVK. +func (c *Manifests) filterCustomResources() { + crdGVKSet := make(map[schema.GroupVersionKind]struct{}) + for _, crd := range c.CustomResourceDefinitions { + for _, version := range crd.Spec.Versions { + gvk := schema.GroupVersionKind{ + Group: crd.Spec.Group, + Version: version.Name, + Kind: crd.Spec.Names.Kind, + } + crdGVKSet[gvk] = struct{}{} + } + } + + customResources := []unstructured.Unstructured{} + for _, other := range c.Others { + if _, gvkMatches := crdGVKSet[other.GroupVersionKind()]; gvkMatches { + customResources = append(customResources, other) + } + } + c.CustomResources = customResources +} + +// deduplicate removes duplicate objects from the collection, since we are +// collecting an arbitrary list of manifests. +func (c *Manifests) deduplicate() error { + hashes := make(map[string]struct{}) + + roles := []rbacv1.Role{} + for _, role := range c.Roles { + hasHash, err := addToHashes(&role, hashes) + if err != nil { + return err + } + if !hasHash { + roles = append(roles, role) + } + } + c.Roles = roles + + clusterRoles := []rbacv1.ClusterRole{} + for _, clusterRole := range c.ClusterRoles { + hasHash, err := addToHashes(&clusterRole, hashes) + if err != nil { + return err + } + if !hasHash { + clusterRoles = append(clusterRoles, clusterRole) + } + } + c.ClusterRoles = clusterRoles + + deps := []appsv1.Deployment{} + for _, dep := range c.Deployments { + hasHash, err := addToHashes(&dep, hashes) + if err != nil { + return err + } + if !hasHash { + deps = append(deps, dep) + } + } + c.Deployments = deps + + crds := []apiextv1beta1.CustomResourceDefinition{} + for _, crd := range c.CustomResourceDefinitions { + hasHash, err := addToHashes(&crd, hashes) + if err != nil { + return err + } + if !hasHash { + crds = append(crds, crd) + } + } + c.CustomResourceDefinitions = crds + + crs := []unstructured.Unstructured{} + for _, cr := range c.CustomResources { + b, err := cr.MarshalJSON() + if err != nil { + return err + } + hash := hashContents(b) + if _, hasHash := hashes[hash]; !hasHash { + crs = append(crs, cr) + hashes[hash] = struct{}{} + } + } + c.CustomResources = crs + + return nil +} + +// marshaller is an interface used to generalize hashing for deduplication. +type marshaller interface { + Marshal() ([]byte, error) +} + +// addToHashes calls m.Marshal(), hashes the returned bytes, and adds the +// hash to hashes if it does not exist. addToHashes returns true if m's hash +// was not in hashes. +func addToHashes(m marshaller, hashes map[string]struct{}) (bool, error) { + b, err := m.Marshal() + if err != nil { + return false, err + } + hash := hashContents(b) + _, hasHash := hashes[hash] + if !hasHash { + hashes[hash] = struct{}{} + } + return hasHash, nil +} + +// hashContents creates a sha256 md5 digest of b's bytes. +func hashContents(b []byte) string { + h := sha256.New() + _, _ = h.Write(b) + return string(h.Sum(nil)) +} diff --git a/internal/generate/collector/util.go b/internal/generate/collector/util.go new file mode 100644 index 0000000000..e29a1a4f8d --- /dev/null +++ b/internal/generate/collector/util.go @@ -0,0 +1,28 @@ +// 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 collector + +import ( + "os" +) + +// isDirExist returns true if dir exists on disk. +func isDirExist(dir string) bool { + if dir == "" { + return false + } + info, err := os.Stat(dir) + return (err == nil && info.IsDir()) || os.IsExist(err) +} diff --git a/internal/generate/crd/crd.go b/internal/generate/crd/crd.go index 5629b0d144..5729792eed 100644 --- a/internal/generate/crd/crd.go +++ b/internal/generate/crd/crd.go @@ -15,6 +15,7 @@ package crd import ( + "bytes" "errors" "fmt" "io/ioutil" @@ -159,7 +160,7 @@ func (g Generator) generateGo() (map[string][]byte, error) { if err != nil { return nil, fmt.Errorf("error reading cached CRD file %s: %w", path, err) } - scanner := k8sutil.NewYAMLScanner(b) + scanner := k8sutil.NewYAMLScanner(bytes.NewBuffer(b)) modifiedCRD := []byte{} for scanner.Scan() { crd := unstructured.Unstructured{} diff --git a/internal/generate/internal/genutil.go b/internal/generate/internal/genutil.go new file mode 100644 index 0000000000..f17f19e647 --- /dev/null +++ b/internal/generate/internal/genutil.go @@ -0,0 +1,95 @@ +// 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 genutil + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + + "sigs.k8s.io/yaml" + + "github.com/operator-framework/operator-sdk/internal/util/k8sutil" +) + +type InternalError string + +func (e InternalError) Error() string { + return fmt.Sprintf("internal error: %s", string(e)) +} + +func GetCSVName(name, version string) string { + return fmt.Sprintf("%s.v%s", name, version) +} + +type File struct { + *os.File +} + +func Open(dir, fileName string) (*File, error) { + if err := os.MkdirAll(dir, 0700); err != nil { + return nil, err + } + f, err := os.OpenFile(filepath.Join(dir, fileName), os.O_RDWR|os.O_CREATE, 0666) + return &File{f}, err +} + +func WriteObject(w io.Writer, obj interface{}) error { + b, err := k8sutil.GetObjectBytes(obj, yaml.Marshal) + if err != nil { + return err + } + return write(w, b) +} + +func WriteYAML(w io.Writer, obj interface{}) error { + b, err := yaml.Marshal(obj) + if err != nil { + return err + } + return write(w, b) +} + +func write(w io.Writer, b []byte) error { + if f, isFile := w.(*File); isFile { + if err := f.Truncate(0); err != nil { + return err + } + defer func() { + _ = f.Close() + }() + } + _, err := w.Write(b) + return err +} + +// IsExist returns true if path exists on disk. +func IsExist(path string) bool { + if path == "" { + return false + } + _, err := os.Stat(path) + return err == nil || errors.Is(err, os.ErrExist) +} + +func IsNotExist(path string) bool { + if path == "" { + return true + } + _, err := os.Stat(path) + return err != nil && errors.Is(err, os.ErrNotExist) +} diff --git a/internal/generate/olm-catalog/csv.go b/internal/generate/olm-catalog/csv.go index 541e335ac6..11b07545cb 100644 --- a/internal/generate/olm-catalog/csv.go +++ b/internal/generate/olm-catalog/csv.go @@ -15,6 +15,7 @@ package olmcatalog import ( + "bytes" "errors" "fmt" "io/ioutil" @@ -37,9 +38,6 @@ import ( "sigs.k8s.io/yaml" ) -// KB_INTEGRATION_TODO(estroz): generate these using kustomize and pass -// from stdin, like 'make deploy'. - const ( OLMCatalogChildDir = "olm-catalog" // OLMCatalogDir is the default location for OLM catalog directory. @@ -418,7 +416,7 @@ func (g BundleGenerator) updateCSVFromManifests(csv *olmapiv1alpha1.ClusterServi if err != nil { return err } - scanner := k8sutil.NewYAMLScanner(b) + scanner := k8sutil.NewYAMLScanner(bytes.NewBuffer(b)) for scanner.Scan() { manifest := scanner.Bytes() typeMeta, err := k8sutil.GetTypeMetaFromBytes(manifest) diff --git a/internal/generate/olm-catalog/csv_util.go b/internal/generate/olm-catalog/csv_util.go index 0d96a0eeec..01de7e2fed 100644 --- a/internal/generate/olm-catalog/csv_util.go +++ b/internal/generate/olm-catalog/csv_util.go @@ -15,6 +15,7 @@ package olmcatalog import ( + "bytes" "fmt" "io/ioutil" "os" @@ -59,7 +60,7 @@ func addCustomResourceDefinitionsToFileSet(dir string, fileMap map[string][]byte return fmt.Errorf("error reading manifest %s: %v", fromPath, err) } - scanner := k8sutil.NewYAMLScanner(b) + scanner := k8sutil.NewYAMLScanner(bytes.NewBuffer(b)) manifests := []byte{} for scanner.Scan() { manifest := scanner.Bytes() @@ -104,7 +105,7 @@ func getCSVFromDir(dir string) (*olmapiv1alpha1.ClusterServiceVersion, error) { return nil, fmt.Errorf("error reading manifest %s: %v", path, err) } - scanner := k8sutil.NewYAMLScanner(b) + scanner := k8sutil.NewYAMLScanner(bytes.NewBuffer(b)) for scanner.Scan() { manifest := scanner.Bytes() typeMeta, err := k8sutil.GetTypeMetaFromBytes(manifest) diff --git a/internal/generate/packagemanifest/bases/packagemanifest.go b/internal/generate/packagemanifest/bases/packagemanifest.go new file mode 100644 index 0000000000..de73c0254b --- /dev/null +++ b/internal/generate/packagemanifest/bases/packagemanifest.go @@ -0,0 +1,63 @@ +// 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 bases + +import ( + "fmt" + "io/ioutil" + + "github.com/operator-framework/operator-registry/pkg/registry" + "sigs.k8s.io/yaml" +) + +type PackageManifest struct { + PackageName string + BasePath string +} + +func (b PackageManifest) GetBase() (base *registry.PackageManifest, err error) { + if b.BasePath != "" { + if base, err = readPackageManifestBase(b.BasePath); err != nil { + return nil, fmt.Errorf("error reading existing PackageManifest base %s: %v", b.BasePath, err) + } + } else { + base = b.makeNewBase() + } + + return base, nil +} + +func (b PackageManifest) makeNewBase() *registry.PackageManifest { + return ®istry.PackageManifest{ + PackageName: b.PackageName, + } +} + +// readPackageManifestBase returns the PackageManifest base at path. +// If no base is found, readPackageManifestBase returns an error. +func readPackageManifestBase(path string) (*registry.PackageManifest, error) { + b, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + pkg := ®istry.PackageManifest{} + if err := yaml.Unmarshal(b, pkg); err != nil { + return nil, fmt.Errorf("error unmarshalling PackageManifest from %s: %w", path, err) + } + if pkg.PackageName == "" { + return nil, fmt.Errorf("no PackageManifest in %s", path) + } + return pkg, nil +} diff --git a/internal/generate/packagemanifest/packagemanifest.go b/internal/generate/packagemanifest/packagemanifest.go new file mode 100644 index 0000000000..8e50444c50 --- /dev/null +++ b/internal/generate/packagemanifest/packagemanifest.go @@ -0,0 +1,197 @@ +// 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 packagemanifest + +import ( + "errors" + "fmt" + "io" + "path/filepath" + "sort" + "strings" + + "github.com/operator-framework/api/pkg/validation" + "github.com/operator-framework/operator-registry/pkg/registry" + log "github.com/sirupsen/logrus" + + genutil "github.com/operator-framework/operator-sdk/internal/generate/internal" + "github.com/operator-framework/operator-sdk/internal/generate/packagemanifest/bases" +) + +// Deprecated: The package manifest generator will no longer create new package +// manifests, only update existing ones. This generator will be removed in v0.19.0. + +const ( + packageManifestFileExt = ".package.yaml" +) + +type Generator struct { + // + OperatorName string + // version is the version of the CSV being updated. + Version string + // channel is operator's package manifest channel. If a new package + // manifest is generated, this channel will be the manifest default. + ChannelName string + // If channelIsDefault is true, channel will be the package manifests' + // default channel. + IsDefaultChannel bool + + // + getBase func() (*registry.PackageManifest, error) + // + getWriter func() (io.Writer, error) +} + +type Option func(*Generator) error + +func WithGetBase(inputDir string) Option { + return func(g *Generator) error { + g.getBase = g.makeBaseGetter(inputDir) + return nil + } +} + +func WithFileWriter(dir string) Option { + return func(g *Generator) (err error) { + g.getWriter = func() (io.Writer, error) { + return genutil.Open(dir, getPackageManifestFile(g.OperatorName)) + } + return nil + } +} + +func WithWriter(w io.Writer) Option { + return func(g *Generator) error { + g.getWriter = func() (io.Writer, error) { + return w, nil + } + return nil + } +} + +func (g *Generator) Generate(opts ...Option) error { + for _, opt := range opts { + if err := opt(g); err != nil { + return err + } + } + + return g.generate() +} + +func (g *Generator) generate() error { + if g.getWriter == nil { + return genutil.InternalError("getWriter must be set") + } + if g.getBase == nil { + return genutil.InternalError("getBase must be set") + } + + base, err := g.getBase() + if err != nil { + return fmt.Errorf("error getting PackageManifest base: %v", err) + } + + csvName := genutil.GetCSVName(g.OperatorName, g.Version) + if g.ChannelName != "" { + setChannels(base, g.ChannelName, csvName) + sortChannelsByName(base) + if g.IsDefaultChannel || len(base.Channels) == 0 { + base.DefaultChannelName = g.ChannelName + } + } else if len(base.Channels) == 0 { + setChannels(base, "alpha", csvName) + base.DefaultChannelName = "alpha" + } + + if err := validatePackageManifest(base); err != nil { + return err + } + + w, err := g.getWriter() + if err != nil { + return err + } + return genutil.WriteYAML(w, base) +} + +func (g Generator) makeBaseGetter(inputDir string) func() (*registry.PackageManifest, error) { + basePath := filepath.Join(inputDir, getPackageManifestFile(g.OperatorName)) + if genutil.IsNotExist(basePath) { + basePath = "" + } + + return func() (*registry.PackageManifest, error) { + b := bases.PackageManifest{ + PackageName: g.OperatorName, + BasePath: basePath, + } + return b.GetBase() + } +} + +// getPackageManifestFile will return the file name of a PackageManifest. +func getPackageManifestFile(operatorName string) string { + return strings.ToLower(operatorName) + packageManifestFileExt +} + +// sortChannelsByName sorts pkg.Channels by each element's name. +// NOTE: sorting makes the channel order always consistent when appending new channels +func sortChannelsByName(pkg *registry.PackageManifest) { + sort.Slice(pkg.Channels, func(i int, j int) bool { + return pkg.Channels[i].Name < pkg.Channels[j].Name + }) +} + +// validatePackageManifest will validate pkg using the api validation library. +// More info: https://github.com/operator-framework/api +func validatePackageManifest(pkg *registry.PackageManifest) error { + if pkg == nil { + return errors.New("empty PackageManifest") + } + results := validation.PackageManifestValidator.Validate(pkg) + for _, r := range results { + if r.HasError() { + for _, e := range r.Errors { + log.Errorf("PackageManifest validation: [%s] %s", e.Type, e.Detail) + } + return errors.New("got PackageManifest validation errors") + } + for _, w := range r.Warnings { + log.Warnf("PackageManifest validation: [%s] %s", w.Type, w.Detail) + } + } + return nil +} + +// setChannels checks for duplicate channels in pkg and sets the default +// channel if possible. +func setChannels(pkg *registry.PackageManifest, channelName, csvName string) { + channelIdx := -1 + for i, channel := range pkg.Channels { + if channel.Name == channelName { + pkg.Channels[i].CurrentCSVName = csvName + channelIdx = i + break + } + } + if channelIdx == -1 { + pkg.Channels = append(pkg.Channels, registry.PackageChannel{ + Name: channelName, + CurrentCSVName: csvName, + }) + } +} diff --git a/internal/generate/packagemanifest/packagemanifest_test.go b/internal/generate/packagemanifest/packagemanifest_test.go new file mode 100644 index 0000000000..eed4703192 --- /dev/null +++ b/internal/generate/packagemanifest/packagemanifest_test.go @@ -0,0 +1,151 @@ +// 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 packagemanifest + +import ( + "bytes" + "io" + "path/filepath" + "testing" + + "github.com/operator-framework/operator-registry/pkg/registry" + "github.com/stretchr/testify/assert" + + genutil "github.com/operator-framework/operator-sdk/internal/generate/internal" + "github.com/operator-framework/operator-sdk/internal/generate/packagemanifest/bases" +) + +const ( + testProjectName = "memcached-operator" + + // Dir names/CSV versions + version = "0.0.3" +) + +var ( + testGoDataDir = filepath.Join("..", "testdata", "go") + testNonStandardLayoutDataDir = filepath.Join("..", "testdata", "non-standard-layout") +) + +func TestGeneratePkgManifestToOutput(t *testing.T) { + + inputDir := filepath.Join(testNonStandardLayoutDataDir, "expected-catalog", "olm-catalog", testProjectName) + buf := &bytes.Buffer{} + g := Generator{ + OperatorName: testProjectName, + Version: version, + ChannelName: "beta", + IsDefaultChannel: false, + getWriter: func() (io.Writer, error) { return buf, nil }, + } + err := g.Generate(WithGetBase(inputDir)) + if err != nil { + t.Fatalf("Failed to execute package manifest generator: %v", err) + } + + assert.Equal(t, packageManifestNonStandardExp, buf.String()) +} + +const packageManifestNonStandardExp = `channels: +- currentCSV: memcached-operator.v0.0.1 + name: alpha +- currentCSV: memcached-operator.v0.0.3 + name: beta +- currentCSV: memcached-operator.v0.0.4 + name: stable +defaultChannel: stable +packageName: memcached-operator +` + +func TestGeneratePackageManifest(t *testing.T) { + + inputDir := filepath.Join(testGoDataDir, "deploy", "olm-catalog", testProjectName) + buf := &bytes.Buffer{} + g := Generator{ + OperatorName: testProjectName, + Version: version, + ChannelName: "stable", + IsDefaultChannel: true, + getWriter: func() (io.Writer, error) { return buf, nil }, + } + err := g.Generate(WithGetBase(inputDir)) + if err != nil { + t.Fatalf("Failed to execute package manifest generator: %v", err) + } + + assert.Equal(t, packageManifestExp, buf.String()) +} + +const packageManifestExp = `channels: +- currentCSV: memcached-operator.v0.0.2 + name: alpha +- currentCSV: memcached-operator.v0.0.3 + name: stable +defaultChannel: stable +packageName: memcached-operator +` + +func TestValidatePackageManifest(t *testing.T) { + + b := bases.PackageManifest{ + PackageName: testProjectName, + } + pkg, err := b.GetBase() + if err != nil { + t.Fatal(err) + } + + setChannels(pkg, "stable", genutil.GetCSVName(testProjectName, version)) + sortChannelsByName(pkg) + + // invalid mock data, pkg with empty channel + invalidPkgWithEmptyChannels := *pkg + invalidPkgWithEmptyChannels.Channels = []registry.PackageChannel{} + + type args struct { + pkg *registry.PackageManifest + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "Should work successfully with a valid pkg", + wantErr: false, + args: args{ + pkg: pkg, + }, + }, + { + name: "Should return error when the pkg is not informed", + wantErr: true, + }, + { + name: "Should return error when the pkg is invalid", + wantErr: true, + args: args{ + pkg: &invalidPkgWithEmptyChannels, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := validatePackageManifest(tt.args.pkg); (err != nil) != tt.wantErr { + t.Errorf("Failed to check package manifest validate: error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/olm/operator/manager.go b/internal/olm/operator/manager.go index 0f603e08be..13ed4ada58 100644 --- a/internal/olm/operator/manager.go +++ b/internal/olm/operator/manager.go @@ -15,6 +15,7 @@ package olm import ( + "bytes" "context" "errors" "fmt" @@ -432,7 +433,7 @@ func readObjectsFromFile(path string) (objs []*unstructured.Unstructured, err er if err != nil { return nil, err } - scanner := k8sutil.NewYAMLScanner(b) + scanner := k8sutil.NewYAMLScanner(bytes.NewBuffer(b)) for scanner.Scan() { b, err := yaml.YAMLToJSON(scanner.Bytes()) if err != nil { diff --git a/internal/plugins/golang/api.go b/internal/plugins/golang/api.go index c844d44a91..3e5a8376b2 100644 --- a/internal/plugins/golang/api.go +++ b/internal/plugins/golang/api.go @@ -15,11 +15,21 @@ package golang import ( + "fmt" + "path/filepath" + "strings" + + "github.com/operator-framework/operator-sdk/internal/scaffold/kustomize" + "github.com/spf13/pflag" "sigs.k8s.io/kubebuilder/pkg/model/config" "sigs.k8s.io/kubebuilder/pkg/plugin" ) +const sampleKustomizationFragment = `## This file is auto-generated, do not modify ## +resources: +` + type createAPIPlugin struct { plugin.CreateAPI @@ -52,5 +62,18 @@ func (p *createAPIPlugin) Run() error { // SDK plugin-specific scaffolds. func (p *createAPIPlugin) run() error { + // Add CR paths to the samples' kustomization file. + samplesKustomization := sampleKustomizationFragment + for _, gvk := range p.config.Resources { + samplesKustomization += fmt.Sprintf("- %s\n", makeCRFile(gvk)) + } + kpath := filepath.Join("config", "samples") + if err := kustomize.Write(kpath, samplesKustomization); err != nil { + return err + } return nil } + +func makeCRFile(gvk config.GVK) string { + return fmt.Sprintf("%s_%s_%s.yaml", gvk.Group, gvk.Version, strings.ToLower(gvk.Kind)) +} diff --git a/internal/plugins/golang/init.go b/internal/plugins/golang/init.go index 6024351034..35e5663daf 100644 --- a/internal/plugins/golang/init.go +++ b/internal/plugins/golang/init.go @@ -15,7 +15,11 @@ package golang import ( + "bufio" + "bytes" "fmt" + "io/ioutil" + "strings" "github.com/spf13/pflag" "sigs.k8s.io/kubebuilder/pkg/model/config" @@ -43,6 +47,10 @@ func (p *initPlugin) Run() error { return err } + if err := initUpdateMakefile(); err != nil { + return fmt.Errorf("error updating Makefile: %v", err) + } + // Update plugin config section with this plugin's configuration. cfg := Config{} if err := p.config.EncodePluginConfig(pluginConfigKey, cfg); err != nil { @@ -51,3 +59,86 @@ func (p *initPlugin) Run() error { return nil } + +const ( + makefileBundleImgVarFragment = `# Current operator version +VERSION ?= 0.0.1 +# Bundle image URL +BUNDLE_IMG ?= controller-bundle:$(VERSION)` + + makefileManifestsName = "manifests" + //nolint:lll + makefileManifestsFragment = `manifests: controller-gen + $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases + operator-sdk generate bundle -q --kustomize +` + + makefileBundleName = "bundle" + makefileBundleFragment = `# Generate a bundle directory +bundle: manifests + kustomize build config/bundle | operator-sdk generate bundle -q --manifests --version $(VERSION) +` + + makefileBuildBundleName = "bundle-build" + //nolint:lll + makefileBuildBundleFragment = `# Build the bundle OCI image +bundle-build: manifests + kustomize build config/bundle | operator-sdk generate bundle -q --manifests --metadata --overwrite --version $(VERSION) + operator-sdk bundle validate config/bundle + docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) . +` +) + +func initUpdateMakefile() error { + makefileBytes, err := ioutil.ReadFile("Makefile") + if err != nil { + return err + } + + makefileBytes = append([]byte(makefileBundleImgVarFragment), makefileBytes...) + + // Modify Makefile with OLM recipes. + namedFragments := map[string]string{ + makefileManifestsName: makefileManifestsFragment, + makefileBundleName: makefileBundleFragment, + makefileBuildBundleName: makefileBuildBundleFragment, + } + for name, fragment := range namedFragments { + makefileBytes = replaceOrAppendMakefileRecipe(makefileBytes, name, fragment) + } + + return ioutil.WriteFile("Makefile", makefileBytes, 0644) +} + +func replaceOrAppendMakefileRecipe(oldMakefile []byte, recipeName, fragment string) (newMakefile []byte) { + // Clean up fragment. + fragment = strings.TrimSpace(fragment) + "\n" + + // TODO: handle comments above recipes. + var foundRecipeStart, foundRecipeEnd bool + scanner := bufio.NewScanner(bytes.NewBuffer(oldMakefile)) + for scanner.Scan() { + line := scanner.Text() + switch { + case strings.HasPrefix(line, recipeName+":"): + foundRecipeStart = true + newMakefile = append(newMakefile, []byte(fragment+"\n")...) + case isRecipeLine(line) && foundRecipeStart: + foundRecipeEnd = true + fallthrough + case !foundRecipeStart || foundRecipeEnd: + newMakefile = append(newMakefile, []byte(line+"\n")...) + } + } + + if !foundRecipeStart { + newMakefile = append(newMakefile, "\n"+fragment...) + } + + return newMakefile +} + +func isRecipeLine(line string) bool { + recipeNameEnd := strings.Index(line, ":") + return recipeNameEnd != -1 && !strings.Contains(line[:recipeNameEnd], " ") +} diff --git a/internal/scaffold/kustomize/kustomize.go b/internal/scaffold/kustomize/kustomize.go new file mode 100644 index 0000000000..bc8fba3606 --- /dev/null +++ b/internal/scaffold/kustomize/kustomize.go @@ -0,0 +1,43 @@ +// 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 kustomize + +import ( + "errors" + "io/ioutil" + "os" + "path/filepath" +) + +const File = "kustomization.yaml" + +// WriteIfNotExist writes a kustomization.yaml to dir. +func Write(dir, content string) error { + if err := os.MkdirAll(dir, 0700); err != nil { + return err + } + path := filepath.Join(dir, File) + return ioutil.WriteFile(path, []byte(content), 0666) +} + +// WriteIfNotExist writes a kustomization.yaml to dir if the file does not +// already exist. If it does, this function is a no-op. +func WriteIfNotExist(dir, content string) error { + _, err := os.Stat(filepath.Join(dir, File)) + if err != nil && errors.Is(err, os.ErrNotExist) { + return Write(dir, content) + } + return nil +} diff --git a/internal/scorecard/plugins/resource_handler.go b/internal/scorecard/plugins/resource_handler.go index 9501839976..dec24de0d6 100644 --- a/internal/scorecard/plugins/resource_handler.go +++ b/internal/scorecard/plugins/resource_handler.go @@ -94,7 +94,7 @@ func createFromYAMLFile(cfg BasicAndOLMPluginConfig, yamlPath string) error { if err != nil { return fmt.Errorf("failed to read file %s: %v", yamlPath, err) } - scanner := internalk8sutil.NewYAMLScanner(yamlSpecs) + scanner := internalk8sutil.NewYAMLScanner(bytes.NewBuffer(yamlSpecs)) for scanner.Scan() { obj := &unstructured.Unstructured{} jsonSpec, err := yaml.YAMLToJSON(scanner.Bytes()) @@ -405,7 +405,7 @@ func getProxyLogs(proxyPod *v1.Pod) (string, error) { func getGVKs(yamlFile []byte) ([]schema.GroupVersionKind, error) { var gvks []schema.GroupVersionKind - scanner := internalk8sutil.NewYAMLScanner(yamlFile) + scanner := internalk8sutil.NewYAMLScanner(bytes.NewBuffer(yamlFile)) for scanner.Scan() { yamlSpec := scanner.Bytes() diff --git a/internal/util/k8sutil/api.go b/internal/util/k8sutil/api.go index cad3decc49..3df4a7795a 100644 --- a/internal/util/k8sutil/api.go +++ b/internal/util/k8sutil/api.go @@ -15,6 +15,7 @@ package k8sutil import ( + "bytes" "fmt" "io/ioutil" "path" @@ -44,7 +45,7 @@ func GetCustomResourceDefinitions(crdsDir string) (crds []apiextv1beta1.CustomRe return nil, fmt.Errorf("error reading manifest %s: %w", path, err) } - scanner := NewYAMLScanner(b) + scanner := NewYAMLScanner(bytes.NewBuffer(b)) for scanner.Scan() { manifest := scanner.Bytes() typeMeta, err := GetTypeMetaFromBytes(manifest) diff --git a/internal/util/k8sutil/scan.go b/internal/util/k8sutil/scan.go index 85deb10e28..14e07905b7 100644 --- a/internal/util/k8sutil/scan.go +++ b/internal/util/k8sutil/scan.go @@ -34,9 +34,8 @@ type Scanner struct { done bool // Scan has finished. } -func NewYAMLScanner(b []byte) *Scanner { - r := bufio.NewReader(bytes.NewBuffer(b)) - return &Scanner{reader: k8syaml.NewYAMLReader(r)} +func NewYAMLScanner(r io.Reader) *Scanner { + return &Scanner{reader: k8syaml.NewYAMLReader(bufio.NewReader(r))} } func (s *Scanner) Err() error { diff --git a/internal/util/kubebuilder/project.go b/internal/util/kubebuilder/project.go index bece9b5495..4393d5300d 100644 --- a/internal/util/kubebuilder/project.go +++ b/internal/util/kubebuilder/project.go @@ -15,9 +15,11 @@ package kbutil import ( + "io/ioutil" "os" log "github.com/sirupsen/logrus" + "sigs.k8s.io/kubebuilder/pkg/model/config" ) const configFile = "PROJECT" @@ -34,3 +36,15 @@ func HasProjectFile() bool { } return true } + +func ReadConfig() (*config.Config, error) { + b, err := ioutil.ReadFile(configFile) + if err != nil { + return nil, err + } + c := &config.Config{} + if err = c.Unmarshal(b); err != nil { + return nil, err + } + return c, nil +} diff --git a/pkg/test/resource_creator.go b/pkg/test/resource_creator.go index 3b3088e523..dd045b95de 100644 --- a/pkg/test/resource_creator.go +++ b/pkg/test/resource_creator.go @@ -15,6 +15,7 @@ package test import ( + "bytes" goctx "context" "fmt" "io/ioutil" @@ -100,7 +101,7 @@ func (ctx *Context) createFromYAML(yamlFile []byte, skipIfExists bool, cleanupOp if err != nil { return err } - scanner := k8sutil.NewYAMLScanner(yamlFile) + scanner := k8sutil.NewYAMLScanner(bytes.NewBuffer(yamlFile)) for scanner.Scan() { yamlSpec := scanner.Bytes() diff --git a/test/mockdata/kb-memcached-operator/go.sum b/test/mockdata/kb-memcached-operator/go.sum deleted file mode 100644 index fe3ee6c960..0000000000 --- a/test/mockdata/kb-memcached-operator/go.sum +++ /dev/null @@ -1,389 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= -github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= -github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= -github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= -github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= -github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= -github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= -github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= -github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= -github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= -github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= -github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= -github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= -github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= -github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= -github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= -github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= -gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4= -k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs= -k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= -k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo= -k8s.io/client-go v0.17.2/go.mod h1:QAzRgsa0C2xl4/eVpeVAZMvikCn8Nm81yqVx3Kk9XYI= -k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= -k8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs= -k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= -k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= -modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= -modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= -modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= -modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= -sigs.k8s.io/controller-runtime v0.5.0/go.mod h1:REiJzC7Y00U+2YkMbT8wxgrsX5USpXKGhb2sCtAXiT8= -sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= -sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=