Merge pull request #92017 from neolit123/1.19-patches

kubeadm: introduce --experimental-patches and deprecate --experimental-kustomize
This commit is contained in:
Kubernetes Prow Robot 2020-07-02 04:17:01 -07:00 committed by GitHub
commit d71a09271c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1153 additions and 33 deletions

View File

@ -463,6 +463,7 @@ func isAllowedFlag(flagName string) bool {
kubeadmcmdoptions.KubeconfigDir, kubeadmcmdoptions.KubeconfigDir,
kubeadmcmdoptions.UploadCerts, kubeadmcmdoptions.UploadCerts,
kubeadmcmdoptions.Kustomize, kubeadmcmdoptions.Kustomize,
kubeadmcmdoptions.Patches,
"print-join-command", "rootfs", "v") "print-join-command", "rootfs", "v")
if knownFlags.Has(flagName) { if knownFlags.Has(flagName) {
return true return true

View File

@ -99,6 +99,7 @@ type initOptions struct {
uploadCerts bool uploadCerts bool
skipCertificateKeyPrint bool skipCertificateKeyPrint bool
kustomizeDir string kustomizeDir string
patchesDir string
} }
// compile-time assert that the local data object satisfies the phases data interface. // compile-time assert that the local data object satisfies the phases data interface.
@ -121,6 +122,7 @@ type initData struct {
uploadCerts bool uploadCerts bool
skipCertificateKeyPrint bool skipCertificateKeyPrint bool
kustomizeDir string kustomizeDir string
patchesDir string
} }
// NewCmdInit returns "kubeadm init" command. // NewCmdInit returns "kubeadm init" command.
@ -277,6 +279,7 @@ func AddInitOtherFlags(flagSet *flag.FlagSet, initOptions *initOptions) {
"Don't print the key used to encrypt the control-plane certificates.", "Don't print the key used to encrypt the control-plane certificates.",
) )
options.AddKustomizePodsFlag(flagSet, &initOptions.kustomizeDir) options.AddKustomizePodsFlag(flagSet, &initOptions.kustomizeDir)
options.AddPatchesFlag(flagSet, &initOptions.patchesDir)
} }
// newInitOptions returns a struct ready for being used for creating cmd init flags. // newInitOptions returns a struct ready for being used for creating cmd init flags.
@ -413,6 +416,7 @@ func newInitData(cmd *cobra.Command, args []string, options *initOptions, out io
uploadCerts: options.uploadCerts, uploadCerts: options.uploadCerts,
skipCertificateKeyPrint: options.skipCertificateKeyPrint, skipCertificateKeyPrint: options.skipCertificateKeyPrint,
kustomizeDir: options.kustomizeDir, kustomizeDir: options.kustomizeDir,
patchesDir: options.patchesDir,
}, nil }, nil
} }
@ -550,6 +554,11 @@ func (d *initData) KustomizeDir() string {
return d.kustomizeDir return d.kustomizeDir
} }
// PatchesDir returns the folder where patches for components are stored
func (d *initData) PatchesDir() string {
return d.patchesDir
}
func printJoinCommand(out io.Writer, adminKubeConfigPath, token string, i *initData) error { func printJoinCommand(out io.Writer, adminKubeConfigPath, token string, i *initData) error {
joinControlPlaneCommand, err := cmdutil.GetJoinControlPlaneCommand(adminKubeConfigPath, token, i.CertificateKey(), i.skipTokenPrint, i.skipCertificateKeyPrint) joinControlPlaneCommand, err := cmdutil.GetJoinControlPlaneCommand(adminKubeConfigPath, token, i.CertificateKey(), i.skipTokenPrint, i.skipCertificateKeyPrint)
if err != nil { if err != nil {

View File

@ -130,6 +130,7 @@ type joinOptions struct {
externalcfg *kubeadmapiv1beta2.JoinConfiguration externalcfg *kubeadmapiv1beta2.JoinConfiguration
joinControlPlane *kubeadmapiv1beta2.JoinControlPlane joinControlPlane *kubeadmapiv1beta2.JoinControlPlane
kustomizeDir string kustomizeDir string
patchesDir string
} }
// compile-time assert that the local data object satisfies the phases data interface. // compile-time assert that the local data object satisfies the phases data interface.
@ -145,6 +146,7 @@ type joinData struct {
ignorePreflightErrors sets.String ignorePreflightErrors sets.String
outputWriter io.Writer outputWriter io.Writer
kustomizeDir string kustomizeDir string
patchesDir string
} }
// NewCmdJoin returns "kubeadm join" command. // NewCmdJoin returns "kubeadm join" command.
@ -286,6 +288,7 @@ func addJoinOtherFlags(flagSet *flag.FlagSet, joinOptions *joinOptions) {
"Create a new control plane instance on this node", "Create a new control plane instance on this node",
) )
options.AddKustomizePodsFlag(flagSet, &joinOptions.kustomizeDir) options.AddKustomizePodsFlag(flagSet, &joinOptions.kustomizeDir)
options.AddPatchesFlag(flagSet, &joinOptions.patchesDir)
} }
// newJoinOptions returns a struct ready for being used for creating cmd join flags. // newJoinOptions returns a struct ready for being used for creating cmd join flags.
@ -441,6 +444,7 @@ func newJoinData(cmd *cobra.Command, args []string, opt *joinOptions, out io.Wri
ignorePreflightErrors: ignorePreflightErrorsSet, ignorePreflightErrors: ignorePreflightErrorsSet,
outputWriter: out, outputWriter: out,
kustomizeDir: opt.kustomizeDir, kustomizeDir: opt.kustomizeDir,
patchesDir: opt.patchesDir,
}, nil }, nil
} }
@ -511,6 +515,11 @@ func (j *joinData) KustomizeDir() string {
return j.kustomizeDir return j.kustomizeDir
} }
// PatchesDir returns the folder where patches for components are stored
func (j *joinData) PatchesDir() string {
return j.patchesDir
}
// fetchInitConfigurationFromJoinConfiguration retrieves the init configuration from a join configuration, performing the discovery // fetchInitConfigurationFromJoinConfiguration retrieves the init configuration from a join configuration, performing the discovery
func fetchInitConfigurationFromJoinConfiguration(cfg *kubeadmapi.JoinConfiguration, tlsBootstrapCfg *clientcmdapi.Config) (*kubeadmapi.InitConfiguration, error) { func fetchInitConfigurationFromJoinConfiguration(cfg *kubeadmapi.JoinConfiguration, tlsBootstrapCfg *clientcmdapi.Config) (*kubeadmapi.InitConfiguration, error) {
// Retrieves the kubeadm configuration // Retrieves the kubeadm configuration

View File

@ -145,4 +145,7 @@ const (
// Kustomize flag sets the folder where kustomize patches for static pod manifest are stored // Kustomize flag sets the folder where kustomize patches for static pod manifest are stored
Kustomize = "experimental-kustomize" Kustomize = "experimental-kustomize"
// Patches flag sets the folder where kubeadm component patches are stored
Patches = "experimental-patches"
) )

View File

@ -17,6 +17,7 @@ limitations under the License.
package options package options
import ( import (
"fmt"
"strings" "strings"
"github.com/spf13/pflag" "github.com/spf13/pflag"
@ -92,4 +93,17 @@ func AddKubeadmOtherFlags(flagSet *pflag.FlagSet, rootfsPath *string) {
// AddKustomizePodsFlag adds the --kustomize flag to the given flagset // AddKustomizePodsFlag adds the --kustomize flag to the given flagset
func AddKustomizePodsFlag(fs *pflag.FlagSet, kustomizeDir *string) { func AddKustomizePodsFlag(fs *pflag.FlagSet, kustomizeDir *string) {
fs.StringVarP(kustomizeDir, Kustomize, "k", *kustomizeDir, "The path where kustomize patches for static pod manifests are stored.") fs.StringVarP(kustomizeDir, Kustomize, "k", *kustomizeDir, "The path where kustomize patches for static pod manifests are stored.")
fs.MarkDeprecated(Kustomize, fmt.Sprintf("This flag is deprecated and will be removed in a future version. Please use %s instead.", Patches))
}
// AddPatchesFlag adds the --patches flag to the given flagset
func AddPatchesFlag(fs *pflag.FlagSet, patchesDir *string) {
fs.StringVar(patchesDir, Patches, *patchesDir, `Path to a directory that contains files named `+
`"target[suffix][+patchtype].extension". For example, `+
`"kube-apiserver0+merge.yaml" or just "etcd.json". `+
`"patchtype" can be one of "strategic", "merge" or "json" and they match the patch formats `+
`supported by kubectl. The default "patchtype" is "strategic". "extension" must be either `+
`"json" or "yaml". "suffix" is an optional string that can be used to determine `+
`which patches are applied first alpha-numerically.`,
)
} }

View File

@ -145,6 +145,6 @@ func runControlPlaneSubphase(component string) func(c workflow.RunData) error {
cfg := data.Cfg() cfg := data.Cfg()
fmt.Printf("[control-plane] Creating static Pod manifest for %q\n", component) fmt.Printf("[control-plane] Creating static Pod manifest for %q\n", component)
return controlplane.CreateStaticPodFiles(data.ManifestDir(), data.KustomizeDir(), &cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint, component) return controlplane.CreateStaticPodFiles(data.ManifestDir(), data.KustomizeDir(), data.PatchesDir(), &cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint, component)
} }
} }

View File

@ -46,4 +46,5 @@ type InitData interface {
Client() (clientset.Interface, error) Client() (clientset.Interface, error)
Tokens() []string Tokens() []string
KustomizeDir() string KustomizeDir() string
PatchesDir() string
} }

View File

@ -49,3 +49,4 @@ func (t *testInitData) OutputWriter() io.Writer { return nil }
func (t *testInitData) Client() (clientset.Interface, error) { return nil, nil } func (t *testInitData) Client() (clientset.Interface, error) { return nil, nil }
func (t *testInitData) Tokens() []string { return nil } func (t *testInitData) Tokens() []string { return nil }
func (t *testInitData) KustomizeDir() string { return "" } func (t *testInitData) KustomizeDir() string { return "" }
func (t *testInitData) PatchesDir() string { return "" }

View File

@ -70,6 +70,7 @@ func getEtcdPhaseFlags() []string {
options.CfgPath, options.CfgPath,
options.ImageRepository, options.ImageRepository,
options.Kustomize, options.Kustomize,
options.Patches,
} }
return flags return flags
} }
@ -93,7 +94,7 @@ func runEtcdPhaseLocal() func(c workflow.RunData) error {
fmt.Printf("[dryrun] Would ensure that %q directory is present\n", cfg.Etcd.Local.DataDir) fmt.Printf("[dryrun] Would ensure that %q directory is present\n", cfg.Etcd.Local.DataDir)
} }
fmt.Printf("[etcd] Creating static Pod manifest for local etcd in %q\n", data.ManifestDir()) fmt.Printf("[etcd] Creating static Pod manifest for local etcd in %q\n", data.ManifestDir())
if err := etcdphase.CreateLocalEtcdStaticPodManifestFile(data.ManifestDir(), data.KustomizeDir(), cfg.NodeRegistration.Name, &cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint); err != nil { if err := etcdphase.CreateLocalEtcdStaticPodManifestFile(data.ManifestDir(), data.KustomizeDir(), data.PatchesDir(), cfg.NodeRegistration.Name, &cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint); err != nil {
return errors.Wrap(err, "error creating local etcd static pod manifest file") return errors.Wrap(err, "error creating local etcd static pod manifest file")
} }
} else { } else {

View File

@ -43,7 +43,7 @@ func getControlPlaneJoinPhaseFlags(name string) []string {
options.NodeName, options.NodeName,
} }
if name == "etcd" { if name == "etcd" {
flags = append(flags, options.Kustomize) flags = append(flags, options.Kustomize, options.Patches)
} }
if name != "mark-control-plane" { if name != "mark-control-plane" {
flags = append(flags, options.APIServerAdvertiseAddress) flags = append(flags, options.APIServerAdvertiseAddress)
@ -139,8 +139,9 @@ func runEtcdPhase(c workflow.RunData) error {
// From https://coreos.com/etcd/docs/latest/v2/runtime-configuration.html // From https://coreos.com/etcd/docs/latest/v2/runtime-configuration.html
// "If you add a new member to a 1-node cluster, the cluster cannot make progress before the new member starts // "If you add a new member to a 1-node cluster, the cluster cannot make progress before the new member starts
// because it needs two members as majority to agree on the consensus. You will only see this behavior between the time // because it needs two members as majority to agree on the consensus. You will only see this behavior between the time
// etcdctl member add informs the cluster about the new member and the new member successfully establishing a connection to the // existing one." // etcdctl member add informs the cluster about the new member and the new member successfully establishing a connection to the
if err := etcdphase.CreateStackedEtcdStaticPodManifestFile(client, kubeadmconstants.GetStaticPodDirectory(), data.KustomizeDir(), cfg.NodeRegistration.Name, &cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint); err != nil { // existing one."
if err := etcdphase.CreateStackedEtcdStaticPodManifestFile(client, kubeadmconstants.GetStaticPodDirectory(), data.KustomizeDir(), data.PatchesDir(), cfg.NodeRegistration.Name, &cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint); err != nil {
return errors.Wrap(err, "error creating local etcd static pod manifest file") return errors.Wrap(err, "error creating local etcd static pod manifest file")
} }

View File

@ -79,6 +79,7 @@ func getControlPlanePreparePhaseFlags(name string) []string {
options.TokenStr, options.TokenStr,
options.CertificateKey, options.CertificateKey,
options.Kustomize, options.Kustomize,
options.Patches,
} }
case "download-certs": case "download-certs":
flags = []string{ flags = []string{
@ -124,6 +125,7 @@ func getControlPlanePreparePhaseFlags(name string) []string {
options.CfgPath, options.CfgPath,
options.ControlPlane, options.ControlPlane,
options.Kustomize, options.Kustomize,
options.Patches,
} }
default: default:
flags = []string{} flags = []string{}
@ -190,6 +192,7 @@ func runControlPlanePrepareControlPlaneSubphase(c workflow.RunData) error {
err := controlplane.CreateStaticPodFiles( err := controlplane.CreateStaticPodFiles(
kubeadmconstants.GetStaticPodDirectory(), kubeadmconstants.GetStaticPodDirectory(),
data.KustomizeDir(), data.KustomizeDir(),
data.PatchesDir(),
&cfg.ClusterConfiguration, &cfg.ClusterConfiguration,
&cfg.LocalAPIEndpoint, &cfg.LocalAPIEndpoint,
component, component,

View File

@ -36,4 +36,5 @@ type JoinData interface {
IgnorePreflightErrors() sets.String IgnorePreflightErrors() sets.String
OutputWriter() io.Writer OutputWriter() io.Writer
KustomizeDir() string KustomizeDir() string
PatchesDir() string
} }

View File

@ -39,3 +39,4 @@ func (j *testJoinData) ClientSet() (*clientset.Clientset, error) { return
func (j *testJoinData) IgnorePreflightErrors() sets.String { return nil } func (j *testJoinData) IgnorePreflightErrors() sets.String { return nil }
func (j *testJoinData) OutputWriter() io.Writer { return nil } func (j *testJoinData) OutputWriter() io.Writer { return nil }
func (j *testJoinData) KustomizeDir() string { return "" } func (j *testJoinData) KustomizeDir() string { return "" }
func (j *testJoinData) PatchesDir() string { return "" }

View File

@ -40,6 +40,7 @@ func NewControlPlane() workflow.Phase {
options.CertificateRenewal, options.CertificateRenewal,
options.EtcdUpgrade, options.EtcdUpgrade,
options.Kustomize, options.Kustomize,
options.Patches,
}, },
} }
return phase return phase
@ -65,16 +66,17 @@ func runControlPlane() func(c workflow.RunData) error {
etcdUpgrade := data.EtcdUpgrade() etcdUpgrade := data.EtcdUpgrade()
renewCerts := data.RenewCerts() renewCerts := data.RenewCerts()
kustomizeDir := data.KustomizeDir() kustomizeDir := data.KustomizeDir()
patchesDir := data.PatchesDir()
// Upgrade the control plane and etcd if installed on this node // Upgrade the control plane and etcd if installed on this node
fmt.Printf("[upgrade] Upgrading your Static Pod-hosted control plane instance to version %q...\n", cfg.KubernetesVersion) fmt.Printf("[upgrade] Upgrading your Static Pod-hosted control plane instance to version %q...\n", cfg.KubernetesVersion)
if dryRun { if dryRun {
return upgrade.DryRunStaticPodUpgrade(kustomizeDir, cfg) return upgrade.DryRunStaticPodUpgrade(kustomizeDir, patchesDir, cfg)
} }
waiter := apiclient.NewKubeWaiter(data.Client(), upgrade.UpgradeManifestTimeout, os.Stdout) waiter := apiclient.NewKubeWaiter(data.Client(), upgrade.UpgradeManifestTimeout, os.Stdout)
if err := upgrade.PerformStaticPodUpgrade(client, waiter, cfg, etcdUpgrade, renewCerts, kustomizeDir); err != nil { if err := upgrade.PerformStaticPodUpgrade(client, waiter, cfg, etcdUpgrade, renewCerts, kustomizeDir, patchesDir); err != nil {
return errors.Wrap(err, "couldn't complete the static pod upgrade") return errors.Wrap(err, "couldn't complete the static pod upgrade")
} }

View File

@ -34,4 +34,5 @@ type Data interface {
Client() clientset.Interface Client() clientset.Interface
IgnorePreflightErrors() sets.String IgnorePreflightErrors() sets.String
KustomizeDir() string KustomizeDir() string
PatchesDir() string
} }

View File

@ -52,6 +52,7 @@ type applyFlags struct {
renewCerts bool renewCerts bool
imagePullTimeout time.Duration imagePullTimeout time.Duration
kustomizeDir string kustomizeDir string
patchesDir string
} }
// sessionIsInteractive returns true if the session is of an interactive type (the default, can be opted out of with -y, -f or --dry-run) // sessionIsInteractive returns true if the session is of an interactive type (the default, can be opted out of with -y, -f or --dry-run)
@ -89,6 +90,7 @@ func NewCmdApply(apf *applyPlanFlags) *cobra.Command {
// TODO: The flag was deprecated in 1.19; remove the flag following a GA deprecation policy of 12 months or 2 releases (whichever is longer) // TODO: The flag was deprecated in 1.19; remove the flag following a GA deprecation policy of 12 months or 2 releases (whichever is longer)
cmd.Flags().MarkDeprecated("image-pull-timeout", "This flag is deprecated and will be removed in a future version.") cmd.Flags().MarkDeprecated("image-pull-timeout", "This flag is deprecated and will be removed in a future version.")
options.AddKustomizePodsFlag(cmd.Flags(), &flags.kustomizeDir) options.AddKustomizePodsFlag(cmd.Flags(), &flags.kustomizeDir)
options.AddPatchesFlag(cmd.Flags(), &flags.patchesDir)
return cmd return cmd
} }
@ -215,8 +217,8 @@ func PerformControlPlaneUpgrade(flags *applyFlags, client clientset.Interface, w
fmt.Printf("[upgrade/apply] Upgrading your Static Pod-hosted control plane to version %q...\n", internalcfg.KubernetesVersion) fmt.Printf("[upgrade/apply] Upgrading your Static Pod-hosted control plane to version %q...\n", internalcfg.KubernetesVersion)
if flags.dryRun { if flags.dryRun {
return upgrade.DryRunStaticPodUpgrade(flags.kustomizeDir, internalcfg) return upgrade.DryRunStaticPodUpgrade(flags.kustomizeDir, flags.patchesDir, internalcfg)
} }
return upgrade.PerformStaticPodUpgrade(client, waiter, internalcfg, flags.etcdUpgrade, flags.renewCerts, flags.kustomizeDir) return upgrade.PerformStaticPodUpgrade(client, waiter, internalcfg, flags.etcdUpgrade, flags.renewCerts, flags.kustomizeDir, flags.patchesDir)
} }

View File

@ -44,6 +44,7 @@ type nodeOptions struct {
renewCerts bool renewCerts bool
dryRun bool dryRun bool
kustomizeDir string kustomizeDir string
patchesDir string
ignorePreflightErrors []string ignorePreflightErrors []string
} }
@ -61,6 +62,7 @@ type nodeData struct {
isControlPlaneNode bool isControlPlaneNode bool
client clientset.Interface client clientset.Interface
kustomizeDir string kustomizeDir string
patchesDir string
ignorePreflightErrors sets.String ignorePreflightErrors sets.String
} }
@ -82,6 +84,7 @@ func NewCmdNode() *cobra.Command {
// flags could be eventually inherited by the sub-commands automatically generated for phases // flags could be eventually inherited by the sub-commands automatically generated for phases
addUpgradeNodeFlags(cmd.Flags(), nodeOptions) addUpgradeNodeFlags(cmd.Flags(), nodeOptions)
options.AddKustomizePodsFlag(cmd.Flags(), &nodeOptions.kustomizeDir) options.AddKustomizePodsFlag(cmd.Flags(), &nodeOptions.kustomizeDir)
options.AddPatchesFlag(cmd.Flags(), &nodeOptions.patchesDir)
// initialize the workflow runner with the list of phases // initialize the workflow runner with the list of phases
nodeRunner.AppendPhase(phases.NewPreflightPhase()) nodeRunner.AppendPhase(phases.NewPreflightPhase())
@ -162,6 +165,7 @@ func newNodeData(cmd *cobra.Command, args []string, options *nodeOptions) (*node
client: client, client: client,
isControlPlaneNode: isControlPlaneNode, isControlPlaneNode: isControlPlaneNode,
kustomizeDir: options.kustomizeDir, kustomizeDir: options.kustomizeDir,
patchesDir: options.patchesDir,
ignorePreflightErrors: ignorePreflightErrorsSet, ignorePreflightErrors: ignorePreflightErrorsSet,
}, nil }, nil
} }
@ -206,6 +210,11 @@ func (d *nodeData) KustomizeDir() string {
return d.kustomizeDir return d.kustomizeDir
} }
// PatchesDir returns the folder where patches for components are stored
func (d *nodeData) PatchesDir() string {
return d.patchesDir
}
// IgnorePreflightErrors returns the list of preflight errors to ignore. // IgnorePreflightErrors returns the list of preflight errors to ignore.
func (d *nodeData) IgnorePreflightErrors() sets.String { func (d *nodeData) IgnorePreflightErrors() sets.String {
return d.ignorePreflightErrors return d.ignorePreflightErrors

View File

@ -19,6 +19,7 @@ package controlplane
import ( import (
"fmt" "fmt"
"net" "net"
"os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@ -37,9 +38,9 @@ import (
) )
// CreateInitStaticPodManifestFiles will write all static pod manifest files needed to bring up the control plane. // CreateInitStaticPodManifestFiles will write all static pod manifest files needed to bring up the control plane.
func CreateInitStaticPodManifestFiles(manifestDir, kustomizeDir string, cfg *kubeadmapi.InitConfiguration) error { func CreateInitStaticPodManifestFiles(manifestDir, kustomizeDir, patchesDir string, cfg *kubeadmapi.InitConfiguration) error {
klog.V(1).Infoln("[control-plane] creating static Pod files") klog.V(1).Infoln("[control-plane] creating static Pod files")
return CreateStaticPodFiles(manifestDir, kustomizeDir, &cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint, kubeadmconstants.KubeAPIServer, kubeadmconstants.KubeControllerManager, kubeadmconstants.KubeScheduler) return CreateStaticPodFiles(manifestDir, kustomizeDir, patchesDir, &cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint, kubeadmconstants.KubeAPIServer, kubeadmconstants.KubeControllerManager, kubeadmconstants.KubeScheduler)
} }
// GetStaticPodSpecs returns all staticPodSpecs actualized to the context of the current configuration // GetStaticPodSpecs returns all staticPodSpecs actualized to the context of the current configuration
@ -90,7 +91,7 @@ func GetStaticPodSpecs(cfg *kubeadmapi.ClusterConfiguration, endpoint *kubeadmap
} }
// CreateStaticPodFiles creates all the requested static pod files. // CreateStaticPodFiles creates all the requested static pod files.
func CreateStaticPodFiles(manifestDir, kustomizeDir string, cfg *kubeadmapi.ClusterConfiguration, endpoint *kubeadmapi.APIEndpoint, componentNames ...string) error { func CreateStaticPodFiles(manifestDir, kustomizeDir, patchesDir string, cfg *kubeadmapi.ClusterConfiguration, endpoint *kubeadmapi.APIEndpoint, componentNames ...string) error {
// gets the StaticPodSpecs, actualized for the current ClusterConfiguration // gets the StaticPodSpecs, actualized for the current ClusterConfiguration
klog.V(1).Infoln("[control-plane] getting StaticPodSpecs") klog.V(1).Infoln("[control-plane] getting StaticPodSpecs")
specs := GetStaticPodSpecs(cfg, endpoint) specs := GetStaticPodSpecs(cfg, endpoint)
@ -117,6 +118,15 @@ func CreateStaticPodFiles(manifestDir, kustomizeDir string, cfg *kubeadmapi.Clus
spec = *kustomizedSpec 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 // writes the StaticPodSpec to disk
if err := staticpodutil.WriteStaticPodToDisk(componentName, manifestDir, spec); err != nil { if err := staticpodutil.WriteStaticPodToDisk(componentName, manifestDir, spec); err != nil {
return errors.Wrapf(err, "failed to create static pod manifest file for %q", componentName) return errors.Wrapf(err, "failed to create static pod manifest file for %q", componentName)

View File

@ -125,7 +125,7 @@ func TestCreateStaticPodFilesAndWrappers(t *testing.T) {
// Execute createStaticPodFunction // Execute createStaticPodFunction
manifestPath := filepath.Join(tmpdir, kubeadmconstants.ManifestsSubDirName) manifestPath := filepath.Join(tmpdir, kubeadmconstants.ManifestsSubDirName)
err := CreateStaticPodFiles(manifestPath, "", cfg, &kubeadmapi.APIEndpoint{}, test.components...) err := CreateStaticPodFiles(manifestPath, "", "", cfg, &kubeadmapi.APIEndpoint{}, test.components...)
if err != nil { if err != nil {
t.Errorf("Error executing createStaticPodFunction: %v", err) t.Errorf("Error executing createStaticPodFunction: %v", err)
return return
@ -174,7 +174,7 @@ func TestCreateStaticPodFilesKustomize(t *testing.T) {
// Execute createStaticPodFunction with kustomizations // Execute createStaticPodFunction with kustomizations
manifestPath := filepath.Join(tmpdir, kubeadmconstants.ManifestsSubDirName) manifestPath := filepath.Join(tmpdir, kubeadmconstants.ManifestsSubDirName)
err = CreateStaticPodFiles(manifestPath, kustomizePath, cfg, &kubeadmapi.APIEndpoint{}, kubeadmconstants.KubeAPIServer) err = CreateStaticPodFiles(manifestPath, kustomizePath, "", cfg, &kubeadmapi.APIEndpoint{}, kubeadmconstants.KubeAPIServer)
if err != nil { if err != nil {
t.Errorf("Error executing createStaticPodFunction: %v", err) t.Errorf("Error executing createStaticPodFunction: %v", err)
return return
@ -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) { func TestGetAPIServerCommand(t *testing.T) {
var tests = []struct { var tests = []struct {
name string name string

View File

@ -19,6 +19,7 @@ package etcd
import ( import (
"fmt" "fmt"
"net" "net"
"os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@ -48,7 +49,7 @@ const (
// CreateLocalEtcdStaticPodManifestFile will write local etcd static pod manifest file. // CreateLocalEtcdStaticPodManifestFile will write local etcd static pod manifest file.
// This function is used by init - when the etcd cluster is empty - or by kubeadm // This function is used by init - when the etcd cluster is empty - or by kubeadm
// upgrade - when the etcd cluster is already up and running (and the --initial-cluster flag have no impact) // upgrade - when the etcd cluster is already up and running (and the --initial-cluster flag have no impact)
func CreateLocalEtcdStaticPodManifestFile(manifestDir, kustomizeDir string, nodeName string, cfg *kubeadmapi.ClusterConfiguration, endpoint *kubeadmapi.APIEndpoint) error { func CreateLocalEtcdStaticPodManifestFile(manifestDir, kustomizeDir, patchesDir string, nodeName string, cfg *kubeadmapi.ClusterConfiguration, endpoint *kubeadmapi.APIEndpoint) error {
if cfg.Etcd.External != nil { if cfg.Etcd.External != nil {
return errors.New("etcd static pod manifest cannot be generated for cluster using external etcd") return errors.New("etcd static pod manifest cannot be generated for cluster using external etcd")
} }
@ -64,6 +65,15 @@ func CreateLocalEtcdStaticPodManifestFile(manifestDir, kustomizeDir string, node
spec = *kustomizedSpec 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 // writes etcd StaticPod to disk
if err := staticpodutil.WriteStaticPodToDisk(kubeadmconstants.Etcd, manifestDir, spec); err != nil { if err := staticpodutil.WriteStaticPodToDisk(kubeadmconstants.Etcd, manifestDir, spec); err != nil {
return err return err
@ -141,7 +151,7 @@ func RemoveStackedEtcdMemberFromCluster(client clientset.Interface, cfg *kubeadm
// CreateStackedEtcdStaticPodManifestFile will write local etcd static pod manifest file // CreateStackedEtcdStaticPodManifestFile will write local etcd static pod manifest file
// for an additional etcd member that is joining an existing local/stacked etcd cluster. // for an additional etcd member that is joining an existing local/stacked etcd cluster.
// Other members of the etcd cluster will be notified of the joining node in beforehand as well. // Other members of the etcd cluster will be notified of the joining node in beforehand as well.
func CreateStackedEtcdStaticPodManifestFile(client clientset.Interface, manifestDir, kustomizeDir string, nodeName string, cfg *kubeadmapi.ClusterConfiguration, endpoint *kubeadmapi.APIEndpoint) error { func CreateStackedEtcdStaticPodManifestFile(client clientset.Interface, manifestDir, kustomizeDir, patchesDir string, nodeName string, cfg *kubeadmapi.ClusterConfiguration, endpoint *kubeadmapi.APIEndpoint) error {
// creates an etcd client that connects to all the local/stacked etcd members // creates an etcd client that connects to all the local/stacked etcd members
klog.V(1).Info("creating etcd client that connects to etcd pods") klog.V(1).Info("creating etcd client that connects to etcd pods")
etcdClient, err := etcdutil.NewFromCluster(client, cfg.CertificatesDir) etcdClient, err := etcdutil.NewFromCluster(client, cfg.CertificatesDir)
@ -194,6 +204,15 @@ func CreateStackedEtcdStaticPodManifestFile(client clientset.Interface, manifest
spec = *kustomizedSpec 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 // writes etcd StaticPod to disk
if err := staticpodutil.WriteStaticPodToDisk(kubeadmconstants.Etcd, manifestDir, spec); err != nil { if err := staticpodutil.WriteStaticPodToDisk(kubeadmconstants.Etcd, manifestDir, spec); err != nil {
return err return err

View File

@ -96,7 +96,7 @@ func TestCreateLocalEtcdStaticPodManifestFile(t *testing.T) {
for _, test := range tests { for _, test := range tests {
// Execute createStaticPodFunction // Execute createStaticPodFunction
manifestPath := filepath.Join(tmpdir, kubeadmconstants.ManifestsSubDirName) manifestPath := filepath.Join(tmpdir, kubeadmconstants.ManifestsSubDirName)
err := CreateLocalEtcdStaticPodManifestFile(manifestPath, "", "", test.cfg, &kubeadmapi.APIEndpoint{}) err := CreateLocalEtcdStaticPodManifestFile(manifestPath, "", "", "", test.cfg, &kubeadmapi.APIEndpoint{})
if !test.expectedError { if !test.expectedError {
if err != nil { if err != nil {
@ -149,7 +149,7 @@ func TestCreateLocalEtcdStaticPodManifestFileKustomize(t *testing.T) {
// Execute createStaticPodFunction with kustomizations // Execute createStaticPodFunction with kustomizations
manifestPath := filepath.Join(tmpdir, kubeadmconstants.ManifestsSubDirName) manifestPath := filepath.Join(tmpdir, kubeadmconstants.ManifestsSubDirName)
err = CreateLocalEtcdStaticPodManifestFile(manifestPath, kustomizePath, "", cfg, &kubeadmapi.APIEndpoint{}) err = CreateLocalEtcdStaticPodManifestFile(manifestPath, kustomizePath, "", "", cfg, &kubeadmapi.APIEndpoint{})
if err != nil { if err != nil {
t.Errorf("Error executing createStaticPodFunction: %v", err) t.Errorf("Error executing createStaticPodFunction: %v", err)
return return
@ -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) { func TestGetEtcdCommand(t *testing.T) {
var tests = []struct { var tests = []struct {
name string name string

View File

@ -55,6 +55,8 @@ type StaticPodPathManager interface {
KubernetesDir() string KubernetesDir() string
// KustomizeDir should point to the folder where kustomize patches for static pod manifest are stored // KustomizeDir should point to the folder where kustomize patches for static pod manifest are stored
KustomizeDir() string KustomizeDir() string
// PatchesDir should point to the folder where patches for components are stored
PatchesDir() string
// RealManifestPath gets the file path for the component in the "real" static pod manifest directory used by the kubelet // RealManifestPath gets the file path for the component in the "real" static pod manifest directory used by the kubelet
RealManifestPath(component string) string RealManifestPath(component string) string
// RealManifestDir should point to the static pod manifest directory used by the kubelet // RealManifestDir should point to the static pod manifest directory used by the kubelet
@ -77,6 +79,7 @@ type StaticPodPathManager interface {
type KubeStaticPodPathManager struct { type KubeStaticPodPathManager struct {
kubernetesDir string kubernetesDir string
kustomizeDir string kustomizeDir string
patchesDir string
realManifestDir string realManifestDir string
tempManifestDir string tempManifestDir string
backupManifestDir string backupManifestDir string
@ -87,10 +90,11 @@ type KubeStaticPodPathManager struct {
} }
// NewKubeStaticPodPathManager creates a new instance of KubeStaticPodPathManager // NewKubeStaticPodPathManager creates a new instance of KubeStaticPodPathManager
func NewKubeStaticPodPathManager(kubernetesDir, kustomizeDir, tempDir, backupDir, backupEtcdDir string, keepManifestDir, keepEtcdDir bool) StaticPodPathManager { func NewKubeStaticPodPathManager(kubernetesDir, kustomizeDir, patchesDir, tempDir, backupDir, backupEtcdDir string, keepManifestDir, keepEtcdDir bool) StaticPodPathManager {
return &KubeStaticPodPathManager{ return &KubeStaticPodPathManager{
kubernetesDir: kubernetesDir, kubernetesDir: kubernetesDir,
kustomizeDir: kustomizeDir, kustomizeDir: kustomizeDir,
patchesDir: patchesDir,
realManifestDir: filepath.Join(kubernetesDir, constants.ManifestsSubDirName), realManifestDir: filepath.Join(kubernetesDir, constants.ManifestsSubDirName),
tempManifestDir: tempDir, tempManifestDir: tempDir,
backupManifestDir: backupDir, backupManifestDir: backupDir,
@ -101,7 +105,7 @@ func NewKubeStaticPodPathManager(kubernetesDir, kustomizeDir, tempDir, backupDir
} }
// NewKubeStaticPodPathManagerUsingTempDirs creates a new instance of KubeStaticPodPathManager with temporary directories backing it // NewKubeStaticPodPathManagerUsingTempDirs creates a new instance of KubeStaticPodPathManager with temporary directories backing it
func NewKubeStaticPodPathManagerUsingTempDirs(kubernetesDir, kustomizeDir string, saveManifestsDir, saveEtcdDir bool) (StaticPodPathManager, error) { func NewKubeStaticPodPathManagerUsingTempDirs(kubernetesDir, kustomizeDir, patchesDir string, saveManifestsDir, saveEtcdDir bool) (StaticPodPathManager, error) {
upgradedManifestsDir, err := constants.CreateTempDirForKubeadm(kubernetesDir, "kubeadm-upgraded-manifests") upgradedManifestsDir, err := constants.CreateTempDirForKubeadm(kubernetesDir, "kubeadm-upgraded-manifests")
if err != nil { if err != nil {
@ -116,7 +120,7 @@ func NewKubeStaticPodPathManagerUsingTempDirs(kubernetesDir, kustomizeDir string
return nil, err return nil, err
} }
return NewKubeStaticPodPathManager(kubernetesDir, kustomizeDir, upgradedManifestsDir, backupManifestsDir, backupEtcdDir, saveManifestsDir, saveEtcdDir), nil return NewKubeStaticPodPathManager(kubernetesDir, kustomizeDir, patchesDir, upgradedManifestsDir, backupManifestsDir, backupEtcdDir, saveManifestsDir, saveEtcdDir), nil
} }
// MoveFile should move a file from oldPath to newPath // MoveFile should move a file from oldPath to newPath
@ -134,6 +138,11 @@ func (spm *KubeStaticPodPathManager) KustomizeDir() string {
return spm.kustomizeDir return spm.kustomizeDir
} }
// PatchesDir should point to the folder where patches for components are stored
func (spm *KubeStaticPodPathManager) PatchesDir() string {
return spm.patchesDir
}
// RealManifestPath gets the file path for the component in the "real" static pod manifest directory used by the kubelet // RealManifestPath gets the file path for the component in the "real" static pod manifest directory used by the kubelet
func (spm *KubeStaticPodPathManager) RealManifestPath(component string) string { func (spm *KubeStaticPodPathManager) RealManifestPath(component string) string {
return constants.GetStaticPodFilepath(component, spm.realManifestDir) return constants.GetStaticPodFilepath(component, spm.realManifestDir)
@ -323,7 +332,7 @@ func performEtcdStaticPodUpgrade(certsRenewMgr *renewal.Manager, client clientse
// Write the updated etcd static Pod manifest into the temporary directory, at this point no etcd change // Write the updated etcd static Pod manifest into the temporary directory, at this point no etcd change
// has occurred in any aspects. // has occurred in any aspects.
if err := etcdphase.CreateLocalEtcdStaticPodManifestFile(pathMgr.TempManifestDir(), pathMgr.KustomizeDir(), cfg.NodeRegistration.Name, &cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint); err != nil { if err := etcdphase.CreateLocalEtcdStaticPodManifestFile(pathMgr.TempManifestDir(), pathMgr.KustomizeDir(), pathMgr.PatchesDir(), cfg.NodeRegistration.Name, &cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint); err != nil {
return true, errors.Wrap(err, "error creating local etcd static pod manifest file") return true, errors.Wrap(err, "error creating local etcd static pod manifest file")
} }
@ -469,7 +478,7 @@ func StaticPodControlPlane(client clientset.Interface, waiter apiclient.Waiter,
// Write the updated static Pod manifests into the temporary directory // Write the updated static Pod manifests into the temporary directory
fmt.Printf("[upgrade/staticpods] Writing new Static Pod manifests to %q\n", pathMgr.TempManifestDir()) fmt.Printf("[upgrade/staticpods] Writing new Static Pod manifests to %q\n", pathMgr.TempManifestDir())
err = controlplane.CreateInitStaticPodManifestFiles(pathMgr.TempManifestDir(), pathMgr.KustomizeDir(), cfg) err = controlplane.CreateInitStaticPodManifestFiles(pathMgr.TempManifestDir(), pathMgr.KustomizeDir(), pathMgr.PatchesDir(), cfg)
if err != nil { if err != nil {
return errors.Wrap(err, "error creating init static pod manifest files") return errors.Wrap(err, "error creating init static pod manifest files")
} }
@ -596,14 +605,14 @@ func renewCertsByComponent(cfg *kubeadmapi.InitConfiguration, component string,
} }
// GetPathManagerForUpgrade returns a path manager properly configured for the given InitConfiguration. // GetPathManagerForUpgrade returns a path manager properly configured for the given InitConfiguration.
func GetPathManagerForUpgrade(kubernetesDir, kustomizeDir string, internalcfg *kubeadmapi.InitConfiguration, etcdUpgrade bool) (StaticPodPathManager, error) { func GetPathManagerForUpgrade(kubernetesDir, kustomizeDir, patchesDir string, internalcfg *kubeadmapi.InitConfiguration, etcdUpgrade bool) (StaticPodPathManager, error) {
isExternalEtcd := internalcfg.Etcd.External != nil isExternalEtcd := internalcfg.Etcd.External != nil
return NewKubeStaticPodPathManagerUsingTempDirs(kubernetesDir, kustomizeDir, true, etcdUpgrade && !isExternalEtcd) return NewKubeStaticPodPathManagerUsingTempDirs(kubernetesDir, kustomizeDir, patchesDir, true, etcdUpgrade && !isExternalEtcd)
} }
// PerformStaticPodUpgrade performs the upgrade of the control plane components for a static pod hosted cluster // PerformStaticPodUpgrade performs the upgrade of the control plane components for a static pod hosted cluster
func PerformStaticPodUpgrade(client clientset.Interface, waiter apiclient.Waiter, internalcfg *kubeadmapi.InitConfiguration, etcdUpgrade, renewCerts bool, kustomizeDir string) error { func PerformStaticPodUpgrade(client clientset.Interface, waiter apiclient.Waiter, internalcfg *kubeadmapi.InitConfiguration, etcdUpgrade, renewCerts bool, kustomizeDir, patchesDir string) error {
pathManager, err := GetPathManagerForUpgrade(constants.KubernetesDir, kustomizeDir, internalcfg, etcdUpgrade) pathManager, err := GetPathManagerForUpgrade(constants.KubernetesDir, kustomizeDir, patchesDir, internalcfg, etcdUpgrade)
if err != nil { if err != nil {
return err return err
} }
@ -613,14 +622,14 @@ func PerformStaticPodUpgrade(client clientset.Interface, waiter apiclient.Waiter
} }
// DryRunStaticPodUpgrade fakes an upgrade of the control plane // DryRunStaticPodUpgrade fakes an upgrade of the control plane
func DryRunStaticPodUpgrade(kustomizeDir string, internalcfg *kubeadmapi.InitConfiguration) error { func DryRunStaticPodUpgrade(kustomizeDir, patchesDir string, internalcfg *kubeadmapi.InitConfiguration) error {
dryRunManifestDir, err := constants.CreateTempDirForKubeadm("", "kubeadm-upgrade-dryrun") dryRunManifestDir, err := constants.CreateTempDirForKubeadm("", "kubeadm-upgrade-dryrun")
if err != nil { if err != nil {
return err return err
} }
defer os.RemoveAll(dryRunManifestDir) defer os.RemoveAll(dryRunManifestDir)
if err := controlplane.CreateInitStaticPodManifestFiles(dryRunManifestDir, kustomizeDir, internalcfg); err != nil { if err := controlplane.CreateInitStaticPodManifestFiles(dryRunManifestDir, kustomizeDir, patchesDir, internalcfg); err != nil {
return err return err
} }

View File

@ -144,6 +144,7 @@ func (w *fakeWaiter) WaitForKubeletAndFunc(f func() error) error {
type fakeStaticPodPathManager struct { type fakeStaticPodPathManager struct {
kubernetesDir string kubernetesDir string
kustomizeDir string kustomizeDir string
patchesDir string
realManifestDir string realManifestDir string
tempManifestDir string tempManifestDir string
backupManifestDir string backupManifestDir string
@ -199,6 +200,10 @@ func (spm *fakeStaticPodPathManager) KustomizeDir() string {
return spm.kustomizeDir return spm.kustomizeDir
} }
func (spm *fakeStaticPodPathManager) PatchesDir() string {
return spm.patchesDir
}
func (spm *fakeStaticPodPathManager) RealManifestPath(component string) string { func (spm *fakeStaticPodPathManager) RealManifestPath(component string) string {
return constants.GetStaticPodFilepath(component, spm.realManifestDir) return constants.GetStaticPodFilepath(component, spm.realManifestDir)
} }
@ -488,11 +493,11 @@ func TestStaticPodControlPlane(t *testing.T) {
} }
// Initialize the directory with v1.7 manifests; should then be upgraded to v1.8 using the method // Initialize the directory with v1.7 manifests; should then be upgraded to v1.8 using the method
err = controlplanephase.CreateInitStaticPodManifestFiles(pathMgr.RealManifestDir(), pathMgr.KustomizeDir(), oldcfg) err = controlplanephase.CreateInitStaticPodManifestFiles(pathMgr.RealManifestDir(), pathMgr.KustomizeDir(), pathMgr.PatchesDir(), oldcfg)
if err != nil { if err != nil {
t.Fatalf("couldn't run CreateInitStaticPodManifestFiles: %v", err) t.Fatalf("couldn't run CreateInitStaticPodManifestFiles: %v", err)
} }
err = etcdphase.CreateLocalEtcdStaticPodManifestFile(pathMgr.RealManifestDir(), pathMgr.KustomizeDir(), oldcfg.NodeRegistration.Name, &oldcfg.ClusterConfiguration, &oldcfg.LocalAPIEndpoint) err = etcdphase.CreateLocalEtcdStaticPodManifestFile(pathMgr.RealManifestDir(), pathMgr.KustomizeDir(), pathMgr.PatchesDir(), oldcfg.NodeRegistration.Name, &oldcfg.ClusterConfiguration, &oldcfg.LocalAPIEndpoint)
if err != nil { if err != nil {
t.Fatalf("couldn't run CreateLocalEtcdStaticPodManifestFile: %v", err) t.Fatalf("couldn't run CreateLocalEtcdStaticPodManifestFile: %v", err)
} }
@ -628,7 +633,7 @@ func TestCleanupDirs(t *testing.T) {
backupEtcdDir, cleanup := getTempDir(t, "backupEtcdDir") backupEtcdDir, cleanup := getTempDir(t, "backupEtcdDir")
defer cleanup() defer cleanup()
mgr := NewKubeStaticPodPathManager(realKubernetesDir, "", tempManifestDir, backupManifestDir, backupEtcdDir, test.keepManifest, test.keepEtcd) mgr := NewKubeStaticPodPathManager(realKubernetesDir, "", "", tempManifestDir, backupManifestDir, backupEtcdDir, test.keepManifest, test.keepEtcd)
err := mgr.CleanupDirs() err := mgr.CleanupDirs()
if err != nil { if err != nil {
t.Errorf("unexpected error cleaning up: %v", err) t.Errorf("unexpected error cleaning up: %v", err)
@ -943,7 +948,7 @@ func TestGetPathManagerForUpgrade(t *testing.T) {
os.RemoveAll(tmpdir) os.RemoveAll(tmpdir)
}() }()
pathmgr, err := GetPathManagerForUpgrade(tmpdir, "", test.cfg, test.etcdUpgrade) pathmgr, err := GetPathManagerForUpgrade(tmpdir, "", "", test.cfg, test.etcdUpgrade)
if err != nil { if err != nil {
t.Fatalf("unexpected error creating path manager: %v", err) t.Fatalf("unexpected error creating path manager: %v", err)
} }

View File

@ -89,6 +89,7 @@ filegroup(
"//cmd/kubeadm/app/util/kubeconfig:all-srcs", "//cmd/kubeadm/app/util/kubeconfig:all-srcs",
"//cmd/kubeadm/app/util/kustomize:all-srcs", "//cmd/kubeadm/app/util/kustomize:all-srcs",
"//cmd/kubeadm/app/util/output:all-srcs", "//cmd/kubeadm/app/util/output:all-srcs",
"//cmd/kubeadm/app/util/patches:all-srcs",
"//cmd/kubeadm/app/util/pkiutil:all-srcs", "//cmd/kubeadm/app/util/pkiutil:all-srcs",
"//cmd/kubeadm/app/util/pubkeypin:all-srcs", "//cmd/kubeadm/app/util/pubkeypin:all-srcs",
"//cmd/kubeadm/app/util/runtime:all-srcs", "//cmd/kubeadm/app/util/runtime:all-srcs",

View File

@ -0,0 +1,40 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["patches.go"],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/patches",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
"//vendor/github.com/evanphx/json-patch:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/sigs.k8s.io/yaml:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["patches_test.go"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,342 @@
/*
Copyright 2020 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 patches
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
jsonpatch "github.com/evanphx/json-patch"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/strategicpatch"
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
"sigs.k8s.io/yaml"
)
// PatchTarget defines a target to be patched, such as a control-plane static Pod.
type PatchTarget struct {
// Name must be the name of a known target. In the case of Kubernetes objects
// this is likely to match the ObjectMeta.Name of a target.
Name string
// StrategicMergePatchObject is only used for strategic merge patches.
// It represents the underlying object type that is patched - e.g. "v1.Pod"
StrategicMergePatchObject interface{}
// Data must contain the bytes that will be patched.
Data []byte
}
// PatchManager defines an object that can apply patches.
type PatchManager struct {
patchSets []*patchSet
knownTargets []string
output io.Writer
}
// patchSet defines a set of patches of a certain type that can patch a PatchTarget.
type patchSet struct {
targetName string
patchType types.PatchType
patches []string
}
// String() is used for unit-testing.
func (ps *patchSet) String() string {
return fmt.Sprintf(
"{%q, %q, %#v}",
ps.targetName,
ps.patchType,
ps.patches,
)
}
var (
pathLock = &sync.RWMutex{}
pathCache = map[string]*PatchManager{}
patchTypes = map[string]types.PatchType{
"json": types.JSONPatchType,
"merge": types.MergePatchType,
"strategic": types.StrategicMergePatchType,
"": types.StrategicMergePatchType, // Default
}
patchTypeList = []string{"json", "merge", "strategic"}
patchTypesJoined = strings.Join(patchTypeList, "|")
knownExtensions = []string{"json", "yaml"}
regExtension = regexp.MustCompile(`.+\.(` + strings.Join(knownExtensions, "|") + `)$`)
)
// GetPatchManagerForPath creates a patch manager that can be used to apply patches to "knownTargets".
// "path" should contain patches that can be used to patch the "knownTargets".
// If "output" is non-nil, messages about actions performed by the manager would go on this io.Writer.
func GetPatchManagerForPath(path string, knownTargets []string, output io.Writer) (*PatchManager, error) {
pathLock.RLock()
if pm, known := pathCache[path]; known {
pathLock.RUnlock()
return pm, nil
}
pathLock.RUnlock()
if output == nil {
output = ioutil.Discard
}
fmt.Fprintf(output, "[patches] Reading patches from path %q\n", path)
// Get the files in the path.
patchSets, patchFiles, ignoredFiles, err := getPatchSetsFromPath(path, knownTargets, output)
if err != nil {
return nil, err
}
if len(patchFiles) > 0 {
fmt.Fprintf(output, "[patches] Found the following patch files: %v\n", patchFiles)
}
if len(ignoredFiles) > 0 {
fmt.Fprintf(output, "[patches] Ignored the following files: %v\n", ignoredFiles)
}
pm := &PatchManager{
patchSets: patchSets,
knownTargets: knownTargets,
output: output,
}
pathLock.Lock()
pathCache[path] = pm
pathLock.Unlock()
return pm, nil
}
// ApplyPatchesToTarget takes a patch target and patches its "Data" using the patches
// stored in the patch manager. The resulted "Data" is always converted to JSON.
func (pm *PatchManager) ApplyPatchesToTarget(patchTarget *PatchTarget) error {
var err error
var patchedData []byte
var found bool
for _, pt := range pm.knownTargets {
if pt == patchTarget.Name {
found = true
break
}
}
if !found {
return errors.Errorf("unknown patch target name %q, must be one of %v", patchTarget.Name, pm.knownTargets)
}
// Always convert the target data to JSON.
patchedData, err = yaml.YAMLToJSON(patchTarget.Data)
if err != nil {
return err
}
// Iterate over the patchSets.
for _, patchSet := range pm.patchSets {
if patchSet.targetName != patchTarget.Name {
continue
}
// Iterate over the patches in the patchSets.
for _, patch := range patchSet.patches {
patchBytes := []byte(patch)
// Patch based on the patch type.
switch patchSet.patchType {
// JSON patch.
case types.JSONPatchType:
var patchObj jsonpatch.Patch
patchObj, err = jsonpatch.DecodePatch(patchBytes)
if err == nil {
patchedData, err = patchObj.Apply(patchedData)
}
// Merge patch.
case types.MergePatchType:
patchedData, err = jsonpatch.MergePatch(patchedData, patchBytes)
// Strategic merge patch.
case types.StrategicMergePatchType:
patchedData, err = strategicpatch.StrategicMergePatch(
patchedData,
patchBytes,
patchTarget.StrategicMergePatchObject,
)
}
if err != nil {
return errors.Wrapf(err, "could not apply the following patch of type %q to target %q:\n%s\n",
patchSet.patchType,
patchTarget.Name,
patch)
}
fmt.Fprintf(pm.output, "[patches] Applied patch of type %q to target %q\n", patchSet.patchType, patchTarget.Name)
}
// Update the data for this patch target.
patchTarget.Data = patchedData
}
return nil
}
// parseFilename validates a file name and retrieves the encoded target name and patch type.
// - On unknown extension or target name it returns a warning
// - On unknown patch type it returns an error
// - On success it returns a target name and patch type
func parseFilename(fileName string, knownTargets []string) (string, types.PatchType, error, error) {
// Return a warning if the extension cannot be matched.
if !regExtension.MatchString(fileName) {
return "", "", errors.Errorf("the file extension must be one of %v", knownExtensions), nil
}
regFileNameSplit := regexp.MustCompile(
fmt.Sprintf(`^(%s)([^.+\n]*)?(\+)?(%s)?`, strings.Join(knownTargets, "|"), patchTypesJoined),
)
// Extract the target name and patch type. The resulting sub-string slice would look like this:
// [full-match, targetName, suffix, +, patchType]
sub := regFileNameSplit.FindStringSubmatch(fileName)
if sub == nil {
return "", "", errors.Errorf("unknown target, must be one of %v", knownTargets), nil
}
targetName := sub[1]
if len(sub[3]) > 0 && len(sub[4]) == 0 {
return "", "", nil, errors.Errorf("unknown or missing patch type after '+', must be one of %v", patchTypeList)
}
patchType := patchTypes[sub[4]]
return targetName, patchType, nil, nil
}
// createPatchSet creates a patchSet object, by splitting the given "data" by "\n---".
func createPatchSet(targetName string, patchType types.PatchType, data string) (*patchSet, error) {
var patches []string
// Split the patches and convert them to JSON.
// Data that is already JSON will not cause an error.
buf := bytes.NewBuffer([]byte(data))
reader := utilyaml.NewYAMLReader(bufio.NewReader(buf))
for {
patch, err := reader.Read()
if err == io.EOF {
break
} else if err != nil {
return nil, errors.Wrapf(err, "could not split patches for data:\n%s\n", data)
}
patch = bytes.TrimSpace(patch)
if len(patch) == 0 {
continue
}
patchJSON, err := yaml.YAMLToJSON(patch)
if err != nil {
return nil, errors.Wrapf(err, "could not convert patch to JSON:\n%s\n", patch)
}
patches = append(patches, string(patchJSON))
}
return &patchSet{
targetName: targetName,
patchType: patchType,
patches: patches,
}, nil
}
// getPatchSetsFromPath walks a path, ignores sub-directories and non-patch files, and
// returns a list of patchFile objects.
func getPatchSetsFromPath(targetPath string, knownTargets []string, output io.Writer) ([]*patchSet, []string, []string, error) {
patchFiles := []string{}
ignoredFiles := []string{}
patchSets := []*patchSet{}
// Check if targetPath is a directory.
info, err := os.Lstat(targetPath)
if err != nil {
goto return_path_error
}
if !info.IsDir() {
err = errors.New("not a directory")
goto return_path_error
}
err = filepath.Walk(targetPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Sub-directories and "." are ignored.
if info.IsDir() {
return nil
}
baseName := info.Name()
// Parse the filename and retrieve the target and patch type
targetName, patchType, warn, err := parseFilename(baseName, knownTargets)
if err != nil {
return err
}
if warn != nil {
fmt.Fprintf(output, "[patches] Ignoring file %q: %v\n", baseName, warn)
ignoredFiles = append(ignoredFiles, baseName)
return nil
}
// Read the patch file.
data, err := ioutil.ReadFile(path)
if err != nil {
return errors.Wrapf(err, "could not read the file %q", path)
}
if len(data) == 0 {
fmt.Fprintf(output, "[patches] Ignoring empty file: %q\n", baseName)
ignoredFiles = append(ignoredFiles, baseName)
return nil
}
// Create a patchSet object.
patchSet, err := createPatchSet(targetName, patchType, string(data))
if err != nil {
return err
}
patchFiles = append(patchFiles, baseName)
patchSets = append(patchSets, patchSet)
return nil
})
return_path_error:
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "could not list patch files for path %q", targetPath)
}
return patchSets, patchFiles, ignoredFiles, nil
}

View File

@ -0,0 +1,413 @@
/*
Copyright 2020 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 patches
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"testing"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
)
var testKnownTargets = []string{
"etcd",
"kube-apiserver",
"kube-controller-manager",
"kube-scheduler",
}
const testDirPattern = "patch-files"
func TestParseFilename(t *testing.T) {
tests := []struct {
name string
fileName string
expectedTargetName string
expectedPatchType types.PatchType
expectedWarning bool
expectedError bool
}{
{
name: "valid: known target and patch type",
fileName: "etcd+merge.json",
expectedTargetName: "etcd",
expectedPatchType: types.MergePatchType,
},
{
name: "valid: known target and default patch type",
fileName: "etcd0.yaml",
expectedTargetName: "etcd",
expectedPatchType: types.StrategicMergePatchType,
},
{
name: "valid: known target and custom patch type",
fileName: "etcd0+merge.yaml",
expectedTargetName: "etcd",
expectedPatchType: types.MergePatchType,
},
{
name: "invalid: unknown target",
fileName: "foo.yaml",
expectedWarning: true,
},
{
name: "invalid: unknown extension",
fileName: "etcd.foo",
expectedWarning: true,
},
{
name: "invalid: missing extension",
fileName: "etcd",
expectedWarning: true,
},
{
name: "invalid: unknown patch type",
fileName: "etcd+foo.json",
expectedError: true,
},
{
name: "invalid: missing patch type",
fileName: "etcd+.json",
expectedError: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
targetName, patchType, warn, err := parseFilename(tc.fileName, testKnownTargets)
if (err != nil) != tc.expectedError {
t.Errorf("expected error: %v, got: %v, error: %v", tc.expectedError, err != nil, err)
}
if (warn != nil) != tc.expectedWarning {
t.Errorf("expected warning: %v, got: %v, warning: %v", tc.expectedWarning, warn != nil, warn)
}
if targetName != tc.expectedTargetName {
t.Errorf("expected target name: %v, got: %v", tc.expectedTargetName, targetName)
}
if patchType != tc.expectedPatchType {
t.Errorf("expected patch type: %v, got: %v", tc.expectedPatchType, patchType)
}
})
}
}
func TestCreatePatchSet(t *testing.T) {
tests := []struct {
name string
targetName string
patchType types.PatchType
expectedPatchSet *patchSet
data string
}{
{
name: "valid: YAML patches are separated and converted to JSON",
targetName: "etcd",
patchType: types.StrategicMergePatchType,
data: "foo: bar\n---\nfoo: baz\n",
expectedPatchSet: &patchSet{
targetName: "etcd",
patchType: types.StrategicMergePatchType,
patches: []string{`{"foo":"bar"}`, `{"foo":"baz"}`},
},
},
{
name: "valid: JSON patches are separated",
targetName: "etcd",
patchType: types.StrategicMergePatchType,
data: `{"foo":"bar"}` + "\n---\n" + `{"foo":"baz"}`,
expectedPatchSet: &patchSet{
targetName: "etcd",
patchType: types.StrategicMergePatchType,
patches: []string{`{"foo":"bar"}`, `{"foo":"baz"}`},
},
},
{
name: "valid: empty patches are ignored",
targetName: "etcd",
patchType: types.StrategicMergePatchType,
data: `{"foo":"bar"}` + "\n---\n ---\n" + `{"foo":"baz"}`,
expectedPatchSet: &patchSet{
targetName: "etcd",
patchType: types.StrategicMergePatchType,
patches: []string{`{"foo":"bar"}`, `{"foo":"baz"}`},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ps, _ := createPatchSet(tc.targetName, tc.patchType, tc.data)
if !reflect.DeepEqual(ps, tc.expectedPatchSet) {
t.Fatalf("expected patch set:\n%+v\ngot:\n%+v\n", tc.expectedPatchSet, ps)
}
})
}
}
func TestGetPatchSetsForPathMustBeDirectory(t *testing.T) {
tempFile, err := ioutil.TempFile("", "test-file")
if err != nil {
t.Errorf("error creating temporary file: %v", err)
}
defer os.Remove(tempFile.Name())
_, _, _, err = getPatchSetsFromPath(tempFile.Name(), testKnownTargets, ioutil.Discard)
if err == nil {
t.Fatalf("expected error for non-directory path %q", tempFile.Name())
}
}
func TestGetPatchSetsForPath(t *testing.T) {
const patchData = `{"foo":"bar"}`
tests := []struct {
name string
filesToWrite []string
expectedPatchSets []*patchSet
expectedPatchFiles []string
expectedIgnoredFiles []string
expectedError bool
patchData string
}{
{
name: "valid: patch files are sorted and non-patch files are ignored",
filesToWrite: []string{"kube-scheduler+merge.json", "kube-apiserver+json.yaml", "etcd.yaml", "foo", "bar.json"},
patchData: patchData,
expectedPatchSets: []*patchSet{
{
targetName: "etcd",
patchType: types.StrategicMergePatchType,
patches: []string{patchData},
},
{
targetName: "kube-apiserver",
patchType: types.JSONPatchType,
patches: []string{patchData},
},
{
targetName: "kube-scheduler",
patchType: types.MergePatchType,
patches: []string{patchData},
},
},
expectedPatchFiles: []string{"etcd.yaml", "kube-apiserver+json.yaml", "kube-scheduler+merge.json"},
expectedIgnoredFiles: []string{"bar.json", "foo"},
},
{
name: "valid: empty files are ignored",
patchData: "",
filesToWrite: []string{"kube-scheduler.json"},
expectedPatchFiles: []string{},
expectedIgnoredFiles: []string{"kube-scheduler.json"},
expectedPatchSets: []*patchSet{},
},
{
name: "invalid: bad patch type in filename returns and error",
filesToWrite: []string{"kube-scheduler+foo.json"},
expectedError: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tempDir, err := ioutil.TempDir("", testDirPattern)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempDir)
for _, file := range tc.filesToWrite {
filePath := filepath.Join(tempDir, file)
err := ioutil.WriteFile(filePath, []byte(tc.patchData), 0644)
if err != nil {
t.Fatalf("could not write temporary file %q", filePath)
}
}
patchSets, patchFiles, ignoredFiles, err := getPatchSetsFromPath(tempDir, testKnownTargets, ioutil.Discard)
if (err != nil) != tc.expectedError {
t.Fatalf("expected error: %v, got: %v, error: %v", tc.expectedError, err != nil, err)
}
if !reflect.DeepEqual(tc.expectedPatchFiles, patchFiles) {
t.Fatalf("expected patch files:\n%+v\ngot:\n%+v", tc.expectedPatchFiles, patchFiles)
}
if !reflect.DeepEqual(tc.expectedIgnoredFiles, ignoredFiles) {
t.Fatalf("expected ignored files:\n%+v\ngot:\n%+v", tc.expectedIgnoredFiles, ignoredFiles)
}
if !reflect.DeepEqual(tc.expectedPatchSets, patchSets) {
t.Fatalf("expected patch sets:\n%+v\ngot:\n%+v", tc.expectedPatchSets, patchSets)
}
})
}
}
func TestGetPatchManagerForPath(t *testing.T) {
type file struct {
name string
data string
}
tests := []struct {
name string
files []*file
patchTarget *PatchTarget
expectedData []byte
expectedError bool
}{
{
name: "valid: patch a kube-apiserver target using merge patch; json patch is applied first",
patchTarget: &PatchTarget{
Name: "kube-apiserver",
StrategicMergePatchObject: v1.Pod{},
Data: []byte("foo: bar\nbaz: qux\n"),
},
expectedData: []byte(`{"baz":"qux","foo":"patched"}`),
files: []*file{
{
name: "kube-apiserver+merge.yaml",
data: "foo: patched",
},
{
name: "kube-apiserver+json.json",
data: `[{"op": "replace", "path": "/foo", "value": "zzz"}]`,
},
},
},
{
name: "valid: kube-apiserver target is patched with json patch",
patchTarget: &PatchTarget{
Name: "kube-apiserver",
StrategicMergePatchObject: v1.Pod{},
Data: []byte("foo: bar\n"),
},
expectedData: []byte(`{"foo":"zzz"}`),
files: []*file{
{
name: "kube-apiserver+json.json",
data: `[{"op": "replace", "path": "/foo", "value": "zzz"}]`,
},
},
},
{
name: "valid: kube-apiserver target is patched with strategic merge patch",
patchTarget: &PatchTarget{
Name: "kube-apiserver",
StrategicMergePatchObject: v1.Pod{},
Data: []byte("foo: bar\n"),
},
expectedData: []byte(`{"foo":"zzz"}`),
files: []*file{
{
name: "kube-apiserver+strategic.json",
data: `{"foo":"zzz"}`,
},
},
},
{
name: "valid: etcd target is not changed because there are no patches for it",
patchTarget: &PatchTarget{
Name: "etcd",
StrategicMergePatchObject: v1.Pod{},
Data: []byte("foo: bar\n"),
},
expectedData: []byte("foo: bar\n"),
files: []*file{
{
name: "kube-apiserver+merge.yaml",
data: "foo: patched",
},
},
},
{
name: "invalid: cannot patch etcd target due to malformed json patch",
patchTarget: &PatchTarget{
Name: "etcd",
StrategicMergePatchObject: v1.Pod{},
Data: []byte("foo: bar\n"),
},
files: []*file{
{
name: "etcd+json.json",
data: `{"foo":"zzz"}`,
},
},
expectedError: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tempDir, err := ioutil.TempDir("", testDirPattern)
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)
}
}
pm, err := GetPatchManagerForPath(tempDir, testKnownTargets, nil)
if err != nil {
t.Fatal(err)
}
err = pm.ApplyPatchesToTarget(tc.patchTarget)
if (err != nil) != tc.expectedError {
t.Fatalf("expected error: %v, got: %v, error: %v", tc.expectedError, err != nil, err)
}
if err != nil {
return
}
if !bytes.Equal(tc.patchTarget.Data, tc.expectedData) {
t.Fatalf("expected result:\n%s\ngot:\n%s", tc.expectedData, tc.patchTarget.Data)
}
})
}
}
func TestGetPatchManagerForPathCache(t *testing.T) {
tempDir, err := ioutil.TempDir("", testDirPattern)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempDir)
pmOld, err := GetPatchManagerForPath(tempDir, testKnownTargets, nil)
if err != nil {
t.Fatal(err)
}
pmNew, err := GetPatchManagerForPath(tempDir, testKnownTargets, nil)
if err != nil {
t.Fatal(err)
}
if pmOld != pmNew {
t.Logf("path %q was not cached, expected pointer: %p, got: %p", tempDir, pmOld, pmNew)
}
}

View File

@ -29,6 +29,7 @@ go_library(
"//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/util:go_default_library", "//cmd/kubeadm/app/util:go_default_library",
"//cmd/kubeadm/app/util/kustomize: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/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource: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", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",

View File

@ -19,6 +19,7 @@ package staticpod
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"math" "math"
"net/url" "net/url"
@ -37,6 +38,7 @@ import (
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/cmd/kubeadm/app/util/kustomize" "k8s.io/kubernetes/cmd/kubeadm/app/util/kustomize"
"k8s.io/kubernetes/cmd/kubeadm/app/util/patches"
) )
const ( const (
@ -180,6 +182,48 @@ func KustomizeStaticPod(pod *v1.Pod, kustomizeDir string) (*v1.Pod, error) {
return pod2, nil 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 // WriteStaticPodToDisk writes a static pod file to disk
func WriteStaticPodToDisk(componentName, manifestDir string, pod v1.Pod) error { 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") 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())
}
})
}
}