From 66d1dd8d1a8d8c1072d529773675c19f84aa2be2 Mon Sep 17 00:00:00 2001 From: dweepgogia Date: Tue, 4 Jun 2019 10:22:10 -0400 Subject: [PATCH 01/20] [lib] Adding serialization for CSV type - Checking CSV yaml for data type mismatch against OLM's CSV type --- pkg/validate/serialize.go | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 pkg/validate/serialize.go diff --git a/pkg/validate/serialize.go b/pkg/validate/serialize.go new file mode 100644 index 000000000..292fdca8b --- /dev/null +++ b/pkg/validate/serialize.go @@ -0,0 +1,44 @@ +package validate + +import ( + "fmt" + "io/ioutil" + + "github.com/ghodss/yaml" + + olm "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" +) + +// Verify takes in name of the yaml file to be validated, reads it, and calls the unmarshal function on rawYaml. +func Verify(yamlFileName string) error { + rawYaml, err := ioutil.ReadFile(yamlFileName) + if err != nil { + return fmt.Errorf("Error in reading %s file: #%s ", yamlFileName, err) + } + + // Value returned is a marshaled go type (CSV Struct). + _, err = unmarshal(rawYaml) + if err != nil { + return fmt.Errorf("Error unmarshalling YAML to OLM's csv type for %s file: #%s ", yamlFileName, err) + } + + fmt.Printf("%s is verified", yamlFileName) + return nil +} + +// Unmarshal takes in a raw YAML file and deserializes it to OLM's ClusterServiceVersion type. +// Throws an error if: +// (1) the yaml file can not be converted to json. +// (2) there is a problem while unmarshalling in go type. +// Returns an object of type olm.ClusterServiceVersion. +func unmarshal(rawYAML []byte) (*olm.ClusterServiceVersion, error) { + + var csv olm.ClusterServiceVersion + + if err := yaml.Unmarshal(rawYAML, &csv); err != nil { + return nil, fmt.Errorf("error parsing CSV list (JSON) : %s", err) + } + + return &csv, nil + +} From 41cd8df7fe1f020e2006d1434ec064792d9349b8 Mon Sep 17 00:00:00 2001 From: dweepgogia Date: Tue, 11 Jun 2019 10:53:28 -0400 Subject: [PATCH 02/20] [cli] Adding verify command - Adding Command Line Tool for the library --- cmd/operator-verify/csv.yaml | 292 ++++++++++++++++++ cmd/operator-verify/dataTypeMismatch.csv.yaml | 290 +++++++++++++++++ cmd/operator-verify/main.go | 10 + cmd/root.go | 24 ++ cmd/verify.go | 30 ++ 5 files changed, 646 insertions(+) create mode 100644 cmd/operator-verify/csv.yaml create mode 100644 cmd/operator-verify/dataTypeMismatch.csv.yaml create mode 100644 cmd/operator-verify/main.go create mode 100644 cmd/root.go create mode 100644 cmd/verify.go diff --git a/cmd/operator-verify/csv.yaml b/cmd/operator-verify/csv.yaml new file mode 100644 index 000000000..4dad39663 --- /dev/null +++ b/cmd/operator-verify/csv.yaml @@ -0,0 +1,292 @@ +#! validate-crd: deploy/chart/templates/0000_30_02-clusterserviceversion.crd.yaml +#! parse-kind: ClusterServiceVersion +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + name: etcdoperator.v0.9.0 + namespace: placeholder + annotations: + capabilities: Full Lifecycle + tectonic-visibility: ocs + alm-examples: '[{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdCluster","metadata":{"name":"example","namespace":"default"},"spec":{"size":3,"version":"3.2.13"}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdRestore","metadata":{"name":"example-etcd-cluster"},"spec":{"etcdCluster":{"name":"example-etcd-cluster"},"backupStorageType":"S3","s3":{"path":"","awsSecret":""}}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdBackup","metadata":{"name":"example-etcd-cluster-backup"},"spec":{"etcdEndpoints":[""],"storageType":"S3","s3":{"path":"","awsSecret":""}}}]' + description: etcd is a distributed key value store providing a reliable way to store data across a cluster of machines. +spec: + displayName: etcd + description: | + etcd is a distributed key value store that provides a reliable way to store data across a cluster of machines. It’s open-source and available on GitHub. etcd gracefully handles leader elections during network partitions and will tolerate machine failure, including the leader. Your applications can read and write data into etcd. + A simple use-case is to store database connection details or feature flags within etcd as key value pairs. These values can be watched, allowing your app to reconfigure itself when they change. Advanced uses take advantage of the consistency guarantees to implement database leader elections or do distributed locking across a cluster of workers. + + _The etcd Open Cloud Service is Public Alpha. The goal before Beta is to fully implement backup features._ + + ### Reading and writing to etcd + + Communicate with etcd though its command line utility `etcdctl` or with the API using the automatically generated Kubernetes Service. + + [Read the complete guide to using the etcd Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/etcd-ocs.html) + + ### Supported Features + + + **High availability** + + + Multiple instances of etcd are networked together and secured. Individual failures or networking issues are transparently handled to keep your cluster up and running. + + + **Automated updates** + + + Rolling out a new etcd version works like all Kubernetes rolling updates. Simply declare the desired version, and the etcd service starts a safe rolling update to the new version automatically. + + + **Backups included** + + + Coming soon, the ability to schedule backups to happen on or off cluster. + keywords: ['etcd', 'key value', 'database', 'coreos', 'open source'] + version: 0.9.0 + maturity: alpha + maintainers: + - name: CoreOS, Inc + email: support@coreos.com + + provider: + name: CoreOS, Inc + labels: + alm-owner-etcd: etcdoperator + operated-by: etcdoperator + selector: + matchLabels: + alm-owner-etcd: etcdoperator + operated-by: etcdoperator + links: + - name: Blog + url: https://coreos.com/etcd + - name: Documentation + url: https://coreos.com/operators/etcd/docs/latest/ + - name: etcd Operator Source Code + url: https://github.com/coreos/etcd-operator + + icon: + - base64data: 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 + mediatype: image/png + installModes: + - type: OwnNamespace + supported: true + - type: SingleNamespace + supported: true + - type: MultiNamespace + supported: false + - type: AllNamespaces + supported: true + install: + strategy: deployment + spec: + permissions: + - serviceAccountName: etcd-operator + rules: + - apiGroups: + - etcd.database.coreos.com + resources: + - etcdclusters + - etcdbackups + - etcdrestores + verbs: + - "*" + - apiGroups: + - "" + resources: + - pods + - services + - endpoints + - persistentvolumeclaims + - events + verbs: + - "*" + - apiGroups: + - apps + resources: + - deployments + verbs: + - "*" + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + deployments: + - name: etcd-operator + spec: + replicas: 1 + selector: + matchLabels: + name: etcd-operator-alm-owned + template: + metadata: + name: etcd-operator-alm-owned + labels: + name: etcd-operator-alm-owned + spec: + serviceAccountName: etcd-operator + containers: + - name: etcd-operator + command: + - etcd-operator + - --create-crd=false + image: quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8 + env: + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: etcd-backup-operator + image: quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8 + command: + - etcd-backup-operator + - --create-crd=false + env: + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: etcd-restore-operator + image: quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8 + command: + - etcd-restore-operator + - --create-crd=false + env: + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + customresourcedefinitions: + owned: + - name: etcdclusters.etcd.database.coreos.com + version: v1beta2 + kind: EtcdCluster + displayName: etcd Cluster + description: Represents a cluster of etcd nodes. + resources: + - kind: Service + version: v1 + - kind: Pod + version: v1 + specDescriptors: + - description: The desired number of member Pods for the etcd cluster. + displayName: Size + path: size + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:podCount' + - description: Limits describes the minimum/maximum amount of compute resources required/allowed + displayName: Resource Requirements + path: pod.resources + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:resourceRequirements' + statusDescriptors: + - description: The status of each of the member Pods for the etcd cluster. + displayName: Member Status + path: members + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:podStatuses' + - description: The service at which the running etcd cluster can be accessed. + displayName: Service + path: serviceName + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes:Service' + - description: The current size of the etcd cluster. + displayName: Cluster Size + path: size + - description: The current version of the etcd cluster. + displayName: Current Version + path: currentVersion + - description: 'The target version of the etcd cluster, after upgrading.' + displayName: Target Version + path: targetVersion + - description: The current status of the etcd cluster. + displayName: Status + path: phase + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes.phase' + - description: Explanation for the current status of the cluster. + displayName: Status Details + path: reason + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes.phase:reason' + - name: etcdbackups.etcd.database.coreos.com + version: v1beta2 + kind: EtcdBackup + displayName: etcd Backup + description: Represents the intent to backup an etcd cluster. + specDescriptors: + - description: Specifies the endpoints of an etcd cluster. + displayName: etcd Endpoint(s) + path: etcdEndpoints + x-descriptors: + - 'urn:alm:descriptor:etcd:endpoint' + - description: The full AWS S3 path where the backup is saved. + displayName: S3 Path + path: s3.path + x-descriptors: + - 'urn:alm:descriptor:aws:s3:path' + - description: The name of the secret object that stores the AWS credential and config files. + displayName: AWS Secret + path: s3.awsSecret + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes:Secret' + statusDescriptors: + - description: Indicates if the backup was successful. + displayName: Succeeded + path: succeeded + x-descriptors: + - 'urn:alm:descriptor:text' + - description: Indicates the reason for any backup related failures. + displayName: Reason + path: reason + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes.phase:reason' + - name: etcdrestores.etcd.database.coreos.com + version: v1beta2 + kind: EtcdRestore + displayName: etcd Restore + description: Represents the intent to restore an etcd cluster from a backup. + specDescriptors: + - description: References the EtcdCluster which should be restored, + displayName: etcd Cluster + path: etcdCluster.name + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes:EtcdCluster' + - 'urn:alm:descriptor:text' + - description: The full AWS S3 path where the backup is saved. + displayName: S3 Path + path: s3.path + x-descriptors: + - 'urn:alm:descriptor:aws:s3:path' + - description: The name of the secret object that stores the AWS credential and config files. + displayName: AWS Secret + path: s3.awsSecret + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes:Secret' + statusDescriptors: + - description: Indicates if the restore was successful. + displayName: Succeeded + path: succeeded + x-descriptors: + - 'urn:alm:descriptor:text' + - description: Indicates the reason for any restore related failures. + displayName: Reason + path: reason + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes.phase:reason' \ No newline at end of file diff --git a/cmd/operator-verify/dataTypeMismatch.csv.yaml b/cmd/operator-verify/dataTypeMismatch.csv.yaml new file mode 100644 index 000000000..a915b115c --- /dev/null +++ b/cmd/operator-verify/dataTypeMismatch.csv.yaml @@ -0,0 +1,290 @@ +#! validate-crd: deploy/chart/templates/0000_30_02-clusterserviceversion.crd.yaml +#! parse-kind: ClusterServiceVersion +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + name: etcdoperator.v0.9.0 + namespace: placeholder + annotations: + capabilities: Full Lifecycle + tectonic-visibility: ocs + alm-examples: '[{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdCluster","metadata":{"name":"example","namespace":"default"},"spec":{"size":3,"version":"3.2.13"}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdRestore","metadata":{"name":"example-etcd-cluster"},"spec":{"etcdCluster":{"name":"example-etcd-cluster"},"backupStorageType":"S3","s3":{"path":"","awsSecret":""}}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdBackup","metadata":{"name":"example-etcd-cluster-backup"},"spec":{"etcdEndpoints":[""],"storageType":"S3","s3":{"path":"","awsSecret":""}}}]' + description: etcd is a distributed key value store providing a reliable way to store data across a cluster of machines. +spec: + displayName: etcd + description: | + etcd is a distributed key value store that provides a reliable way to store data across a cluster of machines. It’s open-source and available on GitHub. etcd gracefully handles leader elections during network partitions and will tolerate machine failure, including the leader. Your applications can read and write data into etcd. + A simple use-case is to store database connection details or feature flags within etcd as key value pairs. These values can be watched, allowing your app to reconfigure itself when they change. Advanced uses take advantage of the consistency guarantees to implement database leader elections or do distributed locking across a cluster of workers. + + _The etcd Open Cloud Service is Public Alpha. The goal before Beta is to fully implement backup features._ + + ### Reading and writing to etcd + + Communicate with etcd though its command line utility `etcdctl` or with the API using the automatically generated Kubernetes Service. + + [Read the complete guide to using the etcd Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/etcd-ocs.html) + + ### Supported Features + + + **High availability** + + + Multiple instances of etcd are networked together and secured. Individual failures or networking issues are transparently handled to keep your cluster up and running. + + + **Automated updates** + + + Rolling out a new etcd version works like all Kubernetes rolling updates. Simply declare the desired version, and the etcd service starts a safe rolling update to the new version automatically. + + + **Backups included** + + + Coming soon, the ability to schedule backups to happen on or off cluster. + keywords: ['etcd', 'key value', 'database', 'coreos', 'open source'] + version: 0.9.0 + maturity: alpha + maintainers: testing_with_incorrect_data_type + + provider: + name: CoreOS, Inc + labels: + alm-owner-etcd: etcdoperator + operated-by: etcdoperator + selector: + matchLabels: + alm-owner-etcd: etcdoperator + operated-by: etcdoperator + links: + - name: Blog + url: https://coreos.com/etcd + - name: Documentation + url: https://coreos.com/operators/etcd/docs/latest/ + - name: etcd Operator Source Code + url: https://github.com/coreos/etcd-operator + + icon: + - base64data: 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 + mediatype: image/png + installModes: + - type: OwnNamespace + supported: true + - type: SingleNamespace + supported: true + - type: MultiNamespace + supported: false + - type: AllNamespaces + supported: true + install: + strategy: deployment + spec: + permissions: + - serviceAccountName: etcd-operator + rules: + - apiGroups: + - etcd.database.coreos.com + resources: + - etcdclusters + - etcdbackups + - etcdrestores + verbs: + - "*" + - apiGroups: + - "" + resources: + - pods + - services + - endpoints + - persistentvolumeclaims + - events + verbs: + - "*" + - apiGroups: + - apps + resources: + - deployments + verbs: + - "*" + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + deployments: + - name: etcd-operator + spec: + replicas: 1 + selector: + matchLabels: + name: etcd-operator-alm-owned + template: + metadata: + name: etcd-operator-alm-owned + labels: + name: etcd-operator-alm-owned + spec: + serviceAccountName: etcd-operator + containers: + - name: etcd-operator + command: + - etcd-operator + - --create-crd=false + image: quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8 + env: + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: etcd-backup-operator + image: quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8 + command: + - etcd-backup-operator + - --create-crd=false + env: + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: etcd-restore-operator + image: quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8 + command: + - etcd-restore-operator + - --create-crd=false + env: + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + customresourcedefinitions: + owned: + - name: etcdclusters.etcd.database.coreos.com + version: v1beta2 + kind: EtcdCluster + displayName: etcd Cluster + description: Represents a cluster of etcd nodes. + resources: + - kind: Service + version: v1 + - kind: Pod + version: v1 + specDescriptors: + - description: The desired number of member Pods for the etcd cluster. + displayName: Size + path: size + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:podCount' + - description: Limits describes the minimum/maximum amount of compute resources required/allowed + displayName: Resource Requirements + path: pod.resources + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:resourceRequirements' + statusDescriptors: + - description: The status of each of the member Pods for the etcd cluster. + displayName: Member Status + path: members + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:podStatuses' + - description: The service at which the running etcd cluster can be accessed. + displayName: Service + path: serviceName + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes:Service' + - description: The current size of the etcd cluster. + displayName: Cluster Size + path: size + - description: The current version of the etcd cluster. + displayName: Current Version + path: currentVersion + - description: 'The target version of the etcd cluster, after upgrading.' + displayName: Target Version + path: targetVersion + - description: The current status of the etcd cluster. + displayName: Status + path: phase + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes.phase' + - description: Explanation for the current status of the cluster. + displayName: Status Details + path: reason + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes.phase:reason' + - name: etcdbackups.etcd.database.coreos.com + version: v1beta2 + kind: EtcdBackup + displayName: etcd Backup + description: Represents the intent to backup an etcd cluster. + specDescriptors: + - description: Specifies the endpoints of an etcd cluster. + displayName: etcd Endpoint(s) + path: etcdEndpoints + x-descriptors: + - 'urn:alm:descriptor:etcd:endpoint' + - description: The full AWS S3 path where the backup is saved. + displayName: S3 Path + path: s3.path + x-descriptors: + - 'urn:alm:descriptor:aws:s3:path' + - description: The name of the secret object that stores the AWS credential and config files. + displayName: AWS Secret + path: s3.awsSecret + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes:Secret' + statusDescriptors: + - description: Indicates if the backup was successful. + displayName: Succeeded + path: succeeded + x-descriptors: + - 'urn:alm:descriptor:text' + - description: Indicates the reason for any backup related failures. + displayName: Reason + path: reason + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes.phase:reason' + - name: etcdrestores.etcd.database.coreos.com + version: v1beta2 + kind: EtcdRestore + displayName: etcd Restore + description: Represents the intent to restore an etcd cluster from a backup. + specDescriptors: + - description: References the EtcdCluster which should be restored, + displayName: etcd Cluster + path: etcdCluster.name + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes:EtcdCluster' + - 'urn:alm:descriptor:text' + - description: The full AWS S3 path where the backup is saved. + displayName: S3 Path + path: s3.path + x-descriptors: + - 'urn:alm:descriptor:aws:s3:path' + - description: The name of the secret object that stores the AWS credential and config files. + displayName: AWS Secret + path: s3.awsSecret + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes:Secret' + statusDescriptors: + - description: Indicates if the restore was successful. + displayName: Succeeded + path: succeeded + x-descriptors: + - 'urn:alm:descriptor:text' + - description: Indicates the reason for any restore related failures. + displayName: Reason + path: reason + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes.phase:reason' \ No newline at end of file diff --git a/cmd/operator-verify/main.go b/cmd/operator-verify/main.go new file mode 100644 index 000000000..6fce3e329 --- /dev/null +++ b/cmd/operator-verify/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/dweepgogia/new-manifest-verification/cmd" +) + +func main() { + // Launch CLI tool. + cmd.Execute() +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 000000000..868ef166d --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,24 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "operator-verify", + Short: "New Manifest Verification Tool Prototype", + Long: `operator-verify is a CLI tool for the Operator Manifest Verification Library. This library provides functions to validate the operator manifest bundles against Operator-Lifecycle-Manager's ClusterServiceVersion type, CustomResourceDefinitions, and Package Manifest yamls. Currently, this application supports validation of ClusterServiceVersion yaml for any mismatched data types with Operator-Lifecycle-Manager's ClusterServiceVersion type.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Initializing verification CLI tool...") + }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/cmd/verify.go b/cmd/verify.go new file mode 100644 index 000000000..e2fc1c61e --- /dev/null +++ b/cmd/verify.go @@ -0,0 +1,30 @@ +package cmd + +import ( + "fmt" + + "github.com/dweepgogia/new-manifest-verification/pkg/validate" + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(verifyCmd) +} + +var verifyCmd = &cobra.Command{ + Use: "verify", + Short: "Validate YAML against OLM's CSV type.", + Long: `Verifies the yaml file against Operator-Lifecycle-Manager's ClusterServiceVersion type. Reports errors for any mismatched data types. Takes in one argument i.e. path to the yaml file. Version: 1.0`, + RunE: verifyFunc, +} + +func verifyFunc(cmd *cobra.Command, args []string) error { + + if len(args) != 1 { + return fmt.Errorf("command %s requires exactly one argument", cmd.CommandPath()) + } + + yamlFileName := args[0] + + return validate.Verify(yamlFileName) +} From d0170bc55ddfd0e1c545db5634af58c2ea5e2519 Mon Sep 17 00:00:00 2001 From: dweepgogia Date: Mon, 17 Jun 2019 11:09:55 -0400 Subject: [PATCH 03/20] [cli] Handling error reporting through cli - To avoid "usage" and "flags" getting printed every time there's an error --- cmd/verify.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cmd/verify.go b/cmd/verify.go index e2fc1c61e..6e413e9bd 100644 --- a/cmd/verify.go +++ b/cmd/verify.go @@ -15,16 +15,18 @@ var verifyCmd = &cobra.Command{ Use: "verify", Short: "Validate YAML against OLM's CSV type.", Long: `Verifies the yaml file against Operator-Lifecycle-Manager's ClusterServiceVersion type. Reports errors for any mismatched data types. Takes in one argument i.e. path to the yaml file. Version: 1.0`, - RunE: verifyFunc, + Run: verifyFunc, } -func verifyFunc(cmd *cobra.Command, args []string) error { +func verifyFunc(cmd *cobra.Command, args []string) { if len(args) != 1 { - return fmt.Errorf("command %s requires exactly one argument", cmd.CommandPath()) + fmt.Printf("command %s requires exactly one argument", cmd.CommandPath()) } yamlFileName := args[0] - return validate.Verify(yamlFileName) + if err := validate.Verify(yamlFileName); err != nil { + fmt.Println(err) + } } From 0953276867569dffa8e6a8352534728ad8b67796 Mon Sep 17 00:00:00 2001 From: dweepgogia Date: Fri, 14 Jun 2019 14:25:46 -0400 Subject: [PATCH 04/20] [lib] Handling mandatory/optional fields and reporting custom errors - For reporting warnings and errors in case of optional and mandatory fields missing, respectively. - To enable users to extract more information from an error/warning. --- pkg/validate/serialize.go | 181 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 174 insertions(+), 7 deletions(-) diff --git a/pkg/validate/serialize.go b/pkg/validate/serialize.go index 292fdca8b..290b9026d 100644 --- a/pkg/validate/serialize.go +++ b/pkg/validate/serialize.go @@ -1,14 +1,30 @@ package validate import ( + "encoding/json" "fmt" "io/ioutil" + "reflect" + "strings" "github.com/ghodss/yaml" - olm "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" ) +// Represents verification result for each of the yaml files from the manifest bundle. +type manifestResult struct { + errors []missingTypeError + warnings []missingTypeError +} + +// Represents a warning or an error in a yaml file. +type missingTypeError struct { + err string + typeName string + path string + isMandatory bool +} + // Verify takes in name of the yaml file to be validated, reads it, and calls the unmarshal function on rawYaml. func Verify(yamlFileName string) error { rawYaml, err := ioutil.ReadFile(yamlFileName) @@ -17,28 +33,179 @@ func Verify(yamlFileName string) error { } // Value returned is a marshaled go type (CSV Struct). - _, err = unmarshal(rawYaml) + csv, err := unmarshal(rawYaml) if err != nil { return fmt.Errorf("Error unmarshalling YAML to OLM's csv type for %s file: #%s ", yamlFileName, err) } - fmt.Printf("%s is verified", yamlFileName) + // Contains error logs for all missing optional and mandatory fields. + errorLog := csvInspect(csv) + getErrorsFromManifestResult(errorLog.warnings) + + // There is no mandatory field thats missing if errorLog.errors is nil. + if errorLog.errors != nil { + fmt.Println() + getErrorsFromManifestResult(errorLog.errors) + return fmt.Errorf("Populate all the mandatory fields missing from %s file.", yamlFileName) + } + fmt.Printf("%s is verified.", yamlFileName) return nil } +// Iterates over the list of warnings and errors. +func getErrorsFromManifestResult(err []missingTypeError) { + for _, v := range err { + assertTypeToGetValue(v) + } +} + +// Asserts type to get the underlying field value. +func assertTypeToGetValue(v interface{}) { + if v, ok := v.(missingTypeError); ok { + fmt.Println(v) + } +} + // Unmarshal takes in a raw YAML file and deserializes it to OLM's ClusterServiceVersion type. // Throws an error if: // (1) the yaml file can not be converted to json. // (2) there is a problem while unmarshalling in go type. // Returns an object of type olm.ClusterServiceVersion. -func unmarshal(rawYAML []byte) (*olm.ClusterServiceVersion, error) { +func unmarshal(rawYAML []byte) (olm.ClusterServiceVersion, error) { var csv olm.ClusterServiceVersion - if err := yaml.Unmarshal(rawYAML, &csv); err != nil { - return nil, fmt.Errorf("error parsing CSV list (JSON) : %s", err) + rawJson, err := yaml.YAMLToJSON(rawYAML) + if err != nil { + fmt.Printf("error parsing raw YAML to Json: %s", err) + return csv, err + } + if err := json.Unmarshal(rawJson, &csv); err != nil { + return csv, fmt.Errorf("error parsing CSV list (JSON) : %s", err) + } + + return csv, nil +} + +// missingTypeError strut implements the Error interface to define custom error formatting. +func (err missingTypeError) Error() string { + if err.isMandatory { + return fmt.Sprintf("Error: Mandatory %s Missing (%s)", err.typeName, err.path) + } else { + return fmt.Sprintf("Warning: Optional %s Missing (%s)", err.typeName, err.path) } +} + +// Iterates over the given CSV. Returns a manifestResult type object. +func csvInspect(val interface{}) manifestResult { - return &csv, nil + fieldValue := reflect.ValueOf(val) + switch fieldValue.Kind() { + case reflect.Struct: + return checkMissingFields(fieldValue, "", manifestResult{}) + default: + err := []missingTypeError{{"Error: input file is not a valid CSV.", "", "", false}} + return manifestResult{errors: err, warnings: nil} + } +} + +// Takes in a string slice and checks if a string (x) is present in the slice. +// Return true if the string is present in the slice. +func containsStrict(a []string, x string) bool { + for _, n := range a { + if x == n { + return true + } + } + return false +} + +// Recursive function that traverses a nested struct passed in as reflect value, and reports for errors/warnings +// in case of null struct field values. +// Returns a log of errors as slice of strings. +func checkMissingFields(v reflect.Value, parentStructName string, log manifestResult) manifestResult { + + for i := 0; i < v.NumField(); i++ { + + fieldValue := v.Field(i) + + tag := v.Type().Field(i).Tag.Get("json") + // Ignore fields that are subsets of a primitive field. + if tag == "" { + continue + } + + fields := strings.Split(tag, ",") + isOptionalField := containsStrict(fields, "omitempty") + emptyVal := isEmptyValue(fieldValue) + + newParentStructName := "" + if parentStructName == "" { + newParentStructName = v.Type().Field(i).Name + } else { + newParentStructName = parentStructName + "." + v.Type().Field(i).Name + } + + switch fieldValue.Kind() { + case reflect.Struct: + log = updateLog(log, "Struct", newParentStructName, emptyVal, isOptionalField) + if emptyVal { + continue + } + log = checkMissingFields(fieldValue, newParentStructName, log) + default: + log = updateLog(log, "Field", newParentStructName, emptyVal, isOptionalField) + } + } + return log +} + +// Returns updated error log with missing optional/mandatory field/struct objects. +func updateLog(log manifestResult, typeName string, newParentStructName string, emptyVal bool, isOptionalField bool) manifestResult { + + if emptyVal && isOptionalField { + errString := fmt.Sprintf("Warning: Optional %s Missing.", typeName) + log.warnings = append(log.warnings, missingTypeError{errString, typeName, newParentStructName, false}) + } else if emptyVal && !isOptionalField { + if newParentStructName != "Status" { + errString := fmt.Sprintf("Error: Mandatory %s Missing.", typeName) + log.errors = append(log.errors, missingTypeError{errString, typeName, newParentStructName, true}) + } + } + return log +} + +// Uses reflect package to check if the value of the object passed is null, returns a boolean accordingly. +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + // Check if the value for 'Spec.InstallStrategy.StrategySpecRaw' field is present. This field is a RawMessage value type. Without a value, the key is explicitly set to 'null'. + if fieldValue, ok := v.Interface().(json.RawMessage); ok { + valString := string(fieldValue) + if valString == "null" { + return true + } + } + return v.Len() == 0 + // Currently the only CSV field with integer type is containerPort. Operator Verification Library raises a warning if containerPort field is missisng or if its value is 0. + // It is an optional field so the user can ignore the warning saying this field is missing if they intend to use port 0. + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Struct: + for i, n := 0, v.NumField(); i < n; i++ { + if !isEmptyValue(v.Field(i)) { + return false + } + } + return true + default: + panic(fmt.Sprintf("%v kind is not supported.", v.Kind())) + } } From 2b156e3a033748a83367760dfd3e0fc51e92d24c Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Mon, 15 Jul 2019 15:53:32 -0700 Subject: [PATCH 05/20] pkg/validate: add Validator interface to add k8s objects and run validation logic, refactoring around interface cmd/verify.go: Verify() name change --- cmd/verify.go | 2 +- pkg/validate/csv_validator.go | 45 +++++++++++++++++++++++++++++++++++ pkg/validate/interface.go | 44 ++++++++++++++++++++++++++++++++++ pkg/validate/serialize.go | 21 ++++++++-------- 4 files changed, 100 insertions(+), 12 deletions(-) create mode 100644 pkg/validate/csv_validator.go create mode 100644 pkg/validate/interface.go diff --git a/cmd/verify.go b/cmd/verify.go index 6e413e9bd..25e6c6fff 100644 --- a/cmd/verify.go +++ b/cmd/verify.go @@ -26,7 +26,7 @@ func verifyFunc(cmd *cobra.Command, args []string) { yamlFileName := args[0] - if err := validate.Verify(yamlFileName); err != nil { + if err := validate.ValidateCSVManifest(yamlFileName); err != nil { fmt.Println(err) } } diff --git a/pkg/validate/csv_validator.go b/pkg/validate/csv_validator.go new file mode 100644 index 000000000..61f00c113 --- /dev/null +++ b/pkg/validate/csv_validator.go @@ -0,0 +1,45 @@ +package validate + +import ( + "fmt" + + olm "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" +) + +type CSVValidator struct { + csvs []olm.ClusterServiceVersion +} + +var _ Validator = &CSVValidator{} + +func (v *CSVValidator) Validate() error { + for _, csv := range v.csvs { + // Contains error logs for all missing optional and mandatory fields. + errorLog := csvInspect(csv) + getErrorsFromManifestResult(errorLog.warnings) + + // There is no mandatory field thats missing if errorLog.errors is nil. + if errorLog.errors != nil { + fmt.Println() + getErrorsFromManifestResult(errorLog.errors) + return fmt.Errorf("Populate all the mandatory fields missing from CSV %s.", csv.GetName()) + } + } + return nil +} + +func (v *CSVValidator) AddObjects(objs ...interface{}) error { + for _, o := range objs { + switch t := o.(type) { + case olm.ClusterServiceVersion: + v.csvs = append(v.csvs, t) + case *olm.ClusterServiceVersion: + v.csvs = append(v.csvs, *t) + } + } + return nil +} + +func (v CSVValidator) Name() string { + return "ClusterServiceVersion Validator" +} diff --git a/pkg/validate/interface.go b/pkg/validate/interface.go new file mode 100644 index 000000000..d581b0dc9 --- /dev/null +++ b/pkg/validate/interface.go @@ -0,0 +1,44 @@ +package validate + +// Validator is an interface for implementing a validator of a single +// Kubernetes object type. Ideally each Validator will check one aspect of +// an object, or perform several steps that have a common theme or goal. +type Validator interface { + // Validate should run validation logic on an arbitrary object, and return + // an error if the object does not pass validation. + Validate() error + // AddObjects adds objects to the Validator. Each object will be validated + // when Validate() is called. + AddObjects(...interface{}) error + // Name should return a succinct name for this validator. + Name() string +} + +type ValidatorSet struct { + validators []Validator +} + +func NewValidatorSet(vs ...Validator) *ValidatorSet { + set := &ValidatorSet{} + set.AddValidators(vs...) + return set +} + +func (set *ValidatorSet) AddValidators(vs ...Validator) { + seenNames := map[string]struct{}{} + for _, v := range vs { + if _, seen := seenNames[v.Name()]; !seen { + set.validators = append(set.validators, v) + seenNames[v.Name()] = struct{}{} + } + } +} + +func (set ValidatorSet) ValidateAll() (errs []error) { + for _, v := range set.validators { + if err := v.Validate(); err != nil { + errs = append(errs, err) + } + } + return errs +} diff --git a/pkg/validate/serialize.go b/pkg/validate/serialize.go index 290b9026d..0d76a60cb 100644 --- a/pkg/validate/serialize.go +++ b/pkg/validate/serialize.go @@ -25,8 +25,9 @@ type missingTypeError struct { isMandatory bool } -// Verify takes in name of the yaml file to be validated, reads it, and calls the unmarshal function on rawYaml. -func Verify(yamlFileName string) error { +// ValidateCSVManifest takes in name of the yaml file to be validated, reads +// it, and calls the unmarshal function on rawYaml. +func ValidateCSVManifest(yamlFileName string) error { rawYaml, err := ioutil.ReadFile(yamlFileName) if err != nil { return fmt.Errorf("Error in reading %s file: #%s ", yamlFileName, err) @@ -38,15 +39,13 @@ func Verify(yamlFileName string) error { return fmt.Errorf("Error unmarshalling YAML to OLM's csv type for %s file: #%s ", yamlFileName, err) } - // Contains error logs for all missing optional and mandatory fields. - errorLog := csvInspect(csv) - getErrorsFromManifestResult(errorLog.warnings) - - // There is no mandatory field thats missing if errorLog.errors is nil. - if errorLog.errors != nil { - fmt.Println() - getErrorsFromManifestResult(errorLog.errors) - return fmt.Errorf("Populate all the mandatory fields missing from %s file.", yamlFileName) + v := &CSVValidator{} + if err = v.AddObjects(csv); err != nil { + return err + } + fmt.Println("Running", v.Name()) + if err = v.Validate(); err != nil { + return err } fmt.Printf("%s is verified.", yamlFileName) return nil From d80f5d6e7f3f0270c0afc5884cd6979c144faebb Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Tue, 16 Jul 2019 11:28:13 -0700 Subject: [PATCH 06/20] refactor Validator and error/result types into pkg/validate/validator --- pkg/validate/csv_validator.go | 20 +++----- pkg/validate/interface.go | 44 ----------------- pkg/validate/serialize.go | 75 +++++++++++++---------------- pkg/validate/validator/interface.go | 15 ++++++ pkg/validate/validator/validator.go | 70 +++++++++++++++++++++++++++ 5 files changed, 126 insertions(+), 98 deletions(-) delete mode 100644 pkg/validate/interface.go create mode 100644 pkg/validate/validator/interface.go create mode 100644 pkg/validate/validator/validator.go diff --git a/pkg/validate/csv_validator.go b/pkg/validate/csv_validator.go index 61f00c113..c9f8025c9 100644 --- a/pkg/validate/csv_validator.go +++ b/pkg/validate/csv_validator.go @@ -1,7 +1,7 @@ package validate import ( - "fmt" + "github.com/dweepgogia/new-manifest-verification/pkg/validate/validator" olm "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" ) @@ -10,22 +10,18 @@ type CSVValidator struct { csvs []olm.ClusterServiceVersion } -var _ Validator = &CSVValidator{} +var _ validator.Validator = &CSVValidator{} -func (v *CSVValidator) Validate() error { +func (v *CSVValidator) Validate() (results []validator.ManifestResult) { for _, csv := range v.csvs { // Contains error logs for all missing optional and mandatory fields. - errorLog := csvInspect(csv) - getErrorsFromManifestResult(errorLog.warnings) - - // There is no mandatory field thats missing if errorLog.errors is nil. - if errorLog.errors != nil { - fmt.Println() - getErrorsFromManifestResult(errorLog.errors) - return fmt.Errorf("Populate all the mandatory fields missing from CSV %s.", csv.GetName()) + result := csvInspect(csv) + if result.Name == "" { + result.Name = csv.GetName() } + results = append(results, result) } - return nil + return results } func (v *CSVValidator) AddObjects(objs ...interface{}) error { diff --git a/pkg/validate/interface.go b/pkg/validate/interface.go deleted file mode 100644 index d581b0dc9..000000000 --- a/pkg/validate/interface.go +++ /dev/null @@ -1,44 +0,0 @@ -package validate - -// Validator is an interface for implementing a validator of a single -// Kubernetes object type. Ideally each Validator will check one aspect of -// an object, or perform several steps that have a common theme or goal. -type Validator interface { - // Validate should run validation logic on an arbitrary object, and return - // an error if the object does not pass validation. - Validate() error - // AddObjects adds objects to the Validator. Each object will be validated - // when Validate() is called. - AddObjects(...interface{}) error - // Name should return a succinct name for this validator. - Name() string -} - -type ValidatorSet struct { - validators []Validator -} - -func NewValidatorSet(vs ...Validator) *ValidatorSet { - set := &ValidatorSet{} - set.AddValidators(vs...) - return set -} - -func (set *ValidatorSet) AddValidators(vs ...Validator) { - seenNames := map[string]struct{}{} - for _, v := range vs { - if _, seen := seenNames[v.Name()]; !seen { - set.validators = append(set.validators, v) - seenNames[v.Name()] = struct{}{} - } - } -} - -func (set ValidatorSet) ValidateAll() (errs []error) { - for _, v := range set.validators { - if err := v.Validate(); err != nil { - errs = append(errs, err) - } - } - return errs -} diff --git a/pkg/validate/serialize.go b/pkg/validate/serialize.go index 0d76a60cb..f63c08578 100644 --- a/pkg/validate/serialize.go +++ b/pkg/validate/serialize.go @@ -7,24 +7,13 @@ import ( "reflect" "strings" + "github.com/dweepgogia/new-manifest-verification/pkg/validate/validator" + "github.com/ghodss/yaml" olm "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" + "github.com/pkg/errors" ) -// Represents verification result for each of the yaml files from the manifest bundle. -type manifestResult struct { - errors []missingTypeError - warnings []missingTypeError -} - -// Represents a warning or an error in a yaml file. -type missingTypeError struct { - err string - typeName string - path string - isMandatory bool -} - // ValidateCSVManifest takes in name of the yaml file to be validated, reads // it, and calls the unmarshal function on rawYaml. func ValidateCSVManifest(yamlFileName string) error { @@ -44,15 +33,24 @@ func ValidateCSVManifest(yamlFileName string) error { return err } fmt.Println("Running", v.Name()) - if err = v.Validate(); err != nil { - return err + for _, errorLog := range v.Validate() { + fmt.Println("Validating CSV", errorLog.Name) + + getErrorsFromManifestResult(errorLog.Warnings) + + // There is no mandatory field thats missing if errorLog.errors is nil. + if errorLog.Errors != nil { + fmt.Println() + getErrorsFromManifestResult(errorLog.Errors) + return fmt.Errorf("Populate all the mandatory fields missing from CSV %s.", csv.GetName()) + } } - fmt.Printf("%s is verified.", yamlFileName) + fmt.Printf("%s is verified.\n", yamlFileName) return nil } -// Iterates over the list of warnings and errors. -func getErrorsFromManifestResult(err []missingTypeError) { +// Iterates over the list of Warnings and Errors. +func getErrorsFromManifestResult(err []validator.MissingTypeError) { for _, v := range err { assertTypeToGetValue(v) } @@ -60,7 +58,7 @@ func getErrorsFromManifestResult(err []missingTypeError) { // Asserts type to get the underlying field value. func assertTypeToGetValue(v interface{}) { - if v, ok := v.(missingTypeError); ok { + if v, ok := v.(validator.MissingTypeError); ok { fmt.Println(v) } } @@ -86,26 +84,19 @@ func unmarshal(rawYAML []byte) (olm.ClusterServiceVersion, error) { return csv, nil } -// missingTypeError strut implements the Error interface to define custom error formatting. -func (err missingTypeError) Error() string { - if err.isMandatory { - return fmt.Sprintf("Error: Mandatory %s Missing (%s)", err.typeName, err.path) - } else { - return fmt.Sprintf("Warning: Optional %s Missing (%s)", err.typeName, err.path) - } -} - -// Iterates over the given CSV. Returns a manifestResult type object. -func csvInspect(val interface{}) manifestResult { +// Iterates over the given CSV. Returns a ManifestResult type object. +func csvInspect(val interface{}) validator.ManifestResult { fieldValue := reflect.ValueOf(val) switch fieldValue.Kind() { case reflect.Struct: - return checkMissingFields(fieldValue, "", manifestResult{}) + return checkMissingFields(fieldValue, "", validator.ManifestResult{}) default: - err := []missingTypeError{{"Error: input file is not a valid CSV.", "", "", false}} - return manifestResult{errors: err, warnings: nil} + errs := []validator.MissingTypeError{ + {Err: errors.New("Error: input file is not a valid CSV.")}, + } + return validator.ManifestResult{Errors: errs, Warnings: nil} } } @@ -120,10 +111,10 @@ func containsStrict(a []string, x string) bool { return false } -// Recursive function that traverses a nested struct passed in as reflect value, and reports for errors/warnings +// Recursive function that traverses a nested struct passed in as reflect value, and reports for Errors/Warnings // in case of null struct field values. -// Returns a log of errors as slice of strings. -func checkMissingFields(v reflect.Value, parentStructName string, log manifestResult) manifestResult { +// Returns a log of Errors as slice of strings. +func checkMissingFields(v reflect.Value, parentStructName string, log validator.ManifestResult) validator.ManifestResult { for i := 0; i < v.NumField(); i++ { @@ -161,15 +152,15 @@ func checkMissingFields(v reflect.Value, parentStructName string, log manifestRe } // Returns updated error log with missing optional/mandatory field/struct objects. -func updateLog(log manifestResult, typeName string, newParentStructName string, emptyVal bool, isOptionalField bool) manifestResult { +func updateLog(log validator.ManifestResult, typeName string, newParentStructName string, emptyVal bool, isOptionalField bool) validator.ManifestResult { if emptyVal && isOptionalField { - errString := fmt.Sprintf("Warning: Optional %s Missing.", typeName) - log.warnings = append(log.warnings, missingTypeError{errString, typeName, newParentStructName, false}) + err := errors.Errorf("Warning: Optional %s Missing.", typeName) + log.Warnings = append(log.Warnings, validator.MissingTypeError{err, typeName, newParentStructName, false}) } else if emptyVal && !isOptionalField { if newParentStructName != "Status" { - errString := fmt.Sprintf("Error: Mandatory %s Missing.", typeName) - log.errors = append(log.errors, missingTypeError{errString, typeName, newParentStructName, true}) + err := errors.Errorf("Error: Mandatory %s Missing.", typeName) + log.Errors = append(log.Errors, validator.MissingTypeError{err, typeName, newParentStructName, true}) } } return log diff --git a/pkg/validate/validator/interface.go b/pkg/validate/validator/interface.go new file mode 100644 index 000000000..d34cd3ee4 --- /dev/null +++ b/pkg/validate/validator/interface.go @@ -0,0 +1,15 @@ +package validator + +// Validator is an interface for implementing a validator of a single +// Kubernetes object type. Ideally each Validator will check one aspect of +// an object, or perform several steps that have a common theme or goal. +type Validator interface { + // Validate should run validation logic on an arbitrary object, and return + // a one ManifestResult for each object that did not pass validation. + Validate() []ManifestResult + // AddObjects adds objects to the Validator. Each object will be validated + // when Validate() is called. + AddObjects(...interface{}) error + // Name should return a succinct name for this validator. + Name() string +} diff --git a/pkg/validate/validator/validator.go b/pkg/validate/validator/validator.go new file mode 100644 index 000000000..99e903450 --- /dev/null +++ b/pkg/validate/validator/validator.go @@ -0,0 +1,70 @@ +package validator + +import "fmt" + +// ManifestResult represents verification result for each of the yaml files +// from the manifest bundle. +type ManifestResult struct { + // Name is some piece of information identifying the manifest. This should + // usually be set to object.GetName(). + Name string + // Errors pertain to issues with the manifest that must be corrected. + Errors []MissingTypeError + // Warnings pertain to issues with the manifest that are optional to correct. + Warnings []MissingTypeError +} + +// MissingTypeError represents a warning or an error in a yaml file. +type MissingTypeError struct { + // Err is the underlying error caused by a missing type, if any. + Err error + // TypeName is the syntactical name of missing data, ex. Struct, Field. + TypeName string + // Path is the dot-hierarchical YAML path of the missing data. + Path string + // IsMandatory determines whether the missing data should generate a + // warning (false, the default) or error (true). + IsMandatory bool +} + +// MissingTypeError strut implements the Error interface to define custom error formatting. +func (err MissingTypeError) Error() string { + if err.IsMandatory { + return fmt.Sprintf("Error: Mandatory %s Missing (%s)", err.TypeName, err.Path) + } else { + return fmt.Sprintf("Warning: Optional %s Missing (%s)", err.TypeName, err.Path) + } +} + +// ValidatorSet contains a set of Validators to be executed sequentially. +// TODO: add configurable logger. +type ValidatorSet struct { + validators []Validator +} + +// NewValidatorSet creates a ValidatorSet containing vs. +func NewValidatorSet(vs ...Validator) *ValidatorSet { + set := &ValidatorSet{} + set.AddValidators(vs...) + return set +} + +// AddValidators adds each unique Validator in vs to the receiver. +func (set *ValidatorSet) AddValidators(vs ...Validator) { + seenNames := map[string]struct{}{} + for _, v := range vs { + if _, seen := seenNames[v.Name()]; !seen { + set.validators = append(set.validators, v) + seenNames[v.Name()] = struct{}{} + } + } +} + +// ValidateAll runs each Validator in the receiver and returns all results. +func (set ValidatorSet) ValidateAll() (allResults []ManifestResult) { + for _, v := range set.validators { + results := v.Validate() + allResults = append(allResults, results...) + } + return allResults +} From f5276659c160e999d015ceb8e8e50aa892fbe4dd Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Tue, 16 Jul 2019 11:45:20 -0700 Subject: [PATCH 07/20] fix comment --- pkg/validate/serialize.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/validate/serialize.go b/pkg/validate/serialize.go index f63c08578..de4c6419d 100644 --- a/pkg/validate/serialize.go +++ b/pkg/validate/serialize.go @@ -49,7 +49,7 @@ func ValidateCSVManifest(yamlFileName string) error { return nil } -// Iterates over the list of Warnings and Errors. +// Iterates over the list of warnings and errors. func getErrorsFromManifestResult(err []validator.MissingTypeError) { for _, v := range err { assertTypeToGetValue(v) @@ -111,9 +111,8 @@ func containsStrict(a []string, x string) bool { return false } -// Recursive function that traverses a nested struct passed in as reflect value, and reports for Errors/Warnings +// Recursive function that traverses a nested struct passed in as reflect value, and reports for errors/warnings // in case of null struct field values. -// Returns a log of Errors as slice of strings. func checkMissingFields(v reflect.Value, parentStructName string, log validator.ManifestResult) validator.ManifestResult { for i := 0; i < v.NumField(); i++ { From ccf242dc2f752029b5d02df7dbd94be168c1ecfc Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 17 Jul 2019 09:18:21 -0700 Subject: [PATCH 08/20] go.mod,go.sum: add modfiles --- go.mod | 13 +++ go.sum | 255 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 268 insertions(+) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..47af2ad78 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module github.com/dweepgogia/new-manifest-verification + +go 1.12 + +require ( + github.com/ghodss/yaml v1.0.0 + github.com/operator-framework/operator-lifecycle-manager v3.11.0+incompatible + github.com/pkg/errors v0.8.0 + github.com/spf13/cobra v0.0.4 + k8s.io/apiextensions-apiserver v0.0.0-20190713023830-b4b8a3310a45 // indirect + k8s.io/apimachinery v0.0.0-20190715170309-6171873045ff // indirect + k8s.io/client-go v11.0.0+incompatible // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..c8826460c --- /dev/null +++ b/go.sum @@ -0,0 +1,255 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +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.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-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +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/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+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 v0.0.0-20180117170138-065b426bd416/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 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +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/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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/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/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +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-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/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.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/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/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/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/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/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/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/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/gogo/protobuf v1.0.0 h1:2jyBKDKU/8v3v2xVR2PtiWQviFUyiaGk2rpfyFT8rTM= +github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v0.0.0-20170330212424-2500245aa611/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/hashicorp/golang-lru v0.5.0/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/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +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 v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +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-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/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-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/operator-framework/operator-lifecycle-manager v3.11.0+incompatible h1:Po8C8RVLRWq7pNQ5pKonM9CXpC/osoBWbmsuf+HJnSI= +github.com/operator-framework/operator-lifecycle-manager v3.11.0+incompatible/go.mod h1:Ma5ZXd4S1vmMyewWlF7aO8CZiokR7Sd8dhSfkGkNU4U= +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 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +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.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/soheilhy/cmux v0.1.3/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.4 h1:S0tLZ3VOKl2Te0hpq8+ke0eSJPfCnNTPiDlsfwi1/NE= +github.com/spf13/cobra v0.0.4/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +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 v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +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/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +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/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/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/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-20181201002055-351d144fa1fc/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-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 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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 h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +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/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-20170731182057-09f6ed296fc6/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.13.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o= +gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +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.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +k8s.io/api v0.0.0-20190712022805-31fe033ae6f9 h1:sbrvmfIQxEkFARFDPWY7DMwG+1cGcFwOzfACZbfFhY4= +k8s.io/api v0.0.0-20190712022805-31fe033ae6f9/go.mod h1:quCKyJONbDKoYS6YlndaZ0BUdM1jU4J8fCiC0S2ctLI= +k8s.io/apiextensions-apiserver v0.0.0-20190713023830-b4b8a3310a45 h1:PnGyTcZx6K/YcKsIHfH+VrcNryybh06OOALs9A1XY9U= +k8s.io/apiextensions-apiserver v0.0.0-20190713023830-b4b8a3310a45/go.mod h1:uPYx3KN9c9FtxeO2CEqTIztxbjMPlb0xdHeds0XZwZo= +k8s.io/apimachinery v0.0.0-20190711103026-7bf792636534/go.mod h1:M2fZgZL9DbLfeJaPBCDqSqNsdsmLN+V29knYJnIXlMA= +k8s.io/apimachinery v0.0.0-20190711222657-391ed67afa7b/go.mod h1:M2fZgZL9DbLfeJaPBCDqSqNsdsmLN+V29knYJnIXlMA= +k8s.io/apimachinery v0.0.0-20190712095106-75ce4d1e60f1/go.mod h1:M2fZgZL9DbLfeJaPBCDqSqNsdsmLN+V29knYJnIXlMA= +k8s.io/apimachinery v0.0.0-20190715170309-6171873045ff h1:xezFJNivtQCzq73n4MFKshwXX1ftSsxxK8k4BfVJ3X4= +k8s.io/apimachinery v0.0.0-20190715170309-6171873045ff/go.mod h1:M2fZgZL9DbLfeJaPBCDqSqNsdsmLN+V29knYJnIXlMA= +k8s.io/apiserver v0.0.0-20190712103608-7754ffe9aeba/go.mod h1:7VNJRtV6E+92bMQYfSvcpj9nbeXWUpu6mUhkyi7VIaA= +k8s.io/client-go v0.0.0-20190712102959-611184f7c43a/go.mod h1:fdQc9ByAiSeRWlHAp/NtRHzoYmdTblX8jATAi8iwHLU= +k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o= +k8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/code-generator v0.0.0-20190713022532-93d7507fc8ff/go.mod h1:mdlckYc+qIcm9LO4wF2aMreloe3RdtmWtJWN0a5jC7w= +k8s.io/component-base v0.0.0-20190711104712-4ad84870f76c/go.mod h1:yHi8nnYgJn3NxM9NE+ohCGKltBvgqw69dHs0g+QGIPY= +k8s.io/gengo v0.0.0-20190116091435-f8a0810f38af/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.1 h1:RVgyDHY/kFKtLqh67NvEWIgkMneNoIrdkN0CxDSQc68= +k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a h1:2jUDc9gJja832Ftp+QbDV0tVhQHMISFn01els+2ZAcw= +k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a/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/structured-merge-diff v0.0.0-20190302045857-e85c7b244fd2/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= From 3638614f95ac2813ea52dc491ae77af669f6421d Mon Sep 17 00:00:00 2001 From: dweepgogia Date: Thu, 18 Jul 2019 16:34:40 -0400 Subject: [PATCH 09/20] [lib] refactoring the library - To implement a generic custom error type which is also compatible with upstream. --- cmd/verify.go | 5 +- pkg/validate/{csv_validator.go => csv.go} | 4 +- pkg/validate/serialize.go | 40 ++++---- pkg/validate/validator/error.go | 112 ++++++++++++++++++++++ pkg/validate/validator/interface.go | 15 --- pkg/validate/validator/validator.go | 47 +++------ 6 files changed, 153 insertions(+), 70 deletions(-) rename pkg/validate/{csv_validator.go => csv.go} (90%) create mode 100644 pkg/validate/validator/error.go delete mode 100644 pkg/validate/validator/interface.go diff --git a/cmd/verify.go b/cmd/verify.go index 25e6c6fff..0d8630e59 100644 --- a/cmd/verify.go +++ b/cmd/verify.go @@ -4,6 +4,8 @@ import ( "fmt" "github.com/dweepgogia/new-manifest-verification/pkg/validate" + "github.com/dweepgogia/new-manifest-verification/pkg/validate/validator" + "github.com/spf13/cobra" ) @@ -26,7 +28,8 @@ func verifyFunc(cmd *cobra.Command, args []string) { yamlFileName := args[0] - if err := validate.ValidateCSVManifest(yamlFileName); err != nil { + // TODO: return a pointer instead + if err := validate.ValidateCSVManifest(yamlFileName); err != (validator.Error{}) { fmt.Println(err) } } diff --git a/pkg/validate/csv_validator.go b/pkg/validate/csv.go similarity index 90% rename from pkg/validate/csv_validator.go rename to pkg/validate/csv.go index c9f8025c9..27c1ab800 100644 --- a/pkg/validate/csv_validator.go +++ b/pkg/validate/csv.go @@ -24,7 +24,7 @@ func (v *CSVValidator) Validate() (results []validator.ManifestResult) { return results } -func (v *CSVValidator) AddObjects(objs ...interface{}) error { +func (v *CSVValidator) AddObjects(objs ...interface{}) validator.Error { for _, o := range objs { switch t := o.(type) { case olm.ClusterServiceVersion: @@ -33,7 +33,7 @@ func (v *CSVValidator) AddObjects(objs ...interface{}) error { v.csvs = append(v.csvs, *t) } } - return nil + return validator.Error{} } func (v CSVValidator) Name() string { diff --git a/pkg/validate/serialize.go b/pkg/validate/serialize.go index de4c6419d..f4b4d0ddd 100644 --- a/pkg/validate/serialize.go +++ b/pkg/validate/serialize.go @@ -16,21 +16,21 @@ import ( // ValidateCSVManifest takes in name of the yaml file to be validated, reads // it, and calls the unmarshal function on rawYaml. -func ValidateCSVManifest(yamlFileName string) error { +func ValidateCSVManifest(yamlFileName string) validator.Error { rawYaml, err := ioutil.ReadFile(yamlFileName) if err != nil { - return fmt.Errorf("Error in reading %s file: #%s ", yamlFileName, err) + return validator.IOError(fmt.Sprintf("Error in reading %s file: #%s ", yamlFileName, err), yamlFileName) } // Value returned is a marshaled go type (CSV Struct). csv, err := unmarshal(rawYaml) if err != nil { - return fmt.Errorf("Error unmarshalling YAML to OLM's csv type for %s file: #%s ", yamlFileName, err) + return validator.InvalidParse(fmt.Sprintf("Error unmarshalling YAML to OLM's csv type for %s file: #%s ", yamlFileName, err), yamlFileName) } v := &CSVValidator{} - if err = v.AddObjects(csv); err != nil { - return err + if err := v.AddObjects(csv); err != (validator.Error{}) { + return err // TODO: update when 'AddObjects' returns an actual error. } fmt.Println("Running", v.Name()) for _, errorLog := range v.Validate() { @@ -42,15 +42,16 @@ func ValidateCSVManifest(yamlFileName string) error { if errorLog.Errors != nil { fmt.Println() getErrorsFromManifestResult(errorLog.Errors) - return fmt.Errorf("Populate all the mandatory fields missing from CSV %s.", csv.GetName()) + fmt.Printf("Populate all the mandatory fields missing from CSV %s.", csv.GetName()) + return validator.Error{} } } fmt.Printf("%s is verified.\n", yamlFileName) - return nil + return validator.Error{} } // Iterates over the list of warnings and errors. -func getErrorsFromManifestResult(err []validator.MissingTypeError) { +func getErrorsFromManifestResult(err []validator.Error) { for _, v := range err { assertTypeToGetValue(v) } @@ -58,8 +59,8 @@ func getErrorsFromManifestResult(err []validator.MissingTypeError) { // Asserts type to get the underlying field value. func assertTypeToGetValue(v interface{}) { - if v, ok := v.(validator.MissingTypeError); ok { - fmt.Println(v) + if v, ok := v.(validator.Error); ok { + fmt.Println(v.String()) } } @@ -75,10 +76,10 @@ func unmarshal(rawYAML []byte) (olm.ClusterServiceVersion, error) { rawJson, err := yaml.YAMLToJSON(rawYAML) if err != nil { fmt.Printf("error parsing raw YAML to Json: %s", err) - return csv, err + return olm.ClusterServiceVersion{}, err } if err := json.Unmarshal(rawJson, &csv); err != nil { - return csv, fmt.Errorf("error parsing CSV list (JSON) : %s", err) + return olm.ClusterServiceVersion{}, fmt.Errorf("error parsing CSV list (JSON) : %s", err) } return csv, nil @@ -93,9 +94,10 @@ func csvInspect(val interface{}) validator.ManifestResult { case reflect.Struct: return checkMissingFields(fieldValue, "", validator.ManifestResult{}) default: - errs := []validator.MissingTypeError{ - {Err: errors.New("Error: input file is not a valid CSV.")}, + errs := []validator.Error{ + validator.InvalidCSV("Error: input file is not a valid CSV."), } + return validator.ManifestResult{Errors: errs, Warnings: nil} } } @@ -154,12 +156,14 @@ func checkMissingFields(v reflect.Value, parentStructName string, log validator. func updateLog(log validator.ManifestResult, typeName string, newParentStructName string, emptyVal bool, isOptionalField bool) validator.ManifestResult { if emptyVal && isOptionalField { - err := errors.Errorf("Warning: Optional %s Missing.", typeName) - log.Warnings = append(log.Warnings, validator.MissingTypeError{err, typeName, newParentStructName, false}) + err := errors.Errorf("Warning: Optional %s Missing", typeName) + // TODO: update the value field (typeName). + log.Warnings = append(log.Warnings, validator.OptionalFieldMissing(newParentStructName, typeName, err.Error())) } else if emptyVal && !isOptionalField { if newParentStructName != "Status" { - err := errors.Errorf("Error: Mandatory %s Missing.", typeName) - log.Errors = append(log.Errors, validator.MissingTypeError{err, typeName, newParentStructName, true}) + err := errors.Errorf("Error: Mandatory %s Missing", typeName) + // TODO: update the value field (typeName). + log.Errors = append(log.Errors, validator.MandatoryFieldMissing(newParentStructName, typeName, err.Error())) } } return log diff --git a/pkg/validate/validator/error.go b/pkg/validate/validator/error.go new file mode 100644 index 000000000..960608891 --- /dev/null +++ b/pkg/validate/validator/error.go @@ -0,0 +1,112 @@ +package validator + +import ( + "fmt" +) + +// ManifestResult represents verification result for each of the yaml files +// from the operator manifest. +type ManifestResult struct { + // Name is some piece of information identifying the manifest. This should + // usually be set to object.GetName(). + Name string + // Errors pertain to issues with the manifest that must be corrected. + Errors []Error + // Warnings pertain to issues with the manifest that are optional to correct. + Warnings []Error +} + +// Error is an implementation of the 'error' interface, which represents a +// warning or an error in a yaml file. Error type is taken as is from +// https://github.com/operator-framework/operator-registry/blob/master/vendor/k8s.io/apimachinery/pkg/util/validation/field/errors.go#L31 +// to maintain compatibility with upstream. +type Error struct { + // Type is the ErrorType string constant that represents the kind of + // error, ex. "MandatoryStructMissing", "I/O". + Type ErrorType + // Field is the dot-hierarchical YAML path of the missing data. + Field string + // BadValue is the field or file that caused an error or warning. + BadValue interface{} + // Detail represents the error message as a string. + Detail string +} + +func (err Error) String() string { + return fmt.Sprintf("Detail: %s | Error type: %s | Value: %v | Field: %s", err.Detail, err.Type.String(), err.BadValue, err.Field) +} + +type ErrorType string + +func InvalidCSV(detail string) Error { + return Error{ErrorInvalidCSV, "", "", detail} +} + +func OptionalFieldMissing(field string, value interface{}, detail string) Error { + return Error{WarningFieldMissing, field, value, detail} +} + +func MandatoryFieldMissing(field string, value interface{}, detail string) Error { + return Error{ErrorFieldMissing, field, value, detail} +} + +func UnsupportedType(detail string) Error { + return Error{ErrorUnsupportedType, "", "", detail} +} + +// TODO: see if more information can be extracted out of 'unmarshall/parsing' errors. +func InvalidParse(detail string, value interface{}) Error { + return Error{ErrorInvalidParse, "", value, detail} +} + +func IOError(detail string, value interface{}) Error { + return Error{ErrorIO, "", value, detail} +} + +func FailedValidation(detail string, value interface{}) Error { + return Error{ErrorFailedValidation, "", value, detail} +} + +func InvalidOperation(detail string) Error { + return Error{ErrorInvalidOperation, "", "", detail} +} + +const ( + ErrorInvalidCSV ErrorType = "CSVFileNotValid" + WarningFieldMissing ErrorType = "OptionalFieldNotFound" + ErrorFieldMissing ErrorType = "MandatoryFieldNotFound" + ErrorUnsupportedType ErrorType = "FieldTypeNotSupported" + ErrorInvalidParse ErrorType = "Unmarshall/ParseError" + ErrorIO ErrorType = "FileReadError" + ErrorFailedValidation ErrorType = "ValidationFailed" + ErrorInvalidOperation ErrorType = "OperationFailed" +) + +// String converts a ErrorType into its corresponding canonical error message. +func (t ErrorType) String() string { + switch t { + case ErrorInvalidCSV: + return "CSV file not valid" + case WarningFieldMissing: + return "Optional field not found" + case ErrorFieldMissing: + return "Mandatory field not found" + case ErrorUnsupportedType: + return "Field type not supported" + case ErrorInvalidParse: + return "Unmarshall/Parse error" + case ErrorIO: + return "File read error" + case ErrorFailedValidation: + return "Validation failed" + case ErrorInvalidOperation: + return "Operation failed" + default: + panic(fmt.Sprintf("Unrecognized validation error: %q", string(t))) + } +} + +// Error strut implements the 'error' interface to define custom error formatting. +func (err Error) Error() string { + return err.Detail +} diff --git a/pkg/validate/validator/interface.go b/pkg/validate/validator/interface.go deleted file mode 100644 index d34cd3ee4..000000000 --- a/pkg/validate/validator/interface.go +++ /dev/null @@ -1,15 +0,0 @@ -package validator - -// Validator is an interface for implementing a validator of a single -// Kubernetes object type. Ideally each Validator will check one aspect of -// an object, or perform several steps that have a common theme or goal. -type Validator interface { - // Validate should run validation logic on an arbitrary object, and return - // a one ManifestResult for each object that did not pass validation. - Validate() []ManifestResult - // AddObjects adds objects to the Validator. Each object will be validated - // when Validate() is called. - AddObjects(...interface{}) error - // Name should return a succinct name for this validator. - Name() string -} diff --git a/pkg/validate/validator/validator.go b/pkg/validate/validator/validator.go index 99e903450..58c200afc 100644 --- a/pkg/validate/validator/validator.go +++ b/pkg/validate/validator/validator.go @@ -1,39 +1,18 @@ package validator -import "fmt" - -// ManifestResult represents verification result for each of the yaml files -// from the manifest bundle. -type ManifestResult struct { - // Name is some piece of information identifying the manifest. This should - // usually be set to object.GetName(). - Name string - // Errors pertain to issues with the manifest that must be corrected. - Errors []MissingTypeError - // Warnings pertain to issues with the manifest that are optional to correct. - Warnings []MissingTypeError -} - -// MissingTypeError represents a warning or an error in a yaml file. -type MissingTypeError struct { - // Err is the underlying error caused by a missing type, if any. - Err error - // TypeName is the syntactical name of missing data, ex. Struct, Field. - TypeName string - // Path is the dot-hierarchical YAML path of the missing data. - Path string - // IsMandatory determines whether the missing data should generate a - // warning (false, the default) or error (true). - IsMandatory bool -} - -// MissingTypeError strut implements the Error interface to define custom error formatting. -func (err MissingTypeError) Error() string { - if err.IsMandatory { - return fmt.Sprintf("Error: Mandatory %s Missing (%s)", err.TypeName, err.Path) - } else { - return fmt.Sprintf("Warning: Optional %s Missing (%s)", err.TypeName, err.Path) - } +// Validator is an interface for implementing a validator of a single +// Kubernetes object type. Ideally each Validator will check one aspect of +// an object, or perform several steps that have a common theme or goal. +type Validator interface { + // Validate should run validation logic on an arbitrary object, and return + // a one ManifestResult for each object that did not pass validation. + // TODO: use pointers + Validate() []ManifestResult + // AddObjects adds objects to the Validator. Each object will be validated + // when Validate() is called. + AddObjects(...interface{}) Error + // Name should return a succinct name for this validator. + Name() string } // ValidatorSet contains a set of Validators to be executed sequentially. From 5efb42a1ecbeb6e72b001951a960e3c8f2713a9a Mon Sep 17 00:00:00 2001 From: dweepgogia Date: Fri, 26 Jul 2019 14:31:24 -0400 Subject: [PATCH 10/20] [lib] refactor logic for csv validation - To have all the csv checks in one file (csv_validator.go). --- pkg/validate/csv.go | 124 ++++++++++++++++++++++++++++++++++++++ pkg/validate/serialize.go | 122 ------------------------------------- 2 files changed, 124 insertions(+), 122 deletions(-) diff --git a/pkg/validate/csv.go b/pkg/validate/csv.go index 27c1ab800..85ee99605 100644 --- a/pkg/validate/csv.go +++ b/pkg/validate/csv.go @@ -1,7 +1,13 @@ package validate import ( + "encoding/json" + "fmt" + "reflect" + "strings" + "github.com/dweepgogia/new-manifest-verification/pkg/validate/validator" + "github.com/pkg/errors" olm "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" ) @@ -39,3 +45,121 @@ func (v *CSVValidator) AddObjects(objs ...interface{}) validator.Error { func (v CSVValidator) Name() string { return "ClusterServiceVersion Validator" } + +// Iterates over the given CSV. Returns a ManifestResult type object. +func csvInspect(val interface{}) validator.ManifestResult { + + fieldValue := reflect.ValueOf(val) + + switch fieldValue.Kind() { + case reflect.Struct: + return checkMissingFields(fieldValue, "", validator.ManifestResult{}) + default: + errs := []validator.Error{ + validator.InvalidCSV("Error: input file is not a valid CSV."), + } + + return validator.ManifestResult{Errors: errs, Warnings: nil} + } +} + +// Recursive function that traverses a nested struct passed in as reflect value, and reports for errors/warnings +// in case of null struct field values. +func checkMissingFields(v reflect.Value, parentStructName string, log validator.ManifestResult) validator.ManifestResult { + + for i := 0; i < v.NumField(); i++ { + + fieldValue := v.Field(i) + + tag := v.Type().Field(i).Tag.Get("json") + // Ignore fields that are subsets of a primitive field. + if tag == "" { + continue + } + + fields := strings.Split(tag, ",") + isOptionalField := containsStrict(fields, "omitempty") + emptyVal := isEmptyValue(fieldValue) + + newParentStructName := "" + if parentStructName == "" { + newParentStructName = v.Type().Field(i).Name + } else { + newParentStructName = parentStructName + "." + v.Type().Field(i).Name + } + + switch fieldValue.Kind() { + case reflect.Struct: + log = updateLog(log, "Struct", newParentStructName, emptyVal, isOptionalField) + if emptyVal { + continue + } + log = checkMissingFields(fieldValue, newParentStructName, log) + default: + log = updateLog(log, "Field", newParentStructName, emptyVal, isOptionalField) + } + } + return log +} + +// Returns updated error log with missing optional/mandatory field/struct objects. +func updateLog(log validator.ManifestResult, typeName string, newParentStructName string, emptyVal bool, isOptionalField bool) validator.ManifestResult { + + if emptyVal && isOptionalField { + err := errors.Errorf("Warning: Optional %s Missing", typeName) + // TODO: update the value field (typeName). + log.Warnings = append(log.Warnings, validator.OptionalFieldMissing(newParentStructName, typeName, err.Error())) + } else if emptyVal && !isOptionalField { + if newParentStructName != "Status" { + err := errors.Errorf("Error: Mandatory %s Missing", typeName) + // TODO: update the value field (typeName). + log.Errors = append(log.Errors, validator.MandatoryFieldMissing(newParentStructName, typeName, err.Error())) + } + } + return log +} + +// Takes in a string slice and checks if a string (x) is present in the slice. +// Return true if the string is present in the slice. +func containsStrict(a []string, x string) bool { + for _, n := range a { + if x == n { + return true + } + } + return false +} + +// Uses reflect package to check if the value of the object passed is null, returns a boolean accordingly. +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + // Check if the value for 'Spec.InstallStrategy.StrategySpecRaw' field is present. This field is a RawMessage value type. Without a value, the key is explicitly set to 'null'. + if fieldValue, ok := v.Interface().(json.RawMessage); ok { + valString := string(fieldValue) + if valString == "null" { + return true + } + } + return v.Len() == 0 + // Currently the only CSV field with integer type is containerPort. Operator Verification Library raises a warning if containerPort field is missisng or if its value is 0. + // It is an optional field so the user can ignore the warning saying this field is missing if they intend to use port 0. + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Struct: + for i, n := 0, v.NumField(); i < n; i++ { + if !isEmptyValue(v.Field(i)) { + return false + } + } + return true + default: + panic(fmt.Sprintf("%v kind is not supported.", v.Kind())) + } +} diff --git a/pkg/validate/serialize.go b/pkg/validate/serialize.go index f4b4d0ddd..094bf8fad 100644 --- a/pkg/validate/serialize.go +++ b/pkg/validate/serialize.go @@ -4,14 +4,11 @@ import ( "encoding/json" "fmt" "io/ioutil" - "reflect" - "strings" "github.com/dweepgogia/new-manifest-verification/pkg/validate/validator" "github.com/ghodss/yaml" olm "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" - "github.com/pkg/errors" ) // ValidateCSVManifest takes in name of the yaml file to be validated, reads @@ -81,124 +78,5 @@ func unmarshal(rawYAML []byte) (olm.ClusterServiceVersion, error) { if err := json.Unmarshal(rawJson, &csv); err != nil { return olm.ClusterServiceVersion{}, fmt.Errorf("error parsing CSV list (JSON) : %s", err) } - return csv, nil } - -// Iterates over the given CSV. Returns a ManifestResult type object. -func csvInspect(val interface{}) validator.ManifestResult { - - fieldValue := reflect.ValueOf(val) - - switch fieldValue.Kind() { - case reflect.Struct: - return checkMissingFields(fieldValue, "", validator.ManifestResult{}) - default: - errs := []validator.Error{ - validator.InvalidCSV("Error: input file is not a valid CSV."), - } - - return validator.ManifestResult{Errors: errs, Warnings: nil} - } -} - -// Takes in a string slice and checks if a string (x) is present in the slice. -// Return true if the string is present in the slice. -func containsStrict(a []string, x string) bool { - for _, n := range a { - if x == n { - return true - } - } - return false -} - -// Recursive function that traverses a nested struct passed in as reflect value, and reports for errors/warnings -// in case of null struct field values. -func checkMissingFields(v reflect.Value, parentStructName string, log validator.ManifestResult) validator.ManifestResult { - - for i := 0; i < v.NumField(); i++ { - - fieldValue := v.Field(i) - - tag := v.Type().Field(i).Tag.Get("json") - // Ignore fields that are subsets of a primitive field. - if tag == "" { - continue - } - - fields := strings.Split(tag, ",") - isOptionalField := containsStrict(fields, "omitempty") - emptyVal := isEmptyValue(fieldValue) - - newParentStructName := "" - if parentStructName == "" { - newParentStructName = v.Type().Field(i).Name - } else { - newParentStructName = parentStructName + "." + v.Type().Field(i).Name - } - - switch fieldValue.Kind() { - case reflect.Struct: - log = updateLog(log, "Struct", newParentStructName, emptyVal, isOptionalField) - if emptyVal { - continue - } - log = checkMissingFields(fieldValue, newParentStructName, log) - default: - log = updateLog(log, "Field", newParentStructName, emptyVal, isOptionalField) - } - } - return log -} - -// Returns updated error log with missing optional/mandatory field/struct objects. -func updateLog(log validator.ManifestResult, typeName string, newParentStructName string, emptyVal bool, isOptionalField bool) validator.ManifestResult { - - if emptyVal && isOptionalField { - err := errors.Errorf("Warning: Optional %s Missing", typeName) - // TODO: update the value field (typeName). - log.Warnings = append(log.Warnings, validator.OptionalFieldMissing(newParentStructName, typeName, err.Error())) - } else if emptyVal && !isOptionalField { - if newParentStructName != "Status" { - err := errors.Errorf("Error: Mandatory %s Missing", typeName) - // TODO: update the value field (typeName). - log.Errors = append(log.Errors, validator.MandatoryFieldMissing(newParentStructName, typeName, err.Error())) - } - } - return log -} - -// Uses reflect package to check if the value of the object passed is null, returns a boolean accordingly. -func isEmptyValue(v reflect.Value) bool { - switch v.Kind() { - case reflect.Array, reflect.Map, reflect.Slice, reflect.String: - // Check if the value for 'Spec.InstallStrategy.StrategySpecRaw' field is present. This field is a RawMessage value type. Without a value, the key is explicitly set to 'null'. - if fieldValue, ok := v.Interface().(json.RawMessage); ok { - valString := string(fieldValue) - if valString == "null" { - return true - } - } - return v.Len() == 0 - // Currently the only CSV field with integer type is containerPort. Operator Verification Library raises a warning if containerPort field is missisng or if its value is 0. - // It is an optional field so the user can ignore the warning saying this field is missing if they intend to use port 0. - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return v.Int() == 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return v.Uint() == 0 - case reflect.Float32, reflect.Float64: - return v.Float() == 0 - case reflect.Interface, reflect.Ptr: - return v.IsNil() - case reflect.Struct: - for i, n := 0, v.NumField(); i < n; i++ { - if !isEmptyValue(v.Field(i)) { - return false - } - } - return true - default: - panic(fmt.Sprintf("%v kind is not supported.", v.Kind())) - } -} From 03c49b58ac430d271796cf1ac5b61fe7538afab6 Mon Sep 17 00:00:00 2001 From: dweepgogia Date: Wed, 7 Aug 2019 17:37:13 -0400 Subject: [PATCH 11/20] [cli] change from verify to manifest command - To avoid redundancy while using the cli (operator-verify manifest) instead of (operator-verify verify). --- cmd/verify.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/cmd/verify.go b/cmd/verify.go index 0d8630e59..e11dd7edb 100644 --- a/cmd/verify.go +++ b/cmd/verify.go @@ -4,8 +4,6 @@ import ( "fmt" "github.com/dweepgogia/new-manifest-verification/pkg/validate" - "github.com/dweepgogia/new-manifest-verification/pkg/validate/validator" - "github.com/spf13/cobra" ) @@ -14,7 +12,7 @@ func init() { } var verifyCmd = &cobra.Command{ - Use: "verify", + Use: "manifest", Short: "Validate YAML against OLM's CSV type.", Long: `Verifies the yaml file against Operator-Lifecycle-Manager's ClusterServiceVersion type. Reports errors for any mismatched data types. Takes in one argument i.e. path to the yaml file. Version: 1.0`, Run: verifyFunc, @@ -26,10 +24,7 @@ func verifyFunc(cmd *cobra.Command, args []string) { fmt.Printf("command %s requires exactly one argument", cmd.CommandPath()) } - yamlFileName := args[0] + manifestDirectory := args[0] - // TODO: return a pointer instead - if err := validate.ValidateCSVManifest(yamlFileName); err != (validator.Error{}) { - fmt.Println(err) - } + _ = validate.ValidateManifest(manifestDirectory) } From a5bb22e598fac306cce32346acf7b3999a6c486b Mon Sep 17 00:00:00 2001 From: dweepgogia Date: Wed, 7 Aug 2019 17:41:53 -0400 Subject: [PATCH 12/20] [lib] add support for handling manifest - To verify the operator manifest. --- pkg/validate/serialize.go | 119 ++++++++++++++++++++++---------- pkg/validate/validator/error.go | 47 +++++++++---- 2 files changed, 116 insertions(+), 50 deletions(-) diff --git a/pkg/validate/serialize.go b/pkg/validate/serialize.go index 094bf8fad..9d84771a0 100644 --- a/pkg/validate/serialize.go +++ b/pkg/validate/serialize.go @@ -1,50 +1,48 @@ package validate import ( - "encoding/json" "fmt" "io/ioutil" "github.com/dweepgogia/new-manifest-verification/pkg/validate/validator" - - "github.com/ghodss/yaml" - olm "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" ) -// ValidateCSVManifest takes in name of the yaml file to be validated, reads -// it, and calls the unmarshal function on rawYaml. -func ValidateCSVManifest(yamlFileName string) validator.Error { - rawYaml, err := ioutil.ReadFile(yamlFileName) +func Validate(v validator.Validator) (manifestResult validator.ManifestResult) { + fmt.Printf("\nRunning %s\n", v.Name()) + fmt.Printf("Validating %s\n\n", v.FileName()) + rawYaml, err := ioutil.ReadFile(v.FileName()) if err != nil { - return validator.IOError(fmt.Sprintf("Error in reading %s file: #%s ", yamlFileName, err), yamlFileName) + manifestResult.Errors = append(manifestResult.Errors, validator.IOError(fmt.Sprintf("Error in reading %s file: #%s ", v.FileName(), err), v.FileName())) + getErrorsFromManifestResult(manifestResult.Errors) + return } - // Value returned is a marshaled go type (CSV Struct). - csv, err := unmarshal(rawYaml) + // Value returned is a marshaled go type. + unmarshalledObject, err := v.Unmarshal(rawYaml) if err != nil { - return validator.InvalidParse(fmt.Sprintf("Error unmarshalling YAML to OLM's csv type for %s file: #%s ", yamlFileName, err), yamlFileName) + manifestResult.Errors = append(manifestResult.Errors, validator.InvalidParse(fmt.Sprintf("Error unmarshalling YAML for %s file: #%s ", v.FileName(), err), v.FileName())) + getErrorsFromManifestResult(manifestResult.Errors) + return } - v := &CSVValidator{} - if err := v.AddObjects(csv); err != (validator.Error{}) { - return err // TODO: update when 'AddObjects' returns an actual error. + if err := v.AddObjects(unmarshalledObject); err != (validator.Error{}) { + manifestResult.Errors = append(manifestResult.Errors, err) + getErrorsFromManifestResult(manifestResult.Errors) + return // TODO: update when 'AddObjects' returns an actual error. } - fmt.Println("Running", v.Name()) + for _, errorLog := range v.Validate() { - fmt.Println("Validating CSV", errorLog.Name) getErrorsFromManifestResult(errorLog.Warnings) - // There is no mandatory field thats missing if errorLog.errors is nil. - if errorLog.Errors != nil { + if len(errorLog.Errors) != 0 { fmt.Println() getErrorsFromManifestResult(errorLog.Errors) - fmt.Printf("Populate all the mandatory fields missing from CSV %s.", csv.GetName()) - return validator.Error{} + } else { + fmt.Printf("\n%s is verified\n", v.FileName()) } } - fmt.Printf("%s is verified.\n", yamlFileName) - return validator.Error{} + return } // Iterates over the list of warnings and errors. @@ -61,22 +59,69 @@ func assertTypeToGetValue(v interface{}) { } } -// Unmarshal takes in a raw YAML file and deserializes it to OLM's ClusterServiceVersion type. -// Throws an error if: -// (1) the yaml file can not be converted to json. -// (2) there is a problem while unmarshalling in go type. -// Returns an object of type olm.ClusterServiceVersion. -func unmarshal(rawYAML []byte) (olm.ClusterServiceVersion, error) { +func validateBundle(manifest Manifest) []validator.ManifestResult { + v := &BundleValidator{Manifest: manifest} + manifestResult := v.Validate() + for _, errorLog := range manifestResult { + fmt.Printf("\nValidating `%s` Manifest\n", errorLog.Name) + fmt.Println() + if len(errorLog.Warnings) != 0 { + getErrorsFromManifestResult(errorLog.Warnings) + } + + if len(errorLog.Errors) != 0 { + fmt.Println() + getErrorsFromManifestResult(errorLog.Errors) + fmt.Printf("Invalid manifest: `%s`\n", errorLog.Name) + } else { + fmt.Printf("`%s` manifest verified", errorLog.Name) + } + } + return manifestResult +} - var csv olm.ClusterServiceVersion +func parseManifestDirectory(manifestDirectory string) (Manifest, []validator.ManifestResult) { + manifestResultList := []validator.ManifestResult{} + fmt.Printf("Parsing `%s` operator manifest\n\n", manifestDirectory) + manifest, manifestResultFromDirectoryParse := ParseDir(manifestDirectory) - rawJson, err := yaml.YAMLToJSON(rawYAML) - if err != nil { - fmt.Printf("error parsing raw YAML to Json: %s", err) - return olm.ClusterServiceVersion{}, err + if len(manifestResultFromDirectoryParse.Errors) != 0 || len(manifestResultFromDirectoryParse.Warnings) != 0 { + manifestResultList = append(manifestResultList, manifestResultFromDirectoryParse) + getErrorsFromManifestResult(manifestResultFromDirectoryParse.Warnings) + if len(manifestResultFromDirectoryParse.Errors) != 0 { + getErrorsFromManifestResult(manifestResultFromDirectoryParse.Errors) + fmt.Printf("Invalid operator manifest structure for `%s`\n", manifestDirectory) + return Manifest{}, manifestResultList + } } - if err := json.Unmarshal(rawJson, &csv); err != nil { - return olm.ClusterServiceVersion{}, fmt.Errorf("error parsing CSV list (JSON) : %s", err) + return manifest, manifestResultList +} + +func ValidateManifest(manifestDirectory string) []validator.ManifestResult { + // parse manifest directory + manifest, manifestResultList := parseManifestDirectory(manifestDirectory) + for _, manifestResult := range manifestResultList { + if len(manifestResult.Errors) != 0 { + return manifestResultList + } } - return csv, nil + + var result []validator.ManifestResult + // validate individual bundle files + for _, bundle := range manifest.Bundle { + validators := []validator.Validator{&CSVValidator{fileName: bundle.CSV}} + for _, crd := range bundle.CRDs { + validators = append(validators, &CRDValidator{fileName: crd}) + } + for _, validator := range validators { + result = append(result, Validate(validator)) + } + } + var pkgValidator validator.Validator + pkgValidator = &PackageValidator{fileName: manifest.Package} + Validate(pkgValidator) + + // validate bundle + validateBundle(manifest) + return []validator.ManifestResult{} } diff --git a/pkg/validate/validator/error.go b/pkg/validate/validator/error.go index 960608891..a52637dc4 100644 --- a/pkg/validate/validator/error.go +++ b/pkg/validate/validator/error.go @@ -33,20 +33,28 @@ type Error struct { } func (err Error) String() string { - return fmt.Sprintf("Detail: %s | Error type: %s | Value: %v | Field: %s", err.Detail, err.Type.String(), err.BadValue, err.Field) + return err.Error() } type ErrorType string +func InvalidBundle(detail string, value interface{}) Error { + return Error{ErrorInvalidBundle, "", value, detail} +} + +func InvalidManifestStructure(detail string) Error { + return Error{ErrorInvalidManifestStructure, "", "", detail} +} + func InvalidCSV(detail string) Error { return Error{ErrorInvalidCSV, "", "", detail} } -func OptionalFieldMissing(field string, value interface{}, detail string) Error { +func OptionalFieldMissing(detail string, field string, value interface{}) Error { return Error{WarningFieldMissing, field, value, detail} } -func MandatoryFieldMissing(field string, value interface{}, detail string) Error { +func MandatoryFieldMissing(detail string, field string, value interface{}) Error { return Error{ErrorFieldMissing, field, value, detail} } @@ -59,6 +67,10 @@ func InvalidParse(detail string, value interface{}) Error { return Error{ErrorInvalidParse, "", value, detail} } +func InvalidDefaultChannel(detail string, value interface{}) Error { + return Error{ErrorInvalidDefaultChannel, "", value, detail} +} + func IOError(detail string, value interface{}) Error { return Error{ErrorIO, "", value, detail} } @@ -67,19 +79,22 @@ func FailedValidation(detail string, value interface{}) Error { return Error{ErrorFailedValidation, "", value, detail} } -func InvalidOperation(detail string) Error { - return Error{ErrorInvalidOperation, "", "", detail} +func InvalidOperation(detail string, value interface{}) Error { + return Error{ErrorInvalidOperation, "", value, detail} } const ( - ErrorInvalidCSV ErrorType = "CSVFileNotValid" - WarningFieldMissing ErrorType = "OptionalFieldNotFound" - ErrorFieldMissing ErrorType = "MandatoryFieldNotFound" - ErrorUnsupportedType ErrorType = "FieldTypeNotSupported" - ErrorInvalidParse ErrorType = "Unmarshall/ParseError" - ErrorIO ErrorType = "FileReadError" - ErrorFailedValidation ErrorType = "ValidationFailed" - ErrorInvalidOperation ErrorType = "OperationFailed" + ErrorInvalidCSV ErrorType = "CSVFileNotValid" + WarningFieldMissing ErrorType = "OptionalFieldNotFound" + ErrorFieldMissing ErrorType = "MandatoryFieldNotFound" + ErrorUnsupportedType ErrorType = "FieldTypeNotSupported" + ErrorInvalidParse ErrorType = "Unmarshall/ParseError" + ErrorIO ErrorType = "FileReadError" + ErrorFailedValidation ErrorType = "ValidationFailed" + ErrorInvalidOperation ErrorType = "OperationFailed" + ErrorInvalidManifestStructure ErrorType = "ManifestStructureNotValid" + ErrorInvalidBundle ErrorType = "BundleNotValid" + ErrorInvalidDefaultChannel ErrorType = "DefaultChannelNotValid" ) // String converts a ErrorType into its corresponding canonical error message. @@ -101,6 +116,12 @@ func (t ErrorType) String() string { return "Validation failed" case ErrorInvalidOperation: return "Operation failed" + case ErrorInvalidManifestStructure: + return "Manifest directory structure not valid" + case ErrorInvalidBundle: + return "Manifest bundle not valid" + case ErrorInvalidDefaultChannel: + return "Default channel not valid" default: panic(fmt.Sprintf("Unrecognized validation error: %q", string(t))) } From 968a8b4da3ee57a1195665c6fd586f51d5ac4167 Mon Sep 17 00:00:00 2001 From: dweepgogia Date: Wed, 7 Aug 2019 17:44:33 -0400 Subject: [PATCH 13/20] [lib] csv validator for example annotations - To compare example annotations to the provided APIs list and report appropriate errors. --- cmd/operator-verify/csv.yaml | 292 ------------------ cmd/operator-verify/dataTypeMismatch.csv.yaml | 290 ----------------- pkg/validate/csv.go | 182 ++++++++++- 3 files changed, 165 insertions(+), 599 deletions(-) delete mode 100644 cmd/operator-verify/csv.yaml delete mode 100644 cmd/operator-verify/dataTypeMismatch.csv.yaml diff --git a/cmd/operator-verify/csv.yaml b/cmd/operator-verify/csv.yaml deleted file mode 100644 index 4dad39663..000000000 --- a/cmd/operator-verify/csv.yaml +++ /dev/null @@ -1,292 +0,0 @@ -#! validate-crd: deploy/chart/templates/0000_30_02-clusterserviceversion.crd.yaml -#! parse-kind: ClusterServiceVersion -apiVersion: operators.coreos.com/v1alpha1 -kind: ClusterServiceVersion -metadata: - name: etcdoperator.v0.9.0 - namespace: placeholder - annotations: - capabilities: Full Lifecycle - tectonic-visibility: ocs - alm-examples: '[{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdCluster","metadata":{"name":"example","namespace":"default"},"spec":{"size":3,"version":"3.2.13"}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdRestore","metadata":{"name":"example-etcd-cluster"},"spec":{"etcdCluster":{"name":"example-etcd-cluster"},"backupStorageType":"S3","s3":{"path":"","awsSecret":""}}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdBackup","metadata":{"name":"example-etcd-cluster-backup"},"spec":{"etcdEndpoints":[""],"storageType":"S3","s3":{"path":"","awsSecret":""}}}]' - description: etcd is a distributed key value store providing a reliable way to store data across a cluster of machines. -spec: - displayName: etcd - description: | - etcd is a distributed key value store that provides a reliable way to store data across a cluster of machines. It’s open-source and available on GitHub. etcd gracefully handles leader elections during network partitions and will tolerate machine failure, including the leader. Your applications can read and write data into etcd. - A simple use-case is to store database connection details or feature flags within etcd as key value pairs. These values can be watched, allowing your app to reconfigure itself when they change. Advanced uses take advantage of the consistency guarantees to implement database leader elections or do distributed locking across a cluster of workers. - - _The etcd Open Cloud Service is Public Alpha. The goal before Beta is to fully implement backup features._ - - ### Reading and writing to etcd - - Communicate with etcd though its command line utility `etcdctl` or with the API using the automatically generated Kubernetes Service. - - [Read the complete guide to using the etcd Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/etcd-ocs.html) - - ### Supported Features - - - **High availability** - - - Multiple instances of etcd are networked together and secured. Individual failures or networking issues are transparently handled to keep your cluster up and running. - - - **Automated updates** - - - Rolling out a new etcd version works like all Kubernetes rolling updates. Simply declare the desired version, and the etcd service starts a safe rolling update to the new version automatically. - - - **Backups included** - - - Coming soon, the ability to schedule backups to happen on or off cluster. - keywords: ['etcd', 'key value', 'database', 'coreos', 'open source'] - version: 0.9.0 - maturity: alpha - maintainers: - - name: CoreOS, Inc - email: support@coreos.com - - provider: - name: CoreOS, Inc - labels: - alm-owner-etcd: etcdoperator - operated-by: etcdoperator - selector: - matchLabels: - alm-owner-etcd: etcdoperator - operated-by: etcdoperator - links: - - name: Blog - url: https://coreos.com/etcd - - name: Documentation - url: https://coreos.com/operators/etcd/docs/latest/ - - name: etcd Operator Source Code - url: https://github.com/coreos/etcd-operator - - icon: - - base64data: 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 - mediatype: image/png - installModes: - - type: OwnNamespace - supported: true - - type: SingleNamespace - supported: true - - type: MultiNamespace - supported: false - - type: AllNamespaces - supported: true - install: - strategy: deployment - spec: - permissions: - - serviceAccountName: etcd-operator - rules: - - apiGroups: - - etcd.database.coreos.com - resources: - - etcdclusters - - etcdbackups - - etcdrestores - verbs: - - "*" - - apiGroups: - - "" - resources: - - pods - - services - - endpoints - - persistentvolumeclaims - - events - verbs: - - "*" - - apiGroups: - - apps - resources: - - deployments - verbs: - - "*" - - apiGroups: - - "" - resources: - - secrets - verbs: - - get - deployments: - - name: etcd-operator - spec: - replicas: 1 - selector: - matchLabels: - name: etcd-operator-alm-owned - template: - metadata: - name: etcd-operator-alm-owned - labels: - name: etcd-operator-alm-owned - spec: - serviceAccountName: etcd-operator - containers: - - name: etcd-operator - command: - - etcd-operator - - --create-crd=false - image: quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8 - env: - - name: MY_POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: MY_POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: etcd-backup-operator - image: quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8 - command: - - etcd-backup-operator - - --create-crd=false - env: - - name: MY_POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: MY_POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: etcd-restore-operator - image: quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8 - command: - - etcd-restore-operator - - --create-crd=false - env: - - name: MY_POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: MY_POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - customresourcedefinitions: - owned: - - name: etcdclusters.etcd.database.coreos.com - version: v1beta2 - kind: EtcdCluster - displayName: etcd Cluster - description: Represents a cluster of etcd nodes. - resources: - - kind: Service - version: v1 - - kind: Pod - version: v1 - specDescriptors: - - description: The desired number of member Pods for the etcd cluster. - displayName: Size - path: size - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:podCount' - - description: Limits describes the minimum/maximum amount of compute resources required/allowed - displayName: Resource Requirements - path: pod.resources - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:resourceRequirements' - statusDescriptors: - - description: The status of each of the member Pods for the etcd cluster. - displayName: Member Status - path: members - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:podStatuses' - - description: The service at which the running etcd cluster can be accessed. - displayName: Service - path: serviceName - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes:Service' - - description: The current size of the etcd cluster. - displayName: Cluster Size - path: size - - description: The current version of the etcd cluster. - displayName: Current Version - path: currentVersion - - description: 'The target version of the etcd cluster, after upgrading.' - displayName: Target Version - path: targetVersion - - description: The current status of the etcd cluster. - displayName: Status - path: phase - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes.phase' - - description: Explanation for the current status of the cluster. - displayName: Status Details - path: reason - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes.phase:reason' - - name: etcdbackups.etcd.database.coreos.com - version: v1beta2 - kind: EtcdBackup - displayName: etcd Backup - description: Represents the intent to backup an etcd cluster. - specDescriptors: - - description: Specifies the endpoints of an etcd cluster. - displayName: etcd Endpoint(s) - path: etcdEndpoints - x-descriptors: - - 'urn:alm:descriptor:etcd:endpoint' - - description: The full AWS S3 path where the backup is saved. - displayName: S3 Path - path: s3.path - x-descriptors: - - 'urn:alm:descriptor:aws:s3:path' - - description: The name of the secret object that stores the AWS credential and config files. - displayName: AWS Secret - path: s3.awsSecret - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes:Secret' - statusDescriptors: - - description: Indicates if the backup was successful. - displayName: Succeeded - path: succeeded - x-descriptors: - - 'urn:alm:descriptor:text' - - description: Indicates the reason for any backup related failures. - displayName: Reason - path: reason - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes.phase:reason' - - name: etcdrestores.etcd.database.coreos.com - version: v1beta2 - kind: EtcdRestore - displayName: etcd Restore - description: Represents the intent to restore an etcd cluster from a backup. - specDescriptors: - - description: References the EtcdCluster which should be restored, - displayName: etcd Cluster - path: etcdCluster.name - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes:EtcdCluster' - - 'urn:alm:descriptor:text' - - description: The full AWS S3 path where the backup is saved. - displayName: S3 Path - path: s3.path - x-descriptors: - - 'urn:alm:descriptor:aws:s3:path' - - description: The name of the secret object that stores the AWS credential and config files. - displayName: AWS Secret - path: s3.awsSecret - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes:Secret' - statusDescriptors: - - description: Indicates if the restore was successful. - displayName: Succeeded - path: succeeded - x-descriptors: - - 'urn:alm:descriptor:text' - - description: Indicates the reason for any restore related failures. - displayName: Reason - path: reason - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes.phase:reason' \ No newline at end of file diff --git a/cmd/operator-verify/dataTypeMismatch.csv.yaml b/cmd/operator-verify/dataTypeMismatch.csv.yaml deleted file mode 100644 index a915b115c..000000000 --- a/cmd/operator-verify/dataTypeMismatch.csv.yaml +++ /dev/null @@ -1,290 +0,0 @@ -#! validate-crd: deploy/chart/templates/0000_30_02-clusterserviceversion.crd.yaml -#! parse-kind: ClusterServiceVersion -apiVersion: operators.coreos.com/v1alpha1 -kind: ClusterServiceVersion -metadata: - name: etcdoperator.v0.9.0 - namespace: placeholder - annotations: - capabilities: Full Lifecycle - tectonic-visibility: ocs - alm-examples: '[{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdCluster","metadata":{"name":"example","namespace":"default"},"spec":{"size":3,"version":"3.2.13"}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdRestore","metadata":{"name":"example-etcd-cluster"},"spec":{"etcdCluster":{"name":"example-etcd-cluster"},"backupStorageType":"S3","s3":{"path":"","awsSecret":""}}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdBackup","metadata":{"name":"example-etcd-cluster-backup"},"spec":{"etcdEndpoints":[""],"storageType":"S3","s3":{"path":"","awsSecret":""}}}]' - description: etcd is a distributed key value store providing a reliable way to store data across a cluster of machines. -spec: - displayName: etcd - description: | - etcd is a distributed key value store that provides a reliable way to store data across a cluster of machines. It’s open-source and available on GitHub. etcd gracefully handles leader elections during network partitions and will tolerate machine failure, including the leader. Your applications can read and write data into etcd. - A simple use-case is to store database connection details or feature flags within etcd as key value pairs. These values can be watched, allowing your app to reconfigure itself when they change. Advanced uses take advantage of the consistency guarantees to implement database leader elections or do distributed locking across a cluster of workers. - - _The etcd Open Cloud Service is Public Alpha. The goal before Beta is to fully implement backup features._ - - ### Reading and writing to etcd - - Communicate with etcd though its command line utility `etcdctl` or with the API using the automatically generated Kubernetes Service. - - [Read the complete guide to using the etcd Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/etcd-ocs.html) - - ### Supported Features - - - **High availability** - - - Multiple instances of etcd are networked together and secured. Individual failures or networking issues are transparently handled to keep your cluster up and running. - - - **Automated updates** - - - Rolling out a new etcd version works like all Kubernetes rolling updates. Simply declare the desired version, and the etcd service starts a safe rolling update to the new version automatically. - - - **Backups included** - - - Coming soon, the ability to schedule backups to happen on or off cluster. - keywords: ['etcd', 'key value', 'database', 'coreos', 'open source'] - version: 0.9.0 - maturity: alpha - maintainers: testing_with_incorrect_data_type - - provider: - name: CoreOS, Inc - labels: - alm-owner-etcd: etcdoperator - operated-by: etcdoperator - selector: - matchLabels: - alm-owner-etcd: etcdoperator - operated-by: etcdoperator - links: - - name: Blog - url: https://coreos.com/etcd - - name: Documentation - url: https://coreos.com/operators/etcd/docs/latest/ - - name: etcd Operator Source Code - url: https://github.com/coreos/etcd-operator - - icon: - - base64data: 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 - mediatype: image/png - installModes: - - type: OwnNamespace - supported: true - - type: SingleNamespace - supported: true - - type: MultiNamespace - supported: false - - type: AllNamespaces - supported: true - install: - strategy: deployment - spec: - permissions: - - serviceAccountName: etcd-operator - rules: - - apiGroups: - - etcd.database.coreos.com - resources: - - etcdclusters - - etcdbackups - - etcdrestores - verbs: - - "*" - - apiGroups: - - "" - resources: - - pods - - services - - endpoints - - persistentvolumeclaims - - events - verbs: - - "*" - - apiGroups: - - apps - resources: - - deployments - verbs: - - "*" - - apiGroups: - - "" - resources: - - secrets - verbs: - - get - deployments: - - name: etcd-operator - spec: - replicas: 1 - selector: - matchLabels: - name: etcd-operator-alm-owned - template: - metadata: - name: etcd-operator-alm-owned - labels: - name: etcd-operator-alm-owned - spec: - serviceAccountName: etcd-operator - containers: - - name: etcd-operator - command: - - etcd-operator - - --create-crd=false - image: quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8 - env: - - name: MY_POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: MY_POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: etcd-backup-operator - image: quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8 - command: - - etcd-backup-operator - - --create-crd=false - env: - - name: MY_POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: MY_POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: etcd-restore-operator - image: quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8 - command: - - etcd-restore-operator - - --create-crd=false - env: - - name: MY_POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: MY_POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - customresourcedefinitions: - owned: - - name: etcdclusters.etcd.database.coreos.com - version: v1beta2 - kind: EtcdCluster - displayName: etcd Cluster - description: Represents a cluster of etcd nodes. - resources: - - kind: Service - version: v1 - - kind: Pod - version: v1 - specDescriptors: - - description: The desired number of member Pods for the etcd cluster. - displayName: Size - path: size - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:podCount' - - description: Limits describes the minimum/maximum amount of compute resources required/allowed - displayName: Resource Requirements - path: pod.resources - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:resourceRequirements' - statusDescriptors: - - description: The status of each of the member Pods for the etcd cluster. - displayName: Member Status - path: members - x-descriptors: - - 'urn:alm:descriptor:com.tectonic.ui:podStatuses' - - description: The service at which the running etcd cluster can be accessed. - displayName: Service - path: serviceName - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes:Service' - - description: The current size of the etcd cluster. - displayName: Cluster Size - path: size - - description: The current version of the etcd cluster. - displayName: Current Version - path: currentVersion - - description: 'The target version of the etcd cluster, after upgrading.' - displayName: Target Version - path: targetVersion - - description: The current status of the etcd cluster. - displayName: Status - path: phase - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes.phase' - - description: Explanation for the current status of the cluster. - displayName: Status Details - path: reason - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes.phase:reason' - - name: etcdbackups.etcd.database.coreos.com - version: v1beta2 - kind: EtcdBackup - displayName: etcd Backup - description: Represents the intent to backup an etcd cluster. - specDescriptors: - - description: Specifies the endpoints of an etcd cluster. - displayName: etcd Endpoint(s) - path: etcdEndpoints - x-descriptors: - - 'urn:alm:descriptor:etcd:endpoint' - - description: The full AWS S3 path where the backup is saved. - displayName: S3 Path - path: s3.path - x-descriptors: - - 'urn:alm:descriptor:aws:s3:path' - - description: The name of the secret object that stores the AWS credential and config files. - displayName: AWS Secret - path: s3.awsSecret - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes:Secret' - statusDescriptors: - - description: Indicates if the backup was successful. - displayName: Succeeded - path: succeeded - x-descriptors: - - 'urn:alm:descriptor:text' - - description: Indicates the reason for any backup related failures. - displayName: Reason - path: reason - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes.phase:reason' - - name: etcdrestores.etcd.database.coreos.com - version: v1beta2 - kind: EtcdRestore - displayName: etcd Restore - description: Represents the intent to restore an etcd cluster from a backup. - specDescriptors: - - description: References the EtcdCluster which should be restored, - displayName: etcd Cluster - path: etcdCluster.name - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes:EtcdCluster' - - 'urn:alm:descriptor:text' - - description: The full AWS S3 path where the backup is saved. - displayName: S3 Path - path: s3.path - x-descriptors: - - 'urn:alm:descriptor:aws:s3:path' - - description: The name of the secret object that stores the AWS credential and config files. - displayName: AWS Secret - path: s3.awsSecret - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes:Secret' - statusDescriptors: - - description: Indicates if the restore was successful. - displayName: Succeeded - path: succeeded - x-descriptors: - - 'urn:alm:descriptor:text' - - description: Indicates the reason for any restore related failures. - displayName: Reason - path: reason - x-descriptors: - - 'urn:alm:descriptor:io.kubernetes.phase:reason' \ No newline at end of file diff --git a/pkg/validate/csv.go b/pkg/validate/csv.go index 85ee99605..e89eb3d75 100644 --- a/pkg/validate/csv.go +++ b/pkg/validate/csv.go @@ -7,20 +7,21 @@ import ( "strings" "github.com/dweepgogia/new-manifest-verification/pkg/validate/validator" - "github.com/pkg/errors" - - olm "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" + "github.com/ghodss/yaml" + "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apimachinery/pkg/runtime/schema" ) type CSVValidator struct { - csvs []olm.ClusterServiceVersion + fileName string + csvs []v1alpha1.ClusterServiceVersion } var _ validator.Validator = &CSVValidator{} func (v *CSVValidator) Validate() (results []validator.ManifestResult) { for _, csv := range v.csvs { - // Contains error logs for all missing optional and mandatory fields. result := csvInspect(csv) if result.Name == "" { result.Name = csv.GetName() @@ -33,9 +34,9 @@ func (v *CSVValidator) Validate() (results []validator.ManifestResult) { func (v *CSVValidator) AddObjects(objs ...interface{}) validator.Error { for _, o := range objs { switch t := o.(type) { - case olm.ClusterServiceVersion: + case v1alpha1.ClusterServiceVersion: v.csvs = append(v.csvs, t) - case *olm.ClusterServiceVersion: + case *v1alpha1.ClusterServiceVersion: v.csvs = append(v.csvs, *t) } } @@ -46,17 +47,41 @@ func (v CSVValidator) Name() string { return "ClusterServiceVersion Validator" } +func (v CSVValidator) FileName() string { + return v.fileName +} + +func (v CSVValidator) Unmarshal(rawYaml []byte) (interface{}, error) { + var csv v1alpha1.ClusterServiceVersion + + rawJson, err := yaml.YAMLToJSON(rawYaml) + if err != nil { + return v1alpha1.ClusterServiceVersion{}, fmt.Errorf("error parsing raw YAML to Json: %s", err) + } + if err := json.Unmarshal(rawJson, &csv); err != nil { + return v1alpha1.ClusterServiceVersion{}, fmt.Errorf("error parsing CSV (JSON) : %s", err) + } + return csv, nil +} + // Iterates over the given CSV. Returns a ManifestResult type object. -func csvInspect(val interface{}) validator.ManifestResult { +func csvInspect(csv v1alpha1.ClusterServiceVersion) validator.ManifestResult { - fieldValue := reflect.ValueOf(val) + // validate example annotations ("alm-examples", "olm.examples"). + manifestResult := validateExamplesAnnotations(csv) + + // validate installModes + manifestResult = validateInstallModes(csv, manifestResult) + + // check missing optional/mandatory fields. + fieldValue := reflect.ValueOf(csv) switch fieldValue.Kind() { case reflect.Struct: - return checkMissingFields(fieldValue, "", validator.ManifestResult{}) + return checkMissingFields(fieldValue, "", manifestResult) default: errs := []validator.Error{ - validator.InvalidCSV("Error: input file is not a valid CSV."), + validator.InvalidCSV("Error: input file is not a valid CSV"), } return validator.ManifestResult{Errors: errs, Warnings: nil} @@ -90,13 +115,13 @@ func checkMissingFields(v reflect.Value, parentStructName string, log validator. switch fieldValue.Kind() { case reflect.Struct: - log = updateLog(log, "Struct", newParentStructName, emptyVal, isOptionalField) + log = updateLog(log, "struct", newParentStructName, emptyVal, isOptionalField) if emptyVal { continue } log = checkMissingFields(fieldValue, newParentStructName, log) default: - log = updateLog(log, "Field", newParentStructName, emptyVal, isOptionalField) + log = updateLog(log, "field", newParentStructName, emptyVal, isOptionalField) } } return log @@ -106,14 +131,12 @@ func checkMissingFields(v reflect.Value, parentStructName string, log validator. func updateLog(log validator.ManifestResult, typeName string, newParentStructName string, emptyVal bool, isOptionalField bool) validator.ManifestResult { if emptyVal && isOptionalField { - err := errors.Errorf("Warning: Optional %s Missing", typeName) // TODO: update the value field (typeName). - log.Warnings = append(log.Warnings, validator.OptionalFieldMissing(newParentStructName, typeName, err.Error())) + log.Warnings = append(log.Warnings, validator.OptionalFieldMissing(fmt.Sprintf("Warning: optional %s missing: (%s)", typeName, newParentStructName), newParentStructName, typeName)) } else if emptyVal && !isOptionalField { if newParentStructName != "Status" { - err := errors.Errorf("Error: Mandatory %s Missing", typeName) // TODO: update the value field (typeName). - log.Errors = append(log.Errors, validator.MandatoryFieldMissing(newParentStructName, typeName, err.Error())) + log.Errors = append(log.Errors, validator.MandatoryFieldMissing(fmt.Sprintf("Error: mandatory %s missing: (%s)", typeName, newParentStructName), newParentStructName, typeName)) } } return log @@ -163,3 +186,128 @@ func isEmptyValue(v reflect.Value) bool { panic(fmt.Sprintf("%v kind is not supported.", v.Kind())) } } + +// validateExamplesAnnotations compares alm/olm example annotations with provided APIs given +// by Spec.CustomResourceDefinitions.Owned and Spec.APIServiceDefinitions.Owned. +func validateExamplesAnnotations(csv v1alpha1.ClusterServiceVersion) (manifestResult validator.ManifestResult) { + var examples []v1beta1.CustomResourceDefinition + var annotationsExamples string + annotations := csv.ObjectMeta.GetAnnotations() + // Return right away if no examples annotations are found. + if len(annotations) == 0 { + manifestResult.Warnings = append(manifestResult.Warnings, validator.InvalidCSV(fmt.Sprintf("Warning: example annotations not found for %s csv", csv.GetName()))) + return + } + // Expect either `alm-examples` or `olm.examples` but not both + // If both are present, `alm-examples` will be used + if value, ok := annotations["alm-examples"]; ok { + annotationsExamples = value + if _, ok = annotations["olm.examples"]; ok { + // both `alm-examples` and `olm.examples` are present + manifestResult.Warnings = append(manifestResult.Warnings, validator.InvalidCSV(fmt.Sprintf("Warning: both `alm-examples` and `olm.examples` are present in %s CSV. Defaulting to `alm-examples` and ignoring `olm.examples`", csv.GetName()))) + } + } else { + annotationsExamples = annotations["olm.examples"] + } + + // Can't find examples annotations, simply return + if annotationsExamples == "" { + manifestResult.Warnings = append(manifestResult.Warnings, validator.InvalidCSV(fmt.Sprintf("Warning: example annotations not found for %s csv", csv.GetName()))) + return + } + + if err := json.Unmarshal([]byte(annotationsExamples), &examples); err != nil { + manifestResult = getManifestResult(validator.InvalidParse(fmt.Sprintf("Error: parsing example annotations to %T type: %s ", examples, err), nil)) + return + } + + providedAPIs, manRes := getProvidedAPIs(csv, manifestResult) + + parsedExamples, manRes := parseExamplesAnnotations(examples, manifestResult) + if len(manRes.Errors) != 0 || len(manRes.Warnings) != 0 { + return manRes + } + + return matchGVKProvidedAPIs(parsedExamples, providedAPIs, manifestResult) +} + +func getProvidedAPIs(csv v1alpha1.ClusterServiceVersion, manifestResult validator.ManifestResult) (map[schema.GroupVersionKind]struct{}, validator.ManifestResult) { + provided := map[schema.GroupVersionKind]struct{}{} + + for _, owned := range csv.Spec.CustomResourceDefinitions.Owned { + parts := strings.SplitN(owned.Name, ".", 2) + if len(parts) < 2 { + manifestResult.Errors = append(manifestResult.Errors, validator.InvalidParse(fmt.Sprintf("Error: couldn't parse plural.group from crd name: %s", owned.Name), owned.Name)) + continue + } + provided[schema.GroupVersionKind{Group: parts[1], Version: owned.Version, Kind: owned.Kind}] = struct{}{} + } + + for _, api := range csv.Spec.APIServiceDefinitions.Owned { + provided[schema.GroupVersionKind{Group: api.Group, Version: api.Version, Kind: api.Kind}] = struct{}{} + } + + return provided, manifestResult +} + +func parseExamplesAnnotations(examples []v1beta1.CustomResourceDefinition, manifestResult validator.ManifestResult) (map[schema.GroupVersionKind]struct{}, validator.ManifestResult) { + parsed := map[schema.GroupVersionKind]struct{}{} + for _, value := range examples { + parts := strings.SplitN(value.APIVersion, "/", 2) + if len(parts) < 2 { + manifestResult.Errors = append(manifestResult.Errors, validator.InvalidParse(fmt.Sprintf("Error: couldn't parse group/version from crd kind: %s", value.Kind), value.Kind)) + continue + } + parsed[schema.GroupVersionKind{Group: parts[0], Version: parts[1], Kind: value.Kind}] = struct{}{} + } + + return parsed, manifestResult +} + +func matchGVKProvidedAPIs(examples map[schema.GroupVersionKind]struct{}, providedAPIs map[schema.GroupVersionKind]struct{}, manifestResult validator.ManifestResult) validator.ManifestResult { + for key := range examples { + if _, ok := providedAPIs[key]; !ok { + manifestResult.Errors = append(manifestResult.Errors, validator.InvalidOperation(fmt.Sprintf("Error: couldn't match %v in provided APIs list: %v", key, providedAPIs), key)) + continue + } + } + return manifestResult +} + +func getManifestResult(errs ...validator.Error) validator.ManifestResult { + errList := append([]validator.Error{}, errs...) + return validator.ManifestResult{Errors: errList, Warnings: nil} +} + +func validateInstallModes(csv v1alpha1.ClusterServiceVersion, manifestResult validator.ManifestResult) validator.ManifestResult { + // var installModeSet v1alpha1.InstallModeSet + installModeSet := make(v1alpha1.InstallModeSet) + for _, installMode := range csv.Spec.InstallModes { + if _, ok := installModeSet[installMode.Type]; ok { + manifestResult.Errors = append(manifestResult.Errors, validator.InvalidCSV(fmt.Sprintf("Error: duplicate install modes present in %s csv", csv.GetName()))) + } else { + installModeSet[installMode.Type] = installMode.Supported + } + } + + // installModes not found, return with a warning + if len(installModeSet) == 0 { + manifestResult.Warnings = append(manifestResult.Warnings, validator.InvalidCSV(fmt.Sprintf("Warning: install modes not found for %s csv", csv.GetName()))) + return manifestResult + } + + // all installModes should not be `false` + if checkAllFalseForInstallModeSet(installModeSet) { + manifestResult.Errors = append(manifestResult.Errors, validator.InvalidCSV(fmt.Sprintf("Error: none of InstallModeTypes are supported for %s csv", csv.GetName()))) + } + return manifestResult +} + +func checkAllFalseForInstallModeSet(installModeSet v1alpha1.InstallModeSet) bool { + for _, isSupported := range installModeSet { + if isSupported { + return false + } + } + return true +} From ffa1afa7b58752b3c860e9f922649b95ee246bbd Mon Sep 17 00:00:00 2001 From: dweepgogia Date: Wed, 7 Aug 2019 17:47:35 -0400 Subject: [PATCH 14/20] [lib] parse operator manifest directory - To check for the manifest format and validate the manifest and individual operator files. --- go.mod | 72 ++++++++- go.sum | 334 +++++++++++++++++++++++++++++++---------- pkg/validate/parser.go | 158 +++++++++++++++++++ 3 files changed, 479 insertions(+), 85 deletions(-) create mode 100644 pkg/validate/parser.go diff --git a/go.mod b/go.mod index 47af2ad78..eee77bb29 100644 --- a/go.mod +++ b/go.mod @@ -2,12 +2,72 @@ module github.com/dweepgogia/new-manifest-verification go 1.12 +replace ( + // Pin openshift version to 4.2 (uses kube 1.14) + github.com/openshift/api => github.com/openshift/api v3.9.1-0.20190717200738-0390d1e77d64+incompatible + github.com/openshift/client-go => github.com/openshift/client-go v0.0.0-20190627172412-c44a8b61b9f4 + + // Pin kube version to 1.14 + k8s.io/api => k8s.io/api v0.0.0-20190704095032-f4ca3d3bdf1d + k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.0.0-20190704104557-6209bbe9f7a9 + k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190704094733-8f6ac2502e51 + k8s.io/apiserver => k8s.io/apiserver v0.0.0-20190704101451-e5f5c6e528cd + k8s.io/client-go => k8s.io/client-go v11.0.1-0.20190521191137-11646d1007e0+incompatible + k8s.io/code-generator => k8s.io/code-generator v0.0.0-20190704094409-6c2a4329ac29 + k8s.io/component-base => k8s.io/component-base v0.0.0-20190704100636-f0322db00a10 + k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.0.0-20190704101955-e796fd6d55e0 + k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30 + k8s.io/kubernetes => k8s.io/kubernetes v1.14.5-beta.0.0.20190708100021-7936da50c68f + sigs.k8s.io/structured-merge-diff => sigs.k8s.io/structured-merge-diff v0.0.0-20190302045857-e85c7b244fd2 +) + require ( + github.com/blang/semver v3.5.1+incompatible + github.com/coreos/go-semver v0.3.0 + github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a // indirect + github.com/docker/distribution v2.7.1+incompatible // indirect + github.com/emicklei/go-restful v2.9.6+incompatible // indirect + github.com/evanphx/json-patch v4.5.0+incompatible // indirect github.com/ghodss/yaml v1.0.0 - github.com/operator-framework/operator-lifecycle-manager v3.11.0+incompatible - github.com/pkg/errors v0.8.0 - github.com/spf13/cobra v0.0.4 - k8s.io/apiextensions-apiserver v0.0.0-20190713023830-b4b8a3310a45 // indirect - k8s.io/apimachinery v0.0.0-20190715170309-6171873045ff // indirect - k8s.io/client-go v11.0.0+incompatible // indirect + github.com/go-openapi/spec v0.19.2 + github.com/go-openapi/validate v0.19.2 // indirect + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b + github.com/golang/mock v1.3.1 + github.com/google/btree v1.0.0 // indirect + github.com/google/gofuzz v1.0.0 // indirect + github.com/googleapis/gnostic v0.3.0 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.9.4 // indirect + github.com/json-iterator/go v1.1.6 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/maxbrunsfeld/counterfeiter/v6 v6.2.1 + github.com/mitchellh/hashstructure v1.0.0 + github.com/munnerz/goautoneg v0.0.0-20190414153302-2ae31c8b6b30 // indirect + github.com/openshift/api v0.0.0-00010101000000-000000000000 + github.com/openshift/client-go v0.0.0-00010101000000-000000000000 + github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190125151539-1e295784b30a + github.com/operator-framework/operator-registry v1.1.1 + github.com/pkg/errors v0.8.1 + github.com/prometheus/client_golang v0.9.2 + github.com/sirupsen/logrus v1.4.2 + github.com/spf13/cobra v0.0.5 + github.com/stretchr/testify v1.3.0 + go.uber.org/atomic v1.4.0 // indirect + go.uber.org/zap v1.10.0 // indirect + golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 + gonum.org/v1/gonum v0.0.0-20190710053202-4340aa3071a0 // indirect + google.golang.org/grpc v1.22.0 + k8s.io/api v0.0.0-20190717022910-653c86b0609b + k8s.io/apiextensions-apiserver v0.0.0-20181204003618-e419c5771cdc + k8s.io/apimachinery v0.0.0-20190717022731-0bb8574e0887 + k8s.io/apiserver v0.0.0-20181026151315-13cfe3978170 + k8s.io/client-go v8.0.0+incompatible + k8s.io/code-generator v0.0.0-20181203235156-f8cba74510f3 + k8s.io/component-base v0.0.0-20190717023551-b4f50308a616 + k8s.io/gengo v0.0.0-20190327210449-e17681d19d3a // indirect + k8s.io/kube-aggregator v0.0.0-20181204002017-122bac39d429 + k8s.io/kube-openapi v0.0.0-20181031203759-72693cb1fadd + k8s.io/kubernetes v1.11.8-beta.0.0.20190124204751-3a10094374f2 + k8s.io/utils v0.0.0-20190712204705-3dccf664f023 // indirect + sigs.k8s.io/structured-merge-diff v0.0.0-00010101000000-000000000000 // indirect + sigs.k8s.io/yaml v1.1.0 // indirect ) diff --git a/go.sum b/go.sum index c8826460c..4c0369c7a 100644 --- a/go.sum +++ b/go.sum @@ -1,255 +1,431 @@ +bitbucket.org/ww/goautoneg v0.0.0-20120707110453-75cd24fc2f2c/go.mod h1:1vhO7Mn/FZMgOgDVGLy5X1mE6rq1HbkBdkF/yj8zkcg= +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= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= 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/NYTimes/gziphandler v1.0.1/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +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/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.0/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.9+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.13+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 v0.0.0-20180117170138-065b426bd416/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/go-systemd v0.0.0-20181031085051-9002847aa142/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/docker/distribution v2.6.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -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/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/emicklei/go-restful v2.8.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.8.1+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.6+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful-swagger12 v0.0.0-20170926063155-7524189396c6/go.mod h1:qr0VowGBT4CS4Q8vFF8BSeKz34PuqKGxs/L0IAQA9DQ= +github.com/evanphx/json-patch v3.0.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb h1:D4uzjWwKYQ5XnAvUbuvHW93esHg7F8N/OYeBBcJoTr0= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= 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-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 h1:ophLETFestFZHk3ji7niPEL4d466QjW+0Tdg5VyDq7E= github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/errors v0.17.0 h1:g5DzIh94VpuR/dd6Ff8KqyHNnw7yBa2xSHIPPzjRDUo= 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 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY= github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= 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 h1:A9+F4Dc/MCNB5jibxf6rRvOvR/iFgQdyNx9eIhnGqq0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= 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 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= 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 h1:rf5ArTHmIJxyV5Oiks+Su0mUens1+AjpkPoWr5xFRcI= github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= +github.com/go-openapi/runtime v0.19.0 h1:sU6pp4dSV2sGlNKKyHxZzi1m1kG4WnYtWcJ+HYbygjE= github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.17.2/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 h1:SStNd1jRcYtfKCN7R0laGNs80WYYvn5CbBjM2sOmCrE= github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.17.2/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.18.0 h1:FqqmmVCKn3di+ilU/+1m957T1CnMz3IteVUcV3aGXWA= github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.19.0 h1:0Dn9qy1G9+UJfRU7TR8bmdGxb4uifB7HNrJjOnV0yPk= github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= 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 h1:jvO6bCMBEilGwMfHhrd61zIID4oIFdwb76V17SM88dE= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/validate v0.17.2/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.19.2 h1:ky5l57HjyVRrsJfd2+Ro5Z9PjGuKbsmftwyMtk8H7js= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= -github.com/gogo/protobuf v1.0.0 h1:2jyBKDKU/8v3v2xVR2PtiWQviFUyiaGk2rpfyFT8rTM= -github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +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.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 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-20180924190550-6f2cf27854a4 h1:6UVLWz0fIIrv0UVj6t0A7cL48n8IyAdLVQqAYzEfsKI= +github.com/golang/groupcache v0.0.0-20180924190550-6f2cf27854a4/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff h1:kOkM9whyQYodu09SJ6W3NCsHG7crFaJILQ22Gozp3lg= +github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/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/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= +github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.3.0 h1:CcQijm0XKekKjP/YCz28LXVSpgguuB+nCxaSjCe09y0= +github.com/googleapis/gnostic v0.3.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v0.0.0-20170330212424-2500245aa611/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20190203031600-7a902570cb17/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/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.5.1/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.6.3/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.9.4/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-health-probe v0.2.0/go.mod h1:4GVx/bTCtZaSzhjbGueDY5YgBdsmKeVx+LErv/n0L6s= +github.com/grpc-ecosystem/grpc-health-probe v0.2.1-0.20181220223928-2bf0a5b182db/go.mod h1:uBKkC2RbarFsvS5jMJHpVhTLvGlGQj9JJwkaePE3FWI= +github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 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 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= +github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= 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 h1:nTT4s92Dgz2HlrB2NaMgvlfqHH39OgMhA7z3PK7PGD4= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/maxbrunsfeld/counterfeiter v0.0.0-20181017030959-1aadac120687/go.mod h1:aoVsckWnsNzazwF2kmD+bzgdr4GBlbK91zsdivQJ2eU= +github.com/maxbrunsfeld/counterfeiter/v6 v6.2.1/go.mod h1:F9YacGpnZbLQMzuPI0rR6op21YvNu/RjL705LJJpM3k= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/munnerz/goautoneg v0.0.0-20190414153302-2ae31c8b6b30/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.2-0.20180831124310-ae19f1b56d53/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/openshift/api v3.9.1-0.20190717200738-0390d1e77d64+incompatible/go.mod h1:dh9o4Fs58gpFXGSYfnVxGR9PnV53I8TW84pQaJDdGiY= +github.com/openshift/client-go v0.0.0-20190627172412-c44a8b61b9f4/go.mod h1:6rzn+JTr7+WYS2E1TExP4gByoABxMznR6y2SnUIkmxk= +github.com/operator-framework/go-appr v0.0.0-20180917210448-f2aef88446f2/go.mod h1:YNzwUx1i6C4dXWcffyq3yaIb0rh/K8/OvQ4vG0SNlSw= +github.com/operator-framework/operator-lifecycle-manager v0.0.0-20181023032605-e838f7fb2186/go.mod h1:Ma5ZXd4S1vmMyewWlF7aO8CZiokR7Sd8dhSfkGkNU4U= +github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190105193533-81104ffdc4fb/go.mod h1:XMyE4n2opUK4N6L45YGQkXXi8F9fD7XDYFv/CsS6V5I= +github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190125151539-1e295784b30a h1:gBTaGobFQZVx+M2ItXxJp7oxoc9dltkLMrkgyDE0Qfc= +github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190125151539-1e295784b30a/go.mod h1:vq6TTFvg6ti1Bn6ACsZneZTmjTsURgDD6tQtVDbEgsU= github.com/operator-framework/operator-lifecycle-manager v3.11.0+incompatible h1:Po8C8RVLRWq7pNQ5pKonM9CXpC/osoBWbmsuf+HJnSI= github.com/operator-framework/operator-lifecycle-manager v3.11.0+incompatible/go.mod h1:Ma5ZXd4S1vmMyewWlF7aO8CZiokR7Sd8dhSfkGkNU4U= +github.com/operator-framework/operator-marketplace v0.0.0-20190216021216-57300a3ef3ba/go.mod h1:msZSL8pXwzQjB+hU+awVrZQw94IwJi3sNZVD3NoESIs= +github.com/operator-framework/operator-registry v1.0.1 h1:Z2155w77HzIkTrdp2qoY0QMkywxhJpuABUSGcgogXuc= +github.com/operator-framework/operator-registry v1.0.1/go.mod h1:1xEdZjjUg2hPEd52LG3YQ0jtwiwEGdm98S1TH5P4RAA= +github.com/operator-framework/operator-registry v1.0.4/go.mod h1:hve6YwcjM2nGVlscLtNsp9sIIBkNZo6jlJgzWw7vP9s= +github.com/operator-framework/operator-registry v1.1.1 h1:oDIevJvKXFsp7BEb7iJHuLvuhPZYBtIx5oZQ7iSISAs= +github.com/operator-framework/operator-registry v1.1.1/go.mod h1:7D4WEwL+EKti5npUh4/u64DQhawCBRugp8Ql20duUb4= +github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= 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/petar/GoLLRB v0.0.0-20130427215148-53be0d36a84c/go.mod h1:HUpKUBZnpzkdx0kD/+Yfuft+uD3zHGtXF/XJB14TUr4= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= 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 v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20190104105734-b1c43a6df3ae/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= +github.com/prometheus/procfs v0.0.0-20190104112138-b1a0a9a36d74/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +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/sclevine/spec v1.0.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= +github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= +github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +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.4 h1:S0tLZ3VOKl2Te0hpq8+ke0eSJPfCnNTPiDlsfwi1/NE= github.com/spf13/cobra v0.0.4/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +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 v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50/go.mod h1:1pdIZTAHUz+HDKDVZ++5xg/duPlhKAIzw9qy42CWYp4= 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 v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= +github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +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.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/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.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +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-20181015023909-0c41d7ab0a0e/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 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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-20180911220305-26e67e76b6c3/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-20181023162649-9b4f9f5ad519/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-20181201002055-351d144fa1fc/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 h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/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 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU= 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-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/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-20181105165119-ca4130e427c7 h1:g9UOdtsRWEwHYUG2bDHMxKrvfSGE5epIX2HkaMHSMBY= +golang.org/x/oauth2 v0.0.0-20181105165119-ca4130e427c7/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA= golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/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-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +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-20181023152157-44b849a8bc13/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 h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM= +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 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/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-20181011152555-a398e557df60/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181207222222-4c874b978acb/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-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +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= -gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= +golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +gonum.org/v1/gonum v0.0.0-20190710053202-4340aa3071a0/go.mod h1:03dgh78c4UvU1WksguQ/lvJQXbezKQGJSrwwRq5MraQ= 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/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 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20170731182057-09f6ed296fc6/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/grpc v1.13.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181016170114-94acd270e44e h1:I5s8aUkxqPjgAssfOv+dVr+4/7BC40WV6JhcVoORltI= +google.golang.org/genproto v0.0.0-20181016170114-94acd270e44e/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.16.0 h1:dz5IJGuC2BB7qXR5AyHNwAUBhZscK2xVez7mznh72sY= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.22.0/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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o= -gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.0.0-20170531160350-a96e63847dc3/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 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 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -k8s.io/api v0.0.0-20190712022805-31fe033ae6f9 h1:sbrvmfIQxEkFARFDPWY7DMwG+1cGcFwOzfACZbfFhY4= -k8s.io/api v0.0.0-20190712022805-31fe033ae6f9/go.mod h1:quCKyJONbDKoYS6YlndaZ0BUdM1jU4J8fCiC0S2ctLI= -k8s.io/apiextensions-apiserver v0.0.0-20190713023830-b4b8a3310a45 h1:PnGyTcZx6K/YcKsIHfH+VrcNryybh06OOALs9A1XY9U= -k8s.io/apiextensions-apiserver v0.0.0-20190713023830-b4b8a3310a45/go.mod h1:uPYx3KN9c9FtxeO2CEqTIztxbjMPlb0xdHeds0XZwZo= -k8s.io/apimachinery v0.0.0-20190711103026-7bf792636534/go.mod h1:M2fZgZL9DbLfeJaPBCDqSqNsdsmLN+V29knYJnIXlMA= -k8s.io/apimachinery v0.0.0-20190711222657-391ed67afa7b/go.mod h1:M2fZgZL9DbLfeJaPBCDqSqNsdsmLN+V29knYJnIXlMA= -k8s.io/apimachinery v0.0.0-20190712095106-75ce4d1e60f1/go.mod h1:M2fZgZL9DbLfeJaPBCDqSqNsdsmLN+V29knYJnIXlMA= -k8s.io/apimachinery v0.0.0-20190715170309-6171873045ff h1:xezFJNivtQCzq73n4MFKshwXX1ftSsxxK8k4BfVJ3X4= -k8s.io/apimachinery v0.0.0-20190715170309-6171873045ff/go.mod h1:M2fZgZL9DbLfeJaPBCDqSqNsdsmLN+V29knYJnIXlMA= -k8s.io/apiserver v0.0.0-20190712103608-7754ffe9aeba/go.mod h1:7VNJRtV6E+92bMQYfSvcpj9nbeXWUpu6mUhkyi7VIaA= -k8s.io/client-go v0.0.0-20190712102959-611184f7c43a/go.mod h1:fdQc9ByAiSeRWlHAp/NtRHzoYmdTblX8jATAi8iwHLU= -k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o= -k8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= -k8s.io/code-generator v0.0.0-20190713022532-93d7507fc8ff/go.mod h1:mdlckYc+qIcm9LO4wF2aMreloe3RdtmWtJWN0a5jC7w= -k8s.io/component-base v0.0.0-20190711104712-4ad84870f76c/go.mod h1:yHi8nnYgJn3NxM9NE+ohCGKltBvgqw69dHs0g+QGIPY= -k8s.io/gengo v0.0.0-20190116091435-f8a0810f38af/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/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.0.0-20190704095032-f4ca3d3bdf1d h1:X3GqeHwOBOJa0O7jrPobw6MGLMwthSnA/sM86ENzbCo= +k8s.io/api v0.0.0-20190704095032-f4ca3d3bdf1d/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/apiextensions-apiserver v0.0.0-20190704104557-6209bbe9f7a9 h1:t7SgiXt+POEBGV2vcmWbYz9JrWjmW+j3L/brBsbMGTQ= +k8s.io/apiextensions-apiserver v0.0.0-20190704104557-6209bbe9f7a9/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE= +k8s.io/apimachinery v0.0.0-20190704094733-8f6ac2502e51 h1:Nuz3yWD9v9dVvDiVSBIah+B+pxkxQxtuBAx2712t7tQ= +k8s.io/apimachinery v0.0.0-20190704094733-8f6ac2502e51/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/apiserver v0.0.0-20190704101451-e5f5c6e528cd h1:cZZGzLQgvZ2mJWGh3J8XF5oZYbRTSFYIFXh7fGtxyXs= +k8s.io/apiserver v0.0.0-20190704101451-e5f5c6e528cd/go.mod h1:6bqaTSOSJavUIXUtfaR9Os9JtTCm8ZqH2SUl2S60C4w= +k8s.io/client-go v11.0.1-0.20190521191137-11646d1007e0+incompatible h1:QaNvRzeHEah25zPuza7iFf/twiBa4HFWZXzjY/V94qo= +k8s.io/client-go v11.0.1-0.20190521191137-11646d1007e0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/code-generator v0.0.0-20190704094409-6c2a4329ac29/go.mod h1:MYiN+ZJZ9HkETbgVZdWw2AsuAi9PZ4V80cwfuf2axe8= +k8s.io/component-base v0.0.0-20190704100636-f0322db00a10/go.mod h1:DMaomcf3j3MM2j1FsvlLVVlc7wA2jPytEur3cP9zRxQ= +k8s.io/gengo v0.0.0-20181106084056-51747d6e00da/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20181113154421-fd15ee9cc2f7/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20190327210449-e17681d19d3a/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.1.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.3.1 h1:RVgyDHY/kFKtLqh67NvEWIgkMneNoIrdkN0CxDSQc68= -k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.3 h1:niceAagH1tzskmaie/icWd7ci1wbG7Bf2c6YGcQv+3c= +k8s.io/klog v0.3.3/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/kube-aggregator v0.0.0-20190704101955-e796fd6d55e0/go.mod h1:8sbzT4QQKDEmSCIbfqjV0sd97GpUT7A4W626sBiYJmU= +k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30 h1:TRb4wNWoBVrH9plmkp2q86FIDppkbrEXdXlxU3a3BMI= k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= -k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a h1:2jUDc9gJja832Ftp+QbDV0tVhQHMISFn01els+2ZAcw= -k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a/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= +k8s.io/kubernetes v1.14.5-beta.0.0.20190708100021-7936da50c68f/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/utils v0.0.0-20190712204705-3dccf664f023 h1:1H4Jyzb0z2X0GfBMTwRjnt5ejffRHrGftUgJcV/ZfDc= +k8s.io/utils v0.0.0-20190712204705-3dccf664f023/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20190801114015-581e00157fb1 h1:+ySTxfHnfzZb9ys375PXNlLhkJPLKgHajBU0N62BDvE= +k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +sigs.k8s.io/controller-runtime v0.1.10/go.mod h1:HFAYoOh6XMV+jKF1UjFwrknPbowfyHEHHRdJMf2jMX8= sigs.k8s.io/structured-merge-diff v0.0.0-20190302045857-e85c7b244fd2/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/pkg/validate/parser.go b/pkg/validate/parser.go new file mode 100644 index 000000000..da1e1b0e1 --- /dev/null +++ b/pkg/validate/parser.go @@ -0,0 +1,158 @@ +package validate + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/dweepgogia/new-manifest-verification/pkg/validate/validator" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/util/yaml" + yamlForUnmarshalStrict "sigs.k8s.io/yaml" +) + +// Manifest represents files in the operator manifest. +type Manifest struct { + Name string + // Package stores the name (path) of package yaml file. + Package string + // Bundle represents a directory of files with one `ClusterServiceVersion`. + Bundle map[string]ManifestBundle +} + +type ManifestBundle struct { + // Version stores the CSV version for the bundle. + Version string + // List of CustomResourceDefinition file names inside the bundle. + CRDs []string + // CSV file name in the bundle. + CSV string +} + +// getFileType identifies the file type and returns it as a string. +func getFileType(filePath string) (string, error) { + + rawYaml, err := ioutil.ReadFile(filePath) + if err != nil { + return "", fmt.Errorf("Error in reading %s file", filePath) + } + pkg := registry.PackageManifest{} + + if checkFileTypeWithUnmarshalStrict(rawYaml, &pkg) { + return "Package", nil + } + + kind, err := getKindFromFileBytes(rawYaml, filePath) + if err != nil { + return "", err + } + return kind, nil +} + +func getKindFromFileBytes(rawYaml []byte, filePath string) (string, error) { + u := unstructured.Unstructured{} + r := bytes.NewReader(rawYaml) + dec := yaml.NewYAMLOrJSONDecoder(r, 8) + // There is only one YAML doc if there are no more bytes to be read or EOF + // is hit. + if err := dec.Decode(&u); err == nil && r.Len() != 0 { + return "", fmt.Errorf("error getting TypeMeta from bytes: more than one manifest in bytes") + } else if err != nil && err != io.EOF { + return "", fmt.Errorf("error getting TypeMeta from bytes") + } + return u.GetKind(), nil +} + +func checkFileTypeWithUnmarshalStrict(rawYaml []byte, obj interface{}) bool { + if err := yamlForUnmarshalStrict.UnmarshalStrict(rawYaml, &obj); err != nil { + return false + } + return true +} + +// ParseDir walks through the operator manifest directory, checks its format, +// and populates the Manifest object with relevant file names. +func ParseDir(manifestDirectory string) (Manifest, validator.ManifestResult) { + + countPkg := 0 + manifest := Manifest{} + manifest.Name = manifestDirectory + visitedFile := map[string]struct{}{} + manifestResult := validator.ManifestResult{} + isManifestResultNameSet := false + manifest.Bundle = make(map[string]ManifestBundle) + // parse manifest directory structure + err := filepath.Walk(manifestDirectory, func(path string, f os.FileInfo, err error) error { + + // set manifest name + if !isManifestResultNameSet { + manifestResult.Name = filepath.Base(path) + isManifestResultNameSet = true + } + // create a manifest bundle for each version in the manifest + if f.IsDir() && path != manifestDirectory { + if _, ok := manifest.Bundle[path]; !ok { + bundle := ManifestBundle{} + bundle.Version = f.Name() + manifest.Bundle[path] = bundle + } + } else if !f.IsDir() { + fileType, err := getFileType(path) + if err != nil { + updateErr := fmt.Sprintf("Error: %s file may not be of ClusterServiceVersion, CustomResourceDefinition, or Package yaml type. If it is supposed to be ClusterServiceVersion or CustomResourceDefinition type, make sure the TypeMeta is correctly defined. If this is a package yaml, instead, make sure it follows the PackageManifest type definition", path) + manifestResult.Errors = append(manifestResult.Errors, validator.InvalidManifestStructure(updateErr)) + return nil + } + + directoryPath := filepath.Dir(path) + switch fileType { + case "ClusterServiceVersion": + if _, ok := visitedFile[directoryPath]; ok { + manifestResult.Errors = append(manifestResult.Errors, validator.InvalidManifestStructure(fmt.Sprintf("Error: more than one CSV in the bundle found at %s bundle", directoryPath))) + return nil + } else { + visitedFile[directoryPath] = struct{}{} + } + if bundleObj, ok := manifest.Bundle[directoryPath]; ok { + bundleObj.CSV = path + manifest.Bundle[directoryPath] = bundleObj + } else { + manifestResult.Errors = append(manifestResult.Errors, validator.InvalidManifestStructure(fmt.Sprintf("Error: %s file at %s path does not align with the operator manifest format", f.Name(), path))) + return nil + } + case "CustomResourceDefinition": + if bundleObj, ok := manifest.Bundle[directoryPath]; ok { + bundleObj.CRDs = append(bundleObj.CRDs, path) + manifest.Bundle[directoryPath] = bundleObj + } else { + manifestResult.Errors = append(manifestResult.Errors, validator.InvalidManifestStructure(fmt.Sprintf("Error: %s file at %s path does not align with the operator manifest format", f.Name(), path))) + return nil + } + case "Package": + countPkg++ + if countPkg > 1 { + manifestResult.Errors = append(manifestResult.Errors, validator.InvalidManifestStructure(fmt.Sprintf("Error: more than one package yaml file in the manifest; found at %s", path))) + return nil + } + manifest.Package = path + return nil + default: + // return a `Warning` for files other than CSV, CRD, package yaml present in the manifest. If required, we can keep a list of these file + // paths and remove them from the manifest. + manifestResult.Warnings = append(manifestResult.Warnings, validator.InvalidManifestStructure(fmt.Sprintf("Warning: %s file at %s path is not a ClusterServiceVersion, CustomResourceDefinition, or Package yaml type", f.Name(), path))) + } + } + return nil + }) + if err != nil { + fmt.Printf("walk error [%v]\n", err) + } + if countPkg == 0 { + manifestResult.Errors = append(manifestResult.Errors, validator.InvalidManifestStructure(fmt.Sprintf("Error: no package yaml in `%s` manifest", manifestDirectory))) + } + return manifest, manifestResult +} From c7ab3cb36990d9b4c4175c4b65474fc459588607 Mon Sep 17 00:00:00 2001 From: dweepgogia Date: Wed, 7 Aug 2019 17:51:51 -0400 Subject: [PATCH 15/20] [lib] add crd validator - To support validating the CRDs present in the manifest. --- pkg/validate/crd.go | 88 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 pkg/validate/crd.go diff --git a/pkg/validate/crd.go b/pkg/validate/crd.go new file mode 100644 index 000000000..e00e1fe9c --- /dev/null +++ b/pkg/validate/crd.go @@ -0,0 +1,88 @@ +package validate + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/dweepgogia/new-manifest-verification/pkg/validate/validator" + "github.com/ghodss/yaml" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime" +) + +type CRDValidator struct { + fileName string + crds []v1beta1.CustomResourceDefinition +} + +var _ validator.Validator = &CRDValidator{} + +func (v *CRDValidator) Validate() (results []validator.ManifestResult) { + for _, crd := range v.crds { + scheme := runtime.NewScheme() + result := crdInspect(crd, scheme) + if result.Name == "" { + result.Name = crd.GetName() + } + results = append(results, result) + } + return results +} + +func (v *CRDValidator) AddObjects(objs ...interface{}) validator.Error { + for _, o := range objs { + switch t := o.(type) { + case v1beta1.CustomResourceDefinition: + v.crds = append(v.crds, t) + case *v1beta1.CustomResourceDefinition: + v.crds = append(v.crds, *t) + } + } + return validator.Error{} +} + +func (v CRDValidator) Name() string { + return "CustomResourceDefinition Validator" +} + +func (v CRDValidator) FileName() string { + return v.fileName +} + +func (v CRDValidator) Unmarshal(rawYaml []byte) (interface{}, error) { + var crd v1beta1.CustomResourceDefinition + + rawJson, err := yaml.YAMLToJSON(rawYaml) + if err != nil { + return v1beta1.CustomResourceDefinition{}, fmt.Errorf("error parsing raw YAML to Json: %s", err) + } + if err := json.Unmarshal(rawJson, &crd); err != nil { + return v1beta1.CustomResourceDefinition{}, fmt.Errorf("error parsing CRD (JSON) : %s", err) + } + return crd, nil +} + +func crdInspect(crd v1beta1.CustomResourceDefinition, scheme *runtime.Scheme) (manifestResult validator.ManifestResult) { + err := apiextensions.AddToScheme(scheme) + if err != nil { + return + } + err = v1beta1.AddToScheme(scheme) + if err != nil { + return + } + unversionedCRD := apiextensions.CustomResourceDefinition{} + scheme.Converter().Convert(&crd, &unversionedCRD, conversion.SourceToDest, nil) + errList := validation.ValidateCustomResourceDefinition(&unversionedCRD) + for _, err := range errList { + if !strings.Contains(err.Field, "openAPIV3Schema") && !strings.Contains(err.Field, "status") { + er := validator.Error{Type: validator.ErrorType(err.Type), Field: err.Field, BadValue: err.BadValue, Detail: err.Error()} + manifestResult.Errors = append(manifestResult.Errors, er) + } + } + return +} From 9d8d567df6a8cc99885ff0ee03e7322a339c1998 Mon Sep 17 00:00:00 2001 From: dweepgogia Date: Tue, 13 Aug 2019 15:24:43 -0400 Subject: [PATCH 16/20] [lib] add package validator - To support validating package yaml present in the manifest. --- pkg/validate/package.go | 91 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 pkg/validate/package.go diff --git a/pkg/validate/package.go b/pkg/validate/package.go new file mode 100644 index 000000000..6813c072b --- /dev/null +++ b/pkg/validate/package.go @@ -0,0 +1,91 @@ +package validate + +import ( + "encoding/json" + "fmt" + + "github.com/dweepgogia/new-manifest-verification/pkg/validate/validator" + "github.com/ghodss/yaml" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" +) + +type PackageValidator struct { + fileName string + pkgs []registry.PackageManifest +} + +var _ validator.Validator = &PackageValidator{} + +// packageManifest is an alias of `registry.PackageManifest` used +// to define new methods on the non-local type. +type packageManifest registry.PackageManifest + +func (p packageManifest) getPkgName() string { + return p.PackageName +} + +func (v *PackageValidator) Validate() (results []validator.ManifestResult) { + for _, pkg := range v.pkgs { + result := pkgInspect(pkg) + if result.Name == "" { + result.Name = packageManifest(pkg).getPkgName() + } + results = append(results, result) + } + return results +} + +func (v *PackageValidator) AddObjects(objs ...interface{}) validator.Error { + for _, o := range objs { + switch t := o.(type) { + case registry.PackageManifest: + v.pkgs = append(v.pkgs, t) + case *registry.PackageManifest: + v.pkgs = append(v.pkgs, *t) + } + } + return validator.Error{} +} + +func (v PackageValidator) Name() string { + return "Package Validator" +} + +func (v PackageValidator) FileName() string { + return v.fileName +} + +func (v PackageValidator) Unmarshal(rawYaml []byte) (interface{}, error) { + var pkg registry.PackageManifest + + rawJson, err := yaml.YAMLToJSON(rawYaml) + if err != nil { + return registry.PackageManifest{}, fmt.Errorf("error parsing raw YAML to Json: %s", err) + } + if err := json.Unmarshal(rawJson, &pkg); err != nil { + return registry.PackageManifest{}, fmt.Errorf("error parsing package type (JSON) : %s", err) + } + return pkg, nil +} + +func pkgInspect(pkg registry.PackageManifest) (manifestResult validator.ManifestResult) { + manifestResult = validator.ManifestResult{} + present, manifestResult := isDefaultPresent(pkg, manifestResult) + if !present { + manifestResult.Errors = append(manifestResult.Errors, validator.InvalidDefaultChannel(fmt.Sprintf("Error: default channel %s not found in the list of declared channels", pkg.DefaultChannelName), pkg.DefaultChannelName)) + } + return +} + +func isDefaultPresent(pkg registry.PackageManifest, manifestResult validator.ManifestResult) (bool, validator.ManifestResult) { + present := false + for _, channel := range pkg.Channels { + if pkg.DefaultChannelName == "" { + manifestResult.Warnings = append(manifestResult.Warnings, validator.InvalidDefaultChannel(fmt.Sprintf("Warning: default channel not found in %s package manifest", pkg.PackageName), pkg.PackageName)) + return true, manifestResult + } else if pkg.DefaultChannelName == channel.Name { + present = true + } + } + return present, manifestResult +} From 8ee9933e22f39cf2e4ddc6a5dda2e42ff14247a2 Mon Sep 17 00:00:00 2001 From: dweepgogia Date: Tue, 13 Aug 2019 15:26:21 -0400 Subject: [PATCH 17/20] [lib] add bundle validator - To perform validation across manifest files. --- pkg/validate/bundle.go | 191 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 pkg/validate/bundle.go diff --git a/pkg/validate/bundle.go b/pkg/validate/bundle.go new file mode 100644 index 000000000..e93c6bc41 --- /dev/null +++ b/pkg/validate/bundle.go @@ -0,0 +1,191 @@ +package validate + +import ( + "encoding/json" + "fmt" + "io/ioutil" + + "github.com/dweepgogia/new-manifest-verification/pkg/validate/validator" + "github.com/ghodss/yaml" + "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type BundleValidator struct { + fileName string + Manifest Manifest +} + +var _ validator.Validator = &BundleValidator{} + +func (v *BundleValidator) Validate() (results []validator.ManifestResult) { + + result := bundleInspect(v.Manifest) + if result.Name == "" { + result.Name = v.Manifest.Name + } + results = append(results, result) + + return results +} + +func (v *BundleValidator) AddObjects(objs ...interface{}) validator.Error { + // TODO: define addObjects for bundle.go + return validator.Error{} +} + +func (v BundleValidator) Name() string { + return "Bundle Validator" +} + +func (v BundleValidator) FileName() string { + return v.fileName +} + +func (v BundleValidator) Unmarshal(rawYaml []byte) (interface{}, error) { + return nil, fmt.Errorf("Error: unsupported operation; unmarshal not defined for bundle validator") +} + +func bundleInspect(manifest Manifest) validator.ManifestResult { + manifestResult := validator.ManifestResult{} + csvReplacesMap := make(map[string]string) + var csvsInBundle []string + for _, bundle := range manifest.Bundle { + csv, err := readAndUnmarshalCSV(bundle.CSV) + if err != (validator.Error{}) { + manifestResult.Errors = append(manifestResult.Errors, err) + return manifestResult + } + csvsInBundle = append(csvsInBundle, csv.ObjectMeta.Name) + csvReplacesMap[bundle.CSV] = csv.Spec.Replaces + if csv.ObjectMeta.Name == csv.Spec.Replaces { + manifestResult.Warnings = append(manifestResult.Warnings, validator.InvalidCSV(fmt.Sprintf("Warning: `spec.replaces` field matches its own `metadata.Name` for %s CSV. It should contain `metadata.Name` of the old CSV to be replaced", bundle.CSV))) + } + manifestResult = validateOwnedCRDs(bundle, csv, manifestResult) + } + manifestResult = checkReplacesForCSVs(csvReplacesMap, csvsInBundle, manifestResult) + manifestResult = checkDefaultChannelInBundle(manifest.Package, csvsInBundle, manifestResult) + return manifestResult +} + +func checkDefaultChannelInBundle(pkgName string, csvsInBundle []string, manifestResult validator.ManifestResult) validator.ManifestResult { + rawYaml, err := ioutil.ReadFile(pkgName) + if err != nil { + manifestResult.Errors = append(manifestResult.Errors, validator.IOError(fmt.Sprintf("Error in reading %s file: #%s ", pkgName, err), pkgName)) + return manifestResult + } + v := &PackageValidator{} + pkg, err := v.Unmarshal(rawYaml) + if err != nil { + manifestResult.Errors = append(manifestResult.Errors, validator.InvalidParse(fmt.Sprintf("Error unmarshalling YAML to package manifest type for %s file: #%s ", pkgName, err), pkgName)) + return manifestResult + } + if pkg, ok := pkg.(registry.PackageManifest); ok { + for _, channel := range pkg.Channels { + if !isStringPresent(csvsInBundle, channel.CurrentCSVName) { + manifestResult.Errors = append(manifestResult.Errors, validator.InvalidBundle(fmt.Sprintf("Error: currentCSV `%s` for channel name `%s` in package `%s` not found in manifest", channel.CurrentCSVName, channel.Name, pkg.PackageName), channel.CurrentCSVName)) + } + } + } + return manifestResult +} + +func validateOwnedCRDs(bundle ManifestBundle, csv v1alpha1.ClusterServiceVersion, manifestResult validator.ManifestResult) validator.ManifestResult { + ownedCrdNames := getOwnedCustomResourceDefintionNames(csv) + bundleCrdNames, err := getBundleCRDNames(bundle) + if err != (validator.Error{}) { + manifestResult.Errors = append(manifestResult.Errors, err) + return manifestResult + } + + // validating names + for _, ownedCrd := range ownedCrdNames { + if !bundleCrdNames[ownedCrd] { + manifestResult.Errors = append(manifestResult.Errors, validator.InvalidBundle(fmt.Sprintf("Error: owned crd (%s) not found in bundle %s", ownedCrd, bundle.Version), ownedCrd)) + } else { + delete(bundleCrdNames, ownedCrd) + } + } + // CRDs not defined in the CSV present in the bundle + if len(bundleCrdNames) != 0 { + for crd, _ := range bundleCrdNames { + manifestResult.Warnings = append(manifestResult.Warnings, validator.InvalidBundle(fmt.Sprintf("Warning: `%s` crd present in bundle `%s` not defined in csv", crd, bundle.Version), crd)) + } + } + return manifestResult +} + +type CRDObjectMeta struct { + metav1.ObjectMeta `json:"metadata"` +} + +func getOwnedCustomResourceDefintionNames(csv v1alpha1.ClusterServiceVersion) []string { + var names []string + for _, ownedCrd := range csv.Spec.CustomResourceDefinitions.Owned { + names = append(names, ownedCrd.Name) + } + return names +} + +func getBundleCRDNames(bundle ManifestBundle) (map[string]bool, validator.Error) { + bundleCrdNames := make(map[string]bool) + for _, crdFileName := range bundle.CRDs { + parsedName := CRDObjectMeta{} + rawYaml, err := ioutil.ReadFile(crdFileName) + if err != nil { + return nil, validator.IOError(fmt.Sprintf("Error in reading %s file: #%s ", crdFileName, err), crdFileName) + } + rawJson, err := yaml.YAMLToJSON(rawYaml) + if err != nil { + return nil, validator.InvalidParse(fmt.Sprintf("Error in converting to JSON for %s file: #%s ", crdFileName, err), crdFileName) + } + + if err := json.Unmarshal(rawJson, &parsedName); err != nil { + return nil, validator.InvalidParse(fmt.Sprintf("Error parsing object meta names for %s file: #%s ", crdFileName, err), crdFileName) + } + bundleCrdNames[parsedName.Name] = true + } + return bundleCrdNames, validator.Error{} +} + +func readAndUnmarshalCSV(pathCSV string) (v1alpha1.ClusterServiceVersion, validator.Error) { + rawYaml, err := ioutil.ReadFile(pathCSV) + if err != nil { + return v1alpha1.ClusterServiceVersion{}, validator.IOError(fmt.Sprintf("Error in reading %s file: #%s ", pathCSV, err), pathCSV) + } + v := &CSVValidator{} + csv, err := v.Unmarshal(rawYaml) + if err != nil { + return v1alpha1.ClusterServiceVersion{}, validator.InvalidParse(fmt.Sprintf("Error unmarshalling YAML to OLM's csv type for %s file: #%s ", pathCSV, err), pathCSV) + } + if csv, ok := csv.(v1alpha1.ClusterServiceVersion); ok { + return csv, validator.Error{} + } + return v1alpha1.ClusterServiceVersion{}, validator.InvalidParse(fmt.Sprintf("Error unmarshalling YAML to OLM's csv type for %s file: #%s ", pathCSV, err), pathCSV) +} + +// checkReplacesForCSVs generates an error if value of the `replaces` field in the +// csv does not match the `metadata.Name` field of the old csv to be replaced. +// It also generates a warning if the `replaces` field of a csv is empty. +func checkReplacesForCSVs(csvReplacesMap map[string]string, csvsInBundle []string, manifestResult validator.ManifestResult) validator.ManifestResult { + for pathCSV, replaces := range csvReplacesMap { + if replaces == "" { + manifestResult.Warnings = append(manifestResult.Warnings, validator.InvalidCSV(fmt.Sprintf("Warning: `spec.replaces` field not present in %s csv. If this csv replaces an old version, populate this field with the `metadata.Name` of the old csv", pathCSV))) + } else { + if !isStringPresent(csvsInBundle, replaces) { + manifestResult.Errors = append(manifestResult.Errors, validator.InvalidCSV(fmt.Sprintf("Error: `%s` mentioned in the `spec.replaces` field of %s csv not present in the manifest", replaces, pathCSV))) + } + } + } + return manifestResult +} + +func isStringPresent(list []string, val string) bool { + for _, str := range list { + if val == str { + return true + } + } + return false +} From 3fe9a43664967e0bc9269170c0cdf2f40c926230 Mon Sep 17 00:00:00 2001 From: Dweep Gogia Date: Mon, 26 Aug 2019 07:53:25 -0700 Subject: [PATCH 18/20] [lib] add two methods to validator interface - To improve the design. --- pkg/validate/validator/validator.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/validate/validator/validator.go b/pkg/validate/validator/validator.go index 58c200afc..4c56a2b9a 100644 --- a/pkg/validate/validator/validator.go +++ b/pkg/validate/validator/validator.go @@ -13,6 +13,10 @@ type Validator interface { AddObjects(...interface{}) Error // Name should return a succinct name for this validator. Name() string + // FileName returns the file name of the object to be validated. + FileName() string + // Unmarshal returns the unmarshalled object of caller's underlying type. + Unmarshal([]byte) (interface{}, error) } // ValidatorSet contains a set of Validators to be executed sequentially. From 439c7697fcc9db3890c8427bdeeae8dd355e905c Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Mon, 26 Aug 2019 16:26:07 -0700 Subject: [PATCH 19/20] pkg/validation/interfaces: Validator's implement Validate() to return errors.ManifestResult. pkg/validation/internal: default validator implementations pkg/validation/errors: result and error types for validators pkg/internal: bundle parser pkg/manifests: GetManifestsDir() parses and validates a directory of Operator manifests *: update import paths to operator-framework/api go.*: revendor README.md: concise description of pkg/validation --- README.md | 10 +- cmd/operator-verify/main.go | 20 +- cmd/operator-verify/manifests/cmd.go | 40 +++ cmd/root.go | 24 -- cmd/verify.go | 30 -- go.mod | 49 +-- go.sum | 187 +++++++++-- pkg/internal/bundle.go | 134 ++++++++ pkg/manifests/directory.go | 31 ++ pkg/validate/bundle.go | 191 ----------- pkg/validate/crd.go | 88 ----- pkg/validate/csv.go | 313 ------------------ pkg/validate/package.go | 91 ----- pkg/validate/parser.go | 158 --------- pkg/validate/serialize.go | 127 ------- pkg/validate/validator/error.go | 133 -------- pkg/validate/validator/validator.go | 53 --- pkg/validation/doc.go | 15 + pkg/validation/errors/error.go | 234 +++++++++++++ pkg/validation/interfaces/validator.go | 24 ++ pkg/validation/internal/bundle.go | 93 ++++++ pkg/validation/internal/crd.go | 54 +++ pkg/validation/internal/csv.go | 187 +++++++++++ pkg/validation/internal/csv_test.go | 50 +++ pkg/validation/internal/manifests.go | 205 ++++++++++++ pkg/validation/internal/package_manifest.go | 60 ++++ .../internal/package_manifest_test.go | 123 +++++++ pkg/validation/internal/test_suite.go | 63 ++++ .../internal/testdata/correct.csv.yaml | 292 ++++++++++++++++ .../testdata/dataTypeMismatch.csv.yaml | 290 ++++++++++++++++ pkg/validation/internal/typecheck.go | 100 ++++++ pkg/validation/validation.go | 62 ++++ 32 files changed, 2248 insertions(+), 1283 deletions(-) create mode 100644 cmd/operator-verify/manifests/cmd.go delete mode 100644 cmd/root.go delete mode 100644 cmd/verify.go create mode 100644 pkg/internal/bundle.go create mode 100644 pkg/manifests/directory.go delete mode 100644 pkg/validate/bundle.go delete mode 100644 pkg/validate/crd.go delete mode 100644 pkg/validate/csv.go delete mode 100644 pkg/validate/package.go delete mode 100644 pkg/validate/parser.go delete mode 100644 pkg/validate/serialize.go delete mode 100644 pkg/validate/validator/error.go delete mode 100644 pkg/validate/validator/validator.go create mode 100644 pkg/validation/doc.go create mode 100644 pkg/validation/errors/error.go create mode 100644 pkg/validation/interfaces/validator.go create mode 100644 pkg/validation/internal/bundle.go create mode 100644 pkg/validation/internal/crd.go create mode 100644 pkg/validation/internal/csv.go create mode 100644 pkg/validation/internal/csv_test.go create mode 100644 pkg/validation/internal/manifests.go create mode 100644 pkg/validation/internal/package_manifest.go create mode 100644 pkg/validation/internal/package_manifest_test.go create mode 100644 pkg/validation/internal/test_suite.go create mode 100644 pkg/validation/internal/testdata/correct.csv.yaml create mode 100644 pkg/validation/internal/testdata/dataTypeMismatch.csv.yaml create mode 100644 pkg/validation/internal/typecheck.go create mode 100644 pkg/validation/validation.go diff --git a/README.md b/README.md index 1561c101a..91bb12ffb 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,10 @@ # api -Contains the API definitions used by OLM and Marketplace + +Contains the API definitions used by [Operator Lifecycle Manager][olm] (OLM) and [Marketplace Operator][marketplace] + +## `pkg/validation`: Operator Manifest Validation + +`pkg/validation` exposes a convenient set of interfaces to validate Kubernetes object manifests, primarily for use in an Operator project. + +[olm]:https://github.com/operator-framework/operator-lifecycle-manager +[marketplace]:https://github.com/operator-framework/operator-marketplace diff --git a/cmd/operator-verify/main.go b/cmd/operator-verify/main.go index 6fce3e329..41a29a5ff 100644 --- a/cmd/operator-verify/main.go +++ b/cmd/operator-verify/main.go @@ -1,10 +1,24 @@ package main import ( - "github.com/dweepgogia/new-manifest-verification/cmd" + "fmt" + "os" + + manifests "github.com/operator-framework/api/cmd/operator-verify/manifests" + + "github.com/spf13/cobra" ) func main() { - // Launch CLI tool. - cmd.Execute() + rootCmd := &cobra.Command{ + Use: "operator-verify", + Short: "Operator manifest validation tool", + Long: `operator-verify is a CLI tool that calls functions in pkg/validation.`, + } + + rootCmd.AddCommand(manifests.NewCmd()) + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } } diff --git a/cmd/operator-verify/manifests/cmd.go b/cmd/operator-verify/manifests/cmd.go new file mode 100644 index 000000000..eccdd0970 --- /dev/null +++ b/cmd/operator-verify/manifests/cmd.go @@ -0,0 +1,40 @@ +package manifests + +import ( + "fmt" + "os" + + "github.com/operator-framework/api/pkg/manifests" + "github.com/operator-framework/api/pkg/validation/errors" + + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +func NewCmd() *cobra.Command { + return &cobra.Command{ + Use: "manifests", + Short: "Validates all manifests in a directory", + Long: `'operator-verify manifests' validates all manifests in the supplied directory +and prints errors and warnings corresponding to each manifest found to be +invalid. Manifests are only validated if a validator for that manifest +type/kind, ex. CustomResourceDefinition, is implemented in the Operator +validation library.`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + log.Fatalf("command %s requires exactly one argument", cmd.CommandPath()) + } + _, _, results := manifests.GetManifestsDir(args[0]) + nonEmptyResults := []errors.ManifestResult{} + for _, result := range results { + if result.HasError() || result.HasWarn() { + nonEmptyResults = append(nonEmptyResults, result) + } + } + if len(nonEmptyResults) != 0 { + fmt.Println(nonEmptyResults) + os.Exit(1) + } + }, + } +} diff --git a/cmd/root.go b/cmd/root.go deleted file mode 100644 index 868ef166d..000000000 --- a/cmd/root.go +++ /dev/null @@ -1,24 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - - "github.com/spf13/cobra" -) - -var rootCmd = &cobra.Command{ - Use: "operator-verify", - Short: "New Manifest Verification Tool Prototype", - Long: `operator-verify is a CLI tool for the Operator Manifest Verification Library. This library provides functions to validate the operator manifest bundles against Operator-Lifecycle-Manager's ClusterServiceVersion type, CustomResourceDefinitions, and Package Manifest yamls. Currently, this application supports validation of ClusterServiceVersion yaml for any mismatched data types with Operator-Lifecycle-Manager's ClusterServiceVersion type.`, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("Initializing verification CLI tool...") - }, -} - -func Execute() { - if err := rootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } -} diff --git a/cmd/verify.go b/cmd/verify.go deleted file mode 100644 index e11dd7edb..000000000 --- a/cmd/verify.go +++ /dev/null @@ -1,30 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/dweepgogia/new-manifest-verification/pkg/validate" - "github.com/spf13/cobra" -) - -func init() { - rootCmd.AddCommand(verifyCmd) -} - -var verifyCmd = &cobra.Command{ - Use: "manifest", - Short: "Validate YAML against OLM's CSV type.", - Long: `Verifies the yaml file against Operator-Lifecycle-Manager's ClusterServiceVersion type. Reports errors for any mismatched data types. Takes in one argument i.e. path to the yaml file. Version: 1.0`, - Run: verifyFunc, -} - -func verifyFunc(cmd *cobra.Command, args []string) { - - if len(args) != 1 { - fmt.Printf("command %s requires exactly one argument", cmd.CommandPath()) - } - - manifestDirectory := args[0] - - _ = validate.ValidateManifest(manifestDirectory) -} diff --git a/go.mod b/go.mod index eee77bb29..09c106eac 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/dweepgogia/new-manifest-verification +module github.com/operator-framework/api go 1.12 @@ -23,51 +23,16 @@ replace ( require ( github.com/blang/semver v3.5.1+incompatible - github.com/coreos/go-semver v0.3.0 - github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a // indirect - github.com/docker/distribution v2.7.1+incompatible // indirect - github.com/emicklei/go-restful v2.9.6+incompatible // indirect - github.com/evanphx/json-patch v4.5.0+incompatible // indirect github.com/ghodss/yaml v1.0.0 - github.com/go-openapi/spec v0.19.2 - github.com/go-openapi/validate v0.19.2 // indirect - github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b - github.com/golang/mock v1.3.1 - github.com/google/btree v1.0.0 // indirect - github.com/google/gofuzz v1.0.0 // indirect - github.com/googleapis/gnostic v0.3.0 // indirect - github.com/grpc-ecosystem/grpc-gateway v1.9.4 // indirect - github.com/json-iterator/go v1.1.6 // indirect - github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect - github.com/maxbrunsfeld/counterfeiter/v6 v6.2.1 - github.com/mitchellh/hashstructure v1.0.0 - github.com/munnerz/goautoneg v0.0.0-20190414153302-2ae31c8b6b30 // indirect - github.com/openshift/api v0.0.0-00010101000000-000000000000 - github.com/openshift/client-go v0.0.0-00010101000000-000000000000 - github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190125151539-1e295784b30a - github.com/operator-framework/operator-registry v1.1.1 + github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect + github.com/hashicorp/golang-lru v0.5.3 // indirect + github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190926160646-a61144936680 + github.com/operator-framework/operator-registry v1.5.3 github.com/pkg/errors v0.8.1 - github.com/prometheus/client_golang v0.9.2 github.com/sirupsen/logrus v1.4.2 github.com/spf13/cobra v0.0.5 github.com/stretchr/testify v1.3.0 - go.uber.org/atomic v1.4.0 // indirect - go.uber.org/zap v1.10.0 // indirect - golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 - gonum.org/v1/gonum v0.0.0-20190710053202-4340aa3071a0 // indirect - google.golang.org/grpc v1.22.0 - k8s.io/api v0.0.0-20190717022910-653c86b0609b - k8s.io/apiextensions-apiserver v0.0.0-20181204003618-e419c5771cdc - k8s.io/apimachinery v0.0.0-20190717022731-0bb8574e0887 - k8s.io/apiserver v0.0.0-20181026151315-13cfe3978170 + k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783 + k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655 k8s.io/client-go v8.0.0+incompatible - k8s.io/code-generator v0.0.0-20181203235156-f8cba74510f3 - k8s.io/component-base v0.0.0-20190717023551-b4f50308a616 - k8s.io/gengo v0.0.0-20190327210449-e17681d19d3a // indirect - k8s.io/kube-aggregator v0.0.0-20181204002017-122bac39d429 - k8s.io/kube-openapi v0.0.0-20181031203759-72693cb1fadd - k8s.io/kubernetes v1.11.8-beta.0.0.20190124204751-3a10094374f2 - k8s.io/utils v0.0.0-20190712204705-3dccf664f023 // indirect - sigs.k8s.io/structured-merge-diff v0.0.0-00010101000000-000000000000 // indirect - sigs.k8s.io/yaml v1.1.0 // indirect ) diff --git a/go.sum b/go.sum index 4c0369c7a..822aeae17 100644 --- a/go.sum +++ b/go.sum @@ -1,25 +1,43 @@ bitbucket.org/ww/goautoneg v0.0.0-20120707110453-75cd24fc2f2c/go.mod h1:1vhO7Mn/FZMgOgDVGLy5X1mE6rq1HbkBdkF/yj8zkcg= +bou.ke/monkey v1.0.1 h1:zEMLInw9xvNakzUUPjfS4Ds6jYPqCFx3m7bRmG5NH2U= +bou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg= 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.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/NYTimes/gziphandler v1.0.1/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= 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/antihax/optional v0.0.0-20180407024304-ca021399b1a6 h1:uZuxRZCz65cG1o6K/xUqImNcYKtmk9ylqaH0itMSvzA= +github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= +github.com/containerd/containerd v1.2.7/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/coreos/bbolt v1.3.0/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.9+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -28,20 +46,38 @@ github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazu github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20181031085051-9002847aa142/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= +github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg= +github.com/cznic/golex v0.0.0-20170803123110-4ab7c5e190e4/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc= +github.com/cznic/internal v0.0.0-20180608152220-f44710a21d00/go.mod h1:olo7eAdKwJdXxb55TKGLiJ6xt1H0/tiiRCWKVLmtjY4= +github.com/cznic/lldb v1.1.0/go.mod h1:FIZVUmYUVhPwRiPzL8nD/mpFcJ/G7SSXjjXYG4uRI3A= +github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= +github.com/cznic/ql v1.2.0/go.mod h1:FbpzhyZrqr0PVlK6ury+PoW3T0ODUV22OeWIxcaOrSE= +github.com/cznic/sortutil v0.0.0-20150617083342-4c7342852e65/go.mod h1:q2w6Bg5jeox1B+QkJ6Wp/+Vn0G/bo3f1uY7Fn3vivIQ= +github.com/cznic/strutil v0.0.0-20171016134553-529a34b1c186/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc= +github.com/cznic/zappy v0.0.0-20160723133515-2533cb5b45cc/go.mod h1:Y1SNZ4dRUOKXshKUbwUapqNncRrho4mkjQebgEHZLj8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dhui/dktest v0.3.0/go.mod h1:cyzIUfGsBEbZ6BT7tnXqAShHSXCZhSNmFl70sZ7c1yc= github.com/docker/distribution v2.6.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v0.7.3-0.20190103212154-2b7e084dc98b/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v0.7.3-0.20190817195342-4760db040282/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/emicklei/go-restful v2.8.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.8.1+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= @@ -50,7 +86,9 @@ github.com/emicklei/go-restful-swagger12 v0.0.0-20170926063155-7524189396c6/go.m github.com/evanphx/json-patch v3.0.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb h1:D4uzjWwKYQ5XnAvUbuvHW93esHg7F8N/OYeBBcJoTr0= @@ -59,6 +97,7 @@ github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7a 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-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= @@ -104,38 +143,56 @@ github.com/go-openapi/validate v0.17.2/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+ github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2 h1:ky5l57HjyVRrsJfd2+Ro5Z9PjGuKbsmftwyMtk8H7js= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang-migrate/migrate/v4 v4.6.2 h1:LDDOHo/q1W5UDj6PbkxdCv7lv9yunyZHXvxuwDkGo3k= +github.com/golang-migrate/migrate/v4 v4.6.2/go.mod h1:JYi6reN3+Z734VZ0akNuyOJNcrg45ZL7LDBMW3WGJL0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20180924190550-6f2cf27854a4 h1:6UVLWz0fIIrv0UVj6t0A7cL48n8IyAdLVQqAYzEfsKI= github.com/golang/groupcache v0.0.0-20180924190550-6f2cf27854a4/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff h1:kOkM9whyQYodu09SJ6W3NCsHG7crFaJILQ22Gozp3lg= github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 h1:uHTyIjqVhYRhLbJ8nIiOJHkEZZ+5YoOsAbD3sk82NiE= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= 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/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 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-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= 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 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 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.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.3.0 h1:CcQijm0XKekKjP/YCz28LXVSpgguuB+nCxaSjCe09y0= github.com/googleapis/gnostic v0.3.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 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/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= @@ -147,29 +204,39 @@ github.com/grpc-ecosystem/grpc-gateway v1.6.3/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpg github.com/grpc-ecosystem/grpc-gateway v1.9.4/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-health-probe v0.2.0/go.mod h1:4GVx/bTCtZaSzhjbGueDY5YgBdsmKeVx+LErv/n0L6s= github.com/grpc-ecosystem/grpc-health-probe v0.2.1-0.20181220223928-2bf0a5b182db/go.mod h1:uBKkC2RbarFsvS5jMJHpVhTLvGlGQj9JJwkaePE3FWI= -github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 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/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= +github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= -github.com/json-iterator/go v1.1.7/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/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -177,6 +244,8 @@ 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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kshvakov/clickhouse v1.3.5/go.mod h1:DMzX7FxRymoNkVgizH0DWAL8Cur7wHLgx3MUnGwJqpE= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= @@ -185,6 +254,7 @@ github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63 h1:nTT4s92Dgz2HlrB2NaMgvlfqHH39OgMhA7z3PK7PGD4= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -194,63 +264,85 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20190414153302-2ae31c8b6b30/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.2-0.20180831124310-ae19f1b56d53/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/openshift/api v3.9.1-0.20190717200738-0390d1e77d64+incompatible/go.mod h1:dh9o4Fs58gpFXGSYfnVxGR9PnV53I8TW84pQaJDdGiY= github.com/openshift/client-go v0.0.0-20190627172412-c44a8b61b9f4/go.mod h1:6rzn+JTr7+WYS2E1TExP4gByoABxMznR6y2SnUIkmxk= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/operator-framework/go-appr v0.0.0-20180917210448-f2aef88446f2/go.mod h1:YNzwUx1i6C4dXWcffyq3yaIb0rh/K8/OvQ4vG0SNlSw= github.com/operator-framework/operator-lifecycle-manager v0.0.0-20181023032605-e838f7fb2186/go.mod h1:Ma5ZXd4S1vmMyewWlF7aO8CZiokR7Sd8dhSfkGkNU4U= github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190105193533-81104ffdc4fb/go.mod h1:XMyE4n2opUK4N6L45YGQkXXi8F9fD7XDYFv/CsS6V5I= -github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190125151539-1e295784b30a h1:gBTaGobFQZVx+M2ItXxJp7oxoc9dltkLMrkgyDE0Qfc= github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190125151539-1e295784b30a/go.mod h1:vq6TTFvg6ti1Bn6ACsZneZTmjTsURgDD6tQtVDbEgsU= -github.com/operator-framework/operator-lifecycle-manager v3.11.0+incompatible h1:Po8C8RVLRWq7pNQ5pKonM9CXpC/osoBWbmsuf+HJnSI= -github.com/operator-framework/operator-lifecycle-manager v3.11.0+incompatible/go.mod h1:Ma5ZXd4S1vmMyewWlF7aO8CZiokR7Sd8dhSfkGkNU4U= +github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190926160646-a61144936680 h1:7/79nDoXBQGFidupTJNeVJef3TeirnaxeFP5kNF+YLk= +github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190926160646-a61144936680/go.mod h1:NSydXcV6IwxJ6gZwegvanaVCzYt9Ep6Cl1HFcd1Ithk= github.com/operator-framework/operator-marketplace v0.0.0-20190216021216-57300a3ef3ba/go.mod h1:msZSL8pXwzQjB+hU+awVrZQw94IwJi3sNZVD3NoESIs= -github.com/operator-framework/operator-registry v1.0.1 h1:Z2155w77HzIkTrdp2qoY0QMkywxhJpuABUSGcgogXuc= github.com/operator-framework/operator-registry v1.0.1/go.mod h1:1xEdZjjUg2hPEd52LG3YQ0jtwiwEGdm98S1TH5P4RAA= github.com/operator-framework/operator-registry v1.0.4/go.mod h1:hve6YwcjM2nGVlscLtNsp9sIIBkNZo6jlJgzWw7vP9s= -github.com/operator-framework/operator-registry v1.1.1 h1:oDIevJvKXFsp7BEb7iJHuLvuhPZYBtIx5oZQ7iSISAs= github.com/operator-framework/operator-registry v1.1.1/go.mod h1:7D4WEwL+EKti5npUh4/u64DQhawCBRugp8Ql20duUb4= +github.com/operator-framework/operator-registry v1.5.3 h1:az83WDwgB+tHsmVn+tFq72yQBbaUAye8e4+KkDQmzLs= +github.com/operator-framework/operator-registry v1.5.3/go.mod h1:agrQlkWOo1q8U1SAaLSS2WQ+Z9vswNT2M2HFib9iuLY= +github.com/otiai10/copy v1.0.1 h1:gtBjD8aq4nychvRZ2CyJvFWAw0aja+VHazDdruZKGZA= +github.com/otiai10/copy v1.0.1/go.mod h1:8bMCJrAqOtN/d9oyh5HR7HhLQMvcGMpGdwRDYsfOCHc= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v0.0.0-20190513014714-f5a3d24e5776 h1:o59bHXu8Ejas8Kq6pjoVJQ9/neN66SM8AKh6wI42BBs= +github.com/otiai10/curr v0.0.0-20190513014714-f5a3d24e5776/go.mod h1:3HNVkVOU7vZeFXocWuvtcS0XSFLcf2XUSDHkq9t1jU4= +github.com/otiai10/mint v1.2.3/go.mod h1:YnfyPNhBvnY8bW4SGQHCs/aAFhkgySlMZbrF5U0bOVw= +github.com/otiai10/mint v1.2.4 h1:DxYL0itZyPaR5Z9HILdxSoHx+gNs6Yx+neOGS3IVUk0= +github.com/otiai10/mint v1.2.4/go.mod h1:d+b7n/0R3tdyUYYylALXpWQ/kTN+QobSq/4SRGBkR3M= github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= 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/petar/GoLLRB v0.0.0-20130427215148-53be0d36a84c/go.mod h1:HUpKUBZnpzkdx0kD/+Yfuft+uD3zHGtXF/XJB14TUr4= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20190104105734-b1c43a6df3ae/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190104112138-b1a0a9a36d74/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 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/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/spec v1.0.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -258,8 +350,6 @@ github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B 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.4 h1:S0tLZ3VOKl2Te0hpq8+ke0eSJPfCnNTPiDlsfwi1/NE= -github.com/spf13/cobra v0.0.4/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= 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= @@ -274,14 +364,22 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 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= +gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= +go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +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/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= @@ -292,11 +390,16 @@ golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/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-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/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/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 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/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= @@ -304,27 +407,35 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/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-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181108082009-03003ca0c849/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-20181201002055-351d144fa1fc/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 h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/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-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU= 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-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68= +golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181105165119-ca4130e427c7 h1:g9UOdtsRWEwHYUG2bDHMxKrvfSGE5epIX2HkaMHSMBY= golang.org/x/oauth2 v0.0.0-20181105165119-ca4130e427c7/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/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-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +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 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -333,68 +444,88 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181023152157-44b849a8bc13/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-20181122145206-62eef0e2fa9b/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-20190102155601-82a175fd1598/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-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-20190426135247-a129542de9ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 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 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM= 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 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= 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-20180828015842-6cd1fcedba52/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-20181011152555-a398e557df60/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181207222222-4c874b978acb/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-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-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425222832-ad9eeb80039a/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 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-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= gonum.org/v1/gonum v0.0.0-20190710053202-4340aa3071a0/go.mod h1:03dgh78c4UvU1WksguQ/lvJQXbezKQGJSrwwRq5MraQ= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= 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-20181016170114-94acd270e44e h1:I5s8aUkxqPjgAssfOv+dVr+4/7BC40WV6JhcVoORltI= google.golang.org/genproto v0.0.0-20181016170114-94acd270e44e/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/grpc v1.16.0 h1:dz5IJGuC2BB7qXR5AyHNwAUBhZscK2xVez7mznh72sY= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb h1:i1Ppqkc3WQXikh8bXiwHqAN5Rv3/qDCcRk0/Otx73BY= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= +google.golang.org/grpc v1.23.0/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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.0.0-20170531160350-a96e63847dc3/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= 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/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 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 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 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.0.0-20190704095032-f4ca3d3bdf1d h1:X3GqeHwOBOJa0O7jrPobw6MGLMwthSnA/sM86ENzbCo= k8s.io/api v0.0.0-20190704095032-f4ca3d3bdf1d/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= @@ -415,16 +546,14 @@ k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUc k8s.io/klog v0.1.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.3.3 h1:niceAagH1tzskmaie/icWd7ci1wbG7Bf2c6YGcQv+3c= -k8s.io/klog v0.3.3/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.4.0 h1:lCJCxf/LIowc2IGS9TPjWDyXY4nOmdGdfcwwDQCOURQ= +k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/kube-aggregator v0.0.0-20190704101955-e796fd6d55e0/go.mod h1:8sbzT4QQKDEmSCIbfqjV0sd97GpUT7A4W626sBiYJmU= k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30 h1:TRb4wNWoBVrH9plmkp2q86FIDppkbrEXdXlxU3a3BMI= k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= k8s.io/kubernetes v1.14.5-beta.0.0.20190708100021-7936da50c68f/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20190712204705-3dccf664f023 h1:1H4Jyzb0z2X0GfBMTwRjnt5ejffRHrGftUgJcV/ZfDc= k8s.io/utils v0.0.0-20190712204705-3dccf664f023/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20190801114015-581e00157fb1 h1:+ySTxfHnfzZb9ys375PXNlLhkJPLKgHajBU0N62BDvE= -k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= sigs.k8s.io/controller-runtime v0.1.10/go.mod h1:HFAYoOh6XMV+jKF1UjFwrknPbowfyHEHHRdJMf2jMX8= sigs.k8s.io/structured-merge-diff v0.0.0-20190302045857-e85c7b244fd2/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= diff --git a/pkg/internal/bundle.go b/pkg/internal/bundle.go new file mode 100644 index 000000000..5b01ddb69 --- /dev/null +++ b/pkg/internal/bundle.go @@ -0,0 +1,134 @@ +package manifests + +import ( + "encoding/json" + + "github.com/blang/semver" + operatorsv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" + "github.com/operator-framework/operator-registry/pkg/registry" + "github.com/operator-framework/operator-registry/pkg/sqlite" + "github.com/pkg/errors" +) + +// TODO: use internal version of registry.Bundle/registry.PackageManifest so +// operator-registry can use validation library. + +// manifestsLoad loads a manifests directory from disk. +type manifestsLoad struct { + dir string + pkg registry.PackageManifest + bundles map[string]*registry.Bundle +} + +// Ensure manifestsLoad implements registry.Load. +var _ registry.Load = &manifestsLoad{} + +// populate uses operator-registry's sqlite.NewSQLLoaderForDirectory to load +// l.dir's manifests. Note that this method does not call any functions that +// use SQL drivers. +func (l *manifestsLoad) populate() error { + loader := sqlite.NewSQLLoaderForDirectory(l, l.dir) + if err := loader.Populate(); err != nil { + return errors.Wrapf(err, "error getting bundles from manifests dir %q", l.dir) + } + return nil +} + +// AddOperatorBundle adds a bundle to l. +func (l *manifestsLoad) AddOperatorBundle(bundle *registry.Bundle) error { + csvRaw, err := bundle.ClusterServiceVersion() + if err != nil { + return errors.Wrap(err, "error getting bundle CSV") + } + csvSpec := operatorsv1alpha1.ClusterServiceVersionSpec{} + if err := json.Unmarshal(csvRaw.Spec, &csvSpec); err != nil { + return errors.Wrap(err, "error unmarshaling CSV spec") + } + bundle.Name = csvSpec.Version.String() + l.bundles[csvSpec.Version.String()] = bundle + return nil +} + +// AddOperatorBundle adds the package manifest to l. +func (l *manifestsLoad) AddPackageChannels(pkg registry.PackageManifest) error { + l.pkg = pkg + return nil +} + +// AddBundlePackageChannels is a no-op to implement the registry.Load interface. +func (l *manifestsLoad) AddBundlePackageChannels(manifest registry.PackageManifest, bundle registry.Bundle) error { + return nil +} + +// RmPackageName is a no-op to implement the registry.Load interface. +func (l *manifestsLoad) RmPackageName(packageName string) error { + return nil +} + +// ClearNonDefaultBundles is a no-op to implement the registry.Load interface. +func (l *manifestsLoad) ClearNonDefaultBundles(packageName string) error { + return nil +} + +// ManifestsStore knows how to query for an operator's package manifest and +// related bundles. +type ManifestsStore interface { + // GetPackageManifest returns the ManifestsStore's registry.PackageManifest. + // The returned object is assumed to be valid. + GetPackageManifest() registry.PackageManifest + // GetBundles returns the ManifestsStore's set of registry.Bundle. These + // bundles are unique by CSV version, since only one operator type should + // exist in one manifests dir. + // The returned objects are assumed to be valid. + GetBundles() []*registry.Bundle + // GetBundleForVersion returns the ManifestsStore's registry.Bundle for a + // given version string. An error should be returned if the passed version + // does not exist in the store. + // The returned object is assumed to be valid. + GetBundleForVersion(string) (*registry.Bundle, error) +} + +// manifests implements ManifestsStore +type manifests struct { + pkg registry.PackageManifest + bundles map[string]*registry.Bundle +} + +// ManifestsStoreForDir populates a ManifestsStore from the metadata in dir. +// Each bundle and the package manifest are statically validated, and will +// return an error if any are not valid. +func ManifestsStoreForDir(dir string) (ManifestsStore, error) { + load := &manifestsLoad{ + dir: dir, + bundles: map[string]*registry.Bundle{}, + } + if err := load.populate(); err != nil { + return nil, err + } + return &manifests{ + pkg: load.pkg, + bundles: load.bundles, + }, nil +} + +func (l manifests) GetPackageManifest() registry.PackageManifest { + return l.pkg +} + +func (l manifests) GetBundles() (bundles []*registry.Bundle) { + for _, bundle := range l.bundles { + bundles = append(bundles, bundle) + } + return bundles +} + +func (l manifests) GetBundleForVersion(version string) (*registry.Bundle, error) { + if _, err := semver.Parse(version); err != nil { + return nil, errors.Wrapf(err, "error getting bundle for version %q", version) + } + bundle, ok := l.bundles[version] + if !ok { + return nil, errors.Errorf("bundle for version %q does not exist", version) + } + return bundle, nil +} diff --git a/pkg/manifests/directory.go b/pkg/manifests/directory.go new file mode 100644 index 000000000..e31077128 --- /dev/null +++ b/pkg/manifests/directory.go @@ -0,0 +1,31 @@ +package manifests + +import ( + "fmt" + + internal "github.com/operator-framework/api/pkg/internal" + "github.com/operator-framework/api/pkg/validation" + "github.com/operator-framework/api/pkg/validation/errors" + + "github.com/operator-framework/operator-registry/pkg/registry" +) + +// GetManifestsDir parses all bundles and a package manifest from dir, which +// are returned if found along with any errors or warnings encountered while +// parsing/validating found manifests. +func GetManifestsDir(dir string) (registry.PackageManifest, []*registry.Bundle, []errors.ManifestResult) { + manifests, err := internal.ManifestsStoreForDir(dir) + if err != nil { + result := errors.ManifestResult{} + result.Add(errors.ErrInvalidParse(fmt.Sprintf("parse manifests from %q", dir), err)) + return registry.PackageManifest{}, nil, []errors.ManifestResult{result} + } + pkg := manifests.GetPackageManifest() + bundles := manifests.GetBundles() + objs := []interface{}{} + for _, obj := range bundles { + objs = append(objs, obj) + } + results := validation.DefaultValidators().Apply(objs...) + return pkg, bundles, results +} diff --git a/pkg/validate/bundle.go b/pkg/validate/bundle.go deleted file mode 100644 index e93c6bc41..000000000 --- a/pkg/validate/bundle.go +++ /dev/null @@ -1,191 +0,0 @@ -package validate - -import ( - "encoding/json" - "fmt" - "io/ioutil" - - "github.com/dweepgogia/new-manifest-verification/pkg/validate/validator" - "github.com/ghodss/yaml" - "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" - "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type BundleValidator struct { - fileName string - Manifest Manifest -} - -var _ validator.Validator = &BundleValidator{} - -func (v *BundleValidator) Validate() (results []validator.ManifestResult) { - - result := bundleInspect(v.Manifest) - if result.Name == "" { - result.Name = v.Manifest.Name - } - results = append(results, result) - - return results -} - -func (v *BundleValidator) AddObjects(objs ...interface{}) validator.Error { - // TODO: define addObjects for bundle.go - return validator.Error{} -} - -func (v BundleValidator) Name() string { - return "Bundle Validator" -} - -func (v BundleValidator) FileName() string { - return v.fileName -} - -func (v BundleValidator) Unmarshal(rawYaml []byte) (interface{}, error) { - return nil, fmt.Errorf("Error: unsupported operation; unmarshal not defined for bundle validator") -} - -func bundleInspect(manifest Manifest) validator.ManifestResult { - manifestResult := validator.ManifestResult{} - csvReplacesMap := make(map[string]string) - var csvsInBundle []string - for _, bundle := range manifest.Bundle { - csv, err := readAndUnmarshalCSV(bundle.CSV) - if err != (validator.Error{}) { - manifestResult.Errors = append(manifestResult.Errors, err) - return manifestResult - } - csvsInBundle = append(csvsInBundle, csv.ObjectMeta.Name) - csvReplacesMap[bundle.CSV] = csv.Spec.Replaces - if csv.ObjectMeta.Name == csv.Spec.Replaces { - manifestResult.Warnings = append(manifestResult.Warnings, validator.InvalidCSV(fmt.Sprintf("Warning: `spec.replaces` field matches its own `metadata.Name` for %s CSV. It should contain `metadata.Name` of the old CSV to be replaced", bundle.CSV))) - } - manifestResult = validateOwnedCRDs(bundle, csv, manifestResult) - } - manifestResult = checkReplacesForCSVs(csvReplacesMap, csvsInBundle, manifestResult) - manifestResult = checkDefaultChannelInBundle(manifest.Package, csvsInBundle, manifestResult) - return manifestResult -} - -func checkDefaultChannelInBundle(pkgName string, csvsInBundle []string, manifestResult validator.ManifestResult) validator.ManifestResult { - rawYaml, err := ioutil.ReadFile(pkgName) - if err != nil { - manifestResult.Errors = append(manifestResult.Errors, validator.IOError(fmt.Sprintf("Error in reading %s file: #%s ", pkgName, err), pkgName)) - return manifestResult - } - v := &PackageValidator{} - pkg, err := v.Unmarshal(rawYaml) - if err != nil { - manifestResult.Errors = append(manifestResult.Errors, validator.InvalidParse(fmt.Sprintf("Error unmarshalling YAML to package manifest type for %s file: #%s ", pkgName, err), pkgName)) - return manifestResult - } - if pkg, ok := pkg.(registry.PackageManifest); ok { - for _, channel := range pkg.Channels { - if !isStringPresent(csvsInBundle, channel.CurrentCSVName) { - manifestResult.Errors = append(manifestResult.Errors, validator.InvalidBundle(fmt.Sprintf("Error: currentCSV `%s` for channel name `%s` in package `%s` not found in manifest", channel.CurrentCSVName, channel.Name, pkg.PackageName), channel.CurrentCSVName)) - } - } - } - return manifestResult -} - -func validateOwnedCRDs(bundle ManifestBundle, csv v1alpha1.ClusterServiceVersion, manifestResult validator.ManifestResult) validator.ManifestResult { - ownedCrdNames := getOwnedCustomResourceDefintionNames(csv) - bundleCrdNames, err := getBundleCRDNames(bundle) - if err != (validator.Error{}) { - manifestResult.Errors = append(manifestResult.Errors, err) - return manifestResult - } - - // validating names - for _, ownedCrd := range ownedCrdNames { - if !bundleCrdNames[ownedCrd] { - manifestResult.Errors = append(manifestResult.Errors, validator.InvalidBundle(fmt.Sprintf("Error: owned crd (%s) not found in bundle %s", ownedCrd, bundle.Version), ownedCrd)) - } else { - delete(bundleCrdNames, ownedCrd) - } - } - // CRDs not defined in the CSV present in the bundle - if len(bundleCrdNames) != 0 { - for crd, _ := range bundleCrdNames { - manifestResult.Warnings = append(manifestResult.Warnings, validator.InvalidBundle(fmt.Sprintf("Warning: `%s` crd present in bundle `%s` not defined in csv", crd, bundle.Version), crd)) - } - } - return manifestResult -} - -type CRDObjectMeta struct { - metav1.ObjectMeta `json:"metadata"` -} - -func getOwnedCustomResourceDefintionNames(csv v1alpha1.ClusterServiceVersion) []string { - var names []string - for _, ownedCrd := range csv.Spec.CustomResourceDefinitions.Owned { - names = append(names, ownedCrd.Name) - } - return names -} - -func getBundleCRDNames(bundle ManifestBundle) (map[string]bool, validator.Error) { - bundleCrdNames := make(map[string]bool) - for _, crdFileName := range bundle.CRDs { - parsedName := CRDObjectMeta{} - rawYaml, err := ioutil.ReadFile(crdFileName) - if err != nil { - return nil, validator.IOError(fmt.Sprintf("Error in reading %s file: #%s ", crdFileName, err), crdFileName) - } - rawJson, err := yaml.YAMLToJSON(rawYaml) - if err != nil { - return nil, validator.InvalidParse(fmt.Sprintf("Error in converting to JSON for %s file: #%s ", crdFileName, err), crdFileName) - } - - if err := json.Unmarshal(rawJson, &parsedName); err != nil { - return nil, validator.InvalidParse(fmt.Sprintf("Error parsing object meta names for %s file: #%s ", crdFileName, err), crdFileName) - } - bundleCrdNames[parsedName.Name] = true - } - return bundleCrdNames, validator.Error{} -} - -func readAndUnmarshalCSV(pathCSV string) (v1alpha1.ClusterServiceVersion, validator.Error) { - rawYaml, err := ioutil.ReadFile(pathCSV) - if err != nil { - return v1alpha1.ClusterServiceVersion{}, validator.IOError(fmt.Sprintf("Error in reading %s file: #%s ", pathCSV, err), pathCSV) - } - v := &CSVValidator{} - csv, err := v.Unmarshal(rawYaml) - if err != nil { - return v1alpha1.ClusterServiceVersion{}, validator.InvalidParse(fmt.Sprintf("Error unmarshalling YAML to OLM's csv type for %s file: #%s ", pathCSV, err), pathCSV) - } - if csv, ok := csv.(v1alpha1.ClusterServiceVersion); ok { - return csv, validator.Error{} - } - return v1alpha1.ClusterServiceVersion{}, validator.InvalidParse(fmt.Sprintf("Error unmarshalling YAML to OLM's csv type for %s file: #%s ", pathCSV, err), pathCSV) -} - -// checkReplacesForCSVs generates an error if value of the `replaces` field in the -// csv does not match the `metadata.Name` field of the old csv to be replaced. -// It also generates a warning if the `replaces` field of a csv is empty. -func checkReplacesForCSVs(csvReplacesMap map[string]string, csvsInBundle []string, manifestResult validator.ManifestResult) validator.ManifestResult { - for pathCSV, replaces := range csvReplacesMap { - if replaces == "" { - manifestResult.Warnings = append(manifestResult.Warnings, validator.InvalidCSV(fmt.Sprintf("Warning: `spec.replaces` field not present in %s csv. If this csv replaces an old version, populate this field with the `metadata.Name` of the old csv", pathCSV))) - } else { - if !isStringPresent(csvsInBundle, replaces) { - manifestResult.Errors = append(manifestResult.Errors, validator.InvalidCSV(fmt.Sprintf("Error: `%s` mentioned in the `spec.replaces` field of %s csv not present in the manifest", replaces, pathCSV))) - } - } - } - return manifestResult -} - -func isStringPresent(list []string, val string) bool { - for _, str := range list { - if val == str { - return true - } - } - return false -} diff --git a/pkg/validate/crd.go b/pkg/validate/crd.go deleted file mode 100644 index e00e1fe9c..000000000 --- a/pkg/validate/crd.go +++ /dev/null @@ -1,88 +0,0 @@ -package validate - -import ( - "encoding/json" - "fmt" - "strings" - - "github.com/dweepgogia/new-manifest-verification/pkg/validate/validator" - "github.com/ghodss/yaml" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation" - "k8s.io/apimachinery/pkg/conversion" - "k8s.io/apimachinery/pkg/runtime" -) - -type CRDValidator struct { - fileName string - crds []v1beta1.CustomResourceDefinition -} - -var _ validator.Validator = &CRDValidator{} - -func (v *CRDValidator) Validate() (results []validator.ManifestResult) { - for _, crd := range v.crds { - scheme := runtime.NewScheme() - result := crdInspect(crd, scheme) - if result.Name == "" { - result.Name = crd.GetName() - } - results = append(results, result) - } - return results -} - -func (v *CRDValidator) AddObjects(objs ...interface{}) validator.Error { - for _, o := range objs { - switch t := o.(type) { - case v1beta1.CustomResourceDefinition: - v.crds = append(v.crds, t) - case *v1beta1.CustomResourceDefinition: - v.crds = append(v.crds, *t) - } - } - return validator.Error{} -} - -func (v CRDValidator) Name() string { - return "CustomResourceDefinition Validator" -} - -func (v CRDValidator) FileName() string { - return v.fileName -} - -func (v CRDValidator) Unmarshal(rawYaml []byte) (interface{}, error) { - var crd v1beta1.CustomResourceDefinition - - rawJson, err := yaml.YAMLToJSON(rawYaml) - if err != nil { - return v1beta1.CustomResourceDefinition{}, fmt.Errorf("error parsing raw YAML to Json: %s", err) - } - if err := json.Unmarshal(rawJson, &crd); err != nil { - return v1beta1.CustomResourceDefinition{}, fmt.Errorf("error parsing CRD (JSON) : %s", err) - } - return crd, nil -} - -func crdInspect(crd v1beta1.CustomResourceDefinition, scheme *runtime.Scheme) (manifestResult validator.ManifestResult) { - err := apiextensions.AddToScheme(scheme) - if err != nil { - return - } - err = v1beta1.AddToScheme(scheme) - if err != nil { - return - } - unversionedCRD := apiextensions.CustomResourceDefinition{} - scheme.Converter().Convert(&crd, &unversionedCRD, conversion.SourceToDest, nil) - errList := validation.ValidateCustomResourceDefinition(&unversionedCRD) - for _, err := range errList { - if !strings.Contains(err.Field, "openAPIV3Schema") && !strings.Contains(err.Field, "status") { - er := validator.Error{Type: validator.ErrorType(err.Type), Field: err.Field, BadValue: err.BadValue, Detail: err.Error()} - manifestResult.Errors = append(manifestResult.Errors, er) - } - } - return -} diff --git a/pkg/validate/csv.go b/pkg/validate/csv.go deleted file mode 100644 index e89eb3d75..000000000 --- a/pkg/validate/csv.go +++ /dev/null @@ -1,313 +0,0 @@ -package validate - -import ( - "encoding/json" - "fmt" - "reflect" - "strings" - - "github.com/dweepgogia/new-manifest-verification/pkg/validate/validator" - "github.com/ghodss/yaml" - "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type CSVValidator struct { - fileName string - csvs []v1alpha1.ClusterServiceVersion -} - -var _ validator.Validator = &CSVValidator{} - -func (v *CSVValidator) Validate() (results []validator.ManifestResult) { - for _, csv := range v.csvs { - result := csvInspect(csv) - if result.Name == "" { - result.Name = csv.GetName() - } - results = append(results, result) - } - return results -} - -func (v *CSVValidator) AddObjects(objs ...interface{}) validator.Error { - for _, o := range objs { - switch t := o.(type) { - case v1alpha1.ClusterServiceVersion: - v.csvs = append(v.csvs, t) - case *v1alpha1.ClusterServiceVersion: - v.csvs = append(v.csvs, *t) - } - } - return validator.Error{} -} - -func (v CSVValidator) Name() string { - return "ClusterServiceVersion Validator" -} - -func (v CSVValidator) FileName() string { - return v.fileName -} - -func (v CSVValidator) Unmarshal(rawYaml []byte) (interface{}, error) { - var csv v1alpha1.ClusterServiceVersion - - rawJson, err := yaml.YAMLToJSON(rawYaml) - if err != nil { - return v1alpha1.ClusterServiceVersion{}, fmt.Errorf("error parsing raw YAML to Json: %s", err) - } - if err := json.Unmarshal(rawJson, &csv); err != nil { - return v1alpha1.ClusterServiceVersion{}, fmt.Errorf("error parsing CSV (JSON) : %s", err) - } - return csv, nil -} - -// Iterates over the given CSV. Returns a ManifestResult type object. -func csvInspect(csv v1alpha1.ClusterServiceVersion) validator.ManifestResult { - - // validate example annotations ("alm-examples", "olm.examples"). - manifestResult := validateExamplesAnnotations(csv) - - // validate installModes - manifestResult = validateInstallModes(csv, manifestResult) - - // check missing optional/mandatory fields. - fieldValue := reflect.ValueOf(csv) - - switch fieldValue.Kind() { - case reflect.Struct: - return checkMissingFields(fieldValue, "", manifestResult) - default: - errs := []validator.Error{ - validator.InvalidCSV("Error: input file is not a valid CSV"), - } - - return validator.ManifestResult{Errors: errs, Warnings: nil} - } -} - -// Recursive function that traverses a nested struct passed in as reflect value, and reports for errors/warnings -// in case of null struct field values. -func checkMissingFields(v reflect.Value, parentStructName string, log validator.ManifestResult) validator.ManifestResult { - - for i := 0; i < v.NumField(); i++ { - - fieldValue := v.Field(i) - - tag := v.Type().Field(i).Tag.Get("json") - // Ignore fields that are subsets of a primitive field. - if tag == "" { - continue - } - - fields := strings.Split(tag, ",") - isOptionalField := containsStrict(fields, "omitempty") - emptyVal := isEmptyValue(fieldValue) - - newParentStructName := "" - if parentStructName == "" { - newParentStructName = v.Type().Field(i).Name - } else { - newParentStructName = parentStructName + "." + v.Type().Field(i).Name - } - - switch fieldValue.Kind() { - case reflect.Struct: - log = updateLog(log, "struct", newParentStructName, emptyVal, isOptionalField) - if emptyVal { - continue - } - log = checkMissingFields(fieldValue, newParentStructName, log) - default: - log = updateLog(log, "field", newParentStructName, emptyVal, isOptionalField) - } - } - return log -} - -// Returns updated error log with missing optional/mandatory field/struct objects. -func updateLog(log validator.ManifestResult, typeName string, newParentStructName string, emptyVal bool, isOptionalField bool) validator.ManifestResult { - - if emptyVal && isOptionalField { - // TODO: update the value field (typeName). - log.Warnings = append(log.Warnings, validator.OptionalFieldMissing(fmt.Sprintf("Warning: optional %s missing: (%s)", typeName, newParentStructName), newParentStructName, typeName)) - } else if emptyVal && !isOptionalField { - if newParentStructName != "Status" { - // TODO: update the value field (typeName). - log.Errors = append(log.Errors, validator.MandatoryFieldMissing(fmt.Sprintf("Error: mandatory %s missing: (%s)", typeName, newParentStructName), newParentStructName, typeName)) - } - } - return log -} - -// Takes in a string slice and checks if a string (x) is present in the slice. -// Return true if the string is present in the slice. -func containsStrict(a []string, x string) bool { - for _, n := range a { - if x == n { - return true - } - } - return false -} - -// Uses reflect package to check if the value of the object passed is null, returns a boolean accordingly. -func isEmptyValue(v reflect.Value) bool { - switch v.Kind() { - case reflect.Array, reflect.Map, reflect.Slice, reflect.String: - // Check if the value for 'Spec.InstallStrategy.StrategySpecRaw' field is present. This field is a RawMessage value type. Without a value, the key is explicitly set to 'null'. - if fieldValue, ok := v.Interface().(json.RawMessage); ok { - valString := string(fieldValue) - if valString == "null" { - return true - } - } - return v.Len() == 0 - // Currently the only CSV field with integer type is containerPort. Operator Verification Library raises a warning if containerPort field is missisng or if its value is 0. - // It is an optional field so the user can ignore the warning saying this field is missing if they intend to use port 0. - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return v.Int() == 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return v.Uint() == 0 - case reflect.Float32, reflect.Float64: - return v.Float() == 0 - case reflect.Interface, reflect.Ptr: - return v.IsNil() - case reflect.Struct: - for i, n := 0, v.NumField(); i < n; i++ { - if !isEmptyValue(v.Field(i)) { - return false - } - } - return true - default: - panic(fmt.Sprintf("%v kind is not supported.", v.Kind())) - } -} - -// validateExamplesAnnotations compares alm/olm example annotations with provided APIs given -// by Spec.CustomResourceDefinitions.Owned and Spec.APIServiceDefinitions.Owned. -func validateExamplesAnnotations(csv v1alpha1.ClusterServiceVersion) (manifestResult validator.ManifestResult) { - var examples []v1beta1.CustomResourceDefinition - var annotationsExamples string - annotations := csv.ObjectMeta.GetAnnotations() - // Return right away if no examples annotations are found. - if len(annotations) == 0 { - manifestResult.Warnings = append(manifestResult.Warnings, validator.InvalidCSV(fmt.Sprintf("Warning: example annotations not found for %s csv", csv.GetName()))) - return - } - // Expect either `alm-examples` or `olm.examples` but not both - // If both are present, `alm-examples` will be used - if value, ok := annotations["alm-examples"]; ok { - annotationsExamples = value - if _, ok = annotations["olm.examples"]; ok { - // both `alm-examples` and `olm.examples` are present - manifestResult.Warnings = append(manifestResult.Warnings, validator.InvalidCSV(fmt.Sprintf("Warning: both `alm-examples` and `olm.examples` are present in %s CSV. Defaulting to `alm-examples` and ignoring `olm.examples`", csv.GetName()))) - } - } else { - annotationsExamples = annotations["olm.examples"] - } - - // Can't find examples annotations, simply return - if annotationsExamples == "" { - manifestResult.Warnings = append(manifestResult.Warnings, validator.InvalidCSV(fmt.Sprintf("Warning: example annotations not found for %s csv", csv.GetName()))) - return - } - - if err := json.Unmarshal([]byte(annotationsExamples), &examples); err != nil { - manifestResult = getManifestResult(validator.InvalidParse(fmt.Sprintf("Error: parsing example annotations to %T type: %s ", examples, err), nil)) - return - } - - providedAPIs, manRes := getProvidedAPIs(csv, manifestResult) - - parsedExamples, manRes := parseExamplesAnnotations(examples, manifestResult) - if len(manRes.Errors) != 0 || len(manRes.Warnings) != 0 { - return manRes - } - - return matchGVKProvidedAPIs(parsedExamples, providedAPIs, manifestResult) -} - -func getProvidedAPIs(csv v1alpha1.ClusterServiceVersion, manifestResult validator.ManifestResult) (map[schema.GroupVersionKind]struct{}, validator.ManifestResult) { - provided := map[schema.GroupVersionKind]struct{}{} - - for _, owned := range csv.Spec.CustomResourceDefinitions.Owned { - parts := strings.SplitN(owned.Name, ".", 2) - if len(parts) < 2 { - manifestResult.Errors = append(manifestResult.Errors, validator.InvalidParse(fmt.Sprintf("Error: couldn't parse plural.group from crd name: %s", owned.Name), owned.Name)) - continue - } - provided[schema.GroupVersionKind{Group: parts[1], Version: owned.Version, Kind: owned.Kind}] = struct{}{} - } - - for _, api := range csv.Spec.APIServiceDefinitions.Owned { - provided[schema.GroupVersionKind{Group: api.Group, Version: api.Version, Kind: api.Kind}] = struct{}{} - } - - return provided, manifestResult -} - -func parseExamplesAnnotations(examples []v1beta1.CustomResourceDefinition, manifestResult validator.ManifestResult) (map[schema.GroupVersionKind]struct{}, validator.ManifestResult) { - parsed := map[schema.GroupVersionKind]struct{}{} - for _, value := range examples { - parts := strings.SplitN(value.APIVersion, "/", 2) - if len(parts) < 2 { - manifestResult.Errors = append(manifestResult.Errors, validator.InvalidParse(fmt.Sprintf("Error: couldn't parse group/version from crd kind: %s", value.Kind), value.Kind)) - continue - } - parsed[schema.GroupVersionKind{Group: parts[0], Version: parts[1], Kind: value.Kind}] = struct{}{} - } - - return parsed, manifestResult -} - -func matchGVKProvidedAPIs(examples map[schema.GroupVersionKind]struct{}, providedAPIs map[schema.GroupVersionKind]struct{}, manifestResult validator.ManifestResult) validator.ManifestResult { - for key := range examples { - if _, ok := providedAPIs[key]; !ok { - manifestResult.Errors = append(manifestResult.Errors, validator.InvalidOperation(fmt.Sprintf("Error: couldn't match %v in provided APIs list: %v", key, providedAPIs), key)) - continue - } - } - return manifestResult -} - -func getManifestResult(errs ...validator.Error) validator.ManifestResult { - errList := append([]validator.Error{}, errs...) - return validator.ManifestResult{Errors: errList, Warnings: nil} -} - -func validateInstallModes(csv v1alpha1.ClusterServiceVersion, manifestResult validator.ManifestResult) validator.ManifestResult { - // var installModeSet v1alpha1.InstallModeSet - installModeSet := make(v1alpha1.InstallModeSet) - for _, installMode := range csv.Spec.InstallModes { - if _, ok := installModeSet[installMode.Type]; ok { - manifestResult.Errors = append(manifestResult.Errors, validator.InvalidCSV(fmt.Sprintf("Error: duplicate install modes present in %s csv", csv.GetName()))) - } else { - installModeSet[installMode.Type] = installMode.Supported - } - } - - // installModes not found, return with a warning - if len(installModeSet) == 0 { - manifestResult.Warnings = append(manifestResult.Warnings, validator.InvalidCSV(fmt.Sprintf("Warning: install modes not found for %s csv", csv.GetName()))) - return manifestResult - } - - // all installModes should not be `false` - if checkAllFalseForInstallModeSet(installModeSet) { - manifestResult.Errors = append(manifestResult.Errors, validator.InvalidCSV(fmt.Sprintf("Error: none of InstallModeTypes are supported for %s csv", csv.GetName()))) - } - return manifestResult -} - -func checkAllFalseForInstallModeSet(installModeSet v1alpha1.InstallModeSet) bool { - for _, isSupported := range installModeSet { - if isSupported { - return false - } - } - return true -} diff --git a/pkg/validate/package.go b/pkg/validate/package.go deleted file mode 100644 index 6813c072b..000000000 --- a/pkg/validate/package.go +++ /dev/null @@ -1,91 +0,0 @@ -package validate - -import ( - "encoding/json" - "fmt" - - "github.com/dweepgogia/new-manifest-verification/pkg/validate/validator" - "github.com/ghodss/yaml" - "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" -) - -type PackageValidator struct { - fileName string - pkgs []registry.PackageManifest -} - -var _ validator.Validator = &PackageValidator{} - -// packageManifest is an alias of `registry.PackageManifest` used -// to define new methods on the non-local type. -type packageManifest registry.PackageManifest - -func (p packageManifest) getPkgName() string { - return p.PackageName -} - -func (v *PackageValidator) Validate() (results []validator.ManifestResult) { - for _, pkg := range v.pkgs { - result := pkgInspect(pkg) - if result.Name == "" { - result.Name = packageManifest(pkg).getPkgName() - } - results = append(results, result) - } - return results -} - -func (v *PackageValidator) AddObjects(objs ...interface{}) validator.Error { - for _, o := range objs { - switch t := o.(type) { - case registry.PackageManifest: - v.pkgs = append(v.pkgs, t) - case *registry.PackageManifest: - v.pkgs = append(v.pkgs, *t) - } - } - return validator.Error{} -} - -func (v PackageValidator) Name() string { - return "Package Validator" -} - -func (v PackageValidator) FileName() string { - return v.fileName -} - -func (v PackageValidator) Unmarshal(rawYaml []byte) (interface{}, error) { - var pkg registry.PackageManifest - - rawJson, err := yaml.YAMLToJSON(rawYaml) - if err != nil { - return registry.PackageManifest{}, fmt.Errorf("error parsing raw YAML to Json: %s", err) - } - if err := json.Unmarshal(rawJson, &pkg); err != nil { - return registry.PackageManifest{}, fmt.Errorf("error parsing package type (JSON) : %s", err) - } - return pkg, nil -} - -func pkgInspect(pkg registry.PackageManifest) (manifestResult validator.ManifestResult) { - manifestResult = validator.ManifestResult{} - present, manifestResult := isDefaultPresent(pkg, manifestResult) - if !present { - manifestResult.Errors = append(manifestResult.Errors, validator.InvalidDefaultChannel(fmt.Sprintf("Error: default channel %s not found in the list of declared channels", pkg.DefaultChannelName), pkg.DefaultChannelName)) - } - return -} - -func isDefaultPresent(pkg registry.PackageManifest, manifestResult validator.ManifestResult) (bool, validator.ManifestResult) { - present := false - for _, channel := range pkg.Channels { - if pkg.DefaultChannelName == "" { - manifestResult.Warnings = append(manifestResult.Warnings, validator.InvalidDefaultChannel(fmt.Sprintf("Warning: default channel not found in %s package manifest", pkg.PackageName), pkg.PackageName)) - return true, manifestResult - } else if pkg.DefaultChannelName == channel.Name { - present = true - } - } - return present, manifestResult -} diff --git a/pkg/validate/parser.go b/pkg/validate/parser.go deleted file mode 100644 index da1e1b0e1..000000000 --- a/pkg/validate/parser.go +++ /dev/null @@ -1,158 +0,0 @@ -package validate - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - - "github.com/dweepgogia/new-manifest-verification/pkg/validate/validator" - "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/util/yaml" - yamlForUnmarshalStrict "sigs.k8s.io/yaml" -) - -// Manifest represents files in the operator manifest. -type Manifest struct { - Name string - // Package stores the name (path) of package yaml file. - Package string - // Bundle represents a directory of files with one `ClusterServiceVersion`. - Bundle map[string]ManifestBundle -} - -type ManifestBundle struct { - // Version stores the CSV version for the bundle. - Version string - // List of CustomResourceDefinition file names inside the bundle. - CRDs []string - // CSV file name in the bundle. - CSV string -} - -// getFileType identifies the file type and returns it as a string. -func getFileType(filePath string) (string, error) { - - rawYaml, err := ioutil.ReadFile(filePath) - if err != nil { - return "", fmt.Errorf("Error in reading %s file", filePath) - } - pkg := registry.PackageManifest{} - - if checkFileTypeWithUnmarshalStrict(rawYaml, &pkg) { - return "Package", nil - } - - kind, err := getKindFromFileBytes(rawYaml, filePath) - if err != nil { - return "", err - } - return kind, nil -} - -func getKindFromFileBytes(rawYaml []byte, filePath string) (string, error) { - u := unstructured.Unstructured{} - r := bytes.NewReader(rawYaml) - dec := yaml.NewYAMLOrJSONDecoder(r, 8) - // There is only one YAML doc if there are no more bytes to be read or EOF - // is hit. - if err := dec.Decode(&u); err == nil && r.Len() != 0 { - return "", fmt.Errorf("error getting TypeMeta from bytes: more than one manifest in bytes") - } else if err != nil && err != io.EOF { - return "", fmt.Errorf("error getting TypeMeta from bytes") - } - return u.GetKind(), nil -} - -func checkFileTypeWithUnmarshalStrict(rawYaml []byte, obj interface{}) bool { - if err := yamlForUnmarshalStrict.UnmarshalStrict(rawYaml, &obj); err != nil { - return false - } - return true -} - -// ParseDir walks through the operator manifest directory, checks its format, -// and populates the Manifest object with relevant file names. -func ParseDir(manifestDirectory string) (Manifest, validator.ManifestResult) { - - countPkg := 0 - manifest := Manifest{} - manifest.Name = manifestDirectory - visitedFile := map[string]struct{}{} - manifestResult := validator.ManifestResult{} - isManifestResultNameSet := false - manifest.Bundle = make(map[string]ManifestBundle) - // parse manifest directory structure - err := filepath.Walk(manifestDirectory, func(path string, f os.FileInfo, err error) error { - - // set manifest name - if !isManifestResultNameSet { - manifestResult.Name = filepath.Base(path) - isManifestResultNameSet = true - } - // create a manifest bundle for each version in the manifest - if f.IsDir() && path != manifestDirectory { - if _, ok := manifest.Bundle[path]; !ok { - bundle := ManifestBundle{} - bundle.Version = f.Name() - manifest.Bundle[path] = bundle - } - } else if !f.IsDir() { - fileType, err := getFileType(path) - if err != nil { - updateErr := fmt.Sprintf("Error: %s file may not be of ClusterServiceVersion, CustomResourceDefinition, or Package yaml type. If it is supposed to be ClusterServiceVersion or CustomResourceDefinition type, make sure the TypeMeta is correctly defined. If this is a package yaml, instead, make sure it follows the PackageManifest type definition", path) - manifestResult.Errors = append(manifestResult.Errors, validator.InvalidManifestStructure(updateErr)) - return nil - } - - directoryPath := filepath.Dir(path) - switch fileType { - case "ClusterServiceVersion": - if _, ok := visitedFile[directoryPath]; ok { - manifestResult.Errors = append(manifestResult.Errors, validator.InvalidManifestStructure(fmt.Sprintf("Error: more than one CSV in the bundle found at %s bundle", directoryPath))) - return nil - } else { - visitedFile[directoryPath] = struct{}{} - } - if bundleObj, ok := manifest.Bundle[directoryPath]; ok { - bundleObj.CSV = path - manifest.Bundle[directoryPath] = bundleObj - } else { - manifestResult.Errors = append(manifestResult.Errors, validator.InvalidManifestStructure(fmt.Sprintf("Error: %s file at %s path does not align with the operator manifest format", f.Name(), path))) - return nil - } - case "CustomResourceDefinition": - if bundleObj, ok := manifest.Bundle[directoryPath]; ok { - bundleObj.CRDs = append(bundleObj.CRDs, path) - manifest.Bundle[directoryPath] = bundleObj - } else { - manifestResult.Errors = append(manifestResult.Errors, validator.InvalidManifestStructure(fmt.Sprintf("Error: %s file at %s path does not align with the operator manifest format", f.Name(), path))) - return nil - } - case "Package": - countPkg++ - if countPkg > 1 { - manifestResult.Errors = append(manifestResult.Errors, validator.InvalidManifestStructure(fmt.Sprintf("Error: more than one package yaml file in the manifest; found at %s", path))) - return nil - } - manifest.Package = path - return nil - default: - // return a `Warning` for files other than CSV, CRD, package yaml present in the manifest. If required, we can keep a list of these file - // paths and remove them from the manifest. - manifestResult.Warnings = append(manifestResult.Warnings, validator.InvalidManifestStructure(fmt.Sprintf("Warning: %s file at %s path is not a ClusterServiceVersion, CustomResourceDefinition, or Package yaml type", f.Name(), path))) - } - } - return nil - }) - if err != nil { - fmt.Printf("walk error [%v]\n", err) - } - if countPkg == 0 { - manifestResult.Errors = append(manifestResult.Errors, validator.InvalidManifestStructure(fmt.Sprintf("Error: no package yaml in `%s` manifest", manifestDirectory))) - } - return manifest, manifestResult -} diff --git a/pkg/validate/serialize.go b/pkg/validate/serialize.go deleted file mode 100644 index 9d84771a0..000000000 --- a/pkg/validate/serialize.go +++ /dev/null @@ -1,127 +0,0 @@ -package validate - -import ( - "fmt" - "io/ioutil" - - "github.com/dweepgogia/new-manifest-verification/pkg/validate/validator" -) - -func Validate(v validator.Validator) (manifestResult validator.ManifestResult) { - fmt.Printf("\nRunning %s\n", v.Name()) - fmt.Printf("Validating %s\n\n", v.FileName()) - rawYaml, err := ioutil.ReadFile(v.FileName()) - if err != nil { - manifestResult.Errors = append(manifestResult.Errors, validator.IOError(fmt.Sprintf("Error in reading %s file: #%s ", v.FileName(), err), v.FileName())) - getErrorsFromManifestResult(manifestResult.Errors) - return - } - - // Value returned is a marshaled go type. - unmarshalledObject, err := v.Unmarshal(rawYaml) - if err != nil { - manifestResult.Errors = append(manifestResult.Errors, validator.InvalidParse(fmt.Sprintf("Error unmarshalling YAML for %s file: #%s ", v.FileName(), err), v.FileName())) - getErrorsFromManifestResult(manifestResult.Errors) - return - } - - if err := v.AddObjects(unmarshalledObject); err != (validator.Error{}) { - manifestResult.Errors = append(manifestResult.Errors, err) - getErrorsFromManifestResult(manifestResult.Errors) - return // TODO: update when 'AddObjects' returns an actual error. - } - - for _, errorLog := range v.Validate() { - - getErrorsFromManifestResult(errorLog.Warnings) - - if len(errorLog.Errors) != 0 { - fmt.Println() - getErrorsFromManifestResult(errorLog.Errors) - } else { - fmt.Printf("\n%s is verified\n", v.FileName()) - } - } - return -} - -// Iterates over the list of warnings and errors. -func getErrorsFromManifestResult(err []validator.Error) { - for _, v := range err { - assertTypeToGetValue(v) - } -} - -// Asserts type to get the underlying field value. -func assertTypeToGetValue(v interface{}) { - if v, ok := v.(validator.Error); ok { - fmt.Println(v.String()) - } -} - -func validateBundle(manifest Manifest) []validator.ManifestResult { - v := &BundleValidator{Manifest: manifest} - manifestResult := v.Validate() - for _, errorLog := range manifestResult { - fmt.Printf("\nValidating `%s` Manifest\n", errorLog.Name) - fmt.Println() - if len(errorLog.Warnings) != 0 { - getErrorsFromManifestResult(errorLog.Warnings) - } - - if len(errorLog.Errors) != 0 { - fmt.Println() - getErrorsFromManifestResult(errorLog.Errors) - fmt.Printf("Invalid manifest: `%s`\n", errorLog.Name) - } else { - fmt.Printf("`%s` manifest verified", errorLog.Name) - } - } - return manifestResult -} - -func parseManifestDirectory(manifestDirectory string) (Manifest, []validator.ManifestResult) { - manifestResultList := []validator.ManifestResult{} - fmt.Printf("Parsing `%s` operator manifest\n\n", manifestDirectory) - manifest, manifestResultFromDirectoryParse := ParseDir(manifestDirectory) - - if len(manifestResultFromDirectoryParse.Errors) != 0 || len(manifestResultFromDirectoryParse.Warnings) != 0 { - manifestResultList = append(manifestResultList, manifestResultFromDirectoryParse) - getErrorsFromManifestResult(manifestResultFromDirectoryParse.Warnings) - if len(manifestResultFromDirectoryParse.Errors) != 0 { - getErrorsFromManifestResult(manifestResultFromDirectoryParse.Errors) - fmt.Printf("Invalid operator manifest structure for `%s`\n", manifestDirectory) - return Manifest{}, manifestResultList - } - } - return manifest, manifestResultList -} - -func ValidateManifest(manifestDirectory string) []validator.ManifestResult { - // parse manifest directory - manifest, manifestResultList := parseManifestDirectory(manifestDirectory) - for _, manifestResult := range manifestResultList { - if len(manifestResult.Errors) != 0 { - return manifestResultList - } - } - - var result []validator.ManifestResult - // validate individual bundle files - for _, bundle := range manifest.Bundle { - validators := []validator.Validator{&CSVValidator{fileName: bundle.CSV}} - for _, crd := range bundle.CRDs { - validators = append(validators, &CRDValidator{fileName: crd}) - } - for _, validator := range validators { - result = append(result, Validate(validator)) - } - } - var pkgValidator validator.Validator - pkgValidator = &PackageValidator{fileName: manifest.Package} - Validate(pkgValidator) - - // validate bundle - validateBundle(manifest) - return []validator.ManifestResult{} -} diff --git a/pkg/validate/validator/error.go b/pkg/validate/validator/error.go deleted file mode 100644 index a52637dc4..000000000 --- a/pkg/validate/validator/error.go +++ /dev/null @@ -1,133 +0,0 @@ -package validator - -import ( - "fmt" -) - -// ManifestResult represents verification result for each of the yaml files -// from the operator manifest. -type ManifestResult struct { - // Name is some piece of information identifying the manifest. This should - // usually be set to object.GetName(). - Name string - // Errors pertain to issues with the manifest that must be corrected. - Errors []Error - // Warnings pertain to issues with the manifest that are optional to correct. - Warnings []Error -} - -// Error is an implementation of the 'error' interface, which represents a -// warning or an error in a yaml file. Error type is taken as is from -// https://github.com/operator-framework/operator-registry/blob/master/vendor/k8s.io/apimachinery/pkg/util/validation/field/errors.go#L31 -// to maintain compatibility with upstream. -type Error struct { - // Type is the ErrorType string constant that represents the kind of - // error, ex. "MandatoryStructMissing", "I/O". - Type ErrorType - // Field is the dot-hierarchical YAML path of the missing data. - Field string - // BadValue is the field or file that caused an error or warning. - BadValue interface{} - // Detail represents the error message as a string. - Detail string -} - -func (err Error) String() string { - return err.Error() -} - -type ErrorType string - -func InvalidBundle(detail string, value interface{}) Error { - return Error{ErrorInvalidBundle, "", value, detail} -} - -func InvalidManifestStructure(detail string) Error { - return Error{ErrorInvalidManifestStructure, "", "", detail} -} - -func InvalidCSV(detail string) Error { - return Error{ErrorInvalidCSV, "", "", detail} -} - -func OptionalFieldMissing(detail string, field string, value interface{}) Error { - return Error{WarningFieldMissing, field, value, detail} -} - -func MandatoryFieldMissing(detail string, field string, value interface{}) Error { - return Error{ErrorFieldMissing, field, value, detail} -} - -func UnsupportedType(detail string) Error { - return Error{ErrorUnsupportedType, "", "", detail} -} - -// TODO: see if more information can be extracted out of 'unmarshall/parsing' errors. -func InvalidParse(detail string, value interface{}) Error { - return Error{ErrorInvalidParse, "", value, detail} -} - -func InvalidDefaultChannel(detail string, value interface{}) Error { - return Error{ErrorInvalidDefaultChannel, "", value, detail} -} - -func IOError(detail string, value interface{}) Error { - return Error{ErrorIO, "", value, detail} -} - -func FailedValidation(detail string, value interface{}) Error { - return Error{ErrorFailedValidation, "", value, detail} -} - -func InvalidOperation(detail string, value interface{}) Error { - return Error{ErrorInvalidOperation, "", value, detail} -} - -const ( - ErrorInvalidCSV ErrorType = "CSVFileNotValid" - WarningFieldMissing ErrorType = "OptionalFieldNotFound" - ErrorFieldMissing ErrorType = "MandatoryFieldNotFound" - ErrorUnsupportedType ErrorType = "FieldTypeNotSupported" - ErrorInvalidParse ErrorType = "Unmarshall/ParseError" - ErrorIO ErrorType = "FileReadError" - ErrorFailedValidation ErrorType = "ValidationFailed" - ErrorInvalidOperation ErrorType = "OperationFailed" - ErrorInvalidManifestStructure ErrorType = "ManifestStructureNotValid" - ErrorInvalidBundle ErrorType = "BundleNotValid" - ErrorInvalidDefaultChannel ErrorType = "DefaultChannelNotValid" -) - -// String converts a ErrorType into its corresponding canonical error message. -func (t ErrorType) String() string { - switch t { - case ErrorInvalidCSV: - return "CSV file not valid" - case WarningFieldMissing: - return "Optional field not found" - case ErrorFieldMissing: - return "Mandatory field not found" - case ErrorUnsupportedType: - return "Field type not supported" - case ErrorInvalidParse: - return "Unmarshall/Parse error" - case ErrorIO: - return "File read error" - case ErrorFailedValidation: - return "Validation failed" - case ErrorInvalidOperation: - return "Operation failed" - case ErrorInvalidManifestStructure: - return "Manifest directory structure not valid" - case ErrorInvalidBundle: - return "Manifest bundle not valid" - case ErrorInvalidDefaultChannel: - return "Default channel not valid" - default: - panic(fmt.Sprintf("Unrecognized validation error: %q", string(t))) - } -} - -// Error strut implements the 'error' interface to define custom error formatting. -func (err Error) Error() string { - return err.Detail -} diff --git a/pkg/validate/validator/validator.go b/pkg/validate/validator/validator.go deleted file mode 100644 index 4c56a2b9a..000000000 --- a/pkg/validate/validator/validator.go +++ /dev/null @@ -1,53 +0,0 @@ -package validator - -// Validator is an interface for implementing a validator of a single -// Kubernetes object type. Ideally each Validator will check one aspect of -// an object, or perform several steps that have a common theme or goal. -type Validator interface { - // Validate should run validation logic on an arbitrary object, and return - // a one ManifestResult for each object that did not pass validation. - // TODO: use pointers - Validate() []ManifestResult - // AddObjects adds objects to the Validator. Each object will be validated - // when Validate() is called. - AddObjects(...interface{}) Error - // Name should return a succinct name for this validator. - Name() string - // FileName returns the file name of the object to be validated. - FileName() string - // Unmarshal returns the unmarshalled object of caller's underlying type. - Unmarshal([]byte) (interface{}, error) -} - -// ValidatorSet contains a set of Validators to be executed sequentially. -// TODO: add configurable logger. -type ValidatorSet struct { - validators []Validator -} - -// NewValidatorSet creates a ValidatorSet containing vs. -func NewValidatorSet(vs ...Validator) *ValidatorSet { - set := &ValidatorSet{} - set.AddValidators(vs...) - return set -} - -// AddValidators adds each unique Validator in vs to the receiver. -func (set *ValidatorSet) AddValidators(vs ...Validator) { - seenNames := map[string]struct{}{} - for _, v := range vs { - if _, seen := seenNames[v.Name()]; !seen { - set.validators = append(set.validators, v) - seenNames[v.Name()] = struct{}{} - } - } -} - -// ValidateAll runs each Validator in the receiver and returns all results. -func (set ValidatorSet) ValidateAll() (allResults []ManifestResult) { - for _, v := range set.validators { - results := v.Validate() - allResults = append(allResults, results...) - } - return allResults -} diff --git a/pkg/validation/doc.go b/pkg/validation/doc.go new file mode 100644 index 000000000..fdc867312 --- /dev/null +++ b/pkg/validation/doc.go @@ -0,0 +1,15 @@ +// This package defines the valid Operator manifests directory format +// by exposing a set of interfaces.Validator's to verify a directory and +// its constituent manifests. A manifests directory consists of a +// Package Manifest and a set of versioned Bundles. Each Bundle contains a +// ClusterServiceVersion and one or more CustomResourceDefinition's. +// +// Errors and warnings, both represented by the errors.Error type, are +// returned by exported functions for missing mandatory and optional fields, +// respectively. Each Error implements the error interface. +// +// Manifest and Bundle format: https://github.com/operator-framework/operator-registry/#manifest-format +// ClusterServiceVersion documentation: https://github.com/operator-framework/operator-lifecycle-manager/blob/master/Documentation/design/building-your-csv.md +// Package manifest documentation: https://github.com/operator-framework/operator-lifecycle-manager#discovery-catalogs-and-automated-upgrades +// CustomResourceDefinition documentation: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/ +package validation diff --git a/pkg/validation/errors/error.go b/pkg/validation/errors/error.go new file mode 100644 index 000000000..f1469f1c8 --- /dev/null +++ b/pkg/validation/errors/error.go @@ -0,0 +1,234 @@ +package errors + +import ( + "fmt" +) + +// ManifestResult represents verification result for each of the yaml files +// from the operator manifest. +type ManifestResult struct { + // Name is some piece of information identifying the manifest. + Name string + // Errors pertain to issues with the manifest that must be corrected. + Errors []Error + // Warnings pertain to issues with the manifest that are optional to correct. + Warnings []Error +} + +// Add appends errs to r in either r.Errors or r.Warnings depending on an +// error's Level. +func (r *ManifestResult) Add(errs ...Error) { + for _, err := range errs { + if err.Level == LevelError { + r.Errors = append(r.Errors, err) + } else { + r.Warnings = append(r.Warnings, err) + } + } +} + +// HasError returns true if r has any Errors of Level == LevelError. +func (r ManifestResult) HasError() bool { + return len(r.Errors) != 0 +} + +// HasWarn returns true if r has any Errors of Level == LevelWarn. +func (r ManifestResult) HasWarn() bool { + return len(r.Warnings) != 0 +} + +// QUESTION: use field.Error instead of our own implementation? seems like most +// of what we want is already in the field package: +// https://godoc.org/k8s.io/apimachinery/pkg/util/validation/field + +// Error is an implementation of the 'error' interface, which represents a +// warning or an error in a yaml file. Error type is taken as is from +// https://github.com/operator-framework/operator-registry/blob/master/vendor/k8s.io/apimachinery/pkg/util/validation/field/errors.go#L31 +// to maintain compatibility with upstream. +type Error struct { + // Type is the ErrorType string constant that represents the kind of + // error, ex. "MandatoryStructMissing", "I/O". + Type ErrorType + // Level is the severity of the Error. + Level Level + // Field is the dot-hierarchical YAML path of the missing data. + Field string + // BadValue is the field or file that caused an error or warning. + BadValue interface{} + // Detail represents the error message as a string. + Detail string +} + +// Error implements the 'error' interface to define custom error formatting. +func (e Error) Error() string { + detail := e.Detail + if detail != "" { + detail = fmt.Sprintf(": %s", detail) + } + if e.Field != "" && e.BadValue != nil { + detail = fmt.Sprintf("Field %s, Value %v%s", e.Field, e.BadValue, detail) + } else if e.Field != "" { + detail = fmt.Sprintf("Field %s%s", e.Field, detail) + } else if e.BadValue != nil { + detail = fmt.Sprintf("Value %v%s", e.BadValue, detail) + } + if detail != "" { + return fmt.Sprintf("%s: %s", e.Level, detail) + } + return "ErrMessageMissing" +} + +// Level is the severity of an Error. +type Level string + +const ( + // LevelWarn is for Errors that should be addressed but do not have to be. + LevelWarn = "Warning" + // LevelError is for Errors that must be addressed. + LevelError = "Error" +) + +// ErrorType defines what the error resulted from. +type ErrorType string + +const ( + ErrorInvalidCSV ErrorType = "CSVFileNotValid" + ErrorFieldMissing ErrorType = "FieldNotFound" + ErrorUnsupportedType ErrorType = "FieldTypeNotSupported" + ErrorInvalidParse ErrorType = "ParseError" + ErrorIO ErrorType = "FileReadError" + ErrorFailedValidation ErrorType = "ValidationFailed" + ErrorInvalidOperation ErrorType = "OperationFailed" + ErrorInvalidManifestStructure ErrorType = "ManifestStructureNotValid" + ErrorInvalidBundle ErrorType = "BundleNotValid" + ErrorInvalidPackageManifest ErrorType = "PackageManifestNotValid" +) + +func NewError(t ErrorType, detail, field string, v interface{}) Error { + return Error{t, LevelError, field, v, detail} +} + +func NewWarn(t ErrorType, detail, field string, v interface{}) Error { + return Error{t, LevelWarn, field, v, detail} +} + +func ErrInvalidBundle(detail string, value interface{}) Error { + return invalidBundle(LevelError, detail, value) +} + +func WarnInvalidBundle(detail string, value interface{}) Error { + return invalidBundle(LevelError, detail, value) +} + +func invalidBundle(lvl Level, detail string, value interface{}) Error { + return Error{ErrorInvalidBundle, lvl, "", value, detail} +} + +func ErrInvalidManifestStructure(detail string) Error { + return invalidManifestStructure(LevelError, detail) +} + +func WarnInvalidManifestStructure(detail string) Error { + return invalidManifestStructure(LevelWarn, detail) +} + +func invalidManifestStructure(lvl Level, detail string) Error { + return Error{ErrorInvalidManifestStructure, lvl, "", "", detail} +} + +func ErrInvalidCSV(detail, csvName string) Error { + return invalidCSV(LevelError, detail, csvName) +} + +func WarnInvalidCSV(detail, csvName string) Error { + return invalidCSV(LevelWarn, detail, csvName) +} + +func invalidCSV(lvl Level, detail, csvName string) Error { + return Error{ErrorInvalidCSV, lvl, "", "", fmt.Sprintf("(%s) %s", csvName, detail)} +} + +func ErrFieldMissing(detail string, field string, value interface{}) Error { + return fieldMissing(LevelError, detail, field, value) +} + +func WarnFieldMissing(detail string, field string, value interface{}) Error { + return fieldMissing(LevelWarn, detail, field, value) +} + +func fieldMissing(lvl Level, detail string, field string, value interface{}) Error { + return Error{ErrorFieldMissing, lvl, field, value, detail} +} + +func ErrUnsupportedType(detail string) Error { + return unsupportedType(LevelError, detail) +} + +func WarnUnsupportedType(detail string) Error { + return unsupportedType(LevelWarn, detail) +} + +func unsupportedType(lvl Level, detail string) Error { + return Error{ErrorUnsupportedType, lvl, "", "", detail} +} + +// TODO: see if more information can be extracted out of 'unmarshall/parsing' errors. +func ErrInvalidParse(detail string, value interface{}) Error { + return invalidParse(LevelError, detail, value) +} + +func WarnInvalidParse(detail string, value interface{}) Error { + return invalidParse(LevelWarn, detail, value) +} + +func invalidParse(lvl Level, detail string, value interface{}) Error { + return Error{ErrorInvalidParse, lvl, "", value, detail} +} + +func ErrInvalidPackageManifest(detail string, pkgName string) Error { + return invalidPackageManifest(LevelError, detail, pkgName) +} + +func WarnInvalidPackageManifest(detail string, pkgName string) Error { + return invalidPackageManifest(LevelWarn, detail, pkgName) +} + +func invalidPackageManifest(lvl Level, detail string, pkgName string) Error { + return Error{ErrorInvalidPackageManifest, lvl, "", "", fmt.Sprintf("(%s) %s", pkgName, detail)} +} + +func ErrIOError(detail string, value interface{}) Error { + return iOError(LevelError, detail, value) +} + +func WarnIOError(detail string, value interface{}) Error { + return iOError(LevelWarn, detail, value) +} + +func iOError(lvl Level, detail string, value interface{}) Error { + return Error{ErrorIO, lvl, "", value, detail} +} + +func ErrFailedValidation(detail string, value interface{}) Error { + return failedValidation(LevelError, detail, value) +} + +func WarnFailedValidation(detail string, value interface{}) Error { + return failedValidation(LevelWarn, detail, value) +} + +func failedValidation(lvl Level, detail string, value interface{}) Error { + return Error{ErrorFailedValidation, lvl, "", value, detail} +} + +func ErrInvalidOperation(detail string, value interface{}) Error { + return invalidOperation(LevelError, detail, value) +} + +func WarnInvalidOperation(detail string, value interface{}) Error { + return invalidOperation(LevelWarn, detail, value) +} + +func invalidOperation(lvl Level, detail string, value interface{}) Error { + return Error{ErrorInvalidOperation, lvl, "", value, detail} +} diff --git a/pkg/validation/interfaces/validator.go b/pkg/validation/interfaces/validator.go new file mode 100644 index 000000000..4d2e6f7c1 --- /dev/null +++ b/pkg/validation/interfaces/validator.go @@ -0,0 +1,24 @@ +package validator + +import ( + "github.com/operator-framework/api/pkg/validation/errors" +) + +// Validator is an interface for validating arbitrary objects. +type Validator interface { + // Validate takes a list of arbitrary objects and returns a slice of results, + // one for each object validated. + Validate(...interface{}) []errors.ManifestResult +} + +// Validators is a set of Validator's that can be run via Apply. +type Validators []Validator + +// ApplyParallel invokes each Validator in vals, and collects and returns +// the results. +func (vals Validators) Apply(objs ...interface{}) (results []errors.ManifestResult) { + for _, validator := range vals { + results = append(results, validator.Validate(objs...)...) + } + return results +} diff --git a/pkg/validation/internal/bundle.go b/pkg/validation/internal/bundle.go new file mode 100644 index 000000000..67ee538f8 --- /dev/null +++ b/pkg/validation/internal/bundle.go @@ -0,0 +1,93 @@ +package internal + +import ( + "encoding/json" + "fmt" + + "github.com/operator-framework/api/pkg/validation/errors" + + operatorsv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" + "github.com/operator-framework/operator-registry/pkg/registry" +) + +type BundleValidator struct{} + +func (f BundleValidator) Validate(objs ...interface{}) (results []errors.ManifestResult) { + for _, obj := range objs { + switch v := obj.(type) { + case *registry.Bundle: + results = append(results, validateBundle(v)) + } + } + return results +} + +func validateBundle(bundle *registry.Bundle) (result errors.ManifestResult) { + bcsv, err := bundle.ClusterServiceVersion() + if err != nil { + result.Add(errors.ErrInvalidParse("error getting bundle CSV", err)) + return result + } + csv, rerr := bundleCSVToCSV(bcsv) + if rerr != (errors.Error{}) { + result.Add(rerr) + return result + } + result = validateOwnedCRDs(bundle, csv) + result.Name = csv.Spec.Version.String() + return result +} + +func bundleCSVToCSV(bcsv *registry.ClusterServiceVersion) (*operatorsv1alpha1.ClusterServiceVersion, errors.Error) { + spec := operatorsv1alpha1.ClusterServiceVersionSpec{} + if err := json.Unmarshal(bcsv.Spec, &spec); err != nil { + return nil, errors.ErrInvalidParse(fmt.Sprintf("converting bundle CSV %q", bcsv.GetName()), err) + } + return &operatorsv1alpha1.ClusterServiceVersion{ + TypeMeta: bcsv.TypeMeta, + ObjectMeta: bcsv.ObjectMeta, + Spec: spec, + }, errors.Error{} +} + +func validateOwnedCRDs(bundle *registry.Bundle, csv *operatorsv1alpha1.ClusterServiceVersion) (result errors.ManifestResult) { + ownedCrdNames := getOwnedCustomResourceDefintionNames(csv) + crdNames, err := getBundleCRDNames(bundle) + if err != (errors.Error{}) { + result.Add(err) + return result + } + + // validating names + for _, crdName := range ownedCrdNames { + if _, ok := crdNames[crdName]; !ok { + result.Add(errors.ErrInvalidBundle(fmt.Sprintf("owned CRD %q not found in bundle %q", crdName, bundle.Name), crdName)) + } else { + delete(crdNames, crdName) + } + } + // CRDs not defined in the CSV present in the bundle + for crdName := range crdNames { + result.Add(errors.WarnInvalidBundle(fmt.Sprintf("owned CRD %q is present in bundle %q but not defined in CSV", crdName, bundle.Name), crdName)) + } + return result +} + +func getOwnedCustomResourceDefintionNames(csv *operatorsv1alpha1.ClusterServiceVersion) (names []string) { + for _, ownedCrd := range csv.Spec.CustomResourceDefinitions.Owned { + names = append(names, ownedCrd.Name) + } + return names +} + +func getBundleCRDNames(bundle *registry.Bundle) (map[string]struct{}, errors.Error) { + crds, err := bundle.CustomResourceDefinitions() + if err != nil { + return nil, errors.ErrInvalidParse("error getting bundle CRDs", err) + } + crdNames := map[string]struct{}{} + for _, crd := range crds { + crdNames[crd.GetName()] = struct{}{} + } + return crdNames, errors.Error{} +} diff --git a/pkg/validation/internal/crd.go b/pkg/validation/internal/crd.go new file mode 100644 index 000000000..15e94f4c6 --- /dev/null +++ b/pkg/validation/internal/crd.go @@ -0,0 +1,54 @@ +package internal + +import ( + "strings" + + "github.com/operator-framework/api/pkg/validation/errors" + + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/client-go/kubernetes/scheme" +) + +var Scheme = scheme.Scheme + +func init() { + install.Install(Scheme) +} + +type CRDValidator struct{} + +func (f CRDValidator) Validate(objs ...interface{}) (results []errors.ManifestResult) { + for _, obj := range objs { + switch v := obj.(type) { + case *v1beta1.CustomResourceDefinition: + results = append(results, validateCRD(v)) + } + } + return results +} + +func validateCRD(crd interface{}) (result errors.ManifestResult) { + unversionedCRD := apiextensions.CustomResourceDefinition{} + err := Scheme.Converter().Convert(&crd, &unversionedCRD, conversion.SourceToDest, nil) + if err != nil { + result.Add(errors.ErrInvalidParse("error converting versioned crd to unversioned crd", err)) + return result + } + result = validateCRDUnversioned(&unversionedCRD) + result.Name = unversionedCRD.GetName() + return result +} + +func validateCRDUnversioned(crd *apiextensions.CustomResourceDefinition) (result errors.ManifestResult) { + errList := validation.ValidateCustomResourceDefinition(crd) + for _, err := range errList { + if !strings.Contains(err.Field, "openAPIV3Schema") && !strings.Contains(err.Field, "status") { + result.Add(errors.NewError(errors.ErrorType(err.Type), err.Error(), err.Field, err.BadValue)) + } + } + return result +} diff --git a/pkg/validation/internal/csv.go b/pkg/validation/internal/csv.go new file mode 100644 index 000000000..c930bc9a5 --- /dev/null +++ b/pkg/validation/internal/csv.go @@ -0,0 +1,187 @@ +package internal + +import ( + "fmt" + "io" + "reflect" + "strings" + + "github.com/operator-framework/api/pkg/validation/errors" + "github.com/operator-framework/operator-registry/pkg/registry" + + "github.com/blang/semver" + "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + k8svalidation "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/apimachinery/pkg/util/yaml" +) + +type CSVValidator struct{} + +func (f CSVValidator) Validate(objs ...interface{}) (results []errors.ManifestResult) { + for _, obj := range objs { + switch v := obj.(type) { + case *v1alpha1.ClusterServiceVersion: + results = append(results, validateCSV(v)) + case *registry.ClusterServiceVersion: + results = append(results, validateCSVRegistry(v)) + } + } + return results +} + +func validateCSVRegistry(bcsv *registry.ClusterServiceVersion) (result errors.ManifestResult) { + csv, err := bundleCSVToCSV(bcsv) + if err != (errors.Error{}) { + result.Add(err) + return result + } + return validateCSV(csv) +} + +// Iterates over the given CSV. Returns a ManifestResult type object. +func validateCSV(csv *v1alpha1.ClusterServiceVersion) errors.ManifestResult { + result := errors.ManifestResult{Name: csv.GetName()} + // Ensure CSV names are of the correct format. + if _, _, err := parseCSVNameFormat(csv.GetName()); err != (errors.Error{}) { + result.Add(errors.ErrInvalidCSV(fmt.Sprintf("metadata.name %s", err), csv.GetName())) + } + if replaces := csv.Spec.Replaces; replaces != "" { + if _, _, err := parseCSVNameFormat(replaces); err != (errors.Error{}) { + result.Add(errors.ErrInvalidCSV(fmt.Sprintf("spec.replaces %s", err), csv.GetName())) + } + } + // validate example annotations ("alm-examples", "olm.examples"). + result.Add(validateExamplesAnnotations(csv)...) + // validate installModes + result.Add(validateInstallModes(csv)...) + // check missing optional/mandatory fields. + result.Add(checkFields(csv)...) + return result +} + +func parseCSVNameFormat(name string) (string, semver.Version, error) { + if violations := k8svalidation.IsDNS1123Subdomain(name); len(violations) != 0 { + return "", semver.Version{}, fmt.Errorf("%q is invalid:\n%s", name, violations) + } + splitName := strings.SplitN(name, ".", 2) + if len(splitName) != 2 { + return "", semver.Version{}, fmt.Errorf("%q must have format: {operator name}.(v)X.Y.Z", name) + } + verStr := strings.TrimLeft(splitName[1], "v") + nameVer, err := semver.Parse(verStr) + if err != nil { + return "", semver.Version{}, fmt.Errorf("%q contains an invalid semver %q", name, splitName[1]) + } + return splitName[0], nameVer, errors.Error{} +} + +// checkFields runs checkEmptyFields and returns its errors. +func checkFields(csv *v1alpha1.ClusterServiceVersion) (errs []errors.Error) { + result := errors.ManifestResult{} + checkEmptyFields(&result, reflect.ValueOf(csv), "") + return append(result.Errors, result.Warnings...) +} + +// validateExamplesAnnotations compares alm/olm example annotations with provided APIs given +// by Spec.CustomResourceDefinitions.Owned and Spec.APIServiceDefinitions.Owned. +func validateExamplesAnnotations(csv *v1alpha1.ClusterServiceVersion) (errs []errors.Error) { + annotations := csv.ObjectMeta.GetAnnotations() + // Return right away if no examples annotations are found. + if len(annotations) == 0 { + errs = append(errs, errors.WarnInvalidCSV("annotations not found", csv.GetName())) + return errs + } + // Expect either `alm-examples` or `olm.examples` but not both + // If both are present, `alm-examples` will be used + var examplesString string + almExamples, almOK := annotations["alm-examples"] + olmExamples, olmOK := annotations["olm.examples"] + if !almOK && !olmOK { + errs = append(errs, errors.WarnInvalidCSV("example annotations not found", csv.GetName())) + return errs + } else if almOK { + if olmOK { + errs = append(errs, errors.WarnInvalidCSV("both `alm-examples` and `olm.examples` are present. Checking only `alm-examples`", csv.GetName())) + } + examplesString = almExamples + } else { + examplesString = olmExamples + } + us := []unstructured.Unstructured{} + dec := yaml.NewYAMLOrJSONDecoder(strings.NewReader(examplesString), 8) + if err := dec.Decode(&us); err != nil && err != io.EOF { + errs = append(errs, errors.ErrInvalidParse("error decoding example CustomResource", err)) + return errs + } + parsed := map[schema.GroupVersionKind]struct{}{} + for _, u := range us { + parsed[u.GetObjectKind().GroupVersionKind()] = struct{}{} + } + + providedAPISet, aerrs := getProvidedAPIs(csv) + errs = append(errs, aerrs...) + + errs = append(errs, matchGVKProvidedAPIs(parsed, providedAPISet)...) + return errs +} + +func getProvidedAPIs(csv *v1alpha1.ClusterServiceVersion) (provided map[schema.GroupVersionKind]struct{}, errs []errors.Error) { + provided = map[schema.GroupVersionKind]struct{}{} + for _, owned := range csv.Spec.CustomResourceDefinitions.Owned { + parts := strings.SplitN(owned.Name, ".", 2) + if len(parts) < 2 { + errs = append(errs, errors.ErrInvalidParse(fmt.Sprintf("couldn't parse plural.group from crd name: %s", owned.Name), nil)) + continue + } + provided[newGVK(parts[1], owned.Version, owned.Kind)] = struct{}{} + } + + for _, api := range csv.Spec.APIServiceDefinitions.Owned { + provided[newGVK(api.Group, api.Version, api.Kind)] = struct{}{} + } + + return provided, errs +} + +func newGVK(g, v, k string) schema.GroupVersionKind { + return schema.GroupVersionKind{Group: g, Version: v, Kind: k} +} + +func matchGVKProvidedAPIs(exampleSet map[schema.GroupVersionKind]struct{}, providedAPISet map[schema.GroupVersionKind]struct{}) (errs []errors.Error) { + for example := range exampleSet { + if _, ok := providedAPISet[example]; !ok { + errs = append(errs, errors.ErrInvalidOperation("example must have a provided API", example)) + } + } + for api := range providedAPISet { + if _, ok := exampleSet[api]; !ok { + errs = append(errs, errors.WarnInvalidOperation("provided API should have an example annotation", api)) + } + } + return errs +} + +func validateInstallModes(csv *v1alpha1.ClusterServiceVersion) (errs []errors.Error) { + if len(csv.Spec.InstallModes) == 0 { + errs = append(errs, errors.ErrInvalidCSV("install modes not found", csv.GetName())) + return errs + } + + installModeSet := v1alpha1.InstallModeSet{} + anySupported := false + for _, installMode := range csv.Spec.InstallModes { + if _, ok := installModeSet[installMode.Type]; ok { + errs = append(errs, errors.ErrInvalidCSV("duplicate install modes present", csv.GetName())) + } else if installMode.Supported { + anySupported = true + } + } + + // all installModes should not be `false` + if !anySupported { + errs = append(errs, errors.ErrInvalidCSV("none of InstallModeTypes are supported", csv.GetName())) + } + return errs +} diff --git a/pkg/validation/internal/csv_test.go b/pkg/validation/internal/csv_test.go new file mode 100644 index 000000000..feb99567b --- /dev/null +++ b/pkg/validation/internal/csv_test.go @@ -0,0 +1,50 @@ +package internal + +import ( + "io/ioutil" + "path/filepath" + "testing" + + "github.com/operator-framework/api/pkg/validation/errors" + + "github.com/ghodss/yaml" + "github.com/operator-framework/operator-registry/pkg/registry" +) + +func TestValidateCSV(t *testing.T) { + cases := []struct { + validatorFuncTest + csvPath string + }{ + { + validatorFuncTest{ + description: "successfully validated", + }, + filepath.Join("testdata", "correct.csv.yaml"), + }, + { + validatorFuncTest{ + description: "data type mismatch", + wantErr: true, + errors: []errors.Error{ + errors.ErrInvalidParse( + `converting bundle CSV "etcdoperator.v0.9.0"`, + "json: cannot unmarshal string into Go struct field ClusterServiceVersionSpec.maintainers of type []v1alpha1.Maintainer"), + }, + }, + filepath.Join("testdata", "dataTypeMismatch.csv.yaml"), + }, + } + for _, c := range cases { + b, err := ioutil.ReadFile(c.csvPath) + if err != nil { + t.Fatalf("Error reading CSV path %s: %v", c.csvPath, err) + } + csv := registry.ClusterServiceVersion{} + if err = yaml.Unmarshal(b, &csv); err != nil { + t.Fatalf("Error unmarshalling CSV at path %s: %v", c.csvPath, err) + } + result := validateCSVRegistry(&csv) + c.check(t, result) + } +} diff --git a/pkg/validation/internal/manifests.go b/pkg/validation/internal/manifests.go new file mode 100644 index 000000000..2781bf799 --- /dev/null +++ b/pkg/validation/internal/manifests.go @@ -0,0 +1,205 @@ +package internal + +import ( + "fmt" + + "github.com/operator-framework/api/pkg/validation/errors" + + "github.com/blang/semver" + "github.com/operator-framework/operator-registry/pkg/registry" +) + +const skipPackageAnnotationKey = "olm.skipRange" + +type ManifestsValidator struct{} + +func (f ManifestsValidator) Validate(objs ...interface{}) (results []errors.ManifestResult) { + var pkg *registry.PackageManifest + bundles := []*registry.Bundle{} + for _, obj := range objs { + switch v := obj.(type) { + case *registry.PackageManifest: + if pkg == nil { + pkg = v + } + case *registry.Bundle: + bundles = append(bundles, v) + } + } + if pkg != nil && len(bundles) > 0 { + results = append(results, validateManifests(pkg, bundles)) + } + return results +} + +func validateManifests(pkg *registry.PackageManifest, bundles []*registry.Bundle) (result errors.ManifestResult) { + // Collect all CSV names and ensure no duplicates. We will use these names + // to check whether a spec.replaces references an existing CSV in bundles. + csvNameMap := map[string]struct{}{} + for _, bundle := range bundles { + csv, err := bundle.ClusterServiceVersion() + if err != nil { + result.Add(errors.ErrInvalidParse("error getting bundle CSV", err)) + return result + } + if _, seenCSV := csvNameMap[csv.GetName()]; seenCSV { + result.Add(errors.ErrInvalidCSV("duplicate CSV in bundle set", csv.GetName())) + return result + } else { + csvNameMap[csv.GetName()] = struct{}{} + } + } + + // Check that all CSV fields follow package graph invariants. + replacesGraph, replacesSet := map[string]string{}, map[string]struct{}{} + for _, bundle := range bundles { + // We already know each CSV in bundles can be marshalled correctly. + csv, _ := bundle.ClusterServiceVersion() + + // spec.replaces, if present: + // - Must be a valid Kubernetes resource name. + // - Should reference the name of a CSV defined by another bundle. + replaces, err := csv.GetReplaces() + if err != nil { + result.Add(errors.ErrInvalidParse("error getting spec.replaces from bundle CSV", err)) + return result + } + replacesGraph[csv.GetName()] = replaces + if replaces != "" { + if _, seen := replacesSet[replaces]; seen { + result.Add(errors.ErrInvalidCSV( + fmt.Sprintf("spec.replaces %q referenced by more than one CSV", replaces), + csv.GetName())) + return result + } + replacesSet[replaces] = struct{}{} + + if _, _, err = parseCSVNameFormat(replaces); err != nil { + result.Add(errors.ErrInvalidCSV(fmt.Sprintf("spec.replaces %s", err), csv.GetName())) + } + if csv.GetName() == replaces { + result.Add(errors.ErrInvalidCSV( + "spec.replaces field cannot match its own metadata.name.", csv.GetName())) + } + if _, replacesInBundles := csvNameMap[replaces]; !replacesInBundles { + result.Add(errors.WarnInvalidCSV( + fmt.Sprintf("spec.replaces %q CSV is not present in manifests", replaces), + csv.GetName())) + } + } + + // spec.skips, if present: + // - Must contain valid Kubernetes resource names. + // - Must not contain an element matching spec.replaces. + skips, err := csv.GetSkips() + if err != nil { + result.Add(errors.ErrInvalidParse("error getting spec.skips from bundle CSV", err)) + return result + } + for i, skip := range skips { + if _, _, err = parseCSVNameFormat(skip); err != nil { + result.Add(errors.ErrInvalidCSV( + fmt.Sprintf("spec.skips[%d] %s", i, err), csv.GetName())) + } + if skip == replaces && replaces != "" { + result.Add(errors.ErrInvalidCSV( + fmt.Sprintf("spec.skips[%d] %q cannot match spec.replaces", i, skip), + csv.GetName())) + } + } + + // metadata.annotations["olm.skipRange"], if present: + // - Must be a valid semver range. + // - Must not be inclusive of its CSV’s version. + skipRange, rerr := parseSkipRange(csv) + if rerr != (errors.Error{}) { + result.Add(rerr) + return result + } + if skipRange != nil { + csvVerStr, err := csv.GetVersion() + if err != nil { + result.Add(errors.ErrInvalidParse("error getting spec.version from bundle CSV", err)) + } + csvVer, err := semver.Parse(csvVerStr) + if err != nil { + result.Add(errors.ErrInvalidParse("error parsing spec.version", err)) + } + if skipRange(csvVer) { + result.Add(errors.ErrInvalidCSV( + fmt.Sprintf("metadata.annotations[\"%s\"] range contains the CSV's version", + skipPackageAnnotationKey), + csv.GetName())) + } + } + } + + // Ensure no spec.replaces reference parent CSV's. + result.Add(checkReplacesGraphForCycles(replacesGraph)...) + // Ensure all channels reference existing CSV's. + result.Add(checkChannelInBundle(pkg, replacesGraph)...) + return result +} + +func parseSkipRange(csv *registry.ClusterServiceVersion) (semver.Range, errors.Error) { + if csv.GetAnnotations() != nil { + if skipRangeStr, ok := csv.GetAnnotations()[skipPackageAnnotationKey]; ok { + skipRange, err := semver.ParseRange(skipRangeStr) + if err != nil { + return nil, errors.ErrInvalidCSV( + fmt.Sprintf("metadata.annotations[\"%s\"] %q is an invalid semantic version range", + skipPackageAnnotationKey, skipRangeStr), + csv.GetName()) + } + return skipRange, errors.Error{} + } + } + return nil, errors.Error{} +} + +// checkReplacesGraphForCycles ensures no cycles occur in spec.replaces +// references. No spec.replaces should reference a parent CSV in the graph. +func checkReplacesGraphForCycles(graph map[string]string) (errs []errors.Error) { + for csvName, replaces := range graph { + currReplaces := replaces + currCSVName := csvName + for { + newReplaces, ok := graph[currReplaces] + if ok { + if newReplaces == "" { + break + } + if newReplaces == csvName { + errs = append(errs, errors.ErrInvalidCSV( + fmt.Sprintf("spec.replaces %q references a parent in CSV replace chain", + newReplaces), + currReplaces)) + break + } + currCSVName = currReplaces + currReplaces = newReplaces + } else { + errs = append(errs, errors.ErrInvalidCSV( + fmt.Sprintf("spec.replaces %q does not map to a CSV in bundles", + currReplaces), + currCSVName)) + break + } + } + } + return errs +} + +// checkChannelInBundle ensures that each package channel's currentCSV exists +// in one bundle. +func checkChannelInBundle(pkg *registry.PackageManifest, csvNames map[string]string) (errs []errors.Error) { + for _, channel := range pkg.Channels { + if _, csvExists := csvNames[channel.CurrentCSVName]; !csvExists { + errs = append(errs, errors.ErrInvalidPackageManifest( + fmt.Sprintf("currentCSV %q for channel name %q not found in bundle", + channel.CurrentCSVName, channel.Name), + pkg.PackageName)) + } + } + return errs +} diff --git a/pkg/validation/internal/package_manifest.go b/pkg/validation/internal/package_manifest.go new file mode 100644 index 000000000..76b19f5cc --- /dev/null +++ b/pkg/validation/internal/package_manifest.go @@ -0,0 +1,60 @@ +package internal + +import ( + "fmt" + + "github.com/operator-framework/api/pkg/validation/errors" + + "github.com/operator-framework/operator-registry/pkg/registry" +) + +type PackageManifestValidator struct{} + +func (f PackageManifestValidator) Validate(objs ...interface{}) (results []errors.ManifestResult) { + for _, obj := range objs { + switch v := obj.(type) { + case *registry.PackageManifest: + results = append(results, validatePackageManifest(v)) + } + } + return results +} + +func validatePackageManifest(pkg *registry.PackageManifest) errors.ManifestResult { + result := errors.ManifestResult{Name: pkg.PackageName} + result.Add(validateChannels(pkg)...) + return result +} + +func validateChannels(pkg *registry.PackageManifest) (errs []errors.Error) { + if pkg.PackageName == "" { + errs = append(errs, errors.ErrInvalidPackageManifest("packageName empty", pkg.PackageName)) + } + numChannels := len(pkg.Channels) + if numChannels == 0 { + errs = append(errs, errors.ErrInvalidPackageManifest("channels empty", pkg.PackageName)) + return errs + } + if pkg.DefaultChannelName == "" && numChannels > 1 { + errs = append(errs, errors.ErrInvalidPackageManifest("default channel is empty but more than one channel exists", pkg.PackageName)) + } + + seen := map[string]struct{}{} + for i, c := range pkg.Channels { + if c.Name == "" { + errs = append(errs, errors.ErrInvalidPackageManifest(fmt.Sprintf("channel %d name is empty", i), pkg.PackageName)) + } + if c.CurrentCSVName == "" { + errs = append(errs, errors.ErrInvalidPackageManifest(fmt.Sprintf("channel %q currentCSV is empty", c.Name), pkg.PackageName)) + } + if _, ok := seen[c.Name]; ok { + errs = append(errs, errors.ErrInvalidPackageManifest(fmt.Sprintf("duplicate package manifest channel name %q", c.Name), pkg.PackageName)) + } + seen[c.Name] = struct{}{} + } + if _, found := seen[pkg.DefaultChannelName]; pkg.DefaultChannelName != "" && !found { + errs = append(errs, errors.ErrInvalidPackageManifest(fmt.Sprintf("default channel %q not found in the list of declared channels", pkg.DefaultChannelName), pkg.PackageName)) + } + + return errs +} diff --git a/pkg/validation/internal/package_manifest_test.go b/pkg/validation/internal/package_manifest_test.go new file mode 100644 index 000000000..835f074f3 --- /dev/null +++ b/pkg/validation/internal/package_manifest_test.go @@ -0,0 +1,123 @@ +package internal + +import ( + "testing" + + "github.com/operator-framework/api/pkg/validation/errors" + "github.com/operator-framework/operator-registry/pkg/registry" +) + +func TestValidatePackageManifest(t *testing.T) { + pkgName := "test-package" + + cases := []struct { + validatorFuncTest + pkg *registry.PackageManifest + }{ + { + validatorFuncTest{ + description: "successful validation", + }, + ®istry.PackageManifest{ + Channels: []registry.PackageChannel{ + {Name: "foo", CurrentCSVName: "bar"}, + }, + DefaultChannelName: "foo", + PackageName: "test-package", + }, + }, + { + validatorFuncTest{ + description: "successful validation no default channel with only one channel", + }, + ®istry.PackageManifest{ + Channels: []registry.PackageChannel{ + {Name: "foo", CurrentCSVName: "bar"}, + }, + PackageName: "test-package", + }, + }, + { + validatorFuncTest{ + description: "no default channel and more than one channel", + wantErr: true, + errors: []errors.Error{ + errors.ErrInvalidPackageManifest("default channel is empty but more than one channel exists", pkgName), + }, + }, + ®istry.PackageManifest{ + Channels: []registry.PackageChannel{ + {Name: "foo", CurrentCSVName: "bar"}, + {Name: "foo2", CurrentCSVName: "baz"}, + }, + PackageName: "test-package", + }, + }, + { + validatorFuncTest{ + description: "default channel does not exist in channels", + wantErr: true, + errors: []errors.Error{ + errors.ErrInvalidPackageManifest(`default channel "baz" not found in the list of declared channels`, pkgName), + }, + }, + ®istry.PackageManifest{ + Channels: []registry.PackageChannel{ + {Name: "foo", CurrentCSVName: "bar"}, + }, + DefaultChannelName: "baz", + PackageName: "test-package", + }, + }, + { + validatorFuncTest{ + description: "channels are empty", + wantErr: true, + errors: []errors.Error{ + errors.ErrInvalidPackageManifest("channels empty", pkgName), + }, + }, + ®istry.PackageManifest{ + Channels: nil, + DefaultChannelName: "baz", + PackageName: "test-package", + }, + }, + { + validatorFuncTest{ + description: "one channel's CSVName is empty", + wantErr: true, + errors: []errors.Error{ + errors.ErrInvalidPackageManifest(`channel "foo" currentCSV is empty`, pkgName), + }, + }, + ®istry.PackageManifest{ + Channels: []registry.PackageChannel{{Name: "foo"}}, + DefaultChannelName: "foo", + PackageName: "test-package", + }, + }, + { + validatorFuncTest{ + description: "duplicate channel name", + wantErr: true, + errors: []errors.Error{ + errors.ErrInvalidPackageManifest(`duplicate package manifest channel name "foo"`, pkgName), + }, + }, + ®istry.PackageManifest{ + Channels: []registry.PackageChannel{ + {Name: "foo", CurrentCSVName: "bar"}, + {Name: "foo", CurrentCSVName: "baz"}, + }, + DefaultChannelName: "foo", + PackageName: "test-package", + }, + }, + } + + for _, c := range cases { + result := validatePackageManifest(c.pkg) + c.check(t, result) + } +} diff --git a/pkg/validation/internal/test_suite.go b/pkg/validation/internal/test_suite.go new file mode 100644 index 000000000..f15a59d73 --- /dev/null +++ b/pkg/validation/internal/test_suite.go @@ -0,0 +1,63 @@ +package internal + +import ( + "testing" + + "github.com/operator-framework/api/pkg/validation/errors" + + "github.com/stretchr/testify/require" +) + +type validatorFuncTest struct { + description string + wantErr, wantWarn bool + errors []errors.Error +} + +func (c validatorFuncTest) check(t *testing.T, result errors.ManifestResult) { + if c.wantErr { + if !result.HasError() { + t.Errorf("%s: expected errors %#v, got nil", c.description, c.errors) + } else { + errs, _ := splitErrorsWarnings(c.errors) + checkErrorsMatch(t, errs, result.Errors) + } + } + if c.wantWarn { + if !result.HasWarn() { + t.Errorf("%s: expected warnings %#v, got nil", c.description, c.errors) + } else { + _, warns := splitErrorsWarnings(c.errors) + checkErrorsMatch(t, warns, result.Warnings) + } + } + if !c.wantErr && !c.wantWarn && (result.HasError() || result.HasWarn()) { + t.Errorf("%s: expected no errors or warnings, got:\n%v", c.description, result) + } +} + +func splitErrorsWarnings(all []errors.Error) (errs, warns []errors.Error) { + for _, a := range all { + if a.Level == errors.LevelError { + errs = append(errs, a) + } else { + warns = append(warns, a) + } + } + return +} + +func checkErrorsMatch(t *testing.T, errs1, errs2 []errors.Error) { + // Do string matching on error types for test purposes. + for i, err := range errs1 { + if badErr, ok := err.BadValue.(error); ok && badErr != nil { + errs1[i].BadValue = badErr.Error() + } + } + for i, err := range errs2 { + if badErr, ok := err.BadValue.(error); ok && badErr != nil { + errs2[i].BadValue = badErr.Error() + } + } + require.ElementsMatch(t, errs1, errs2) +} diff --git a/pkg/validation/internal/testdata/correct.csv.yaml b/pkg/validation/internal/testdata/correct.csv.yaml new file mode 100644 index 000000000..4dad39663 --- /dev/null +++ b/pkg/validation/internal/testdata/correct.csv.yaml @@ -0,0 +1,292 @@ +#! validate-crd: deploy/chart/templates/0000_30_02-clusterserviceversion.crd.yaml +#! parse-kind: ClusterServiceVersion +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + name: etcdoperator.v0.9.0 + namespace: placeholder + annotations: + capabilities: Full Lifecycle + tectonic-visibility: ocs + alm-examples: '[{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdCluster","metadata":{"name":"example","namespace":"default"},"spec":{"size":3,"version":"3.2.13"}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdRestore","metadata":{"name":"example-etcd-cluster"},"spec":{"etcdCluster":{"name":"example-etcd-cluster"},"backupStorageType":"S3","s3":{"path":"","awsSecret":""}}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdBackup","metadata":{"name":"example-etcd-cluster-backup"},"spec":{"etcdEndpoints":[""],"storageType":"S3","s3":{"path":"","awsSecret":""}}}]' + description: etcd is a distributed key value store providing a reliable way to store data across a cluster of machines. +spec: + displayName: etcd + description: | + etcd is a distributed key value store that provides a reliable way to store data across a cluster of machines. It’s open-source and available on GitHub. etcd gracefully handles leader elections during network partitions and will tolerate machine failure, including the leader. Your applications can read and write data into etcd. + A simple use-case is to store database connection details or feature flags within etcd as key value pairs. These values can be watched, allowing your app to reconfigure itself when they change. Advanced uses take advantage of the consistency guarantees to implement database leader elections or do distributed locking across a cluster of workers. + + _The etcd Open Cloud Service is Public Alpha. The goal before Beta is to fully implement backup features._ + + ### Reading and writing to etcd + + Communicate with etcd though its command line utility `etcdctl` or with the API using the automatically generated Kubernetes Service. + + [Read the complete guide to using the etcd Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/etcd-ocs.html) + + ### Supported Features + + + **High availability** + + + Multiple instances of etcd are networked together and secured. Individual failures or networking issues are transparently handled to keep your cluster up and running. + + + **Automated updates** + + + Rolling out a new etcd version works like all Kubernetes rolling updates. Simply declare the desired version, and the etcd service starts a safe rolling update to the new version automatically. + + + **Backups included** + + + Coming soon, the ability to schedule backups to happen on or off cluster. + keywords: ['etcd', 'key value', 'database', 'coreos', 'open source'] + version: 0.9.0 + maturity: alpha + maintainers: + - name: CoreOS, Inc + email: support@coreos.com + + provider: + name: CoreOS, Inc + labels: + alm-owner-etcd: etcdoperator + operated-by: etcdoperator + selector: + matchLabels: + alm-owner-etcd: etcdoperator + operated-by: etcdoperator + links: + - name: Blog + url: https://coreos.com/etcd + - name: Documentation + url: https://coreos.com/operators/etcd/docs/latest/ + - name: etcd Operator Source Code + url: https://github.com/coreos/etcd-operator + + icon: + - base64data: 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 + mediatype: image/png + installModes: + - type: OwnNamespace + supported: true + - type: SingleNamespace + supported: true + - type: MultiNamespace + supported: false + - type: AllNamespaces + supported: true + install: + strategy: deployment + spec: + permissions: + - serviceAccountName: etcd-operator + rules: + - apiGroups: + - etcd.database.coreos.com + resources: + - etcdclusters + - etcdbackups + - etcdrestores + verbs: + - "*" + - apiGroups: + - "" + resources: + - pods + - services + - endpoints + - persistentvolumeclaims + - events + verbs: + - "*" + - apiGroups: + - apps + resources: + - deployments + verbs: + - "*" + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + deployments: + - name: etcd-operator + spec: + replicas: 1 + selector: + matchLabels: + name: etcd-operator-alm-owned + template: + metadata: + name: etcd-operator-alm-owned + labels: + name: etcd-operator-alm-owned + spec: + serviceAccountName: etcd-operator + containers: + - name: etcd-operator + command: + - etcd-operator + - --create-crd=false + image: quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8 + env: + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: etcd-backup-operator + image: quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8 + command: + - etcd-backup-operator + - --create-crd=false + env: + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: etcd-restore-operator + image: quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8 + command: + - etcd-restore-operator + - --create-crd=false + env: + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + customresourcedefinitions: + owned: + - name: etcdclusters.etcd.database.coreos.com + version: v1beta2 + kind: EtcdCluster + displayName: etcd Cluster + description: Represents a cluster of etcd nodes. + resources: + - kind: Service + version: v1 + - kind: Pod + version: v1 + specDescriptors: + - description: The desired number of member Pods for the etcd cluster. + displayName: Size + path: size + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:podCount' + - description: Limits describes the minimum/maximum amount of compute resources required/allowed + displayName: Resource Requirements + path: pod.resources + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:resourceRequirements' + statusDescriptors: + - description: The status of each of the member Pods for the etcd cluster. + displayName: Member Status + path: members + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:podStatuses' + - description: The service at which the running etcd cluster can be accessed. + displayName: Service + path: serviceName + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes:Service' + - description: The current size of the etcd cluster. + displayName: Cluster Size + path: size + - description: The current version of the etcd cluster. + displayName: Current Version + path: currentVersion + - description: 'The target version of the etcd cluster, after upgrading.' + displayName: Target Version + path: targetVersion + - description: The current status of the etcd cluster. + displayName: Status + path: phase + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes.phase' + - description: Explanation for the current status of the cluster. + displayName: Status Details + path: reason + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes.phase:reason' + - name: etcdbackups.etcd.database.coreos.com + version: v1beta2 + kind: EtcdBackup + displayName: etcd Backup + description: Represents the intent to backup an etcd cluster. + specDescriptors: + - description: Specifies the endpoints of an etcd cluster. + displayName: etcd Endpoint(s) + path: etcdEndpoints + x-descriptors: + - 'urn:alm:descriptor:etcd:endpoint' + - description: The full AWS S3 path where the backup is saved. + displayName: S3 Path + path: s3.path + x-descriptors: + - 'urn:alm:descriptor:aws:s3:path' + - description: The name of the secret object that stores the AWS credential and config files. + displayName: AWS Secret + path: s3.awsSecret + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes:Secret' + statusDescriptors: + - description: Indicates if the backup was successful. + displayName: Succeeded + path: succeeded + x-descriptors: + - 'urn:alm:descriptor:text' + - description: Indicates the reason for any backup related failures. + displayName: Reason + path: reason + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes.phase:reason' + - name: etcdrestores.etcd.database.coreos.com + version: v1beta2 + kind: EtcdRestore + displayName: etcd Restore + description: Represents the intent to restore an etcd cluster from a backup. + specDescriptors: + - description: References the EtcdCluster which should be restored, + displayName: etcd Cluster + path: etcdCluster.name + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes:EtcdCluster' + - 'urn:alm:descriptor:text' + - description: The full AWS S3 path where the backup is saved. + displayName: S3 Path + path: s3.path + x-descriptors: + - 'urn:alm:descriptor:aws:s3:path' + - description: The name of the secret object that stores the AWS credential and config files. + displayName: AWS Secret + path: s3.awsSecret + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes:Secret' + statusDescriptors: + - description: Indicates if the restore was successful. + displayName: Succeeded + path: succeeded + x-descriptors: + - 'urn:alm:descriptor:text' + - description: Indicates the reason for any restore related failures. + displayName: Reason + path: reason + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes.phase:reason' \ No newline at end of file diff --git a/pkg/validation/internal/testdata/dataTypeMismatch.csv.yaml b/pkg/validation/internal/testdata/dataTypeMismatch.csv.yaml new file mode 100644 index 000000000..a915b115c --- /dev/null +++ b/pkg/validation/internal/testdata/dataTypeMismatch.csv.yaml @@ -0,0 +1,290 @@ +#! validate-crd: deploy/chart/templates/0000_30_02-clusterserviceversion.crd.yaml +#! parse-kind: ClusterServiceVersion +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + name: etcdoperator.v0.9.0 + namespace: placeholder + annotations: + capabilities: Full Lifecycle + tectonic-visibility: ocs + alm-examples: '[{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdCluster","metadata":{"name":"example","namespace":"default"},"spec":{"size":3,"version":"3.2.13"}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdRestore","metadata":{"name":"example-etcd-cluster"},"spec":{"etcdCluster":{"name":"example-etcd-cluster"},"backupStorageType":"S3","s3":{"path":"","awsSecret":""}}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdBackup","metadata":{"name":"example-etcd-cluster-backup"},"spec":{"etcdEndpoints":[""],"storageType":"S3","s3":{"path":"","awsSecret":""}}}]' + description: etcd is a distributed key value store providing a reliable way to store data across a cluster of machines. +spec: + displayName: etcd + description: | + etcd is a distributed key value store that provides a reliable way to store data across a cluster of machines. It’s open-source and available on GitHub. etcd gracefully handles leader elections during network partitions and will tolerate machine failure, including the leader. Your applications can read and write data into etcd. + A simple use-case is to store database connection details or feature flags within etcd as key value pairs. These values can be watched, allowing your app to reconfigure itself when they change. Advanced uses take advantage of the consistency guarantees to implement database leader elections or do distributed locking across a cluster of workers. + + _The etcd Open Cloud Service is Public Alpha. The goal before Beta is to fully implement backup features._ + + ### Reading and writing to etcd + + Communicate with etcd though its command line utility `etcdctl` or with the API using the automatically generated Kubernetes Service. + + [Read the complete guide to using the etcd Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/etcd-ocs.html) + + ### Supported Features + + + **High availability** + + + Multiple instances of etcd are networked together and secured. Individual failures or networking issues are transparently handled to keep your cluster up and running. + + + **Automated updates** + + + Rolling out a new etcd version works like all Kubernetes rolling updates. Simply declare the desired version, and the etcd service starts a safe rolling update to the new version automatically. + + + **Backups included** + + + Coming soon, the ability to schedule backups to happen on or off cluster. + keywords: ['etcd', 'key value', 'database', 'coreos', 'open source'] + version: 0.9.0 + maturity: alpha + maintainers: testing_with_incorrect_data_type + + provider: + name: CoreOS, Inc + labels: + alm-owner-etcd: etcdoperator + operated-by: etcdoperator + selector: + matchLabels: + alm-owner-etcd: etcdoperator + operated-by: etcdoperator + links: + - name: Blog + url: https://coreos.com/etcd + - name: Documentation + url: https://coreos.com/operators/etcd/docs/latest/ + - name: etcd Operator Source Code + url: https://github.com/coreos/etcd-operator + + icon: + - base64data: iVBORw0KGgoAAAANSUhEUgAAAOEAAADZCAYAAADWmle6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAEKlJREFUeNrsndt1GzkShmEev4sTgeiHfRYdgVqbgOgITEVgOgLTEQydwIiKwFQCayoCU6+7DyYjsBiBFyVVz7RkXvqCSxXw/+f04XjGQ6IL+FBVuL769euXgZ7r39f/G9iP0X+u/jWDNZzZdGI/Ftama1jjuV4BwmcNpbAf1Fgu+V/9YRvNAyzT2a59+/GT/3hnn5m16wKWedJrmOCxkYztx9Q+py/+E0GJxtJdReWfz+mxNt+QzS2Mc0AI+HbBBwj9QViKbH5t64DsP2fvmGXUkWU4WgO+Uve2YQzBUGd7r+zH2ZG/tiUQc4QxKwgbwFfVGwwmdLL5wH78aPC/ZBem9jJpCAX3xtcNASSNgJLzUPSQyjB1zQNl8IQJ9MIU4lx2+Jo72ysXYKl1HSzN02BMa/vbZ5xyNJIshJzwf3L0dQhJw4Sih/SFw9Tk8sVeghVPoefaIYCkMZCKbrcP9lnZuk0uPUjGE/KE8JQry7W2tgfuC3vXgvNV+qSQbyFtAtyWk7zWiYevvuUQ9QEQCvJ+5mmu6dTjz1zFHLFj8Eb87MtxaZh/IQFIHom+9vgTWwZxAQjT9X4vtbEVPojwjiV471s00mhAckpwGuCn1HtFtRDaSh6y9zsL+LNBvCG/24ThcxHObdlWc1v+VQJe8LcO0jwtuF8BwnAAUgP9M8JPU2Me+Oh12auPGT6fHuTePE3bLDy+x9pTLnhMn+07TQGh//Bz1iI0c6kvtqInjvPZcYR3KsPVmUsPYt9nFig9SCY8VQNhpPBzn952bbgcsk2EvM89wzh3UEffBbyPqvBUBYQ8ODGPFOLsa7RF096WJ69L+E4EmnpjWu5o4ChlKaRTKT39RMMaVPEQRsz/nIWlDN80chjdJlSd1l0pJCAMVZsniobQVuxceMM9OFoaMd9zqZtjMEYYDW38Drb8Y0DYPLShxn0pvIFuOSxd7YCPet9zk452wsh54FJoeN05hcgSQoG5RR0Qh9Q4E4VvL4wcZq8UACgaRFEQKgSwWrkr5WFnGxiHSutqJGlXjBgIOayhwYBTA0ER0oisIVSUV0AAMT0IASCUO4hRIQSAEECMCCEPwqyQA0JCQBzEGjWNAqHiUVAoXUWbvggOIQCEAOJzxTjoaQ4AIaE64/aZridUsBYUgkhB15oGg1DBIl8IqirYwV6hPSGBSFteMCUBSVXwfYixBmamRubeMyjzMJQBDDowE3OesDD+zwqFoDqiEwXoXJpljB+PvWJGy75BKF1FPxhKygJuqUdYQGlLxNEXkrYyjQ0GbaAwEnUIlLRNvVjQDYUAsJB0HKLE4y0AIpQNgCIhBIhQTgCKhZBBpAN/v6LtQI50JfUgYOnnjmLUFHKhjxbAmdTCaTiBm3ovLPqG2urWAij6im0Nd9aTN9ygLUEt9LgSRnohxUPIKxlGaE+/6Y7znFf0yX+GnkvFFWmarkab2o9PmTeq8sbd2a7DaysXz7i64VeznN4jCQhN9gdDbRiuWrfrsq0mHIrlaq+hlotCtd3Um9u0BYWY8y5D67wccJoZjFca7iUs9VqZcfsZwTd1sbWGG+OcYaTnPAP7rTQVVlM4Sg3oGvB1tmNh0t/HKXZ1jFoIMwCQjtqbhNxUmkGYqgZEDZP11HN/S3gAYRozf0l8C5kKEKUvW0t1IfeWG/5MwgheZTT1E0AEhDkAePQO+Ig2H3DncAkQM4cwUQCD530dU4B5Yvmi2LlDqXfWrxMCcMth51RToRMNUXFnfc2KJ0+Ryl0VNOUwlhh6NoxK5gnViTgQpUG4SqSyt5z3zRJpuKmt3Q1614QaCBPaN6je+2XiFcWAKOXcUfIYKRyL/1lb7pe5VxSxxjQ6hImshqGRt5GWZVKO6q2wHwujfwDtIvaIdexj8Cm8+a68EqMfox6x/voMouZF4dHnEGNeCDMwT6vdNfekH1MafMk4PI06YtqLVGl95aEM9Z5vAeCTOA++YLtoVJRrsqNCaJ6WRmkdYaNec5BT/lcTRMqrhmwfjbpkj55+OKp8IEbU/JLgPJE6Wa3TTe9sHS+ShVD5QIyqIxMEwKh12olC6mHIed5ewEop80CNlfIOADYOT2nd6ZXCop+Ebqchc0JqxKcKASxChycJgUh1rnHA5ow9eTrhqNI7JWiAYYwBGGdpyNLoGw0Pkh96h1BpHihyywtATDM/7Hk2fN9EnH8BgKJCU4ooBkbXFMZJiPbrOyecGl3zgQDQL4hk10IZiOe+5w99Q/gBAEIJgPhJM4QAEEoFREAIAAEiIASAkD8Qt4AQAEIAERAGFlX4CACKAXGVM4ivMwWwCLFAlyeoaa70QePKm5Dlp+/n+ye/5dYgva6YsUaVeMa+tzNFeJtWwc+udbJ0Fg399kLielQJ5Ze61c2+7ytA6EZetiPxZC6tj22yJCv6jUwOyj/zcbqAxOMyAKEbfeHtNa7DtYXptjsk2kJxR+eIeim/tHNofUKYy8DMrQcAKWz6brpvzyIAlpwPhQ49l6b7skJf5Z+YTOYQc4FwLDxvoTDwaygQK+U/kVr+ytSFBG01Q3gnJJR4cNiAhx4HDub8/b5DULXlj6SVZghFiE+LdvE9vo/o8Lp1RmH5hzm0T6wdbZ6n+D6i44zDRc3ln6CpAEJfXiRU45oqLz8gFAThWsh7ughrRibc0QynHgZpNJa/ENJ+loCwu/qOGnFIjYR/n7TfgycULhcQhu6VC+HfF+L3BoAQ4WiZTw1M+FPCnA2gKC6/FAhXgDC+ojQGh3NuWsvfF1L/D5ohlCKtl1j2ldu9a/nPAKFwN56Bst10zCG0CPleXN/zXPgHQZXaZaBgrbzyY5V/mUA+6F0hwtGN9rwu5DVZPuwWqfxdFz1LWbJ2lwKEa+0Qsm4Dl3fp+Pu0lV97PgwIPfSsS+UQhj5Oo+vvFULazRIQyvGEcxPuNLCth2MvFsrKn8UOilAQShkh7TTczYNMoS6OdP47msrPi82lXKGWhCdMZYS0bFy+vcnGAjP1CIfvgbKNA9glecEH9RD6Ol4wRuWyN/G9MHnksS6o/GPf5XcwNSUlHzQhDuAKtWJmkwKElU7lylP5rgIcsquh/FI8YZCDpkJBuE4FQm7Icw8N+SrUGaQKyi8FwiDt1ve5o+Vu7qYHy/psgK8cvh+FTYuO77bhEC7GuaPiys/L1X4IgXDL+e3M5+ovLxBy5VLuIebw1oqcHoPfoaMJUsHays878r8KbDc3xtPx/84gZPBG/JwaufrsY/SRG/OY3//8QMNdsvdZCFtbW6f8pFuf5bflILAlX7O+4fdfugKyFYS8T2zAsXthdG0VurPGKwI06oF5vkBgHWkNp6ry29+lsPZMU3vijnXFNmoclr+6+Ou/FIb8yb30sS8YGjmTqCLyQsi5N/6ZwKs0Yenj68pfPjF6N782Dp2FzV9CTyoSeY8mLK16qGxIkLI8oa1n8tz9juP40DlK0epxYEbojbq+9QfurBeVIlCO9D2396bxiV4lkYQ3hOAFw2pbhqMGISkkQOMcQ9EqhDmGZZdo92JC0YHRNTfoSg+5e0IT+opqCKHoIU+4ztQIgBD1EFNrQAgIpYSil9lDmPHqkROPt+JC6AgPquSuumJmg0YARVCuneDfvPVeJokZ6pIXDkNxQtGzTF9/BQjRG0tQznfb74RwCQghpALBtIQnfK4zhxdyQvVCUeknMIT3hLyY+T5jo0yABqKPQNpUNw/09tGZod5jgCaYFxyYvJcNPkv9eof+I3pnCFEHIETjSM8L9tHZHYCQT9PaZGycU6yg8S4akDnJ+P03L0+t23XGzCLzRgII/Wqa+fv/xlfvmKvMUOcOrlCDdoei1MGdZm6G5VEIfRzzjd4aQs69n699Rx7ewhvCGzr2gmTPs8zNsJOrXt24FbkhhOjCfT4ICA/rPbyhUy94Dks0gJCX1NzCZui9YUd3oei+c257TalFbgg19ILHrlrL2gvWgXAL26EX76gZTNASQnad8Ibwhl284NhgXpB0c+jKhWO3Ms1hP9ihJYB9eMF6qd1BCPk0qA1s+LimFIu7m4nsdQIzPK4VbQ8hYvrnuSH2G9b2ggP78QmWqBdF9Vx8SSY6QYdUW7BTA1schZATyhvY8lHvcRbNUS9YGFy2U+qmzh2YPVc0I7yAOFyHfRpyUwtCSzOdPXMHmz7qDIM0e0V2wZTEk+6Ym6N63eBLp/b5Bts+2cKCSJ/LuoZO3ANSiE5hKAZjnvNSS4931jcw9jpwT0feV/qSJ1pVtCyfHKDkvK8Ejx7pUxGh2xFNSwx8QTi2H9ceC0/nni64MS/5N5dG39pDqvRV+WgGk71c9VFXF9b+xYvOw/d61iv7m3MvEHryhvecwC52jSSx4VIIgwnMNT/UsTxIgpPt3K/ARj15CptwL3Zd/ceDSATj2DGQjbxgWwhdeMMte7zpy5On9vymRm/YxBYljGVjKWF9VJf7I1+sex3wY8w/V1QPTborW/72gkdsRDaZMJBdbdHIC7aCkAu9atlLbtnrzerMnyToDaGwelOnk3/hHSem/ZK7e/t7jeeR20LYBgqa8J80gS8jbwi5F02Uj1u2NYJxap8PLkJfLxA2hIJyvnHX/AfeEPLpBfe0uSFHbnXaea3Qd5d6HcpYZ8L6M7lnFwMQ3MNg+RxUR1+6AshtbsVgfXTEg1sIGax9UND2p7f270wdG3eK9gXVGHdw2k5sOyZv+Nbs39Z308XR9DqWb2J+PwKDhuKHPobfuXf7gnYGHdCs7bhDDadD4entDug7LWNsnRNW4mYqwJ9dk+GGSTPBiA2j0G8RWNM5upZtcG4/3vMfP7KnbK2egx6CCnDPhRn7NgD3cghLIad5WcM2SO38iqHvvMOosyeMpQ5zlVCaaj06GVs9xUbHdiKoqrHWgquFEFMWUEWfXUxJAML23hAHFOctmjZQffKD2pywkhtSGHKNtpitLroscAeE7kCkSsC60vxEl6yMtL9EL5HKGCMszU5bk8gdkklAyEn5FO0yK419rIxBOIqwFMooDE0tHEVYijAUECIshRCGIhxFWIowFJ5QkEYIS5PTJrUwNGlPyN6QQPyKtpuM1E/K5+YJDV/MiA3AaehzqgAm7QnZG9IGYKo8bHnSK7VblLL3hOwNHziPuEGOqE5brrdR6i+atCfckyeWD47HkAkepRGLY/e8A8J0gCwYSNypF08bBm+e6zVz2UL4AshhBUjML/rXLefqC82bcQFhGC9JDwZ1uuu+At0S5gCETYHsV4DUeD9fDN2Zfy5OXaW2zAwQygCzBLJ8cvaW5OXKC1FxfTggFAHmoAJnSiOw2wps9KwRWgJCLaEswaj5NqkLwAYIU4BxqTSXbHXpJdRMPZgAOiAMqABCNGYIEEJutEK5IUAIwYMDQgiCACEEAcJs1Vda7gGqDhCmoiEghAAhBAHCrKXVo2C1DCBMRlp37uMIEECoX7xrX3P5C9QiINSuIcoPAUI0YkAICLNWgfJDh4T9hH7zqYH9+JHAq7zBqWjwhPAicTVCVQJCNF50JghHocahKK0X/ZnQKyEkhSdUpzG8OgQI42qC94EQjsYLRSmH+pbgq73L6bYkeEJ4DYTYmeg1TOBFc/usTTp3V9DdEuXJ2xDCUbXhaXk0/kAYmBvuMB4qkC35E5e5AMKkwSQgyxufyuPy6fMMgAFCSI73LFXU/N8AmEL9X4ABACNSKMHAgb34AAAAAElFTkSuQmCC + mediatype: image/png + installModes: + - type: OwnNamespace + supported: true + - type: SingleNamespace + supported: true + - type: MultiNamespace + supported: false + - type: AllNamespaces + supported: true + install: + strategy: deployment + spec: + permissions: + - serviceAccountName: etcd-operator + rules: + - apiGroups: + - etcd.database.coreos.com + resources: + - etcdclusters + - etcdbackups + - etcdrestores + verbs: + - "*" + - apiGroups: + - "" + resources: + - pods + - services + - endpoints + - persistentvolumeclaims + - events + verbs: + - "*" + - apiGroups: + - apps + resources: + - deployments + verbs: + - "*" + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + deployments: + - name: etcd-operator + spec: + replicas: 1 + selector: + matchLabels: + name: etcd-operator-alm-owned + template: + metadata: + name: etcd-operator-alm-owned + labels: + name: etcd-operator-alm-owned + spec: + serviceAccountName: etcd-operator + containers: + - name: etcd-operator + command: + - etcd-operator + - --create-crd=false + image: quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8 + env: + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: etcd-backup-operator + image: quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8 + command: + - etcd-backup-operator + - --create-crd=false + env: + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: etcd-restore-operator + image: quay.io/coreos/etcd-operator@sha256:db563baa8194fcfe39d1df744ed70024b0f1f9e9b55b5923c2f3a413c44dc6b8 + command: + - etcd-restore-operator + - --create-crd=false + env: + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + customresourcedefinitions: + owned: + - name: etcdclusters.etcd.database.coreos.com + version: v1beta2 + kind: EtcdCluster + displayName: etcd Cluster + description: Represents a cluster of etcd nodes. + resources: + - kind: Service + version: v1 + - kind: Pod + version: v1 + specDescriptors: + - description: The desired number of member Pods for the etcd cluster. + displayName: Size + path: size + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:podCount' + - description: Limits describes the minimum/maximum amount of compute resources required/allowed + displayName: Resource Requirements + path: pod.resources + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:resourceRequirements' + statusDescriptors: + - description: The status of each of the member Pods for the etcd cluster. + displayName: Member Status + path: members + x-descriptors: + - 'urn:alm:descriptor:com.tectonic.ui:podStatuses' + - description: The service at which the running etcd cluster can be accessed. + displayName: Service + path: serviceName + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes:Service' + - description: The current size of the etcd cluster. + displayName: Cluster Size + path: size + - description: The current version of the etcd cluster. + displayName: Current Version + path: currentVersion + - description: 'The target version of the etcd cluster, after upgrading.' + displayName: Target Version + path: targetVersion + - description: The current status of the etcd cluster. + displayName: Status + path: phase + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes.phase' + - description: Explanation for the current status of the cluster. + displayName: Status Details + path: reason + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes.phase:reason' + - name: etcdbackups.etcd.database.coreos.com + version: v1beta2 + kind: EtcdBackup + displayName: etcd Backup + description: Represents the intent to backup an etcd cluster. + specDescriptors: + - description: Specifies the endpoints of an etcd cluster. + displayName: etcd Endpoint(s) + path: etcdEndpoints + x-descriptors: + - 'urn:alm:descriptor:etcd:endpoint' + - description: The full AWS S3 path where the backup is saved. + displayName: S3 Path + path: s3.path + x-descriptors: + - 'urn:alm:descriptor:aws:s3:path' + - description: The name of the secret object that stores the AWS credential and config files. + displayName: AWS Secret + path: s3.awsSecret + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes:Secret' + statusDescriptors: + - description: Indicates if the backup was successful. + displayName: Succeeded + path: succeeded + x-descriptors: + - 'urn:alm:descriptor:text' + - description: Indicates the reason for any backup related failures. + displayName: Reason + path: reason + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes.phase:reason' + - name: etcdrestores.etcd.database.coreos.com + version: v1beta2 + kind: EtcdRestore + displayName: etcd Restore + description: Represents the intent to restore an etcd cluster from a backup. + specDescriptors: + - description: References the EtcdCluster which should be restored, + displayName: etcd Cluster + path: etcdCluster.name + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes:EtcdCluster' + - 'urn:alm:descriptor:text' + - description: The full AWS S3 path where the backup is saved. + displayName: S3 Path + path: s3.path + x-descriptors: + - 'urn:alm:descriptor:aws:s3:path' + - description: The name of the secret object that stores the AWS credential and config files. + displayName: AWS Secret + path: s3.awsSecret + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes:Secret' + statusDescriptors: + - description: Indicates if the restore was successful. + displayName: Succeeded + path: succeeded + x-descriptors: + - 'urn:alm:descriptor:text' + - description: Indicates the reason for any restore related failures. + displayName: Reason + path: reason + x-descriptors: + - 'urn:alm:descriptor:io.kubernetes.phase:reason' \ No newline at end of file diff --git a/pkg/validation/internal/typecheck.go b/pkg/validation/internal/typecheck.go new file mode 100644 index 000000000..4e5d386b8 --- /dev/null +++ b/pkg/validation/internal/typecheck.go @@ -0,0 +1,100 @@ +package internal + +import ( + "encoding/json" + "fmt" + "reflect" + "strings" + + "github.com/operator-framework/api/pkg/validation/errors" +) + +// Recursive function that traverses a nested struct passed in as reflect +// value, and reports for errors/warnings in case of nil struct field values. +// TODO: make iterative +func checkEmptyFields(result *errors.ManifestResult, v reflect.Value, parentStructName string) { + if v.Kind() != reflect.Struct { + return + } + typ := v.Type() + + for i := 0; i < v.NumField(); i++ { + fieldValue := v.Field(i) + fieldType := typ.Field(i) + + tag := fieldType.Tag.Get("json") + // Ignore fields that are subsets of a primitive field. + if tag == "" { + continue + } + + // Omitted field tags will contain ",omitempty", and ignored tags will + // match "-" exactly, respectively. + isOptionalField := strings.Contains(tag, ",omitempty") || tag == "-" + emptyVal := isEmptyValue(fieldValue) + + newParentStructName := fieldType.Name + if parentStructName != "" { + newParentStructName = parentStructName + "." + newParentStructName + } + + switch fieldValue.Kind() { + case reflect.Struct: + updateResult(result, "struct", newParentStructName, emptyVal, isOptionalField) + if !emptyVal { + checkEmptyFields(result, fieldValue, newParentStructName) + } + default: + updateResult(result, "field", newParentStructName, emptyVal, isOptionalField) + } + } +} + +// Returns updated ManifestResult with missing optional/mandatory field/struct objects. +func updateResult(result *errors.ManifestResult, typeName string, newParentStructName string, emptyVal bool, isOptionalField bool) { + if !emptyVal { + return + } + if isOptionalField { + // TODO: update the value field (typeName). + result.Add(errors.WarnFieldMissing("", newParentStructName, typeName)) + } else if newParentStructName != "Status" { + // TODO: update the value field (typeName). + result.Add(errors.ErrFieldMissing("", newParentStructName, typeName)) + } +} + +// Uses reflect package to check if the value of the object passed is null, returns a boolean accordingly. +// TODO: replace with reflect.Kind.IsZero() in go 1.13 +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + // Check if the value for 'Spec.InstallStrategy.StrategySpecRaw' field is present. This field is a RawMessage value type. Without a value, the key is explicitly set to 'null'. + if fieldValue, ok := v.Interface().(json.RawMessage); ok { + valString := string(fieldValue) + if valString == "null" { + return true + } + } + return v.Len() == 0 + // Currently the only CSV field with integer type is containerPort. Operator Verification Library raises a warning if containerPort field is missisng or if its value is 0. + // It is an optional field so the user can ignore the warning saying this field is missing if they intend to use port 0. + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Struct: + for i, n := 0, v.NumField(); i < n; i++ { + if !isEmptyValue(v.Field(i)) { + return false + } + } + return true + default: + panic(fmt.Sprintf("%v kind is not supported.", v.Kind())) + } +} diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go new file mode 100644 index 000000000..8e249108c --- /dev/null +++ b/pkg/validation/validation.go @@ -0,0 +1,62 @@ +// Package validation provides default Validator's that can be run with a list +// of arbitrary objects. The defaults exposed here consist of all Validator's +// implemented by this validation library. +// +// Each default Validator runs an independent set of validation functions on +// a set of objects. To run all implemented Validator's, use DefaultValidators(). +// The Validator will not be run on objects not of the appropriate type. + +package validation + +import ( + interfaces "github.com/operator-framework/api/pkg/validation/interfaces" + "github.com/operator-framework/api/pkg/validation/internal" +) + +// DefaultPackageManifestValidators returns a package manifest Validator that +// can be run directly with Apply(objs...). Optionally, any additional +// Validator's can be added to the returned Validators set. +func DefaultPackageManifestValidators(vals ...interfaces.Validator) interfaces.Validators { + return append(vals, internal.PackageManifestValidator{}) +} + +// DefaultClusterServiceVersionValidators returns a ClusterServiceVersion +// Validator that can be run directly with Apply(objs...). Optionally, any +// additional Validator's can be added to the returned Validators set. +func DefaultClusterServiceVersionValidators(vals ...interfaces.Validator) interfaces.Validators { + return append(vals, internal.CSVValidator{}) +} + +// DefaultCustomResourceDefinitionValidators returns a CustomResourceDefinition +// Validator that can be run directly with Apply(objs...). Optionally, any +// additional Validator's can be added to the returned Validators set. +func DefaultCustomResourceDefinitionValidators(vals ...interfaces.Validator) interfaces.Validators { + return append(vals, internal.CRDValidator{}) +} + +// DefaultBundleValidators returns a bundle Validator that can be run directly +// with Apply(objs...). Optionally, any additional Validator's can be added to +// the returned Validators set. +func DefaultBundleValidators(vals ...interfaces.Validator) interfaces.Validators { + return append(vals, internal.BundleValidator{}) +} + +// DefaultManifestsValidators returns a manifests Validator that can be run +// directly with Apply(objs...). Optionally, any additional Validator's can be +// added to the returned Validators set. +func DefaultManifestsValidators(vals ...interfaces.Validator) interfaces.Validators { + return append(vals, internal.ManifestsValidator{}) +} + +// DefaultValidators returns all default Validator's, which can be run directly +// with Apply(objs...). Optionally, any additional Validator's can be added to +// the returned Validators set. +func DefaultValidators(vals ...interfaces.Validator) interfaces.Validators { + return append(vals, + internal.PackageManifestValidator{}, + internal.CSVValidator{}, + internal.CRDValidator{}, + internal.BundleValidator{}, + internal.ManifestsValidator{}, + ) +} From 02bfec5a82a689001bdd42fb14b01f5e5b71d3e6 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Fri, 15 Nov 2019 10:39:21 -0800 Subject: [PATCH 20/20] pkg/validation/interfaces: Validator's implement WithValidators to append non-default Validators to the Validator; ValidatorFunc implements Validator --- pkg/manifests/directory.go | 2 +- pkg/validation/doc.go | 8 +-- pkg/validation/interfaces/validator.go | 30 ++++++++-- pkg/validation/internal/bundle.go | 5 +- pkg/validation/internal/crd.go | 5 +- pkg/validation/internal/csv.go | 7 ++- pkg/validation/internal/manifests.go | 9 +-- pkg/validation/internal/package_manifest.go | 5 +- pkg/validation/validation.go | 65 +++++++-------------- 9 files changed, 71 insertions(+), 65 deletions(-) diff --git a/pkg/manifests/directory.go b/pkg/manifests/directory.go index e31077128..e910b55df 100644 --- a/pkg/manifests/directory.go +++ b/pkg/manifests/directory.go @@ -26,6 +26,6 @@ func GetManifestsDir(dir string) (registry.PackageManifest, []*registry.Bundle, for _, obj := range bundles { objs = append(objs, obj) } - results := validation.DefaultValidators().Apply(objs...) + results := validation.AllValidators.Validate(objs...) return pkg, bundles, results } diff --git a/pkg/validation/doc.go b/pkg/validation/doc.go index fdc867312..079e4510a 100644 --- a/pkg/validation/doc.go +++ b/pkg/validation/doc.go @@ -1,11 +1,11 @@ // This package defines the valid Operator manifests directory format -// by exposing a set of interfaces.Validator's to verify a directory and +// by exposing a set of Validator's to verify a directory and // its constituent manifests. A manifests directory consists of a -// Package Manifest and a set of versioned Bundles. Each Bundle contains a +// package manifest and a set of versioned Bundles. Each Bundle contains a // ClusterServiceVersion and one or more CustomResourceDefinition's. // -// Errors and warnings, both represented by the errors.Error type, are -// returned by exported functions for missing mandatory and optional fields, +// Errors and warnings, both represented by the Error type, are returned +// by exported functions for missing mandatory and optional fields, // respectively. Each Error implements the error interface. // // Manifest and Bundle format: https://github.com/operator-framework/operator-registry/#manifest-format diff --git a/pkg/validation/interfaces/validator.go b/pkg/validation/interfaces/validator.go index 4d2e6f7c1..5c498302e 100644 --- a/pkg/validation/interfaces/validator.go +++ b/pkg/validation/interfaces/validator.go @@ -9,16 +9,38 @@ type Validator interface { // Validate takes a list of arbitrary objects and returns a slice of results, // one for each object validated. Validate(...interface{}) []errors.ManifestResult + // WithValidators returns a Validator appended to a variable number of + // Validator's. + WithValidators(...Validator) Validators } -// Validators is a set of Validator's that can be run via Apply. +// ValidatorFunc implements Validator. ValidatorFunc can be used as a wrapper +// for functions that run object validators. +type ValidatorFunc func(...interface{}) []errors.ManifestResult + +// Validate runs the ValidatorFunc on objs. +func (f ValidatorFunc) Validate(objs ...interface{}) (results []errors.ManifestResult) { + return f(objs...) +} + +// WithValidators appends the ValidatorFunc to vals. +func (f ValidatorFunc) WithValidators(vals ...Validator) Validators { + return append(vals, f) +} + +// Validators is a set of Validator's that implements Validate. type Validators []Validator -// ApplyParallel invokes each Validator in vals, and collects and returns +// Validate invokes each Validator in Validators, collecting and returning // the results. -func (vals Validators) Apply(objs ...interface{}) (results []errors.ManifestResult) { - for _, validator := range vals { +func (validators Validators) Validate(objs ...interface{}) (results []errors.ManifestResult) { + for _, validator := range validators { results = append(results, validator.Validate(objs...)...) } return results } + +// WithValidators appends vals to Validators. +func (validators Validators) WithValidators(vals ...Validator) Validators { + return append(vals, validators...) +} diff --git a/pkg/validation/internal/bundle.go b/pkg/validation/internal/bundle.go index 67ee538f8..7c09b1191 100644 --- a/pkg/validation/internal/bundle.go +++ b/pkg/validation/internal/bundle.go @@ -5,14 +5,15 @@ import ( "fmt" "github.com/operator-framework/api/pkg/validation/errors" + interfaces "github.com/operator-framework/api/pkg/validation/interfaces" operatorsv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" "github.com/operator-framework/operator-registry/pkg/registry" ) -type BundleValidator struct{} +var BundleValidator interfaces.Validator = interfaces.ValidatorFunc(validateBundles) -func (f BundleValidator) Validate(objs ...interface{}) (results []errors.ManifestResult) { +func validateBundles(objs ...interface{}) (results []errors.ManifestResult) { for _, obj := range objs { switch v := obj.(type) { case *registry.Bundle: diff --git a/pkg/validation/internal/crd.go b/pkg/validation/internal/crd.go index 15e94f4c6..d545ef585 100644 --- a/pkg/validation/internal/crd.go +++ b/pkg/validation/internal/crd.go @@ -4,6 +4,7 @@ import ( "strings" "github.com/operator-framework/api/pkg/validation/errors" + interfaces "github.com/operator-framework/api/pkg/validation/interfaces" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install" @@ -19,9 +20,9 @@ func init() { install.Install(Scheme) } -type CRDValidator struct{} +var CRDValidator interfaces.Validator = interfaces.ValidatorFunc(validateCRDs) -func (f CRDValidator) Validate(objs ...interface{}) (results []errors.ManifestResult) { +func validateCRDs(objs ...interface{}) (results []errors.ManifestResult) { for _, obj := range objs { switch v := obj.(type) { case *v1beta1.CustomResourceDefinition: diff --git a/pkg/validation/internal/csv.go b/pkg/validation/internal/csv.go index c930bc9a5..27a573844 100644 --- a/pkg/validation/internal/csv.go +++ b/pkg/validation/internal/csv.go @@ -7,19 +7,20 @@ import ( "strings" "github.com/operator-framework/api/pkg/validation/errors" - "github.com/operator-framework/operator-registry/pkg/registry" + interfaces "github.com/operator-framework/api/pkg/validation/interfaces" "github.com/blang/semver" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" + "github.com/operator-framework/operator-registry/pkg/registry" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" k8svalidation "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/yaml" ) -type CSVValidator struct{} +var CSVValidator interfaces.Validator = interfaces.ValidatorFunc(validateCSVs) -func (f CSVValidator) Validate(objs ...interface{}) (results []errors.ManifestResult) { +func validateCSVs(objs ...interface{}) (results []errors.ManifestResult) { for _, obj := range objs { switch v := obj.(type) { case *v1alpha1.ClusterServiceVersion: diff --git a/pkg/validation/internal/manifests.go b/pkg/validation/internal/manifests.go index 2781bf799..4b65d1318 100644 --- a/pkg/validation/internal/manifests.go +++ b/pkg/validation/internal/manifests.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/operator-framework/api/pkg/validation/errors" + interfaces "github.com/operator-framework/api/pkg/validation/interfaces" "github.com/blang/semver" "github.com/operator-framework/operator-registry/pkg/registry" @@ -11,9 +12,9 @@ import ( const skipPackageAnnotationKey = "olm.skipRange" -type ManifestsValidator struct{} +var PackageUpdateGraphValidator interfaces.Validator = interfaces.ValidatorFunc(validatePackageUpdateGraphs) -func (f ManifestsValidator) Validate(objs ...interface{}) (results []errors.ManifestResult) { +func validatePackageUpdateGraphs(objs ...interface{}) (results []errors.ManifestResult) { var pkg *registry.PackageManifest bundles := []*registry.Bundle{} for _, obj := range objs { @@ -27,12 +28,12 @@ func (f ManifestsValidator) Validate(objs ...interface{}) (results []errors.Mani } } if pkg != nil && len(bundles) > 0 { - results = append(results, validateManifests(pkg, bundles)) + results = append(results, validatePackageUpdateGraph(pkg, bundles)) } return results } -func validateManifests(pkg *registry.PackageManifest, bundles []*registry.Bundle) (result errors.ManifestResult) { +func validatePackageUpdateGraph(pkg *registry.PackageManifest, bundles []*registry.Bundle) (result errors.ManifestResult) { // Collect all CSV names and ensure no duplicates. We will use these names // to check whether a spec.replaces references an existing CSV in bundles. csvNameMap := map[string]struct{}{} diff --git a/pkg/validation/internal/package_manifest.go b/pkg/validation/internal/package_manifest.go index 76b19f5cc..a4dfd6ce6 100644 --- a/pkg/validation/internal/package_manifest.go +++ b/pkg/validation/internal/package_manifest.go @@ -4,13 +4,14 @@ import ( "fmt" "github.com/operator-framework/api/pkg/validation/errors" + interfaces "github.com/operator-framework/api/pkg/validation/interfaces" "github.com/operator-framework/operator-registry/pkg/registry" ) -type PackageManifestValidator struct{} +var PackageManifestValidator interfaces.Validator = interfaces.ValidatorFunc(validatePackageManifests) -func (f PackageManifestValidator) Validate(objs ...interface{}) (results []errors.ManifestResult) { +func validatePackageManifests(objs ...interface{}) (results []errors.ManifestResult) { for _, obj := range objs { switch v := obj.(type) { case *registry.PackageManifest: diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index 8e249108c..ffe01f2ef 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -3,8 +3,8 @@ // implemented by this validation library. // // Each default Validator runs an independent set of validation functions on -// a set of objects. To run all implemented Validator's, use DefaultValidators(). -// The Validator will not be run on objects not of the appropriate type. +// a set of objects. To run all implemented Validator's, use AllValidators. +// The Validator will not be run on objects of an inappropriate type. package validation @@ -13,50 +13,29 @@ import ( "github.com/operator-framework/api/pkg/validation/internal" ) -// DefaultPackageManifestValidators returns a package manifest Validator that -// can be run directly with Apply(objs...). Optionally, any additional -// Validator's can be added to the returned Validators set. -func DefaultPackageManifestValidators(vals ...interfaces.Validator) interfaces.Validators { - return append(vals, internal.PackageManifestValidator{}) -} +// PackageManifestValidator implements Validator to validate package manifests. +var PackageManifestValidator = internal.PackageManifestValidator -// DefaultClusterServiceVersionValidators returns a ClusterServiceVersion -// Validator that can be run directly with Apply(objs...). Optionally, any -// additional Validator's can be added to the returned Validators set. -func DefaultClusterServiceVersionValidators(vals ...interfaces.Validator) interfaces.Validators { - return append(vals, internal.CSVValidator{}) -} +// ClusterServiceVersionValidator implements Validator to validate +// ClusterServiceVersions. +var ClusterServiceVersionValidator = internal.CSVValidator -// DefaultCustomResourceDefinitionValidators returns a CustomResourceDefinition -// Validator that can be run directly with Apply(objs...). Optionally, any -// additional Validator's can be added to the returned Validators set. -func DefaultCustomResourceDefinitionValidators(vals ...interfaces.Validator) interfaces.Validators { - return append(vals, internal.CRDValidator{}) -} +// CustomResourceDefinitionValidator implements Validator to validate +// CustomResourceDefinitions. +var CustomResourceDefinitionValidator = internal.CRDValidator -// DefaultBundleValidators returns a bundle Validator that can be run directly -// with Apply(objs...). Optionally, any additional Validator's can be added to -// the returned Validators set. -func DefaultBundleValidators(vals ...interfaces.Validator) interfaces.Validators { - return append(vals, internal.BundleValidator{}) -} +// BundleValidator implements Validator to validate Bundles. +var BundleValidator = internal.BundleValidator -// DefaultManifestsValidators returns a manifests Validator that can be run -// directly with Apply(objs...). Optionally, any additional Validator's can be -// added to the returned Validators set. -func DefaultManifestsValidators(vals ...interfaces.Validator) interfaces.Validators { - return append(vals, internal.ManifestsValidator{}) -} +// PackageUpdateGraphValidator implements Validator to validate the +// package update graph between a package manifest and Bundles. +var PackageUpdateGraphValidator = internal.PackageUpdateGraphValidator -// DefaultValidators returns all default Validator's, which can be run directly -// with Apply(objs...). Optionally, any additional Validator's can be added to -// the returned Validators set. -func DefaultValidators(vals ...interfaces.Validator) interfaces.Validators { - return append(vals, - internal.PackageManifestValidator{}, - internal.CSVValidator{}, - internal.CRDValidator{}, - internal.BundleValidator{}, - internal.ManifestsValidator{}, - ) +// AllValidators implements Validator to validate all Operator manifest types. +var AllValidators = interfaces.Validators{ + PackageManifestValidator, + ClusterServiceVersionValidator, + CustomResourceDefinitionValidator, + BundleValidator, + PackageUpdateGraphValidator, }