Repository: kubernetes-sigs/gcp-filestore-csi-driver
Pull request #107 (2021-03-04 06:16:28)

add cloud-config and some more fixes
<!--  Thanks for sending a pull request!  Here are some tips for you:

1. If this is your first time, please read our contributor guidelines: https://git.k8s.io/community/contributors/guide#your-first-contribution and developer guide https://git.k8s.io/community/contributors/devel/development.md#development-guide
2. Follow the instructions for writing a release note: https://git.k8s.io/community/contributors/guide/release-notes.md
3. If the PR is unfinished, see how to mark it: https://git.k8s.io/community/contributors/guide/pull-requests.md#marking-unfinished-pull-requests
-->

**What type of PR is this?**
> Uncomment only one ` /kind <>` line, hit enter to put that in a new line, and remove leading whitespaces from that line:
>
> /kind api-change
> /kind bug
/kind cleanup
> /kind design
> /kind documentation
> /kind failing-test
> /kind feature
> /kind flake

**What this PR does / why we need it**:
1. Add support for cloud-config for GCE cloud provider (2bd88cd) to enable `--cloud-config=/etc/gce.conf ` args for managed GKE driver
2. Remove dependency on nodeId for controller service (c78f30c)
3. Minor fixes: a. kustomize version b. fix do-driver-build (there was a bug which did not replace the image tag) (4b18df2)

**Which issue(s) this PR fixes**:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes #

**Special notes for your reviewer**:

**Does this PR introduce a user-facing change?**:
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".
-->
```release-note
Enable support for cloud provider config
```


k8s-ci-robot: @saikat-royc: Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our [release note process](https://git.k8s.io/community/contributors/guide/release-notes.md) to remove it.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/gcp-filestore-csi-driver/pull/107#" title="Author self-approved">saikat-royc</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fgcp-filestore-csi-driver).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes-sigs/gcp-filestore-csi-driver/blob/master/OWNERS)~~ [saikat-royc]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
saikat-royc: /assign @verult  @mattcary 
saikat-royc: /test pull-gcp-filestore-csi-driver-k8s-integration

k8s-ci-robot: @saikat-royc: The specified target(s) for `/test` were not found.
The following commands are available to trigger jobs:
* `/test pull-gcp-filestore-csi-driver-e2e`
* `/test pull-gcp-filestore-csi-driver-sanity`
* `/test pull-gcp-filestore-csi-driver-unit`
* `/test pull-gcp-filestore-csi-driver-verify`
* `/test pull-gcp-filestore-csi-driver-kubernetes-integration`

Use `/test all` to run the following jobs:
* `pull-gcp-filestore-csi-driver-e2e`
* `pull-gcp-filestore-csi-driver-sanity`
* `pull-gcp-filestore-csi-driver-unit`
* `pull-gcp-filestore-csi-driver-verify`



<details>

In response to [this](https://github.com/kubernetes-sigs/gcp-filestore-csi-driver/pull/107#issuecomment-790351898):

>/test pull-gcp-filestore-csi-driver-k8s-integration
>


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
saikat-royc: /test pull-gcp-filestore-csi-driver-kubernetes-integration
mattcary: > Also I'm not sure we need hostNetwork for the filestore driver---there may be enough of the required info from the WI metadata server (?). That's probably out of scope of this PR, though.

It would be nice to document as well that hostNetwork means that WI can't be used to authorize the driver, the token source needs to be used.
k8s-ci-robot: @saikat-royc: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-gcp-filestore-csi-driver-kubernetes-integration | 4b18df22bad1a1bb10c90c42d9bdc4260380ed16 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-filestore-csi-driver/107/pull-gcp-filestore-csi-driver-kubernetes-integration/1367364161004113920/) | `/test pull-gcp-filestore-csi-driver-kubernetes-integration`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=gcp-filestore-csi-driver&pr=107). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asaikat-royc). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/gcp-filestore-csi-driver/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
saikat-royc: > It would be nice to document as well that hostNetwork means that WI can't be used to authorize the driver, the token source needs to be used.

Ack. I ll add the doc changes in a separate PR, as this one is too big already
saikat-royc: > > Also I'm not sure we need hostNetwork for the filestore driver---there may be enough of the required info from the WI metadata server (?). That's probably out of scope of this PR, though.
> 
> It would be nice to document as well that hostNetwork means that WI can't be used to authorize the driver, the token source needs to be used.

Created an internal bug to investigate hostNetwork dependency and update the readme
saikat-royc: /test pull-gcp-filestore-csi-driver-kubernetes-integration
k8s-ci-robot: @saikat-royc: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-gcp-filestore-csi-driver-kubernetes-integration | 4b18df22bad1a1bb10c90c42d9bdc4260380ed16 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-filestore-csi-driver/107/pull-gcp-filestore-csi-driver-kubernetes-integration/1367364161004113920/) | `/test pull-gcp-filestore-csi-driver-kubernetes-integration`
pull-gcp-filestore-csi-driver-verify | ac0ac2ee374d2f1b960cf0a441460d62f1f1317d | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-filestore-csi-driver/107/pull-gcp-filestore-csi-driver-verify/1367627933333590016/) | `/test pull-gcp-filestore-csi-driver-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=gcp-filestore-csi-driver&pr=107). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asaikat-royc). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/gcp-filestore-csi-driver/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->

Patch:
diff --git a/cmd/main.go b/cmd/main.go
index 7c859e69..2da15165 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -17,22 +17,26 @@ limitations under the License.
 package main
 
 import (
+	"context"
 	"flag"
 	"os"
 
 	"github.com/golang/glog"
 
+	"k8s.io/klog"
 	"k8s.io/utils/mount"
 	cloud "sigs.k8s.io/gcp-filestore-csi-driver/pkg/cloud_provider"
+	"sigs.k8s.io/gcp-filestore-csi-driver/pkg/cloud_provider/metadata"
+	metadataservice "sigs.k8s.io/gcp-filestore-csi-driver/pkg/cloud_provider/metadata"
 	driver "sigs.k8s.io/gcp-filestore-csi-driver/pkg/csi_driver"
 )
 
 var (
-	endpoint      = flag.String("endpoint", "unix:/tmp/csi.sock", "CSI endpoint")
-	nodeID        = flag.String("nodeid", "", "node id")
-	runController = flag.Bool("controller", false, "run controller service")
-	runNode       = flag.Bool("node", false, "run node service")
-
+	endpoint            = flag.String("endpoint", "unix:/tmp/csi.sock", "CSI endpoint")
+	nodeID              = flag.String("nodeid", "", "node id")
+	runController       = flag.Bool("controller", false, "run controller service")
+	runNode             = flag.Bool("node", false, "run node service")
+	cloudConfigFilePath = flag.String("cloud-config", "", "Path to GCE cloud provider config")
 	// This is set at compile time
 	version = "unknown"
 )
@@ -45,11 +49,20 @@ func main() {
 
 	var provider *cloud.Cloud
 	var err error
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+	var meta metadata.Service
 	if *runController {
-		provider, err = cloud.NewCloud(version)
+		provider, err = cloud.NewCloud(ctx, version, *cloudConfigFilePath)
 	} else {
-		// For driver running in worker nodes, initialize cloud provider with only medata service.
-		provider, err = cloud.NewCloudWithMetadataService()
+		if *nodeID == "" {
+			klog.Fatalf("nodeid cannot be empty for node service")
+		}
+
+		meta, err = metadataservice.NewMetadataService()
+		if err != nil {
+			klog.Fatalf("Failed to set up metadata service: %v", err)
+		}
 	}
 
 	if err != nil {
@@ -58,13 +71,14 @@ func main() {
 
 	mounter := mount.New("")
 	config := &driver.GCFSDriverConfig{
-		Name:          driverName,
-		Version:       version,
-		NodeID:        *nodeID,
-		RunController: *runController,
-		RunNode:       *runNode,
-		Mounter:       mounter,
-		Cloud:         provider,
+		Name:            driverName,
+		Version:         version,
+		NodeID:          *nodeID,
+		RunController:   *runController,
+		RunNode:         *runNode,
+		Mounter:         mounter,
+		Cloud:           provider,
+		MetadataService: meta,
 	}
 
 	gcfsDriver, err := driver.NewGCFSDriver(config)
diff --git a/deploy/kubernetes/install_kustomize.sh b/deploy/kubernetes/install_kustomize.sh
index f8fe7478..645b761e 100755
--- a/deploy/kubernetes/install_kustomize.sh
+++ b/deploy/kubernetes/install_kustomize.sh
@@ -50,7 +50,7 @@ curl -s https://api.github.com/repos/kubernetes-sigs/kustomize/releases |\
   grep browser_download |\
   grep $opsys |\
   cut -d '"' -f 4 |\
-  grep /kustomize/v3.8.6 |\
+  grep /kustomize/v4.0.4 |\
   sort | tail -n 1 |\
   xargs curl -s -O -L
 
diff --git a/go.mod b/go.mod
index 634f81b7..e7973b52 100644
--- a/go.mod
+++ b/go.mod
@@ -27,6 +27,7 @@ require (
 	google.golang.org/grpc v1.32.0
 	google.golang.org/protobuf v1.25.0
 	gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
+	gopkg.in/gcfg.v1 v1.2.0
 	gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
 	k8s.io/apimachinery v0.19.2
 	k8s.io/client-go v11.0.1-0.20190805182717-6502b5e7b1b5+incompatible
diff --git a/go.sum b/go.sum
index 1fba9a6e..3310935f 100644
--- a/go.sum
+++ b/go.sum
@@ -1778,6 +1778,7 @@ gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/gcfg.v1 v1.2.0 h1:0HIbH907iBTAntm+88IJV2qmJALDAh8sPekI9Vc1fm0=
 gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
 gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
 gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
@@ -1803,6 +1804,7 @@ gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQb
 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.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
+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.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/pkg/cloud_provider/cloud.go b/pkg/cloud_provider/cloud.go
index 64909533..3b5a8a44 100644
--- a/pkg/cloud_provider/cloud.go
+++ b/pkg/cloud_provider/cloud.go
@@ -17,40 +17,159 @@ limitations under the License.
 package cloud
 
 import (
+	"context"
 	"fmt"
+	"net/http"
+	"os"
+	"time"
 
+	"cloud.google.com/go/compute/metadata"
+	gcpmeta "cloud.google.com/go/compute/metadata"
+	"golang.org/x/oauth2"
+	"golang.org/x/oauth2/google"
+	"google.golang.org/api/compute/v1"
+	"gopkg.in/gcfg.v1"
+	"k8s.io/apimachinery/pkg/util/wait"
+	"k8s.io/klog"
 	"sigs.k8s.io/gcp-filestore-csi-driver/pkg/cloud_provider/file"
-	"sigs.k8s.io/gcp-filestore-csi-driver/pkg/cloud_provider/metadata"
 )
 
 type Cloud struct {
-	File file.Service
-	Meta metadata.Service
+	File    file.Service
+	Project string
+	Zone    string
 }
 
-func NewCloud(version string) (*Cloud, error) {
-	file, err := file.NewGCFSService(version)
+type ConfigFile struct {
+	Global ConfigGlobal `gcfg:"global"`
+}
+
+type ConfigGlobal struct {
+	TokenURL  string `gcfg:"token-url"`
+	TokenBody string `gcfg:"token-body"`
+	ProjectId string `gcfg:"project-id"`
+	Zone      string `gcfg:"zone"`
+}
+
+func NewCloud(ctx context.Context, version string, configPath string) (*Cloud, error) {
+	configFile, err := maybeReadConfig(configPath)
 	if err != nil {
-		return nil, fmt.Errorf("failed to initialize Filestore service: %v", err)
+		return nil, err
 	}
-	meta, err := metadata.NewMetadataService()
+
+	tokenSource, err := generateTokenSource(ctx, configFile)
 	if err != nil {
-		return nil, fmt.Errorf("failed to initialize Metadata service: %v", err)
+		return nil, err
 	}
 
+	client, err := newOauthClient(ctx, tokenSource)
+	if err != nil {
+		return nil, err
+	}
+
+	file, err := file.NewGCFSService(version, client)
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize Filestore service: %v", err)
+	}
+
+	project, zone, err := getProjectAndZone(configFile)
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize project information: %v", err)
+	}
 	return &Cloud{
-		File: file,
-		Meta: meta,
+		File:    file,
+		Project: project,
+		Zone:    zone,
 	}, nil
 }
 
-func NewCloudWithMetadataService() (*Cloud, error) {
-	meta, err := metadata.NewMetadataService()
+func maybeReadConfig(configPath string) (*ConfigFile, error) {
+	if configPath == "" {
+		return nil, nil
+	}
+
+	reader, err := os.Open(configPath)
 	if err != nil {
-		return nil, fmt.Errorf("failed to initialize Metadata service: %v", err)
+		return nil, fmt.Errorf("couldn't open cloud provider configuration at %s: %v", configPath, err)
 	}
+	defer reader.Close()
 
-	return &Cloud{
-		Meta: meta,
-	}, nil
+	cfg := &ConfigFile{}
+	if err := gcfg.FatalOnly(gcfg.ReadInto(cfg, reader)); err != nil {
+		return nil, fmt.Errorf("couldn't read cloud provider configuration at %s: %v", configPath, err)
+	}
+	klog.Infof("Config file read %#v", cfg)
+	return cfg, nil
+}
+
+func generateTokenSource(ctx context.Context, configFile *ConfigFile) (oauth2.TokenSource, error) {
+	// If configFile.Global.TokenURL is defined use AltTokenSource
+	if configFile != nil && configFile.Global.TokenURL != "" && configFile.Global.TokenURL != "nil" {
+		tokenSource := NewAltTokenSource(configFile.Global.TokenURL, configFile.Global.TokenBody)
+		klog.Infof("Using AltTokenSource %#v", tokenSource)
+		return tokenSource, nil
+	}
+
+	// Use DefaultTokenSource
+	tokenSource, err := google.DefaultTokenSource(
+		ctx,
+		compute.CloudPlatformScope)
+
+	// DefaultTokenSource relies on GOOGLE_APPLICATION_CREDENTIALS env var being set.
+	if gac, ok := os.LookupEnv("GOOGLE_APPLICATION_CREDENTIALS"); ok {
+		klog.Infof("GOOGLE_APPLICATION_CREDENTIALS env var set %v", gac)
+	} else {
+		klog.Warningf("GOOGLE_APPLICATION_CREDENTIALS env var not set")
+	}
+	klog.Infof("Using DefaultTokenSource %#v", tokenSource)
+
+	return tokenSource, err
+}
+
+func newOauthClient(ctx context.Context, tokenSource oauth2.TokenSource) (*http.Client, error) {
+	if err := wait.PollImmediate(5*time.Second, 30*time.Second, func() (bool, error) {
+		if _, err := tokenSource.Token(); err != nil {
+			klog.Errorf("error fetching initial token: %v", err)
+			return false, nil
+		}
+		return true, nil
+	}); err != nil {
+		return nil, err
+	}
+
+	return oauth2.NewClient(ctx, tokenSource), nil
+}
+
+// getProjectAndZone fetches project and zone information from either the configFile or metadata server.
+// The lookup is first done in configFile contents and then metadata server.
+func getProjectAndZone(config *ConfigFile) (string, string, error) {
+	var err error
+	var zone string
+	if config == nil || config.Global.Zone == "" {
+		zone, err = metadata.Zone()
+		if err != nil {
+			return "", "", err
+		}
+		klog.Infof("Using GCP zone from the Metadata server: %q", zone)
+	} else {
+		zone = config.Global.Zone
+		klog.Infof("Using GCP zone from the local GCE cloud provider config file: %q", zone)
+	}
+
+	var projectID string
+	if config == nil || config.Global.ProjectId == "" {
+		// Project ID is not available from the local GCE cloud provider config file.
+		// This could happen if the driver is not running in the master VM.
+		// Defaulting to project ID from the Metadata server.
+		projectID, err = gcpmeta.ProjectID()
+		if err != nil {
+			return "", "", err
+		}
+		klog.Infof("Using GCP project ID %q from the Metadata server", projectID)
+	} else {
+		projectID = config.Global.ProjectId
+		klog.Infof("Using GCP project ID %q from the local GCE cloud provider config file: %#v", config, projectID)
+	}
+
+	return projectID, zone, nil
 }
diff --git a/pkg/cloud_provider/fake.go b/pkg/cloud_provider/fake.go
index 261c2656..d6934b60 100644
--- a/pkg/cloud_provider/fake.go
+++ b/pkg/cloud_provider/fake.go
@@ -20,7 +20,6 @@ import (
 	"fmt"
 
 	"sigs.k8s.io/gcp-filestore-csi-driver/pkg/cloud_provider/file"
-	"sigs.k8s.io/gcp-filestore-csi-driver/pkg/cloud_provider/metadata"
 )
 
 func NewFakeCloud() (*Cloud, error) {
@@ -28,13 +27,10 @@ func NewFakeCloud() (*Cloud, error) {
 	if err != nil {
 		return nil, fmt.Errorf("failed to initialize Filestore service: %v", err)
 	}
-	meta, err := metadata.NewFakeService()
-	if err != nil {
-		return nil, fmt.Errorf("failed to initialize Metadata service: %v", err)
-	}
 
 	return &Cloud{
-		File: file,
-		Meta: meta,
+		File:    file,
+		Project: "test-project",
+		Zone:    "us-central1-c",
 	}, nil
 }
diff --git a/pkg/cloud_provider/file/file.go b/pkg/cloud_provider/file/file.go
index 424fb025..7990cc65 100644
--- a/pkg/cloud_provider/file/file.go
+++ b/pkg/cloud_provider/file/file.go
@@ -19,6 +19,7 @@ package file
 import (
 	"context"
 	"fmt"
+	"net/http"
 	"regexp"
 	"runtime"
 	"strings"
@@ -26,6 +27,7 @@ import (
 
 	"github.com/golang/glog"
 	"google.golang.org/api/googleapi"
+	"google.golang.org/api/option"
 	"k8s.io/apimachinery/pkg/util/wait"
 	"sigs.k8s.io/gcp-filestore-csi-driver/pkg/util"
 
@@ -93,20 +95,19 @@ const (
 
 var _ Service = &gcfsServiceManager{}
 
-func NewGCFSService(version string) (Service, error) {
-
+func NewGCFSService(version string, client *http.Client) (Service, error) {
 	ctx := context.Background()
-	fileService, err := filev1.NewService(ctx)
+	fileService, err := filev1.NewService(ctx, option.WithHTTPClient(client))
 	if err != nil {
 		return nil, err
 	}
 	fileService.UserAgent = fmt.Sprintf("Google Cloud Filestore CSI Driver/%s (%s %s)", version, runtime.GOOS, runtime.GOARCH)
 
-	fileV1beta1Service, err := filev1beta1.NewService(ctx)
+	fileV1beta1Service, err := filev1beta1.NewService(ctx, option.WithHTTPClient(client))
 	if err != nil {
 		return nil, err
 	}
-
+	fileV1beta1Service.UserAgent = fmt.Sprintf("Google Cloud Filestore CSI Driver/%s (%s %s)", version, runtime.GOOS, runtime.GOARCH)
 	return &gcfsServiceManager{
 		fileService:              fileService,
 		instancesService:         filev1.NewProjectsLocationsInstancesService(fileService),
diff --git a/pkg/cloud_provider/token_source.go b/pkg/cloud_provider/token_source.go
new file mode 100644
index 00000000..d142ceef
--- /dev/null
+++ b/pkg/cloud_provider/token_source.go
@@ -0,0 +1,86 @@
+/*
+Copyright 2020 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 (
+	"encoding/json"
+	"net/http"
+	"strings"
+	"time"
+
+	"k8s.io/client-go/util/flowcontrol"
+
+	"golang.org/x/oauth2"
+	"golang.org/x/oauth2/google"
+	"google.golang.org/api/googleapi"
+)
+
+const (
+	// Max QPS to allow through to the token URL.
+	tokenURLQPS = .05 // back off to once every 20 seconds when failing
+	// Maximum burst of requests to token URL before limiting.
+	tokenURLBurst = 3
+)
+
+// AltTokenSource is the structure holding the data for the functionality needed to generates tokens
+type AltTokenSource struct {
+	oauthClient *http.Client
+	tokenURL    string
+	tokenBody   string
+	throttle    flowcontrol.RateLimiter
+}
+
+// Token returns a token which may be used for authentication
+func (a *AltTokenSource) Token() (*oauth2.Token, error) {
+	a.throttle.Accept()
+	return a.token()
+}
+
+func (a *AltTokenSource) token() (*oauth2.Token, error) {
+	req, err := http.NewRequest("POST", a.tokenURL, strings.NewReader(a.tokenBody))
+	if err != nil {
+		return nil, err
+	}
+	res, err := a.oauthClient.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer res.Body.Close()
+	if err := googleapi.CheckResponse(res); err != nil {
+		return nil, err
+	}
+	var tok struct {
+		AccessToken string    `json:"accessToken"`
+		ExpireTime  time.Time `json:"expireTime"`
+	}
+	if err := json.NewDecoder(res.Body).Decode(&tok); err != nil {
+		return nil, err
+	}
+	return &oauth2.Token{
+		AccessToken: tok.AccessToken,
+		Expiry:      tok.ExpireTime,
+	}, nil
+}
+
+// NewAltTokenSource constructs a new alternate token source for generating tokens.
+func NewAltTokenSource(tokenURL, tokenBody string) oauth2.TokenSource {
+	client := oauth2.NewClient(oauth2.NoContext, google.ComputeTokenSource(""))
+	a := &AltTokenSource{
+		oauthClient: client,
+		tokenURL:    tokenURL,
+		tokenBody:   tokenBody,
+		throttle:    flowcontrol.NewTokenBucketRateLimiter(tokenURLQPS, tokenURLBurst),
+	}
+	return oauth2.ReuseTokenSource(nil, a)
+}
diff --git a/pkg/csi_driver/controller.go b/pkg/csi_driver/controller.go
index 4fbd0f6d..b5a646a6 100644
--- a/pkg/csi_driver/controller.go
+++ b/pkg/csi_driver/controller.go
@@ -25,8 +25,8 @@ import (
 	"golang.org/x/net/context"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
+	cloud "sigs.k8s.io/gcp-filestore-csi-driver/pkg/cloud_provider"
 	"sigs.k8s.io/gcp-filestore-csi-driver/pkg/cloud_provider/file"
-	"sigs.k8s.io/gcp-filestore-csi-driver/pkg/cloud_provider/metadata"
 	"sigs.k8s.io/gcp-filestore-csi-driver/pkg/util"
 )
 
@@ -79,7 +79,7 @@ type controllerServer struct {
 type controllerServerConfig struct {
 	driver      *GCFSDriver
 	fileService file.Service
-	metaService metadata.Service
+	cloud       *cloud.Cloud
 	ipAllocator *util.IPAllocator
 }
 
@@ -236,7 +236,7 @@ func (s *controllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVolu
 		return &csi.DeleteVolumeResponse{}, nil
 	}
 
-	filer.Project = s.config.metaService.GetProject()
+	filer.Project = s.config.cloud.Project
 	err = s.config.fileService.DeleteInstance(ctx, filer)
 	if err != nil {
 		return nil, status.Error(codes.Internal, err.Error())
@@ -263,7 +263,7 @@ func (s *controllerServer) ValidateVolumeCapabilities(ctx context.Context, req *
 		return nil, status.Error(codes.NotFound, err.Error())
 	}
 
-	filer.Project = s.config.metaService.GetProject()
+	filer.Project = s.config.cloud.Project
 	newFiler, err := s.config.fileService.GetInstance(ctx, filer)
 	if err != nil && !file.IsNotFoundErr(err) {
 		return nil, status.Error(codes.Internal, err.Error())
@@ -358,7 +358,7 @@ func (s *controllerServer) generateNewFileInstance(name string, capBytes int64,
 		}
 	}
 	return &file.ServiceInstance{
-		Project:  s.config.metaService.GetProject(),
+		Project:  s.config.cloud.Project,
 		Name:     name,
 		Location: location,
 		Tier:     tier,
@@ -414,7 +414,7 @@ func (s *controllerServer) ControllerExpandVolume(ctx context.Context, req *csi.
 		return nil, err
 	}
 
-	filer.Project = s.config.metaService.GetProject()
+	filer.Project = s.config.cloud.Project
 	filer.Volume.SizeBytes = reqBytes
 	newfiler, err := s.config.fileService.ResizeInstance(ctx, filer)
 	if err != nil {
@@ -430,7 +430,7 @@ func (s *controllerServer) ControllerExpandVolume(ctx context.Context, req *csi.
 
 func (s *controllerServer) pickZone(top *csi.TopologyRequirement) (string, error) {
 	if top == nil {
-		return s.config.metaService.GetZone(), nil
+		return s.config.cloud.Zone, nil
 	}
 
 	return pickZoneFromTopology(top)
@@ -546,7 +546,7 @@ func (s *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateSn
 		glog.Errorf("Failed to get instance for volumeID %v snapshot, error: %v", volumeID, err)
 		return nil, status.Error(codes.Internal, err.Error())
 	}
-	filer.Project = s.config.metaService.GetProject()
+	filer.Project = s.config.cloud.Project
 	// If parameters are empty we assume 'backup' type by default.
 	if req.GetParameters() != nil {
 		if _, err := util.IsSnapshotTypeSupported(req.GetParameters()); err != nil {
diff --git a/pkg/csi_driver/gcfs_driver.go b/pkg/csi_driver/gcfs_driver.go
index 8adf6f06..ca645ac8 100644
--- a/pkg/csi_driver/gcfs_driver.go
+++ b/pkg/csi_driver/gcfs_driver.go
@@ -25,16 +25,18 @@ import (
 	"google.golang.org/grpc/status"
 	"k8s.io/utils/mount"
 	cloud "sigs.k8s.io/gcp-filestore-csi-driver/pkg/cloud_provider"
+	metadataservice "sigs.k8s.io/gcp-filestore-csi-driver/pkg/cloud_provider/metadata"
 )
 
 type GCFSDriverConfig struct {
-	Name          string          // Driver name
-	Version       string          // Driver version
-	NodeID        string          // Node name
-	RunController bool            // Run CSI controller service
-	RunNode       bool            // Run CSI node service
-	Mounter       mount.Interface // Mount library
-	Cloud         *cloud.Cloud    // Cloud provider
+	Name            string          // Driver name
+	Version         string          // Driver version
+	NodeID          string          // Node name
+	RunController   bool            // Run CSI controller service
+	RunNode         bool            // Run CSI node service
+	Mounter         mount.Interface // Mount library
+	Cloud           *cloud.Cloud    // Cloud provider
+	MetadataService metadataservice.Service
 }
 
 type GCFSDriver struct {
@@ -58,9 +60,6 @@ func NewGCFSDriver(config *GCFSDriverConfig) (*GCFSDriver, error) {
 	if config.Version == "" {
 		return nil, fmt.Errorf("driver version missing")
 	}
-	if config.NodeID == "" {
-		return nil, fmt.Errorf("node id missing")
-	}
 	if config.RunController == false && config.RunNode == false {
 		return nil, fmt.Errorf("must run at least one controller or node service")
 	}
@@ -85,7 +84,7 @@ func NewGCFSDriver(config *GCFSDriverConfig) (*GCFSDriver, error) {
 		nscap := []csi.NodeServiceCapability_RPC_Type{
 			csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
 		}
-		driver.ns = newNodeServer(driver, config.Mounter, config.Cloud.Meta)
+		driver.ns = newNodeServer(driver, config.Mounter, config.MetadataService)
 		driver.addNodeServiceCapabilities(nscap)
 	}
 	if config.RunController {
@@ -100,7 +99,7 @@ func NewGCFSDriver(config *GCFSDriverConfig) (*GCFSDriver, error) {
 		driver.cs = newControllerServer(&controllerServerConfig{
 			driver:      driver,
 			fileService: config.Cloud.File,
-			metaService: config.Cloud.Meta,
+			cloud:       config.Cloud,
 		})
 	}
 
diff --git a/vendor/gopkg.in/gcfg.v1/LICENSE b/vendor/gopkg.in/gcfg.v1/LICENSE
new file mode 100644
index 00000000..87a5cede
--- /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 00000000..1ff233a5
--- /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 00000000..32f3e9d6
--- /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-<sectionname>" (or by setting values in the corresponding struct
+// field "Default_<sectionname>").
+//
+// 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 00000000..853c7602
--- /dev/null
+++ b/vendor/gopkg.in/gcfg.v1/errors.go
@@ -0,0 +1,41 @@
+package gcfg
+
+import (
+	"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 extraData struct {
+	section    string
+	subsection *string
+	variable   *string
+}
+
+func (e extraData) Error() string {
+	s := "can't store data at section \"" + e.section + "\""
+	if e.subsection != nil {
+		s += ", subsection \"" + *e.subsection + "\""
+	}
+	if e.variable != nil {
+		s += ", variable \"" + *e.variable + "\""
+	}
+	return s
+}
+
+var _ error = extraData{}
diff --git a/vendor/gopkg.in/gcfg.v1/go1_0.go b/vendor/gopkg.in/gcfg.v1/go1_0.go
new file mode 100644
index 00000000..66702107
--- /dev/null
+++ b/vendor/gopkg.in/gcfg.v1/go1_0.go
@@ -0,0 +1,7 @@
+// +build !go1.2
+
+package gcfg
+
+type textUnmarshaler interface {
+	UnmarshalText(text []byte) error
+}
diff --git a/vendor/gopkg.in/gcfg.v1/go1_2.go b/vendor/gopkg.in/gcfg.v1/go1_2.go
new file mode 100644
index 00000000..6f5843bc
--- /dev/null
+++ b/vendor/gopkg.in/gcfg.v1/go1_2.go
@@ -0,0 +1,9 @@
+// +build go1.2
+
+package gcfg
+
+import (
+	"encoding"
+)
+
+type textUnmarshaler encoding.TextUnmarshaler
diff --git a/vendor/gopkg.in/gcfg.v1/read.go b/vendor/gopkg.in/gcfg.v1/read.go
new file mode 100644
index 00000000..8400cf12
--- /dev/null
+++ b/vendor/gopkg.in/gcfg.v1/read.go
@@ -0,0 +1,238 @@
+package gcfg
+
+import (
+	"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'}
+
+// 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
+			}
+		}
+	}
+	panic("never reached")
+}
+
+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.
+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
+	}
+	fset := token.NewFileSet()
+	file := fset.AddFile(filename, fset.Base(), len(src))
+	return readInto(config, fset, file, 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 00000000..1a3c0f65
--- /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 "<unknown position>"
+		// 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 00000000..bbbdbf53
--- /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.error(offs, "unquoted '\\' must be followed by new line")
+				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 00000000..e85ec155
--- /dev/null
+++ b/vendor/gopkg.in/gcfg.v1/set.go
@@ -0,0 +1,333 @@
+package gcfg
+
+import (
+	"bytes"
+	"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.(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(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)
+		err = ge.EncodeValue(dfltField)
+		if err != nil {
+			return pv, err
+		}
+		gd := gob.NewDecoder(bytes.NewReader(b.Bytes()))
+		err = gd.DecodeValue(pv.Elem())
+		if 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)
+	if !vSect.IsValid() {
+		err := extraData{section: sect}
+		return c.Collect(err)
+	}
+	isSubsect := vSect.Kind() == reflect.Map
+	if subsectPass != isSubsect {
+		return nil
+	}
+	if isSubsect {
+		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
+			pv, err = newValue(sect, vCfg, vType)
+			if 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 != "" {
+		err := extraData{section: sect, subsection: &sub}
+		return c.Collect(err)
+	}
+	// 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)
+	if !vVar.IsValid() {
+		var err error
+		if isSubsect {
+			err = extraData{section: sect, subsection: &sub, variable: &name}
+		} else {
+			err = extraData{section: sect, variable: &name}
+		}
+		return c.Collect(err)
+	}
+	// 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 err
+		}
+	}
+	if !ok {
+		// in case all setters returned errUnsupportedType
+		return err
+	}
+	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 00000000..fc45c1e7
--- /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 00000000..4adc8f9e
--- /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 00000000..b3c7c83f
--- /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 00000000..8dcae0d8
--- /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 00000000..9f9c345f
--- /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 00000000..1a0c7ef4
--- /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 00000000..af7e75c1
--- /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 00000000..db2f6ed3
--- /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 00000000..d65f7e9d
--- /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 00000000..974212ba
--- /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 00000000..b849d1e3
--- /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 8e8143f4..417f2fcc 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -295,10 +295,18 @@ google.golang.org/protobuf/types/known/timestamppb
 google.golang.org/protobuf/types/known/wrapperspb
 # gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b
 ## explicit
+# gopkg.in/gcfg.v1 v1.2.0
+## explicit
+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.3.0
 gopkg.in/yaml.v2
 # gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776


Test patch:
diff --git a/pkg/csi_driver/controller_test.go b/pkg/csi_driver/controller_test.go
index 75908cab..8f0f740d 100644
--- a/pkg/csi_driver/controller_test.go
+++ b/pkg/csi_driver/controller_test.go
@@ -23,8 +23,8 @@ import (
 	csi "github.com/container-storage-interface/spec/lib/go/csi"
 	"golang.org/x/net/context"
 	"k8s.io/apimachinery/pkg/util/sets"
+	cloud "sigs.k8s.io/gcp-filestore-csi-driver/pkg/cloud_provider"
 	"sigs.k8s.io/gcp-filestore-csi-driver/pkg/cloud_provider/file"
-	"sigs.k8s.io/gcp-filestore-csi-driver/pkg/cloud_provider/metadata"
 	"sigs.k8s.io/gcp-filestore-csi-driver/pkg/util"
 )
 
@@ -39,19 +39,19 @@ const (
 )
 
 func initTestController(t *testing.T) csi.ControllerServer {
-	metaService, err := metadata.NewFakeService()
-	if err != nil {
-		t.Fatalf("failed to initialize metadata service: %v", err)
-	}
 	fileService, err := file.NewFakeService()
 	if err != nil {
 		t.Fatalf("failed to initialize GCFS service: %v", err)
 	}
 
+	cloudProvider, err := cloud.NewFakeCloud()
+	if err != nil {
+		t.Fatalf("Failed to get cloud provider: %v", err)
+	}
 	return newControllerServer(&controllerServerConfig{
 		driver:      initTestDriver(t),
-		metaService: metaService,
 		fileService: fileService,
+		cloud:       cloudProvider,
 	})
 }
 
diff --git a/test/k8s-integration/main.go b/test/k8s-integration/main.go
index 65a60fa6..91aebe21 100644
--- a/test/k8s-integration/main.go
+++ b/test/k8s-integration/main.go
@@ -71,7 +71,7 @@ var (
 )
 
 const (
-	fsImagePlaceholder      = "gcr.io/k8s-staging-cloud-provider-gcp/gcp-filestore-csi-driver"
+	fsImagePlaceholder      = "k8s.gcr.io/cloud-provider-gcp/gcp-filestore-csi-driver"
 	externalDriverNamespace = "gcp-filestore-csi-driver"
 	// If the network name is changed, the same network needs to be provided in storage class template passed in 'storageClassFiles' flag.
 	gceInstanceNetwork = "csi-filestore-test-network"
diff --git a/test/sanity/sanity_test.go b/test/sanity/sanity_test.go
index 957dab7a..7dd3ad24 100644
--- a/test/sanity/sanity_test.go
+++ b/test/sanity/sanity_test.go
@@ -22,6 +22,7 @@ import (
 	"google.golang.org/grpc"
 	"k8s.io/utils/mount"
 	cloud "sigs.k8s.io/gcp-filestore-csi-driver/pkg/cloud_provider"
+	"sigs.k8s.io/gcp-filestore-csi-driver/pkg/cloud_provider/metadata"
 	driver "sigs.k8s.io/gcp-filestore-csi-driver/pkg/csi_driver"
 )
 
@@ -57,14 +58,19 @@ func TestSanity(t *testing.T) {
 	}
 	mounter := &mount.FakeMounter{MountPoints: []mount.MountPoint{}}
 
+	meta, err := metadata.NewFakeService()
+	if err != nil {
+		t.Fatalf("Failed to get metadata service: %v", err)
+	}
 	driverConfig := &driver.GCFSDriverConfig{
-		Name:          driverName,
-		Version:       driverVersion,
-		NodeID:        nodeID,
-		RunController: true,
-		RunNode:       true,
-		Mounter:       mounter,
-		Cloud:         cloudProvider,
+		Name:            driverName,
+		Version:         driverVersion,
+		NodeID:          nodeID,
+		RunController:   true,
+		RunNode:         true,
+		Mounter:         mounter,
+		Cloud:           cloudProvider,
+		MetadataService: meta,
 	}
 	gcfsDriver, err := driver.NewGCFSDriver(driverConfig)
 	if err != nil {