Merge pull request #77847 from yagonobre/reset-phase

Add phase runner to kubeadm reset
This commit is contained in:
Kubernetes Prow Robot 2019-05-16 06:05:56 -07:00 committed by GitHub
commit d823fa23c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 163 additions and 79 deletions

View File

@ -86,7 +86,6 @@ go_test(
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1beta2:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
"//cmd/kubeadm/app/cmd/options:go_default_library",
"//cmd/kubeadm/app/componentconfigs:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",

View File

@ -84,7 +84,7 @@ func NewKubeadmCommand(in io.Reader, out, err io.Writer) *cobra.Command {
cmds.AddCommand(NewCmdConfig(out))
cmds.AddCommand(NewCmdInit(out, nil))
cmds.AddCommand(NewCmdJoin(out, nil))
cmds.AddCommand(NewCmdReset(in, out))
cmds.AddCommand(NewCmdReset(in, out, nil))
cmds.AddCommand(NewCmdVersion(out))
cmds.AddCommand(NewCmdToken(out, err))
cmds.AddCommand(upgrade.NewCmdUpgrade(out))

View File

@ -127,4 +127,7 @@ const (
// SkipCertificateKeyPrint flag instruct kubeadm to skip printing certificate key used to encrypt certs by 'kubeadm init'.
SkipCertificateKeyPrint = "skip-certificate-key-print"
// ForceReset flag instruct kubeadm to reset the node without prompting for confirmation
ForceReset = "force"
)

View File

@ -28,6 +28,7 @@ import (
"github.com/lithammer/dedent"
"github.com/pkg/errors"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/util/sets"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/klog"
@ -35,6 +36,7 @@ import (
kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd"
@ -48,99 +50,197 @@ import (
utilsexec "k8s.io/utils/exec"
)
// NewCmdReset returns the "kubeadm reset" command
func NewCmdReset(in io.Reader, out io.Writer) *cobra.Command {
var certsDir string
// resetOptions defines all the options exposed via flags by kubeadm reset.
type resetOptions struct {
certificatesDir string
criSocketPath string
forceReset bool
ignorePreflightErrors []string
kubeconfigPath string
}
// resetData defines all the runtime information used when running the kubeadm reset worklow;
// this data is shared across all the phases that are included in the workflow.
type resetData struct {
certificatesDir string
client clientset.Interface
criSocketPath string
forceReset bool
ignorePreflightErrors sets.String
inputReader io.Reader
outputWriter io.Writer
cfg *kubeadmapi.InitConfiguration
}
// newResetOptions returns a struct ready for being used for creating cmd join flags.
func newResetOptions() *resetOptions {
return &resetOptions{
certificatesDir: kubeadmapiv1beta2.DefaultCertificatesDir,
forceReset: false,
kubeconfigPath: kubeadmconstants.GetAdminKubeConfigPath(),
}
}
// newResetData returns a new resetData struct to be used for the execution of the kubeadm reset workflow.
func newResetData(cmd *cobra.Command, options *resetOptions, in io.Reader, out io.Writer) (*resetData, error) {
var cfg *kubeadmapi.InitConfiguration
ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(options.ignorePreflightErrors)
if err != nil {
return nil, err
}
client, err := getClientset(options.kubeconfigPath, false)
if err == nil {
klog.V(1).Infof("[reset] Loaded client set from kubeconfig file: %s", options.kubeconfigPath)
cfg, err = configutil.FetchInitConfigurationFromCluster(client, out, "reset", false)
if err != nil {
klog.Warningf("[reset] Unable to fetch the kubeadm-config ConfigMap from cluster: %v", err)
}
} else {
klog.V(1).Infof("[reset] Could not obtain a client set from the kubeconfig file: %s", options.kubeconfigPath)
}
var criSocketPath string
var ignorePreflightErrors []string
var forceReset bool
var client clientset.Interface
kubeConfigFile := kubeadmconstants.GetAdminKubeConfigPath()
if options.criSocketPath == "" {
criSocketPath, err = resetDetectCRISocket(cfg)
if err != nil {
return nil, err
}
klog.V(1).Infof("[reset] Detected and using CRI socket: %s", criSocketPath)
}
return &resetData{
certificatesDir: options.certificatesDir,
client: client,
criSocketPath: criSocketPath,
forceReset: options.forceReset,
ignorePreflightErrors: ignorePreflightErrorsSet,
inputReader: in,
outputWriter: out,
cfg: cfg,
}, nil
}
// AddResetFlags adds reset flags
func AddResetFlags(flagSet *flag.FlagSet, resetOptions *resetOptions) {
flagSet.StringVar(
&resetOptions.certificatesDir, options.CertificatesDir, resetOptions.certificatesDir,
`The path to the directory where the certificates are stored. If specified, clean this directory.`,
)
flagSet.BoolVarP(
&resetOptions.forceReset, options.ForceReset, "f", false,
"Reset the node without prompting for confirmation.",
)
options.AddKubeConfigFlag(flagSet, &resetOptions.kubeconfigPath)
options.AddIgnorePreflightErrorsFlag(flagSet, &resetOptions.ignorePreflightErrors)
cmdutil.AddCRISocketFlag(flagSet, &resetOptions.criSocketPath)
}
// NewCmdReset returns the "kubeadm reset" command
func NewCmdReset(in io.Reader, out io.Writer, resetOptions *resetOptions) *cobra.Command {
if resetOptions == nil {
resetOptions = newResetOptions()
}
resetRunner := workflow.NewRunner()
cmd := &cobra.Command{
Use: "reset",
Short: "Run this to revert any changes made to this host by 'kubeadm init' or 'kubeadm join'",
Run: func(cmd *cobra.Command, args []string) {
ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(ignorePreflightErrors)
c, err := resetRunner.InitData(args)
kubeadmutil.CheckErr(err)
var cfg *kubeadmapi.InitConfiguration
client, err = getClientset(kubeConfigFile, false)
if err == nil {
klog.V(1).Infof("[reset] Loaded client set from kubeconfig file: %s", kubeConfigFile)
cfg, err = configutil.FetchInitConfigurationFromCluster(client, os.Stdout, "reset", false)
if err != nil {
klog.Warningf("[reset] Unable to fetch the kubeadm-config ConfigMap from cluster: %v", err)
}
} else {
klog.V(1).Infof("[reset] Could not obtain a client set from the kubeconfig file: %s", kubeConfigFile)
}
if criSocketPath == "" {
criSocketPath, err = resetDetectCRISocket(cfg)
kubeadmutil.CheckErr(err)
klog.V(1).Infof("[reset] Detected and using CRI socket: %s", criSocketPath)
}
r, err := NewReset(in, ignorePreflightErrorsSet, forceReset, certsDir, criSocketPath)
err = resetRunner.Run(args)
kubeadmutil.CheckErr(err)
kubeadmutil.CheckErr(r.Run(out, client, cfg))
// TODO: remove this once we have all phases in place.
// the method joinData.Run() itself should be removed too.
data := c.(*resetData)
kubeadmutil.CheckErr(data.Run())
},
}
options.AddIgnorePreflightErrorsFlag(cmd.PersistentFlags(), &ignorePreflightErrors)
options.AddKubeConfigFlag(cmd.PersistentFlags(), &kubeConfigFile)
AddResetFlags(cmd.Flags(), resetOptions)
cmd.PersistentFlags().StringVar(
&certsDir, "cert-dir", kubeadmapiv1beta2.DefaultCertificatesDir,
"The path to the directory where the certificates are stored. If specified, clean this directory.",
)
// initialize the workflow runner with the list of phases
// TODO: append phases here
cmdutil.AddCRISocketFlag(cmd.PersistentFlags(), &criSocketPath)
// sets the data builder function, that will be used by the runner
// both when running the entire workflow or single phases
resetRunner.SetDataInitializer(func(cmd *cobra.Command, args []string) (workflow.RunData, error) {
return newResetData(cmd, resetOptions, in, out)
})
cmd.PersistentFlags().BoolVarP(
&forceReset, "force", "f", false,
"Reset the node without prompting for confirmation.",
)
// binds the Runner to kubeadm init command by altering
// command help, adding --skip-phases flag and by adding phases subcommands
resetRunner.BindToCommand(cmd)
return cmd
}
// Reset defines struct used for kubeadm reset command
type Reset struct {
certsDir string
criSocketPath string
// Cfg returns the InitConfiguration.
func (r *resetData) Cfg() *kubeadmapi.InitConfiguration {
return r.cfg
}
// NewReset instantiate Reset struct
func NewReset(in io.Reader, ignorePreflightErrors sets.String, forceReset bool, certsDir, criSocketPath string) (*Reset, error) {
if !forceReset {
// CertificatesDir returns the CertificatesDir.
func (r *resetData) CertificatesDir() string {
return r.certificatesDir
}
// Client returns the Client for accessing the cluster.
func (r *resetData) Client() clientset.Interface {
return r.client
}
// ForceReset returns the forceReset flag.
func (r *resetData) ForceReset() bool {
return r.forceReset
}
// InputReader returns the io.reader used to read messages.
func (r *resetData) InputReader() io.Reader {
return r.inputReader
}
// IgnorePreflightErrors returns the list of preflight errors to ignore.
func (r *resetData) IgnorePreflightErrors() sets.String {
return r.ignorePreflightErrors
}
func (r *resetData) preflight() error {
if !r.ForceReset() {
fmt.Println("[reset] WARNING: Changes made to this host by 'kubeadm init' or 'kubeadm join' will be reverted.")
fmt.Print("[reset] Are you sure you want to proceed? [y/N]: ")
s := bufio.NewScanner(in)
s := bufio.NewScanner(r.InputReader())
s.Scan()
if err := s.Err(); err != nil {
return nil, err
return err
}
if strings.ToLower(s.Text()) != "y" {
return nil, errors.New("Aborted reset operation")
return errors.New("Aborted reset operation")
}
}
fmt.Println("[preflight] Running pre-flight checks")
if err := preflight.RunRootCheckOnly(ignorePreflightErrors); err != nil {
return nil, err
if err := preflight.RunRootCheckOnly(r.IgnorePreflightErrors()); err != nil {
return err
}
return &Reset{
certsDir: certsDir,
criSocketPath: criSocketPath,
}, nil
return nil
}
// Run reverts any changes made to this host by "kubeadm init" or "kubeadm join".
func (r *Reset) Run(out io.Writer, client clientset.Interface, cfg *kubeadmapi.InitConfiguration) error {
func (r *resetData) Run() error {
var dirsToClean []string
cfg := r.Cfg()
certsDir := r.CertificatesDir()
client := r.Client()
err := r.preflight()
if err != nil {
return err
}
// Reset the ClusterStatus for a given control-plane node.
if isControlPlane() && cfg != nil {
@ -203,10 +303,10 @@ func (r *Reset) Run(out io.Writer, client clientset.Interface, cfg *kubeadmapi.I
// Remove contents from the config and pki directories
klog.V(1).Infoln("[reset] Removing contents from the config and pki directories")
if r.certsDir != kubeadmapiv1beta2.DefaultCertificatesDir {
klog.Warningf("[reset] WARNING: Cleaning a non-default certificates directory: %q\n", r.certsDir)
if certsDir != kubeadmapiv1beta2.DefaultCertificatesDir {
klog.Warningf("[reset] WARNING: Cleaning a non-default certificates directory: %q\n", certsDir)
}
resetConfigDir(kubeadmconstants.KubernetesDir, r.certsDir)
resetConfigDir(kubeadmconstants.KubernetesDir, certsDir)
// Output help text instructing user how to remove iptables rules
msg := dedent.Dedent(`

View File

@ -17,7 +17,6 @@ limitations under the License.
package cmd
import (
"io"
"io/ioutil"
"os"
"path/filepath"
@ -26,8 +25,6 @@ import (
"github.com/lithammer/dedent"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
@ -85,21 +82,6 @@ func assertDirEmpty(t *testing.T, path string) {
}
}
func TestNewReset(t *testing.T) {
var in io.Reader
certsDir := kubeadmapiv1beta2.DefaultCertificatesDir
criSocketPath := kubeadmconstants.DefaultDockerCRISocket
forceReset := true
ignorePreflightErrors := []string{"all"}
ignorePreflightErrorsSet, _ := validation.ValidateIgnorePreflightErrors(ignorePreflightErrors)
NewReset(in, ignorePreflightErrorsSet, forceReset, certsDir, criSocketPath)
ignorePreflightErrors = []string{}
ignorePreflightErrorsSet, _ = validation.ValidateIgnorePreflightErrors(ignorePreflightErrors)
NewReset(in, ignorePreflightErrorsSet, forceReset, certsDir, criSocketPath)
}
func TestConfigDirCleaner(t *testing.T) {
tests := map[string]struct {
resetDir string