kubeadm: Fully implement 'kubeadm init --dry-run'
This commit is contained in:
		| @@ -45,6 +45,7 @@ go_library( | |||||||
|         "//cmd/kubeadm/app/util:go_default_library", |         "//cmd/kubeadm/app/util:go_default_library", | ||||||
|         "//cmd/kubeadm/app/util/apiclient:go_default_library", |         "//cmd/kubeadm/app/util/apiclient:go_default_library", | ||||||
|         "//cmd/kubeadm/app/util/config:go_default_library", |         "//cmd/kubeadm/app/util/config:go_default_library", | ||||||
|  |         "//cmd/kubeadm/app/util/dryrun:go_default_library", | ||||||
|         "//cmd/kubeadm/app/util/kubeconfig:go_default_library", |         "//cmd/kubeadm/app/util/kubeconfig:go_default_library", | ||||||
|         "//cmd/kubeadm/app/util/pubkeypin:go_default_library", |         "//cmd/kubeadm/app/util/pubkeypin:go_default_library", | ||||||
|         "//cmd/kubeadm/app/util/token:go_default_library", |         "//cmd/kubeadm/app/util/token:go_default_library", | ||||||
|   | |||||||
| @@ -139,6 +139,8 @@ func NewCmdConfigUploadFromFlags(out io.Writer, kubeConfigFile *string) *cobra.C | |||||||
| 			Using from-flags, you can upload configuration to the ConfigMap in the cluster using the same flags you'd give to kubeadm init. | 			Using from-flags, you can upload configuration to the ConfigMap in the cluster using the same flags you'd give to kubeadm init. | ||||||
| 			If you initialized your cluster using a v1.7.x or lower kubeadm client and set some flag; you need to run this command with the | 			If you initialized your cluster using a v1.7.x or lower kubeadm client and set some flag; you need to run this command with the | ||||||
| 			same flags before upgrading to v1.8 using 'kubeadm upgrade'. | 			same flags before upgrading to v1.8 using 'kubeadm upgrade'. | ||||||
|  |  | ||||||
|  | 			The configuration is located in the %q namespace in the %q ConfigMap | ||||||
| 		`), metav1.NamespaceSystem, constants.MasterConfigurationConfigMap), | 		`), metav1.NamespaceSystem, constants.MasterConfigurationConfigMap), | ||||||
| 		Run: func(cmd *cobra.Command, args []string) { | 		Run: func(cmd *cobra.Command, args []string) { | ||||||
| 			var err error | 			var err error | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ import ( | |||||||
| 	"io" | 	"io" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"text/template" | 	"text/template" | ||||||
| 	"time" | 	"time" | ||||||
| @@ -53,6 +54,7 @@ import ( | |||||||
| 	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" | 	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" | ||||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" | 	"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" | ||||||
| 	configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" | 	configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" | ||||||
|  | 	dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun" | ||||||
| 	kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" | 	kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" | ||||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/util/pubkeypin" | 	"k8s.io/kubernetes/cmd/kubeadm/app/util/pubkeypin" | ||||||
| 	"k8s.io/kubernetes/pkg/api" | 	"k8s.io/kubernetes/pkg/api" | ||||||
| @@ -264,33 +266,67 @@ func (i *Init) Run(out io.Writer) error { | |||||||
| 		return fmt.Errorf("couldn't parse kubernetes version %q: %v", i.cfg.KubernetesVersion, err) | 		return fmt.Errorf("couldn't parse kubernetes version %q: %v", i.cfg.KubernetesVersion, err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Get directories to write files to; can be faked if we're dry-running | ||||||
|  | 	realCertsDir := i.cfg.CertificatesDir | ||||||
|  | 	certsDirToWriteTo, kubeConfigDir, manifestDir, err := getDirectoriesToUse(i.dryRun, i.cfg.CertificatesDir) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	// certsDirToWriteTo is gonna equal cfg.CertificatesDir in the normal case, but gonna be a temp directory if dryrunning | ||||||
|  | 	i.cfg.CertificatesDir = certsDirToWriteTo | ||||||
|  |  | ||||||
|  | 	adminKubeConfigPath := filepath.Join(kubeConfigDir, kubeadmconstants.AdminKubeConfigFileName) | ||||||
|  |  | ||||||
| 	// PHASE 1: Generate certificates | 	// PHASE 1: Generate certificates | ||||||
| 	if err := certsphase.CreatePKIAssets(i.cfg); err != nil { | 	if err := certsphase.CreatePKIAssets(i.cfg); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// PHASE 2: Generate kubeconfig files for the admin and the kubelet | 	// PHASE 2: Generate kubeconfig files for the admin and the kubelet | ||||||
| 	if err := kubeconfigphase.CreateInitKubeConfigFiles(kubeadmconstants.KubernetesDir, i.cfg); err != nil { | 	if err := kubeconfigphase.CreateInitKubeConfigFiles(kubeConfigDir, i.cfg); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Temporarily set cfg.CertificatesDir to the "real value" when writing controlplane manifests | ||||||
|  | 	// This is needed for writing the right kind of manifests | ||||||
|  | 	i.cfg.CertificatesDir = realCertsDir | ||||||
|  |  | ||||||
| 	// PHASE 3: Bootstrap the control plane | 	// PHASE 3: Bootstrap the control plane | ||||||
| 	manifestPath := kubeadmconstants.GetStaticPodDirectory() | 	if err := controlplanephase.CreateInitStaticPodManifestFiles(manifestDir, i.cfg); err != nil { | ||||||
| 	if err := controlplanephase.CreateInitStaticPodManifestFiles(manifestPath, i.cfg); err != nil { |  | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	// Add etcd static pod spec only if external etcd is not configured | 	// Add etcd static pod spec only if external etcd is not configured | ||||||
| 	if len(i.cfg.Etcd.Endpoints) == 0 { | 	if len(i.cfg.Etcd.Endpoints) == 0 { | ||||||
| 		if err := etcdphase.CreateLocalEtcdStaticPodManifestFile(manifestPath, i.cfg); err != nil { | 		if err := etcdphase.CreateLocalEtcdStaticPodManifestFile(manifestDir, i.cfg); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	client, err := createClientsetAndOptionallyWaitForReady(i.cfg, i.dryRun) | 	// Revert the earlier CertificatesDir assignment to the directory that can be written to | ||||||
|  | 	i.cfg.CertificatesDir = certsDirToWriteTo | ||||||
|  |  | ||||||
|  | 	// If we're dry-running, print the generated manifests | ||||||
|  | 	if err := printFilesIfDryRunning(i.dryRun, manifestDir); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Create a kubernetes client and wait for the API server to be healthy (if not dryrunning) | ||||||
|  | 	client, err := createClient(i.cfg, i.dryRun) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// waiter holds the apiclient.Waiter implementation of choice, responsible for querying the API server in various ways and waiting for conditions to be fulfilled | ||||||
|  | 	waiter := getWaiter(i.dryRun, client) | ||||||
|  |  | ||||||
|  | 	fmt.Printf("[init] Waiting for the kubelet to boot up the control plane as Static Pods from directory %q\n", kubeadmconstants.GetStaticPodDirectory()) | ||||||
|  | 	fmt.Println("[init] This process often takes about a minute to perform or longer if the control plane images have to be pulled...") | ||||||
|  | 	// TODO: Adjust this timeout or start polling the kubelet API | ||||||
|  | 	// TODO: Make this timeout more realistic when we do create some more complex logic about the interaction with the kubelet | ||||||
|  | 	if err := waiter.WaitForAPI(); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// PHASE 4: Mark the master with the right label/taint | 	// PHASE 4: Mark the master with the right label/taint | ||||||
| 	if err := markmasterphase.MarkMaster(client, i.cfg.NodeName); err != nil { | 	if err := markmasterphase.MarkMaster(client, i.cfg.NodeName); err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -316,7 +352,7 @@ func (i *Init) Run(out io.Writer) error { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Create the cluster-info ConfigMap with the associated RBAC rules | 	// Create the cluster-info ConfigMap with the associated RBAC rules | ||||||
| 	if err := clusterinfophase.CreateBootstrapConfigMapIfNotExists(client, kubeadmconstants.GetAdminKubeConfigPath()); err != nil { | 	if err := clusterinfophase.CreateBootstrapConfigMapIfNotExists(client, adminKubeConfigPath); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	if err := clusterinfophase.CreateClusterInfoRBACRules(client); err != nil { | 	if err := clusterinfophase.CreateClusterInfoRBACRules(client); err != nil { | ||||||
| @@ -347,11 +383,17 @@ func (i *Init) Run(out io.Writer) error { | |||||||
| 		// Temporary control plane is up, now we create our self hosted control | 		// Temporary control plane is up, now we create our self hosted control | ||||||
| 		// plane components and remove the static manifests: | 		// plane components and remove the static manifests: | ||||||
| 		fmt.Println("[self-hosted] Creating self-hosted control plane...") | 		fmt.Println("[self-hosted] Creating self-hosted control plane...") | ||||||
| 		if err := selfhostingphase.CreateSelfHostedControlPlane(i.cfg, client); err != nil { | 		if err := selfhostingphase.CreateSelfHostedControlPlane(manifestDir, kubeConfigDir, i.cfg, client, waiter); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Exit earlier if we're dryrunning | ||||||
|  | 	if i.dryRun { | ||||||
|  | 		fmt.Println("[dryrun] Finished dry-running successfully; above are the resources that would be created.") | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// Load the CA certificate from so we can pin its public key | 	// Load the CA certificate from so we can pin its public key | ||||||
| 	caCert, err := pkiutil.TryLoadCertFromDisk(i.cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName) | 	caCert, err := pkiutil.TryLoadCertFromDisk(i.cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName) | ||||||
|  |  | ||||||
| @@ -362,8 +404,7 @@ func (i *Init) Run(out io.Writer) error { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ctx := map[string]string{ | 	ctx := map[string]string{ | ||||||
| 		"KubeConfigPath": kubeadmconstants.GetAdminKubeConfigPath(), | 		"KubeConfigPath": adminKubeConfigPath, | ||||||
| 		"KubeConfigName": kubeadmconstants.AdminKubeConfigFileName, |  | ||||||
| 		"Token":          i.cfg.Token, | 		"Token":          i.cfg.Token, | ||||||
| 		"CAPubKeyPin":    pubkeypin.Hash(caCert), | 		"CAPubKeyPin":    pubkeypin.Hash(caCert), | ||||||
| 		"MasterHostPort": masterHostPort, | 		"MasterHostPort": masterHostPort, | ||||||
| @@ -375,24 +416,59 @@ func (i *Init) Run(out io.Writer) error { | |||||||
| 	return initDoneTempl.Execute(out, ctx) | 	return initDoneTempl.Execute(out, ctx) | ||||||
| } | } | ||||||
|  |  | ||||||
| func createClientsetAndOptionallyWaitForReady(cfg *kubeadmapi.MasterConfiguration, dryRun bool) (clientset.Interface, error) { | // createClient creates a clientset.Interface object | ||||||
|  | func createClient(cfg *kubeadmapi.MasterConfiguration, dryRun bool) (clientset.Interface, error) { | ||||||
| 	if dryRun { | 	if dryRun { | ||||||
| 		// If we're dry-running; we should create a faked client that answers some GETs in order to be able to do the full init flow and just logs the rest of requests | 		// If we're dry-running; we should create a faked client that answers some GETs in order to be able to do the full init flow and just logs the rest of requests | ||||||
| 		dryRunGetter := apiclient.NewInitDryRunGetter(cfg.NodeName, cfg.Networking.ServiceSubnet) | 		dryRunGetter := apiclient.NewInitDryRunGetter(cfg.NodeName, cfg.Networking.ServiceSubnet) | ||||||
| 		return apiclient.NewDryRunClient(dryRunGetter, os.Stdout), nil | 		return apiclient.NewDryRunClient(dryRunGetter, os.Stdout), nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// If we're acting for real,we should create a connection to the API server and wait for it to come up | 	// If we're acting for real, we should create a connection to the API server and wait for it to come up | ||||||
| 	client, err := kubeconfigutil.ClientSetFromFile(kubeadmconstants.GetAdminKubeConfigPath()) | 	return kubeconfigutil.ClientSetFromFile(kubeadmconstants.GetAdminKubeConfigPath()) | ||||||
| 	if err != nil { | } | ||||||
| 		return nil, err |  | ||||||
|  | // getDirectoriesToUse returns the (in order) certificates, kubeconfig and Static Pod manifest directories, followed by a possible error | ||||||
|  | // This behaves differently when dry-running vs the normal flow | ||||||
|  | func getDirectoriesToUse(dryRun bool, defaultPkiDir string) (string, string, string, error) { | ||||||
|  | 	if dryRun { | ||||||
|  | 		dryRunDir, err := ioutil.TempDir("", "kubeadm-init-dryrun") | ||||||
|  | 		if err != nil { | ||||||
|  | 			return "", "", "", fmt.Errorf("couldn't create a temporary directory: %v", err) | ||||||
|  | 		} | ||||||
|  | 		// Use the same temp dir for all | ||||||
|  | 		return dryRunDir, dryRunDir, dryRunDir, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	fmt.Printf("[init] Waiting for the kubelet to boot up the control plane as Static Pods from directory %q\n", kubeadmconstants.GetStaticPodDirectory()) | 	return defaultPkiDir, kubeadmconstants.KubernetesDir, kubeadmconstants.GetStaticPodDirectory(), nil | ||||||
| 	// TODO: Adjust this timeout or start polling the kubelet API | } | ||||||
| 	// TODO: Make this timeout more realistic when we do create some more complex logic about the interaction with the kubelet |  | ||||||
| 	if err := apiclient.WaitForAPI(client, 30*time.Minute); err != nil { | // printFilesIfDryRunning prints the Static Pod manifests to stdout and informs about the temporary directory to go and lookup | ||||||
| 		return nil, err | func printFilesIfDryRunning(dryRun bool, manifestDir string) error { | ||||||
| 	} | 	if !dryRun { | ||||||
| 	return client, nil | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	fmt.Printf("[dryrun] Wrote certificates, kubeconfig files and control plane manifests to %q\n", manifestDir) | ||||||
|  | 	fmt.Println("[dryrun] Won't print certificates or kubeconfig files due to the sensitive nature of them") | ||||||
|  | 	fmt.Printf("[dryrun] Please go and examine the %q directory for details about what would be written\n", manifestDir) | ||||||
|  |  | ||||||
|  | 	// Print the contents of the upgraded manifests and pretend like they were in /etc/kubernetes/manifests | ||||||
|  | 	files := []dryrunutil.FileToPrint{} | ||||||
|  | 	for _, component := range kubeadmconstants.MasterComponents { | ||||||
|  | 		realPath := kubeadmconstants.GetStaticPodFilepath(component, manifestDir) | ||||||
|  | 		outputPath := kubeadmconstants.GetStaticPodFilepath(component, kubeadmconstants.GetStaticPodDirectory()) | ||||||
|  | 		files = append(files, dryrunutil.NewFileToPrint(realPath, outputPath)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return dryrunutil.PrintDryRunFiles(files, os.Stdout) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // getWaiter gets the right waiter implementation | ||||||
|  | func getWaiter(dryRun bool, client clientset.Interface) apiclient.Waiter { | ||||||
|  | 	if dryRun { | ||||||
|  | 		return dryrunutil.NewWaiter() | ||||||
|  | 	} | ||||||
|  | 	// TODO: Adjust this timeout slightly? | ||||||
|  | 	return apiclient.NewKubeWaiter(client, 30*time.Minute, os.Stdout) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -39,6 +39,7 @@ go_library( | |||||||
|         "//cmd/kubeadm/app/phases/uploadconfig:go_default_library", |         "//cmd/kubeadm/app/phases/uploadconfig:go_default_library", | ||||||
|         "//cmd/kubeadm/app/preflight:go_default_library", |         "//cmd/kubeadm/app/preflight:go_default_library", | ||||||
|         "//cmd/kubeadm/app/util:go_default_library", |         "//cmd/kubeadm/app/util:go_default_library", | ||||||
|  |         "//cmd/kubeadm/app/util/apiclient:go_default_library", | ||||||
|         "//cmd/kubeadm/app/util/config:go_default_library", |         "//cmd/kubeadm/app/util/config:go_default_library", | ||||||
|         "//cmd/kubeadm/app/util/kubeconfig:go_default_library", |         "//cmd/kubeadm/app/util/kubeconfig:go_default_library", | ||||||
|         "//pkg/api:go_default_library", |         "//pkg/api:go_default_library", | ||||||
|   | |||||||
| @@ -17,16 +17,20 @@ limitations under the License. | |||||||
| package phases | package phases | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"os" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/spf13/cobra" | 	"github.com/spf13/cobra" | ||||||
|  |  | ||||||
| 	kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" | 	kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" | ||||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" | 	"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" | ||||||
| 	cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" | 	cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" | ||||||
|  | 	"k8s.io/kubernetes/cmd/kubeadm/app/constants" | ||||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/features" | 	"k8s.io/kubernetes/cmd/kubeadm/app/features" | ||||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting" | 	"k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting" | ||||||
| 	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" | 	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" | ||||||
|  | 	"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" | ||||||
| 	configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" | 	configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" | ||||||
| 	kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" | 	kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" | ||||||
| 	"k8s.io/kubernetes/pkg/api" | 	"k8s.io/kubernetes/pkg/api" | ||||||
| @@ -78,7 +82,8 @@ func getSelfhostingSubCommand() *cobra.Command { | |||||||
| 			kubeadmutil.CheckErr(err) | 			kubeadmutil.CheckErr(err) | ||||||
|  |  | ||||||
| 			// Converts the Static Pod-hosted control plane into a self-hosted one | 			// Converts the Static Pod-hosted control plane into a self-hosted one | ||||||
| 			err = selfhosting.CreateSelfHostedControlPlane(internalcfg, client) | 			waiter := apiclient.NewKubeWaiter(client, 2*time.Minute, os.Stdout) | ||||||
|  | 			err = selfhosting.CreateSelfHostedControlPlane(constants.GetStaticPodDirectory(), constants.KubernetesDir, internalcfg, client, waiter) | ||||||
| 			kubeadmutil.CheckErr(err) | 			kubeadmutil.CheckErr(err) | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -54,7 +54,10 @@ const ( | |||||||
| // 8. In order to avoid race conditions, we have to make sure that static pod is deleted correctly before we continue | // 8. In order to avoid race conditions, we have to make sure that static pod is deleted correctly before we continue | ||||||
| //      Otherwise, there is a race condition when we proceed without kubelet having restarted the API server correctly and the next .Create call flakes | //      Otherwise, there is a race condition when we proceed without kubelet having restarted the API server correctly and the next .Create call flakes | ||||||
| // 9. Do that for the kube-apiserver, kube-controller-manager and kube-scheduler in a loop | // 9. Do that for the kube-apiserver, kube-controller-manager and kube-scheduler in a loop | ||||||
| func CreateSelfHostedControlPlane(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface) error { | func CreateSelfHostedControlPlane(manifestsDir, kubeConfigDir string, cfg *kubeadmapi.MasterConfiguration, client clientset.Interface, waiter apiclient.Waiter) error { | ||||||
|  |  | ||||||
|  | 	// Adjust the timeout slightly to something self-hosting specific | ||||||
|  | 	waiter.SetTimeout(selfHostingWaitTimeout) | ||||||
|  |  | ||||||
| 	// Here the map of different mutators to use for the control plane's podspec is stored | 	// Here the map of different mutators to use for the control plane's podspec is stored | ||||||
| 	mutators := getDefaultMutators() | 	mutators := getDefaultMutators() | ||||||
| @@ -66,7 +69,7 @@ func CreateSelfHostedControlPlane(cfg *kubeadmapi.MasterConfiguration, client cl | |||||||
| 		if err := uploadTLSSecrets(client, cfg.CertificatesDir); err != nil { | 		if err := uploadTLSSecrets(client, cfg.CertificatesDir); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		if err := uploadKubeConfigSecrets(client); err != nil { | 		if err := uploadKubeConfigSecrets(client, kubeConfigDir); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		// Add the store-certs-in-secrets-specific mutators here so that the self-hosted component starts using them | 		// Add the store-certs-in-secrets-specific mutators here so that the self-hosted component starts using them | ||||||
| @@ -77,7 +80,7 @@ func CreateSelfHostedControlPlane(cfg *kubeadmapi.MasterConfiguration, client cl | |||||||
|  |  | ||||||
| 	for _, componentName := range kubeadmconstants.MasterComponents { | 	for _, componentName := range kubeadmconstants.MasterComponents { | ||||||
| 		start := time.Now() | 		start := time.Now() | ||||||
| 		manifestPath := kubeadmconstants.GetStaticPodFilepath(componentName, kubeadmconstants.GetStaticPodDirectory()) | 		manifestPath := kubeadmconstants.GetStaticPodFilepath(componentName, manifestsDir) | ||||||
|  |  | ||||||
| 		// Since we want this function to be idempotent; just continue and try the next component if this file doesn't exist | 		// Since we want this function to be idempotent; just continue and try the next component if this file doesn't exist | ||||||
| 		if _, err := os.Stat(manifestPath); err != nil { | 		if _, err := os.Stat(manifestPath); err != nil { | ||||||
| @@ -102,7 +105,7 @@ func CreateSelfHostedControlPlane(cfg *kubeadmapi.MasterConfiguration, client cl | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Wait for the self-hosted component to come up | 		// Wait for the self-hosted component to come up | ||||||
| 		if err := apiclient.WaitForPodsWithLabel(client, selfHostingWaitTimeout, os.Stdout, buildSelfHostedWorkloadLabelQuery(componentName)); err != nil { | 		if err := waiter.WaitForPodsWithLabel(buildSelfHostedWorkloadLabelQuery(componentName)); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -115,12 +118,12 @@ func CreateSelfHostedControlPlane(cfg *kubeadmapi.MasterConfiguration, client cl | |||||||
| 		// remove the Static Pod (or the mirror Pod respectively). This implicitely also tests that the API server endpoint is healthy, | 		// remove the Static Pod (or the mirror Pod respectively). This implicitely also tests that the API server endpoint is healthy, | ||||||
| 		// because this blocks until the API server returns a 404 Not Found when getting the Static Pod | 		// because this blocks until the API server returns a 404 Not Found when getting the Static Pod | ||||||
| 		staticPodName := fmt.Sprintf("%s-%s", componentName, cfg.NodeName) | 		staticPodName := fmt.Sprintf("%s-%s", componentName, cfg.NodeName) | ||||||
| 		if err := apiclient.WaitForStaticPodToDisappear(client, selfHostingWaitTimeout, staticPodName); err != nil { | 		if err := waiter.WaitForPodToDisappear(staticPodName); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Just as an extra safety check; make sure the API server is returning ok at the /healthz endpoint (although we know it could return a GET answer for a Pod above) | 		// Just as an extra safety check; make sure the API server is returning ok at the /healthz endpoint (although we know it could return a GET answer for a Pod above) | ||||||
| 		if err := apiclient.WaitForAPI(client, selfHostingWaitTimeout); err != nil { | 		if err := waiter.WaitForAPI(); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ package selfhosting | |||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"path" | 	"path/filepath" | ||||||
|  |  | ||||||
| 	"k8s.io/api/core/v1" | 	"k8s.io/api/core/v1" | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| @@ -184,8 +184,8 @@ func uploadTLSSecrets(client clientset.Interface, certDir string) error { | |||||||
| 	for _, tlsKeyPair := range getTLSKeyPairs() { | 	for _, tlsKeyPair := range getTLSKeyPairs() { | ||||||
| 		secret, err := createTLSSecretFromFiles( | 		secret, err := createTLSSecretFromFiles( | ||||||
| 			tlsKeyPair.name, | 			tlsKeyPair.name, | ||||||
| 			path.Join(certDir, tlsKeyPair.cert), | 			filepath.Join(certDir, tlsKeyPair.cert), | ||||||
| 			path.Join(certDir, tlsKeyPair.key), | 			filepath.Join(certDir, tlsKeyPair.key), | ||||||
| 		) | 		) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| @@ -200,13 +200,13 @@ func uploadTLSSecrets(client clientset.Interface, certDir string) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func uploadKubeConfigSecrets(client clientset.Interface) error { | func uploadKubeConfigSecrets(client clientset.Interface, kubeConfigDir string) error { | ||||||
| 	files := []string{ | 	files := []string{ | ||||||
| 		kubeadmconstants.SchedulerKubeConfigFileName, | 		kubeadmconstants.SchedulerKubeConfigFileName, | ||||||
| 		kubeadmconstants.ControllerManagerKubeConfigFileName, | 		kubeadmconstants.ControllerManagerKubeConfigFileName, | ||||||
| 	} | 	} | ||||||
| 	for _, file := range files { | 	for _, file := range files { | ||||||
| 		kubeConfigPath := path.Join(kubeadmconstants.KubernetesDir, file) | 		kubeConfigPath := filepath.Join(kubeConfigDir, file) | ||||||
| 		secret, err := createOpaqueSecretFromFile(file, kubeConfigPath) | 		secret, err := createOpaqueSecretFromFile(file, kubeConfigPath) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| @@ -257,7 +257,7 @@ func createOpaqueSecretFromFile(secretName, file string) (*v1.Secret, error) { | |||||||
| 		}, | 		}, | ||||||
| 		Type: v1.SecretTypeOpaque, | 		Type: v1.SecretTypeOpaque, | ||||||
| 		Data: map[string][]byte{ | 		Data: map[string][]byte{ | ||||||
| 			path.Base(file): fileBytes, | 			filepath.Base(file): fileBytes, | ||||||
| 		}, | 		}, | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -51,6 +51,7 @@ filegroup( | |||||||
|         ":package-srcs", |         ":package-srcs", | ||||||
|         "//cmd/kubeadm/app/util/apiclient:all-srcs", |         "//cmd/kubeadm/app/util/apiclient:all-srcs", | ||||||
|         "//cmd/kubeadm/app/util/config:all-srcs", |         "//cmd/kubeadm/app/util/config:all-srcs", | ||||||
|  |         "//cmd/kubeadm/app/util/dryrun:all-srcs", | ||||||
|         "//cmd/kubeadm/app/util/kubeconfig:all-srcs", |         "//cmd/kubeadm/app/util/kubeconfig:all-srcs", | ||||||
|         "//cmd/kubeadm/app/util/pubkeypin:all-srcs", |         "//cmd/kubeadm/app/util/pubkeypin:all-srcs", | ||||||
|         "//cmd/kubeadm/app/util/staticpod:all-srcs", |         "//cmd/kubeadm/app/util/staticpod:all-srcs", | ||||||
|   | |||||||
| @@ -18,13 +18,13 @@ go_library( | |||||||
|     deps = [ |     deps = [ | ||||||
|         "//cmd/kubeadm/app/constants:go_default_library", |         "//cmd/kubeadm/app/constants:go_default_library", | ||||||
|         "//pkg/registry/core/service/ipallocator:go_default_library", |         "//pkg/registry/core/service/ipallocator:go_default_library", | ||||||
|         "//vendor/github.com/ghodss/yaml:go_default_library", |  | ||||||
|         "//vendor/k8s.io/api/core/v1:go_default_library", |         "//vendor/k8s.io/api/core/v1:go_default_library", | ||||||
|         "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", |         "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", | ||||||
|         "//vendor/k8s.io/api/rbac/v1beta1:go_default_library", |         "//vendor/k8s.io/api/rbac/v1beta1:go_default_library", | ||||||
|         "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", |         "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", | ||||||
|         "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", |         "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", | ||||||
|         "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", |         "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", | ||||||
|  |         "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", | ||||||
|         "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", |         "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", | ||||||
|         "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", |         "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", | ||||||
|         "//vendor/k8s.io/client-go/dynamic:go_default_library", |         "//vendor/k8s.io/client-go/dynamic:go_default_library", | ||||||
|   | |||||||
| @@ -23,10 +23,11 @@ import ( | |||||||
| 	"io" | 	"io" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"github.com/ghodss/yaml" |  | ||||||
| 	"k8s.io/apimachinery/pkg/runtime" | 	"k8s.io/apimachinery/pkg/runtime" | ||||||
|  | 	"k8s.io/apimachinery/pkg/runtime/schema" | ||||||
| 	clientset "k8s.io/client-go/kubernetes" | 	clientset "k8s.io/client-go/kubernetes" | ||||||
| 	fakeclientset "k8s.io/client-go/kubernetes/fake" | 	fakeclientset "k8s.io/client-go/kubernetes/fake" | ||||||
|  | 	clientsetscheme "k8s.io/client-go/kubernetes/scheme" | ||||||
| 	core "k8s.io/client-go/testing" | 	core "k8s.io/client-go/testing" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -37,12 +38,18 @@ type DryRunGetter interface { | |||||||
| } | } | ||||||
|  |  | ||||||
| // MarshalFunc takes care of converting any object to a byte array for displaying the object to the user | // MarshalFunc takes care of converting any object to a byte array for displaying the object to the user | ||||||
| type MarshalFunc func(runtime.Object) ([]byte, error) | type MarshalFunc func(runtime.Object, schema.GroupVersion) ([]byte, error) | ||||||
|  |  | ||||||
| // DefaultMarshalFunc is the default MarshalFunc used; uses YAML to print objects to the user | // DefaultMarshalFunc is the default MarshalFunc used; uses YAML to print objects to the user | ||||||
| func DefaultMarshalFunc(obj runtime.Object) ([]byte, error) { | func DefaultMarshalFunc(obj runtime.Object, gv schema.GroupVersion) ([]byte, error) { | ||||||
| 	b, err := yaml.Marshal(obj) | 	mediaType := "application/yaml" | ||||||
| 	return b, err | 	info, ok := runtime.SerializerInfoForMediaType(clientsetscheme.Codecs.SupportedMediaTypes(), mediaType) | ||||||
|  | 	if !ok { | ||||||
|  | 		return []byte{}, fmt.Errorf("unsupported media type %q", mediaType) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	encoder := clientsetscheme.Codecs.EncoderForVersion(info.Serializer, gv) | ||||||
|  | 	return runtime.Encode(encoder, obj) | ||||||
| } | } | ||||||
|  |  | ||||||
| // DryRunClientOptions specifies options to pass to NewDryRunClientWithOpts in order to get a dryrun clientset | // DryRunClientOptions specifies options to pass to NewDryRunClientWithOpts in order to get a dryrun clientset | ||||||
| @@ -115,10 +122,10 @@ func NewDryRunClientWithOpts(opts DryRunClientOptions) clientset.Interface { | |||||||
|  |  | ||||||
| 				if opts.PrintGETAndLIST { | 				if opts.PrintGETAndLIST { | ||||||
| 					// Print the marshalled object format with one tab indentation | 					// Print the marshalled object format with one tab indentation | ||||||
| 					objBytes, err := opts.MarshalFunc(obj) | 					objBytes, err := opts.MarshalFunc(obj, action.GetResource().GroupVersion()) | ||||||
| 					if err == nil { | 					if err == nil { | ||||||
| 						fmt.Println("[dryrun] Returning faked GET response:") | 						fmt.Println("[dryrun] Returning faked GET response:") | ||||||
| 						printBytesWithLinePrefix(opts.Writer, objBytes, "\t") | 						PrintBytesWithLinePrefix(opts.Writer, objBytes, "\t") | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| @@ -140,10 +147,10 @@ func NewDryRunClientWithOpts(opts DryRunClientOptions) clientset.Interface { | |||||||
|  |  | ||||||
| 				if opts.PrintGETAndLIST { | 				if opts.PrintGETAndLIST { | ||||||
| 					// Print the marshalled object format with one tab indentation | 					// Print the marshalled object format with one tab indentation | ||||||
| 					objBytes, err := opts.MarshalFunc(objs) | 					objBytes, err := opts.MarshalFunc(objs, action.GetResource().GroupVersion()) | ||||||
| 					if err == nil { | 					if err == nil { | ||||||
| 						fmt.Println("[dryrun] Returning faked LIST response:") | 						fmt.Println("[dryrun] Returning faked LIST response:") | ||||||
| 						printBytesWithLinePrefix(opts.Writer, objBytes, "\t") | 						PrintBytesWithLinePrefix(opts.Writer, objBytes, "\t") | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| @@ -214,10 +221,10 @@ func logDryRunAction(action core.Action, w io.Writer, marshalFunc MarshalFunc) { | |||||||
| 	objAction, ok := action.(actionWithObject) | 	objAction, ok := action.(actionWithObject) | ||||||
| 	if ok && objAction.GetObject() != nil { | 	if ok && objAction.GetObject() != nil { | ||||||
| 		// Print the marshalled object with a tab indentation | 		// Print the marshalled object with a tab indentation | ||||||
| 		objBytes, err := marshalFunc(objAction.GetObject()) | 		objBytes, err := marshalFunc(objAction.GetObject(), action.GetResource().GroupVersion()) | ||||||
| 		if err == nil { | 		if err == nil { | ||||||
| 			fmt.Println("[dryrun] Attached object:") | 			fmt.Println("[dryrun] Attached object:") | ||||||
| 			printBytesWithLinePrefix(w, objBytes, "\t") | 			PrintBytesWithLinePrefix(w, objBytes, "\t") | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -228,8 +235,8 @@ func logDryRunAction(action core.Action, w io.Writer, marshalFunc MarshalFunc) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // printBytesWithLinePrefix prints objBytes to writer w with linePrefix in the beginning of every line | // PrintBytesWithLinePrefix prints objBytes to writer w with linePrefix in the beginning of every line | ||||||
| func printBytesWithLinePrefix(w io.Writer, objBytes []byte, linePrefix string) { | func PrintBytesWithLinePrefix(w io.Writer, objBytes []byte, linePrefix string) { | ||||||
| 	scanner := bufio.NewScanner(bytes.NewReader(objBytes)) | 	scanner := bufio.NewScanner(bytes.NewReader(objBytes)) | ||||||
| 	for scanner.Scan() { | 	for scanner.Scan() { | ||||||
| 		fmt.Fprintf(w, "%s%s\n", linePrefix, scanner.Text()) | 		fmt.Fprintf(w, "%s%s\n", linePrefix, scanner.Text()) | ||||||
|   | |||||||
| @@ -60,6 +60,8 @@ func TestLogDryRunAction(t *testing.T) { | |||||||
| 				}, | 				}, | ||||||
| 			}), | 			}), | ||||||
| 			expectedBytes: []byte(`[dryrun] Would perform action CREATE on resource "services" in API group "core/v1" | 			expectedBytes: []byte(`[dryrun] Would perform action CREATE on resource "services" in API group "core/v1" | ||||||
|  | 	apiVersion: v1 | ||||||
|  | 	kind: Service | ||||||
| 	metadata: | 	metadata: | ||||||
| 	  creationTimestamp: null | 	  creationTimestamp: null | ||||||
| 	  name: foo | 	  name: foo | ||||||
|   | |||||||
| @@ -30,12 +30,40 @@ import ( | |||||||
| 	"k8s.io/kubernetes/cmd/kubeadm/app/constants" | 	"k8s.io/kubernetes/cmd/kubeadm/app/constants" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | // Waiter is an interface for waiting for criterias in Kubernetes to happen | ||||||
|  | type Waiter interface { | ||||||
|  | 	// WaitForAPI waits for the API Server's /healthz endpoint to become "ok" | ||||||
|  | 	WaitForAPI() error | ||||||
|  | 	// WaitForPodsWithLabel waits for Pods in the kube-system namespace to become Ready | ||||||
|  | 	WaitForPodsWithLabel(kvLabel string) error | ||||||
|  | 	// WaitForPodToDisappear waits for the given Pod in the kube-system namespace to be deleted | ||||||
|  | 	WaitForPodToDisappear(staticPodName string) error | ||||||
|  | 	// SetTimeout adjusts the timeout to the specified duration | ||||||
|  | 	SetTimeout(timeout time.Duration) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // KubeWaiter is an implementation of Waiter that is backed by a Kubernetes client | ||||||
|  | type KubeWaiter struct { | ||||||
|  | 	client  clientset.Interface | ||||||
|  | 	timeout time.Duration | ||||||
|  | 	writer  io.Writer | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewKubeWaiter returns a new Waiter object that talks to the given Kubernetes cluster | ||||||
|  | func NewKubeWaiter(client clientset.Interface, timeout time.Duration, writer io.Writer) Waiter { | ||||||
|  | 	return &KubeWaiter{ | ||||||
|  | 		client:  client, | ||||||
|  | 		timeout: timeout, | ||||||
|  | 		writer:  writer, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| // WaitForAPI waits for the API Server's /healthz endpoint to report "ok" | // WaitForAPI waits for the API Server's /healthz endpoint to report "ok" | ||||||
| func WaitForAPI(client clientset.Interface, timeout time.Duration) error { | func (w *KubeWaiter) WaitForAPI() error { | ||||||
| 	start := time.Now() | 	start := time.Now() | ||||||
| 	return wait.PollImmediate(constants.APICallRetryInterval, timeout, func() (bool, error) { | 	return wait.PollImmediate(constants.APICallRetryInterval, w.timeout, func() (bool, error) { | ||||||
| 		healthStatus := 0 | 		healthStatus := 0 | ||||||
| 		client.Discovery().RESTClient().Get().AbsPath("/healthz").Do().StatusCode(&healthStatus) | 		w.client.Discovery().RESTClient().Get().AbsPath("/healthz").Do().StatusCode(&healthStatus) | ||||||
| 		if healthStatus != http.StatusOK { | 		if healthStatus != http.StatusOK { | ||||||
| 			return false, nil | 			return false, nil | ||||||
| 		} | 		} | ||||||
| @@ -47,19 +75,19 @@ func WaitForAPI(client clientset.Interface, timeout time.Duration) error { | |||||||
|  |  | ||||||
| // WaitForPodsWithLabel will lookup pods with the given label and wait until they are all | // WaitForPodsWithLabel will lookup pods with the given label and wait until they are all | ||||||
| // reporting status as running. | // reporting status as running. | ||||||
| func WaitForPodsWithLabel(client clientset.Interface, timeout time.Duration, out io.Writer, labelKeyValPair string) error { | func (w *KubeWaiter) WaitForPodsWithLabel(kvLabel string) error { | ||||||
|  |  | ||||||
| 	lastKnownPodNumber := -1 | 	lastKnownPodNumber := -1 | ||||||
| 	return wait.PollImmediate(constants.APICallRetryInterval, timeout, func() (bool, error) { | 	return wait.PollImmediate(constants.APICallRetryInterval, w.timeout, func() (bool, error) { | ||||||
| 		listOpts := metav1.ListOptions{LabelSelector: labelKeyValPair} | 		listOpts := metav1.ListOptions{LabelSelector: kvLabel} | ||||||
| 		pods, err := client.CoreV1().Pods(metav1.NamespaceSystem).List(listOpts) | 		pods, err := w.client.CoreV1().Pods(metav1.NamespaceSystem).List(listOpts) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			fmt.Fprintf(out, "[apiclient] Error getting Pods with label selector %q [%v]\n", labelKeyValPair, err) | 			fmt.Fprintf(w.writer, "[apiclient] Error getting Pods with label selector %q [%v]\n", kvLabel, err) | ||||||
| 			return false, nil | 			return false, nil | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if lastKnownPodNumber != len(pods.Items) { | 		if lastKnownPodNumber != len(pods.Items) { | ||||||
| 			fmt.Fprintf(out, "[apiclient] Found %d Pods for label selector %s\n", len(pods.Items), labelKeyValPair) | 			fmt.Fprintf(w.writer, "[apiclient] Found %d Pods for label selector %s\n", len(pods.Items), kvLabel) | ||||||
| 			lastKnownPodNumber = len(pods.Items) | 			lastKnownPodNumber = len(pods.Items) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -77,10 +105,10 @@ func WaitForPodsWithLabel(client clientset.Interface, timeout time.Duration, out | |||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
| // WaitForStaticPodToDisappear blocks until it timeouts or gets a "NotFound" response from the API Server when getting the Static Pod in question | // WaitForPodToDisappear blocks until it timeouts or gets a "NotFound" response from the API Server when getting the Static Pod in question | ||||||
| func WaitForStaticPodToDisappear(client clientset.Interface, timeout time.Duration, podName string) error { | func (w *KubeWaiter) WaitForPodToDisappear(podName string) error { | ||||||
| 	return wait.PollImmediate(constants.APICallRetryInterval, timeout, func() (bool, error) { | 	return wait.PollImmediate(constants.APICallRetryInterval, w.timeout, func() (bool, error) { | ||||||
| 		_, err := client.CoreV1().Pods(metav1.NamespaceSystem).Get(podName, metav1.GetOptions{}) | 		_, err := w.client.CoreV1().Pods(metav1.NamespaceSystem).Get(podName, metav1.GetOptions{}) | ||||||
| 		if apierrors.IsNotFound(err) { | 		if apierrors.IsNotFound(err) { | ||||||
| 			fmt.Printf("[apiclient] The Static Pod %q is now removed\n", podName) | 			fmt.Printf("[apiclient] The Static Pod %q is now removed\n", podName) | ||||||
| 			return true, nil | 			return true, nil | ||||||
| @@ -89,6 +117,11 @@ func WaitForStaticPodToDisappear(client clientset.Interface, timeout time.Durati | |||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // SetTimeout adjusts the timeout to the specified duration | ||||||
|  | func (w *KubeWaiter) SetTimeout(timeout time.Duration) { | ||||||
|  | 	w.timeout = timeout | ||||||
|  | } | ||||||
|  |  | ||||||
| // TryRunCommand runs a function a maximum of failureThreshold times, and retries on error. If failureThreshold is hit; the last error is returned | // TryRunCommand runs a function a maximum of failureThreshold times, and retries on error. If failureThreshold is hit; the last error is returned | ||||||
| func TryRunCommand(f func() error, failureThreshold uint8) error { | func TryRunCommand(f func() error, failureThreshold uint8) error { | ||||||
| 	var numFailures uint8 | 	var numFailures uint8 | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								cmd/kubeadm/app/util/dryrun/BUILD
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								cmd/kubeadm/app/util/dryrun/BUILD
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | load("@io_bazel_rules_go//go:def.bzl", "go_library") | ||||||
|  |  | ||||||
|  | go_library( | ||||||
|  |     name = "go_default_library", | ||||||
|  |     srcs = ["dryrun.go"], | ||||||
|  |     visibility = ["//visibility:public"], | ||||||
|  |     deps = [ | ||||||
|  |         "//cmd/kubeadm/app/util/apiclient:go_default_library", | ||||||
|  |         "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", | ||||||
|  |         "//vendor/k8s.io/apimachinery/pkg/util/errors: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"], | ||||||
|  | ) | ||||||
							
								
								
									
										100
									
								
								cmd/kubeadm/app/util/dryrun/dryrun.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								cmd/kubeadm/app/util/dryrun/dryrun.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | |||||||
|  | /* | ||||||
|  | Copyright 2017 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 dryrun | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
|  | 	"k8s.io/apimachinery/pkg/util/errors" | ||||||
|  | 	"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // FileToPrint represents a temporary file on disk that might want to be aliased when printing | ||||||
|  | // Useful for things like loading a file from /tmp/ but saying to the user "Would write file foo to /etc/kubernetes/..." | ||||||
|  | type FileToPrint struct { | ||||||
|  | 	RealPath  string | ||||||
|  | 	PrintPath string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewFileToPrint makes a new instance of FileToPrint with the specified arguments | ||||||
|  | func NewFileToPrint(realPath, printPath string) FileToPrint { | ||||||
|  | 	return FileToPrint{ | ||||||
|  | 		RealPath:  realPath, | ||||||
|  | 		PrintPath: printPath, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // PrintDryRunFiles prints the contents of the FileToPrints given to it to the writer w | ||||||
|  | func PrintDryRunFiles(files []FileToPrint, w io.Writer) error { | ||||||
|  | 	errs := []error{} | ||||||
|  | 	for _, file := range files { | ||||||
|  | 		if len(file.RealPath) == 0 { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		fileBytes, err := ioutil.ReadFile(file.RealPath) | ||||||
|  | 		if err != nil { | ||||||
|  | 			errs = append(errs, err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Make it possible to fake the path of the file; i.e. you may want to tell the user | ||||||
|  | 		// "Here is what would be written to /etc/kubernetes/admin.conf", although you wrote it to /tmp/kubeadm-dryrun/admin.conf and are loading it from there | ||||||
|  | 		// Fall back to the "real" path if PrintPath is not set | ||||||
|  | 		outputFilePath := file.PrintPath | ||||||
|  | 		if len(outputFilePath) == 0 { | ||||||
|  | 			outputFilePath = file.RealPath | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		fmt.Fprintf(w, "[dryrun] Would write file %q with content:\n", outputFilePath) | ||||||
|  | 		apiclient.PrintBytesWithLinePrefix(w, fileBytes, "\t") | ||||||
|  | 	} | ||||||
|  | 	return errors.NewAggregate(errs) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Waiter is an implementation of apiclient.Waiter that should be used for dry-running | ||||||
|  | type Waiter struct{} | ||||||
|  |  | ||||||
|  | // NewWaiter returns a new Waiter object that talks to the given Kubernetes cluster | ||||||
|  | func NewWaiter() apiclient.Waiter { | ||||||
|  | 	return &Waiter{} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // WaitForAPI just returns a dummy nil, to indicate that the program should just proceed | ||||||
|  | func (w *Waiter) WaitForAPI() error { | ||||||
|  | 	fmt.Println("[dryrun] Would wait for the API Server's /healthz endpoint to return 'ok'") | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // WaitForPodsWithLabel just returns a dummy nil, to indicate that the program should just proceed | ||||||
|  | func (w *Waiter) WaitForPodsWithLabel(kvLabel string) error { | ||||||
|  | 	fmt.Printf("[dryrun] Would wait for the Pods with the label %q in the %s namespace to become Running\n", kvLabel, metav1.NamespaceSystem) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // WaitForPodToDisappear just returns a dummy nil, to indicate that the program should just proceed | ||||||
|  | func (w *Waiter) WaitForPodToDisappear(podName string) error { | ||||||
|  | 	fmt.Printf("[dryrun] Would wait for the %q Pod in the %s namespace to be deleted\n", podName, metav1.NamespaceSystem) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SetTimeout is a no-op; we don't wait in this implementation | ||||||
|  | func (w *Waiter) SetTimeout(_ time.Duration) {} | ||||||
		Reference in New Issue
	
	Block a user
	 Lucas Käldström
					Lucas Käldström