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.
This commit is contained in:
Lubomir I. Ivanov 2020-06-11 20:47:03 +03:00
parent 144778db83
commit ceb768ccbd
7 changed files with 251 additions and 0 deletions

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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",

View File

@ -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 {

View File

@ -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())
}
})
}
}