diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 0bae95c2d47..dc03e0490e4 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -21,9 +21,7 @@ import ( "io" "os" "path/filepath" - "text/template" - "github.com/lithammer/dedent" "github.com/pkg/errors" "github.com/spf13/cobra" flag "github.com/spf13/pflag" @@ -48,46 +46,6 @@ import ( kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" ) -var ( - initDoneTempl = template.Must(template.New("init").Parse(dedent.Dedent(` - Your Kubernetes control-plane has initialized successfully! - - To start using your cluster, you need to run the following as a regular user: - - mkdir -p $HOME/.kube - sudo cp -i {{.KubeConfigPath}} $HOME/.kube/config - sudo chown $(id -u):$(id -g) $HOME/.kube/config - - Alternatively, if you are the root user, you can run: - - export KUBECONFIG=/etc/kubernetes/admin.conf - - You should now deploy a pod network to the cluster. - Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at: - https://kubernetes.io/docs/concepts/cluster-administration/addons/ - - {{if .ControlPlaneEndpoint -}} - {{if .UploadCerts -}} - You can now join any number of the control-plane node running the following command on each as root: - - {{.joinControlPlaneCommand}} - - Please note that the certificate-key gives access to cluster sensitive data, keep it secret! - As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use - "kubeadm init phase upload-certs --upload-certs" to reload certs afterward. - - {{else -}} - You can now join any number of control-plane nodes by copying certificate authorities - and service account keys on each node and then running the following as root: - - {{.joinControlPlaneCommand}} - - {{end}}{{end}}Then you can join any number of worker nodes by running the following on each as root: - - {{.joinWorkerCommand}} - `))) -) - // initOptions defines all the init options exposed via flags by kubeadm init. // Please note that this structure includes the public kubeadm config API, but only a subset of the options // supported by this api will be exposed as a flag. @@ -151,11 +109,7 @@ func newCmdInit(out io.Writer, initOptions *initOptions) *cobra.Command { data := c.(*initData) fmt.Printf("[init] Using Kubernetes version: %s\n", data.cfg.KubernetesVersion) - if err := initRunner.Run(args); err != nil { - return err - } - - return showJoinCommand(data, out) + return initRunner.Run(args) }, Args: cobra.NoArgs, } @@ -191,6 +145,7 @@ func newCmdInit(out io.Writer, initOptions *initOptions) *cobra.Command { initRunner.AppendPhase(phases.NewBootstrapTokenPhase()) initRunner.AppendPhase(phases.NewKubeletFinalizePhase()) initRunner.AppendPhase(phases.NewAddonPhase()) + initRunner.AppendPhase(phases.NewShowJoinCommandPhase()) // sets the data builder function, that will be used by the runner // both when running the entire workflow or single phases @@ -565,39 +520,3 @@ func (d *initData) PatchesDir() string { } return "" } - -func printJoinCommand(out io.Writer, adminKubeConfigPath, token string, i *initData) error { - joinControlPlaneCommand, err := cmdutil.GetJoinControlPlaneCommand(adminKubeConfigPath, token, i.CertificateKey(), i.skipTokenPrint, i.skipCertificateKeyPrint) - if err != nil { - return err - } - - joinWorkerCommand, err := cmdutil.GetJoinWorkerCommand(adminKubeConfigPath, token, i.skipTokenPrint) - if err != nil { - return err - } - - ctx := map[string]interface{}{ - "KubeConfigPath": adminKubeConfigPath, - "ControlPlaneEndpoint": i.Cfg().ControlPlaneEndpoint, - "UploadCerts": i.uploadCerts, - "joinControlPlaneCommand": joinControlPlaneCommand, - "joinWorkerCommand": joinWorkerCommand, - } - - return initDoneTempl.Execute(out, ctx) -} - -// showJoinCommand prints the join command after all the phases in init have finished -func showJoinCommand(i *initData, out io.Writer) error { - adminKubeConfigPath := i.KubeConfigPath() - - // Prints the join command, multiple times in case the user has multiple tokens - for _, token := range i.Tokens() { - if err := printJoinCommand(out, adminKubeConfigPath, token, i); err != nil { - return errors.Wrap(err, "failed to print join command") - } - } - - return nil -} diff --git a/cmd/kubeadm/app/cmd/phases/init/showjoincommand.go b/cmd/kubeadm/app/cmd/phases/init/showjoincommand.go new file mode 100644 index 00000000000..c340e70ef27 --- /dev/null +++ b/cmd/kubeadm/app/cmd/phases/init/showjoincommand.go @@ -0,0 +1,119 @@ +/* +Copyright 2022 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 phases + +import ( + "io" + "text/template" + + "github.com/lithammer/dedent" + "github.com/pkg/errors" + + "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow" + cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" +) + +var ( + initDoneTempl = template.Must(template.New("init").Parse(dedent.Dedent(` + Your Kubernetes control-plane has initialized successfully! + + To start using your cluster, you need to run the following as a regular user: + + mkdir -p $HOME/.kube + sudo cp -i {{.KubeConfigPath}} $HOME/.kube/config + sudo chown $(id -u):$(id -g) $HOME/.kube/config + + Alternatively, if you are the root user, you can run: + + export KUBECONFIG=/etc/kubernetes/admin.conf + + You should now deploy a pod network to the cluster. + Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at: + https://kubernetes.io/docs/concepts/cluster-administration/addons/ + + {{if .ControlPlaneEndpoint -}} + {{if .UploadCerts -}} + You can now join any number of the control-plane node running the following command on each as root: + + {{.joinControlPlaneCommand}} + + Please note that the certificate-key gives access to cluster sensitive data, keep it secret! + As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use + "kubeadm init phase upload-certs --upload-certs" to reload certs afterward. + + {{else -}} + You can now join any number of control-plane nodes by copying certificate authorities + and service account keys on each node and then running the following as root: + + {{.joinControlPlaneCommand}} + + {{end}}{{end}}Then you can join any number of worker nodes by running the following on each as root: + + {{.joinWorkerCommand}} + `))) +) + +// NewShowJoinCommandPhase creates a kubeadm workflow phase that implements showing the join command. +func NewShowJoinCommandPhase() workflow.Phase { + return workflow.Phase{ + Name: "show-join-command", + Short: "Show the join command for control-plane and worker node", + Run: showJoinCommand, + Dependencies: []string{"bootstrap-token", "upload-certs"}, + } +} + +// showJoinCommand prints the join command after all the phases in init have finished +func showJoinCommand(c workflow.RunData) error { + data, ok := c.(InitData) + if !ok { + return errors.New("show-join-command phase invoked with an invalid data struct") + } + + adminKubeConfigPath := data.KubeConfigPath() + + // Prints the join command, multiple times in case the user has multiple tokens + for _, token := range data.Tokens() { + if err := printJoinCommand(data.OutputWriter(), adminKubeConfigPath, token, data); err != nil { + return errors.Wrap(err, "failed to print join command") + } + } + + return nil +} + +func printJoinCommand(out io.Writer, adminKubeConfigPath, token string, i InitData) error { + joinControlPlaneCommand, err := cmdutil.GetJoinControlPlaneCommand(adminKubeConfigPath, token, i.CertificateKey(), i.SkipTokenPrint(), i.SkipCertificateKeyPrint()) + if err != nil { + return err + } + + joinWorkerCommand, err := cmdutil.GetJoinWorkerCommand(adminKubeConfigPath, token, i.SkipTokenPrint()) + if err != nil { + return err + } + + ctx := map[string]interface{}{ + "KubeConfigPath": adminKubeConfigPath, + "ControlPlaneEndpoint": i.Cfg().ControlPlaneEndpoint, + "UploadCerts": i.UploadCerts(), + "joinControlPlaneCommand": joinControlPlaneCommand, + "joinWorkerCommand": joinWorkerCommand, + } + + return initDoneTempl.Execute(out, ctx) +} diff --git a/cmd/kubeadm/app/cmd/phases/workflow/phase.go b/cmd/kubeadm/app/cmd/phases/workflow/phase.go index 554635ac56c..3c3488e6e39 100644 --- a/cmd/kubeadm/app/cmd/phases/workflow/phase.go +++ b/cmd/kubeadm/app/cmd/phases/workflow/phase.go @@ -78,6 +78,9 @@ type Phase struct { // ArgsValidator defines the positional arg function to be used for validating args for this phase // If not set a phase will adopt the args of the top level command. ArgsValidator cobra.PositionalArgs + + // Dependencies is a list of phases that the specific phase depends on. + Dependencies []string } // AppendPhase adds the given phase to the nested, ordered sequence of phases. diff --git a/cmd/kubeadm/app/cmd/phases/workflow/runner.go b/cmd/kubeadm/app/cmd/phases/workflow/runner.go index 32e44e5f1d6..dc0f59b4244 100644 --- a/cmd/kubeadm/app/cmd/phases/workflow/runner.go +++ b/cmd/kubeadm/app/cmd/phases/workflow/runner.go @@ -198,6 +198,29 @@ func (e *Runner) Run(args []string) error { return err } + // precheck phase dependencies before actual execution + missedDeps := make(map[string][]string) + visited := make(map[string]struct{}) + for _, p := range e.phaseRunners { + if run, ok := phaseRunFlags[p.generatedName]; !run || !ok { + continue + } + for _, dep := range p.Phase.Dependencies { + if _, ok := visited[dep]; !ok { + missedDeps[p.Phase.Name] = append(missedDeps[p.Phase.Name], dep) + } + } + visited[p.Phase.Name] = struct{}{} + } + if len(missedDeps) > 0 { + var msg strings.Builder + msg.WriteString("unresolved dependencies:") + for phase, missedPhases := range missedDeps { + msg.WriteString(fmt.Sprintf("\n\tmissing %v phase(s) needed by %q phase", missedPhases, phase)) + } + return errors.New(msg.String()) + } + // builds the runner data var data RunData if data, err = e.InitData(args); err != nil {