From 808860252694f18ccf63d284a242a1d9c7a4db50 Mon Sep 17 00:00:00 2001 From: akutz Date: Mon, 29 Jul 2019 00:29:53 -0500 Subject: [PATCH 1/7] Support for dedicated cloud provider config This patch introduces support for a dedicated cloud provider configuration. --- ...e_v1alpha1_vsphereclusterproviderspec.yaml | 116 +++++ go.mod | 4 +- go.sum | 9 +- hack/update-generated.sh | 1 + pkg/apis/vsphere/v1alpha1/cloud/const.go | 142 ++++++ pkg/apis/vsphere/v1alpha1/cloud/doc.go | 33 ++ pkg/apis/vsphere/v1alpha1/cloud/encoding.go | 143 ++++++ .../vsphere/v1alpha1/cloud/encoding_test.go | 370 ++++++++++++++ pkg/apis/vsphere/v1alpha1/cloud/types.go | 199 ++++++++ .../v1alpha1/cloud/zz_generated.deepcopy.go | 173 +++++++ .../vsphereclusterproviderspec_types.go | 6 + .../vsphere/v1alpha1/zz_generated.deepcopy.go | 1 + .../golang.org/x/tools/go/internal/cgo/cgo.go | 220 --------- .../x/tools/go/internal/cgo/cgo_pkgconfig.go | 39 -- .../golang.org/x/tools/go/packages/golist.go | 56 +-- .../x/tools/go/packages/golist_fallback.go | 450 ------------------ .../go/packages/golist_fallback_testmain.go | 318 ------------- .../x/tools/go/packages/packages.go | 140 +++++- vendor/golang.org/x/tools/imports/mod.go | 8 +- vendor/gopkg.in/gcfg.v1/LICENSE | 28 ++ vendor/gopkg.in/gcfg.v1/README | 4 + vendor/gopkg.in/gcfg.v1/doc.go | 145 ++++++ vendor/gopkg.in/gcfg.v1/errors.go | 57 +++ vendor/gopkg.in/gcfg.v1/read.go | 257 ++++++++++ vendor/gopkg.in/gcfg.v1/scanner/errors.go | 121 +++++ vendor/gopkg.in/gcfg.v1/scanner/scanner.go | 342 +++++++++++++ vendor/gopkg.in/gcfg.v1/set.go | 329 +++++++++++++ vendor/gopkg.in/gcfg.v1/token/position.go | 435 +++++++++++++++++ vendor/gopkg.in/gcfg.v1/token/serialize.go | 56 +++ vendor/gopkg.in/gcfg.v1/token/token.go | 83 ++++ vendor/gopkg.in/gcfg.v1/types/bool.go | 23 + vendor/gopkg.in/gcfg.v1/types/doc.go | 4 + vendor/gopkg.in/gcfg.v1/types/enum.go | 44 ++ vendor/gopkg.in/gcfg.v1/types/int.go | 86 ++++ vendor/gopkg.in/gcfg.v1/types/scan.go | 23 + vendor/gopkg.in/warnings.v0/LICENSE | 24 + vendor/gopkg.in/warnings.v0/README | 77 +++ vendor/gopkg.in/warnings.v0/warnings.go | 194 ++++++++ vendor/modules.txt | 10 +- 39 files changed, 3682 insertions(+), 1088 deletions(-) create mode 100644 pkg/apis/vsphere/v1alpha1/cloud/const.go create mode 100644 pkg/apis/vsphere/v1alpha1/cloud/doc.go create mode 100644 pkg/apis/vsphere/v1alpha1/cloud/encoding.go create mode 100644 pkg/apis/vsphere/v1alpha1/cloud/encoding_test.go create mode 100644 pkg/apis/vsphere/v1alpha1/cloud/types.go create mode 100644 pkg/apis/vsphere/v1alpha1/cloud/zz_generated.deepcopy.go delete mode 100644 vendor/golang.org/x/tools/go/internal/cgo/cgo.go delete mode 100644 vendor/golang.org/x/tools/go/internal/cgo/cgo_pkgconfig.go delete mode 100644 vendor/golang.org/x/tools/go/packages/golist_fallback.go delete mode 100644 vendor/golang.org/x/tools/go/packages/golist_fallback_testmain.go create mode 100644 vendor/gopkg.in/gcfg.v1/LICENSE create mode 100644 vendor/gopkg.in/gcfg.v1/README create mode 100644 vendor/gopkg.in/gcfg.v1/doc.go create mode 100644 vendor/gopkg.in/gcfg.v1/errors.go create mode 100644 vendor/gopkg.in/gcfg.v1/read.go create mode 100644 vendor/gopkg.in/gcfg.v1/scanner/errors.go create mode 100644 vendor/gopkg.in/gcfg.v1/scanner/scanner.go create mode 100644 vendor/gopkg.in/gcfg.v1/set.go create mode 100644 vendor/gopkg.in/gcfg.v1/token/position.go create mode 100644 vendor/gopkg.in/gcfg.v1/token/serialize.go create mode 100644 vendor/gopkg.in/gcfg.v1/token/token.go create mode 100644 vendor/gopkg.in/gcfg.v1/types/bool.go create mode 100644 vendor/gopkg.in/gcfg.v1/types/doc.go create mode 100644 vendor/gopkg.in/gcfg.v1/types/enum.go create mode 100644 vendor/gopkg.in/gcfg.v1/types/int.go create mode 100644 vendor/gopkg.in/gcfg.v1/types/scan.go create mode 100644 vendor/gopkg.in/warnings.v0/LICENSE create mode 100644 vendor/gopkg.in/warnings.v0/README create mode 100644 vendor/gopkg.in/warnings.v0/warnings.go diff --git a/config/crds/vsphere_v1alpha1_vsphereclusterproviderspec.yaml b/config/crds/vsphere_v1alpha1_vsphereclusterproviderspec.yaml index ea6c245112..60c63af55c 100644 --- a/config/crds/vsphere_v1alpha1_vsphereclusterproviderspec.yaml +++ b/config/crds/vsphere_v1alpha1_vsphereclusterproviderspec.yaml @@ -33,6 +33,122 @@ spec: - cert - key type: object + cloudProviderConfiguration: + description: CloudProviderConfiguration holds the cluster-wide configuration + for the vSphere cloud provider. + properties: + disk: + description: Disk is the vSphere cloud provider's disk configuration. + properties: + scsiControllerType: + description: SCSIControllerType defines SCSI controller to be used. + type: string + type: object + global: + description: Global is the vSphere cloud provider's global configuration. + properties: + apiBindPort: + description: APIBindPort configures the vSphere cloud controller + manager API port. Defaults to 43001. + type: string + apiDisable: + description: APIDisable disables the vSphere cloud controller manager + API. Defaults to true. + type: boolean + caFile: + description: CAFile Specifies the path to a CA certificate in PEM + format. If not configured, the system's CA certificates will be + used. + type: string + datacenters: + description: Datacenters is a CSV string of the datacenters in which + VMs are located. + type: string + insecure: + description: Insecure is a flag that disables TLS peer verification. + type: boolean + password: + description: Password is the password used to access a vSphere endpoint. + type: string + port: + description: Port is the port on which the vSphere endpoint is listening. + Defaults to 443. + type: string + roundTripperCount: + description: RoundTripperCount specifies the SOAP round tripper + count (retries = RoundTripper - 1) + format: int32 + type: integer + secretName: + description: SecretName is the name of the Kubernetes secret in + which the vSphere credentials are located. + type: string + secretNamespace: + description: SecretNamespace is the namespace for SecretName. + type: string + secretsDirectory: + description: 'SecretsDirectory is a directory in which secrets may + be found. This may used in the event that: 1. It is not desirable + to use the K8s API to watch changes to secrets 2. The cloud controller + manager is not running in a K8s environment, such as DC/OS. + For example, the container storage interface (CSI) is container + orcehstrator (CO) agnostic, and should support non-K8s COs. Defaults + to /etc/cloud/credentials.' + type: string + serviceAccount: + description: ServiceAccount is the Kubernetes service account used + to launch the cloud controller manager. Defaults to cloud-controller-manager. + type: string + thumbprint: + description: Thumbprint is the cryptographic thumbprint of the vSphere + endpoint's certificate. + type: string + username: + description: Username is the username used to access a vSphere endpoint. + type: string + type: object + labels: + description: Labels is the vSphere cloud provider's zone and region + configuration. + properties: + region: + description: Region is the region in which VMs are created/located. + type: string + zone: + description: Zone is the zone in which VMs are created/located. + type: string + type: object + network: + description: Network is the vSphere cloud provider's network configuration. + properties: + name: + description: Name is the name of the network to which VMs are connected. + type: string + type: object + virtualCenter: + description: VCenter is a list of vCenter configurations. + type: object + workspace: + description: Workspace is the vSphere cloud provider's workspace configuration. + properties: + datacenter: + description: Datacenter is the datacenter in which VMs are created/located. + type: string + defaultDatastore: + description: Datastore is the datastore in which VMs are created/located. + type: string + folder: + description: Folder is the folder in which VMs are created/located. + type: string + resourcePool: + description: ResourcePool is the resource pool in which VMs are + created/located. + type: string + server: + description: Server is the IP address or FQDN of the vSphere endpoint. + type: string + type: object + type: object clusterConfiguration: description: ClusterConfiguration holds the cluster-wide information used during a kubeadm init call. diff --git a/go.mod b/go.mod index 97ff89c387..8010259f0f 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,9 @@ require ( golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 golang.org/x/oauth2 v0.0.0-20190523182746-aaccbc9213b0 // indirect golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect - golang.org/x/tools v0.0.0-20190312170243-e65039ee4138 + golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 + gopkg.in/gcfg.v1 v1.2.3 + gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.2.2 k8s.io/api v0.0.0-20190222213804-5cb15d344471 k8s.io/apimachinery v0.0.0-20190703205208-4cfb76a8bf76 diff --git a/go.sum b/go.sum index 38064ac345..e36465f1b3 100644 --- a/go.sum +++ b/go.sum @@ -274,6 +274,7 @@ github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTV github.com/gophercloud/gophercloud v0.0.0-20190221164956-3f3cc5a566b2/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 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= @@ -612,8 +613,8 @@ golang.org/x/tools v0.0.0-20190102213336-ca9055ed7d04/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190104182027-498d95493402/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190111214448-fc1d57b08d7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190124215303-cc6a436ffe6b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138 h1:H3uGjxCR/6Ds0Mjgyp7LMK81+LvmbvWWEnJhzk1Pi9E= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 h1:TFlARGu6Czu1z7q93HTxcP1P+/ZFC/IKythI5RzrnRg= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0 h1:K6z2u68e86TPdSdefXdzvXgR1zEMa+459vBSfWYAZkI= @@ -646,6 +647,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= @@ -653,6 +656,8 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/mail.v2 v2.0.0-20180731213649-a0242b2233b4/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= 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/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 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= diff --git a/hack/update-generated.sh b/hack/update-generated.sh index cd41580ab2..385c3323aa 100755 --- a/hack/update-generated.sh +++ b/hack/update-generated.sh @@ -55,6 +55,7 @@ gen-rbac() { } deepcopy-v1alpha1() { + gen-deepcopy ./pkg/apis/vsphere/v1alpha1/cloud gen-deepcopy ./pkg/apis/vsphere/v1alpha1 } diff --git a/pkg/apis/vsphere/v1alpha1/cloud/const.go b/pkg/apis/vsphere/v1alpha1/cloud/const.go new file mode 100644 index 0000000000..e68ed094c9 --- /dev/null +++ b/pkg/apis/vsphere/v1alpha1/cloud/const.go @@ -0,0 +1,142 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cloud + +const configFormat = ` +{{- if IsNotEmpty .Global }} +{{- with .Global }} +[Global] +{{- if .Username }} +user = "{{ .Username }}" +{{- end }} +{{- if .Password }} +password = "{{ .Password }}" +{{- end }} +{{- if .Port }} +port = "{{ .Port }}" +{{- end }} +{{- if .SecretName }} +secret-name = "{{ .SecretName }}" +{{- end }} +{{- if .SecretNamespace }} +secret-namespace = "{{ .SecretNamespace }}" +{{- end }} +{{- if .Insecure }} +insecure-flag = "{{ .Insecure }}" +{{- end }} +{{- if .Datacenters }} +datacenters = "{{ .Datacenters }}" +{{- end }} +{{- if .CAFile }} +ca-file = "{{ .CAFile }}" +{{- end }} +{{- if .Thumbprint }} +thumbprint = "{{ .Thumbprint }}" +{{- end }} +{{- if .RoundTripperCount }} +soap-roundtripper-count = {{ .RoundTripperCount }} +{{- end }} +{{- if .ServiceAccount }} +service-account = {{ .ServiceAccount }} +{{- end }} +{{- if .SecretsDirectory }} +secrets-directory = {{ .SecretsDirectory }} +{{- end }} +{{- if .APIDisable }} +api-disable = {{ .APIDisable }} +{{- end }} +{{- if .APIBindPort }} +api-binding = "{{ .APIBindPort }}" +{{- end }} +{{- end }} {{/* with .Global */}} +{{- end }} {{/* if IsNotEmpty .Global */}} + +{{- range $Server, $VCenter := .VCenter }} +[VirtualCenter "{{ $Server }}"] +{{- with $VCenter }} +{{- if .Username }} +user = "{{ .Username }}" +{{- end }} +{{- if .Password }} +password = "{{ .Password }}" +{{- end }} +{{- if .Port }} +port = "{{ .Port }}" +{{- end }} +{{- if .Datacenters }} +datacenters = "{{ .Datacenters }}" +{{- end }} +{{- if .RoundTripperCount }} +soap-roundtripper-count = {{ .RoundTripperCount }} +{{- end }} +{{- if .Thumbprint }} +thumbprint = "{{ .Thumbprint }}" +{{- end }} +{{- end }} {{/* with $VCenter */}} +{{- end }} {{/* range $Server, $VCenter := .VCenter */}} + +{{- if IsNotEmpty .Workspace }} +{{- with .Workspace }} +[Workspace] +{{- if .Server }} +server = "{{ .Server }}" +{{- end }} +{{- if .Datacenter }} +datacenter = "{{ .Datacenter }}" +{{- end }} +{{- if .Folder }} +folder = "{{ .Folder }}" +{{- end }} +{{- if .Datastore }} +default-datastore = "{{ .Datastore }}" +{{- end }} +{{- if .ResourcePool }} +resourcepool-path = "{{ .ResourcePool }}" +{{- end }} +{{- end }} {{/* with .Workspace */}} +{{- end }} {{/* if IsNotEmpty .Workspace */}} + +{{- if IsNotEmpty .Disk }} +{{- with .Disk }} +[Disk] +{{- if .SCSIControllerType }} +scsicontrollertype = "{{ .SCSIControllerType }}" +{{- end }} +{{- end }} {{/* with .Disk */}} +{{- end }} {{/* if IsNotEmpty .Disk */}} + +{{- if IsNotEmpty .Network }} +{{- with .Network }} +[Network] +{{- if .Name }} +public-network = "{{ .Name }}" +{{- end }} +{{- end }} {{/* with .Network */}} +{{- end }} {{/* if IsNotEmpty .Network */}} + +{{- if IsNotEmpty .Labels }} +{{- with .Labels }} +[Labels] +{{- if .Zone }} +zone = "{{ .Zone }}" +{{- end }} +{{- if .Region }} +region = "{{ .Region }}" +{{- end }} +{{- end }} {{/* with .Labels */}} +{{- end }} {{/* if IsNotEmpty .Labels */}} +` diff --git a/pkg/apis/vsphere/v1alpha1/cloud/doc.go b/pkg/apis/vsphere/v1alpha1/cloud/doc.go new file mode 100644 index 0000000000..8086ea1487 --- /dev/null +++ b/pkg/apis/vsphere/v1alpha1/cloud/doc.go @@ -0,0 +1,33 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package cloud contains API types for the vSphere cloud provider. +// +// The configuration may be unmarshalled from an INI-style configuration using +// the "gopkg.in/gcfg.v1" package. +// +// The configuration may be marshalled to an INI-style configuraton using a Go +// template. +// +// The "gopkg.in/go-ini/ini.v1" package was investigated, but it does not +// support reflecting a struct with a field of type "map[string]TYPE" to INI. +// However, because the "gopkg.in/go-ini/ini.v1" is such an elegant solution +// for marshalling the cloud provider configuration to INI, the fields in this +// package retain tags used by the "gopkg.in/go-ini/ini.v1" package in case it +// ever introduces support for reflecting maps to INI. +// +// +k8s:deepcopy-gen=package +package cloud diff --git a/pkg/apis/vsphere/v1alpha1/cloud/encoding.go b/pkg/apis/vsphere/v1alpha1/cloud/encoding.go new file mode 100644 index 0000000000..bc5c5b9be6 --- /dev/null +++ b/pkg/apis/vsphere/v1alpha1/cloud/encoding.go @@ -0,0 +1,143 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cloud + +import ( + "bytes" + "reflect" + "text/template" + + "github.com/pkg/errors" + gcfg "gopkg.in/gcfg.v1" +) + +// MarshalINI marshals the cloud provider configuration to INI-style +// configuration data. +func (c *Config) MarshalINI() ([]byte, error) { + t, err := template.New("t").Funcs(template.FuncMap{ + "IsNotEmpty": IsNotEmpty, + }).Parse(configFormat) + if err != nil { + return nil, errors.Wrap(err, "failed to parse config template") + } + buf := &bytes.Buffer{} + if err := t.Execute(buf, c); err != nil { + return nil, errors.Wrap(err, "failed to execute config template") + } + return buf.Bytes(), nil +} + +// UnmarshalOptions defines the options used to influence how INI data is +// unmarshalled. +type UnmarshalOptions struct { + // WarnAsFatal indicates that warnings that occur when unmarshalling INI + // data should be treated as fatal errors. + WarnAsFatal bool +} + +// UnmarshalOptionFunc is used to set unmarshal options. +type UnmarshalOptionFunc func(*UnmarshalOptions) + +// WarnAsFatal sets the option to treat warnings as fatal errors when +// unmarshalling INI data. +func WarnAsFatal(opts *UnmarshalOptions) { + opts.WarnAsFatal = true +} + +// UnmarshalINI unmarshals the cloud provider configuration from INI-style +// configuration data. +func (c *Config) UnmarshalINI(data []byte, optFuncs ...UnmarshalOptionFunc) error { + opts := &UnmarshalOptions{} + for _, setOpts := range optFuncs { + setOpts(opts) + } + if err := gcfg.ReadStringInto(c, string(data)); err != nil { + if opts.WarnAsFatal { + return err + } + if err := gcfg.FatalOnly(err); err != nil { + return err + } + } + return nil +} + +// IsEmpty returns true if an object is its empty value or if a struct, all of +// its fields are their empty values. +func IsEmpty(obj interface{}) bool { + return isEmpty(reflect.ValueOf(obj)) +} + +// IsNotEmpty returns true when IsEmpty returns false. +func IsNotEmpty(obj interface{}) bool { + return !IsEmpty(obj) +} + +// isEmpty returns true if an object's fields are all set to their empty values. +func isEmpty(val reflect.Value) bool { + switch val.Kind() { + + case reflect.Interface, reflect.Ptr: + return val.IsNil() || isEmpty(val.Elem()) + + case reflect.Struct: + structIsEmpty := true + for fieldIndex := 0; fieldIndex < val.NumField(); fieldIndex++ { + if structIsEmpty = isEmpty(val.Field(fieldIndex)); !structIsEmpty { + break + } + } + return structIsEmpty + + case reflect.Array, reflect.String: + return val.Len() == 0 + + case reflect.Bool: + return !val.Bool() + + case reflect.Map, reflect.Slice: + return val.IsNil() || val.Len() == 0 + + case reflect.Float32, reflect.Float64: + return val.Float() == 0 + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return val.Int() == 0 + + default: + panic(errors.Errorf("invalid kind: %s", val.Kind())) + } +} + +/* +Please see the package documentation for why the MarshalINI function that +uses the "gopkg.in/go-ini/ini.v1" package is commented out. + +// MarshalINI marshals the cloud provider configuration as an INI-style +// configuration. +func (c *Config) MarshalINI() ([]byte, error) { + cfg := ini.Empty() + if err := ini.ReflectFrom(cfg, c); err != nil { + return nil, errors.Wrap(err, "failed to marshal cloud provider config to ini") + } + buf := &bytes.Buffer{} + if _, err := cfg.WriteTo(buf); err != nil { + return nil, err + } + return buf.Bytes(), nil +} +*/ diff --git a/pkg/apis/vsphere/v1alpha1/cloud/encoding_test.go b/pkg/apis/vsphere/v1alpha1/cloud/encoding_test.go new file mode 100644 index 0000000000..1385c388a2 --- /dev/null +++ b/pkg/apis/vsphere/v1alpha1/cloud/encoding_test.go @@ -0,0 +1,370 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cloud_test + +import ( + "testing" + + "github.com/onsi/gomega" + "github.com/pkg/errors" + + "sigs.k8s.io/cluster-api-provider-vsphere/pkg/apis/vsphere/v1alpha1/cloud" +) + +var unmarshalWarnAsFatal = []cloud.UnmarshalOptionFunc{cloud.WarnAsFatal} + +func errDeprecated(section, key string) error { + return errors.Errorf("warning:\ncan't store data at section \"%s\", variable \"%s\"\n", section, key) +} + +type codecTestCase struct { + testName string + iniString string + configObj cloud.Config + expectedError error + unmarshalOptions []cloud.UnmarshalOptionFunc +} + +var twoWayCodecTestCases = []codecTestCase{ + { + testName: "Username and password in global section", + iniString: ` + [Global] + user = user + password = password + datacenters = us-west + + [VirtualCenter "0.0.0.0"] + + [Workspace] + server = 0.0.0.0 + datacenter = us-west + folder = kubernetes + `, + configObj: cloud.Config{ + Global: cloud.GlobalConfig{ + Username: "user", + Password: "password", + Datacenters: "us-west", + }, + VCenter: map[string]*cloud.VCenterConfig{ + "0.0.0.0": {}, + }, + Workspace: cloud.WorkspaceConfig{ + Server: "0.0.0.0", + Datacenter: "us-west", + Folder: "kubernetes", + }, + }, + }, + { + testName: "Username and password in vCenter section", + iniString: ` + [Global] + port = 443 + insecure-flag = true + datacenters = us-west + + [VirtualCenter "0.0.0.0"] + user = user + password = password + + [Workspace] + server = 0.0.0.0 + datacenter = us-west + folder = kubernetes + `, + configObj: cloud.Config{ + Global: cloud.GlobalConfig{ + Port: "443", + Insecure: true, + Datacenters: "us-west", + }, + VCenter: map[string]*cloud.VCenterConfig{ + "0.0.0.0": { + Username: "user", + Password: "password", + }, + }, + Workspace: cloud.WorkspaceConfig{ + Server: "0.0.0.0", + Datacenter: "us-west", + Folder: "kubernetes", + }, + }, + }, + { + testName: "SecretName and SecretNamespace", + iniString: ` + [Global] + secret-name = "vccreds" + secret-namespace = "kube-system" + datacenters = us-west + + [VirtualCenter "0.0.0.0"] + + [Workspace] + server = 0.0.0.0 + datacenter = us-west + folder = kubernetes + `, + configObj: cloud.Config{ + Global: cloud.GlobalConfig{ + SecretName: "vccreds", + SecretNamespace: "kube-system", + Datacenters: "us-west", + }, + VCenter: map[string]*cloud.VCenterConfig{ + "0.0.0.0": {}, + }, + Workspace: cloud.WorkspaceConfig{ + Server: "0.0.0.0", + Datacenter: "us-west", + Folder: "kubernetes", + }, + }, + }, + { + testName: "SecretName and SecretNamespace with Username missing", + iniString: ` + [Global] + port = 443 + insecure-flag = true + datacenters = us-west + secret-name = "vccreds" + secret-namespace = "kube-system" + + [VirtualCenter "0.0.0.0"] + password = password + + [Workspace] + server = 0.0.0.0 + datacenter = us-west + folder = kubernetes + `, + configObj: cloud.Config{ + Global: cloud.GlobalConfig{ + Port: "443", + Insecure: true, + SecretName: "vccreds", + SecretNamespace: "kube-system", + Datacenters: "us-west", + }, + VCenter: map[string]*cloud.VCenterConfig{ + "0.0.0.0": { + Password: "password", + }, + }, + Workspace: cloud.WorkspaceConfig{ + Server: "0.0.0.0", + Datacenter: "us-west", + Folder: "kubernetes", + }, + }, + }, + { + testName: "Multiple virtual centers with different thumbprints", + iniString: ` + [Global] + user = user + password = password + datacenters = us-west + + [VirtualCenter "0.0.0.0"] + thumbprint = thumbprint:0 + + [VirtualCenter "no_thumbprint"] + + [VirtualCenter "1.1.1.1"] + thumbprint = thumbprint:1 + + [Workspace] + server = 0.0.0.0 + datacenter = us-west + folder = kubernetes + `, + configObj: cloud.Config{ + Global: cloud.GlobalConfig{ + Username: "user", + Password: "password", + Datacenters: "us-west", + }, + VCenter: map[string]*cloud.VCenterConfig{ + "0.0.0.0": { + Thumbprint: "thumbprint:0", + }, + "no_thumbprint": {}, + "1.1.1.1": { + Thumbprint: "thumbprint:1", + }, + }, + Workspace: cloud.WorkspaceConfig{ + Server: "0.0.0.0", + Datacenter: "us-west", + Folder: "kubernetes", + }, + }, + }, + { + testName: "Multiple vCenters using global CA cert", + iniString: ` + [Global] + datacenters = "us-west" + secret-name = "vccreds" + secret-namespace = "kube-system" + ca-file = /some/path/to/my/trusted/ca.pem + + [VirtualCenter "0.0.0.0"] + [VirtualCenter "1.1.1.1"] + + [Workspace] + server = 0.0.0.0 + datacenter = us-west + folder = kubernetes + `, + configObj: cloud.Config{ + Global: cloud.GlobalConfig{ + Datacenters: "us-west", + SecretName: "vccreds", + SecretNamespace: "kube-system", + CAFile: "/some/path/to/my/trusted/ca.pem", + }, + VCenter: map[string]*cloud.VCenterConfig{ + "0.0.0.0": {}, + "1.1.1.1": {}, + }, + Workspace: cloud.WorkspaceConfig{ + Server: "0.0.0.0", + Datacenter: "us-west", + Folder: "kubernetes", + }, + }, + }, +} + +func TestMarshalINI(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + for _, tc := range twoWayCodecTestCases { + t.Run(tc.testName, func(t *testing.T) { + buf, err := tc.configObj.MarshalINI() + if err != nil { + g.Expect(err.Error()).Should( + gomega.Equal(tc.expectedError.Error()), + "unexpected error when marshalling data") + } + + var actualConfig cloud.Config + if err := actualConfig.UnmarshalINI( + buf, tc.unmarshalOptions...); err != nil { + g.Expect(err.Error()).Should( + gomega.Equal(tc.expectedError.Error()), + "unexpected error when unmarshalling data") + } + + g.Expect(actualConfig).Should( + gomega.Equal(tc.configObj), + "actual config does not match expected config") + }) + } +} + +func TestUnmarshalINI(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + deprecatedTestCases := []codecTestCase{ + { + testName: "Global server is deprecated", + iniString: ` + [Global] + server = deprecated + `, + expectedError: errDeprecated("Global", "server"), + unmarshalOptions: unmarshalWarnAsFatal, + }, + { + testName: "Global datacenter is deprecated", + iniString: ` + [Global] + datacenter = deprecated + `, + expectedError: errDeprecated("Global", "datacenter"), + unmarshalOptions: unmarshalWarnAsFatal, + }, + { + + testName: "Global datastore is deprecated", + iniString: ` + [Global] + datastore = deprecated + `, + expectedError: errDeprecated("Global", "datastore"), + unmarshalOptions: unmarshalWarnAsFatal, + }, + { + testName: "Global working-dir is deprecated", + iniString: ` + [Global] + working-dir = deprecated + `, + expectedError: errDeprecated("Global", "working-dir"), + unmarshalOptions: unmarshalWarnAsFatal, + }, + { + testName: "Global vm-name is deprecated", + iniString: ` + [Global] + vm-name = deprecated + `, + expectedError: errDeprecated("Global", "vm-name"), + unmarshalOptions: unmarshalWarnAsFatal, + }, + { + testName: "Global vm-uuid is deprecated", + iniString: ` + [Global] + vm-uuid = deprecated + `, + expectedError: errDeprecated("Global", "vm-uuid"), + unmarshalOptions: unmarshalWarnAsFatal, + }, + } + + testCases := append( + twoWayCodecTestCases, + deprecatedTestCases..., + ) + + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + var actualConfig cloud.Config + + if err := actualConfig.UnmarshalINI( + []byte(tc.iniString), + tc.unmarshalOptions...); err != nil { + + g.Expect(err.Error()).Should( + gomega.Equal(tc.expectedError.Error()), + "unexpected error when unmarshalling data") + } + + g.Expect(actualConfig).Should( + gomega.Equal(tc.configObj), + "actual config does not match expected config") + }) + } +} diff --git a/pkg/apis/vsphere/v1alpha1/cloud/types.go b/pkg/apis/vsphere/v1alpha1/cloud/types.go new file mode 100644 index 0000000000..49003933e5 --- /dev/null +++ b/pkg/apis/vsphere/v1alpha1/cloud/types.go @@ -0,0 +1,199 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package cloud contains API types for the vSphere cloud provider. +package cloud + +// Config is the vSphere cloud provider's configuration. +type Config struct { + // Global is the vSphere cloud provider's global configuration. + // +optional + Global GlobalConfig `gcfg:"Global" ini:"Global" json:"global,omitempty"` + + // VCenter is a list of vCenter configurations. + // +optional + VCenter map[string]*VCenterConfig `gcfg:"VirtualCenter" ini:"VirtualCenter" json:"virtualCenter,omitempty"` + + // Network is the vSphere cloud provider's network configuration. + // +optional + Network NetworConfig `gcfg:"Network" ini:"Network" json:"network,omitempty"` + + // Disk is the vSphere cloud provider's disk configuration. + // +optional + Disk DiskConfig `gcfg:"Disk" ini:"Disk" json:"disk,omitempty"` + + // Workspace is the vSphere cloud provider's workspace configuration. + // +optional + Workspace WorkspaceConfig `gcfg:"Workspace" ini:"Workspace" json:"workspace,omitempty"` + + // Labels is the vSphere cloud provider's zone and region configuration. + // +optional + Labels LabelConfig `gcfg:"Labels" ini:"Labels" json:"labels,omitempty"` +} + +// GlobalConfig is the vSphere cloud provider's global configuration. +type GlobalConfig struct { + // Username is the username used to access a vSphere endpoint. + // +optional + Username string `gcfg:"user" ini:"user" json:"username,omitempty"` + + // Password is the password used to access a vSphere endpoint. + // +optional + Password string `gcfg:"password" ini:"password" json:"password,omitempty"` + + // SecretName is the name of the Kubernetes secret in which the vSphere + // credentials are located. + // +optional + SecretName string `gcfg:"secret-name" ini:"secret-name" json:"secretName,omitempty"` + + // SecretNamespace is the namespace for SecretName. + // +optional + SecretNamespace string `gcfg:"secret-namespace" ini:"secret-namespace" json:"secretNamespace,omitempty"` + + // Port is the port on which the vSphere endpoint is listening. + // Defaults to 443. + // +optional + Port string `gcfg:"port" ini:"port" json:"port,omitempty"` + + // Insecure is a flag that disables TLS peer verification. + // +optional + Insecure bool `gcfg:"insecure-flag" ini:"insecure-flag" json:"insecure,omitempty"` + + // CAFile Specifies the path to a CA certificate in PEM format. + // If not configured, the system's CA certificates will be used. + // +optional + CAFile string `gcfg:"ca-file" ini:"ca-file" json:"caFile,omitempty"` + + // Thumbprint is the cryptographic thumbprint of the vSphere endpoint's + // certificate. + // +optional + Thumbprint string `gcfg:"thumbprint" ini:"thumbprint" json:"thumbprint,omitempty"` + + // Datacenters is a CSV string of the datacenters in which VMs are located. + // +optional + Datacenters string `gcfg:"datacenters" ini:"datacenters" json:"datacenters,omitempty"` + + // RoundTripperCount specifies the SOAP round tripper count + // (retries = RoundTripper - 1) + // +optional + RoundTripperCount int32 `gcfg:"soap-roundtrip-count" ini:"soap-roundtrip-count" json:"roundTripperCount,omitempty"` + + // ServiceAccount is the Kubernetes service account used to launch the cloud + // controller manager. + // Defaults to cloud-controller-manager. + // +optional + ServiceAccount string `gcfg:"service-account" ini:"service-account" json:"serviceAccount,omitempty"` + + // SecretsDirectory is a directory in which secrets may be found. This + // may used in the event that: + // 1. It is not desirable to use the K8s API to watch changes to secrets + // 2. The cloud controller manager is not running in a K8s environment, + // such as DC/OS. For example, the container storage interface (CSI) is + // container orcehstrator (CO) agnostic, and should support non-K8s COs. + // Defaults to /etc/cloud/credentials. + // +optional + SecretsDirectory string `gcfg:"secrets-directory" ini:"secrets-directory" json:"secretsDirectory,omitempty"` + + // APIDisable disables the vSphere cloud controller manager API. + // Defaults to true. + // +optional + APIDisable *bool `gcfg:"api-disable" ini:"api-disable" json:"apiDisable,omitempty"` + + // APIBindPort configures the vSphere cloud controller manager API port. + // Defaults to 43001. + // +optional + APIBindPort string `gcfg:"api-binding" ini:"api-binding" json:"apiBindPort,omitempty"` +} + +// VCenterConfig is a vSphere cloud provider's vCenter configuration. +type VCenterConfig struct { + // Username is the username used to access a vSphere endpoint. + // +optional + Username string `gcfg:"user" ini:"user" json:"username,omitempty"` + + // Password is the password used to access a vSphere endpoint. + // +optional + Password string `gcfg:"password" ini:"password" json:"password,omitempty"` + + // Port is the port on which the vSphere endpoint is listening. + // Defaults to 443. + // +optional + Port string `gcfg:"port" ini:"port" json:"port,omitempty"` + + // Datacenters is a CSV string of the datacenters in which VMs are located. + // +optional + Datacenters string `gcfg:"datacenters" ini:"datacenters" json:"datacenters,omitempty"` + + // RoundTripperCount specifies the SOAP round tripper count + // (retries = RoundTripper - 1) + // +optional + RoundTripperCount int32 `gcfg:"soap-roundtrip-count" ini:"soap-roundtrip-count" json:"roundTripperCount,omitempty"` + + // Thumbprint is the cryptographic thumbprint of the vSphere endpoint's + // certificate. + // +optional + Thumbprint string `gcfg:"thumbprint" ini:"thumbprint" json:"thumbprint,omitempty"` +} + +// NetworConfig is the network configuration for the vSphere cloud provider. +type NetworConfig struct { + // Name is the name of the network to which VMs are connected. + // +optional + Name string `gcfg:"public-network" ini:"public-network" json:"name,omitempty"` +} + +// DiskConfig defines the disk configuration for the vSphere cloud provider. +type DiskConfig struct { + // SCSIControllerType defines SCSI controller to be used. + // +optional + SCSIControllerType string `gcfg:"scsicontrollertype" ini:"scsicontrollertype" json:"scsiControllerType,omitempty"` +} + +// WorkspaceConfig defines a workspace configuration for the vSphere cloud +// provider. +type WorkspaceConfig struct { + // Server is the IP address or FQDN of the vSphere endpoint. + // +optional + Server string `gcfg:"server" ini:"server" json:"server,omitempty"` + + // Datacenter is the datacenter in which VMs are created/located. + // +optional + Datacenter string `gcfg:"datacenter" ini:"datacenter" json:"datacenter,omitempty"` + + // Folder is the folder in which VMs are created/located. + // +optional + Folder string `gcfg:"folder" ini:"folder" json:"folder,omitempty"` + + // Datastore is the datastore in which VMs are created/located. + // +optional + Datastore string `gcfg:"default-datastore" ini:"default-datastore" json:"defaultDatastore,omitempty"` + + // ResourcePool is the resource pool in which VMs are created/located. + // +optional + ResourcePool string `gcfg:"resourcepool-path" ini:"resourcepool-path" json:"resourcePool,omitempty"` +} + +// LabelConfig defines the categories and tags which correspond to built-in +// node labels, zone and region. +type LabelConfig struct { + // Zone is the zone in which VMs are created/located. + // +optional + Zone string `gcfg:"zone" ini:"zone" json:"zone,omitempty"` + + // Region is the region in which VMs are created/located. + // +optional + Region string `gcfg:"region" ini:"region" json:"region,omitempty"` +} diff --git a/pkg/apis/vsphere/v1alpha1/cloud/zz_generated.deepcopy.go b/pkg/apis/vsphere/v1alpha1/cloud/zz_generated.deepcopy.go new file mode 100644 index 0000000000..5de60cc39d --- /dev/null +++ b/pkg/apis/vsphere/v1alpha1/cloud/zz_generated.deepcopy.go @@ -0,0 +1,173 @@ +// +build !ignore_autogenerated + +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by main. DO NOT EDIT. + +package cloud + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Config) DeepCopyInto(out *Config) { + *out = *in + in.Global.DeepCopyInto(&out.Global) + if in.VCenter != nil { + in, out := &in.VCenter, &out.VCenter + *out = make(map[string]*VCenterConfig, len(*in)) + for key, val := range *in { + var outVal *VCenterConfig + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = new(VCenterConfig) + **out = **in + } + (*out)[key] = outVal + } + } + out.Network = in.Network + out.Disk = in.Disk + out.Workspace = in.Workspace + out.Labels = in.Labels + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Config. +func (in *Config) DeepCopy() *Config { + if in == nil { + return nil + } + out := new(Config) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DiskConfig) DeepCopyInto(out *DiskConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DiskConfig. +func (in *DiskConfig) DeepCopy() *DiskConfig { + if in == nil { + return nil + } + out := new(DiskConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GlobalConfig) DeepCopyInto(out *GlobalConfig) { + *out = *in + if in.APIDisable != nil { + in, out := &in.APIDisable, &out.APIDisable + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalConfig. +func (in *GlobalConfig) DeepCopy() *GlobalConfig { + if in == nil { + return nil + } + out := new(GlobalConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LabelConfig) DeepCopyInto(out *LabelConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LabelConfig. +func (in *LabelConfig) DeepCopy() *LabelConfig { + if in == nil { + return nil + } + out := new(LabelConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworConfig) DeepCopyInto(out *NetworConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworConfig. +func (in *NetworConfig) DeepCopy() *NetworConfig { + if in == nil { + return nil + } + out := new(NetworConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UnmarshalOptions) DeepCopyInto(out *UnmarshalOptions) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UnmarshalOptions. +func (in *UnmarshalOptions) DeepCopy() *UnmarshalOptions { + if in == nil { + return nil + } + out := new(UnmarshalOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VCenterConfig) DeepCopyInto(out *VCenterConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VCenterConfig. +func (in *VCenterConfig) DeepCopy() *VCenterConfig { + if in == nil { + return nil + } + out := new(VCenterConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkspaceConfig) DeepCopyInto(out *WorkspaceConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceConfig. +func (in *WorkspaceConfig) DeepCopy() *WorkspaceConfig { + if in == nil { + return nil + } + out := new(WorkspaceConfig) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/vsphere/v1alpha1/vsphereclusterproviderspec_types.go b/pkg/apis/vsphere/v1alpha1/vsphereclusterproviderspec_types.go index 1af4744030..edb82fa697 100644 --- a/pkg/apis/vsphere/v1alpha1/vsphereclusterproviderspec_types.go +++ b/pkg/apis/vsphere/v1alpha1/vsphereclusterproviderspec_types.go @@ -19,6 +19,8 @@ package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kubeadmv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1" + + "sigs.k8s.io/cluster-api-provider-vsphere/pkg/apis/vsphere/v1alpha1/cloud" ) // +genclient @@ -77,6 +79,10 @@ type VsphereClusterProviderSpec struct { // ClusterConfiguration holds the cluster-wide information used during a // kubeadm init call. ClusterConfiguration kubeadmv1beta1.ClusterConfiguration `json:"clusterConfiguration,omitempty"` + + // CloudProviderConfiguration holds the cluster-wide configuration for the + // vSphere cloud provider. + CloudProviderConfiguration cloud.Config `json:"cloudProviderConfiguration,omitempty"` } // KeyPair is how operators can supply custom keypairs for kubeadm to use. diff --git a/pkg/apis/vsphere/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/vsphere/v1alpha1/zz_generated.deepcopy.go index 774cb8f179..88ce3de42d 100644 --- a/pkg/apis/vsphere/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/vsphere/v1alpha1/zz_generated.deepcopy.go @@ -193,6 +193,7 @@ func (in *VsphereClusterProviderSpec) DeepCopyInto(out *VsphereClusterProviderSp in.FrontProxyCAKeyPair.DeepCopyInto(&out.FrontProxyCAKeyPair) in.SAKeyPair.DeepCopyInto(&out.SAKeyPair) in.ClusterConfiguration.DeepCopyInto(&out.ClusterConfiguration) + in.CloudProviderConfiguration.DeepCopyInto(&out.CloudProviderConfiguration) return } diff --git a/vendor/golang.org/x/tools/go/internal/cgo/cgo.go b/vendor/golang.org/x/tools/go/internal/cgo/cgo.go deleted file mode 100644 index 0f652ea6fb..0000000000 --- a/vendor/golang.org/x/tools/go/internal/cgo/cgo.go +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cgo - -// This file handles cgo preprocessing of files containing `import "C"`. -// -// DESIGN -// -// The approach taken is to run the cgo processor on the package's -// CgoFiles and parse the output, faking the filenames of the -// resulting ASTs so that the synthetic file containing the C types is -// called "C" (e.g. "~/go/src/net/C") and the preprocessed files -// have their original names (e.g. "~/go/src/net/cgo_unix.go"), -// not the names of the actual temporary files. -// -// The advantage of this approach is its fidelity to 'go build'. The -// downside is that the token.Position.Offset for each AST node is -// incorrect, being an offset within the temporary file. Line numbers -// should still be correct because of the //line comments. -// -// The logic of this file is mostly plundered from the 'go build' -// tool, which also invokes the cgo preprocessor. -// -// -// REJECTED ALTERNATIVE -// -// An alternative approach that we explored is to extend go/types' -// Importer mechanism to provide the identity of the importing package -// so that each time `import "C"` appears it resolves to a different -// synthetic package containing just the objects needed in that case. -// The loader would invoke cgo but parse only the cgo_types.go file -// defining the package-level objects, discarding the other files -// resulting from preprocessing. -// -// The benefit of this approach would have been that source-level -// syntax information would correspond exactly to the original cgo -// file, with no preprocessing involved, making source tools like -// godoc, guru, and eg happy. However, the approach was rejected -// due to the additional complexity it would impose on go/types. (It -// made for a beautiful demo, though.) -// -// cgo files, despite their *.go extension, are not legal Go source -// files per the specification since they may refer to unexported -// members of package "C" such as C.int. Also, a function such as -// C.getpwent has in effect two types, one matching its C type and one -// which additionally returns (errno C.int). The cgo preprocessor -// uses name mangling to distinguish these two functions in the -// processed code, but go/types would need to duplicate this logic in -// its handling of function calls, analogous to the treatment of map -// lookups in which y=m[k] and y,ok=m[k] are both legal. - -import ( - "fmt" - "go/ast" - "go/build" - "go/parser" - "go/token" - "io/ioutil" - "log" - "os" - "os/exec" - "path/filepath" - "regexp" - "strings" -) - -// ProcessFiles invokes the cgo preprocessor on bp.CgoFiles, parses -// the output and returns the resulting ASTs. -// -func ProcessFiles(bp *build.Package, fset *token.FileSet, DisplayPath func(path string) string, mode parser.Mode) ([]*ast.File, error) { - tmpdir, err := ioutil.TempDir("", strings.Replace(bp.ImportPath, "/", "_", -1)+"_C") - if err != nil { - return nil, err - } - defer os.RemoveAll(tmpdir) - - pkgdir := bp.Dir - if DisplayPath != nil { - pkgdir = DisplayPath(pkgdir) - } - - cgoFiles, cgoDisplayFiles, err := Run(bp, pkgdir, tmpdir, false) - if err != nil { - return nil, err - } - var files []*ast.File - for i := range cgoFiles { - rd, err := os.Open(cgoFiles[i]) - if err != nil { - return nil, err - } - display := filepath.Join(bp.Dir, cgoDisplayFiles[i]) - f, err := parser.ParseFile(fset, display, rd, mode) - rd.Close() - if err != nil { - return nil, err - } - files = append(files, f) - } - return files, nil -} - -var cgoRe = regexp.MustCompile(`[/\\:]`) - -// Run invokes the cgo preprocessor on bp.CgoFiles and returns two -// lists of files: the resulting processed files (in temporary -// directory tmpdir) and the corresponding names of the unprocessed files. -// -// Run is adapted from (*builder).cgo in -// $GOROOT/src/cmd/go/build.go, but these features are unsupported: -// Objective C, CGOPKGPATH, CGO_FLAGS. -// -// If useabs is set to true, absolute paths of the bp.CgoFiles will be passed in -// to the cgo preprocessor. This in turn will set the // line comments -// referring to those files to use absolute paths. This is needed for -// go/packages using the legacy go list support so it is able to find -// the original files. -func Run(bp *build.Package, pkgdir, tmpdir string, useabs bool) (files, displayFiles []string, err error) { - cgoCPPFLAGS, _, _, _ := cflags(bp, true) - _, cgoexeCFLAGS, _, _ := cflags(bp, false) - - if len(bp.CgoPkgConfig) > 0 { - pcCFLAGS, err := pkgConfigFlags(bp) - if err != nil { - return nil, nil, err - } - cgoCPPFLAGS = append(cgoCPPFLAGS, pcCFLAGS...) - } - - // Allows including _cgo_export.h from .[ch] files in the package. - cgoCPPFLAGS = append(cgoCPPFLAGS, "-I", tmpdir) - - // _cgo_gotypes.go (displayed "C") contains the type definitions. - files = append(files, filepath.Join(tmpdir, "_cgo_gotypes.go")) - displayFiles = append(displayFiles, "C") - for _, fn := range bp.CgoFiles { - // "foo.cgo1.go" (displayed "foo.go") is the processed Go source. - f := cgoRe.ReplaceAllString(fn[:len(fn)-len("go")], "_") - files = append(files, filepath.Join(tmpdir, f+"cgo1.go")) - displayFiles = append(displayFiles, fn) - } - - var cgoflags []string - if bp.Goroot && bp.ImportPath == "runtime/cgo" { - cgoflags = append(cgoflags, "-import_runtime_cgo=false") - } - if bp.Goroot && bp.ImportPath == "runtime/race" || bp.ImportPath == "runtime/cgo" { - cgoflags = append(cgoflags, "-import_syscall=false") - } - - var cgoFiles []string = bp.CgoFiles - if useabs { - cgoFiles = make([]string, len(bp.CgoFiles)) - for i := range cgoFiles { - cgoFiles[i] = filepath.Join(pkgdir, bp.CgoFiles[i]) - } - } - - args := stringList( - "go", "tool", "cgo", "-objdir", tmpdir, cgoflags, "--", - cgoCPPFLAGS, cgoexeCFLAGS, cgoFiles, - ) - if false { - log.Printf("Running cgo for package %q: %s (dir=%s)", bp.ImportPath, args, pkgdir) - } - cmd := exec.Command(args[0], args[1:]...) - cmd.Dir = pkgdir - cmd.Stdout = os.Stderr - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - return nil, nil, fmt.Errorf("cgo failed: %s: %s", args, err) - } - - return files, displayFiles, nil -} - -// -- unmodified from 'go build' --------------------------------------- - -// Return the flags to use when invoking the C or C++ compilers, or cgo. -func cflags(p *build.Package, def bool) (cppflags, cflags, cxxflags, ldflags []string) { - var defaults string - if def { - defaults = "-g -O2" - } - - cppflags = stringList(envList("CGO_CPPFLAGS", ""), p.CgoCPPFLAGS) - cflags = stringList(envList("CGO_CFLAGS", defaults), p.CgoCFLAGS) - cxxflags = stringList(envList("CGO_CXXFLAGS", defaults), p.CgoCXXFLAGS) - ldflags = stringList(envList("CGO_LDFLAGS", defaults), p.CgoLDFLAGS) - return -} - -// envList returns the value of the given environment variable broken -// into fields, using the default value when the variable is empty. -func envList(key, def string) []string { - v := os.Getenv(key) - if v == "" { - v = def - } - return strings.Fields(v) -} - -// stringList's arguments should be a sequence of string or []string values. -// stringList flattens them into a single []string. -func stringList(args ...interface{}) []string { - var x []string - for _, arg := range args { - switch arg := arg.(type) { - case []string: - x = append(x, arg...) - case string: - x = append(x, arg) - default: - panic("stringList: invalid argument") - } - } - return x -} diff --git a/vendor/golang.org/x/tools/go/internal/cgo/cgo_pkgconfig.go b/vendor/golang.org/x/tools/go/internal/cgo/cgo_pkgconfig.go deleted file mode 100644 index b5bb95a63e..0000000000 --- a/vendor/golang.org/x/tools/go/internal/cgo/cgo_pkgconfig.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cgo - -import ( - "errors" - "fmt" - "go/build" - "os/exec" - "strings" -) - -// pkgConfig runs pkg-config with the specified arguments and returns the flags it prints. -func pkgConfig(mode string, pkgs []string) (flags []string, err error) { - cmd := exec.Command("pkg-config", append([]string{mode}, pkgs...)...) - out, err := cmd.CombinedOutput() - if err != nil { - s := fmt.Sprintf("%s failed: %v", strings.Join(cmd.Args, " "), err) - if len(out) > 0 { - s = fmt.Sprintf("%s: %s", s, out) - } - return nil, errors.New(s) - } - if len(out) > 0 { - flags = strings.Fields(string(out)) - } - return -} - -// pkgConfigFlags calls pkg-config if needed and returns the cflags -// needed to build the package. -func pkgConfigFlags(p *build.Package) (cflags []string, err error) { - if len(p.CgoPkgConfig) == 0 { - return nil, nil - } - return pkgConfig("--cflags", p.CgoPkgConfig) -} diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go index 067b008d3c..71157599f6 100644 --- a/vendor/golang.org/x/tools/go/packages/golist.go +++ b/vendor/golang.org/x/tools/go/packages/golist.go @@ -78,7 +78,7 @@ func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) { var sizes types.Sizes var sizeserr error var sizeswg sync.WaitGroup - if cfg.Mode >= LoadTypes { + if cfg.Mode&NeedTypesSizes != 0 { sizeswg.Add(1) go func() { sizes, sizeserr = getSizes(cfg) @@ -121,20 +121,6 @@ extractQueries: } } - // TODO(matloob): Remove the definition of listfunc and just use golistPackages once go1.12 is released. - var listfunc driver - var isFallback bool - listfunc = func(cfg *Config, words ...string) (*driverResponse, error) { - response, err := golistDriverCurrent(cfg, words...) - if _, ok := err.(goTooOldError); ok { - isFallback = true - listfunc = golistDriverFallback - return listfunc(cfg, words...) - } - listfunc = golistDriverCurrent - return response, err - } - response := &responseDeduper{} var err error @@ -142,7 +128,7 @@ extractQueries: // patterns also requires a go list call, since it's the equivalent of // ".". if len(restPatterns) > 0 || len(patterns) == 0 { - dr, err := listfunc(cfg, restPatterns...) + dr, err := golistDriverCurrent(cfg, restPatterns...) if err != nil { return nil, err } @@ -161,13 +147,13 @@ extractQueries: var containsCandidates []string if len(containFiles) != 0 { - if err := runContainsQueries(cfg, listfunc, isFallback, response, containFiles); err != nil { + if err := runContainsQueries(cfg, golistDriverCurrent, response, containFiles); err != nil { return nil, err } } if len(packagesNamed) != 0 { - if err := runNamedQueries(cfg, listfunc, response, packagesNamed); err != nil { + if err := runNamedQueries(cfg, golistDriverCurrent, response, packagesNamed); err != nil { return nil, err } } @@ -182,7 +168,7 @@ extractQueries: } if len(needPkgs) > 0 { - addNeededOverlayPackages(cfg, listfunc, response, needPkgs) + addNeededOverlayPackages(cfg, golistDriverCurrent, response, needPkgs) if err != nil { return nil, err } @@ -215,7 +201,7 @@ func addNeededOverlayPackages(cfg *Config, driver driver, response *responseDedu return nil } -func runContainsQueries(cfg *Config, driver driver, isFallback bool, response *responseDeduper, queries []string) error { +func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, queries []string) error { for _, query := range queries { // TODO(matloob): Do only one query per directory. fdir := filepath.Dir(query) @@ -225,11 +211,6 @@ func runContainsQueries(cfg *Config, driver driver, isFallback bool, response *r if err != nil { return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err) } - if isFallback { - pattern = "." - cfg.Dir = fdir - } - dirResponse, err := driver(cfg, pattern) if err != nil { return err @@ -605,7 +586,7 @@ func golistDriverCurrent(cfg *Config, words ...string) (*driverResponse, error) if old, found := seen[p.ImportPath]; found { if !reflect.DeepEqual(p, old) { - return nil, fmt.Errorf("go list repeated package %v with different values", p.ImportPath) + return nil, fmt.Errorf("internal error: go list gives conflicting information for package %v", p.ImportPath) } // skip the duplicate continue @@ -720,14 +701,16 @@ func absJoin(dir string, fileses ...[]string) (res []string) { } func golistargs(cfg *Config, words []string) []string { + const findFlags = NeedImports | NeedTypes | NeedSyntax | NeedTypesInfo fullargs := []string{ - "list", "-e", "-json", "-compiled", + "list", "-e", "-json", + fmt.Sprintf("-compiled=%t", cfg.Mode&(NeedCompiledGoFiles|NeedSyntax|NeedTypesInfo|NeedTypesSizes) != 0), fmt.Sprintf("-test=%t", cfg.Tests), fmt.Sprintf("-export=%t", usesExportData(cfg)), - fmt.Sprintf("-deps=%t", cfg.Mode >= LoadImports), + fmt.Sprintf("-deps=%t", cfg.Mode&NeedDeps != 0), // go list doesn't let you pass -test and -find together, // probably because you'd just get the TestMain. - fmt.Sprintf("-find=%t", cfg.Mode < LoadImports && !cfg.Tests), + fmt.Sprintf("-find=%t", !cfg.Tests && cfg.Mode&findFlags == 0), } fullargs = append(fullargs, cfg.BuildFlags...) fullargs = append(fullargs, "--") @@ -757,10 +740,14 @@ func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) { } if err := cmd.Run(); err != nil { + // Check for 'go' executable not being found. + if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound { + return nil, fmt.Errorf("'go list' driver requires 'go', but %s", exec.ErrNotFound) + } + exitErr, ok := err.(*exec.ExitError) if !ok { // Catastrophic error: - // - executable not found // - context cancellation return nil, fmt.Errorf("couldn't exec 'go %v': %s %T", args, err, err) } @@ -770,6 +757,15 @@ func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) { return nil, goTooOldError{fmt.Errorf("unsupported version of go: %s: %s", exitErr, stderr)} } + // This error only appears in stderr. See golang.org/cl/166398 for a fix in go list to show + // the error in the Err section of stdout in case -e option is provided. + // This fix is provided for backwards compatibility. + if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "named files must be .go files") { + output := fmt.Sprintf(`{"ImportPath": "","Incomplete": true,"Error": {"Pos": "","Err": %s}}`, + strconv.Quote(strings.Trim(stderr.String(), "\n"))) + return bytes.NewBufferString(output), nil + } + // Export mode entails a build. // If that build fails, errors appear on stderr // (despite the -e flag) and the Export field is blank. diff --git a/vendor/golang.org/x/tools/go/packages/golist_fallback.go b/vendor/golang.org/x/tools/go/packages/golist_fallback.go deleted file mode 100644 index 141fa19ac1..0000000000 --- a/vendor/golang.org/x/tools/go/packages/golist_fallback.go +++ /dev/null @@ -1,450 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package packages - -import ( - "encoding/json" - "fmt" - "go/build" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "sort" - "strings" - - "golang.org/x/tools/go/internal/cgo" -) - -// TODO(matloob): Delete this file once Go 1.12 is released. - -// This file provides backwards compatibility support for -// loading for versions of Go earlier than 1.11. This support is meant to -// assist with migration to the Package API until there's -// widespread adoption of these newer Go versions. -// This support will be removed once Go 1.12 is released -// in Q1 2019. - -func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error) { - // Turn absolute paths into GOROOT and GOPATH-relative paths to provide to go list. - // This will have surprising behavior if GOROOT or GOPATH contain multiple packages with the same - // path and a user provides an absolute path to a directory that's shadowed by an earlier - // directory in GOROOT or GOPATH with the same package path. - words = cleanAbsPaths(cfg, words) - - original, deps, err := getDeps(cfg, words...) - if err != nil { - return nil, err - } - - var tmpdir string // used for generated cgo files - var needsTestVariant []struct { - pkg, xtestPkg *Package - } - - var response driverResponse - allPkgs := make(map[string]bool) - addPackage := func(p *jsonPackage, isRoot bool) { - id := p.ImportPath - - if allPkgs[id] { - return - } - allPkgs[id] = true - - pkgpath := id - - if pkgpath == "unsafe" { - p.GoFiles = nil // ignore fake unsafe.go file - } - - importMap := func(importlist []string) map[string]*Package { - importMap := make(map[string]*Package) - for _, id := range importlist { - - if id == "C" { - for _, path := range []string{"unsafe", "syscall", "runtime/cgo"} { - if pkgpath != path && importMap[path] == nil { - importMap[path] = &Package{ID: path} - } - } - continue - } - importMap[vendorlessPath(id)] = &Package{ID: id} - } - return importMap - } - compiledGoFiles := absJoin(p.Dir, p.GoFiles) - // Use a function to simplify control flow. It's just a bunch of gotos. - var cgoErrors []error - var outdir string - getOutdir := func() (string, error) { - if outdir != "" { - return outdir, nil - } - if tmpdir == "" { - if tmpdir, err = ioutil.TempDir("", "gopackages"); err != nil { - return "", err - } - } - outdir = filepath.Join(tmpdir, strings.Replace(p.ImportPath, "/", "_", -1)) - if err := os.MkdirAll(outdir, 0755); err != nil { - outdir = "" - return "", err - } - return outdir, nil - } - processCgo := func() bool { - // Suppress any cgo errors. Any relevant errors will show up in typechecking. - // TODO(matloob): Skip running cgo if Mode < LoadTypes. - outdir, err := getOutdir() - if err != nil { - cgoErrors = append(cgoErrors, err) - return false - } - files, _, err := runCgo(p.Dir, outdir, cfg.Env) - if err != nil { - cgoErrors = append(cgoErrors, err) - return false - } - compiledGoFiles = append(compiledGoFiles, files...) - return true - } - if len(p.CgoFiles) == 0 || !processCgo() { - compiledGoFiles = append(compiledGoFiles, absJoin(p.Dir, p.CgoFiles)...) // Punt to typechecker. - } - if isRoot { - response.Roots = append(response.Roots, id) - } - pkg := &Package{ - ID: id, - Name: p.Name, - GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles), - CompiledGoFiles: compiledGoFiles, - OtherFiles: absJoin(p.Dir, otherFiles(p)...), - PkgPath: pkgpath, - Imports: importMap(p.Imports), - // TODO(matloob): set errors on the Package to cgoErrors - } - if p.Error != nil { - pkg.Errors = append(pkg.Errors, Error{ - Pos: p.Error.Pos, - Msg: p.Error.Err, - }) - } - response.Packages = append(response.Packages, pkg) - if cfg.Tests && isRoot { - testID := fmt.Sprintf("%s [%s.test]", id, id) - if len(p.TestGoFiles) > 0 || len(p.XTestGoFiles) > 0 { - response.Roots = append(response.Roots, testID) - testPkg := &Package{ - ID: testID, - Name: p.Name, - GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles, p.TestGoFiles), - CompiledGoFiles: append(compiledGoFiles, absJoin(p.Dir, p.TestGoFiles)...), - OtherFiles: absJoin(p.Dir, otherFiles(p)...), - PkgPath: pkgpath, - Imports: importMap(append(p.Imports, p.TestImports...)), - // TODO(matloob): set errors on the Package to cgoErrors - } - response.Packages = append(response.Packages, testPkg) - var xtestPkg *Package - if len(p.XTestGoFiles) > 0 { - xtestID := fmt.Sprintf("%s_test [%s.test]", id, id) - response.Roots = append(response.Roots, xtestID) - // Generate test variants for all packages q where a path exists - // such that xtestPkg -> ... -> q -> ... -> p (where p is the package under test) - // and rewrite all import map entries of p to point to testPkg (the test variant of - // p), and of each q to point to the test variant of that q. - xtestPkg = &Package{ - ID: xtestID, - Name: p.Name + "_test", - GoFiles: absJoin(p.Dir, p.XTestGoFiles), - CompiledGoFiles: absJoin(p.Dir, p.XTestGoFiles), - PkgPath: pkgpath + "_test", - Imports: importMap(p.XTestImports), - } - // Add to list of packages we need to rewrite imports for to refer to test variants. - // We may need to create a test variant of a package that hasn't been loaded yet, so - // the test variants need to be created later. - needsTestVariant = append(needsTestVariant, struct{ pkg, xtestPkg *Package }{pkg, xtestPkg}) - response.Packages = append(response.Packages, xtestPkg) - } - // testmain package - testmainID := id + ".test" - response.Roots = append(response.Roots, testmainID) - imports := map[string]*Package{} - imports[testPkg.PkgPath] = &Package{ID: testPkg.ID} - if xtestPkg != nil { - imports[xtestPkg.PkgPath] = &Package{ID: xtestPkg.ID} - } - testmainPkg := &Package{ - ID: testmainID, - Name: "main", - PkgPath: testmainID, - Imports: imports, - } - response.Packages = append(response.Packages, testmainPkg) - outdir, err := getOutdir() - if err != nil { - testmainPkg.Errors = append(testmainPkg.Errors, Error{ - Pos: "-", - Msg: fmt.Sprintf("failed to generate testmain: %v", err), - Kind: ListError, - }) - return - } - // Don't use a .go extension on the file, so that the tests think the file is inside GOCACHE. - // This allows the same test to test the pre- and post-Go 1.11 go list logic because the Go 1.11 - // go list generates test mains in the cache, and the test code knows not to rely on paths in the - // cache to stay stable. - testmain := filepath.Join(outdir, "testmain-go") - extraimports, extradeps, err := generateTestmain(testmain, testPkg, xtestPkg) - if err != nil { - testmainPkg.Errors = append(testmainPkg.Errors, Error{ - Pos: "-", - Msg: fmt.Sprintf("failed to generate testmain: %v", err), - Kind: ListError, - }) - } - deps = append(deps, extradeps...) - for _, imp := range extraimports { // testing, testing/internal/testdeps, and maybe os - imports[imp] = &Package{ID: imp} - } - testmainPkg.GoFiles = []string{testmain} - testmainPkg.CompiledGoFiles = []string{testmain} - } - } - } - - for _, pkg := range original { - addPackage(pkg, true) - } - if cfg.Mode < LoadImports || len(deps) == 0 { - return &response, nil - } - - buf, err := invokeGo(cfg, golistArgsFallback(cfg, deps)...) - if err != nil { - return nil, err - } - - // Decode the JSON and convert it to Package form. - for dec := json.NewDecoder(buf); dec.More(); { - p := new(jsonPackage) - if err := dec.Decode(p); err != nil { - return nil, fmt.Errorf("JSON decoding failed: %v", err) - } - - addPackage(p, false) - } - - for _, v := range needsTestVariant { - createTestVariants(&response, v.pkg, v.xtestPkg) - } - - return &response, nil -} - -func createTestVariants(response *driverResponse, pkgUnderTest, xtestPkg *Package) { - allPkgs := make(map[string]*Package) - for _, pkg := range response.Packages { - allPkgs[pkg.ID] = pkg - } - needsTestVariant := make(map[string]bool) - needsTestVariant[pkgUnderTest.ID] = true - var needsVariantRec func(p *Package) bool - needsVariantRec = func(p *Package) bool { - if needsTestVariant[p.ID] { - return true - } - for _, imp := range p.Imports { - if needsVariantRec(allPkgs[imp.ID]) { - // Don't break because we want to make sure all dependencies - // have been processed, and all required test variants of our dependencies - // exist. - needsTestVariant[p.ID] = true - } - } - if !needsTestVariant[p.ID] { - return false - } - // Create a clone of the package. It will share the same strings and lists of source files, - // but that's okay. It's only necessary for the Imports map to have a separate identity. - testVariant := *p - testVariant.ID = fmt.Sprintf("%s [%s.test]", p.ID, pkgUnderTest.ID) - testVariant.Imports = make(map[string]*Package) - for imp, pkg := range p.Imports { - testVariant.Imports[imp] = pkg - if needsTestVariant[pkg.ID] { - testVariant.Imports[imp] = &Package{ID: fmt.Sprintf("%s [%s.test]", pkg.ID, pkgUnderTest.ID)} - } - } - response.Packages = append(response.Packages, &testVariant) - return needsTestVariant[p.ID] - } - // finally, update the xtest package's imports - for imp, pkg := range xtestPkg.Imports { - if allPkgs[pkg.ID] == nil { - fmt.Printf("for %s: package %s doesn't exist\n", xtestPkg.ID, pkg.ID) - } - if needsVariantRec(allPkgs[pkg.ID]) { - xtestPkg.Imports[imp] = &Package{ID: fmt.Sprintf("%s [%s.test]", pkg.ID, pkgUnderTest.ID)} - } - } -} - -// cleanAbsPaths replaces all absolute paths with GOPATH- and GOROOT-relative -// paths. If an absolute path is not GOPATH- or GOROOT- relative, it is left as an -// absolute path so an error can be returned later. -func cleanAbsPaths(cfg *Config, words []string) []string { - var searchpaths []string - var cleaned = make([]string, len(words)) - for i := range cleaned { - cleaned[i] = words[i] - // Ignore relative directory paths (they must already be goroot-relative) and Go source files - // (absolute source files are already allowed for ad-hoc packages). - // TODO(matloob): Can there be non-.go files in ad-hoc packages. - if !filepath.IsAbs(cleaned[i]) || strings.HasSuffix(cleaned[i], ".go") { - continue - } - // otherwise, it's an absolute path. Search GOPATH and GOROOT to find it. - if searchpaths == nil { - cmd := exec.Command("go", "env", "GOPATH", "GOROOT") - cmd.Env = cfg.Env - out, err := cmd.Output() - if err != nil { - searchpaths = []string{} - continue // suppress the error, it will show up again when running go list - } - lines := strings.Split(string(out), "\n") - if len(lines) != 3 || lines[0] == "" || lines[1] == "" || lines[2] != "" { - continue // suppress error - } - // first line is GOPATH - for _, path := range filepath.SplitList(lines[0]) { - searchpaths = append(searchpaths, filepath.Join(path, "src")) - } - // second line is GOROOT - searchpaths = append(searchpaths, filepath.Join(lines[1], "src")) - } - for _, sp := range searchpaths { - if strings.HasPrefix(cleaned[i], sp) { - cleaned[i] = strings.TrimPrefix(cleaned[i], sp) - cleaned[i] = strings.TrimLeft(cleaned[i], string(filepath.Separator)) - } - } - } - return cleaned -} - -// vendorlessPath returns the devendorized version of the import path ipath. -// For example, VendorlessPath("foo/bar/vendor/a/b") returns "a/b". -// Copied from golang.org/x/tools/imports/fix.go. -func vendorlessPath(ipath string) string { - // Devendorize for use in import statement. - if i := strings.LastIndex(ipath, "/vendor/"); i >= 0 { - return ipath[i+len("/vendor/"):] - } - if strings.HasPrefix(ipath, "vendor/") { - return ipath[len("vendor/"):] - } - return ipath -} - -// getDeps runs an initial go list to determine all the dependency packages. -func getDeps(cfg *Config, words ...string) (initial []*jsonPackage, deps []string, err error) { - buf, err := invokeGo(cfg, golistArgsFallback(cfg, words)...) - if err != nil { - return nil, nil, err - } - - depsSet := make(map[string]bool) - var testImports []string - - // Extract deps from the JSON. - for dec := json.NewDecoder(buf); dec.More(); { - p := new(jsonPackage) - if err := dec.Decode(p); err != nil { - return nil, nil, fmt.Errorf("JSON decoding failed: %v", err) - } - - initial = append(initial, p) - for _, dep := range p.Deps { - depsSet[dep] = true - } - if cfg.Tests { - // collect the additional imports of the test packages. - pkgTestImports := append(p.TestImports, p.XTestImports...) - for _, imp := range pkgTestImports { - if depsSet[imp] { - continue - } - depsSet[imp] = true - testImports = append(testImports, imp) - } - } - } - // Get the deps of the packages imported by tests. - if len(testImports) > 0 { - buf, err = invokeGo(cfg, golistArgsFallback(cfg, testImports)...) - if err != nil { - return nil, nil, err - } - // Extract deps from the JSON. - for dec := json.NewDecoder(buf); dec.More(); { - p := new(jsonPackage) - if err := dec.Decode(p); err != nil { - return nil, nil, fmt.Errorf("JSON decoding failed: %v", err) - } - for _, dep := range p.Deps { - depsSet[dep] = true - } - } - } - - for _, orig := range initial { - delete(depsSet, orig.ImportPath) - } - - deps = make([]string, 0, len(depsSet)) - for dep := range depsSet { - deps = append(deps, dep) - } - sort.Strings(deps) // ensure output is deterministic - return initial, deps, nil -} - -func golistArgsFallback(cfg *Config, words []string) []string { - fullargs := []string{"list", "-e", "-json"} - fullargs = append(fullargs, cfg.BuildFlags...) - fullargs = append(fullargs, "--") - fullargs = append(fullargs, words...) - return fullargs -} - -func runCgo(pkgdir, tmpdir string, env []string) (files, displayfiles []string, err error) { - // Use go/build to open cgo files and determine the cgo flags, etc, from them. - // This is tricky so it's best to avoid reimplementing as much as we can, and - // we plan to delete this support once Go 1.12 is released anyways. - // TODO(matloob): This isn't completely correct because we're using the Default - // context. Perhaps we should more accurately fill in the context. - bp, err := build.ImportDir(pkgdir, build.ImportMode(0)) - if err != nil { - return nil, nil, err - } - for _, ev := range env { - if v := strings.TrimPrefix(ev, "CGO_CPPFLAGS"); v != ev { - bp.CgoCPPFLAGS = append(bp.CgoCPPFLAGS, strings.Fields(v)...) - } else if v := strings.TrimPrefix(ev, "CGO_CFLAGS"); v != ev { - bp.CgoCFLAGS = append(bp.CgoCFLAGS, strings.Fields(v)...) - } else if v := strings.TrimPrefix(ev, "CGO_CXXFLAGS"); v != ev { - bp.CgoCXXFLAGS = append(bp.CgoCXXFLAGS, strings.Fields(v)...) - } else if v := strings.TrimPrefix(ev, "CGO_LDFLAGS"); v != ev { - bp.CgoLDFLAGS = append(bp.CgoLDFLAGS, strings.Fields(v)...) - } - } - return cgo.Run(bp, pkgdir, tmpdir, true) -} diff --git a/vendor/golang.org/x/tools/go/packages/golist_fallback_testmain.go b/vendor/golang.org/x/tools/go/packages/golist_fallback_testmain.go deleted file mode 100644 index 128e00e25a..0000000000 --- a/vendor/golang.org/x/tools/go/packages/golist_fallback_testmain.go +++ /dev/null @@ -1,318 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file is largely based on the Go 1.10-era cmd/go/internal/test/test.go -// testmain generation code. - -package packages - -import ( - "errors" - "fmt" - "go/ast" - "go/doc" - "go/parser" - "go/token" - "os" - "sort" - "strings" - "text/template" - "unicode" - "unicode/utf8" -) - -// TODO(matloob): Delete this file once Go 1.12 is released. - -// This file complements golist_fallback.go by providing -// support for generating testmains. - -func generateTestmain(out string, testPkg, xtestPkg *Package) (extraimports, extradeps []string, err error) { - testFuncs, err := loadTestFuncs(testPkg, xtestPkg) - if err != nil { - return nil, nil, err - } - extraimports = []string{"testing", "testing/internal/testdeps"} - if testFuncs.TestMain == nil { - extraimports = append(extraimports, "os") - } - // Transitive dependencies of ("testing", "testing/internal/testdeps"). - // os is part of the transitive closure so it and its transitive dependencies are - // included regardless of whether it's imported in the template below. - extradeps = []string{ - "errors", - "internal/cpu", - "unsafe", - "internal/bytealg", - "internal/race", - "runtime/internal/atomic", - "runtime/internal/sys", - "runtime", - "sync/atomic", - "sync", - "io", - "unicode", - "unicode/utf8", - "bytes", - "math", - "syscall", - "time", - "internal/poll", - "internal/syscall/unix", - "internal/testlog", - "os", - "math/bits", - "strconv", - "reflect", - "fmt", - "sort", - "strings", - "flag", - "runtime/debug", - "context", - "runtime/trace", - "testing", - "bufio", - "regexp/syntax", - "regexp", - "compress/flate", - "encoding/binary", - "hash", - "hash/crc32", - "compress/gzip", - "path/filepath", - "io/ioutil", - "text/tabwriter", - "runtime/pprof", - "testing/internal/testdeps", - } - return extraimports, extradeps, writeTestmain(out, testFuncs) -} - -// The following is adapted from the cmd/go testmain generation code. - -// isTestFunc tells whether fn has the type of a testing function. arg -// specifies the parameter type we look for: B, M or T. -func isTestFunc(fn *ast.FuncDecl, arg string) bool { - if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 || - fn.Type.Params.List == nil || - len(fn.Type.Params.List) != 1 || - len(fn.Type.Params.List[0].Names) > 1 { - return false - } - ptr, ok := fn.Type.Params.List[0].Type.(*ast.StarExpr) - if !ok { - return false - } - // We can't easily check that the type is *testing.M - // because we don't know how testing has been imported, - // but at least check that it's *M or *something.M. - // Same applies for B and T. - if name, ok := ptr.X.(*ast.Ident); ok && name.Name == arg { - return true - } - if sel, ok := ptr.X.(*ast.SelectorExpr); ok && sel.Sel.Name == arg { - return true - } - return false -} - -// isTest tells whether name looks like a test (or benchmark, according to prefix). -// It is a Test (say) if there is a character after Test that is not a lower-case letter. -// We don't want TesticularCancer. -func isTest(name, prefix string) bool { - if !strings.HasPrefix(name, prefix) { - return false - } - if len(name) == len(prefix) { // "Test" is ok - return true - } - rune, _ := utf8.DecodeRuneInString(name[len(prefix):]) - return !unicode.IsLower(rune) -} - -// loadTestFuncs returns the testFuncs describing the tests that will be run. -func loadTestFuncs(ptest, pxtest *Package) (*testFuncs, error) { - t := &testFuncs{ - TestPackage: ptest, - XTestPackage: pxtest, - } - for _, file := range ptest.GoFiles { - if !strings.HasSuffix(file, "_test.go") { - continue - } - if err := t.load(file, "_test", &t.ImportTest, &t.NeedTest); err != nil { - return nil, err - } - } - if pxtest != nil { - for _, file := range pxtest.GoFiles { - if err := t.load(file, "_xtest", &t.ImportXtest, &t.NeedXtest); err != nil { - return nil, err - } - } - } - return t, nil -} - -// writeTestmain writes the _testmain.go file for t to the file named out. -func writeTestmain(out string, t *testFuncs) error { - f, err := os.Create(out) - if err != nil { - return err - } - defer f.Close() - - if err := testmainTmpl.Execute(f, t); err != nil { - return err - } - - return nil -} - -type testFuncs struct { - Tests []testFunc - Benchmarks []testFunc - Examples []testFunc - TestMain *testFunc - TestPackage *Package - XTestPackage *Package - ImportTest bool - NeedTest bool - ImportXtest bool - NeedXtest bool -} - -// Tested returns the name of the package being tested. -func (t *testFuncs) Tested() string { - return t.TestPackage.Name -} - -type testFunc struct { - Package string // imported package name (_test or _xtest) - Name string // function name - Output string // output, for examples - Unordered bool // output is allowed to be unordered. -} - -func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error { - var fset = token.NewFileSet() - - f, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) - if err != nil { - return errors.New("failed to parse test file " + filename) - } - for _, d := range f.Decls { - n, ok := d.(*ast.FuncDecl) - if !ok { - continue - } - if n.Recv != nil { - continue - } - name := n.Name.String() - switch { - case name == "TestMain": - if isTestFunc(n, "T") { - t.Tests = append(t.Tests, testFunc{pkg, name, "", false}) - *doImport, *seen = true, true - continue - } - err := checkTestFunc(fset, n, "M") - if err != nil { - return err - } - if t.TestMain != nil { - return errors.New("multiple definitions of TestMain") - } - t.TestMain = &testFunc{pkg, name, "", false} - *doImport, *seen = true, true - case isTest(name, "Test"): - err := checkTestFunc(fset, n, "T") - if err != nil { - return err - } - t.Tests = append(t.Tests, testFunc{pkg, name, "", false}) - *doImport, *seen = true, true - case isTest(name, "Benchmark"): - err := checkTestFunc(fset, n, "B") - if err != nil { - return err - } - t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, "", false}) - *doImport, *seen = true, true - } - } - ex := doc.Examples(f) - sort.Slice(ex, func(i, j int) bool { return ex[i].Order < ex[j].Order }) - for _, e := range ex { - *doImport = true // import test file whether executed or not - if e.Output == "" && !e.EmptyOutput { - // Don't run examples with no output. - continue - } - t.Examples = append(t.Examples, testFunc{pkg, "Example" + e.Name, e.Output, e.Unordered}) - *seen = true - } - return nil -} - -func checkTestFunc(fset *token.FileSet, fn *ast.FuncDecl, arg string) error { - if !isTestFunc(fn, arg) { - name := fn.Name.String() - pos := fset.Position(fn.Pos()) - return fmt.Errorf("%s: wrong signature for %s, must be: func %s(%s *testing.%s)", pos, name, name, strings.ToLower(arg), arg) - } - return nil -} - -var testmainTmpl = template.Must(template.New("main").Parse(` -package main - -import ( -{{if not .TestMain}} - "os" -{{end}} - "testing" - "testing/internal/testdeps" - -{{if .ImportTest}} - {{if .NeedTest}}_test{{else}}_{{end}} {{.TestPackage.PkgPath | printf "%q"}} -{{end}} -{{if .ImportXtest}} - {{if .NeedXtest}}_xtest{{else}}_{{end}} {{.XTestPackage.PkgPath | printf "%q"}} -{{end}} -) - -var tests = []testing.InternalTest{ -{{range .Tests}} - {"{{.Name}}", {{.Package}}.{{.Name}}}, -{{end}} -} - -var benchmarks = []testing.InternalBenchmark{ -{{range .Benchmarks}} - {"{{.Name}}", {{.Package}}.{{.Name}}}, -{{end}} -} - -var examples = []testing.InternalExample{ -{{range .Examples}} - {"{{.Name}}", {{.Package}}.{{.Name}}, {{.Output | printf "%q"}}, {{.Unordered}}}, -{{end}} -} - -func init() { - testdeps.ImportPath = {{.TestPackage.PkgPath | printf "%q"}} -} - -func main() { - m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, examples) -{{with .TestMain}} - {{.Package}}.{{.Name}}(m) -{{else}} - os.Exit(m.Run()) -{{end}} -} - -`)) diff --git a/vendor/golang.org/x/tools/go/packages/packages.go b/vendor/golang.org/x/tools/go/packages/packages.go index 1e5836c9e9..775dd940ca 100644 --- a/vendor/golang.org/x/tools/go/packages/packages.go +++ b/vendor/golang.org/x/tools/go/packages/packages.go @@ -30,32 +30,78 @@ import ( // but may be slower. Load may return more information than requested. type LoadMode int +const ( + // The following constants are used to specify which fields of the Package + // should be filled when loading is done. As a special case to provide + // backwards compatibility, a LoadMode of 0 is equivalent to LoadFiles. + // For all other LoadModes, the bits below specify which fields will be filled + // in the result packages. + // WARNING: This part of the go/packages API is EXPERIMENTAL. It might + // be changed or removed up until April 15 2019. After that date it will + // be frozen. + // TODO(matloob): Remove this comment on April 15. + + // ID and Errors (if present) will always be filled. + + // NeedName adds Name and PkgPath. + NeedName LoadMode = 1 << iota + + // NeedFiles adds GoFiles and OtherFiles. + NeedFiles + + // NeedCompiledGoFiles adds CompiledGoFiles. + NeedCompiledGoFiles + + // NeedImports adds Imports. If NeedDeps is not set, the Imports field will contain + // "placeholder" Packages with only the ID set. + NeedImports + + // NeedDeps adds the fields requested by the LoadMode in the packages in Imports. If NeedImports + // is not set NeedDeps has no effect. + NeedDeps + + // NeedExportsFile adds ExportsFile. + NeedExportsFile + + // NeedTypes adds Types, Fset, and IllTyped. + NeedTypes + + // NeedSyntax adds Syntax. + NeedSyntax + + // NeedTypesInfo adds TypesInfo. + NeedTypesInfo + + // NeedTypesSizes adds TypesSizes. + NeedTypesSizes +) + const ( // LoadFiles finds the packages and computes their source file lists. - // Package fields: ID, Name, Errors, GoFiles, and OtherFiles. - LoadFiles LoadMode = iota + // Package fields: ID, Name, Errors, GoFiles, CompiledGoFiles, and OtherFiles. + LoadFiles = NeedName | NeedFiles | NeedCompiledGoFiles // LoadImports adds import information for each package // and its dependencies. // Package fields added: Imports. - LoadImports + LoadImports = LoadFiles | NeedImports | NeedDeps // LoadTypes adds type information for package-level // declarations in the packages matching the patterns. // Package fields added: Types, Fset, and IllTyped. // This mode uses type information provided by the build system when // possible, and may fill in the ExportFile field. - LoadTypes + LoadTypes = LoadImports | NeedTypes // LoadSyntax adds typed syntax trees for the packages matching the patterns. // Package fields added: Syntax, and TypesInfo, for direct pattern matches only. - LoadSyntax + LoadSyntax = LoadTypes | NeedSyntax | NeedTypesInfo | NeedTypesSizes // LoadAllSyntax adds typed syntax trees for the packages matching the patterns // and all dependencies. // Package fields added: Types, Fset, IllTyped, Syntax, and TypesInfo, // for all packages in the import graph. - LoadAllSyntax + LoadAllSyntax = LoadSyntax ) // A Config specifies details about how packages should be loaded. @@ -381,6 +427,9 @@ func newLoader(cfg *Config) *loader { if cfg != nil { ld.Config = *cfg } + if ld.Config.Mode == 0 { + ld.Config.Mode = LoadFiles // Preserve zero behavior of Mode for backwards compatibility. + } if ld.Config.Env == nil { ld.Config.Env = os.Environ() } @@ -393,7 +442,7 @@ func newLoader(cfg *Config) *loader { } } - if ld.Mode >= LoadTypes { + if ld.Mode&NeedTypes != 0 { if ld.Fset == nil { ld.Fset = token.NewFileSet() } @@ -430,11 +479,9 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) { rootIndex = i } lpkg := &loaderPackage{ - Package: pkg, - needtypes: ld.Mode >= LoadAllSyntax || - ld.Mode >= LoadTypes && rootIndex >= 0, - needsrc: ld.Mode >= LoadAllSyntax || - ld.Mode >= LoadSyntax && rootIndex >= 0 || + Package: pkg, + needtypes: (ld.Mode&(NeedTypes|NeedTypesInfo) != 0 && rootIndex < 0) || rootIndex >= 0, + needsrc: (ld.Mode&(NeedSyntax|NeedTypesInfo) != 0 && rootIndex < 0) || rootIndex >= 0 || len(ld.Overlay) > 0 || // Overlays can invalidate export data. TODO(matloob): make this check fine-grained based on dependencies on overlaid files pkg.ExportFile == "" && pkg.PkgPath != "unsafe", } @@ -513,8 +560,8 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) { return lpkg.needsrc } - if ld.Mode < LoadImports { - //we do this to drop the stub import packages that we are not even going to try to resolve + if ld.Mode&NeedImports == 0 { + // We do this to drop the stub import packages that we are not even going to try to resolve. for _, lpkg := range initial { lpkg.Imports = nil } @@ -524,17 +571,19 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) { visit(lpkg) } } - for _, lpkg := range srcPkgs { - // Complete type information is required for the - // immediate dependencies of each source package. - for _, ipkg := range lpkg.Imports { - imp := ld.pkgs[ipkg.ID] - imp.needtypes = true + if ld.Mode&NeedDeps != 0 { + for _, lpkg := range srcPkgs { + // Complete type information is required for the + // immediate dependencies of each source package. + for _, ipkg := range lpkg.Imports { + imp := ld.pkgs[ipkg.ID] + imp.needtypes = true + } } } // Load type data if needed, starting at // the initial packages (roots of the import DAG). - if ld.Mode >= LoadTypes { + if ld.Mode&NeedTypes != 0 { var wg sync.WaitGroup for _, lpkg := range initial { wg.Add(1) @@ -547,8 +596,51 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) { } result := make([]*Package, len(initial)) + importPlaceholders := make(map[string]*Package) for i, lpkg := range initial { result[i] = lpkg.Package + // Clear all unrequested fields, for extra de-Hyrum-ization. + if ld.Mode&NeedName == 0 { + result[i].Name = "" + result[i].PkgPath = "" + } + if ld.Mode&NeedFiles == 0 { + result[i].GoFiles = nil + result[i].OtherFiles = nil + } + if ld.Mode&NeedCompiledGoFiles == 0 { + result[i].CompiledGoFiles = nil + } + if ld.Mode&NeedImports == 0 { + result[i].Imports = nil + } + if ld.Mode&NeedExportsFile == 0 { + result[i].ExportFile = "" + } + if ld.Mode&NeedTypes == 0 { + result[i].Types = nil + result[i].Fset = nil + result[i].IllTyped = false + } + if ld.Mode&NeedSyntax == 0 { + result[i].Syntax = nil + } + if ld.Mode&NeedTypesInfo == 0 { + result[i].TypesInfo = nil + } + if ld.Mode&NeedTypesSizes == 0 { + result[i].TypesSizes = nil + } + if ld.Mode&NeedDeps == 0 { + for j, pkg := range result[i].Imports { + ph, ok := importPlaceholders[pkg.ID] + if !ok { + ph = &Package{ID: pkg.ID} + importPlaceholders[pkg.ID] = ph + } + result[i].Imports[j] = ph + } + } } return result, nil } @@ -556,7 +648,7 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) { // loadRecursive loads the specified package and its dependencies, // recursively, in parallel, in topological order. // It is atomic and idempotent. -// Precondition: ld.Mode >= LoadTypes. +// Precondition: ld.Mode&NeedTypes. func (ld *loader) loadRecursive(lpkg *loaderPackage) { lpkg.loadOnce.Do(func() { // Load the direct dependencies, in parallel. @@ -708,7 +800,7 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { // Type-check bodies of functions only in non-initial packages. // Example: for import graph A->B->C and initial packages {A,C}, // we can ignore function bodies in B. - IgnoreFuncBodies: ld.Mode < LoadAllSyntax && !lpkg.initial, + IgnoreFuncBodies: (ld.Mode&(NeedDeps|NeedTypesInfo) == 0) && !lpkg.initial, Error: appendError, Sizes: ld.sizes, @@ -952,5 +1044,5 @@ func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error } func usesExportData(cfg *Config) bool { - return LoadTypes <= cfg.Mode && cfg.Mode < LoadAllSyntax + return cfg.Mode&NeedExportsFile != 0 || cfg.Mode&NeedTypes != 0 && cfg.Mode&NeedTypesInfo == 0 } diff --git a/vendor/golang.org/x/tools/imports/mod.go b/vendor/golang.org/x/tools/imports/mod.go index ec769145cc..018c43ce84 100644 --- a/vendor/golang.org/x/tools/imports/mod.go +++ b/vendor/golang.org/x/tools/imports/mod.go @@ -24,6 +24,7 @@ import ( type moduleResolver struct { env *fixEnv + initialized bool main *moduleJSON modsByModPath []*moduleJSON // All modules, ordered by # of path components in module Path... modsByDir []*moduleJSON // ...or Dir. @@ -48,7 +49,7 @@ type moduleErrorJSON struct { } func (r *moduleResolver) init() error { - if r.main != nil { + if r.initialized { return nil } stdout, err := r.env.invokeGo("list", "-m", "-json", "...") @@ -87,6 +88,7 @@ func (r *moduleResolver) init() error { return count(j) < count(i) // descending order }) + r.initialized = true return nil } @@ -202,7 +204,9 @@ func (r *moduleResolver) scan(_ references) ([]*pkg, error) { // Walk GOROOT, GOPATH/pkg/mod, and the main module. roots := []gopathwalk.Root{ {filepath.Join(r.env.GOROOT, "/src"), gopathwalk.RootGOROOT}, - {r.main.Dir, gopathwalk.RootCurrentModule}, + } + if r.main != nil { + roots = append(roots, gopathwalk.Root{r.main.Dir, gopathwalk.RootCurrentModule}) } for _, p := range filepath.SplitList(r.env.GOPATH) { roots = append(roots, gopathwalk.Root{filepath.Join(p, "/pkg/mod"), gopathwalk.RootModuleCache}) diff --git a/vendor/gopkg.in/gcfg.v1/LICENSE b/vendor/gopkg.in/gcfg.v1/LICENSE new file mode 100644 index 0000000000..87a5cede33 --- /dev/null +++ b/vendor/gopkg.in/gcfg.v1/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2012 Péter Surányi. Portions Copyright (c) 2009 The Go +Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/gopkg.in/gcfg.v1/README b/vendor/gopkg.in/gcfg.v1/README new file mode 100644 index 0000000000..1ff233a529 --- /dev/null +++ b/vendor/gopkg.in/gcfg.v1/README @@ -0,0 +1,4 @@ +Gcfg reads INI-style configuration files into Go structs; +supports user-defined types and subsections. + +Package docs: https://godoc.org/gopkg.in/gcfg.v1 diff --git a/vendor/gopkg.in/gcfg.v1/doc.go b/vendor/gopkg.in/gcfg.v1/doc.go new file mode 100644 index 0000000000..32f3e9d69c --- /dev/null +++ b/vendor/gopkg.in/gcfg.v1/doc.go @@ -0,0 +1,145 @@ +// Package gcfg reads "INI-style" text-based configuration files with +// "name=value" pairs grouped into sections (gcfg files). +// +// This package is still a work in progress; see the sections below for planned +// changes. +// +// Syntax +// +// The syntax is based on that used by git config: +// http://git-scm.com/docs/git-config#_syntax . +// There are some (planned) differences compared to the git config format: +// - improve data portability: +// - must be encoded in UTF-8 (for now) and must not contain the 0 byte +// - include and "path" type is not supported +// (path type may be implementable as a user-defined type) +// - internationalization +// - section and variable names can contain unicode letters, unicode digits +// (as defined in http://golang.org/ref/spec#Characters ) and hyphens +// (U+002D), starting with a unicode letter +// - disallow potentially ambiguous or misleading definitions: +// - `[sec.sub]` format is not allowed (deprecated in gitconfig) +// - `[sec ""]` is not allowed +// - use `[sec]` for section name "sec" and empty subsection name +// - (planned) within a single file, definitions must be contiguous for each: +// - section: '[secA]' -> '[secB]' -> '[secA]' is an error +// - subsection: '[sec "A"]' -> '[sec "B"]' -> '[sec "A"]' is an error +// - multivalued variable: 'multi=a' -> 'other=x' -> 'multi=b' is an error +// +// Data structure +// +// The functions in this package read values into a user-defined struct. +// Each section corresponds to a struct field in the config struct, and each +// variable in a section corresponds to a data field in the section struct. +// The mapping of each section or variable name to fields is done either based +// on the "gcfg" struct tag or by matching the name of the section or variable, +// ignoring case. In the latter case, hyphens '-' in section and variable names +// correspond to underscores '_' in field names. +// Fields must be exported; to use a section or variable name starting with a +// letter that is neither upper- or lower-case, prefix the field name with 'X'. +// (See https://code.google.com/p/go/issues/detail?id=5763#c4 .) +// +// For sections with subsections, the corresponding field in config must be a +// map, rather than a struct, with string keys and pointer-to-struct values. +// Values for subsection variables are stored in the map with the subsection +// name used as the map key. +// (Note that unlike section and variable names, subsection names are case +// sensitive.) +// When using a map, and there is a section with the same section name but +// without a subsection name, its values are stored with the empty string used +// as the key. +// It is possible to provide default values for subsections in the section +// "default-" (or by setting values in the corresponding struct +// field "Default_"). +// +// The functions in this package panic if config is not a pointer to a struct, +// or when a field is not of a suitable type (either a struct or a map with +// string keys and pointer-to-struct values). +// +// Parsing of values +// +// The section structs in the config struct may contain single-valued or +// multi-valued variables. Variables of unnamed slice type (that is, a type +// starting with `[]`) are treated as multi-value; all others (including named +// slice types) are treated as single-valued variables. +// +// Single-valued variables are handled based on the type as follows. +// Unnamed pointer types (that is, types starting with `*`) are dereferenced, +// and if necessary, a new instance is allocated. +// +// For types implementing the encoding.TextUnmarshaler interface, the +// UnmarshalText method is used to set the value. Implementing this method is +// the recommended way for parsing user-defined types. +// +// For fields of string kind, the value string is assigned to the field, after +// unquoting and unescaping as needed. +// For fields of bool kind, the field is set to true if the value is "true", +// "yes", "on" or "1", and set to false if the value is "false", "no", "off" or +// "0", ignoring case. In addition, single-valued bool fields can be specified +// with a "blank" value (variable name without equals sign and value); in such +// case the value is set to true. +// +// Predefined integer types [u]int(|8|16|32|64) and big.Int are parsed as +// decimal or hexadecimal (if having '0x' prefix). (This is to prevent +// unintuitively handling zero-padded numbers as octal.) Other types having +// [u]int* as the underlying type, such as os.FileMode and uintptr allow +// decimal, hexadecimal, or octal values. +// Parsing mode for integer types can be overridden using the struct tag option +// ",int=mode" where mode is a combination of the 'd', 'h', and 'o' characters +// (each standing for decimal, hexadecimal, and octal, respectively.) +// +// All other types are parsed using fmt.Sscanf with the "%v" verb. +// +// For multi-valued variables, each individual value is parsed as above and +// appended to the slice. If the first value is specified as a "blank" value +// (variable name without equals sign and value), a new slice is allocated; +// that is any values previously set in the slice will be ignored. +// +// The types subpackage for provides helpers for parsing "enum-like" and integer +// types. +// +// Error handling +// +// There are 3 types of errors: +// +// - programmer errors / panics: +// - invalid configuration structure +// - data errors: +// - fatal errors: +// - invalid configuration syntax +// - warnings: +// - data that doesn't belong to any part of the config structure +// +// Programmer errors trigger panics. These are should be fixed by the programmer +// before releasing code that uses gcfg. +// +// Data errors cause gcfg to return a non-nil error value. This includes the +// case when there are extra unknown key-value definitions in the configuration +// data (extra data). +// However, in some occasions it is desirable to be able to proceed in +// situations when the only data error is that of extra data. +// These errors are handled at a different (warning) priority and can be +// filtered out programmatically. To ignore extra data warnings, wrap the +// gcfg.Read*Into invocation into a call to gcfg.FatalOnly. +// +// TODO +// +// The following is a list of changes under consideration: +// - documentation +// - self-contained syntax documentation +// - more practical examples +// - move TODOs to issue tracker (eventually) +// - syntax +// - reconsider valid escape sequences +// (gitconfig doesn't support \r in value, \t in subsection name, etc.) +// - reading / parsing gcfg files +// - define internal representation structure +// - support multiple inputs (readers, strings, files) +// - support declaring encoding (?) +// - support varying fields sets for subsections (?) +// - writing gcfg files +// - error handling +// - make error context accessible programmatically? +// - limit input size? +// +package gcfg // import "gopkg.in/gcfg.v1" diff --git a/vendor/gopkg.in/gcfg.v1/errors.go b/vendor/gopkg.in/gcfg.v1/errors.go new file mode 100644 index 0000000000..83a591dac7 --- /dev/null +++ b/vendor/gopkg.in/gcfg.v1/errors.go @@ -0,0 +1,57 @@ +package gcfg + +import warnings "gopkg.in/warnings.v0" + +// FatalOnly filters the results of a Read*Into invocation and returns only +// fatal errors. That is, errors (warnings) indicating data for unknown +// sections / variables is ignored. Example invocation: +// +// err := gcfg.FatalOnly(gcfg.ReadFileInto(&cfg, configFile)) +// if err != nil { +// ... +// +func FatalOnly(err error) error { + return warnings.FatalOnly(err) +} + +func isFatal(err error) bool { + _, ok := err.(extraData) + return !ok +} + +type loc struct { + section string + subsection *string + variable *string +} + +type extraData struct { + loc +} + +type locErr struct { + msg string + loc +} + +func (l loc) String() string { + s := "section \"" + l.section + "\"" + if l.subsection != nil { + s += ", subsection \"" + *l.subsection + "\"" + } + if l.variable != nil { + s += ", variable \"" + *l.variable + "\"" + } + return s +} + +func (e extraData) Error() string { + return "can't store data at " + e.loc.String() +} + +func (e locErr) Error() string { + return e.msg + " at " + e.loc.String() +} + +var _ error = extraData{} +var _ error = locErr{} diff --git a/vendor/gopkg.in/gcfg.v1/read.go b/vendor/gopkg.in/gcfg.v1/read.go new file mode 100644 index 0000000000..06796653cc --- /dev/null +++ b/vendor/gopkg.in/gcfg.v1/read.go @@ -0,0 +1,257 @@ +package gcfg + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "strings" + + "gopkg.in/gcfg.v1/scanner" + "gopkg.in/gcfg.v1/token" + "gopkg.in/warnings.v0" +) + +var unescape = map[rune]rune{'\\': '\\', '"': '"', 'n': '\n', 't': '\t'} +var utf8Bom = []byte("\ufeff") + +// no error: invalid literals should be caught by scanner +func unquote(s string) string { + u, q, esc := make([]rune, 0, len(s)), false, false + for _, c := range s { + if esc { + uc, ok := unescape[c] + switch { + case ok: + u = append(u, uc) + fallthrough + case !q && c == '\n': + esc = false + continue + } + panic("invalid escape sequence") + } + switch c { + case '"': + q = !q + case '\\': + esc = true + default: + u = append(u, c) + } + } + if q { + panic("missing end quote") + } + if esc { + panic("invalid escape sequence") + } + return string(u) +} + +func readIntoPass(c *warnings.Collector, config interface{}, fset *token.FileSet, + file *token.File, src []byte, subsectPass bool) error { + // + var s scanner.Scanner + var errs scanner.ErrorList + s.Init(file, src, func(p token.Position, m string) { errs.Add(p, m) }, 0) + sect, sectsub := "", "" + pos, tok, lit := s.Scan() + errfn := func(msg string) error { + return fmt.Errorf("%s: %s", fset.Position(pos), msg) + } + for { + if errs.Len() > 0 { + if err := c.Collect(errs.Err()); err != nil { + return err + } + } + switch tok { + case token.EOF: + return nil + case token.EOL, token.COMMENT: + pos, tok, lit = s.Scan() + case token.LBRACK: + pos, tok, lit = s.Scan() + if errs.Len() > 0 { + if err := c.Collect(errs.Err()); err != nil { + return err + } + } + if tok != token.IDENT { + if err := c.Collect(errfn("expected section name")); err != nil { + return err + } + } + sect, sectsub = lit, "" + pos, tok, lit = s.Scan() + if errs.Len() > 0 { + if err := c.Collect(errs.Err()); err != nil { + return err + } + } + if tok == token.STRING { + sectsub = unquote(lit) + if sectsub == "" { + if err := c.Collect(errfn("empty subsection name")); err != nil { + return err + } + } + pos, tok, lit = s.Scan() + if errs.Len() > 0 { + if err := c.Collect(errs.Err()); err != nil { + return err + } + } + } + if tok != token.RBRACK { + if sectsub == "" { + if err := c.Collect(errfn("expected subsection name or right bracket")); err != nil { + return err + } + } + if err := c.Collect(errfn("expected right bracket")); err != nil { + return err + } + } + pos, tok, lit = s.Scan() + if tok != token.EOL && tok != token.EOF && tok != token.COMMENT { + if err := c.Collect(errfn("expected EOL, EOF, or comment")); err != nil { + return err + } + } + // If a section/subsection header was found, ensure a + // container object is created, even if there are no + // variables further down. + err := c.Collect(set(c, config, sect, sectsub, "", true, "", subsectPass)) + if err != nil { + return err + } + case token.IDENT: + if sect == "" { + if err := c.Collect(errfn("expected section header")); err != nil { + return err + } + } + n := lit + pos, tok, lit = s.Scan() + if errs.Len() > 0 { + return errs.Err() + } + blank, v := tok == token.EOF || tok == token.EOL || tok == token.COMMENT, "" + if !blank { + if tok != token.ASSIGN { + if err := c.Collect(errfn("expected '='")); err != nil { + return err + } + } + pos, tok, lit = s.Scan() + if errs.Len() > 0 { + if err := c.Collect(errs.Err()); err != nil { + return err + } + } + if tok != token.STRING { + if err := c.Collect(errfn("expected value")); err != nil { + return err + } + } + v = unquote(lit) + pos, tok, lit = s.Scan() + if errs.Len() > 0 { + if err := c.Collect(errs.Err()); err != nil { + return err + } + } + if tok != token.EOL && tok != token.EOF && tok != token.COMMENT { + if err := c.Collect(errfn("expected EOL, EOF, or comment")); err != nil { + return err + } + } + } + err := set(c, config, sect, sectsub, n, blank, v, subsectPass) + if err != nil { + return err + } + default: + if sect == "" { + if err := c.Collect(errfn("expected section header")); err != nil { + return err + } + } + if err := c.Collect(errfn("expected section header or variable declaration")); err != nil { + return err + } + } + } +} + +func readInto(config interface{}, fset *token.FileSet, file *token.File, + src []byte) error { + // + c := warnings.NewCollector(isFatal) + err := readIntoPass(c, config, fset, file, src, false) + if err != nil { + return err + } + err = readIntoPass(c, config, fset, file, src, true) + if err != nil { + return err + } + return c.Done() +} + +// ReadInto reads gcfg formatted data from reader and sets the values into the +// corresponding fields in config. +func ReadInto(config interface{}, reader io.Reader) error { + src, err := ioutil.ReadAll(reader) + if err != nil { + return err + } + fset := token.NewFileSet() + file := fset.AddFile("", fset.Base(), len(src)) + return readInto(config, fset, file, src) +} + +// ReadStringInto reads gcfg formatted data from str and sets the values into +// the corresponding fields in config. +func ReadStringInto(config interface{}, str string) error { + r := strings.NewReader(str) + return ReadInto(config, r) +} + +// ReadFileInto reads gcfg formatted data from the file filename and sets the +// values into the corresponding fields in config. +// +// For compatibility with files created on Windows, the ReadFileInto skips a +// single leading UTF8 BOM sequence if it exists. +func ReadFileInto(config interface{}, filename string) error { + f, err := os.Open(filename) + if err != nil { + return err + } + defer f.Close() + src, err := ioutil.ReadAll(f) + if err != nil { + return err + } + + // Skips a single leading UTF8 BOM sequence if it exists. + src = skipLeadingUtf8Bom(src) + + fset := token.NewFileSet() + file := fset.AddFile(filename, fset.Base(), len(src)) + return readInto(config, fset, file, src) +} + +func skipLeadingUtf8Bom(src []byte) []byte { + lengthUtf8Bom := len(utf8Bom) + + if len(src) >= lengthUtf8Bom { + if bytes.Equal(src[:lengthUtf8Bom], utf8Bom) { + return src[lengthUtf8Bom:] + } + } + return src +} diff --git a/vendor/gopkg.in/gcfg.v1/scanner/errors.go b/vendor/gopkg.in/gcfg.v1/scanner/errors.go new file mode 100644 index 0000000000..1a3c0f6563 --- /dev/null +++ b/vendor/gopkg.in/gcfg.v1/scanner/errors.go @@ -0,0 +1,121 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package scanner + +import ( + "fmt" + "io" + "sort" +) + +import ( + "gopkg.in/gcfg.v1/token" +) + +// In an ErrorList, an error is represented by an *Error. +// The position Pos, if valid, points to the beginning of +// the offending token, and the error condition is described +// by Msg. +// +type Error struct { + Pos token.Position + Msg string +} + +// Error implements the error interface. +func (e Error) Error() string { + if e.Pos.Filename != "" || e.Pos.IsValid() { + // don't print "" + // TODO(gri) reconsider the semantics of Position.IsValid + return e.Pos.String() + ": " + e.Msg + } + return e.Msg +} + +// ErrorList is a list of *Errors. +// The zero value for an ErrorList is an empty ErrorList ready to use. +// +type ErrorList []*Error + +// Add adds an Error with given position and error message to an ErrorList. +func (p *ErrorList) Add(pos token.Position, msg string) { + *p = append(*p, &Error{pos, msg}) +} + +// Reset resets an ErrorList to no errors. +func (p *ErrorList) Reset() { *p = (*p)[0:0] } + +// ErrorList implements the sort Interface. +func (p ErrorList) Len() int { return len(p) } +func (p ErrorList) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + +func (p ErrorList) Less(i, j int) bool { + e := &p[i].Pos + f := &p[j].Pos + if e.Filename < f.Filename { + return true + } + if e.Filename == f.Filename { + return e.Offset < f.Offset + } + return false +} + +// Sort sorts an ErrorList. *Error entries are sorted by position, +// other errors are sorted by error message, and before any *Error +// entry. +// +func (p ErrorList) Sort() { + sort.Sort(p) +} + +// RemoveMultiples sorts an ErrorList and removes all but the first error per line. +func (p *ErrorList) RemoveMultiples() { + sort.Sort(p) + var last token.Position // initial last.Line is != any legal error line + i := 0 + for _, e := range *p { + if e.Pos.Filename != last.Filename || e.Pos.Line != last.Line { + last = e.Pos + (*p)[i] = e + i++ + } + } + (*p) = (*p)[0:i] +} + +// An ErrorList implements the error interface. +func (p ErrorList) Error() string { + switch len(p) { + case 0: + return "no errors" + case 1: + return p[0].Error() + } + return fmt.Sprintf("%s (and %d more errors)", p[0], len(p)-1) +} + +// Err returns an error equivalent to this error list. +// If the list is empty, Err returns nil. +func (p ErrorList) Err() error { + if len(p) == 0 { + return nil + } + return p +} + +// PrintError is a utility function that prints a list of errors to w, +// one error per line, if the err parameter is an ErrorList. Otherwise +// it prints the err string. +// +func PrintError(w io.Writer, err error) { + if list, ok := err.(ErrorList); ok { + for _, e := range list { + fmt.Fprintf(w, "%s\n", e) + } + } else if err != nil { + fmt.Fprintf(w, "%s\n", err) + } +} diff --git a/vendor/gopkg.in/gcfg.v1/scanner/scanner.go b/vendor/gopkg.in/gcfg.v1/scanner/scanner.go new file mode 100644 index 0000000000..6d0eee916c --- /dev/null +++ b/vendor/gopkg.in/gcfg.v1/scanner/scanner.go @@ -0,0 +1,342 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package scanner implements a scanner for gcfg configuration text. +// It takes a []byte as source which can then be tokenized +// through repeated calls to the Scan method. +// +// Note that the API for the scanner package may change to accommodate new +// features or implementation changes in gcfg. +// +package scanner + +import ( + "fmt" + "path/filepath" + "unicode" + "unicode/utf8" +) + +import ( + "gopkg.in/gcfg.v1/token" +) + +// An ErrorHandler may be provided to Scanner.Init. If a syntax error is +// encountered and a handler was installed, the handler is called with a +// position and an error message. The position points to the beginning of +// the offending token. +// +type ErrorHandler func(pos token.Position, msg string) + +// A Scanner holds the scanner's internal state while processing +// a given text. It can be allocated as part of another data +// structure but must be initialized via Init before use. +// +type Scanner struct { + // immutable state + file *token.File // source file handle + dir string // directory portion of file.Name() + src []byte // source + err ErrorHandler // error reporting; or nil + mode Mode // scanning mode + + // scanning state + ch rune // current character + offset int // character offset + rdOffset int // reading offset (position after current character) + lineOffset int // current line offset + nextVal bool // next token is expected to be a value + + // public state - ok to modify + ErrorCount int // number of errors encountered +} + +// Read the next Unicode char into s.ch. +// s.ch < 0 means end-of-file. +// +func (s *Scanner) next() { + if s.rdOffset < len(s.src) { + s.offset = s.rdOffset + if s.ch == '\n' { + s.lineOffset = s.offset + s.file.AddLine(s.offset) + } + r, w := rune(s.src[s.rdOffset]), 1 + switch { + case r == 0: + s.error(s.offset, "illegal character NUL") + case r >= 0x80: + // not ASCII + r, w = utf8.DecodeRune(s.src[s.rdOffset:]) + if r == utf8.RuneError && w == 1 { + s.error(s.offset, "illegal UTF-8 encoding") + } + } + s.rdOffset += w + s.ch = r + } else { + s.offset = len(s.src) + if s.ch == '\n' { + s.lineOffset = s.offset + s.file.AddLine(s.offset) + } + s.ch = -1 // eof + } +} + +// A mode value is a set of flags (or 0). +// They control scanner behavior. +// +type Mode uint + +const ( + ScanComments Mode = 1 << iota // return comments as COMMENT tokens +) + +// Init prepares the scanner s to tokenize the text src by setting the +// scanner at the beginning of src. The scanner uses the file set file +// for position information and it adds line information for each line. +// It is ok to re-use the same file when re-scanning the same file as +// line information which is already present is ignored. Init causes a +// panic if the file size does not match the src size. +// +// Calls to Scan will invoke the error handler err if they encounter a +// syntax error and err is not nil. Also, for each error encountered, +// the Scanner field ErrorCount is incremented by one. The mode parameter +// determines how comments are handled. +// +// Note that Init may call err if there is an error in the first character +// of the file. +// +func (s *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode) { + // Explicitly initialize all fields since a scanner may be reused. + if file.Size() != len(src) { + panic(fmt.Sprintf("file size (%d) does not match src len (%d)", file.Size(), len(src))) + } + s.file = file + s.dir, _ = filepath.Split(file.Name()) + s.src = src + s.err = err + s.mode = mode + + s.ch = ' ' + s.offset = 0 + s.rdOffset = 0 + s.lineOffset = 0 + s.ErrorCount = 0 + s.nextVal = false + + s.next() +} + +func (s *Scanner) error(offs int, msg string) { + if s.err != nil { + s.err(s.file.Position(s.file.Pos(offs)), msg) + } + s.ErrorCount++ +} + +func (s *Scanner) scanComment() string { + // initial [;#] already consumed + offs := s.offset - 1 // position of initial [;#] + + for s.ch != '\n' && s.ch >= 0 { + s.next() + } + return string(s.src[offs:s.offset]) +} + +func isLetter(ch rune) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch >= 0x80 && unicode.IsLetter(ch) +} + +func isDigit(ch rune) bool { + return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch) +} + +func (s *Scanner) scanIdentifier() string { + offs := s.offset + for isLetter(s.ch) || isDigit(s.ch) || s.ch == '-' { + s.next() + } + return string(s.src[offs:s.offset]) +} + +func (s *Scanner) scanEscape(val bool) { + offs := s.offset + ch := s.ch + s.next() // always make progress + switch ch { + case '\\', '"': + // ok + case 'n', 't': + if val { + break // ok + } + fallthrough + default: + s.error(offs, "unknown escape sequence") + } +} + +func (s *Scanner) scanString() string { + // '"' opening already consumed + offs := s.offset - 1 + + for s.ch != '"' { + ch := s.ch + s.next() + if ch == '\n' || ch < 0 { + s.error(offs, "string not terminated") + break + } + if ch == '\\' { + s.scanEscape(false) + } + } + + s.next() + + return string(s.src[offs:s.offset]) +} + +func stripCR(b []byte) []byte { + c := make([]byte, len(b)) + i := 0 + for _, ch := range b { + if ch != '\r' { + c[i] = ch + i++ + } + } + return c[:i] +} + +func (s *Scanner) scanValString() string { + offs := s.offset + + hasCR := false + end := offs + inQuote := false +loop: + for inQuote || s.ch >= 0 && s.ch != '\n' && s.ch != ';' && s.ch != '#' { + ch := s.ch + s.next() + switch { + case inQuote && ch == '\\': + s.scanEscape(true) + case !inQuote && ch == '\\': + if s.ch == '\r' { + hasCR = true + s.next() + } + if s.ch != '\n' && s.ch != '"' { + s.error(offs, "unquoted '\\' must be followed by new line or double quote") + break loop + } + s.next() + case ch == '"': + inQuote = !inQuote + case ch == '\r': + hasCR = true + case ch < 0 || inQuote && ch == '\n': + s.error(offs, "string not terminated") + break loop + } + if inQuote || !isWhiteSpace(ch) { + end = s.offset + } + } + + lit := s.src[offs:end] + if hasCR { + lit = stripCR(lit) + } + + return string(lit) +} + +func isWhiteSpace(ch rune) bool { + return ch == ' ' || ch == '\t' || ch == '\r' +} + +func (s *Scanner) skipWhitespace() { + for isWhiteSpace(s.ch) { + s.next() + } +} + +// Scan scans the next token and returns the token position, the token, +// and its literal string if applicable. The source end is indicated by +// token.EOF. +// +// If the returned token is a literal (token.IDENT, token.STRING) or +// token.COMMENT, the literal string has the corresponding value. +// +// If the returned token is token.ILLEGAL, the literal string is the +// offending character. +// +// In all other cases, Scan returns an empty literal string. +// +// For more tolerant parsing, Scan will return a valid token if +// possible even if a syntax error was encountered. Thus, even +// if the resulting token sequence contains no illegal tokens, +// a client may not assume that no error occurred. Instead it +// must check the scanner's ErrorCount or the number of calls +// of the error handler, if there was one installed. +// +// Scan adds line information to the file added to the file +// set with Init. Token positions are relative to that file +// and thus relative to the file set. +// +func (s *Scanner) Scan() (pos token.Pos, tok token.Token, lit string) { +scanAgain: + s.skipWhitespace() + + // current token start + pos = s.file.Pos(s.offset) + + // determine token value + switch ch := s.ch; { + case s.nextVal: + lit = s.scanValString() + tok = token.STRING + s.nextVal = false + case isLetter(ch): + lit = s.scanIdentifier() + tok = token.IDENT + default: + s.next() // always make progress + switch ch { + case -1: + tok = token.EOF + case '\n': + tok = token.EOL + case '"': + tok = token.STRING + lit = s.scanString() + case '[': + tok = token.LBRACK + case ']': + tok = token.RBRACK + case ';', '#': + // comment + lit = s.scanComment() + if s.mode&ScanComments == 0 { + // skip comment + goto scanAgain + } + tok = token.COMMENT + case '=': + tok = token.ASSIGN + s.nextVal = true + default: + s.error(s.file.Offset(pos), fmt.Sprintf("illegal character %#U", ch)) + tok = token.ILLEGAL + lit = string(ch) + } + } + + return +} diff --git a/vendor/gopkg.in/gcfg.v1/set.go b/vendor/gopkg.in/gcfg.v1/set.go new file mode 100644 index 0000000000..73aee50035 --- /dev/null +++ b/vendor/gopkg.in/gcfg.v1/set.go @@ -0,0 +1,329 @@ +package gcfg + +import ( + "bytes" + "encoding" + "encoding/gob" + "fmt" + "math/big" + "reflect" + "strings" + "unicode" + "unicode/utf8" + + "gopkg.in/gcfg.v1/types" + "gopkg.in/warnings.v0" +) + +type tag struct { + ident string + intMode string +} + +func newTag(ts string) tag { + t := tag{} + s := strings.Split(ts, ",") + t.ident = s[0] + for _, tse := range s[1:] { + if strings.HasPrefix(tse, "int=") { + t.intMode = tse[len("int="):] + } + } + return t +} + +func fieldFold(v reflect.Value, name string) (reflect.Value, tag) { + var n string + r0, _ := utf8.DecodeRuneInString(name) + if unicode.IsLetter(r0) && !unicode.IsLower(r0) && !unicode.IsUpper(r0) { + n = "X" + } + n += strings.Replace(name, "-", "_", -1) + f, ok := v.Type().FieldByNameFunc(func(fieldName string) bool { + if !v.FieldByName(fieldName).CanSet() { + return false + } + f, _ := v.Type().FieldByName(fieldName) + t := newTag(f.Tag.Get("gcfg")) + if t.ident != "" { + return strings.EqualFold(t.ident, name) + } + return strings.EqualFold(n, fieldName) + }) + if !ok { + return reflect.Value{}, tag{} + } + return v.FieldByName(f.Name), newTag(f.Tag.Get("gcfg")) +} + +type setter func(destp interface{}, blank bool, val string, t tag) error + +var errUnsupportedType = fmt.Errorf("unsupported type") +var errBlankUnsupported = fmt.Errorf("blank value not supported for type") + +var setters = []setter{ + typeSetter, textUnmarshalerSetter, kindSetter, scanSetter, +} + +func textUnmarshalerSetter(d interface{}, blank bool, val string, t tag) error { + dtu, ok := d.(encoding.TextUnmarshaler) + if !ok { + return errUnsupportedType + } + if blank { + return errBlankUnsupported + } + return dtu.UnmarshalText([]byte(val)) +} + +func boolSetter(d interface{}, blank bool, val string, t tag) error { + if blank { + reflect.ValueOf(d).Elem().Set(reflect.ValueOf(true)) + return nil + } + b, err := types.ParseBool(val) + if err == nil { + reflect.ValueOf(d).Elem().Set(reflect.ValueOf(b)) + } + return err +} + +func intMode(mode string) types.IntMode { + var m types.IntMode + if strings.ContainsAny(mode, "dD") { + m |= types.Dec + } + if strings.ContainsAny(mode, "hH") { + m |= types.Hex + } + if strings.ContainsAny(mode, "oO") { + m |= types.Oct + } + return m +} + +var typeModes = map[reflect.Type]types.IntMode{ + reflect.TypeOf(int(0)): types.Dec | types.Hex, + reflect.TypeOf(int8(0)): types.Dec | types.Hex, + reflect.TypeOf(int16(0)): types.Dec | types.Hex, + reflect.TypeOf(int32(0)): types.Dec | types.Hex, + reflect.TypeOf(int64(0)): types.Dec | types.Hex, + reflect.TypeOf(uint(0)): types.Dec | types.Hex, + reflect.TypeOf(uint8(0)): types.Dec | types.Hex, + reflect.TypeOf(uint16(0)): types.Dec | types.Hex, + reflect.TypeOf(uint32(0)): types.Dec | types.Hex, + reflect.TypeOf(uint64(0)): types.Dec | types.Hex, + // use default mode (allow dec/hex/oct) for uintptr type + reflect.TypeOf(big.Int{}): types.Dec | types.Hex, +} + +func intModeDefault(t reflect.Type) types.IntMode { + m, ok := typeModes[t] + if !ok { + m = types.Dec | types.Hex | types.Oct + } + return m +} + +func intSetter(d interface{}, blank bool, val string, t tag) error { + if blank { + return errBlankUnsupported + } + mode := intMode(t.intMode) + if mode == 0 { + mode = intModeDefault(reflect.TypeOf(d).Elem()) + } + return types.ParseInt(d, val, mode) +} + +func stringSetter(d interface{}, blank bool, val string, t tag) error { + if blank { + return errBlankUnsupported + } + dsp, ok := d.(*string) + if !ok { + return errUnsupportedType + } + *dsp = val + return nil +} + +var kindSetters = map[reflect.Kind]setter{ + reflect.String: stringSetter, + reflect.Bool: boolSetter, + reflect.Int: intSetter, + reflect.Int8: intSetter, + reflect.Int16: intSetter, + reflect.Int32: intSetter, + reflect.Int64: intSetter, + reflect.Uint: intSetter, + reflect.Uint8: intSetter, + reflect.Uint16: intSetter, + reflect.Uint32: intSetter, + reflect.Uint64: intSetter, + reflect.Uintptr: intSetter, +} + +var typeSetters = map[reflect.Type]setter{ + reflect.TypeOf(big.Int{}): intSetter, +} + +func typeSetter(d interface{}, blank bool, val string, tt tag) error { + t := reflect.ValueOf(d).Type().Elem() + setter, ok := typeSetters[t] + if !ok { + return errUnsupportedType + } + return setter(d, blank, val, tt) +} + +func kindSetter(d interface{}, blank bool, val string, tt tag) error { + k := reflect.ValueOf(d).Type().Elem().Kind() + setter, ok := kindSetters[k] + if !ok { + return errUnsupportedType + } + return setter(d, blank, val, tt) +} + +func scanSetter(d interface{}, blank bool, val string, tt tag) error { + if blank { + return errBlankUnsupported + } + return types.ScanFully(d, val, 'v') +} + +func newValue(c *warnings.Collector, sect string, vCfg reflect.Value, + vType reflect.Type) (reflect.Value, error) { + // + pv := reflect.New(vType) + dfltName := "default-" + sect + dfltField, _ := fieldFold(vCfg, dfltName) + var err error + if dfltField.IsValid() { + b := bytes.NewBuffer(nil) + ge := gob.NewEncoder(b) + if err = c.Collect(ge.EncodeValue(dfltField)); err != nil { + return pv, err + } + gd := gob.NewDecoder(bytes.NewReader(b.Bytes())) + if err = c.Collect(gd.DecodeValue(pv.Elem())); err != nil { + return pv, err + } + } + return pv, nil +} + +func set(c *warnings.Collector, cfg interface{}, sect, sub, name string, + blank bool, value string, subsectPass bool) error { + // + vPCfg := reflect.ValueOf(cfg) + if vPCfg.Kind() != reflect.Ptr || vPCfg.Elem().Kind() != reflect.Struct { + panic(fmt.Errorf("config must be a pointer to a struct")) + } + vCfg := vPCfg.Elem() + vSect, _ := fieldFold(vCfg, sect) + l := loc{section: sect} + if !vSect.IsValid() { + err := extraData{loc: l} + return c.Collect(err) + } + isSubsect := vSect.Kind() == reflect.Map + if subsectPass != isSubsect { + return nil + } + if isSubsect { + l.subsection = &sub + vst := vSect.Type() + if vst.Key().Kind() != reflect.String || + vst.Elem().Kind() != reflect.Ptr || + vst.Elem().Elem().Kind() != reflect.Struct { + panic(fmt.Errorf("map field for section must have string keys and "+ + " pointer-to-struct values: section %q", sect)) + } + if vSect.IsNil() { + vSect.Set(reflect.MakeMap(vst)) + } + k := reflect.ValueOf(sub) + pv := vSect.MapIndex(k) + if !pv.IsValid() { + vType := vSect.Type().Elem().Elem() + var err error + if pv, err = newValue(c, sect, vCfg, vType); err != nil { + return err + } + vSect.SetMapIndex(k, pv) + } + vSect = pv.Elem() + } else if vSect.Kind() != reflect.Struct { + panic(fmt.Errorf("field for section must be a map or a struct: "+ + "section %q", sect)) + } else if sub != "" { + return c.Collect(extraData{loc: l}) + } + // Empty name is a special value, meaning that only the + // section/subsection object is to be created, with no values set. + if name == "" { + return nil + } + vVar, t := fieldFold(vSect, name) + l.variable = &name + if !vVar.IsValid() { + return c.Collect(extraData{loc: l}) + } + // vVal is either single-valued var, or newly allocated value within multi-valued var + var vVal reflect.Value + // multi-value if unnamed slice type + isMulti := vVar.Type().Name() == "" && vVar.Kind() == reflect.Slice || + vVar.Type().Name() == "" && vVar.Kind() == reflect.Ptr && vVar.Type().Elem().Name() == "" && vVar.Type().Elem().Kind() == reflect.Slice + if isMulti && vVar.Kind() == reflect.Ptr { + if vVar.IsNil() { + vVar.Set(reflect.New(vVar.Type().Elem())) + } + vVar = vVar.Elem() + } + if isMulti && blank { + vVar.Set(reflect.Zero(vVar.Type())) + return nil + } + if isMulti { + vVal = reflect.New(vVar.Type().Elem()).Elem() + } else { + vVal = vVar + } + isDeref := vVal.Type().Name() == "" && vVal.Type().Kind() == reflect.Ptr + isNew := isDeref && vVal.IsNil() + // vAddr is address of value to set (dereferenced & allocated as needed) + var vAddr reflect.Value + switch { + case isNew: + vAddr = reflect.New(vVal.Type().Elem()) + case isDeref && !isNew: + vAddr = vVal + default: + vAddr = vVal.Addr() + } + vAddrI := vAddr.Interface() + err, ok := error(nil), false + for _, s := range setters { + err = s(vAddrI, blank, value, t) + if err == nil { + ok = true + break + } + if err != errUnsupportedType { + return locErr{msg: err.Error(), loc: l} + } + } + if !ok { + // in case all setters returned errUnsupportedType + return locErr{msg: err.Error(), loc: l} + } + if isNew { // set reference if it was dereferenced and newly allocated + vVal.Set(vAddr) + } + if isMulti { // append if multi-valued + vVar.Set(reflect.Append(vVar, vVal)) + } + return nil +} diff --git a/vendor/gopkg.in/gcfg.v1/token/position.go b/vendor/gopkg.in/gcfg.v1/token/position.go new file mode 100644 index 0000000000..fc45c1e769 --- /dev/null +++ b/vendor/gopkg.in/gcfg.v1/token/position.go @@ -0,0 +1,435 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// TODO(gri) consider making this a separate package outside the go directory. + +package token + +import ( + "fmt" + "sort" + "sync" +) + +// ----------------------------------------------------------------------------- +// Positions + +// Position describes an arbitrary source position +// including the file, line, and column location. +// A Position is valid if the line number is > 0. +// +type Position struct { + Filename string // filename, if any + Offset int // offset, starting at 0 + Line int // line number, starting at 1 + Column int // column number, starting at 1 (character count) +} + +// IsValid returns true if the position is valid. +func (pos *Position) IsValid() bool { return pos.Line > 0 } + +// String returns a string in one of several forms: +// +// file:line:column valid position with file name +// line:column valid position without file name +// file invalid position with file name +// - invalid position without file name +// +func (pos Position) String() string { + s := pos.Filename + if pos.IsValid() { + if s != "" { + s += ":" + } + s += fmt.Sprintf("%d:%d", pos.Line, pos.Column) + } + if s == "" { + s = "-" + } + return s +} + +// Pos is a compact encoding of a source position within a file set. +// It can be converted into a Position for a more convenient, but much +// larger, representation. +// +// The Pos value for a given file is a number in the range [base, base+size], +// where base and size are specified when adding the file to the file set via +// AddFile. +// +// To create the Pos value for a specific source offset, first add +// the respective file to the current file set (via FileSet.AddFile) +// and then call File.Pos(offset) for that file. Given a Pos value p +// for a specific file set fset, the corresponding Position value is +// obtained by calling fset.Position(p). +// +// Pos values can be compared directly with the usual comparison operators: +// If two Pos values p and q are in the same file, comparing p and q is +// equivalent to comparing the respective source file offsets. If p and q +// are in different files, p < q is true if the file implied by p was added +// to the respective file set before the file implied by q. +// +type Pos int + +// The zero value for Pos is NoPos; there is no file and line information +// associated with it, and NoPos().IsValid() is false. NoPos is always +// smaller than any other Pos value. The corresponding Position value +// for NoPos is the zero value for Position. +// +const NoPos Pos = 0 + +// IsValid returns true if the position is valid. +func (p Pos) IsValid() bool { + return p != NoPos +} + +// ----------------------------------------------------------------------------- +// File + +// A File is a handle for a file belonging to a FileSet. +// A File has a name, size, and line offset table. +// +type File struct { + set *FileSet + name string // file name as provided to AddFile + base int // Pos value range for this file is [base...base+size] + size int // file size as provided to AddFile + + // lines and infos are protected by set.mutex + lines []int + infos []lineInfo +} + +// Name returns the file name of file f as registered with AddFile. +func (f *File) Name() string { + return f.name +} + +// Base returns the base offset of file f as registered with AddFile. +func (f *File) Base() int { + return f.base +} + +// Size returns the size of file f as registered with AddFile. +func (f *File) Size() int { + return f.size +} + +// LineCount returns the number of lines in file f. +func (f *File) LineCount() int { + f.set.mutex.RLock() + n := len(f.lines) + f.set.mutex.RUnlock() + return n +} + +// AddLine adds the line offset for a new line. +// The line offset must be larger than the offset for the previous line +// and smaller than the file size; otherwise the line offset is ignored. +// +func (f *File) AddLine(offset int) { + f.set.mutex.Lock() + if i := len(f.lines); (i == 0 || f.lines[i-1] < offset) && offset < f.size { + f.lines = append(f.lines, offset) + } + f.set.mutex.Unlock() +} + +// SetLines sets the line offsets for a file and returns true if successful. +// The line offsets are the offsets of the first character of each line; +// for instance for the content "ab\nc\n" the line offsets are {0, 3}. +// An empty file has an empty line offset table. +// Each line offset must be larger than the offset for the previous line +// and smaller than the file size; otherwise SetLines fails and returns +// false. +// +func (f *File) SetLines(lines []int) bool { + // verify validity of lines table + size := f.size + for i, offset := range lines { + if i > 0 && offset <= lines[i-1] || size <= offset { + return false + } + } + + // set lines table + f.set.mutex.Lock() + f.lines = lines + f.set.mutex.Unlock() + return true +} + +// SetLinesForContent sets the line offsets for the given file content. +func (f *File) SetLinesForContent(content []byte) { + var lines []int + line := 0 + for offset, b := range content { + if line >= 0 { + lines = append(lines, line) + } + line = -1 + if b == '\n' { + line = offset + 1 + } + } + + // set lines table + f.set.mutex.Lock() + f.lines = lines + f.set.mutex.Unlock() +} + +// A lineInfo object describes alternative file and line number +// information (such as provided via a //line comment in a .go +// file) for a given file offset. +type lineInfo struct { + // fields are exported to make them accessible to gob + Offset int + Filename string + Line int +} + +// AddLineInfo adds alternative file and line number information for +// a given file offset. The offset must be larger than the offset for +// the previously added alternative line info and smaller than the +// file size; otherwise the information is ignored. +// +// AddLineInfo is typically used to register alternative position +// information for //line filename:line comments in source files. +// +func (f *File) AddLineInfo(offset int, filename string, line int) { + f.set.mutex.Lock() + if i := len(f.infos); i == 0 || f.infos[i-1].Offset < offset && offset < f.size { + f.infos = append(f.infos, lineInfo{offset, filename, line}) + } + f.set.mutex.Unlock() +} + +// Pos returns the Pos value for the given file offset; +// the offset must be <= f.Size(). +// f.Pos(f.Offset(p)) == p. +// +func (f *File) Pos(offset int) Pos { + if offset > f.size { + panic("illegal file offset") + } + return Pos(f.base + offset) +} + +// Offset returns the offset for the given file position p; +// p must be a valid Pos value in that file. +// f.Offset(f.Pos(offset)) == offset. +// +func (f *File) Offset(p Pos) int { + if int(p) < f.base || int(p) > f.base+f.size { + panic("illegal Pos value") + } + return int(p) - f.base +} + +// Line returns the line number for the given file position p; +// p must be a Pos value in that file or NoPos. +// +func (f *File) Line(p Pos) int { + // TODO(gri) this can be implemented much more efficiently + return f.Position(p).Line +} + +func searchLineInfos(a []lineInfo, x int) int { + return sort.Search(len(a), func(i int) bool { return a[i].Offset > x }) - 1 +} + +// info returns the file name, line, and column number for a file offset. +func (f *File) info(offset int) (filename string, line, column int) { + filename = f.name + if i := searchInts(f.lines, offset); i >= 0 { + line, column = i+1, offset-f.lines[i]+1 + } + if len(f.infos) > 0 { + // almost no files have extra line infos + if i := searchLineInfos(f.infos, offset); i >= 0 { + alt := &f.infos[i] + filename = alt.Filename + if i := searchInts(f.lines, alt.Offset); i >= 0 { + line += alt.Line - i - 1 + } + } + } + return +} + +func (f *File) position(p Pos) (pos Position) { + offset := int(p) - f.base + pos.Offset = offset + pos.Filename, pos.Line, pos.Column = f.info(offset) + return +} + +// Position returns the Position value for the given file position p; +// p must be a Pos value in that file or NoPos. +// +func (f *File) Position(p Pos) (pos Position) { + if p != NoPos { + if int(p) < f.base || int(p) > f.base+f.size { + panic("illegal Pos value") + } + pos = f.position(p) + } + return +} + +// ----------------------------------------------------------------------------- +// FileSet + +// A FileSet represents a set of source files. +// Methods of file sets are synchronized; multiple goroutines +// may invoke them concurrently. +// +type FileSet struct { + mutex sync.RWMutex // protects the file set + base int // base offset for the next file + files []*File // list of files in the order added to the set + last *File // cache of last file looked up +} + +// NewFileSet creates a new file set. +func NewFileSet() *FileSet { + s := new(FileSet) + s.base = 1 // 0 == NoPos + return s +} + +// Base returns the minimum base offset that must be provided to +// AddFile when adding the next file. +// +func (s *FileSet) Base() int { + s.mutex.RLock() + b := s.base + s.mutex.RUnlock() + return b + +} + +// AddFile adds a new file with a given filename, base offset, and file size +// to the file set s and returns the file. Multiple files may have the same +// name. The base offset must not be smaller than the FileSet's Base(), and +// size must not be negative. +// +// Adding the file will set the file set's Base() value to base + size + 1 +// as the minimum base value for the next file. The following relationship +// exists between a Pos value p for a given file offset offs: +// +// int(p) = base + offs +// +// with offs in the range [0, size] and thus p in the range [base, base+size]. +// For convenience, File.Pos may be used to create file-specific position +// values from a file offset. +// +func (s *FileSet) AddFile(filename string, base, size int) *File { + s.mutex.Lock() + defer s.mutex.Unlock() + if base < s.base || size < 0 { + panic("illegal base or size") + } + // base >= s.base && size >= 0 + f := &File{s, filename, base, size, []int{0}, nil} + base += size + 1 // +1 because EOF also has a position + if base < 0 { + panic("token.Pos offset overflow (> 2G of source code in file set)") + } + // add the file to the file set + s.base = base + s.files = append(s.files, f) + s.last = f + return f +} + +// Iterate calls f for the files in the file set in the order they were added +// until f returns false. +// +func (s *FileSet) Iterate(f func(*File) bool) { + for i := 0; ; i++ { + var file *File + s.mutex.RLock() + if i < len(s.files) { + file = s.files[i] + } + s.mutex.RUnlock() + if file == nil || !f(file) { + break + } + } +} + +func searchFiles(a []*File, x int) int { + return sort.Search(len(a), func(i int) bool { return a[i].base > x }) - 1 +} + +func (s *FileSet) file(p Pos) *File { + // common case: p is in last file + if f := s.last; f != nil && f.base <= int(p) && int(p) <= f.base+f.size { + return f + } + // p is not in last file - search all files + if i := searchFiles(s.files, int(p)); i >= 0 { + f := s.files[i] + // f.base <= int(p) by definition of searchFiles + if int(p) <= f.base+f.size { + s.last = f + return f + } + } + return nil +} + +// File returns the file that contains the position p. +// If no such file is found (for instance for p == NoPos), +// the result is nil. +// +func (s *FileSet) File(p Pos) (f *File) { + if p != NoPos { + s.mutex.RLock() + f = s.file(p) + s.mutex.RUnlock() + } + return +} + +// Position converts a Pos in the fileset into a general Position. +func (s *FileSet) Position(p Pos) (pos Position) { + if p != NoPos { + s.mutex.RLock() + if f := s.file(p); f != nil { + pos = f.position(p) + } + s.mutex.RUnlock() + } + return +} + +// ----------------------------------------------------------------------------- +// Helper functions + +func searchInts(a []int, x int) int { + // This function body is a manually inlined version of: + // + // return sort.Search(len(a), func(i int) bool { return a[i] > x }) - 1 + // + // With better compiler optimizations, this may not be needed in the + // future, but at the moment this change improves the go/printer + // benchmark performance by ~30%. This has a direct impact on the + // speed of gofmt and thus seems worthwhile (2011-04-29). + // TODO(gri): Remove this when compilers have caught up. + i, j := 0, len(a) + for i < j { + h := i + (j-i)/2 // avoid overflow when computing h + // i ≤ h < j + if a[h] <= x { + i = h + 1 + } else { + j = h + } + } + return i - 1 +} diff --git a/vendor/gopkg.in/gcfg.v1/token/serialize.go b/vendor/gopkg.in/gcfg.v1/token/serialize.go new file mode 100644 index 0000000000..4adc8f9e33 --- /dev/null +++ b/vendor/gopkg.in/gcfg.v1/token/serialize.go @@ -0,0 +1,56 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package token + +type serializedFile struct { + // fields correspond 1:1 to fields with same (lower-case) name in File + Name string + Base int + Size int + Lines []int + Infos []lineInfo +} + +type serializedFileSet struct { + Base int + Files []serializedFile +} + +// Read calls decode to deserialize a file set into s; s must not be nil. +func (s *FileSet) Read(decode func(interface{}) error) error { + var ss serializedFileSet + if err := decode(&ss); err != nil { + return err + } + + s.mutex.Lock() + s.base = ss.Base + files := make([]*File, len(ss.Files)) + for i := 0; i < len(ss.Files); i++ { + f := &ss.Files[i] + files[i] = &File{s, f.Name, f.Base, f.Size, f.Lines, f.Infos} + } + s.files = files + s.last = nil + s.mutex.Unlock() + + return nil +} + +// Write calls encode to serialize the file set s. +func (s *FileSet) Write(encode func(interface{}) error) error { + var ss serializedFileSet + + s.mutex.Lock() + ss.Base = s.base + files := make([]serializedFile, len(s.files)) + for i, f := range s.files { + files[i] = serializedFile{f.name, f.base, f.size, f.lines, f.infos} + } + ss.Files = files + s.mutex.Unlock() + + return encode(ss) +} diff --git a/vendor/gopkg.in/gcfg.v1/token/token.go b/vendor/gopkg.in/gcfg.v1/token/token.go new file mode 100644 index 0000000000..b3c7c83fa9 --- /dev/null +++ b/vendor/gopkg.in/gcfg.v1/token/token.go @@ -0,0 +1,83 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package token defines constants representing the lexical tokens of the gcfg +// configuration syntax and basic operations on tokens (printing, predicates). +// +// Note that the API for the token package may change to accommodate new +// features or implementation changes in gcfg. +// +package token + +import "strconv" + +// Token is the set of lexical tokens of the gcfg configuration syntax. +type Token int + +// The list of tokens. +const ( + // Special tokens + ILLEGAL Token = iota + EOF + COMMENT + + literal_beg + // Identifiers and basic type literals + // (these tokens stand for classes of literals) + IDENT // section-name, variable-name + STRING // "subsection-name", variable value + literal_end + + operator_beg + // Operators and delimiters + ASSIGN // = + LBRACK // [ + RBRACK // ] + EOL // \n + operator_end +) + +var tokens = [...]string{ + ILLEGAL: "ILLEGAL", + + EOF: "EOF", + COMMENT: "COMMENT", + + IDENT: "IDENT", + STRING: "STRING", + + ASSIGN: "=", + LBRACK: "[", + RBRACK: "]", + EOL: "\n", +} + +// String returns the string corresponding to the token tok. +// For operators and delimiters, the string is the actual token character +// sequence (e.g., for the token ASSIGN, the string is "="). For all other +// tokens the string corresponds to the token constant name (e.g. for the +// token IDENT, the string is "IDENT"). +// +func (tok Token) String() string { + s := "" + if 0 <= tok && tok < Token(len(tokens)) { + s = tokens[tok] + } + if s == "" { + s = "token(" + strconv.Itoa(int(tok)) + ")" + } + return s +} + +// Predicates + +// IsLiteral returns true for tokens corresponding to identifiers +// and basic type literals; it returns false otherwise. +// +func (tok Token) IsLiteral() bool { return literal_beg < tok && tok < literal_end } + +// IsOperator returns true for tokens corresponding to operators and +// delimiters; it returns false otherwise. +// +func (tok Token) IsOperator() bool { return operator_beg < tok && tok < operator_end } diff --git a/vendor/gopkg.in/gcfg.v1/types/bool.go b/vendor/gopkg.in/gcfg.v1/types/bool.go new file mode 100644 index 0000000000..8dcae0d8cf --- /dev/null +++ b/vendor/gopkg.in/gcfg.v1/types/bool.go @@ -0,0 +1,23 @@ +package types + +// BoolValues defines the name and value mappings for ParseBool. +var BoolValues = map[string]interface{}{ + "true": true, "yes": true, "on": true, "1": true, + "false": false, "no": false, "off": false, "0": false, +} + +var boolParser = func() *EnumParser { + ep := &EnumParser{} + ep.AddVals(BoolValues) + return ep +}() + +// ParseBool parses bool values according to the definitions in BoolValues. +// Parsing is case-insensitive. +func ParseBool(s string) (bool, error) { + v, err := boolParser.Parse(s) + if err != nil { + return false, err + } + return v.(bool), nil +} diff --git a/vendor/gopkg.in/gcfg.v1/types/doc.go b/vendor/gopkg.in/gcfg.v1/types/doc.go new file mode 100644 index 0000000000..9f9c345f6e --- /dev/null +++ b/vendor/gopkg.in/gcfg.v1/types/doc.go @@ -0,0 +1,4 @@ +// Package types defines helpers for type conversions. +// +// The API for this package is not finalized yet. +package types diff --git a/vendor/gopkg.in/gcfg.v1/types/enum.go b/vendor/gopkg.in/gcfg.v1/types/enum.go new file mode 100644 index 0000000000..1a0c7ef453 --- /dev/null +++ b/vendor/gopkg.in/gcfg.v1/types/enum.go @@ -0,0 +1,44 @@ +package types + +import ( + "fmt" + "reflect" + "strings" +) + +// EnumParser parses "enum" values; i.e. a predefined set of strings to +// predefined values. +type EnumParser struct { + Type string // type name; if not set, use type of first value added + CaseMatch bool // if true, matching of strings is case-sensitive + // PrefixMatch bool + vals map[string]interface{} +} + +// AddVals adds strings and values to an EnumParser. +func (ep *EnumParser) AddVals(vals map[string]interface{}) { + if ep.vals == nil { + ep.vals = make(map[string]interface{}) + } + for k, v := range vals { + if ep.Type == "" { + ep.Type = reflect.TypeOf(v).Name() + } + if !ep.CaseMatch { + k = strings.ToLower(k) + } + ep.vals[k] = v + } +} + +// Parse parses the string and returns the value or an error. +func (ep EnumParser) Parse(s string) (interface{}, error) { + if !ep.CaseMatch { + s = strings.ToLower(s) + } + v, ok := ep.vals[s] + if !ok { + return false, fmt.Errorf("failed to parse %s %#q", ep.Type, s) + } + return v, nil +} diff --git a/vendor/gopkg.in/gcfg.v1/types/int.go b/vendor/gopkg.in/gcfg.v1/types/int.go new file mode 100644 index 0000000000..af7e75c125 --- /dev/null +++ b/vendor/gopkg.in/gcfg.v1/types/int.go @@ -0,0 +1,86 @@ +package types + +import ( + "fmt" + "strings" +) + +// An IntMode is a mode for parsing integer values, representing a set of +// accepted bases. +type IntMode uint8 + +// IntMode values for ParseInt; can be combined using binary or. +const ( + Dec IntMode = 1 << iota + Hex + Oct +) + +// String returns a string representation of IntMode; e.g. `IntMode(Dec|Hex)`. +func (m IntMode) String() string { + var modes []string + if m&Dec != 0 { + modes = append(modes, "Dec") + } + if m&Hex != 0 { + modes = append(modes, "Hex") + } + if m&Oct != 0 { + modes = append(modes, "Oct") + } + return "IntMode(" + strings.Join(modes, "|") + ")" +} + +var errIntAmbig = fmt.Errorf("ambiguous integer value; must include '0' prefix") + +func prefix0(val string) bool { + return strings.HasPrefix(val, "0") || strings.HasPrefix(val, "-0") +} + +func prefix0x(val string) bool { + return strings.HasPrefix(val, "0x") || strings.HasPrefix(val, "-0x") +} + +// ParseInt parses val using mode into intptr, which must be a pointer to an +// integer kind type. Non-decimal value require prefix `0` or `0x` in the cases +// when mode permits ambiguity of base; otherwise the prefix can be omitted. +func ParseInt(intptr interface{}, val string, mode IntMode) error { + val = strings.TrimSpace(val) + verb := byte(0) + switch mode { + case Dec: + verb = 'd' + case Dec + Hex: + if prefix0x(val) { + verb = 'v' + } else { + verb = 'd' + } + case Dec + Oct: + if prefix0(val) && !prefix0x(val) { + verb = 'v' + } else { + verb = 'd' + } + case Dec + Hex + Oct: + verb = 'v' + case Hex: + if prefix0x(val) { + verb = 'v' + } else { + verb = 'x' + } + case Oct: + verb = 'o' + case Hex + Oct: + if prefix0(val) { + verb = 'v' + } else { + return errIntAmbig + } + } + if verb == 0 { + panic("unsupported mode") + } + return ScanFully(intptr, val, verb) +} diff --git a/vendor/gopkg.in/gcfg.v1/types/scan.go b/vendor/gopkg.in/gcfg.v1/types/scan.go new file mode 100644 index 0000000000..db2f6ed3ca --- /dev/null +++ b/vendor/gopkg.in/gcfg.v1/types/scan.go @@ -0,0 +1,23 @@ +package types + +import ( + "fmt" + "io" + "reflect" +) + +// ScanFully uses fmt.Sscanf with verb to fully scan val into ptr. +func ScanFully(ptr interface{}, val string, verb byte) error { + t := reflect.ValueOf(ptr).Elem().Type() + // attempt to read extra bytes to make sure the value is consumed + var b []byte + n, err := fmt.Sscanf(val, "%"+string(verb)+"%s", ptr, &b) + switch { + case n < 1 || n == 1 && err != io.EOF: + return fmt.Errorf("failed to parse %q as %v: %v", val, t, err) + case n > 1: + return fmt.Errorf("failed to parse %q as %v: extra characters %q", val, t, string(b)) + } + // n == 1 && err == io.EOF + return nil +} diff --git a/vendor/gopkg.in/warnings.v0/LICENSE b/vendor/gopkg.in/warnings.v0/LICENSE new file mode 100644 index 0000000000..d65f7e9d8c --- /dev/null +++ b/vendor/gopkg.in/warnings.v0/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2016 Péter Surányi. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/gopkg.in/warnings.v0/README b/vendor/gopkg.in/warnings.v0/README new file mode 100644 index 0000000000..974212ba1b --- /dev/null +++ b/vendor/gopkg.in/warnings.v0/README @@ -0,0 +1,77 @@ +Package warnings implements error handling with non-fatal errors (warnings). + +import path: "gopkg.in/warnings.v0" +package docs: https://godoc.org/gopkg.in/warnings.v0 +issues: https://github.com/go-warnings/warnings/issues +pull requests: https://github.com/go-warnings/warnings/pulls + +A recurring pattern in Go programming is the following: + + func myfunc(params) error { + if err := doSomething(...); err != nil { + return err + } + if err := doSomethingElse(...); err != nil { + return err + } + if ok := doAnotherThing(...); !ok { + return errors.New("my error") + } + ... + return nil + } + +This pattern allows interrupting the flow on any received error. But what if +there are errors that should be noted but still not fatal, for which the flow +should not be interrupted? Implementing such logic at each if statement would +make the code complex and the flow much harder to follow. + +Package warnings provides the Collector type and a clean and simple pattern +for achieving such logic. The Collector takes care of deciding when to break +the flow and when to continue, collecting any non-fatal errors (warnings) +along the way. The only requirement is that fatal and non-fatal errors can be +distinguished programmatically; that is a function such as + + IsFatal(error) bool + +must be implemented. The following is an example of what the above snippet +could look like using the warnings package: + + import "gopkg.in/warnings.v0" + + func isFatal(err error) bool { + _, ok := err.(WarningType) + return !ok + } + + func myfunc(params) error { + c := warnings.NewCollector(isFatal) + c.FatalWithWarnings = true + if err := c.Collect(doSomething()); err != nil { + return err + } + if err := c.Collect(doSomethingElse(...)); err != nil { + return err + } + if ok := doAnotherThing(...); !ok { + if err := c.Collect(errors.New("my error")); err != nil { + return err + } + } + ... + return c.Done() + } + +For an example of a non-trivial code base using this library, see +gopkg.in/gcfg.v1 + +Rules for using warnings + + - ensure that warnings are programmatically distinguishable from fatal + errors (i.e. implement an isFatal function and any necessary error types) + - ensure that there is a single Collector instance for a call of each + exported function + - ensure that all errors (fatal or warning) are fed through Collect + - ensure that every time an error is returned, it is one returned by a + Collector (from Collect or Done) + - ensure that Collect is never called after Done diff --git a/vendor/gopkg.in/warnings.v0/warnings.go b/vendor/gopkg.in/warnings.v0/warnings.go new file mode 100644 index 0000000000..b849d1e3d9 --- /dev/null +++ b/vendor/gopkg.in/warnings.v0/warnings.go @@ -0,0 +1,194 @@ +// Package warnings implements error handling with non-fatal errors (warnings). +// +// A recurring pattern in Go programming is the following: +// +// func myfunc(params) error { +// if err := doSomething(...); err != nil { +// return err +// } +// if err := doSomethingElse(...); err != nil { +// return err +// } +// if ok := doAnotherThing(...); !ok { +// return errors.New("my error") +// } +// ... +// return nil +// } +// +// This pattern allows interrupting the flow on any received error. But what if +// there are errors that should be noted but still not fatal, for which the flow +// should not be interrupted? Implementing such logic at each if statement would +// make the code complex and the flow much harder to follow. +// +// Package warnings provides the Collector type and a clean and simple pattern +// for achieving such logic. The Collector takes care of deciding when to break +// the flow and when to continue, collecting any non-fatal errors (warnings) +// along the way. The only requirement is that fatal and non-fatal errors can be +// distinguished programmatically; that is a function such as +// +// IsFatal(error) bool +// +// must be implemented. The following is an example of what the above snippet +// could look like using the warnings package: +// +// import "gopkg.in/warnings.v0" +// +// func isFatal(err error) bool { +// _, ok := err.(WarningType) +// return !ok +// } +// +// func myfunc(params) error { +// c := warnings.NewCollector(isFatal) +// c.FatalWithWarnings = true +// if err := c.Collect(doSomething()); err != nil { +// return err +// } +// if err := c.Collect(doSomethingElse(...)); err != nil { +// return err +// } +// if ok := doAnotherThing(...); !ok { +// if err := c.Collect(errors.New("my error")); err != nil { +// return err +// } +// } +// ... +// return c.Done() +// } +// +// For an example of a non-trivial code base using this library, see +// gopkg.in/gcfg.v1 +// +// Rules for using warnings +// +// - ensure that warnings are programmatically distinguishable from fatal +// errors (i.e. implement an isFatal function and any necessary error types) +// - ensure that there is a single Collector instance for a call of each +// exported function +// - ensure that all errors (fatal or warning) are fed through Collect +// - ensure that every time an error is returned, it is one returned by a +// Collector (from Collect or Done) +// - ensure that Collect is never called after Done +// +// TODO +// +// - optionally limit the number of warnings (e.g. stop after 20 warnings) (?) +// - consider interaction with contexts +// - go vet-style invocations verifier +// - semi-automatic code converter +// +package warnings // import "gopkg.in/warnings.v0" + +import ( + "bytes" + "fmt" +) + +// List holds a collection of warnings and optionally one fatal error. +type List struct { + Warnings []error + Fatal error +} + +// Error implements the error interface. +func (l List) Error() string { + b := bytes.NewBuffer(nil) + if l.Fatal != nil { + fmt.Fprintln(b, "fatal:") + fmt.Fprintln(b, l.Fatal) + } + switch len(l.Warnings) { + case 0: + // nop + case 1: + fmt.Fprintln(b, "warning:") + default: + fmt.Fprintln(b, "warnings:") + } + for _, err := range l.Warnings { + fmt.Fprintln(b, err) + } + return b.String() +} + +// A Collector collects errors up to the first fatal error. +type Collector struct { + // IsFatal distinguishes between warnings and fatal errors. + IsFatal func(error) bool + // FatalWithWarnings set to true means that a fatal error is returned as + // a List together with all warnings so far. The default behavior is to + // only return the fatal error and discard any warnings that have been + // collected. + FatalWithWarnings bool + + l List + done bool +} + +// NewCollector returns a new Collector; it uses isFatal to distinguish between +// warnings and fatal errors. +func NewCollector(isFatal func(error) bool) *Collector { + return &Collector{IsFatal: isFatal} +} + +// Collect collects a single error (warning or fatal). It returns nil if +// collection can continue (only warnings so far), or otherwise the errors +// collected. Collect mustn't be called after the first fatal error or after +// Done has been called. +func (c *Collector) Collect(err error) error { + if c.done { + panic("warnings.Collector already done") + } + if err == nil { + return nil + } + if c.IsFatal(err) { + c.done = true + c.l.Fatal = err + } else { + c.l.Warnings = append(c.l.Warnings, err) + } + if c.l.Fatal != nil { + return c.erorr() + } + return nil +} + +// Done ends collection and returns the collected error(s). +func (c *Collector) Done() error { + c.done = true + return c.erorr() +} + +func (c *Collector) erorr() error { + if !c.FatalWithWarnings && c.l.Fatal != nil { + return c.l.Fatal + } + if c.l.Fatal == nil && len(c.l.Warnings) == 0 { + return nil + } + // Note that a single warning is also returned as a List. This is to make it + // easier to determine fatal-ness of the returned error. + return c.l +} + +// FatalOnly returns the fatal error, if any, **in an error returned by a +// Collector**. It returns nil if and only if err is nil or err is a List +// with err.Fatal == nil. +func FatalOnly(err error) error { + l, ok := err.(List) + if !ok { + return err + } + return l.Fatal +} + +// WarningsOnly returns the warnings **in an error returned by a Collector**. +func WarningsOnly(err error) []error { + l, ok := err.(List) + if !ok { + return nil + } + return l.Warnings +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 2a80176870..ae12ed72ba 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -252,7 +252,7 @@ golang.org/x/text/internal/language/compact golang.org/x/text/internal/tag # golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 golang.org/x/time/rate -# golang.org/x/tools v0.0.0-20190312170243-e65039ee4138 +# golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 golang.org/x/tools/cmd/goimports golang.org/x/tools/imports golang.org/x/tools/go/ast/astutil @@ -261,7 +261,6 @@ golang.org/x/tools/go/packages golang.org/x/tools/internal/gopathwalk golang.org/x/tools/internal/module golang.org/x/tools/go/internal/gcimporter -golang.org/x/tools/go/internal/cgo golang.org/x/tools/go/internal/packagesdriver golang.org/x/tools/internal/semver golang.org/x/tools/internal/fastwalk @@ -314,10 +313,17 @@ google.golang.org/grpc/binarylog/grpc_binarylog_v1 google.golang.org/grpc/internal/syscall # gopkg.in/fsnotify.v1 v1.4.7 gopkg.in/fsnotify.v1 +# gopkg.in/gcfg.v1 v1.2.3 +gopkg.in/gcfg.v1 +gopkg.in/gcfg.v1/scanner +gopkg.in/gcfg.v1/token +gopkg.in/gcfg.v1/types # gopkg.in/inf.v0 v0.9.1 gopkg.in/inf.v0 # gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 gopkg.in/tomb.v1 +# gopkg.in/warnings.v0 v0.1.2 +gopkg.in/warnings.v0 # gopkg.in/yaml.v2 v2.2.2 gopkg.in/yaml.v2 # k8s.io/api v0.0.0-20190222213804-5cb15d344471 From 6c61c0e84d6aa31921302ab5d384c1d8360f933b Mon Sep 17 00:00:00 2001 From: akutz Date: Mon, 29 Jul 2019 10:31:29 -0500 Subject: [PATCH 2/7] Remove field tags for "ini" package This patch removes the field tags for the "ini" package as they add confusion for people who haven't read why the "ini" package is not being used to marshal data. --- pkg/apis/vsphere/v1alpha1/cloud/types.go | 70 ++++++++++++------------ 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/pkg/apis/vsphere/v1alpha1/cloud/types.go b/pkg/apis/vsphere/v1alpha1/cloud/types.go index 49003933e5..9c4bfb6da7 100644 --- a/pkg/apis/vsphere/v1alpha1/cloud/types.go +++ b/pkg/apis/vsphere/v1alpha1/cloud/types.go @@ -21,81 +21,81 @@ package cloud type Config struct { // Global is the vSphere cloud provider's global configuration. // +optional - Global GlobalConfig `gcfg:"Global" ini:"Global" json:"global,omitempty"` + Global GlobalConfig `gcfg:"Global" json:"global,omitempty"` // VCenter is a list of vCenter configurations. // +optional - VCenter map[string]*VCenterConfig `gcfg:"VirtualCenter" ini:"VirtualCenter" json:"virtualCenter,omitempty"` + VCenter map[string]*VCenterConfig `gcfg:"VirtualCenter" json:"virtualCenter,omitempty"` // Network is the vSphere cloud provider's network configuration. // +optional - Network NetworConfig `gcfg:"Network" ini:"Network" json:"network,omitempty"` + Network NetworConfig `gcfg:"Network" json:"network,omitempty"` // Disk is the vSphere cloud provider's disk configuration. // +optional - Disk DiskConfig `gcfg:"Disk" ini:"Disk" json:"disk,omitempty"` + Disk DiskConfig `gcfg:"Disk" json:"disk,omitempty"` // Workspace is the vSphere cloud provider's workspace configuration. // +optional - Workspace WorkspaceConfig `gcfg:"Workspace" ini:"Workspace" json:"workspace,omitempty"` + Workspace WorkspaceConfig `gcfg:"Workspace" json:"workspace,omitempty"` // Labels is the vSphere cloud provider's zone and region configuration. // +optional - Labels LabelConfig `gcfg:"Labels" ini:"Labels" json:"labels,omitempty"` + Labels LabelConfig `gcfg:"Labels" json:"labels,omitempty"` } // GlobalConfig is the vSphere cloud provider's global configuration. type GlobalConfig struct { // Username is the username used to access a vSphere endpoint. // +optional - Username string `gcfg:"user" ini:"user" json:"username,omitempty"` + Username string `gcfg:"user" json:"username,omitempty"` // Password is the password used to access a vSphere endpoint. // +optional - Password string `gcfg:"password" ini:"password" json:"password,omitempty"` + Password string `gcfg:"password" json:"password,omitempty"` // SecretName is the name of the Kubernetes secret in which the vSphere // credentials are located. // +optional - SecretName string `gcfg:"secret-name" ini:"secret-name" json:"secretName,omitempty"` + SecretName string `gcfg:"secret-name" json:"secretName,omitempty"` // SecretNamespace is the namespace for SecretName. // +optional - SecretNamespace string `gcfg:"secret-namespace" ini:"secret-namespace" json:"secretNamespace,omitempty"` + SecretNamespace string `gcfg:"secret-namespace" json:"secretNamespace,omitempty"` // Port is the port on which the vSphere endpoint is listening. // Defaults to 443. // +optional - Port string `gcfg:"port" ini:"port" json:"port,omitempty"` + Port string `gcfg:"port" json:"port,omitempty"` // Insecure is a flag that disables TLS peer verification. // +optional - Insecure bool `gcfg:"insecure-flag" ini:"insecure-flag" json:"insecure,omitempty"` + Insecure bool `gcfg:"insecure-flag" json:"insecure,omitempty"` // CAFile Specifies the path to a CA certificate in PEM format. // If not configured, the system's CA certificates will be used. // +optional - CAFile string `gcfg:"ca-file" ini:"ca-file" json:"caFile,omitempty"` + CAFile string `gcfg:"ca-file" json:"caFile,omitempty"` // Thumbprint is the cryptographic thumbprint of the vSphere endpoint's // certificate. // +optional - Thumbprint string `gcfg:"thumbprint" ini:"thumbprint" json:"thumbprint,omitempty"` + Thumbprint string `gcfg:"thumbprint" json:"thumbprint,omitempty"` // Datacenters is a CSV string of the datacenters in which VMs are located. // +optional - Datacenters string `gcfg:"datacenters" ini:"datacenters" json:"datacenters,omitempty"` + Datacenters string `gcfg:"datacenters" json:"datacenters,omitempty"` // RoundTripperCount specifies the SOAP round tripper count // (retries = RoundTripper - 1) // +optional - RoundTripperCount int32 `gcfg:"soap-roundtrip-count" ini:"soap-roundtrip-count" json:"roundTripperCount,omitempty"` + RoundTripperCount int32 `gcfg:"soap-roundtrip-count" json:"roundTripperCount,omitempty"` // ServiceAccount is the Kubernetes service account used to launch the cloud // controller manager. // Defaults to cloud-controller-manager. // +optional - ServiceAccount string `gcfg:"service-account" ini:"service-account" json:"serviceAccount,omitempty"` + ServiceAccount string `gcfg:"service-account" json:"serviceAccount,omitempty"` // SecretsDirectory is a directory in which secrets may be found. This // may used in the event that: @@ -105,61 +105,61 @@ type GlobalConfig struct { // container orcehstrator (CO) agnostic, and should support non-K8s COs. // Defaults to /etc/cloud/credentials. // +optional - SecretsDirectory string `gcfg:"secrets-directory" ini:"secrets-directory" json:"secretsDirectory,omitempty"` + SecretsDirectory string `gcfg:"secrets-directory" json:"secretsDirectory,omitempty"` // APIDisable disables the vSphere cloud controller manager API. // Defaults to true. // +optional - APIDisable *bool `gcfg:"api-disable" ini:"api-disable" json:"apiDisable,omitempty"` + APIDisable *bool `gcfg:"api-disable" json:"apiDisable,omitempty"` // APIBindPort configures the vSphere cloud controller manager API port. // Defaults to 43001. // +optional - APIBindPort string `gcfg:"api-binding" ini:"api-binding" json:"apiBindPort,omitempty"` + APIBindPort string `gcfg:"api-binding" json:"apiBindPort,omitempty"` } // VCenterConfig is a vSphere cloud provider's vCenter configuration. type VCenterConfig struct { // Username is the username used to access a vSphere endpoint. // +optional - Username string `gcfg:"user" ini:"user" json:"username,omitempty"` + Username string `gcfg:"user" json:"username,omitempty"` // Password is the password used to access a vSphere endpoint. // +optional - Password string `gcfg:"password" ini:"password" json:"password,omitempty"` + Password string `gcfg:"password" json:"password,omitempty"` // Port is the port on which the vSphere endpoint is listening. // Defaults to 443. // +optional - Port string `gcfg:"port" ini:"port" json:"port,omitempty"` + Port string `gcfg:"port" json:"port,omitempty"` // Datacenters is a CSV string of the datacenters in which VMs are located. // +optional - Datacenters string `gcfg:"datacenters" ini:"datacenters" json:"datacenters,omitempty"` + Datacenters string `gcfg:"datacenters" json:"datacenters,omitempty"` // RoundTripperCount specifies the SOAP round tripper count // (retries = RoundTripper - 1) // +optional - RoundTripperCount int32 `gcfg:"soap-roundtrip-count" ini:"soap-roundtrip-count" json:"roundTripperCount,omitempty"` + RoundTripperCount int32 `gcfg:"soap-roundtrip-count" json:"roundTripperCount,omitempty"` // Thumbprint is the cryptographic thumbprint of the vSphere endpoint's // certificate. // +optional - Thumbprint string `gcfg:"thumbprint" ini:"thumbprint" json:"thumbprint,omitempty"` + Thumbprint string `gcfg:"thumbprint" json:"thumbprint,omitempty"` } // NetworConfig is the network configuration for the vSphere cloud provider. type NetworConfig struct { // Name is the name of the network to which VMs are connected. // +optional - Name string `gcfg:"public-network" ini:"public-network" json:"name,omitempty"` + Name string `gcfg:"public-network" json:"name,omitempty"` } // DiskConfig defines the disk configuration for the vSphere cloud provider. type DiskConfig struct { // SCSIControllerType defines SCSI controller to be used. // +optional - SCSIControllerType string `gcfg:"scsicontrollertype" ini:"scsicontrollertype" json:"scsiControllerType,omitempty"` + SCSIControllerType string `gcfg:"scsicontrollertype" json:"scsiControllerType,omitempty"` } // WorkspaceConfig defines a workspace configuration for the vSphere cloud @@ -167,23 +167,23 @@ type DiskConfig struct { type WorkspaceConfig struct { // Server is the IP address or FQDN of the vSphere endpoint. // +optional - Server string `gcfg:"server" ini:"server" json:"server,omitempty"` + Server string `gcfg:"server" json:"server,omitempty"` // Datacenter is the datacenter in which VMs are created/located. // +optional - Datacenter string `gcfg:"datacenter" ini:"datacenter" json:"datacenter,omitempty"` + Datacenter string `gcfg:"datacenter" json:"datacenter,omitempty"` // Folder is the folder in which VMs are created/located. // +optional - Folder string `gcfg:"folder" ini:"folder" json:"folder,omitempty"` + Folder string `gcfg:"folder" json:"folder,omitempty"` // Datastore is the datastore in which VMs are created/located. // +optional - Datastore string `gcfg:"default-datastore" ini:"default-datastore" json:"defaultDatastore,omitempty"` + Datastore string `gcfg:"default-datastore" json:"defaultDatastore,omitempty"` // ResourcePool is the resource pool in which VMs are created/located. // +optional - ResourcePool string `gcfg:"resourcepool-path" ini:"resourcepool-path" json:"resourcePool,omitempty"` + ResourcePool string `gcfg:"resourcepool-path" json:"resourcePool,omitempty"` } // LabelConfig defines the categories and tags which correspond to built-in @@ -191,9 +191,9 @@ type WorkspaceConfig struct { type LabelConfig struct { // Zone is the zone in which VMs are created/located. // +optional - Zone string `gcfg:"zone" ini:"zone" json:"zone,omitempty"` + Zone string `gcfg:"zone" json:"zone,omitempty"` // Region is the region in which VMs are created/located. // +optional - Region string `gcfg:"region" ini:"region" json:"region,omitempty"` + Region string `gcfg:"region" json:"region,omitempty"` } From 6fea97f25e5028ede36ac9e8d3e3f82b3f3e14f9 Mon Sep 17 00:00:00 2001 From: akutz Date: Mon, 29 Jul 2019 10:50:03 -0500 Subject: [PATCH 3/7] Use reflection to marshal cloud provider config This patch switches from using a Go template to marshal a cloud provider config to using reflection. This ensures the section and property names match those defined by the "gcfg" field tags. --- pkg/apis/vsphere/v1alpha1/cloud/const.go | 142 ------------------ pkg/apis/vsphere/v1alpha1/cloud/doc.go | 4 - pkg/apis/vsphere/v1alpha1/cloud/encoding.go | 98 ++++++++---- .../vsphere/v1alpha1/cloud/encoding_test.go | 36 +++-- 4 files changed, 98 insertions(+), 182 deletions(-) delete mode 100644 pkg/apis/vsphere/v1alpha1/cloud/const.go diff --git a/pkg/apis/vsphere/v1alpha1/cloud/const.go b/pkg/apis/vsphere/v1alpha1/cloud/const.go deleted file mode 100644 index e68ed094c9..0000000000 --- a/pkg/apis/vsphere/v1alpha1/cloud/const.go +++ /dev/null @@ -1,142 +0,0 @@ -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cloud - -const configFormat = ` -{{- if IsNotEmpty .Global }} -{{- with .Global }} -[Global] -{{- if .Username }} -user = "{{ .Username }}" -{{- end }} -{{- if .Password }} -password = "{{ .Password }}" -{{- end }} -{{- if .Port }} -port = "{{ .Port }}" -{{- end }} -{{- if .SecretName }} -secret-name = "{{ .SecretName }}" -{{- end }} -{{- if .SecretNamespace }} -secret-namespace = "{{ .SecretNamespace }}" -{{- end }} -{{- if .Insecure }} -insecure-flag = "{{ .Insecure }}" -{{- end }} -{{- if .Datacenters }} -datacenters = "{{ .Datacenters }}" -{{- end }} -{{- if .CAFile }} -ca-file = "{{ .CAFile }}" -{{- end }} -{{- if .Thumbprint }} -thumbprint = "{{ .Thumbprint }}" -{{- end }} -{{- if .RoundTripperCount }} -soap-roundtripper-count = {{ .RoundTripperCount }} -{{- end }} -{{- if .ServiceAccount }} -service-account = {{ .ServiceAccount }} -{{- end }} -{{- if .SecretsDirectory }} -secrets-directory = {{ .SecretsDirectory }} -{{- end }} -{{- if .APIDisable }} -api-disable = {{ .APIDisable }} -{{- end }} -{{- if .APIBindPort }} -api-binding = "{{ .APIBindPort }}" -{{- end }} -{{- end }} {{/* with .Global */}} -{{- end }} {{/* if IsNotEmpty .Global */}} - -{{- range $Server, $VCenter := .VCenter }} -[VirtualCenter "{{ $Server }}"] -{{- with $VCenter }} -{{- if .Username }} -user = "{{ .Username }}" -{{- end }} -{{- if .Password }} -password = "{{ .Password }}" -{{- end }} -{{- if .Port }} -port = "{{ .Port }}" -{{- end }} -{{- if .Datacenters }} -datacenters = "{{ .Datacenters }}" -{{- end }} -{{- if .RoundTripperCount }} -soap-roundtripper-count = {{ .RoundTripperCount }} -{{- end }} -{{- if .Thumbprint }} -thumbprint = "{{ .Thumbprint }}" -{{- end }} -{{- end }} {{/* with $VCenter */}} -{{- end }} {{/* range $Server, $VCenter := .VCenter */}} - -{{- if IsNotEmpty .Workspace }} -{{- with .Workspace }} -[Workspace] -{{- if .Server }} -server = "{{ .Server }}" -{{- end }} -{{- if .Datacenter }} -datacenter = "{{ .Datacenter }}" -{{- end }} -{{- if .Folder }} -folder = "{{ .Folder }}" -{{- end }} -{{- if .Datastore }} -default-datastore = "{{ .Datastore }}" -{{- end }} -{{- if .ResourcePool }} -resourcepool-path = "{{ .ResourcePool }}" -{{- end }} -{{- end }} {{/* with .Workspace */}} -{{- end }} {{/* if IsNotEmpty .Workspace */}} - -{{- if IsNotEmpty .Disk }} -{{- with .Disk }} -[Disk] -{{- if .SCSIControllerType }} -scsicontrollertype = "{{ .SCSIControllerType }}" -{{- end }} -{{- end }} {{/* with .Disk */}} -{{- end }} {{/* if IsNotEmpty .Disk */}} - -{{- if IsNotEmpty .Network }} -{{- with .Network }} -[Network] -{{- if .Name }} -public-network = "{{ .Name }}" -{{- end }} -{{- end }} {{/* with .Network */}} -{{- end }} {{/* if IsNotEmpty .Network */}} - -{{- if IsNotEmpty .Labels }} -{{- with .Labels }} -[Labels] -{{- if .Zone }} -zone = "{{ .Zone }}" -{{- end }} -{{- if .Region }} -region = "{{ .Region }}" -{{- end }} -{{- end }} {{/* with .Labels */}} -{{- end }} {{/* if IsNotEmpty .Labels */}} -` diff --git a/pkg/apis/vsphere/v1alpha1/cloud/doc.go b/pkg/apis/vsphere/v1alpha1/cloud/doc.go index 8086ea1487..bb329ce188 100644 --- a/pkg/apis/vsphere/v1alpha1/cloud/doc.go +++ b/pkg/apis/vsphere/v1alpha1/cloud/doc.go @@ -24,10 +24,6 @@ limitations under the License. // // The "gopkg.in/go-ini/ini.v1" package was investigated, but it does not // support reflecting a struct with a field of type "map[string]TYPE" to INI. -// However, because the "gopkg.in/go-ini/ini.v1" is such an elegant solution -// for marshalling the cloud provider configuration to INI, the fields in this -// package retain tags used by the "gopkg.in/go-ini/ini.v1" package in case it -// ever introduces support for reflecting maps to INI. // // +k8s:deepcopy-gen=package package cloud diff --git a/pkg/apis/vsphere/v1alpha1/cloud/encoding.go b/pkg/apis/vsphere/v1alpha1/cloud/encoding.go index bc5c5b9be6..106c3f2dac 100644 --- a/pkg/apis/vsphere/v1alpha1/cloud/encoding.go +++ b/pkg/apis/vsphere/v1alpha1/cloud/encoding.go @@ -18,29 +18,92 @@ package cloud import ( "bytes" + "fmt" + "io" "reflect" - "text/template" "github.com/pkg/errors" gcfg "gopkg.in/gcfg.v1" ) +const gcfgTag = "gcfg" + // MarshalINI marshals the cloud provider configuration to INI-style // configuration data. func (c *Config) MarshalINI() ([]byte, error) { - t, err := template.New("t").Funcs(template.FuncMap{ - "IsNotEmpty": IsNotEmpty, - }).Parse(configFormat) - if err != nil { - return nil, errors.Wrap(err, "failed to parse config template") + if c == nil { + return nil, errors.New("config is nil") } + buf := &bytes.Buffer{} - if err := t.Execute(buf, c); err != nil { - return nil, errors.Wrap(err, "failed to execute config template") + + // Get the reflected type and value of the Config object. + configValue := reflect.ValueOf(*c) + configType := reflect.TypeOf(*c) + + for fieldIndex := 0; fieldIndex < configValue.NumField(); fieldIndex++ { + fieldValue := configValue.Field(fieldIndex) + fieldType := configType.Field(fieldIndex) + + // Do not proceed if the field is empty. + if isEmpty(fieldValue) { + continue + } + + // Get the name of the section by inspecting the field's gcfg tag. + sectionName, sectionNameOk := fieldType.Tag.Lookup(gcfgTag) + if !sectionNameOk { + return nil, errors.Errorf("field %q is missing tag %q", fieldType.Name, gcfgTag) + } + + switch fieldValue.Kind() { + case reflect.Map: + iter := fieldValue.MapRange() + for iter.Next() { + mapKey, mapVal := iter.Key(), iter.Value() + sectionName := fmt.Sprintf(`%s "%v"`, sectionName, mapKey.String()) + c.marshalINISectionProperties(buf, mapVal, sectionName) + } + default: + c.marshalINISectionProperties(buf, fieldValue, sectionName) + } } + return buf.Bytes(), nil } +func (c *Config) marshalINISectionProperties( + out io.Writer, + sectionValue reflect.Value, + sectionName string) error { + + sectionKind := sectionValue.Kind() + if sectionKind == reflect.Interface || sectionKind == reflect.Ptr { + return c.marshalINISectionProperties(out, sectionValue.Elem(), sectionName) + } + + fmt.Fprintf(out, "[%s]\n", sectionName) + + sectionType := sectionValue.Type() + for fieldIndex := 0; fieldIndex < sectionType.NumField(); fieldIndex++ { + fieldType := sectionType.Field(fieldIndex) + propertyName, propertyNameOk := fieldType.Tag.Lookup(gcfgTag) + if !propertyNameOk { + return errors.Errorf("field %q is missing tag %q", fieldType.Name, gcfgTag) + } + propertyValue := sectionValue.Field(fieldIndex) + if isEmpty(propertyValue) { + continue + } + propertyKind := propertyValue.Kind() + if propertyKind == reflect.Interface || propertyKind == reflect.Ptr { + propertyValue = propertyValue.Elem() + } + fmt.Fprintf(out, "%s = %v\n", propertyName, propertyValue.Interface()) + } + return nil +} + // UnmarshalOptions defines the options used to influence how INI data is // unmarshalled. type UnmarshalOptions struct { @@ -122,22 +185,3 @@ func isEmpty(val reflect.Value) bool { panic(errors.Errorf("invalid kind: %s", val.Kind())) } } - -/* -Please see the package documentation for why the MarshalINI function that -uses the "gopkg.in/go-ini/ini.v1" package is commented out. - -// MarshalINI marshals the cloud provider configuration as an INI-style -// configuration. -func (c *Config) MarshalINI() ([]byte, error) { - cfg := ini.Empty() - if err := ini.ReflectFrom(cfg, c); err != nil { - return nil, errors.Wrap(err, "failed to marshal cloud provider config to ini") - } - buf := &bytes.Buffer{} - if _, err := cfg.WriteTo(buf); err != nil { - return nil, err - } - return buf.Bytes(), nil -} -*/ diff --git a/pkg/apis/vsphere/v1alpha1/cloud/encoding_test.go b/pkg/apis/vsphere/v1alpha1/cloud/encoding_test.go index 1385c388a2..bec5268cf2 100644 --- a/pkg/apis/vsphere/v1alpha1/cloud/encoding_test.go +++ b/pkg/apis/vsphere/v1alpha1/cloud/encoding_test.go @@ -263,17 +263,29 @@ func TestMarshalINI(t *testing.T) { t.Run(tc.testName, func(t *testing.T) { buf, err := tc.configObj.MarshalINI() if err != nil { - g.Expect(err.Error()).Should( - gomega.Equal(tc.expectedError.Error()), - "unexpected error when marshalling data") + if tc.expectedError == nil { + g.Expect(err).ShouldNot( + gomega.HaveOccurred(), + "unexpected error when marshalling data") + } else { + g.Expect(err.Error()).Should( + gomega.Equal(tc.expectedError.Error()), + "unexpected error when marshalling data") + } } var actualConfig cloud.Config if err := actualConfig.UnmarshalINI( buf, tc.unmarshalOptions...); err != nil { - g.Expect(err.Error()).Should( - gomega.Equal(tc.expectedError.Error()), - "unexpected error when unmarshalling data") + if tc.expectedError == nil { + g.Expect(err).ShouldNot( + gomega.HaveOccurred(), + "unexpected error when unmarshalling data") + } else { + g.Expect(err.Error()).Should( + gomega.Equal(tc.expectedError.Error()), + "unexpected error when unmarshalling data") + } } g.Expect(actualConfig).Should( @@ -357,9 +369,15 @@ func TestUnmarshalINI(t *testing.T) { []byte(tc.iniString), tc.unmarshalOptions...); err != nil { - g.Expect(err.Error()).Should( - gomega.Equal(tc.expectedError.Error()), - "unexpected error when unmarshalling data") + if tc.expectedError == nil { + g.Expect(err).ShouldNot( + gomega.HaveOccurred(), + "unexpected error when unmarshalling data") + } else { + g.Expect(err.Error()).Should( + gomega.Equal(tc.expectedError.Error()), + "unexpected error when unmarshalling data") + } } g.Expect(actualConfig).Should( From 526b47794da61fee73568a48ae6fa885f76ff89a Mon Sep 17 00:00:00 2001 From: akutz Date: Mon, 29 Jul 2019 11:47:49 -0500 Subject: [PATCH 4/7] Implement gcfg omitempty This patch adds support for the "omitempty" tag to gcfg fields. --- pkg/apis/vsphere/v1alpha1/cloud/encoding.go | 99 ++++++++++++------- .../vsphere/v1alpha1/cloud/encoding_test.go | 4 +- pkg/apis/vsphere/v1alpha1/cloud/types.go | 70 ++++++------- .../v1alpha1/cloud/zz_generated.deepcopy.go | 8 +- 4 files changed, 103 insertions(+), 78 deletions(-) diff --git a/pkg/apis/vsphere/v1alpha1/cloud/encoding.go b/pkg/apis/vsphere/v1alpha1/cloud/encoding.go index 106c3f2dac..f7ee42abfd 100644 --- a/pkg/apis/vsphere/v1alpha1/cloud/encoding.go +++ b/pkg/apis/vsphere/v1alpha1/cloud/encoding.go @@ -21,6 +21,7 @@ import ( "fmt" "io" "reflect" + "strings" "github.com/pkg/errors" gcfg "gopkg.in/gcfg.v1" @@ -41,31 +42,29 @@ func (c *Config) MarshalINI() ([]byte, error) { configValue := reflect.ValueOf(*c) configType := reflect.TypeOf(*c) - for fieldIndex := 0; fieldIndex < configValue.NumField(); fieldIndex++ { - fieldValue := configValue.Field(fieldIndex) - fieldType := configType.Field(fieldIndex) + for sectionIndex := 0; sectionIndex < configValue.NumField(); sectionIndex++ { + sectionType := configType.Field(sectionIndex) + sectionValue := configValue.Field(sectionIndex) - // Do not proceed if the field is empty. - if isEmpty(fieldValue) { - continue - } + // Get the value of the gcfg tag to help determine the section + // name and whether to omit an empty value. + sectionName, omitEmpty := parseGcfgTag(sectionType) - // Get the name of the section by inspecting the field's gcfg tag. - sectionName, sectionNameOk := fieldType.Tag.Lookup(gcfgTag) - if !sectionNameOk { - return nil, errors.Errorf("field %q is missing tag %q", fieldType.Name, gcfgTag) + // Do not marshal a section if it is empty. + if omitEmpty && isEmpty(sectionValue) { + continue } - switch fieldValue.Kind() { + switch sectionValue.Kind() { case reflect.Map: - iter := fieldValue.MapRange() + iter := sectionValue.MapRange() for iter.Next() { - mapKey, mapVal := iter.Key(), iter.Value() - sectionName := fmt.Sprintf(`%s "%v"`, sectionName, mapKey.String()) - c.marshalINISectionProperties(buf, mapVal, sectionName) + sectionNameKey, sectionValue := iter.Key(), iter.Value() + sectionName := fmt.Sprintf(`%s "%v"`, sectionName, sectionNameKey.String()) + c.marshalINISectionProperties(buf, sectionValue, sectionName) } default: - c.marshalINISectionProperties(buf, fieldValue, sectionName) + c.marshalINISectionProperties(buf, sectionValue, sectionName) } } @@ -77,54 +76,80 @@ func (c *Config) marshalINISectionProperties( sectionValue reflect.Value, sectionName string) error { - sectionKind := sectionValue.Kind() - if sectionKind == reflect.Interface || sectionKind == reflect.Ptr { + switch sectionValue.Kind() { + case reflect.Interface, reflect.Ptr: return c.marshalINISectionProperties(out, sectionValue.Elem(), sectionName) } fmt.Fprintf(out, "[%s]\n", sectionName) sectionType := sectionValue.Type() - for fieldIndex := 0; fieldIndex < sectionType.NumField(); fieldIndex++ { - fieldType := sectionType.Field(fieldIndex) - propertyName, propertyNameOk := fieldType.Tag.Lookup(gcfgTag) - if !propertyNameOk { - return errors.Errorf("field %q is missing tag %q", fieldType.Name, gcfgTag) - } - propertyValue := sectionValue.Field(fieldIndex) - if isEmpty(propertyValue) { + for propertyIndex := 0; propertyIndex < sectionType.NumField(); propertyIndex++ { + propertyType := sectionType.Field(propertyIndex) + propertyValue := sectionValue.Field(propertyIndex) + + // Get the value of the gcfg tag to help determine the property + // name and whether to omit an empty value. + propertyName, omitEmpty := parseGcfgTag(propertyType) + + // Do not marshal a property if it is empty. + if omitEmpty && isEmpty(propertyValue) { continue } - propertyKind := propertyValue.Kind() - if propertyKind == reflect.Interface || propertyKind == reflect.Ptr { + + switch propertyValue.Kind() { + case reflect.Interface, reflect.Ptr: propertyValue = propertyValue.Elem() } - fmt.Fprintf(out, "%s = %v\n", propertyName, propertyValue.Interface()) + + fmt.Fprintf(out, "%s", propertyName) + if propertyValue.IsValid() { + fmt.Fprintf(out, " = %v\n", propertyValue.Interface()) + } } return nil } -// UnmarshalOptions defines the options used to influence how INI data is +func parseGcfgTag(field reflect.StructField) (string, bool) { + name := field.Name + omitEmpty := false + if tagVal, ok := field.Tag.Lookup(gcfgTag); ok { + tagParts := strings.Split(tagVal, ",") + lenTagParts := len(tagParts) + if lenTagParts > 0 { + tagName := tagParts[0] + if len(tagName) > 0 && tagName != "-" { + name = tagName + } + } + if lenTagParts > 1 { + omitEmpty = tagParts[1] == "omitempty" + } + } + return name, omitEmpty +} + +// UnmarshalINIOptions defines the options used to influence how INI data is // unmarshalled. -type UnmarshalOptions struct { +type UnmarshalINIOptions struct { // WarnAsFatal indicates that warnings that occur when unmarshalling INI // data should be treated as fatal errors. WarnAsFatal bool } -// UnmarshalOptionFunc is used to set unmarshal options. -type UnmarshalOptionFunc func(*UnmarshalOptions) +// UnmarshalINIOptionFunc is used to set unmarshal options. +type UnmarshalINIOptionFunc func(*UnmarshalINIOptions) // WarnAsFatal sets the option to treat warnings as fatal errors when // unmarshalling INI data. -func WarnAsFatal(opts *UnmarshalOptions) { +func WarnAsFatal(opts *UnmarshalINIOptions) { opts.WarnAsFatal = true } // UnmarshalINI unmarshals the cloud provider configuration from INI-style // configuration data. -func (c *Config) UnmarshalINI(data []byte, optFuncs ...UnmarshalOptionFunc) error { - opts := &UnmarshalOptions{} +func (c *Config) UnmarshalINI(data []byte, optFuncs ...UnmarshalINIOptionFunc) error { + opts := &UnmarshalINIOptions{} for _, setOpts := range optFuncs { setOpts(opts) } diff --git a/pkg/apis/vsphere/v1alpha1/cloud/encoding_test.go b/pkg/apis/vsphere/v1alpha1/cloud/encoding_test.go index bec5268cf2..0a5c2b1871 100644 --- a/pkg/apis/vsphere/v1alpha1/cloud/encoding_test.go +++ b/pkg/apis/vsphere/v1alpha1/cloud/encoding_test.go @@ -25,7 +25,7 @@ import ( "sigs.k8s.io/cluster-api-provider-vsphere/pkg/apis/vsphere/v1alpha1/cloud" ) -var unmarshalWarnAsFatal = []cloud.UnmarshalOptionFunc{cloud.WarnAsFatal} +var unmarshalWarnAsFatal = []cloud.UnmarshalINIOptionFunc{cloud.WarnAsFatal} func errDeprecated(section, key string) error { return errors.Errorf("warning:\ncan't store data at section \"%s\", variable \"%s\"\n", section, key) @@ -36,7 +36,7 @@ type codecTestCase struct { iniString string configObj cloud.Config expectedError error - unmarshalOptions []cloud.UnmarshalOptionFunc + unmarshalOptions []cloud.UnmarshalINIOptionFunc } var twoWayCodecTestCases = []codecTestCase{ diff --git a/pkg/apis/vsphere/v1alpha1/cloud/types.go b/pkg/apis/vsphere/v1alpha1/cloud/types.go index 9c4bfb6da7..3b6f6e6569 100644 --- a/pkg/apis/vsphere/v1alpha1/cloud/types.go +++ b/pkg/apis/vsphere/v1alpha1/cloud/types.go @@ -21,81 +21,81 @@ package cloud type Config struct { // Global is the vSphere cloud provider's global configuration. // +optional - Global GlobalConfig `gcfg:"Global" json:"global,omitempty"` + Global GlobalConfig `gcfg:"Global,omitempty" json:"global,omitempty"` // VCenter is a list of vCenter configurations. // +optional - VCenter map[string]*VCenterConfig `gcfg:"VirtualCenter" json:"virtualCenter,omitempty"` + VCenter map[string]*VCenterConfig `gcfg:"VirtualCenter,omitempty" json:"virtualCenter,omitempty"` // Network is the vSphere cloud provider's network configuration. // +optional - Network NetworConfig `gcfg:"Network" json:"network,omitempty"` + Network NetworConfig `gcfg:"Network,omitempty" json:"network,omitempty"` // Disk is the vSphere cloud provider's disk configuration. // +optional - Disk DiskConfig `gcfg:"Disk" json:"disk,omitempty"` + Disk DiskConfig `gcfg:"Disk,omitempty" json:"disk,omitempty"` // Workspace is the vSphere cloud provider's workspace configuration. // +optional - Workspace WorkspaceConfig `gcfg:"Workspace" json:"workspace,omitempty"` + Workspace WorkspaceConfig `gcfg:"Workspace,omitempty" json:"workspace,omitempty"` // Labels is the vSphere cloud provider's zone and region configuration. // +optional - Labels LabelConfig `gcfg:"Labels" json:"labels,omitempty"` + Labels LabelConfig `gcfg:"Labels,omitempty" json:"labels,omitempty"` } // GlobalConfig is the vSphere cloud provider's global configuration. type GlobalConfig struct { // Username is the username used to access a vSphere endpoint. // +optional - Username string `gcfg:"user" json:"username,omitempty"` + Username string `gcfg:"user,omitempty" json:"username,omitempty"` // Password is the password used to access a vSphere endpoint. // +optional - Password string `gcfg:"password" json:"password,omitempty"` + Password string `gcfg:"password,omitempty" json:"password,omitempty"` // SecretName is the name of the Kubernetes secret in which the vSphere // credentials are located. // +optional - SecretName string `gcfg:"secret-name" json:"secretName,omitempty"` + SecretName string `gcfg:"secret-name,omitempty" json:"secretName,omitempty"` // SecretNamespace is the namespace for SecretName. // +optional - SecretNamespace string `gcfg:"secret-namespace" json:"secretNamespace,omitempty"` + SecretNamespace string `gcfg:"secret-namespace,omitempty" json:"secretNamespace,omitempty"` // Port is the port on which the vSphere endpoint is listening. // Defaults to 443. // +optional - Port string `gcfg:"port" json:"port,omitempty"` + Port string `gcfg:"port,omitempty" json:"port,omitempty"` // Insecure is a flag that disables TLS peer verification. // +optional - Insecure bool `gcfg:"insecure-flag" json:"insecure,omitempty"` + Insecure bool `gcfg:"insecure-flag,omitempty" json:"insecure,omitempty"` // CAFile Specifies the path to a CA certificate in PEM format. // If not configured, the system's CA certificates will be used. // +optional - CAFile string `gcfg:"ca-file" json:"caFile,omitempty"` + CAFile string `gcfg:"ca-file,omitempty" json:"caFile,omitempty"` // Thumbprint is the cryptographic thumbprint of the vSphere endpoint's // certificate. // +optional - Thumbprint string `gcfg:"thumbprint" json:"thumbprint,omitempty"` + Thumbprint string `gcfg:"thumbprint,omitempty" json:"thumbprint,omitempty"` // Datacenters is a CSV string of the datacenters in which VMs are located. // +optional - Datacenters string `gcfg:"datacenters" json:"datacenters,omitempty"` + Datacenters string `gcfg:"datacenters,omitempty" json:"datacenters,omitempty"` // RoundTripperCount specifies the SOAP round tripper count // (retries = RoundTripper - 1) // +optional - RoundTripperCount int32 `gcfg:"soap-roundtrip-count" json:"roundTripperCount,omitempty"` + RoundTripperCount int32 `gcfg:"soap-roundtrip-count,omitempty" json:"roundTripperCount,omitempty"` // ServiceAccount is the Kubernetes service account used to launch the cloud // controller manager. // Defaults to cloud-controller-manager. // +optional - ServiceAccount string `gcfg:"service-account" json:"serviceAccount,omitempty"` + ServiceAccount string `gcfg:"service-account,omitempty" json:"serviceAccount,omitempty"` // SecretsDirectory is a directory in which secrets may be found. This // may used in the event that: @@ -105,61 +105,61 @@ type GlobalConfig struct { // container orcehstrator (CO) agnostic, and should support non-K8s COs. // Defaults to /etc/cloud/credentials. // +optional - SecretsDirectory string `gcfg:"secrets-directory" json:"secretsDirectory,omitempty"` + SecretsDirectory string `gcfg:"secrets-directory,omitempty" json:"secretsDirectory,omitempty"` // APIDisable disables the vSphere cloud controller manager API. // Defaults to true. // +optional - APIDisable *bool `gcfg:"api-disable" json:"apiDisable,omitempty"` + APIDisable *bool `gcfg:"api-disable,omitempty" json:"apiDisable,omitempty"` // APIBindPort configures the vSphere cloud controller manager API port. // Defaults to 43001. // +optional - APIBindPort string `gcfg:"api-binding" json:"apiBindPort,omitempty"` + APIBindPort string `gcfg:"api-binding,omitempty" json:"apiBindPort,omitempty"` } // VCenterConfig is a vSphere cloud provider's vCenter configuration. type VCenterConfig struct { // Username is the username used to access a vSphere endpoint. // +optional - Username string `gcfg:"user" json:"username,omitempty"` + Username string `gcfg:"user,omitempty" json:"username,omitempty"` // Password is the password used to access a vSphere endpoint. // +optional - Password string `gcfg:"password" json:"password,omitempty"` + Password string `gcfg:"password,omitempty" json:"password,omitempty"` // Port is the port on which the vSphere endpoint is listening. // Defaults to 443. // +optional - Port string `gcfg:"port" json:"port,omitempty"` + Port string `gcfg:"port,omitempty" json:"port,omitempty"` // Datacenters is a CSV string of the datacenters in which VMs are located. // +optional - Datacenters string `gcfg:"datacenters" json:"datacenters,omitempty"` + Datacenters string `gcfg:"datacenters,omitempty" json:"datacenters,omitempty"` // RoundTripperCount specifies the SOAP round tripper count // (retries = RoundTripper - 1) // +optional - RoundTripperCount int32 `gcfg:"soap-roundtrip-count" json:"roundTripperCount,omitempty"` + RoundTripperCount int32 `gcfg:"soap-roundtrip-count,omitempty" json:"roundTripperCount,omitempty"` // Thumbprint is the cryptographic thumbprint of the vSphere endpoint's // certificate. // +optional - Thumbprint string `gcfg:"thumbprint" json:"thumbprint,omitempty"` + Thumbprint string `gcfg:"thumbprint,omitempty" json:"thumbprint,omitempty"` } // NetworConfig is the network configuration for the vSphere cloud provider. type NetworConfig struct { // Name is the name of the network to which VMs are connected. // +optional - Name string `gcfg:"public-network" json:"name,omitempty"` + Name string `gcfg:"public-network,omitempty" json:"name,omitempty"` } // DiskConfig defines the disk configuration for the vSphere cloud provider. type DiskConfig struct { // SCSIControllerType defines SCSI controller to be used. // +optional - SCSIControllerType string `gcfg:"scsicontrollertype" json:"scsiControllerType,omitempty"` + SCSIControllerType string `gcfg:"scsicontrollertype,omitempty" json:"scsiControllerType,omitempty"` } // WorkspaceConfig defines a workspace configuration for the vSphere cloud @@ -167,23 +167,23 @@ type DiskConfig struct { type WorkspaceConfig struct { // Server is the IP address or FQDN of the vSphere endpoint. // +optional - Server string `gcfg:"server" json:"server,omitempty"` + Server string `gcfg:"server,omitempty" json:"server,omitempty"` // Datacenter is the datacenter in which VMs are created/located. // +optional - Datacenter string `gcfg:"datacenter" json:"datacenter,omitempty"` + Datacenter string `gcfg:"datacenter,omitempty" json:"datacenter,omitempty"` // Folder is the folder in which VMs are created/located. // +optional - Folder string `gcfg:"folder" json:"folder,omitempty"` + Folder string `gcfg:"folder,omitempty" json:"folder,omitempty"` // Datastore is the datastore in which VMs are created/located. // +optional - Datastore string `gcfg:"default-datastore" json:"defaultDatastore,omitempty"` + Datastore string `gcfg:"default-datastore,omitempty" json:"defaultDatastore,omitempty"` // ResourcePool is the resource pool in which VMs are created/located. // +optional - ResourcePool string `gcfg:"resourcepool-path" json:"resourcePool,omitempty"` + ResourcePool string `gcfg:"resourcepool-path,omitempty" json:"resourcePool,omitempty"` } // LabelConfig defines the categories and tags which correspond to built-in @@ -191,9 +191,9 @@ type WorkspaceConfig struct { type LabelConfig struct { // Zone is the zone in which VMs are created/located. // +optional - Zone string `gcfg:"zone" json:"zone,omitempty"` + Zone string `gcfg:"zone,omitempty" json:"zone,omitempty"` // Region is the region in which VMs are created/located. // +optional - Region string `gcfg:"region" json:"region,omitempty"` + Region string `gcfg:"region,omitempty" json:"region,omitempty"` } diff --git a/pkg/apis/vsphere/v1alpha1/cloud/zz_generated.deepcopy.go b/pkg/apis/vsphere/v1alpha1/cloud/zz_generated.deepcopy.go index 5de60cc39d..cde5482de3 100644 --- a/pkg/apis/vsphere/v1alpha1/cloud/zz_generated.deepcopy.go +++ b/pkg/apis/vsphere/v1alpha1/cloud/zz_generated.deepcopy.go @@ -125,17 +125,17 @@ func (in *NetworConfig) DeepCopy() *NetworConfig { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *UnmarshalOptions) DeepCopyInto(out *UnmarshalOptions) { +func (in *UnmarshalINIOptions) DeepCopyInto(out *UnmarshalINIOptions) { *out = *in return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UnmarshalOptions. -func (in *UnmarshalOptions) DeepCopy() *UnmarshalOptions { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UnmarshalINIOptions. +func (in *UnmarshalINIOptions) DeepCopy() *UnmarshalINIOptions { if in == nil { return nil } - out := new(UnmarshalOptions) + out := new(UnmarshalINIOptions) in.DeepCopyInto(out) return out } From f6106cff363aa2a7bc29e79c4f898d54de4c7ea8 Mon Sep 17 00:00:00 2001 From: akutz Date: Mon, 29 Jul 2019 12:20:50 -0500 Subject: [PATCH 5/7] Update YAML tpls, docs for new cloud provider conf This patch updates the YAML examples, generation scripts, and documentation to reflect the new cloud provider configuration. --- .../examples/vsphere/cluster.yaml.template | 14 +++++ docs/getting_started.md | 36 +++++++---- hack/generate-yaml.sh | 2 +- .../vsphere/actuators/cluster/actuator.go | 28 +++++---- pkg/cloud/vsphere/services/govmomi/create.go | 31 ++++------ .../vsphere/services/userdata/controlplane.go | 47 -------------- .../services/userdata/controlplane_test.go | 61 ------------------- 7 files changed, 69 insertions(+), 150 deletions(-) diff --git a/cmd/clusterctl/examples/vsphere/cluster.yaml.template b/cmd/clusterctl/examples/vsphere/cluster.yaml.template index 1763fc464e..22551ca8ff 100644 --- a/cmd/clusterctl/examples/vsphere/cluster.yaml.template +++ b/cmd/clusterctl/examples/vsphere/cluster.yaml.template @@ -18,3 +18,17 @@ spec: password: "${VSPHERE_PASSWORD}" sshAuthorizedKeys: - "${SSH_AUTHORIZED_KEY}" + cloudProviderConfiguration: + global: + secretName: "cloud-provider-vsphere-credentials" + secretNamespace: "kube-system" + virtualCenter: + "${VSPHERE_SERVER}": + network: + name: "${VSPHERE_NETWORK}" + workspace: + server: "${VSPHERE_SERVER}" + datacenter: "${VSPHERE_DATACENTER}" + datastore: "${VSPHERE_DATASTORE}" + resourcePool: "${VSPHERE_RESOURCE_POOL}" + folder: "${VSPHERE_FOLDER}" diff --git a/docs/getting_started.md b/docs/getting_started.md index ab5e318916..c968fe71c2 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -2,19 +2,19 @@ This is a guide on how to get started with CAPV (Cluster API Provider vSphere). To learn more about cluster API in more depth, check out the the [cluster api docs page](https://cluster-api.sigs.k8s.io/). -* [Getting Started](#Getting-Started) - * [Bootstrapping a Management Cluster with clusterctl](#Bootstrapping-a-Management-Cluster-with-clusterctl) - * [Install Requirements](#Install-Requirements) +* [Getting Started](#getting-started) + * [Bootstrapping a Management Cluster with clusterctl](#bootstrapping-a-management-cluster-with-clusterctl) + * [Install Requirements](#install-requirements) * [clusterctl](#clusterctl) - * [Docker](#Docker) - * [Kind](#Kind) + * [Docker](#docker) + * [Kind](#kind) * [kubectl](#kubectl) - * [vSphere Requirements](#vSphere-Requirements) - * [vCenter Credentials](#vCenter-Credentials) - * [Uploading the CAPV Machine Image](#Uploading-the-CAPV-Machine-Image) - * [Generating YAML for the Bootstrap Cluster](#Generating-YAML-for-the-Bootstrap-Cluster) - * [Using clusterctl](#Using-clusterctl) - * [Managing Workload Clusters using the Management Cluster](#Managing-Workload-Clusters-using-the-Management-Cluster) + * [vSphere Requirements](#vsphere-requirements) + * [vCenter Credentials](#vcenter-credentials) + * [Uploading the CAPV Machine Image](#uploading-the-capv-machine-image) + * [Generating YAML for the Bootstrap Cluster](#generating-yaml-for-the-bootstrap-cluster) + * [Using clusterctl](#using-clusterctl) + * [Managing Workload Clusters using the Management Cluster](#managing-workload-clusters-using-the-management-cluster) ## Bootstrapping a Management Cluster with clusterctl @@ -171,6 +171,20 @@ spec: server: "" username: "" password: "" + cloudProviderConfiguration: + global: + secretName: "cloud-provider-vsphere-credentials" + secretNamespace: "kube-system" + virtualCenter: + "": + network: + name: "vm-network-1" + workspace: + server: "" + datacenter: "SDDC-Datacenter" + datastore: "WorkloadDatastore" + resourcePool: "Resources" + folder: "vm" --- apiVersion: cluster.k8s.io/v1alpha1 kind: Machine diff --git a/hack/generate-yaml.sh b/hack/generate-yaml.sh index 281f3dcfe1..de2c430888 100755 --- a/hack/generate-yaml.sh +++ b/hack/generate-yaml.sh @@ -209,4 +209,4 @@ EOF # If running in Docker then ensure the contents of the OUT_DIR have the # the same owner as the volume mounted to the /out directory. -[ "${DOCKER_ENABLED}" ] && chown -R "$(stat -c '%u:%g' /out)" "${OUT_DIR}" +[ "${DOCKER_ENABLED-}" ] && chown -R "$(stat -c '%u:%g' /out)" "${OUT_DIR}" diff --git a/pkg/cloud/vsphere/actuators/cluster/actuator.go b/pkg/cloud/vsphere/actuators/cluster/actuator.go index f3a6a3ba5d..c21f3b7de3 100644 --- a/pkg/cloud/vsphere/actuators/cluster/actuator.go +++ b/pkg/cloud/vsphere/actuators/cluster/actuator.go @@ -36,7 +36,6 @@ import ( "sigs.k8s.io/cluster-api-provider-vsphere/pkg/cloud/vsphere/actuators" "sigs.k8s.io/cluster-api-provider-vsphere/pkg/cloud/vsphere/config" - "sigs.k8s.io/cluster-api-provider-vsphere/pkg/cloud/vsphere/constants" "sigs.k8s.io/cluster-api-provider-vsphere/pkg/cloud/vsphere/context" "sigs.k8s.io/cluster-api-provider-vsphere/pkg/cloud/vsphere/services/certificates" "sigs.k8s.io/cluster-api-provider-vsphere/pkg/cloud/vsphere/services/kubeclient" @@ -216,19 +215,24 @@ func (a *Actuator) reconcileCloudConfigSecret(ctx *context.ClusterContext) error ctx.Logger.Error(err, "target cluster is not ready") return &clusterErr.RequeueAfterError{RequeueAfter: config.DefaultRequeue} } - // Define the cloud provider credentials secret for the target cluster. + if len(ctx.ClusterConfig.CloudProviderConfiguration.VCenter) == 0 { + return errors.New("cloud provider configuration does not define any vCenters") + } + credentials := map[string]string{} + for server := range ctx.ClusterConfig.CloudProviderConfiguration.VCenter { + credentials[fmt.Sprintf("%s.username", server)] = ctx.User() + credentials[fmt.Sprintf("%s.password", server)] = ctx.Pass() + } + // Define the kubeconfig secret for the target cluster. secret := &apiv1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Namespace: constants.CloudProviderSecretNamespace, - Name: constants.CloudProviderSecretName, - }, - Type: apiv1.SecretTypeOpaque, - StringData: map[string]string{ - fmt.Sprintf("%s.username", ctx.ClusterConfig.Server): ctx.User(), - fmt.Sprintf("%s.password", ctx.ClusterConfig.Server): ctx.Pass(), + Namespace: ctx.ClusterConfig.CloudProviderConfiguration.Global.SecretNamespace, + Name: ctx.ClusterConfig.CloudProviderConfiguration.Global.SecretName, }, + Type: apiv1.SecretTypeOpaque, + StringData: credentials, } - if _, err := client.Secrets(constants.CloudProviderSecretNamespace).Create(secret); err != nil { + if _, err := client.Secrets(secret.Namespace).Create(secret); err != nil { if apierrors.IsAlreadyExists(err) { return nil } @@ -237,8 +241,8 @@ func (a *Actuator) reconcileCloudConfigSecret(ctx *context.ClusterContext) error } ctx.Logger.V(6).Info("created cloud provider credential secret", - "secret-name", constants.CloudProviderSecretName, - "secret-namespace", constants.CloudProviderSecretNamespace) + "secret-name", secret.Name, + "secret-namespace", secret.Namespace) return nil } diff --git a/pkg/cloud/vsphere/services/govmomi/create.go b/pkg/cloud/vsphere/services/govmomi/create.go index 528544c37c..b5a98d483c 100644 --- a/pkg/cloud/vsphere/services/govmomi/create.go +++ b/pkg/cloud/vsphere/services/govmomi/create.go @@ -94,25 +94,20 @@ func generateUserData(ctx *context.MachineContext, bootstrapToken string) ([]byt // apply values based on the role of the machine if ctx.HasControlPlaneRole() { + // Get the cloud provider configuration. + cloudConfig := ctx.ClusterConfig.CloudProviderConfiguration.DeepCopy() - // Cloud init needs the a valid vmfolder in cloudconfig - // Check the vmfolder and replace with default if not present - folder, err := ctx.Session.Finder.FolderOrDefault(ctx, ctx.MachineConfig.Folder) - if err != nil { - return nil, errors.Wrapf(err, "unable to get folder for %q", ctx) + // Cloud init needs the a valid folder in the cloud provider config. + // Replace the folder with the default folder if the former does not exit. + if cloudConfig.Workspace.Folder != "" { + folder, err := ctx.Session.Finder.FolderOrDefault(ctx, cloudConfig.Workspace.Folder) + if err != nil { + return nil, errors.Wrapf(err, "unable to get folder for %q", ctx) + } + cloudConfig.Workspace.Folder = folder.Name() } - cloudConfig, err := userdata.NewCloudConfig(&userdata.CloudConfigInput{ - SecretName: constants.CloudProviderSecretName, - SecretNamespace: constants.CloudProviderSecretNamespace, - Server: ctx.ClusterConfig.Server, - Datacenter: ctx.MachineConfig.Datacenter, - ResourcePool: ctx.MachineConfig.ResourcePool, - Folder: folder.Name(), - Datastore: ctx.MachineConfig.Datastore, - // assume the first VM network found for the vSphere cloud provider - Network: ctx.MachineConfig.Network.Devices[0].NetworkName, - }) + cloudConfigINI, err := cloudConfig.MarshalINI() if err != nil { return nil, err } @@ -157,7 +152,7 @@ func generateUserData(ctx *context.MachineContext, bootstrapToken string) ([]byt FrontProxyCAKey: string(ctx.ClusterConfig.FrontProxyCAKeyPair.Key), SaCert: string(ctx.ClusterConfig.SAKeyPair.Cert), SaKey: string(ctx.ClusterConfig.SAKeyPair.Key), - CloudConfig: cloudConfig, + CloudConfig: string(cloudConfigINI), JoinConfiguration: joinConfigurationYAML, }) if err != nil { @@ -237,7 +232,7 @@ func generateUserData(ctx *context.MachineContext, bootstrapToken string) ([]byt FrontProxyCAKey: string(ctx.ClusterConfig.FrontProxyCAKeyPair.Key), SaCert: string(ctx.ClusterConfig.SAKeyPair.Cert), SaKey: string(ctx.ClusterConfig.SAKeyPair.Key), - CloudConfig: cloudConfig, + CloudConfig: string(cloudConfigINI), ClusterConfiguration: clusterConfigYAML, InitConfiguration: initConfigYAML, }) diff --git a/pkg/cloud/vsphere/services/userdata/controlplane.go b/pkg/cloud/vsphere/services/userdata/controlplane.go index 5b6f469ede..465191256e 100644 --- a/pkg/cloud/vsphere/services/userdata/controlplane.go +++ b/pkg/cloud/vsphere/services/userdata/controlplane.go @@ -24,28 +24,6 @@ import ( ) const ( - cloudConfig = `[Global] -secret-name = "{{ .SecretName }}" -secret-namespace = "{{ .SecretNamespace }}" -insecure-flag = "1" # set to 1 if the vCenter uses a self-signed cert -datacenters = "{{ .Datacenter }}" - -[VirtualCenter "{{ .Server }}"] - -[Workspace] -server = "{{ .Server }}" -datacenter = "{{ .Datacenter }}" -folder = "{{ .Folder }}" -default-datastore = "{{ .Datastore }}" -resourcepool-path = "{{ .ResourcePool }}" - -[Disk] -scsicontrollertype = pvscsi - -[Network] -public-network = "{{ .Network }}" -` - controlPlaneCloudInit = `{{.Header}} {{if .SSHAuthorizedKeys}}ssh_authorized_keys:{{range .SSHAuthorizedKeys}} - "{{.}}"{{end}}{{end}} @@ -250,19 +228,6 @@ type ContolPlaneJoinInput struct { JoinConfiguration string } -// CloudConfigInput defines parameters required to generate the -// vSphere Cloud Provider cloud config file -type CloudConfigInput struct { - SecretName string - SecretNamespace string - Server string - Datacenter string - ResourcePool string - Folder string - Datastore string - Network string -} - func (cpi *ControlPlaneInput) validateCertificates() error { if !isKeyPairValid(cpi.CACert, cpi.CAKey) { return errors.New("CA cert material in the ControlPlaneInput is missing cert/key") @@ -343,18 +308,6 @@ func JoinControlPlane(input *ContolPlaneJoinInput) (string, error) { return userData, err } -// NewCloudConfig returns the string content for the vSphere Cloud Provider cloud config file -func NewCloudConfig(input *CloudConfigInput) (string, error) { - fMap := map[string]interface{}{} - - userData, err := generateWithFuncs("cloudprovider", cloudConfig, funcMap(fMap), input) - if err != nil { - return "", errors.Wrapf(err, "failed to generate user data for new control plane machine") - } - - return userData, nil -} - func templateBase64Encode(s string) string { return base64.StdEncoding.EncodeToString([]byte(s)) } diff --git a/pkg/cloud/vsphere/services/userdata/controlplane_test.go b/pkg/cloud/vsphere/services/userdata/controlplane_test.go index 8254dc4878..3e6f6f099c 100644 --- a/pkg/cloud/vsphere/services/userdata/controlplane_test.go +++ b/pkg/cloud/vsphere/services/userdata/controlplane_test.go @@ -18,8 +18,6 @@ package userdata import ( "testing" - - "sigs.k8s.io/cluster-api-provider-vsphere/pkg/cloud/vsphere/constants" ) func TestTemplateYAMLIndent(t *testing.T) { @@ -53,62 +51,3 @@ func TestTemplateYAMLIndent(t *testing.T) { } } - -func Test_CloudConfig(t *testing.T) { - testcases := []struct { - name string - input *CloudConfigInput - userdata string - err error - }{ - { - name: "standard cloud config", - input: &CloudConfigInput{ - SecretName: constants.CloudProviderSecretName, - SecretNamespace: constants.CloudProviderSecretNamespace, - Server: "10.0.0.1", - Datacenter: "myprivatecloud", - ResourcePool: "deadpool", - Folder: "vms", - Datastore: "infinite-data", - Network: "connected", - }, - userdata: `[Global] -secret-name = "` + constants.CloudProviderSecretName + `" -secret-namespace = "` + constants.CloudProviderSecretNamespace + `" -insecure-flag = "1" # set to 1 if the vCenter uses a self-signed cert -datacenters = "myprivatecloud" - -[VirtualCenter "10.0.0.1"] - -[Workspace] -server = "10.0.0.1" -datacenter = "myprivatecloud" -folder = "vms" -default-datastore = "infinite-data" -resourcepool-path = "deadpool" - -[Disk] -scsicontrollertype = pvscsi - -[Network] -public-network = "connected" -`, - }, - } - - for _, testcase := range testcases { - t.Run(testcase.name, func(t *testing.T) { - userdata, err := NewCloudConfig(testcase.input) - if err != nil { - t.Fatalf("error getting cloud config user data: %q", err) - } - - if userdata != testcase.userdata { - t.Logf("actual user data: %q", userdata) - t.Logf("expected user data: %q", testcase.userdata) - t.Error("unexpected user data") - } - }) - } -} From a4c97f8d6830507f4dd4e19f0c4dd903cbfa4cb7 Mon Sep 17 00:00:00 2001 From: akutz Date: Mon, 29 Jul 2019 13:18:12 -0500 Subject: [PATCH 6/7] Fix default-datastore bug in cloud conf This patch fixes an issue with the way the cloud provider configuration's workspace datastore was being processed. --- config/crds/vsphere_v1alpha1_vsphereclusterproviderspec.yaml | 2 +- pkg/apis/vsphere/v1alpha1/cloud/encoding_test.go | 2 ++ pkg/apis/vsphere/v1alpha1/cloud/types.go | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/config/crds/vsphere_v1alpha1_vsphereclusterproviderspec.yaml b/config/crds/vsphere_v1alpha1_vsphereclusterproviderspec.yaml index 60c63af55c..97c9628f19 100644 --- a/config/crds/vsphere_v1alpha1_vsphereclusterproviderspec.yaml +++ b/config/crds/vsphere_v1alpha1_vsphereclusterproviderspec.yaml @@ -134,7 +134,7 @@ spec: datacenter: description: Datacenter is the datacenter in which VMs are created/located. type: string - defaultDatastore: + datastore: description: Datastore is the datastore in which VMs are created/located. type: string folder: diff --git a/pkg/apis/vsphere/v1alpha1/cloud/encoding_test.go b/pkg/apis/vsphere/v1alpha1/cloud/encoding_test.go index 0a5c2b1871..894769f7a9 100644 --- a/pkg/apis/vsphere/v1alpha1/cloud/encoding_test.go +++ b/pkg/apis/vsphere/v1alpha1/cloud/encoding_test.go @@ -54,6 +54,7 @@ var twoWayCodecTestCases = []codecTestCase{ server = 0.0.0.0 datacenter = us-west folder = kubernetes + default-datastore = default `, configObj: cloud.Config{ Global: cloud.GlobalConfig{ @@ -68,6 +69,7 @@ var twoWayCodecTestCases = []codecTestCase{ Server: "0.0.0.0", Datacenter: "us-west", Folder: "kubernetes", + Datastore: "default", }, }, }, diff --git a/pkg/apis/vsphere/v1alpha1/cloud/types.go b/pkg/apis/vsphere/v1alpha1/cloud/types.go index 3b6f6e6569..2ba7e6c3e9 100644 --- a/pkg/apis/vsphere/v1alpha1/cloud/types.go +++ b/pkg/apis/vsphere/v1alpha1/cloud/types.go @@ -179,7 +179,7 @@ type WorkspaceConfig struct { // Datastore is the datastore in which VMs are created/located. // +optional - Datastore string `gcfg:"default-datastore,omitempty" json:"defaultDatastore,omitempty"` + Datastore string `gcfg:"default-datastore,omitempty" json:"datastore,omitempty"` // ResourcePool is the resource pool in which VMs are created/located. // +optional From 62fa84c7c51a938570fb02522bbb885bcff43977 Mon Sep 17 00:00:00 2001 From: akutz Date: Wed, 7 Aug 2019 17:27:56 -0500 Subject: [PATCH 7/7] Centralize dstore/pool/folder in CCM config This patch moves the machine config properties for datastore, resource pool, and folder into the cluster's cloud provider configuration. CAPV cannot deploy clusters that do not use the vSphere cloud provider anyway due to the way ready state is determined by the node ref controller. --- .../examples/vsphere/cluster.yaml.template | 1 + .../examples/vsphere/machines.yaml.template | 3 --- .../examples/vsphere/machineset.yaml.template | 3 --- ...e_v1alpha1_vspheremachineproviderspec.yaml | 20 +------------------ docs/getting_started.md | 8 +------- pkg/apis/vsphere/v1alpha1/cloud/types.go | 6 +++--- .../v1alpha1/cloud/zz_generated.deepcopy.go | 8 ++++---- .../vspheremachineproviderspec_types.go | 20 ++----------------- .../vspheremachineproviderspec_types_test.go | 6 ++---- .../vsphere/services/govmomi/create_test.go | 5 +---- .../vsphere/services/govmomi/vcenter/clone.go | 6 +++--- 11 files changed, 18 insertions(+), 68 deletions(-) diff --git a/cmd/clusterctl/examples/vsphere/cluster.yaml.template b/cmd/clusterctl/examples/vsphere/cluster.yaml.template index 22551ca8ff..0baccab8a7 100644 --- a/cmd/clusterctl/examples/vsphere/cluster.yaml.template +++ b/cmd/clusterctl/examples/vsphere/cluster.yaml.template @@ -24,6 +24,7 @@ spec: secretNamespace: "kube-system" virtualCenter: "${VSPHERE_SERVER}": + datacenters: "${VSPHERE_DATACENTER}" network: name: "${VSPHERE_NETWORK}" workspace: diff --git a/cmd/clusterctl/examples/vsphere/machines.yaml.template b/cmd/clusterctl/examples/vsphere/machines.yaml.template index f3432a410b..28c80d9930 100644 --- a/cmd/clusterctl/examples/vsphere/machines.yaml.template +++ b/cmd/clusterctl/examples/vsphere/machines.yaml.template @@ -13,9 +13,6 @@ items: apiVersion: vsphere.cluster.k8s.io/v1alpha1 kind: VsphereMachineProviderSpec datacenter: "${VSPHERE_DATACENTER}" - datastore: "${VSPHERE_DATASTORE}" - resourcePool: "${VSPHERE_RESOURCE_POOL}" - folder: "${VSPHERE_FOLDER}" network: devices: - networkName: "${VSPHERE_NETWORK}" diff --git a/cmd/clusterctl/examples/vsphere/machineset.yaml.template b/cmd/clusterctl/examples/vsphere/machineset.yaml.template index 9cedb95184..43d8eec0c6 100644 --- a/cmd/clusterctl/examples/vsphere/machineset.yaml.template +++ b/cmd/clusterctl/examples/vsphere/machineset.yaml.template @@ -22,9 +22,6 @@ spec: apiVersion: vsphere.cluster.k8s.io/v1alpha1 kind: VsphereMachineProviderSpec datacenter: "${VSPHERE_DATACENTER}" - datastore: "${VSPHERE_DATASTORE}" - resourcePool: "${VSPHERE_RESOURCE_POOL}" - folder: "${VSPHERE_FOLDER}" network: devices: - networkName: "${VSPHERE_NETWORK}" diff --git a/config/crds/vsphere_v1alpha1_vspheremachineproviderspec.yaml b/config/crds/vsphere_v1alpha1_vspheremachineproviderspec.yaml index f35c9cae66..07b6273e6e 100644 --- a/config/crds/vsphere_v1alpha1_vspheremachineproviderspec.yaml +++ b/config/crds/vsphere_v1alpha1_vspheremachineproviderspec.yaml @@ -23,24 +23,12 @@ spec: description: Datacenter is the name or inventory path of the datacenter where this machine's VM is created/located. type: string - datastore: - description: Datastore is the name or inventory path of the datastore where - this machine's VM is created/located. When omitted, GoVmomi's DatastoreOrDefault - method is used to determine the default datastore. It is recommended to - explicitly set this value. - type: string diskGiB: description: DiskGiB is the size of a virtual machine's disk, in GiB. Defaults to the analogue property value in the template from which this machine is cloned. format: int32 type: integer - folder: - description: Folder is the name or inventory path of the folder where this - machine's VM is created/located. When omitted, GoVmomi's FolderOrDefault - method is used to determine the default folder. It is recommended to explicitly - set this value. - type: string kind: description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client @@ -342,7 +330,7 @@ spec: properties: devices: description: Devices is the list of network devices used by the virtual - machine. + machine. TODO(akutz) Make sure at least one network matches the ClusterSpec.CloudProviderConfiguration.Network.Name items: properties: dhcp4: @@ -469,12 +457,6 @@ spec: template from which this machine is cloned. format: int32 type: integer - resourcePool: - description: ResourcePool is the name or inventory path of the resource - pool where this machine's VM is created/located. When omitted, GoVmomi's - ResourcePoolOrDefault method is used to determine the default resource - pool. It is recommended to explicitly set this value. - type: string template: description: Template is the name, inventory path, or instance UUID of the template used to clone new machines. diff --git a/docs/getting_started.md b/docs/getting_started.md index c968fe71c2..964e6524e8 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -182,7 +182,7 @@ spec: workspace: server: "" datacenter: "SDDC-Datacenter" - datastore: "WorkloadDatastore" + datastore: "DefaultDatastore" resourcePool: "Resources" folder: "vm" --- @@ -198,9 +198,6 @@ spec: apiVersion: vsphere.cluster.k8s.io/v1alpha1 kind: VsphereMachineProviderSpec datacenter: "SDDC-Datacenter" - datastore: "DefaultDatastore" - resourcePool: "Resources" - folder: "vm" network: devices: - networkName: "vm-network-1" @@ -239,9 +236,6 @@ spec: apiVersion: vsphere.cluster.k8s.io/v1alpha1 kind: VsphereMachineProviderSpec datacenter: "SDDC-Datacenter" - datastore: "DefaultDatastore" - resourcePool: "Resources" - folder: "vm" network: devices: - networkName: "vm-network-1" diff --git a/pkg/apis/vsphere/v1alpha1/cloud/types.go b/pkg/apis/vsphere/v1alpha1/cloud/types.go index 2ba7e6c3e9..b7fd932242 100644 --- a/pkg/apis/vsphere/v1alpha1/cloud/types.go +++ b/pkg/apis/vsphere/v1alpha1/cloud/types.go @@ -29,7 +29,7 @@ type Config struct { // Network is the vSphere cloud provider's network configuration. // +optional - Network NetworConfig `gcfg:"Network,omitempty" json:"network,omitempty"` + Network NetworkConfig `gcfg:"Network,omitempty" json:"network,omitempty"` // Disk is the vSphere cloud provider's disk configuration. // +optional @@ -148,8 +148,8 @@ type VCenterConfig struct { Thumbprint string `gcfg:"thumbprint,omitempty" json:"thumbprint,omitempty"` } -// NetworConfig is the network configuration for the vSphere cloud provider. -type NetworConfig struct { +// NetworkConfig is the network configuration for the vSphere cloud provider. +type NetworkConfig struct { // Name is the name of the network to which VMs are connected. // +optional Name string `gcfg:"public-network,omitempty" json:"name,omitempty"` diff --git a/pkg/apis/vsphere/v1alpha1/cloud/zz_generated.deepcopy.go b/pkg/apis/vsphere/v1alpha1/cloud/zz_generated.deepcopy.go index cde5482de3..a2cf645f85 100644 --- a/pkg/apis/vsphere/v1alpha1/cloud/zz_generated.deepcopy.go +++ b/pkg/apis/vsphere/v1alpha1/cloud/zz_generated.deepcopy.go @@ -109,17 +109,17 @@ func (in *LabelConfig) DeepCopy() *LabelConfig { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NetworConfig) DeepCopyInto(out *NetworConfig) { +func (in *NetworkConfig) DeepCopyInto(out *NetworkConfig) { *out = *in return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworConfig. -func (in *NetworConfig) DeepCopy() *NetworConfig { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkConfig. +func (in *NetworkConfig) DeepCopy() *NetworkConfig { if in == nil { return nil } - out := new(NetworConfig) + out := new(NetworkConfig) in.DeepCopyInto(out) return out } diff --git a/pkg/apis/vsphere/v1alpha1/vspheremachineproviderspec_types.go b/pkg/apis/vsphere/v1alpha1/vspheremachineproviderspec_types.go index 51f331487f..a49e7ceae4 100644 --- a/pkg/apis/vsphere/v1alpha1/vspheremachineproviderspec_types.go +++ b/pkg/apis/vsphere/v1alpha1/vspheremachineproviderspec_types.go @@ -43,24 +43,6 @@ type VsphereMachineProviderSpec struct { // Datacenter is the name or inventory path of the datacenter where this // machine's VM is created/located. Datacenter string `json:"datacenter"` - // Datastore is the name or inventory path of the datastore where this - // machine's VM is created/located. - // When omitted, GoVmomi's DatastoreOrDefault method is used to determine - // the default datastore. It is recommended to explicitly set this value. - // +optional - Datastore string `json:"datastore,omitempty"` - // ResourcePool is the name or inventory path of the resource pool where this - // machine's VM is created/located. - // When omitted, GoVmomi's ResourcePoolOrDefault method is used to determine - // the default resource pool. It is recommended to explicitly set this value. - // +optional - ResourcePool string `json:"resourcePool,omitempty"` - // Folder is the name or inventory path of the folder where this - // machine's VM is created/located. - // When omitted, GoVmomi's FolderOrDefault method is used to determine - // the default folder. It is recommended to explicitly set this value. - // +optional - Folder string `json:"folder,omitempty"` // Network is the network configuration for this machine's VM. Network NetworkSpec `json:"network"` @@ -115,6 +97,8 @@ type KubeadmConfiguration struct { // NetworkSpec defines the virtual machine's network configuration. type NetworkSpec struct { // Devices is the list of network devices used by the virtual machine. + // TODO(akutz) Make sure at least one network matches the + // ClusterSpec.CloudProviderConfiguration.Network.Name Devices []NetworkDeviceSpec `json:"devices"` // Routes is a list of optional, static routes applied to the virtual diff --git a/pkg/apis/vsphere/v1alpha1/vspheremachineproviderspec_types_test.go b/pkg/apis/vsphere/v1alpha1/vspheremachineproviderspec_types_test.go index f9df6ad28a..5a884d4ab4 100644 --- a/pkg/apis/vsphere/v1alpha1/vspheremachineproviderspec_types_test.go +++ b/pkg/apis/vsphere/v1alpha1/vspheremachineproviderspec_types_test.go @@ -35,10 +35,8 @@ func TestStorageVsphereMachineProviderSpec(t *testing.T) { Name: "foo", Namespace: "default", }, - MachineRef: "ref123", - Datacenter: "dc1", - Datastore: "ds1", - ResourcePool: "rp1", + MachineRef: "ref123", + Datacenter: "dc1", Network: NetworkSpec{ Devices: []NetworkDeviceSpec{ { diff --git a/pkg/cloud/vsphere/services/govmomi/create_test.go b/pkg/cloud/vsphere/services/govmomi/create_test.go index aae8452317..56b0d87c70 100644 --- a/pkg/cloud/vsphere/services/govmomi/create_test.go +++ b/pkg/cloud/vsphere/services/govmomi/create_test.go @@ -118,10 +118,7 @@ func TestCreate(t *testing.T) { TypeMeta: metav1.TypeMeta{ APIVersion: "vsphereproviderconfig/v1alpha1", }, - Datacenter: "", - Datastore: "", - ResourcePool: "", - Folder: "", + Datacenter: "", Network: v1alpha1.NetworkSpec{ Devices: []v1alpha1.NetworkDeviceSpec{ { diff --git a/pkg/cloud/vsphere/services/govmomi/vcenter/clone.go b/pkg/cloud/vsphere/services/govmomi/vcenter/clone.go index b7bb331ba1..dd7d7aacf9 100644 --- a/pkg/cloud/vsphere/services/govmomi/vcenter/clone.go +++ b/pkg/cloud/vsphere/services/govmomi/vcenter/clone.go @@ -43,17 +43,17 @@ func Clone(ctx *context.MachineContext, userData []byte) error { return err } - folder, err := ctx.Session.Finder.FolderOrDefault(ctx, ctx.MachineConfig.Folder) + folder, err := ctx.Session.Finder.FolderOrDefault(ctx, ctx.ClusterConfig.CloudProviderConfiguration.Workspace.Folder) if err != nil { return errors.Wrapf(err, "unable to get folder for %q", ctx) } - datastore, err := ctx.Session.Finder.DatastoreOrDefault(ctx, ctx.MachineConfig.Datastore) + datastore, err := ctx.Session.Finder.DatastoreOrDefault(ctx, ctx.ClusterConfig.CloudProviderConfiguration.Workspace.Datastore) if err != nil { return errors.Wrapf(err, "unable to get datastore for %q", ctx) } - pool, err := ctx.Session.Finder.ResourcePoolOrDefault(ctx, ctx.MachineConfig.ResourcePool) + pool, err := ctx.Session.Finder.ResourcePoolOrDefault(ctx, ctx.ClusterConfig.CloudProviderConfiguration.Workspace.ResourcePool) if err != nil { return errors.Wrapf(err, "unable to get resource pool for %q", ctx) }