mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 09:22:44 +00:00
kubeadm: Fully implement 'kubeadm init --dry-run'
This commit is contained in:
parent
cb6f32e8ba
commit
2c71814344
@ -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) {}
|
Loading…
Reference in New Issue
Block a user