diff --git a/cmd/kubeadm/app/phases/controlplane/manifests.go b/cmd/kubeadm/app/phases/controlplane/manifests.go index 2af82bf6cb8..bcd6db80b2c 100644 --- a/cmd/kubeadm/app/phases/controlplane/manifests.go +++ b/cmd/kubeadm/app/phases/controlplane/manifests.go @@ -19,6 +19,7 @@ package controlplane import ( "fmt" "net" + "os" "path/filepath" "strconv" "strings" @@ -117,6 +118,15 @@ func CreateStaticPodFiles(manifestDir, kustomizeDir, patchesDir string, cfg *kub spec = *kustomizedSpec } + // if patchesDir is defined, patch the static Pod manifest + if patchesDir != "" { + patchedSpec, err := staticpodutil.PatchStaticPod(&spec, patchesDir, os.Stdout) + if err != nil { + return errors.Wrapf(err, "failed to patch static Pod manifest file for %q", componentName) + } + spec = *patchedSpec + } + // writes the StaticPodSpec to disk if err := staticpodutil.WriteStaticPodToDisk(componentName, manifestDir, spec); err != nil { return errors.Wrapf(err, "failed to create static pod manifest file for %q", componentName) diff --git a/cmd/kubeadm/app/phases/controlplane/manifests_test.go b/cmd/kubeadm/app/phases/controlplane/manifests_test.go index 2d7085c5d4d..b12dc383b2b 100644 --- a/cmd/kubeadm/app/phases/controlplane/manifests_test.go +++ b/cmd/kubeadm/app/phases/controlplane/manifests_test.go @@ -191,6 +191,52 @@ func TestCreateStaticPodFilesKustomize(t *testing.T) { } } +func TestCreateStaticPodFilesWithPatches(t *testing.T) { + // Create temp folder for the test case + tmpdir := testutil.SetupTempDir(t) + defer os.RemoveAll(tmpdir) + + // Creates a Cluster Configuration + cfg := &kubeadmapi.ClusterConfiguration{ + KubernetesVersion: "v1.9.0", + } + + patchesPath := filepath.Join(tmpdir, "patch-files") + err := os.MkdirAll(patchesPath, 0777) + if err != nil { + t.Fatalf("Couldn't create %s", patchesPath) + } + + patchString := dedent.Dedent(` + metadata: + annotations: + patched: "true" + `) + + err = ioutil.WriteFile(filepath.Join(patchesPath, kubeadmconstants.KubeAPIServer+".yaml"), []byte(patchString), 0644) + if err != nil { + t.Fatalf("WriteFile returned unexpected error: %v", err) + } + + // Execute createStaticPodFunction with patches + manifestPath := filepath.Join(tmpdir, kubeadmconstants.ManifestsSubDirName) + err = CreateStaticPodFiles(manifestPath, "", patchesPath, cfg, &kubeadmapi.APIEndpoint{}, kubeadmconstants.KubeAPIServer) + if err != nil { + t.Errorf("Error executing createStaticPodFunction: %v", err) + return + } + + pod, err := staticpodutil.ReadStaticPodFromDisk(filepath.Join(manifestPath, fmt.Sprintf("%s.yaml", kubeadmconstants.KubeAPIServer))) + if err != nil { + t.Errorf("Error executing ReadStaticPodFromDisk: %v", err) + return + } + + if _, ok := pod.ObjectMeta.Annotations["patched"]; !ok { + t.Errorf("Patches were not applied to %s", kubeadmconstants.KubeAPIServer) + } +} + func TestGetAPIServerCommand(t *testing.T) { var tests = []struct { name string diff --git a/cmd/kubeadm/app/phases/etcd/local.go b/cmd/kubeadm/app/phases/etcd/local.go index 3c474512157..82a344ed39a 100644 --- a/cmd/kubeadm/app/phases/etcd/local.go +++ b/cmd/kubeadm/app/phases/etcd/local.go @@ -19,6 +19,7 @@ package etcd import ( "fmt" "net" + "os" "path/filepath" "strconv" "strings" @@ -64,6 +65,15 @@ func CreateLocalEtcdStaticPodManifestFile(manifestDir, kustomizeDir, patchesDir spec = *kustomizedSpec } + // if patchesDir is defined, patch the static Pod manifest + if patchesDir != "" { + patchedSpec, err := staticpodutil.PatchStaticPod(&spec, patchesDir, os.Stdout) + if err != nil { + return errors.Wrapf(err, "failed to patch static Pod manifest file for %q", kubeadmconstants.Etcd) + } + spec = *patchedSpec + } + // writes etcd StaticPod to disk if err := staticpodutil.WriteStaticPodToDisk(kubeadmconstants.Etcd, manifestDir, spec); err != nil { return err @@ -174,6 +184,15 @@ func CreateStackedEtcdStaticPodManifestFile(client clientset.Interface, manifest spec = *kustomizedSpec } + // if patchesDir is defined, patch the static Pod manifest + if patchesDir != "" { + patchedSpec, err := staticpodutil.PatchStaticPod(&spec, patchesDir, os.Stdout) + if err != nil { + return errors.Wrapf(err, "failed to patch static Pod manifest file for %q", kubeadmconstants.Etcd) + } + spec = *patchedSpec + } + // writes etcd StaticPod to disk if err := staticpodutil.WriteStaticPodToDisk(kubeadmconstants.Etcd, manifestDir, spec); err != nil { return err diff --git a/cmd/kubeadm/app/phases/etcd/local_test.go b/cmd/kubeadm/app/phases/etcd/local_test.go index ea0f8549396..42136dfa06a 100644 --- a/cmd/kubeadm/app/phases/etcd/local_test.go +++ b/cmd/kubeadm/app/phases/etcd/local_test.go @@ -166,6 +166,56 @@ func TestCreateLocalEtcdStaticPodManifestFileKustomize(t *testing.T) { } } +func TestCreateLocalEtcdStaticPodManifestFileWithPatches(t *testing.T) { + // Create temp folder for the test case + tmpdir := testutil.SetupTempDir(t) + defer os.RemoveAll(tmpdir) + + // Creates a Cluster Configuration + cfg := &kubeadmapi.ClusterConfiguration{ + KubernetesVersion: "v1.7.0", + Etcd: kubeadmapi.Etcd{ + Local: &kubeadmapi.LocalEtcd{ + DataDir: tmpdir + "/etcd", + }, + }, + } + + patchesPath := filepath.Join(tmpdir, "patch-files") + err := os.MkdirAll(patchesPath, 0777) + if err != nil { + t.Fatalf("Couldn't create %s", patchesPath) + } + + patchString := dedent.Dedent(` + metadata: + annotations: + patched: "true" + `) + + err = ioutil.WriteFile(filepath.Join(patchesPath, kubeadmconstants.Etcd+".yaml"), []byte(patchString), 0644) + if err != nil { + t.Fatalf("WriteFile returned unexpected error: %v", err) + } + + manifestPath := filepath.Join(tmpdir, kubeadmconstants.ManifestsSubDirName) + err = CreateLocalEtcdStaticPodManifestFile(manifestPath, "", patchesPath, "", cfg, &kubeadmapi.APIEndpoint{}) + if err != nil { + t.Errorf("Error executing createStaticPodFunction: %v", err) + return + } + + pod, err := staticpodutil.ReadStaticPodFromDisk(filepath.Join(manifestPath, kubeadmconstants.Etcd+".yaml")) + if err != nil { + t.Errorf("Error executing ReadStaticPodFromDisk: %v", err) + return + } + + if _, ok := pod.ObjectMeta.Annotations["patched"]; !ok { + t.Errorf("Patches were not applied to %s", kubeadmconstants.Etcd) + } +} + func TestGetEtcdCommand(t *testing.T) { var tests = []struct { name string diff --git a/cmd/kubeadm/app/util/staticpod/BUILD b/cmd/kubeadm/app/util/staticpod/BUILD index eae28fe765b..e3c3cdbc4ed 100644 --- a/cmd/kubeadm/app/util/staticpod/BUILD +++ b/cmd/kubeadm/app/util/staticpod/BUILD @@ -29,6 +29,7 @@ go_library( "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/util:go_default_library", "//cmd/kubeadm/app/util/kustomize:go_default_library", + "//cmd/kubeadm/app/util/patches:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/cmd/kubeadm/app/util/staticpod/utils.go b/cmd/kubeadm/app/util/staticpod/utils.go index aa8d0f77a1b..fd243165113 100644 --- a/cmd/kubeadm/app/util/staticpod/utils.go +++ b/cmd/kubeadm/app/util/staticpod/utils.go @@ -19,6 +19,7 @@ package staticpod import ( "bytes" "fmt" + "io" "io/ioutil" "math" "net/url" @@ -37,6 +38,7 @@ import ( kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/cmd/kubeadm/app/util/kustomize" + "k8s.io/kubernetes/cmd/kubeadm/app/util/patches" ) const ( @@ -180,6 +182,48 @@ func KustomizeStaticPod(pod *v1.Pod, kustomizeDir string) (*v1.Pod, error) { return pod2, nil } +// PatchStaticPod applies patches stored in patchesDir to a static Pod. +func PatchStaticPod(pod *v1.Pod, patchesDir string, output io.Writer) (*v1.Pod, error) { + // Marshal the Pod manifest into YAML. + podYAML, err := kubeadmutil.MarshalToYaml(pod, v1.SchemeGroupVersion) + if err != nil { + return pod, errors.Wrapf(err, "failed to marshal Pod manifest to YAML") + } + + var knownTargets = []string{ + kubeadmconstants.Etcd, + kubeadmconstants.KubeAPIServer, + kubeadmconstants.KubeControllerManager, + kubeadmconstants.KubeScheduler, + } + + patchManager, err := patches.GetPatchManagerForPath(patchesDir, knownTargets, output) + if err != nil { + return pod, err + } + + patchTarget := &patches.PatchTarget{ + Name: pod.Name, + StrategicMergePatchObject: v1.Pod{}, + Data: podYAML, + } + if err := patchManager.ApplyPatchesToTarget(patchTarget); err != nil { + return pod, err + } + + obj, err := kubeadmutil.UnmarshalFromYaml(patchTarget.Data, v1.SchemeGroupVersion) + if err != nil { + return pod, errors.Wrap(err, "failed to unmarshal patched manifest from YAML") + } + + pod2, ok := obj.(*v1.Pod) + if !ok { + return pod, errors.Wrap(err, "patched manifest is not a valid Pod object") + } + + return pod2, nil +} + // WriteStaticPodToDisk writes a static pod file to disk func WriteStaticPodToDisk(componentName, manifestDir string, pod v1.Pod) error { diff --git a/cmd/kubeadm/app/util/staticpod/utils_test.go b/cmd/kubeadm/app/util/staticpod/utils_test.go index 4071df9a126..23b88271d6d 100644 --- a/cmd/kubeadm/app/util/staticpod/utils_test.go +++ b/cmd/kubeadm/app/util/staticpod/utils_test.go @@ -796,3 +796,84 @@ func TestKustomizeStaticPod(t *testing.T) { t.Error("Kustomize did not apply patches corresponding to the resource") } } + +func TestPatchStaticPod(t *testing.T) { + type file struct { + name string + data string + } + + tests := []struct { + name string + files []*file + pod *v1.Pod + expectedPod *v1.Pod + expectedError bool + }{ + { + name: "valid: patch a kube-apiserver target using a couple of ordered patches", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kube-apiserver", + Namespace: "foo", + }, + }, + expectedPod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kube-apiserver", + Namespace: "bar2", + }, + }, + files: []*file{ + { + name: "kube-apiserver1+merge.json", + data: `{"metadata":{"namespace":"bar2"}}`, + }, + { + name: "kube-apiserver0+json.json", + data: `[{"op": "replace", "path": "/metadata/namespace", "value": "bar1"}]`, + }, + }, + }, + { + name: "invalid: unknown patch target name", + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "bar", + }, + }, + expectedError: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tempDir, err := ioutil.TempDir("", "patch-files") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tempDir) + + for _, file := range tc.files { + filePath := filepath.Join(tempDir, file.name) + err := ioutil.WriteFile(filePath, []byte(file.data), 0644) + if err != nil { + t.Fatalf("could not write temporary file %q", filePath) + } + } + + pod, err := PatchStaticPod(tc.pod, tempDir, ioutil.Discard) + if (err != nil) != tc.expectedError { + t.Fatalf("expected error: %v, got: %v, error: %v", tc.expectedError, (err != nil), err) + } + if err != nil { + return + } + + if tc.expectedPod.String() != pod.String() { + t.Fatalf("expected object:\n%s\ngot:\n%s", tc.expectedPod.String(), pod.String()) + } + }) + } +}