Skip to content

Commit

Permalink
add new kubectl_version param
Browse files Browse the repository at this point in the history
- enables running the plugin using a non-default `kubectl` executable
- updates image `ENTRYPOINT` to support setting of `EXTRA_KUBECTL_VERSIONS` environment variable used by plugin to validate against `kubectl_version`

depends on #114
  • Loading branch information
montmanu committed Jul 18, 2019
1 parent 59cdf82 commit 2123082
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 6 deletions.
38 changes: 38 additions & 0 deletions DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ The following parameters are used to configure this plugin:
* `vars` - variables to use in `template` and `secret_template` (see [below](#available-vars) for details)
* `secrets` - credential and variables to use in `secret_template` (see [below](#secrets) for details)
* *optional* `expand_env_vars` - expand environment variables for values in `vars` for reference (defaults to `false`)
* *optional* `kubectl_version` - version of kubectl executable to use (see [below](#using-extra-kubectl-versions) for details)

### Debugging parameters

Expand Down Expand Up @@ -114,6 +115,43 @@ To use `$${IMAGE_VERSION}` or `$IMAGE_VERSION`, see the [Drone docs][environment
[expand]: https://golang.org/pkg/os/#ExpandEnv
[environment]: http://docs.drone.io/environment/

## Using "extra" `kubectl` versions

### tl;dr

To run `drone-gke` using a different version of `kubectl` than the default, set `kubectl-version` to the version you'd like to use.

For example, to use the **1.14** version of `kubectl`:

```yml
image: nytimes/drone-gke:0.9.1
kubectl_version: "1.14"
```

This will configure the plugin to execute `/google-cloud-sdk/bin/kubectl.1.14` instead of `/google-cloud-sdk/bin/kubectl` for all `kubectl` commands.

### Background

Beginning with the [`237.0.0 (2019-03-05)` release of the `gcloud` SDK](https://cloud.google.com/sdk/docs/release-notes#23700_2019-03-05), "extra" `kubectl` versions are installed automatically when `kubectl` is installed via `gcloud components install kubectl`.

These "extra" versions are installed alongside the SDK's default `kubectl` version at `/google-cloud-sdk/bin` and are named using the following pattern:

```sh
kubectl.$clientVersionMajor.$clientVersionMinor
```

To list all of the "extra" `kubectl` versions available within a particular version of `drone-gke`, you can run the following:

```sh
# list "extra" kubectl versions available with nytimes/drone-gke:0.9.1
docker run \
--rm \
--interactive \
--tty \
--entrypoint '' \
nytimes/drone-gke:0.9.1 list-extra-kubectl-versions
```

## Example reference usage

### `.drone.yml`
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ ADD bin/install-kubectl /usr/local/bin/
RUN install-kubectl && rm -f /usr/local/bin/install-kubectl

FROM cloud-sdk
ADD drone-gke /usr/local/bin/
ENTRYPOINT ["drone-gke"]
ADD drone-gke bin/entrypoint bin/list-extra-kubectl-versions /usr/local/bin/
ENTRYPOINT ["entrypoint", "drone-gke"]
6 changes: 6 additions & 0 deletions bin/entrypoint
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env sh
set -e

export EXTRA_KUBECTL_VERSIONS=$(echo $(list-extra-kubectl-versions))

exec "$@"
14 changes: 14 additions & 0 deletions bin/list-extra-kubectl-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env sh
set -e

# location to search for extra kubectl version
# defaults to default used by `gcloud components install kubectl`
gcloud_sdk_bin_dir=${1:-'/google-cloud-sdk/bin'}

# filename glob for extra kubectl versions
# defaults to default used by `gcloud components install kubectl`
extra_kubectl_glob=${2:-'kubectl.*'}

find ${gcloud_sdk_bin_dir} -type f -name ${extra_kubectl_glob} -exec basename {} \; \
| cut -d '.' -f 2,3 \
| sort -g
67 changes: 63 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,18 @@ var (
)

const (
gcloudCmd = "gcloud"
kubectlCmd = "kubectl"
timeoutCmd = "timeout"
gcloudCmd = "gcloud"
kubectlCmdName = "kubectl"
timeoutCmd = "timeout"

keyPath = "/tmp/gcloud.json"
nsPath = "/tmp/namespace.json"
templateBasePath = "/tmp"
)

// default to kubectlCmdName, can be overriden via kubectl-version param
var kubectlCmd = kubectlCmdName
var extraKubectlVersions = strings.Split(os.Getenv("EXTRA_KUBECTL_VERSIONS"), " ")
var nsTemplate = `
---
apiVersion: v1
Expand Down Expand Up @@ -167,6 +170,12 @@ func wrapMain() error {
EnvVar: "PLUGIN_WAIT_SECONDS",
Value: 0,
},
cli.StringFlag{
Name: "kubectl-version",
Usage: "optional - version of kubectl binary to use, e.g. 1.14",
EnvVar: "PLUGIN_KUBECTL_VERSION",
Value: "",
},
}

if err := app.Run(os.Args); err != nil {
Expand All @@ -192,6 +201,12 @@ func run(c *cli.Context) error {
}
}

// Use custom kubectl version if provided
kubectlVersion := c.String("kubectl-version")
if kubectlVersion != "" {
kubectlCmd = fmt.Sprintf("%s.%s", kubectlCmdName, kubectlVersion)
}

// Parse variables and secrets
vars, err := parseVars(c)
if err != nil {
Expand Down Expand Up @@ -293,9 +308,53 @@ func checkParams(c *cli.Context) error {
return fmt.Errorf("Missing required param: cluster")
}

if !isValidKubectlVersionParam(c, extraKubectlVersions) {
return fmt.Errorf(getInvalidKubectlVersionMessage(c, extraKubectlVersions))
}

return nil
}

// isValidKubectlVersionParam tests whether a given version is valid within the current environment
func isValidKubectlVersionParam(c *cli.Context, availableVersions []string) bool {
kubectlVersionParam := c.String("kubectl-version")
// using the default version
if kubectlVersionParam == "" {
return true
}

// using a custom version but no extra versions are available
if len(availableVersions) == 0 {
return false
}

// using a custom version ...
// return true if included in available extra versions; false otherwise
for _, availableVersion := range availableVersions {
if kubectlVersionParam == availableVersion {
return true
}
}
return false
}

// getInvalidKubectlVersionMessage returns an error message for invalid kubectl-version values
func getInvalidKubectlVersionMessage(c *cli.Context, availableVersions []string) string {
kubectlVersionParam := c.String("kubectl-version")
// kubectl-version is valid; return an empty string
if isValidKubectlVersionParam(c, availableVersions) {
return ""
}

// using a custom version but no extra versions are available
if len(availableVersions) == 0 {
return fmt.Sprintf("Invalid param: kubectl-version was set to %s but no extra kubectl versions are available", kubectlVersionParam)
}

// using a custom version and extra versions are available, but specified version is not included
return fmt.Sprintf("Invalid param kubectl-version: %s must be one of %s", kubectlVersionParam, strings.Join(availableVersions, ", "))
}

// getProjectFromToken gets project id from token
func getProjectFromToken(j string) string {
t := token{}
Expand Down Expand Up @@ -529,7 +588,7 @@ func setNamespace(c *cli.Context, project string, runner Runner) error {
//replace invalid char in namespace
namespace = strings.ToLower(namespace)
namespace = invalidNameRegex.ReplaceAllString(namespace, "-")

// Set the execution namespace.
log("Configuring kubectl to the %s namespace\n", namespace)

Expand Down
70 changes: 70 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,76 @@ func TestCheckParams(t *testing.T) {
assert.NoError(t, err)
}

func TestIsValidKubectlVersionParam(t *testing.T) {
// kubectl-version is NOT set (using default kubectl version)
set := flag.NewFlagSet("default-kubectl-version", 0)
c := cli.NewContext(nil, set, nil)
availableVersions := []string{}
isValid := isValidKubectlVersionParam(c, availableVersions)
assert.True(t, isValid, "expected isValidKubectlVersionParam to be true when no kubectl-version param was set")

// kubectl-version is set and extra kubectl versions are NOT available
set = flag.NewFlagSet("kubectl-version-set-no-extra-versions-available", 0)
c = cli.NewContext(nil, set, nil)
set.String("kubectl-version", "1.14", "")
availableVersions = []string{}
isValid = isValidKubectlVersionParam(c, availableVersions)
assert.False(t, isValid, "expected isValidKubectlVersionParam to be false when no extra kubectl versions are available")

// kubectl-version is set, extra kubectl versions are available, kubectl-version is included
set = flag.NewFlagSet("valid-kubectl-version", 0)
c = cli.NewContext(nil, set, nil)
set.String("kubectl-version", "1.13", "")
availableVersions = []string{"1.11", "1.12", "1.13", "1.14"}
isValid = isValidKubectlVersionParam(c, availableVersions)
assert.True(t, isValid, "expected isValidKubectlVersionParam to be true when kubectl-version is set, extra kubectl versions are available, kubectl-version is included")

// kubectl-version is set, extra kubectl versions are available, kubectl-version is NOT included
set = flag.NewFlagSet("invalid-kubectl-version", 0)
c = cli.NewContext(nil, set, nil)
set.String("kubectl-version", "9.99", "")
availableVersions = []string{"1.11", "1.12", "1.13", "1.14"}
isValid = isValidKubectlVersionParam(c, availableVersions)
assert.False(t, isValid, "expected isValidKubectlVersionParam to be false when kubectl-version is set, extra kubectl versions are available, kubectl-version is NOT included")
}

func TestGetInvalidKubectlVersionMessage(t *testing.T) {
// kubectl-version is NOT set (using default kubectl version)
set := flag.NewFlagSet("default-kubectl-version", 0)
c := cli.NewContext(nil, set, nil)
availableVersions := []string{}
expected := ""
actual := getInvalidKubectlVersionMessage(c, availableVersions)
assert.Equal(t, expected, actual, "expected getInvalidKubectlVersionMessage to return empty string when kubectl-version is NOT set")

// kubectl-version is set and extra kubectl versions are NOT available
set = flag.NewFlagSet("kubectl-version-set-no-extra-versions-available", 0)
c = cli.NewContext(nil, set, nil)
set.String("kubectl-version", "1.14", "")
availableVersions = []string{}
expected = "Invalid param: kubectl-version was set to 1.14 but no extra kubectl versions are available"
actual = getInvalidKubectlVersionMessage(c, availableVersions)
assert.Equal(t, expected, actual, "expected getInvalidKubectlVersionMessage to return matching string when kubectl-version is set and extra kubectl versions are NOT available")

// kubectl-version is set, extra kubectl versions are available, kubectl-version is included
set = flag.NewFlagSet("valid-kubectl-version", 0)
c = cli.NewContext(nil, set, nil)
set.String("kubectl-version", "1.13", "")
availableVersions = []string{"1.11", "1.12", "1.13", "1.14"}
expected = ""
actual = getInvalidKubectlVersionMessage(c, availableVersions)
assert.Equal(t, expected, actual, "expected getInvalidKubectlVersionMessage to return empty string when kubectl-version is set, extra kubectl versions are available, kubectl-version is included")

// kubectl-version is set, extra kubectl versions are available, kubectl-version is NOT included
set = flag.NewFlagSet("invalid-kubectl-version", 0)
c = cli.NewContext(nil, set, nil)
set.String("kubectl-version", "9.99", "")
availableVersions = []string{"1.11", "1.12", "1.13", "1.14"}
expected = "Invalid param kubectl-version: 9.99 must be one of 1.11, 1.12, 1.13, 1.14"
actual = getInvalidKubectlVersionMessage(c, availableVersions)
assert.Equal(t, expected, actual, "expected getInvalidKubectlVersionMessage to return matching string when kubectl-version is set, extra kubectl versions are available, kubectl-version is NOT included")
}

func TestGetProjectFromToken(t *testing.T) {
token := "{\"project_id\":\"test-project\"}"
assert.Equal(t, "test-project", getProjectFromToken(token))
Expand Down

0 comments on commit 2123082

Please sign in to comment.