Skip to content

Commit

Permalink
cluster: add the ability to load a specific kubernetes version on kind.
Browse files Browse the repository at this point in the history
Fixes #66 (#67)
  • Loading branch information
nicks authored Dec 4, 2020
1 parent 7c613ee commit 6cb903e
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 4 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.14
require (
cloud.google.com/go v0.56.0 // indirect
github.com/Microsoft/go-winio v0.4.14 // indirect
github.com/blang/semver/v4 v4.0.0
github.com/containerd/containerd v1.4.1 // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v17.12.0-ce-rc1.0.20200730172259-9f28837c1d93+incompatible
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/blang/semver v1.1.0 h1:ol1rO7QQB5uy7umSNV7VAmLugfLRD+17sYJujRNYPhg=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
Expand Down
92 changes: 92 additions & 0 deletions pkg/cluster/admin_kind.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os/exec"
"strings"

"github.com/blang/semver/v4"
"github.com/pkg/errors"
"github.com/tilt-dev/ctlptl/pkg/api"
"github.com/tilt-dev/localregistry-go"
Expand Down Expand Up @@ -49,6 +50,18 @@ func (a *kindAdmin) Create(ctx context.Context, desired *api.Cluster, registry *
kindName := strings.TrimPrefix(clusterName, "kind-")

args := []string{"create", "cluster", "--name", kindName}
if desired.KubernetesVersion != "" {
kindVersion, err := a.getKindVersion(ctx)
if err != nil {
return errors.Wrap(err, "creating cluster")
}

node, err := a.getNodeImage(ctx, kindVersion, desired.KubernetesVersion)
if err != nil {
return errors.Wrap(err, "creating cluster")
}
args = append(args, "--image", node)
}

// TODO(nick): Let the user pass in their own Kind configuration.
in := strings.NewReader("")
Expand Down Expand Up @@ -125,3 +138,82 @@ func (a *kindAdmin) Delete(ctx context.Context, config *api.Cluster) error {
}
return nil
}

func (a *kindAdmin) getNodeImage(ctx context.Context, kindVersion, k8sVersion string) (string, error) {
nodeTable, ok := kindK8sNodeTable[kindVersion]
if !ok {
return "", fmt.Errorf("No available kindest/node versions for kind version %s.\n"+
"Please file an issue: https://github.com/tilt-dev/ctlptl/issues/new", kindVersion)
}

// Kind doesn't maintain Kubernetes nodes for every patch version, so just get the closest
// major/minor patch.
k8sVersionParsed, err := semver.ParseTolerant(k8sVersion)
if err != nil {
return "", fmt.Errorf("parsing kubernetesVersion: %v", err)
}

simplifiedK8sVersion := fmt.Sprintf("%d.%d", k8sVersionParsed.Major, k8sVersionParsed.Minor)
node, ok := nodeTable[simplifiedK8sVersion]
if !ok {
return "", fmt.Errorf("Kind %s does not support Kubernetes v%s", kindVersion, simplifiedK8sVersion)
}
return node, nil
}

func (a *kindAdmin) getKindVersion(ctx context.Context) (string, error) {
cmd := exec.CommandContext(ctx, "kind", "version")
out, err := cmd.Output()
if err != nil {
return "", errors.Wrap(err, "kind version")
}

parts := strings.Split(string(out), " ")
if len(parts) < 2 {
return "", fmt.Errorf("parsing kind version output: %s", string(out))
}

return parts[1], nil
}

// This table must be built up manually from the Kind release notes each
// time a new Kind version is released :\
var kindK8sNodeTable = map[string]map[string]string{
"v0.9.0": map[string]string{
"1.19": "kindest/node:v1.19.1@sha256:98cf5288864662e37115e362b23e4369c8c4a408f99cbc06e58ac30ddc721600",
"1.18": "kindest/node:v1.18.8@sha256:f4bcc97a0ad6e7abaf3f643d890add7efe6ee4ab90baeb374b4f41a4c95567eb",
"1.17": "kindest/node:v1.17.11@sha256:5240a7a2c34bf241afb54ac05669f8a46661912eab05705d660971eeb12f6555",
"1.16": "kindest/node:v1.16.15@sha256:a89c771f7de234e6547d43695c7ab047809ffc71a0c3b65aa54eda051c45ed20",
"1.15": "kindest/node:v1.15.12@sha256:d9b939055c1e852fe3d86955ee24976cab46cba518abcb8b13ba70917e6547a6",
"1.14": "kindest/node:v1.14.10@sha256:ce4355398a704fca68006f8a29f37aafb49f8fc2f64ede3ccd0d9198da910146",
"1.13": "kindest/node:v1.13.12@sha256:1c1a48c2bfcbae4d5f4fa4310b5ed10756facad0b7a2ca93c7a4b5bae5db29f5",
},
"v0.8.1": map[string]string{
"1.18": "kindest/node:v1.18.2@sha256:7b27a6d0f2517ff88ba444025beae41491b016bc6af573ba467b70c5e8e0d85f",
"1.17": "kindest/node:v1.17.5@sha256:ab3f9e6ec5ad8840eeb1f76c89bb7948c77bbf76bcebe1a8b59790b8ae9a283a",
"1.16": "kindest/node:v1.16.9@sha256:7175872357bc85847ec4b1aba46ed1d12fa054c83ac7a8a11f5c268957fd5765",
"1.15": "kindest/node:v1.15.11@sha256:6cc31f3533deb138792db2c7d1ffc36f7456a06f1db5556ad3b6927641016f50",
"1.14": "kindest/node:v1.14.10@sha256:6cd43ff41ae9f02bb46c8f455d5323819aec858b99534a290517ebc181b443c6",
"1.13": "kindest/node:v1.13.12@sha256:214476f1514e47fe3f6f54d0f9e24cfb1e4cda449529791286c7161b7f9c08e7",
"1.12": "kindest/node:v1.12.10@sha256:faeb82453af2f9373447bb63f50bae02b8020968e0889c7fa308e19b348916cb",
},
"v0.8.0": map[string]string{
"1.18": "kindest/node:v1.18.2@sha256:7b27a6d0f2517ff88ba444025beae41491b016bc6af573ba467b70c5e8e0d85f",
"1.17": "kindest/node:v1.17.5@sha256:ab3f9e6ec5ad8840eeb1f76c89bb7948c77bbf76bcebe1a8b59790b8ae9a283a",
"1.16": "kindest/node:v1.16.9@sha256:7175872357bc85847ec4b1aba46ed1d12fa054c83ac7a8a11f5c268957fd5765",
"1.15": "kindest/node:v1.15.11@sha256:6cc31f3533deb138792db2c7d1ffc36f7456a06f1db5556ad3b6927641016f50",
"1.14": "kindest/node:v1.14.10@sha256:6cd43ff41ae9f02bb46c8f455d5323819aec858b99534a290517ebc181b443c6",
"1.13": "kindest/node:v1.13.12@sha256:214476f1514e47fe3f6f54d0f9e24cfb1e4cda449529791286c7161b7f9c08e7",
"1.12": "kindest/node:v1.12.10@sha256:faeb82453af2f9373447bb63f50bae02b8020968e0889c7fa308e19b348916cb",
},
"v0.7.0": map[string]string{
"1.18": "kindest/node:v1.18.0@sha256:0e20578828edd939d25eb98496a685c76c98d54084932f76069f886ec315d694",
"1.17": "kindest/node:v1.17.0@sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62",
"1.16": "kindest/node:v1.16.4@sha256:b91a2c2317a000f3a783489dfb755064177dbc3a0b2f4147d50f04825d016f55",
"1.15": "kindest/node:v1.15.7@sha256:e2df133f80ef633c53c0200114fce2ed5e1f6947477dbc83261a6a921169488d",
"1.14": "kindest/node:v1.14.10@sha256:81ae5a3237c779efc4dda43cc81c696f88a194abcc4f8fa34f86cf674aa14977",
"1.13": "kindest/node:v1.13.12@sha256:5e8ae1a4e39f3d151d420ef912e18368745a2ede6d20ea87506920cd947a7e3a",
"1.12": "kindest/node:v1.12.10@sha256:68a6581f64b54994b824708286fafc37f1227b7b54cbb8865182ce1e036ed1cc",
"1.11": "kindest/node:v1.11.10@sha256:e6f3dade95b7cb74081c5b9f3291aaaa6026a90a977e0b990778b6adc9ea6248",
},
}
32 changes: 32 additions & 0 deletions pkg/cluster/admin_kind_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package cluster

import (
"context"
"os"
"testing"

"github.com/stretchr/testify/assert"
"k8s.io/cli-runtime/pkg/genericclioptions"
)

func TestNodeImage(t *testing.T) {
iostreams := genericclioptions.IOStreams{
In: os.Stdin,
Out: os.Stdout,
ErrOut: os.Stderr,
}
a := newKindAdmin(iostreams)
ctx := context.Background()

img, err := a.getNodeImage(ctx, "v0.9.0", "v1.19")
assert.NoError(t, err)
assert.Equal(t, "kindest/node:v1.19.1@sha256:98cf5288864662e37115e362b23e4369c8c4a408f99cbc06e58ac30ddc721600", img)

img, err = a.getNodeImage(ctx, "v0.9.0", "v1.19.3")
assert.NoError(t, err)
assert.Equal(t, "kindest/node:v1.19.1@sha256:98cf5288864662e37115e362b23e4369c8c4a408f99cbc06e58ac30ddc721600", img)

img, err = a.getNodeImage(ctx, "v0.8.1", "v1.16.1")
assert.NoError(t, err)
assert.Equal(t, "kindest/node:v1.16.9@sha256:7175872357bc85847ec4b1aba46ed1d12fa054c83ac7a8a11f5c268957fd5765", img)
}
31 changes: 28 additions & 3 deletions pkg/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"sort"
"sync"

"github.com/blang/semver/v4"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/tilt-dev/ctlptl/pkg/api"
Expand Down Expand Up @@ -381,7 +382,32 @@ func supportsRegistry(product Product) bool {
}

func supportsKubernetesVersion(product Product, version string) bool {
return product == ProductMinikube
return product == ProductKIND || product == ProductMinikube
}

func (c *Controller) canReconcileK8sVersion(ctx context.Context, desired, existing *api.Cluster) bool {
if desired.KubernetesVersion == "" {
return true
}

if desired.KubernetesVersion == existing.Status.KubernetesVersion {
return true
}

// On KIND, it's ok if the patch doesn't match.
if Product(desired.Product) == ProductKIND {
dv, err := semver.ParseTolerant(desired.KubernetesVersion)
if err != nil {
return false
}
ev, err := semver.ParseTolerant(existing.Status.KubernetesVersion)
if err != nil {
return false
}
return dv.Major == ev.Major && dv.Minor == ev.Minor
}

return false
}

func (c *Controller) deleteIfIrreconcilable(ctx context.Context, desired, existing *api.Cluster) error {
Expand All @@ -401,8 +427,7 @@ func (c *Controller) deleteIfIrreconcilable(ctx context.Context, desired, existi
_, _ = fmt.Fprintf(c.iostreams.ErrOut, "Deleting cluster %s to initialize with registry %s\n",
desired.Name, desired.Registry)
needsDelete = true
} else if desired.KubernetesVersion != "" &&
desired.KubernetesVersion != existing.Status.KubernetesVersion {
} else if !c.canReconcileK8sVersion(ctx, desired, existing) {
_, _ = fmt.Fprintf(c.iostreams.ErrOut,
"Deleting cluster %s because desired Kubernetes version (%s) does not match current (%s)\n",
desired.Name, desired.KubernetesVersion, existing.Status.KubernetesVersion)
Expand Down
2 changes: 1 addition & 1 deletion test/kind-cluster-network/cluster.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ kind: Cluster
name: kind-ctlptl-test-cluster
product: kind
registry: ctlptl-test-registry

kubernetesVersion: v1.19.3
1 change: 1 addition & 0 deletions test/minikube-cluster-network/cluster.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ kind: Cluster
name: minikube-ctlptl-test-cluster
product: minikube
registry: ctlptl-test-registry
kubernetesVersion: v1.19.3

0 comments on commit 6cb903e

Please sign in to comment.