Repository: replicatedhq/kots
Pull request #3503 (2022-12-15 21:11:28)

Expose k8s version info as template functions
<!--  Thanks for sending a pull request!  Here are some tips for you:

1. If this is your first time, please read our contributor guidelines here:
https://github.com/replicatedhq/kots/blob/main/CONTRIBUTING.md.
2. Ensure you have added appropriate tests for your PR. For more information read here:
https://github.com/replicatedhq/kots/blob/main/CONTRIBUTING.md#testing
3. If the PR is unfinished, please mark it as a draft.
-->

#### What this PR does / why we need it:

Exposes k8s version info as a template functions:

- KubernetesVersion
- KubernetesMajorVersion
- KubernetesMinorVersion

#### Which issue(s) this PR fixes:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes #

#### Special notes for your reviewer:
<!--
Any additional special notes for your reviewer.
-->

## Steps to reproduce
<!---
Please provide minimum instructions for how someone can view/test/verify your changes.
-->

#### 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:
-->
```release-note
Adds [KubernetesVersion](/reference/template-functions-static-context#kubernetesversion), [KubernetesMajorVersion](/reference/template-functions-static-context#kubernetesmajorversion), and [KubernetesMinorVersion](/reference/template-functions-static-context#kubernetesminorversion) template functions
```

#### Does this PR require documentation?
<!--
If no, just write "NONE" below.
If yes, link to the related https://github.com/replicatedhq/kots.io documentation PR:
-->
https://github.com/replicatedhq/replicated-docs/pull/840


Patch:
diff --git a/pkg/template/static_context.go b/pkg/template/static_context.go
index c9fbe8aa9..30a7bc652 100644
--- a/pkg/template/static_context.go
+++ b/pkg/template/static_context.go
@@ -35,6 +35,7 @@ import (
 	"gopkg.in/yaml.v3"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	k8sversion "k8s.io/apimachinery/pkg/version"
 	"k8s.io/client-go/kubernetes"
 	certUtil "k8s.io/client-go/util/cert"
 )
@@ -44,6 +45,8 @@ type Ctx interface {
 }
 
 type StaticCtx struct {
+	// a new clientset will be initialized if nil
+	clientset kubernetes.Interface
 }
 
 type TLSPair struct {
@@ -100,6 +103,10 @@ func (ctx StaticCtx) FuncMap() template.FuncMap {
 
 	funcMap["YamlEscape"] = ctx.yamlEscape
 
+	funcMap["KubernetesVersion"] = ctx.kubernetesVersion
+	funcMap["KubernetesMajorVersion"] = ctx.kubernetesMajorVersion
+	funcMap["KubernetesMinorVersion"] = ctx.kubernetesMinorVersion
+
 	return funcMap
 }
 
@@ -525,7 +532,7 @@ func getNodes(clientset kubernetes.Interface) ([]corev1.Node, error) {
 }
 
 func (ctx StaticCtx) distribution() string {
-	clientset, err := k8sutil.GetClientset()
+	clientset, err := ctx.getClientset()
 	if err != nil {
 		return ""
 	}
@@ -546,7 +553,7 @@ func (ctx StaticCtx) distribution() string {
 }
 
 func (ctx StaticCtx) nodeCount() int {
-	clientset, err := k8sutil.GetClientset()
+	clientset, err := ctx.getClientset()
 	if err != nil {
 		return 0
 	}
@@ -584,6 +591,61 @@ func (ctx StaticCtx) yamlEscape(plain string) string {
 	return indented
 }
 
+func (ctx StaticCtx) kubernetesVersion() string {
+	clientset, err := ctx.getClientset()
+	if err != nil {
+		return ""
+	}
+	sv, err := getK8sServerVersion(clientset)
+	if err != nil {
+		return ""
+	}
+	return strings.TrimPrefix(sv.GitVersion, "v")
+}
+
+func (ctx StaticCtx) kubernetesMajorVersion() string {
+	clientset, err := ctx.getClientset()
+	if err != nil {
+		return ""
+	}
+	sv, err := getK8sServerVersion(clientset)
+	if err != nil {
+		return ""
+	}
+	return sv.Major
+}
+
+func (ctx StaticCtx) kubernetesMinorVersion() string {
+	clientset, err := ctx.getClientset()
+	if err != nil {
+		return ""
+	}
+	sv, err := getK8sServerVersion(clientset)
+	if err != nil {
+		return ""
+	}
+	return sv.Minor
+}
+
+func getK8sServerVersion(clientset kubernetes.Interface) (*k8sversion.Info, error) {
+	sv, err := clientset.Discovery().ServerVersion()
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to get kubernetes server version")
+	}
+	return sv, nil
+}
+
+func (ctx StaticCtx) getClientset() (kubernetes.Interface, error) {
+	if ctx.clientset != nil {
+		return ctx.clientset, nil
+	}
+	clientset, err := k8sutil.GetClientset()
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to get kubernetes clientset")
+	}
+	return clientset, nil
+}
+
 // copied from sprig
 func indent(spaces int, v string) string {
 	pad := strings.Repeat(" ", spaces)


Test patch:
diff --git a/pkg/template/static_context_test.go b/pkg/template/static_context_test.go
index 1872ae152..12f1f9fab 100644
--- a/pkg/template/static_context_test.go
+++ b/pkg/template/static_context_test.go
@@ -4,10 +4,14 @@ import (
 	"crypto/x509"
 	"encoding/pem"
 	"fmt"
+	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/require"
 	"gopkg.in/yaml.v2"
+	"k8s.io/apimachinery/pkg/version"
+	fakediscovery "k8s.io/client-go/discovery/fake"
+	fakeclientset "k8s.io/client-go/kubernetes/fake"
 )
 
 func TestStaticContext_kubeSeal_badCert(t *testing.T) {
@@ -212,3 +216,38 @@ func TestProxyEnvVars(t *testing.T) {
 	req.Equal(httpProxy, "2.2.2.2")
 	req.Equal(noProxy, "3.3.3.3")
 }
+
+func TestKubernetesVersion(t *testing.T) {
+	clientset := fakeclientset.NewSimpleClientset()
+	fakeDiscovery, ok := clientset.Discovery().(*fakediscovery.FakeDiscovery)
+	if !ok {
+		t.Error("couldn't convert Discovery() to *FakeDiscovery")
+		return
+	}
+
+	wantK8sVersion := "v1.25.0"
+	wantK8sMajorVersion := "1"
+	wantK8sMinorVersion := "25"
+
+	fakeDiscovery.FakedServerVersion = &version.Info{
+		GitVersion: wantK8sVersion,
+		Major:      wantK8sMajorVersion,
+		Minor:      wantK8sMinorVersion,
+	}
+
+	ctx := StaticCtx{
+		clientset: clientset,
+	}
+
+	if actualK8sVersion := ctx.kubernetesVersion(); actualK8sVersion != strings.TrimPrefix(wantK8sVersion, "v") {
+		t.Errorf("expected kubernetes version to be %q, got %q", strings.TrimPrefix(wantK8sVersion, "v"), actualK8sVersion)
+	}
+
+	if actualK8sMajorVersion := ctx.kubernetesMajorVersion(); actualK8sMajorVersion != wantK8sMajorVersion {
+		t.Errorf("expected kubernetes major version to be %q, got %q", wantK8sMajorVersion, actualK8sMajorVersion)
+	}
+
+	if actualK8sMinorVersion := ctx.kubernetesMinorVersion(); actualK8sMinorVersion != wantK8sMinorVersion {
+		t.Errorf("expected kubernetes minor version to be %q, got %q", wantK8sMinorVersion, actualK8sMinorVersion)
+	}
+}