Skip to content

Commit ceb768c

Browse files
committed
kubeadm: apply patches to static Pods
Add PatchStaticPod() in staticpod/utils.go Apply patches to static Pods in: - phases/controlplane/CreateStaticPodFiles() - phases/etcd/CreateLocalEtcdStaticPodManifestFile() and CreateStackedEtcdStaticPodManifestFile() Add unit tests and update Bazel.
1 parent 144778d commit ceb768c

File tree

7 files changed

+251
-0
lines changed

7 files changed

+251
-0
lines changed

cmd/kubeadm/app/phases/controlplane/manifests.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package controlplane
1919
import (
2020
"fmt"
2121
"net"
22+
"os"
2223
"path/filepath"
2324
"strconv"
2425
"strings"
@@ -117,6 +118,15 @@ func CreateStaticPodFiles(manifestDir, kustomizeDir, patchesDir string, cfg *kub
117118
spec = *kustomizedSpec
118119
}
119120

121+
// if patchesDir is defined, patch the static Pod manifest
122+
if patchesDir != "" {
123+
patchedSpec, err := staticpodutil.PatchStaticPod(&spec, patchesDir, os.Stdout)
124+
if err != nil {
125+
return errors.Wrapf(err, "failed to patch static Pod manifest file for %q", componentName)
126+
}
127+
spec = *patchedSpec
128+
}
129+
120130
// writes the StaticPodSpec to disk
121131
if err := staticpodutil.WriteStaticPodToDisk(componentName, manifestDir, spec); err != nil {
122132
return errors.Wrapf(err, "failed to create static pod manifest file for %q", componentName)

cmd/kubeadm/app/phases/controlplane/manifests_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,52 @@ func TestCreateStaticPodFilesKustomize(t *testing.T) {
191191
}
192192
}
193193

194+
func TestCreateStaticPodFilesWithPatches(t *testing.T) {
195+
// Create temp folder for the test case
196+
tmpdir := testutil.SetupTempDir(t)
197+
defer os.RemoveAll(tmpdir)
198+
199+
// Creates a Cluster Configuration
200+
cfg := &kubeadmapi.ClusterConfiguration{
201+
KubernetesVersion: "v1.9.0",
202+
}
203+
204+
patchesPath := filepath.Join(tmpdir, "patch-files")
205+
err := os.MkdirAll(patchesPath, 0777)
206+
if err != nil {
207+
t.Fatalf("Couldn't create %s", patchesPath)
208+
}
209+
210+
patchString := dedent.Dedent(`
211+
metadata:
212+
annotations:
213+
patched: "true"
214+
`)
215+
216+
err = ioutil.WriteFile(filepath.Join(patchesPath, kubeadmconstants.KubeAPIServer+".yaml"), []byte(patchString), 0644)
217+
if err != nil {
218+
t.Fatalf("WriteFile returned unexpected error: %v", err)
219+
}
220+
221+
// Execute createStaticPodFunction with patches
222+
manifestPath := filepath.Join(tmpdir, kubeadmconstants.ManifestsSubDirName)
223+
err = CreateStaticPodFiles(manifestPath, "", patchesPath, cfg, &kubeadmapi.APIEndpoint{}, kubeadmconstants.KubeAPIServer)
224+
if err != nil {
225+
t.Errorf("Error executing createStaticPodFunction: %v", err)
226+
return
227+
}
228+
229+
pod, err := staticpodutil.ReadStaticPodFromDisk(filepath.Join(manifestPath, fmt.Sprintf("%s.yaml", kubeadmconstants.KubeAPIServer)))
230+
if err != nil {
231+
t.Errorf("Error executing ReadStaticPodFromDisk: %v", err)
232+
return
233+
}
234+
235+
if _, ok := pod.ObjectMeta.Annotations["patched"]; !ok {
236+
t.Errorf("Patches were not applied to %s", kubeadmconstants.KubeAPIServer)
237+
}
238+
}
239+
194240
func TestGetAPIServerCommand(t *testing.T) {
195241
var tests = []struct {
196242
name string

cmd/kubeadm/app/phases/etcd/local.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package etcd
1919
import (
2020
"fmt"
2121
"net"
22+
"os"
2223
"path/filepath"
2324
"strconv"
2425
"strings"
@@ -64,6 +65,15 @@ func CreateLocalEtcdStaticPodManifestFile(manifestDir, kustomizeDir, patchesDir
6465
spec = *kustomizedSpec
6566
}
6667

68+
// if patchesDir is defined, patch the static Pod manifest
69+
if patchesDir != "" {
70+
patchedSpec, err := staticpodutil.PatchStaticPod(&spec, patchesDir, os.Stdout)
71+
if err != nil {
72+
return errors.Wrapf(err, "failed to patch static Pod manifest file for %q", kubeadmconstants.Etcd)
73+
}
74+
spec = *patchedSpec
75+
}
76+
6777
// writes etcd StaticPod to disk
6878
if err := staticpodutil.WriteStaticPodToDisk(kubeadmconstants.Etcd, manifestDir, spec); err != nil {
6979
return err
@@ -174,6 +184,15 @@ func CreateStackedEtcdStaticPodManifestFile(client clientset.Interface, manifest
174184
spec = *kustomizedSpec
175185
}
176186

187+
// if patchesDir is defined, patch the static Pod manifest
188+
if patchesDir != "" {
189+
patchedSpec, err := staticpodutil.PatchStaticPod(&spec, patchesDir, os.Stdout)
190+
if err != nil {
191+
return errors.Wrapf(err, "failed to patch static Pod manifest file for %q", kubeadmconstants.Etcd)
192+
}
193+
spec = *patchedSpec
194+
}
195+
177196
// writes etcd StaticPod to disk
178197
if err := staticpodutil.WriteStaticPodToDisk(kubeadmconstants.Etcd, manifestDir, spec); err != nil {
179198
return err

cmd/kubeadm/app/phases/etcd/local_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,56 @@ func TestCreateLocalEtcdStaticPodManifestFileKustomize(t *testing.T) {
166166
}
167167
}
168168

169+
func TestCreateLocalEtcdStaticPodManifestFileWithPatches(t *testing.T) {
170+
// Create temp folder for the test case
171+
tmpdir := testutil.SetupTempDir(t)
172+
defer os.RemoveAll(tmpdir)
173+
174+
// Creates a Cluster Configuration
175+
cfg := &kubeadmapi.ClusterConfiguration{
176+
KubernetesVersion: "v1.7.0",
177+
Etcd: kubeadmapi.Etcd{
178+
Local: &kubeadmapi.LocalEtcd{
179+
DataDir: tmpdir + "/etcd",
180+
},
181+
},
182+
}
183+
184+
patchesPath := filepath.Join(tmpdir, "patch-files")
185+
err := os.MkdirAll(patchesPath, 0777)
186+
if err != nil {
187+
t.Fatalf("Couldn't create %s", patchesPath)
188+
}
189+
190+
patchString := dedent.Dedent(`
191+
metadata:
192+
annotations:
193+
patched: "true"
194+
`)
195+
196+
err = ioutil.WriteFile(filepath.Join(patchesPath, kubeadmconstants.Etcd+".yaml"), []byte(patchString), 0644)
197+
if err != nil {
198+
t.Fatalf("WriteFile returned unexpected error: %v", err)
199+
}
200+
201+
manifestPath := filepath.Join(tmpdir, kubeadmconstants.ManifestsSubDirName)
202+
err = CreateLocalEtcdStaticPodManifestFile(manifestPath, "", patchesPath, "", cfg, &kubeadmapi.APIEndpoint{})
203+
if err != nil {
204+
t.Errorf("Error executing createStaticPodFunction: %v", err)
205+
return
206+
}
207+
208+
pod, err := staticpodutil.ReadStaticPodFromDisk(filepath.Join(manifestPath, kubeadmconstants.Etcd+".yaml"))
209+
if err != nil {
210+
t.Errorf("Error executing ReadStaticPodFromDisk: %v", err)
211+
return
212+
}
213+
214+
if _, ok := pod.ObjectMeta.Annotations["patched"]; !ok {
215+
t.Errorf("Patches were not applied to %s", kubeadmconstants.Etcd)
216+
}
217+
}
218+
169219
func TestGetEtcdCommand(t *testing.T) {
170220
var tests = []struct {
171221
name string

cmd/kubeadm/app/util/staticpod/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ go_library(
2929
"//cmd/kubeadm/app/constants:go_default_library",
3030
"//cmd/kubeadm/app/util:go_default_library",
3131
"//cmd/kubeadm/app/util/kustomize:go_default_library",
32+
"//cmd/kubeadm/app/util/patches:go_default_library",
3233
"//staging/src/k8s.io/api/core/v1:go_default_library",
3334
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
3435
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",

cmd/kubeadm/app/util/staticpod/utils.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package staticpod
1919
import (
2020
"bytes"
2121
"fmt"
22+
"io"
2223
"io/ioutil"
2324
"math"
2425
"net/url"
@@ -37,6 +38,7 @@ import (
3738
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
3839
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
3940
"k8s.io/kubernetes/cmd/kubeadm/app/util/kustomize"
41+
"k8s.io/kubernetes/cmd/kubeadm/app/util/patches"
4042
)
4143

4244
const (
@@ -180,6 +182,48 @@ func KustomizeStaticPod(pod *v1.Pod, kustomizeDir string) (*v1.Pod, error) {
180182
return pod2, nil
181183
}
182184

185+
// PatchStaticPod applies patches stored in patchesDir to a static Pod.
186+
func PatchStaticPod(pod *v1.Pod, patchesDir string, output io.Writer) (*v1.Pod, error) {
187+
// Marshal the Pod manifest into YAML.
188+
podYAML, err := kubeadmutil.MarshalToYaml(pod, v1.SchemeGroupVersion)
189+
if err != nil {
190+
return pod, errors.Wrapf(err, "failed to marshal Pod manifest to YAML")
191+
}
192+
193+
var knownTargets = []string{
194+
kubeadmconstants.Etcd,
195+
kubeadmconstants.KubeAPIServer,
196+
kubeadmconstants.KubeControllerManager,
197+
kubeadmconstants.KubeScheduler,
198+
}
199+
200+
patchManager, err := patches.GetPatchManagerForPath(patchesDir, knownTargets, output)
201+
if err != nil {
202+
return pod, err
203+
}
204+
205+
patchTarget := &patches.PatchTarget{
206+
Name: pod.Name,
207+
StrategicMergePatchObject: v1.Pod{},
208+
Data: podYAML,
209+
}
210+
if err := patchManager.ApplyPatchesToTarget(patchTarget); err != nil {
211+
return pod, err
212+
}
213+
214+
obj, err := kubeadmutil.UnmarshalFromYaml(patchTarget.Data, v1.SchemeGroupVersion)
215+
if err != nil {
216+
return pod, errors.Wrap(err, "failed to unmarshal patched manifest from YAML")
217+
}
218+
219+
pod2, ok := obj.(*v1.Pod)
220+
if !ok {
221+
return pod, errors.Wrap(err, "patched manifest is not a valid Pod object")
222+
}
223+
224+
return pod2, nil
225+
}
226+
183227
// WriteStaticPodToDisk writes a static pod file to disk
184228
func WriteStaticPodToDisk(componentName, manifestDir string, pod v1.Pod) error {
185229

cmd/kubeadm/app/util/staticpod/utils_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,3 +796,84 @@ func TestKustomizeStaticPod(t *testing.T) {
796796
t.Error("Kustomize did not apply patches corresponding to the resource")
797797
}
798798
}
799+
800+
func TestPatchStaticPod(t *testing.T) {
801+
type file struct {
802+
name string
803+
data string
804+
}
805+
806+
tests := []struct {
807+
name string
808+
files []*file
809+
pod *v1.Pod
810+
expectedPod *v1.Pod
811+
expectedError bool
812+
}{
813+
{
814+
name: "valid: patch a kube-apiserver target using a couple of ordered patches",
815+
pod: &v1.Pod{
816+
ObjectMeta: metav1.ObjectMeta{
817+
Name: "kube-apiserver",
818+
Namespace: "foo",
819+
},
820+
},
821+
expectedPod: &v1.Pod{
822+
ObjectMeta: metav1.ObjectMeta{
823+
Name: "kube-apiserver",
824+
Namespace: "bar2",
825+
},
826+
},
827+
files: []*file{
828+
{
829+
name: "kube-apiserver1+merge.json",
830+
data: `{"metadata":{"namespace":"bar2"}}`,
831+
},
832+
{
833+
name: "kube-apiserver0+json.json",
834+
data: `[{"op": "replace", "path": "/metadata/namespace", "value": "bar1"}]`,
835+
},
836+
},
837+
},
838+
{
839+
name: "invalid: unknown patch target name",
840+
pod: &v1.Pod{
841+
ObjectMeta: metav1.ObjectMeta{
842+
Name: "foo",
843+
Namespace: "bar",
844+
},
845+
},
846+
expectedError: true,
847+
},
848+
}
849+
850+
for _, tc := range tests {
851+
t.Run(tc.name, func(t *testing.T) {
852+
tempDir, err := ioutil.TempDir("", "patch-files")
853+
if err != nil {
854+
t.Fatal(err)
855+
}
856+
defer os.RemoveAll(tempDir)
857+
858+
for _, file := range tc.files {
859+
filePath := filepath.Join(tempDir, file.name)
860+
err := ioutil.WriteFile(filePath, []byte(file.data), 0644)
861+
if err != nil {
862+
t.Fatalf("could not write temporary file %q", filePath)
863+
}
864+
}
865+
866+
pod, err := PatchStaticPod(tc.pod, tempDir, ioutil.Discard)
867+
if (err != nil) != tc.expectedError {
868+
t.Fatalf("expected error: %v, got: %v, error: %v", tc.expectedError, (err != nil), err)
869+
}
870+
if err != nil {
871+
return
872+
}
873+
874+
if tc.expectedPod.String() != pod.String() {
875+
t.Fatalf("expected object:\n%s\ngot:\n%s", tc.expectedPod.String(), pod.String())
876+
}
877+
})
878+
}
879+
}

0 commit comments

Comments
 (0)