Repository: kubernetes-sigs/cluster-api-bootstrap-provider-kubeadm
Pull request #204 (2019-09-03 15:34:15)

:warning: Adds encoding field for cloud-init files
Signed-off-by: Chuck Ha <chuckh@vmware.com>

**What this PR does / why we need it**:
This PR adds an Encoding type for the cloud init files.

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Related to #202 

/assign @detiber 


k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/cluster-api-bootstrap-provider-kubeadm/pull/204#" title="Author self-approved">chuckha</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcluster-api-bootstrap-provider-kubeadm).

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/cluster-api-bootstrap-provider-kubeadm/blob/master/OWNERS)~~ [chuckha]

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":[]} -->
chuckha: /hold

i want to add at least one test to the cloud init module 😔

Patch:
diff --git a/api/v1alpha2/kubeadmbootstrapconfig_types.go b/api/v1alpha2/kubeadmbootstrapconfig_types.go
index f750aed..98e9870 100644
--- a/api/v1alpha2/kubeadmbootstrapconfig_types.go
+++ b/api/v1alpha2/kubeadmbootstrapconfig_types.go
@@ -107,6 +107,19 @@ func init() {
 	SchemeBuilder.Register(&KubeadmConfig{}, &KubeadmConfigList{})
 }
 
+// Encoding specifies the cloud-init file encoding.
+// +kubebuilder:validation:Enum=base64;gzip;gzip+base64
+type Encoding string
+
+const (
+	// Base64 implies the contents of the file are encoded as base64.
+	Base64 Encoding = "base64"
+	// Gzip implies the contents of the file are encoded with gzip.
+	Gzip Encoding = "gzip"
+	// GzipBase64 implies the contents of the file are first base64 encoded and then gzip encoded.
+	GzipBase64 Encoding = "gzip+base64"
+)
+
 // File defines the input for generating write_files in cloud-init.
 type File struct {
 	// Path specifies the full path on disk where to store the file.
@@ -120,6 +133,10 @@ type File struct {
 	// +optional
 	Permissions string `json:"permissions,omitempty"`
 
+	// Encoding specifies the encoding of the file contents.
+	// +optional
+	Encoding Encoding `json:"encoding,omitempty"`
+
 	// Content is the actual content of the file.
 	Content string `json:"content"`
 }
diff --git a/cloudinit/files.go b/cloudinit/files.go
index 06aad6c..04a322f 100644
--- a/cloudinit/files.go
+++ b/cloudinit/files.go
@@ -20,7 +20,9 @@ const (
 	filesTemplate = `{{ define "files" -}}
 write_files:{{ range . }}
 -   path: {{.Path}}
-    encoding: "base64"
+    {{ if ne .Encoding "" -}}
+    encoding: "{{.Encoding}}"
+    {{ end -}}
     {{ if ne .Owner "" -}}
     owner: {{.Owner}}
     {{ end -}}
@@ -28,7 +30,7 @@ write_files:{{ range . }}
     permissions: '{{.Permissions}}'
     {{ end -}}
     content: |
-{{.Content | Base64Encode | Indent 6}}
+{{.Content | Indent 6}}
 {{- end -}}
 {{- end -}}
 `
diff --git a/cloudinit/utils.go b/cloudinit/utils.go
index 0bb4a64..628c595 100644
--- a/cloudinit/utils.go
+++ b/cloudinit/utils.go
@@ -17,22 +17,16 @@ limitations under the License.
 package cloudinit
 
 import (
-	"encoding/base64"
 	"strings"
 	"text/template"
 )
 
 var (
 	defaultTemplateFuncMap = template.FuncMap{
-		"Base64Encode": templateBase64Encode,
-		"Indent":       templateYAMLIndent,
+		"Indent": templateYAMLIndent,
 	}
 )
 
-func templateBase64Encode(s string) string {
-	return base64.StdEncoding.EncodeToString([]byte(s))
-}
-
 func templateYAMLIndent(i int, input string) string {
 	split := strings.Split(input, "\n")
 	ident := "\n" + strings.Repeat(" ", i)
diff --git a/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigs.yaml b/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigs.yaml
index 382a8fb..c887b5f 100644
--- a/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigs.yaml
+++ b/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigs.yaml
@@ -359,6 +359,13 @@ spec:
                   content:
                     description: Content is the actual content of the file.
                     type: string
+                  encoding:
+                    description: Encoding specifies the encoding of the file contents.
+                    enum:
+                    - base64
+                    - gzip
+                    - gzip+base64
+                    type: string
                   owner:
                     description: Owner specifies the ownership of the file, e.g. "root:root".
                     type: string
diff --git a/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigtemplates.yaml b/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigtemplates.yaml
index 259d86d..ed4169c 100644
--- a/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigtemplates.yaml
+++ b/config/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigtemplates.yaml
@@ -380,6 +380,14 @@ spec:
                           content:
                             description: Content is the actual content of the file.
                             type: string
+                          encoding:
+                            description: Encoding specifies the encoding of the file
+                              contents.
+                            enum:
+                            - base64
+                            - gzip
+                            - gzip+base64
+                            type: string
                           owner:
                             description: Owner specifies the ownership of the file,
                               e.g. "root:root".


Test patch:
diff --git a/cloudinit/cloudinit_test.go b/cloudinit/cloudinit_test.go
new file mode 100644
index 0000000..4186abd
--- /dev/null
+++ b/cloudinit/cloudinit_test.go
@@ -0,0 +1,88 @@
+/*
+Copyright 2019 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package cloudinit
+
+import (
+	"bytes"
+	"testing"
+
+	infrav1 "sigs.k8s.io/cluster-api-bootstrap-provider-kubeadm/api/v1alpha2"
+	"sigs.k8s.io/cluster-api-bootstrap-provider-kubeadm/certs"
+)
+
+func TestNewInitControlPlaneAdditionalFileEncodings(t *testing.T) {
+	cpinput := &ControlPlaneInput{
+		BaseUserData: BaseUserData{
+			Header:              "test",
+			PreKubeadmCommands:  nil,
+			PostKubeadmCommands: nil,
+			AdditionalFiles: []infrav1.File{
+				{
+					Path:     "/tmp/my-path",
+					Encoding: infrav1.Base64,
+					Content:  "aGk=",
+				},
+				{
+					Path:    "/tmp/my-other-path",
+					Content: "hi",
+				},
+			},
+			WriteFiles: nil,
+			Users:      nil,
+			NTP:        nil,
+		},
+		Certificates: certs.Certificates{
+			ClusterCA: &certs.KeyPair{
+				Cert: []byte("ca cert"),
+				Key:  []byte("ca key"),
+			},
+			EtcdCA: &certs.KeyPair{
+				Cert: []byte("etcd ca cert"),
+				Key:  []byte("etcd ca key"),
+			},
+			FrontProxyCA: &certs.KeyPair{
+				Cert: []byte("front proxy ca cert"),
+				Key:  []byte("front proxy ca key"),
+			},
+			ServiceAccount: &certs.KeyPair{
+				Cert: []byte("service account ca cert"),
+				Key:  []byte("service account ca key"),
+			},
+		},
+		ClusterConfiguration: "my-cluster-config",
+		InitConfiguration:    "my-init-config",
+	}
+
+	out, err := NewInitControlPlane(cpinput)
+	if err != nil {
+		t.Fatal(err)
+	}
+	expectedFiles := []string{
+		`-   path: /tmp/my-path
+    encoding: "base64"
+    content: |
+      aGk=`,
+		`-   path: /tmp/my-other-path
+    content: |
+      hi`,
+	}
+	for _, f := range expectedFiles {
+		if !bytes.Contains(out, []byte(f)) {
+			t.Errorf("%s\ndid not contain\n%s", out, f)
+		}
+	}
+}