diff --git a/cmd/kubeadm/app/cmd/alpha/alpha.go b/cmd/kubeadm/app/cmd/alpha/alpha.go index dbcc722ed21..b3ca7c28169 100644 --- a/cmd/kubeadm/app/cmd/alpha/alpha.go +++ b/cmd/kubeadm/app/cmd/alpha/alpha.go @@ -55,7 +55,6 @@ func newCmdPhase(out io.Writer) *cobra.Command { cmd.AddCommand(phases.NewCmdBootstrapToken()) cmd.AddCommand(phases.NewCmdMarkMaster()) cmd.AddCommand(phases.NewCmdSelfhosting()) - cmd.AddCommand(phases.NewCmdUploadConfig()) return cmd } diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index ab8f25d5868..ce9151c1517 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -50,9 +50,7 @@ import ( kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet" markmasterphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/markmaster" - patchnodephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/patchnode" selfhostingphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting" - uploadconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig" "k8s.io/kubernetes/cmd/kubeadm/app/preflight" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" @@ -183,6 +181,7 @@ func NewCmdInit(out io.Writer) *cobra.Command { initRunner.AppendPhase(phases.NewControlPlanePhase()) initRunner.AppendPhase(phases.NewEtcdPhase()) initRunner.AppendPhase(phases.NewWaitControlPlanePhase()) + initRunner.AppendPhase(phases.NewUploadConfigPhase()) // TODO: add other phases to the runner. // sets the data builder function, that will be used by the runner @@ -487,30 +486,12 @@ func runInit(i *initData, out io.Writer) error { return errors.Wrap(err, "failed to create waiter") } - // Upload currently used configuration to the cluster - // Note: This is done right in the beginning of cluster initialization; as we might want to make other phases - // depend on centralized information from this source in the future - glog.V(1).Infof("[init] uploading currently used configuration to the cluster") - if err := uploadconfigphase.UploadConfiguration(i.cfg, client); err != nil { - return errors.Wrap(err, "error uploading configuration") - } - - glog.V(1).Infof("[init] creating kubelet configuration configmap") - if err := kubeletphase.CreateConfigMap(i.cfg, client); err != nil { - return errors.Wrap(err, "error creating kubelet configuration ConfigMap") - } - // PHASE 4: Mark the master with the right label/taint glog.V(1).Infof("[init] marking the master with right label") if err := markmasterphase.MarkMaster(client, i.cfg.NodeRegistration.Name, i.cfg.NodeRegistration.Taints); err != nil { return errors.Wrap(err, "error marking master") } - glog.V(1).Infof("[init] preserving the crisocket information for the master") - if err := patchnodephase.AnnotateCRISocket(client, i.cfg.NodeRegistration.Name, i.cfg.NodeRegistration.CRISocket); err != nil { - return errors.Wrap(err, "error uploading crisocket") - } - // This feature is disabled by default if features.Enabled(i.cfg.FeatureGates, features.DynamicKubeletConfig) { kubeletVersion, err := preflight.GetKubeletVersion(utilsexec.New()) diff --git a/cmd/kubeadm/app/cmd/phases/kubelet.go b/cmd/kubeadm/app/cmd/phases/kubelet.go index 31090005b58..72ef901e61d 100644 --- a/cmd/kubeadm/app/cmd/phases/kubelet.go +++ b/cmd/kubeadm/app/cmd/phases/kubelet.go @@ -19,18 +19,10 @@ package phases import ( "github.com/golang/glog" "github.com/pkg/errors" - "github.com/spf13/cobra" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" - kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1" "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" - "k8s.io/kubernetes/cmd/kubeadm/app/constants" kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet" - patchnodephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/patchnode" - kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" - configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" - kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" "k8s.io/kubernetes/pkg/util/normalizer" ) @@ -39,24 +31,6 @@ var ( # Writes a dynamic environment file with kubelet flags from a InitConfiguration file. kubeadm init phase kubelet-start --config masterconfig.yaml `) - - kubeletConfigUploadLongDesc = normalizer.LongDesc(` - Uploads kubelet configuration extracted from the kubeadm InitConfiguration object to a ConfigMap - of the form kubelet-config-1.X in the cluster, where X is the minor version of the current (API Server) Kubernetes version. - ` + cmdutil.AlphaDisclaimer) - - kubeletConfigUploadExample = normalizer.Examples(` - # Uploads the kubelet configuration from the kubeadm Config file to a ConfigMap in the cluster. - kubeadm alpha phase kubelet config upload --config kubeadm.yaml - `) - - kubeletConfigAnnotateCRILongDesc = normalizer.LongDesc(` - Adds an annotation to the current node with the CRI socket specified in the kubeadm InitConfiguration object. - ` + cmdutil.AlphaDisclaimer) - - kubeletConfigAnnotateCRIExample = normalizer.Examples(` - kubeadm alpha phase kubelet config annotate-cri --config kubeadm.yaml - `) ) // kubeletStartData defines the behavior that a runtime data struct passed to the kubelet start phase @@ -118,104 +92,3 @@ func runKubeletStart(c workflow.RunData) error { return nil } - -// NewCmdKubelet returns command for `kubeadm phase kubelet` -func NewCmdKubelet() *cobra.Command { - cmd := &cobra.Command{ - Use: "kubelet", - Short: "Commands related to handling the kubelet.", - Long: cmdutil.MacroCommandLongDescription, - } - - cmd.AddCommand(NewCmdKubeletConfig()) - return cmd -} - -// NewCmdKubeletConfig returns command for `kubeadm phase kubelet config` -func NewCmdKubeletConfig() *cobra.Command { - cmd := &cobra.Command{ - Use: "config", - Short: "Handles kubelet configuration.", - Long: cmdutil.MacroCommandLongDescription, - } - - cmd.AddCommand(NewCmdKubeletConfigUpload()) - cmd.AddCommand(NewCmdKubeletAnnotateCRI()) - return cmd -} - -// NewCmdKubeletConfigUpload calls cobra.Command for uploading dynamic kubelet configuration -func NewCmdKubeletConfigUpload() *cobra.Command { - cfg := &kubeadmapiv1beta1.InitConfiguration{} - var cfgPath string - kubeConfigFile := constants.GetAdminKubeConfigPath() - - cmd := &cobra.Command{ - Use: "upload", - Short: "Uploads kubelet configuration to a ConfigMap based on a kubeadm InitConfiguration file.", - Long: kubeletConfigUploadLongDesc, - Example: kubeletConfigUploadExample, - Run: func(cmd *cobra.Command, args []string) { - if len(cfgPath) == 0 { - kubeadmutil.CheckErr(errors.New("The --config argument is required")) - } - - // KubernetesVersion is not used, but we set it explicitly to avoid the lookup - // of the version from the internet when executing ConfigFileAndDefaultsToInternalConfig - SetKubernetesVersion(cfg) - - // This call returns the ready-to-use configuration based on the configuration file - internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg) - kubeadmutil.CheckErr(err) - - kubeConfigFile = cmdutil.FindExistingKubeConfig(kubeConfigFile) - client, err := kubeconfigutil.ClientSetFromFile(kubeConfigFile) - kubeadmutil.CheckErr(err) - - err = kubeletphase.CreateConfigMap(internalcfg, client) - kubeadmutil.CheckErr(err) - }, - } - - options.AddKubeConfigFlag(cmd.Flags(), &kubeConfigFile) - options.AddConfigFlag(cmd.Flags(), &cfgPath) - return cmd -} - -// NewCmdKubeletAnnotateCRI calls cobra.Command for annotating the node with the given crisocket -func NewCmdKubeletAnnotateCRI() *cobra.Command { - cfg := &kubeadmapiv1beta1.InitConfiguration{} - var cfgPath string - kubeConfigFile := constants.GetAdminKubeConfigPath() - - cmd := &cobra.Command{ - Use: "annotate-cri", - Short: "annotates the node with the given crisocket", - Long: kubeletConfigAnnotateCRILongDesc, - Example: kubeletConfigAnnotateCRIExample, - Run: func(cmd *cobra.Command, args []string) { - if len(cfgPath) == 0 { - kubeadmutil.CheckErr(errors.New("The --config argument is required")) - } - - // KubernetesVersion is not used, but we set it explicitly to avoid the lookup - // of the version from the internet when executing ConfigFileAndDefaultsToInternalConfig - SetKubernetesVersion(cfg) - - // This call returns the ready-to-use configuration based on the configuration file - internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg) - kubeadmutil.CheckErr(err) - - kubeConfigFile = cmdutil.FindExistingKubeConfig(kubeConfigFile) - client, err := kubeconfigutil.ClientSetFromFile(kubeConfigFile) - kubeadmutil.CheckErr(err) - - err = patchnodephase.AnnotateCRISocket(client, internalcfg.NodeRegistration.Name, internalcfg.NodeRegistration.CRISocket) - kubeadmutil.CheckErr(err) - }, - } - - options.AddKubeConfigFlag(cmd.Flags(), &kubeConfigFile) - options.AddConfigFlag(cmd.Flags(), &cfgPath) - return cmd -} diff --git a/cmd/kubeadm/app/cmd/phases/uploadconfig.go b/cmd/kubeadm/app/cmd/phases/uploadconfig.go index 5eaa8c0cd73..f7318f03122 100644 --- a/cmd/kubeadm/app/cmd/phases/uploadconfig.go +++ b/cmd/kubeadm/app/cmd/phases/uploadconfig.go @@ -19,70 +19,127 @@ package phases import ( "fmt" + "github.com/golang/glog" "github.com/pkg/errors" - "github.com/spf13/cobra" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1" + clientset "k8s.io/client-go/kubernetes" + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" "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" + kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet" + patchnodephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/patchnode" "k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig" - kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" - configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" - kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" "k8s.io/kubernetes/pkg/util/normalizer" ) var ( - uploadConfigLongDesc = fmt.Sprintf(normalizer.LongDesc(` - Uploads the kubeadm init configuration of your cluster to a ConfigMap called %s in the %s namespace. + uploadKubeadmConfigLongDesc = fmt.Sprintf(normalizer.LongDesc(` + Uploads the kubeadm ClusterConfiguration to a ConfigMap called %s in the %s namespace. This enables correct configuration of system components and a seamless user experience when upgrading. Alternatively, you can use kubeadm config. - `+cmdutil.AlphaDisclaimer), kubeadmconstants.KubeadmConfigConfigMap, metav1.NamespaceSystem) + `), kubeadmconstants.KubeadmConfigConfigMap, metav1.NamespaceSystem) - uploadConfigExample = normalizer.Examples(` + uploadKubeadmConfigExample = normalizer.Examples(` # uploads the configuration of your cluster kubeadm alpha phase upload-config --config=myConfig.yaml `) + + uploadKubeletConfigLongDesc = normalizer.LongDesc(` + Uploads kubelet configuration extracted from the kubeadm InitConfiguration object to a ConfigMap + of the form kubelet-config-1.X in the cluster, where X is the minor version of the current (API Server) Kubernetes version. + `) + + uploadKubeletConfigExample = normalizer.Examples(` + # Uploads the kubelet configuration from the kubeadm Config file to a ConfigMap in the cluster. + kubeadm init phase upload-config kubelet --config kubeadm.yaml + `) ) -// NewCmdUploadConfig returns the Cobra command for running the uploadconfig phase -func NewCmdUploadConfig() *cobra.Command { - cfg := &kubeadmapiv1beta1.InitConfiguration{} - kubeConfigFile := kubeadmconstants.GetAdminKubeConfigPath() - var cfgPath string +type uploadConfigData interface { + Cfg() *kubeadmapi.InitConfiguration + Client() (clientset.Interface, error) +} - cmd := &cobra.Command{ - Use: "upload-config", - Short: "Uploads the currently used configuration for kubeadm to a ConfigMap", - Long: uploadConfigLongDesc, - Example: uploadConfigExample, +// NewUploadConfigPhase returns the phase to uploadConfig +func NewUploadConfigPhase() workflow.Phase { + return workflow.Phase{ + Name: "upload-config", Aliases: []string{"uploadconfig"}, - Run: func(_ *cobra.Command, args []string) { - if len(cfgPath) == 0 { - kubeadmutil.CheckErr(errors.New("the --config flag is mandatory")) - } - - kubeConfigFile = cmdutil.FindExistingKubeConfig(kubeConfigFile) - client, err := kubeconfigutil.ClientSetFromFile(kubeConfigFile) - kubeadmutil.CheckErr(err) - - // KubernetesVersion is not used, but we set it explicitly to avoid the lookup - // of the version from the internet when executing ConfigFileAndDefaultsToInternalConfig - SetKubernetesVersion(cfg) - - internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg) - kubeadmutil.CheckErr(err) - - err = uploadconfig.UploadConfiguration(internalcfg, client) - kubeadmutil.CheckErr(err) + Short: "Uploads the kubeadm and kubelet configuration to a ConfigMap", + Long: cmdutil.MacroCommandLongDescription, + Phases: []workflow.Phase{ + { + Name: "kubeadm", + Short: "Uploads the kubeadm ClusterConfiguration to a ConfigMap", + Long: uploadKubeadmConfigLongDesc, + Example: uploadKubeadmConfigExample, + Run: runUploadKubeadmConfig, + CmdFlags: getUploadConfigPhaseFlags(), + }, + { + Name: "kubelet", + Short: "Uploads the kubelet component config to a ConfigMap", + Long: uploadKubeletConfigLongDesc, + Example: uploadKubeletConfigExample, + Run: runUploadKubeletConfig, + CmdFlags: getUploadConfigPhaseFlags(), + }, }, } - - options.AddKubeConfigFlag(cmd.Flags(), &kubeConfigFile) - cmd.Flags().StringVar(&cfgPath, "config", "", "Path to a kubeadm config file. WARNING: Usage of a configuration file is experimental") - - return cmd +} + +func getUploadConfigPhaseFlags() []string { + return []string{ + options.CfgPath, + options.KubeconfigPath, + } +} + +// runUploadKubeadmConfig uploads the kubeadm configuration to a ConfigMap +func runUploadKubeadmConfig(c workflow.RunData) error { + cfg, client, err := getUploadConfigData(c) + if err != nil { + return err + } + + glog.V(1).Infof("[upload-config] Uploading the kubeadm ClusterConfiguration to a ConfigMap") + if err := uploadconfig.UploadConfiguration(cfg, client); err != nil { + return errors.Wrap(err, "error uploading the kubeadm ClusterConfiguration") + } + return nil +} + +// runUploadKubeletConfig uploads the kubelet configuration to a ConfigMap +func runUploadKubeletConfig(c workflow.RunData) error { + cfg, client, err := getUploadConfigData(c) + if err != nil { + return err + } + + glog.V(1).Infof("[upload-config] Uploading the kubelet component config to a ConfigMap") + if err = kubeletphase.CreateConfigMap(cfg, client); err != nil { + return errors.Wrap(err, "error creating kubelet configuration ConfigMap") + } + + glog.V(1).Infof("[upload-config] Preserving the CRISocket information for the control-plane node") + if err := patchnodephase.AnnotateCRISocket(client, cfg.NodeRegistration.Name, cfg.NodeRegistration.CRISocket); err != nil { + return errors.Wrap(err, "Error writing Crisocket information for the control-plane node") + } + return nil +} + +func getUploadConfigData(c workflow.RunData) (*kubeadmapi.InitConfiguration, clientset.Interface, error) { + data, ok := c.(uploadConfigData) + if !ok { + return nil, nil, errors.New("upload-config phase invoked with an invalid data struct") + } + cfg := data.Cfg() + client, err := data.Client() + if err != nil { + return nil, nil, err + } + return cfg, client, err } diff --git a/cmd/kubeadm/app/cmd/phases/workflow/phase.go b/cmd/kubeadm/app/cmd/phases/workflow/phase.go index b0dad8c28ae..5aac7c5a1a2 100644 --- a/cmd/kubeadm/app/cmd/phases/workflow/phase.go +++ b/cmd/kubeadm/app/cmd/phases/workflow/phase.go @@ -24,6 +24,9 @@ type Phase struct { // the same workflow or phases belonging to the same parent phase). Name string + // Aliases returns the aliases for the phase. + Aliases []string + // Short description of the phase. Short string diff --git a/cmd/kubeadm/app/cmd/phases/workflow/runner.go b/cmd/kubeadm/app/cmd/phases/workflow/runner.go index 77d990f33f3..7d3f2e8de9c 100644 --- a/cmd/kubeadm/app/cmd/phases/workflow/runner.go +++ b/cmd/kubeadm/app/cmd/phases/workflow/runner.go @@ -312,6 +312,7 @@ func (e *Runner) BindToCommand(cmd *cobra.Command) { Short: p.Short, Long: p.Long, Example: p.Example, + Aliases: p.Aliases, Run: func(cmd *cobra.Command, args []string) { e.Options.FilterPhases = []string{p.generatedName} if err := e.Run(); err != nil { diff --git a/docs/.generated_docs b/docs/.generated_docs index 0d06ea8a220..9639f6c0db9 100644 --- a/docs/.generated_docs +++ b/docs/.generated_docs @@ -37,7 +37,6 @@ docs/admin/kubeadm_alpha_phase_bootstrap-token_node_allow-post-csrs.md docs/admin/kubeadm_alpha_phase_mark-master.md docs/admin/kubeadm_alpha_phase_selfhosting.md docs/admin/kubeadm_alpha_phase_selfhosting_convert-from-staticpods.md -docs/admin/kubeadm_alpha_phase_upload-config.md docs/admin/kubeadm_alpha_preflight.md docs/admin/kubeadm_alpha_preflight_node.md docs/admin/kubeadm_completion.md @@ -80,6 +79,9 @@ docs/admin/kubeadm_init_phase_kubeconfig_kubelet.md docs/admin/kubeadm_init_phase_kubeconfig_scheduler.md docs/admin/kubeadm_init_phase_kubelet-start.md docs/admin/kubeadm_init_phase_preflight.md +docs/admin/kubeadm_init_phase_upload-config.md +docs/admin/kubeadm_init_phase_upload-config_kubeadm.md +docs/admin/kubeadm_init_phase_upload-config_kubelet.md docs/admin/kubeadm_join.md docs/admin/kubeadm_reset.md docs/admin/kubeadm_token.md @@ -131,7 +133,6 @@ docs/man/man1/kubeadm-alpha-phase-bootstrap-token.1 docs/man/man1/kubeadm-alpha-phase-mark-master.1 docs/man/man1/kubeadm-alpha-phase-selfhosting-convert-from-staticpods.1 docs/man/man1/kubeadm-alpha-phase-selfhosting.1 -docs/man/man1/kubeadm-alpha-phase-upload-config.1 docs/man/man1/kubeadm-alpha-phase.1 docs/man/man1/kubeadm-alpha-preflight-node.1 docs/man/man1/kubeadm-alpha-preflight.1 @@ -175,6 +176,9 @@ docs/man/man1/kubeadm-init-phase-kubeconfig-scheduler.1 docs/man/man1/kubeadm-init-phase-kubeconfig.1 docs/man/man1/kubeadm-init-phase-kubelet-start.1 docs/man/man1/kubeadm-init-phase-preflight.1 +docs/man/man1/kubeadm-init-phase-upload-config-kubeadm.1 +docs/man/man1/kubeadm-init-phase-upload-config-kubelet.1 +docs/man/man1/kubeadm-init-phase-upload-config.1 docs/man/man1/kubeadm-init-phase.1 docs/man/man1/kubeadm-init.1 docs/man/man1/kubeadm-join.1 diff --git a/docs/admin/kubeadm_alpha_phase_upload-config.md b/docs/admin/kubeadm_init_phase_upload-config.md similarity index 100% rename from docs/admin/kubeadm_alpha_phase_upload-config.md rename to docs/admin/kubeadm_init_phase_upload-config.md diff --git a/docs/man/man1/kubeadm-alpha-phase-upload-config.1 b/docs/admin/kubeadm_init_phase_upload-config_kubeadm.md similarity index 100% rename from docs/man/man1/kubeadm-alpha-phase-upload-config.1 rename to docs/admin/kubeadm_init_phase_upload-config_kubeadm.md diff --git a/docs/admin/kubeadm_init_phase_upload-config_kubelet.md b/docs/admin/kubeadm_init_phase_upload-config_kubelet.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/admin/kubeadm_init_phase_upload-config_kubelet.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-init-phase-upload-config-kubeadm.1 b/docs/man/man1/kubeadm-init-phase-upload-config-kubeadm.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-init-phase-upload-config-kubeadm.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-init-phase-upload-config-kubelet.1 b/docs/man/man1/kubeadm-init-phase-upload-config-kubelet.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-init-phase-upload-config-kubelet.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/man/man1/kubeadm-init-phase-upload-config.1 b/docs/man/man1/kubeadm-init-phase-upload-config.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubeadm-init-phase-upload-config.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file.