From 81e90b2b24f006a34cd0a4f66b3aae6dee496b96 Mon Sep 17 00:00:00 2001 From: Dennis Li <23002167+dli357@users.noreply.github.com> Date: Thu, 31 Mar 2022 02:43:38 -0700 Subject: [PATCH] [CDAP-18995] Add env var support across all CDAP services [CDAP-18995] Switched to using maps and added unit test --- api/v1alpha1/cdapmaster_types.go | 2 + api/v1alpha1/zz_generated.deepcopy.go | 8 ++ .../crd/bases/cdap.cdap.io_cdapmasters.yaml | 100 +++++++++++++++++ controllers/deployment.go | 65 ++++++++++- controllers/deployment_test.go | 103 ++++++++++++++++++ controllers/testdata/appfabric.json | 8 ++ controllers/testdata/artifactcache.json | 4 + controllers/testdata/authentication.json | 4 + controllers/testdata/cdap_master_cr.json | 18 ++- controllers/testdata/logs.json | 4 + controllers/testdata/messaging.json | 4 + controllers/testdata/metadata.json | 4 + controllers/testdata/metrics.json | 4 + controllers/testdata/preview.json | 4 + controllers/testdata/router.json | 4 + controllers/testdata/runtime.json | 8 ++ controllers/testdata/supportbundle.json | 4 + controllers/testdata/tetheringagent.json | 4 + controllers/testdata/userinterface.json | 4 + go.mod | 1 + go.sum | 6 +- 21 files changed, 354 insertions(+), 9 deletions(-) diff --git a/api/v1alpha1/cdapmaster_types.go b/api/v1alpha1/cdapmaster_types.go index 4bef8d1d..c4e0397f 100644 --- a/api/v1alpha1/cdapmaster_types.go +++ b/api/v1alpha1/cdapmaster_types.go @@ -42,6 +42,8 @@ type CDAPMasterSpec struct { SecuritySecret string `json:"securitySecret,omitempty"` // ServiceAccountName is the service account for all the service pods. ServiceAccountName string `json:"serviceAccountName,omitempty"` + // Env is a list of environment variables for the all service containers. + Env []corev1.EnvVar `json:"env,omitempty"` // LocationURI is an URI specifying an object storage for CDAP. LocationURI string `json:"locationURI"` // Config is a set of configurations that goes into cdap-site.xml. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 4632f399..ac077cfb 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1,3 +1,4 @@ +//go:build !ignore_autogenerated // +build !ignore_autogenerated /* @@ -160,6 +161,13 @@ func (in *CDAPMasterList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CDAPMasterSpec) DeepCopyInto(out *CDAPMasterSpec) { *out = *in + if in.Env != nil { + in, out := &in.Env, &out.Env + *out = make([]v1.EnvVar, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } if in.Config != nil { in, out := &in.Config, &out.Config *out = make(map[string]string, len(*in)) diff --git a/config/crd/bases/cdap.cdap.io_cdapmasters.yaml b/config/crd/bases/cdap.cdap.io_cdapmasters.yaml index 22ad91fe..547facb5 100644 --- a/config/crd/bases/cdap.cdap.io_cdapmasters.yaml +++ b/config/crd/bases/cdap.cdap.io_cdapmasters.yaml @@ -6292,6 +6292,106 @@ spec: mount path. This adds ConfigMap data to the directory specified by the volume mount path. type: object + env: + description: Env is a list of environment variables for the all service + containers. + items: + description: EnvVar represents an environment variable present in + a Container. + properties: + name: + description: Name of the environment variable. Must be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded using + the previous defined environment variables in the container + and any service environment variables. If a variable cannot + be resolved, the reference in the input string will be unchanged. + The $(VAR_NAME) syntax can be escaped with a double $$, ie: + $$(VAR_NAME). Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. Cannot + be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its key + must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, metadata.labels, metadata.annotations, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath is written + in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified + API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only resources + limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, + requests.cpu, requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, optional + for env vars' + type: string + divisor: + description: Specifies the output format of the exposed + resources, defaults to "1" + type: string + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array image: description: Image is the docker image name for the CDAP backend. type: string diff --git a/controllers/deployment.go b/controllers/deployment.go index e26744a6..a36c4808 100644 --- a/controllers/deployment.go +++ b/controllers/deployment.go @@ -3,6 +3,7 @@ package controllers import ( "fmt" "reflect" + "sort" "strings" "cdap.io/cdap-operator/api/v1alpha1" @@ -186,7 +187,10 @@ func buildStatefulSets(master *v1alpha1.CDAPMaster, name string, services Servic continue } - c := serviceContainerSpec(ss, master, dataDir, s) + c, err := serviceContainerSpec(ss, master, dataDir, s) + if err != nil { + return nil, err + } spec = spec.withContainer(c) if err := addSystemMetricsServiceIfEnabled(spec, master, ss, dataDir, c); err != nil { return nil, err @@ -246,7 +250,10 @@ func addSystemMetricsServiceIfEnabled(stsSpec *StatefulSpec, master *v1alpha1.CD if err != nil { return err } - c := serviceContainerSpec(ss, master, dataDir, serviceSystemMetricsExporter) + c, err := serviceContainerSpec(ss, master, dataDir, serviceSystemMetricsExporter) + if err != nil { + return err + } stsSpec = stsSpec.withContainer(c) // add env variable to start jmx server in the main container varAdded := false @@ -315,7 +322,11 @@ func buildDeployment(master *v1alpha1.CDAPMaster, name string, services ServiceG if ss == nil { continue } - c := serviceContainerSpec(ss, master, dataDir, s) + + c, err := serviceContainerSpec(ss, master, dataDir, s) + if err != nil { + return nil, err + } spec = spec.withContainer(c) // Adding a label to allow k8s service selector to easily find the pod @@ -343,14 +354,56 @@ func buildDeployment(master *v1alpha1.CDAPMaster, name string, services ServiceG return spec, nil } +type ByEnvKey []corev1.EnvVar + +func (k ByEnvKey) Len() int { return len(k) } +func (k ByEnvKey) Swap(i, j int) { k[i], k[j] = k[j], k[i] } +func (k ByEnvKey) Less(i, j int) bool { return k[i].Name < k[j].Name } + +func mergeEnvVars(baseEnvVars []corev1.EnvVar, overwriteEnvVars []corev1.EnvVar) ([]corev1.EnvVar, error) { + // Merge base and overwrite environment variables + envMap := make(map[string]corev1.EnvVar) + // Add base environment variables. + for _, baseEnvVar := range baseEnvVars { + if _, ok := envMap[baseEnvVar.Name]; ok { + return nil, fmt.Errorf("duplicate env var %q in base slice", baseEnvVar.Name) + } + envMap[baseEnvVar.Name] = baseEnvVar + } + + // Add and overwrite the provided environment variables. + // Maintain a seen map and throw an error if there are duplicates in the overwrite env var slice. + seenVars := make(map[string]bool) + for _, envVar := range overwriteEnvVars { + if _, ok := seenVars[envVar.Name]; ok { + return nil, fmt.Errorf("duplicate env var %q in overwrite slice", envVar.Name) + } + seenVars[envVar.Name] = true + envMap[envVar.Name] = envVar + } + + // Convert the map to a sorted slice. + env := []corev1.EnvVar{} + for _, envVar := range envMap { + env = append(env, envVar) + } + sort.Sort(ByEnvKey(env)) + return env, nil +} + func serviceContainerSpec(ss *v1alpha1.CDAPServiceSpec, - master *v1alpha1.CDAPMaster, dataDir string, service ServiceName) *ContainerSpec { - env := addJavaMaxHeapEnvIfNotPresent(ss.Env, ss.Resources) + master *v1alpha1.CDAPMaster, dataDir string, service ServiceName) (*ContainerSpec, error) { + // Merge environment variables between service spec and master spec + env, err := mergeEnvVars(master.Spec.Env, ss.Env) + if err != nil { + return nil, fmt.Errorf("failed to merge env vars for service %q with error: %v", service, err) + } + env = addJavaMaxHeapEnvIfNotPresent(env, ss.Resources) c := newContainerSpec(master, service, dataDir).setResources(ss.Resources).setEnv(env).setLifecycle(ss.Lifecycle) if service == serviceUserInterface { c = updateSpecForUserInterface(master, c) } - return c + return c, nil } // Return a list of reconciler objects (e.g. statefulsets, deployment, NodePort service) for the given deployment plan diff --git a/controllers/deployment_test.go b/controllers/deployment_test.go index f19caff4..c00bf180 100644 --- a/controllers/deployment_test.go +++ b/controllers/deployment_test.go @@ -4,8 +4,11 @@ import ( "encoding/json" "fmt" "io/ioutil" + "testing" "cdap.io/cdap-operator/api/v1alpha1" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/nsf/jsondiff" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -254,3 +257,103 @@ var _ = Describe("Controller Suite", func() { }) }) }) + +func TestMergeEnvVars(t *testing.T) { + testCases := []struct { + description string + baseEnvVars []corev1.EnvVar + overwriteEnvVars []corev1.EnvVar + wantEnv []corev1.EnvVar + wantErr error + }{ + { + description: "Empty slices returns no env vars", + baseEnvVars: []corev1.EnvVar{}, + overwriteEnvVars: []corev1.EnvVar{}, + wantEnv: []corev1.EnvVar{}, + }, + { + description: "Only one env var in base slice returns one env var", + baseEnvVars: []corev1.EnvVar{corev1.EnvVar{Name: "test", Value: "test-value"}}, + overwriteEnvVars: []corev1.EnvVar{}, + wantEnv: []corev1.EnvVar{corev1.EnvVar{Name: "test", Value: "test-value"}}, + }, + { + description: "Only one env var in overwrite slice returns one env var", + baseEnvVars: []corev1.EnvVar{}, + overwriteEnvVars: []corev1.EnvVar{corev1.EnvVar{Name: "test", Value: "test-value"}}, + wantEnv: []corev1.EnvVar{corev1.EnvVar{Name: "test", Value: "test-value"}}, + }, + { + description: "One different env var in each slice returns two env var", + baseEnvVars: []corev1.EnvVar{corev1.EnvVar{Name: "test-a", Value: "test-value-a"}}, + overwriteEnvVars: []corev1.EnvVar{corev1.EnvVar{Name: "test-b", Value: "test-value-b"}}, + wantEnv: []corev1.EnvVar{ + corev1.EnvVar{Name: "test-a", Value: "test-value-a"}, + corev1.EnvVar{Name: "test-b", Value: "test-value-b"}, + }, + }, + { + description: "Env var in overwrite slice overwrites expected env var from base slice", + baseEnvVars: []corev1.EnvVar{ + corev1.EnvVar{Name: "test-a", Value: "test-value-a"}, + corev1.EnvVar{Name: "test-b", Value: "test-value-b"}, + }, + overwriteEnvVars: []corev1.EnvVar{ + corev1.EnvVar{Name: "test-b", Value: "test-value-d"}, + corev1.EnvVar{Name: "test-c", Value: "test-value-c"}, + }, + wantEnv: []corev1.EnvVar{ + corev1.EnvVar{Name: "test-a", Value: "test-value-a"}, + corev1.EnvVar{Name: "test-b", Value: "test-value-d"}, + corev1.EnvVar{Name: "test-c", Value: "test-value-c"}, + }, + }, + { + description: "Multiple env vars in both slices returns env vars in sorted order", + baseEnvVars: []corev1.EnvVar{ + corev1.EnvVar{Name: "a", Value: "test-value-a"}, + corev1.EnvVar{Name: "c", Value: "test-value-c"}, + }, + overwriteEnvVars: []corev1.EnvVar{ + corev1.EnvVar{Name: "d", Value: "test-value-d"}, + corev1.EnvVar{Name: "b", Value: "test-value-b"}, + }, + wantEnv: []corev1.EnvVar{ + corev1.EnvVar{Name: "a", Value: "test-value-a"}, + corev1.EnvVar{Name: "b", Value: "test-value-b"}, + corev1.EnvVar{Name: "c", Value: "test-value-c"}, + corev1.EnvVar{Name: "d", Value: "test-value-d"}, + }, + }, + { + description: "Duplicate env var keys in base slice returns error", + baseEnvVars: []corev1.EnvVar{ + corev1.EnvVar{Name: "test-a", Value: "test-value-a"}, + corev1.EnvVar{Name: "test-a", Value: "test-value-b"}, + }, + overwriteEnvVars: []corev1.EnvVar{}, + wantErr: cmpopts.AnyError, + }, + { + description: "Duplicate env var keys in overwrite slice returns error", + baseEnvVars: []corev1.EnvVar{}, + overwriteEnvVars: []corev1.EnvVar{ + corev1.EnvVar{Name: "test-a", Value: "test-value-a"}, + corev1.EnvVar{Name: "test-a", Value: "test-value-b"}, + }, + wantErr: cmpopts.AnyError, + }, + } + for _, testCase := range testCases { + t.Run(testCase.description, func(t *testing.T) { + gotEnv, err := mergeEnvVars(testCase.baseEnvVars, testCase.overwriteEnvVars) + if got, want := gotEnv, testCase.wantEnv; !cmp.Equal(got, want) { + t.Errorf("mergeEnvVars(%+v, %+v): unexpected env slice: got %+v, want %+v", testCase.baseEnvVars, testCase.overwriteEnvVars, got, want) + } + if got, want := err, testCase.wantErr; !cmp.Equal(got, want, cmpopts.EquateErrors()) { + t.Errorf("mergeEnvVars(%+v, %+v): unexpected env slice: got %v, want %v", testCase.baseEnvVars, testCase.overwriteEnvVars, got, want) + } + }) + } +} diff --git a/controllers/testdata/appfabric.json b/controllers/testdata/appfabric.json index b36502f7..a885faa9 100644 --- a/controllers/testdata/appfabric.json +++ b/controllers/testdata/appfabric.json @@ -73,6 +73,14 @@ "--env=k8s" ], "env": [ + { + "name": "all-services-test", + "value": "some-value-overridden" + }, + { + "name": "appfabric-env-var-test", + "value": "some-value" + }, { "name": "JAVA_HEAPMAX", "value": "-Xmx62914560" diff --git a/controllers/testdata/artifactcache.json b/controllers/testdata/artifactcache.json index c9fb4f59..e1842e35 100644 --- a/controllers/testdata/artifactcache.json +++ b/controllers/testdata/artifactcache.json @@ -208,6 +208,10 @@ "--env=k8s" ], "env":[ + { + "name": "all-services-test", + "value": "some-value" + }, { "name":"JAVA_HEAPMAX", "value":"-Xmx125829120" diff --git a/controllers/testdata/authentication.json b/controllers/testdata/authentication.json index ab4d1747..53f5b581 100644 --- a/controllers/testdata/authentication.json +++ b/controllers/testdata/authentication.json @@ -73,6 +73,10 @@ "--env=k8s" ], "env": [ + { + "name": "all-services-test", + "value": "some-value" + }, { "name": "JAVA_HEAPMAX", "value": "-Xmx62914560" diff --git a/controllers/testdata/cdap_master_cr.json b/controllers/testdata/cdap_master_cr.json index 0cbfddd5..2c50eec8 100644 --- a/controllers/testdata/cdap_master_cr.json +++ b/controllers/testdata/cdap_master_cr.json @@ -20,6 +20,12 @@ "uid": "7aeaad53-4afe-11ea-8611-42010a800022" }, "spec": { + "env": [ + { + "name": "all-services-test", + "value": "some-value" + } + ], "securityContext": { "runAsUser": 1000, "runAsGroup": 1000, @@ -63,7 +69,17 @@ "command": ["sh", "-c", "echo hello"] } } - } + }, + "env": [ + { + "name": "all-services-test", + "value": "some-value-overridden" + }, + { + "name": "appfabric-env-var-test", + "value": "some-value" + } + ] }, "authentication": { "metadata": { diff --git a/controllers/testdata/logs.json b/controllers/testdata/logs.json index 9879fc49..42035839 100644 --- a/controllers/testdata/logs.json +++ b/controllers/testdata/logs.json @@ -63,6 +63,10 @@ "--env=k8s" ], "env": [ + { + "name": "all-services-test", + "value": "some-value" + }, { "name": "JAVA_HEAPMAX", "value": "-Xmx62914560" diff --git a/controllers/testdata/messaging.json b/controllers/testdata/messaging.json index 0dc024e2..85655013 100644 --- a/controllers/testdata/messaging.json +++ b/controllers/testdata/messaging.json @@ -63,6 +63,10 @@ "--env=k8s" ], "env": [ + { + "name": "all-services-test", + "value": "some-value" + }, { "name": "JAVA_HEAPMAX", "value": "-Xmx62914560" diff --git a/controllers/testdata/metadata.json b/controllers/testdata/metadata.json index 6d1d164c..d9a01d59 100644 --- a/controllers/testdata/metadata.json +++ b/controllers/testdata/metadata.json @@ -73,6 +73,10 @@ "--env=k8s" ], "env": [ + { + "name": "all-services-test", + "value": "some-value" + }, { "name": "JAVA_HEAPMAX", "value": "-Xmx62914560" diff --git a/controllers/testdata/metrics.json b/controllers/testdata/metrics.json index 66117f94..a666d111 100644 --- a/controllers/testdata/metrics.json +++ b/controllers/testdata/metrics.json @@ -63,6 +63,10 @@ "--env=k8s" ], "env": [ + { + "name": "all-services-test", + "value": "some-value" + }, { "name": "JAVA_HEAPMAX", "value": "-Xmx62914560" diff --git a/controllers/testdata/preview.json b/controllers/testdata/preview.json index 3883a095..366b3919 100644 --- a/controllers/testdata/preview.json +++ b/controllers/testdata/preview.json @@ -63,6 +63,10 @@ "--env=k8s" ], "env": [ + { + "name": "all-services-test", + "value": "some-value" + }, { "name": "JAVA_HEAPMAX", "value": "-Xmx62914560" diff --git a/controllers/testdata/router.json b/controllers/testdata/router.json index 8b5b290a..e8e9628a 100644 --- a/controllers/testdata/router.json +++ b/controllers/testdata/router.json @@ -72,6 +72,10 @@ "--env=k8s" ], "env": [ + { + "name": "all-services-test", + "value": "some-value" + }, { "name": "JAVA_HEAPMAX", "value": "-Xmx62914560" diff --git a/controllers/testdata/runtime.json b/controllers/testdata/runtime.json index 70cd059a..abcf255a 100644 --- a/controllers/testdata/runtime.json +++ b/controllers/testdata/runtime.json @@ -73,6 +73,10 @@ "--env=k8s" ], "env": [ + { + "name": "all-services-test", + "value": "some-value" + }, { "name": "JAVA_HEAPMAX", "value": "-Xmx62914560" @@ -154,6 +158,10 @@ "--env=k8s" ], "env": [ + { + "name": "all-services-test", + "value": "some-value" + }, { "name": "JAVA_HEAPMAX", "value": "-Xmx125829120" diff --git a/controllers/testdata/supportbundle.json b/controllers/testdata/supportbundle.json index 9bef44b2..fd792549 100644 --- a/controllers/testdata/supportbundle.json +++ b/controllers/testdata/supportbundle.json @@ -208,6 +208,10 @@ "--env=k8s" ], "env":[ + { + "name": "all-services-test", + "value": "some-value" + }, { "name":"JAVA_HEAPMAX", "value":"-Xmx125829120" diff --git a/controllers/testdata/tetheringagent.json b/controllers/testdata/tetheringagent.json index 572fa68b..e2f84769 100644 --- a/controllers/testdata/tetheringagent.json +++ b/controllers/testdata/tetheringagent.json @@ -208,6 +208,10 @@ "--env=k8s" ], "env":[ + { + "name": "all-services-test", + "value": "some-value" + }, { "name":"JAVA_HEAPMAX", "value":"-Xmx125829120" diff --git a/controllers/testdata/userinterface.json b/controllers/testdata/userinterface.json index 87b2da60..b357c09a 100644 --- a/controllers/testdata/userinterface.json +++ b/controllers/testdata/userinterface.json @@ -75,6 +75,10 @@ "bin/node" ], "env": [ + { + "name": "all-services-test", + "value": "some-value" + }, { "name": "JAVA_HEAPMAX", "value": "-Xmx62914560" diff --git a/go.mod b/go.mod index c5f71ff6..1749d84d 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.12 require ( github.com/go-logr/logr v0.1.0 + github.com/google/go-cmp v0.5.7 github.com/nsf/jsondiff v0.0.0-20190712045011-8443391ee9b6 github.com/onsi/ginkgo v1.11.0 github.com/onsi/gomega v1.8.1 diff --git a/go.sum b/go.sum index f812821a..160a0652 100644 --- a/go.sum +++ b/go.sum @@ -141,8 +141,9 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/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= @@ -403,8 +404,9 @@ golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0= gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=