Add validation to TargetPath and ValuesKey

Formalises the API requirements around TargetPath and ValuesKey,
which were the two fields missing validation within ValuesReference.
In both cases the validation was introduced at CRD level, so that
the apiserver will enforce it.

ValuesKey must be a valid Data Key. Therefore the same logic used by
upstream Kubernetes is reused here to ensure a valid key is being used.

For TargetPath a loose regex is being used to largely represent the
expected format. A max length of 250 is now being enforced.

This is a breaking change, as invalid TargetPath and ValuesKey will now
be rejected by the apiserver, instead of being accepted and potentially
failing at reconciliation time.

Signed-off-by: Paulo Gomes <paulo.gomes@weave.works>
This commit is contained in:
Paulo Gomes 2022-08-17 14:30:35 +01:00
parent 2195310e7c
commit 3cd330cc53
No known key found for this signature in database
GPG Key ID: 9995233870E99BEE
7 changed files with 202 additions and 47 deletions

View File

@ -59,12 +59,18 @@ type ValuesReference struct {
// ValuesKey is the data key where the values.yaml or a specific value can be // ValuesKey is the data key where the values.yaml or a specific value can be
// found at. Defaults to 'values.yaml'. // found at. Defaults to 'values.yaml'.
// When set, must be a valid Data Key, consisting of alphanumeric characters,
// '-', '_' or '.'.
// +kubebuilder:validation:MaxLength=253
// +kubebuilder:validation:Pattern=`^[\-._a-zA-Z0-9]+$`
// +optional // +optional
ValuesKey string `json:"valuesKey,omitempty"` ValuesKey string `json:"valuesKey,omitempty"`
// TargetPath is the YAML dot notation path the value should be merged at. When // TargetPath is the YAML dot notation path the value should be merged at. When
// set, the ValuesKey is expected to be a single flat value. Defaults to 'None', // set, the ValuesKey is expected to be a single flat value. Defaults to 'None',
// which results in the values getting merged at the root. // which results in the values getting merged at the root.
// +kubebuilder:validation:MaxLength=250
// +kubebuilder:validation:Pattern=`^([a-zA-Z0-9_\-.\\\/]|\[[0-9]{1,5}\])+$`
// +optional // +optional
TargetPath string `json:"targetPath,omitempty"` TargetPath string `json:"targetPath,omitempty"`

View File

@ -697,10 +697,16 @@ spec:
should be merged at. When set, the ValuesKey is expected to should be merged at. When set, the ValuesKey is expected to
be a single flat value. Defaults to 'None', which results be a single flat value. Defaults to 'None', which results
in the values getting merged at the root. in the values getting merged at the root.
maxLength: 250
pattern: ^([a-zA-Z0-9_\-.\\\/]|\[[0-9]{1,5}\])+$
type: string type: string
valuesKey: valuesKey:
description: ValuesKey is the data key where the values.yaml description: ValuesKey is the data key where the values.yaml
or a specific value can be found at. Defaults to 'values.yaml'. or a specific value can be found at. Defaults to 'values.yaml'.
When set, must be a valid Data Key, consisting of alphanumeric
characters, '-', '_' or '.'.
maxLength: 253
pattern: ^[\-._a-zA-Z0-9]+$
type: string type: string
required: required:
- kind - kind

View File

@ -19,7 +19,9 @@ package controllers
import ( import (
"context" "context"
"reflect" "reflect"
"strings"
"testing" "testing"
"time"
"github.com/go-logr/logr" "github.com/go-logr/logr"
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/chartutil"
@ -27,6 +29,7 @@ import (
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
@ -278,6 +281,171 @@ invalid`,
} }
} }
func TestValuesReferenceValidation(t *testing.T) {
tests := []struct {
name string
references []v2.ValuesReference
wantErr bool
}{
{
name: "valid ValuesKey",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
ValuesKey: "any-key_na.me",
},
},
wantErr: false,
},
{
name: "valid ValuesKey: empty",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
ValuesKey: "",
},
},
wantErr: false,
},
{
name: "valid ValuesKey: long",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
ValuesKey: strings.Repeat("a", 253),
},
},
wantErr: false,
},
{
name: "invalid ValuesKey",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
ValuesKey: "a($&^%b",
},
},
wantErr: true,
},
{
name: "invalid ValuesKey: too long",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
ValuesKey: strings.Repeat("a", 254),
},
},
wantErr: true,
},
{
name: "valid target path: empty",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
TargetPath: "",
},
},
wantErr: false,
},
{
name: "valid target path",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
TargetPath: "list_with.nested-values.and.index[0]",
},
},
wantErr: false,
},
{
name: "valid target path: long",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
TargetPath: strings.Repeat("a", 250),
},
},
wantErr: false,
},
{
name: "invalid target path: too long",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
TargetPath: strings.Repeat("a", 251),
},
},
wantErr: true,
},
{
name: "invalid target path: opened index",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
ValuesKey: "single",
TargetPath: "a[",
},
},
wantErr: true,
},
{
name: "invalid target path: incorrect index syntax",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
ValuesKey: "single",
TargetPath: "a]0[",
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var values *apiextensionsv1.JSON
v, _ := yaml.YAMLToJSON([]byte("values"))
values = &apiextensionsv1.JSON{Raw: v}
hr := v2.HelmRelease{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
},
Spec: v2.HelmReleaseSpec{
Interval: metav1.Duration{Duration: 5 * time.Minute},
Chart: v2.HelmChartTemplate{
Spec: v2.HelmChartTemplateSpec{
SourceRef: v2.CrossNamespaceObjectReference{
Name: "something",
},
},
},
ValuesFrom: tt.references,
Values: values,
},
}
err := k8sClient.Create(context.TODO(), &hr, client.DryRunAll)
if (err != nil) != tt.wantErr {
t.Errorf("composeValues() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
func valuesSecret(name string, data map[string][]byte) *corev1.Secret { func valuesSecret(name string, data map[string][]byte) *corev1.Secret {
return &corev1.Secret{ return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: name}, ObjectMeta: metav1.ObjectMeta{Name: name},

View File

@ -17,67 +17,48 @@ limitations under the License.
package controllers package controllers
import ( import (
"fmt"
"os"
"path/filepath" "path/filepath"
"testing" "testing"
. "github.com/onsi/ginkgo" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
. "github.com/onsi/gomega"
"k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest" "sigs.k8s.io/controller-runtime/pkg/envtest"
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"github.com/fluxcd/helm-controller/api/v2beta1" "github.com/fluxcd/helm-controller/api/v2beta1"
// +kubebuilder:scaffold:imports // +kubebuilder:scaffold:imports
) )
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
var cfg *rest.Config var cfg *rest.Config
var k8sClient client.Client var k8sClient client.Client
var testEnv *envtest.Environment var testEnv *envtest.Environment
func TestAPIs(t *testing.T) { func TestMain(m *testing.M) {
RegisterFailHandler(Fail)
RunSpecsWithDefaultAndCustomReporters(t,
"Controller Suite",
[]Reporter{printer.NewlineReporter{}})
}
var _ = BeforeSuite(func(done Done) {
logf.SetLogger(
zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)),
)
By("bootstrapping test environment")
testEnv = &envtest.Environment{ testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
} }
var err error var err error
cfg, err = testEnv.Start() cfg, err = testEnv.Start()
Expect(err).ToNot(HaveOccurred()) if err != nil {
Expect(cfg).ToNot(BeNil()) panic(fmt.Errorf("failed to start testenv: %v", err))
}
err = v2beta1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
// +kubebuilder:scaffold:scheme
utilruntime.Must(v2beta1.AddToScheme(scheme.Scheme))
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
Expect(err).ToNot(HaveOccurred()) if err != nil {
Expect(k8sClient).ToNot(BeNil()) panic(fmt.Errorf("failed to create k8s client: %v", err))
}
close(done) code := m.Run()
}, 60)
var _ = AfterSuite(func() { err = testEnv.Stop()
By("tearing down the test environment") if err != nil {
err := testEnv.Stop() panic(fmt.Errorf("failed to stop testenv: %v", err))
Expect(err).ToNot(HaveOccurred()) }
})
os.Exit(code)
}

View File

@ -2092,7 +2092,9 @@ string
<td> <td>
<em>(Optional)</em> <em>(Optional)</em>
<p>ValuesKey is the data key where the values.yaml or a specific value can be <p>ValuesKey is the data key where the values.yaml or a specific value can be
found at. Defaults to &lsquo;values.yaml&rsquo;.</p> found at. Defaults to &lsquo;values.yaml&rsquo;.
When set, must be a valid Data Key, consisting of alphanumeric characters,
&lsquo;-&rsquo;, &lsquo;_&rsquo; or &lsquo;.&rsquo;.</p>
</td> </td>
</tr> </tr>
<tr> <tr>

3
go.mod
View File

@ -13,7 +13,6 @@ require (
github.com/fluxcd/source-controller/api v0.25.9 github.com/fluxcd/source-controller/api v0.25.9
github.com/go-logr/logr v1.2.3 github.com/go-logr/logr v1.2.3
github.com/hashicorp/go-retryablehttp v0.7.1 github.com/hashicorp/go-retryablehttp v0.7.1
github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.19.0 github.com/onsi/gomega v1.19.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
helm.sh/helm/v3 v3.9.0 helm.sh/helm/v3 v3.9.0
@ -120,7 +119,6 @@ require (
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
@ -157,7 +155,6 @@ require (
google.golang.org/grpc v1.43.0 // indirect google.golang.org/grpc v1.43.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
k8s.io/apiserver v0.24.1 // indirect k8s.io/apiserver v0.24.1 // indirect

5
go.sum
View File

@ -259,7 +259,6 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU=
github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs=
github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0=
@ -550,7 +549,6 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
@ -559,7 +557,6 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc= github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
@ -933,7 +930,6 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -1034,7 +1030,6 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=