mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 03:41:45 +00:00
Merge pull request #63450 from chuckha/images
Automatic merge from submit-queue (batch tested with PRs 62665, 62194, 63616, 63672, 63450). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Adds kubeadm images command Closes kubernetes/kubeadm#388 Signed-off-by: Chuck Ha <ha.chuck@gmail.com> **What this PR does / why we need it**: This PR adds a `list-images` subcommand to `kubeadm config`. We need this to make installing kubernetes on air-gapped environments a little easier. This command will print out a list of images it expects to use for the master node. **Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*: Fixes kubernetes/kubeadm#388 **Special notes for your reviewer**: **Release note**: ```release-note Adds a list-images subcommand to kubeadm that lists required images for a kubeadm install. ```
This commit is contained in:
commit
828ffd5a4e
@ -126,3 +126,14 @@ filegroup(
|
|||||||
],
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_xtest",
|
||||||
|
srcs = ["config_test.go"],
|
||||||
|
deps = [
|
||||||
|
":go_default_library",
|
||||||
|
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
|
||||||
|
"//cmd/kubeadm/app/features:go_default_library",
|
||||||
|
"//vendor/github.com/renstrom/dedent:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@ -19,17 +19,21 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"github.com/renstrom/dedent"
|
"github.com/renstrom/dedent"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
flag "github.com/spf13/pflag"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||||
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||||
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/constants"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
||||||
|
"k8s.io/kubernetes/cmd/kubeadm/app/images"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig"
|
"k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig"
|
||||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||||
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
||||||
@ -62,7 +66,7 @@ func NewCmdConfig(out io.Writer) *cobra.Command {
|
|||||||
|
|
||||||
cmd.AddCommand(NewCmdConfigUpload(out, &kubeConfigFile))
|
cmd.AddCommand(NewCmdConfigUpload(out, &kubeConfigFile))
|
||||||
cmd.AddCommand(NewCmdConfigView(out, &kubeConfigFile))
|
cmd.AddCommand(NewCmdConfigView(out, &kubeConfigFile))
|
||||||
|
cmd.AddCommand(NewCmdConfigListImages(out))
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,3 +205,70 @@ func uploadConfiguration(client clientset.Interface, cfgPath string, defaultcfg
|
|||||||
// Then just call the uploadconfig phase to do the rest of the work
|
// Then just call the uploadconfig phase to do the rest of the work
|
||||||
return uploadconfig.UploadConfiguration(internalcfg, client)
|
return uploadconfig.UploadConfiguration(internalcfg, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewCmdConfigListImages returns the "kubeadm images" command
|
||||||
|
func NewCmdConfigListImages(out io.Writer) *cobra.Command {
|
||||||
|
cfg := &kubeadmapiext.MasterConfiguration{}
|
||||||
|
kubeadmapiext.SetDefaults_MasterConfiguration(cfg)
|
||||||
|
var cfgPath, featureGatesString string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "list-images",
|
||||||
|
Short: "Print a list of images kubeadm will use. The configuration file is used in case any images or image repositories are customized.",
|
||||||
|
Run: func(_ *cobra.Command, _ []string) {
|
||||||
|
if cfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, featureGatesString); err != nil {
|
||||||
|
kubeadmutil.CheckErr(err)
|
||||||
|
}
|
||||||
|
listImages, err := NewListImages(cfgPath, cfg)
|
||||||
|
kubeadmutil.CheckErr(err)
|
||||||
|
kubeadmutil.CheckErr(listImages.Run(out))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
AddListImagesConfigFlag(cmd.PersistentFlags(), cfg, &featureGatesString)
|
||||||
|
AddListImagesFlags(cmd.PersistentFlags(), &cfgPath)
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListImages returns a "kubeadm images" command
|
||||||
|
func NewListImages(cfgPath string, cfg *kubeadmapiext.MasterConfiguration) (*ListImages, error) {
|
||||||
|
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not convert cfg to an internal cfg: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ListImages{
|
||||||
|
cfg: internalcfg,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListImages defines the struct used for "kubeadm images"
|
||||||
|
type ListImages struct {
|
||||||
|
cfg *kubeadmapi.MasterConfiguration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run runs the images command and writes the result to the io.Writer passed in
|
||||||
|
func (i *ListImages) Run(out io.Writer) error {
|
||||||
|
imgs := images.GetAllImages(i.cfg)
|
||||||
|
for _, img := range imgs {
|
||||||
|
fmt.Fprintln(out, img)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddListImagesConfigFlag adds the flags that configure kubeadm
|
||||||
|
func AddListImagesConfigFlag(flagSet *flag.FlagSet, cfg *kubeadmapiext.MasterConfiguration, featureGatesString *string) {
|
||||||
|
flagSet.StringVar(
|
||||||
|
&cfg.KubernetesVersion, "kubernetes-version", cfg.KubernetesVersion,
|
||||||
|
`Choose a specific Kubernetes version for the control plane.`,
|
||||||
|
)
|
||||||
|
flagSet.StringVar(featureGatesString, "feature-gates", *featureGatesString, "A set of key=value pairs that describe feature gates for various features. "+
|
||||||
|
"Options are:\n"+strings.Join(features.KnownFeatures(&features.InitFeatureGates), "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddListImagesFlags adds the flag that defines the location of the config file
|
||||||
|
func AddListImagesFlags(flagSet *flag.FlagSet, cfgPath *string) {
|
||||||
|
flagSet.StringVar(cfgPath, "config", *cfgPath, "Path to kubeadm config file.")
|
||||||
|
}
|
||||||
|
172
cmd/kubeadm/app/cmd/config_test.go
Normal file
172
cmd/kubeadm/app/cmd/config_test.go
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 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 cmd_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/renstrom/dedent"
|
||||||
|
|
||||||
|
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||||
|
"k8s.io/kubernetes/cmd/kubeadm/app/cmd"
|
||||||
|
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultNumberOfImages = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewCmdConfigListImages(t *testing.T) {
|
||||||
|
var output bytes.Buffer
|
||||||
|
images := cmd.NewCmdConfigListImages(&output)
|
||||||
|
images.Run(nil, nil)
|
||||||
|
actual := strings.Split(output.String(), "\n")
|
||||||
|
if len(actual) != defaultNumberOfImages {
|
||||||
|
t.Fatalf("Expected %v but found %v images", defaultNumberOfImages, len(actual))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListImagesRunWithCustomConfigPath(t *testing.T) {
|
||||||
|
testcases := []struct {
|
||||||
|
name string
|
||||||
|
expectedImageCount int
|
||||||
|
// each string provided here must appear in at least one image returned by Run
|
||||||
|
expectedImageSubstrings []string
|
||||||
|
configContents []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty config contents",
|
||||||
|
expectedImageCount: defaultNumberOfImages,
|
||||||
|
configContents: []byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "set k8s version",
|
||||||
|
expectedImageCount: defaultNumberOfImages,
|
||||||
|
expectedImageSubstrings: []string{
|
||||||
|
":v1.9.1",
|
||||||
|
},
|
||||||
|
configContents: []byte(dedent.Dedent(`
|
||||||
|
apiVersion: kubeadm.k8s.io/v1alpha1
|
||||||
|
kind: MasterConfiguration
|
||||||
|
kubernetesVersion: 1.9.1
|
||||||
|
`)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "use coredns",
|
||||||
|
expectedImageCount: defaultNumberOfImages,
|
||||||
|
expectedImageSubstrings: []string{
|
||||||
|
"coredns",
|
||||||
|
},
|
||||||
|
configContents: []byte(dedent.Dedent(`
|
||||||
|
apiVersion: kubeadm.k8s.io/v1alpha1
|
||||||
|
kind: MasterConfiguration
|
||||||
|
featureGates:
|
||||||
|
CoreDNS: True
|
||||||
|
`)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
tmpDir, err := ioutil.TempDir("", "kubeadm-images-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to create temporary directory: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
configFilePath := filepath.Join(tmpDir, "test-config-file")
|
||||||
|
err = ioutil.WriteFile(configFilePath, tc.configContents, 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed writing a config file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
i, err := cmd.NewListImages(configFilePath, &kubeadmapiext.MasterConfiguration{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed getting the kubeadm images command: %v", err)
|
||||||
|
}
|
||||||
|
var output bytes.Buffer
|
||||||
|
if i.Run(&output) != nil {
|
||||||
|
t.Fatalf("Error from running the images command: %v", err)
|
||||||
|
}
|
||||||
|
actual := strings.Split(output.String(), "\n")
|
||||||
|
if len(actual) != tc.expectedImageCount {
|
||||||
|
t.Fatalf("did not get the same number of images: actual: %v expected: %v. Actual value: %v", len(actual), tc.expectedImageCount, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, substring := range tc.expectedImageSubstrings {
|
||||||
|
if !strings.Contains(output.String(), substring) {
|
||||||
|
t.Errorf("Expected to find %v but did not in this list of images: %v", substring, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigListImagesRunWithoutPath(t *testing.T) {
|
||||||
|
testcases := []struct {
|
||||||
|
name string
|
||||||
|
cfg kubeadmapiext.MasterConfiguration
|
||||||
|
expectedImages int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty config",
|
||||||
|
expectedImages: defaultNumberOfImages,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "external etcd configuration",
|
||||||
|
cfg: kubeadmapiext.MasterConfiguration{
|
||||||
|
Etcd: kubeadmapiext.Etcd{
|
||||||
|
Endpoints: []string{"hi"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedImages: defaultNumberOfImages - 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "coredns enabled",
|
||||||
|
cfg: kubeadmapiext.MasterConfiguration{
|
||||||
|
FeatureGates: map[string]bool{
|
||||||
|
features.CoreDNS: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedImages: defaultNumberOfImages,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
i, err := cmd.NewListImages("", &tc.cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("did not expect an error while creating the Images command: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var output bytes.Buffer
|
||||||
|
if i.Run(&output) != nil {
|
||||||
|
t.Fatalf("did not expect an error running the Images command: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.Split(output.String(), "\n")
|
||||||
|
if len(actual) != tc.expectedImages {
|
||||||
|
t.Fatalf("expected %v images but got %v", tc.expectedImages, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,10 @@ go_library(
|
|||||||
srcs = ["images.go"],
|
srcs = ["images.go"],
|
||||||
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/images",
|
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/images",
|
||||||
deps = [
|
deps = [
|
||||||
|
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||||
"//cmd/kubeadm/app/constants:go_default_library",
|
"//cmd/kubeadm/app/constants:go_default_library",
|
||||||
|
"//cmd/kubeadm/app/features:go_default_library",
|
||||||
|
"//cmd/kubeadm/app/phases/addons/dns:go_default_library",
|
||||||
"//cmd/kubeadm/app/util:go_default_library",
|
"//cmd/kubeadm/app/util:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -20,7 +20,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
|
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
||||||
|
"k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns"
|
||||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -42,3 +45,25 @@ func GetCoreImage(image, repoPrefix, k8sVersion, overrideImage string) string {
|
|||||||
constants.KubeScheduler: fmt.Sprintf("%s/%s-%s:%s", repoPrefix, "kube-scheduler", runtime.GOARCH, kubernetesImageTag),
|
constants.KubeScheduler: fmt.Sprintf("%s/%s-%s:%s", repoPrefix, "kube-scheduler", runtime.GOARCH, kubernetesImageTag),
|
||||||
}[image]
|
}[image]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAllImages returns a list of container images kubeadm expects to use on a control plane node
|
||||||
|
func GetAllImages(cfg *kubeadmapi.MasterConfiguration) []string {
|
||||||
|
imgs := []string{}
|
||||||
|
imgs = append(imgs, GetCoreImage(constants.KubeAPIServer, cfg.ImageRepository, cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage))
|
||||||
|
imgs = append(imgs, GetCoreImage(constants.KubeControllerManager, cfg.ImageRepository, cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage))
|
||||||
|
imgs = append(imgs, GetCoreImage(constants.KubeScheduler, cfg.ImageRepository, cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage))
|
||||||
|
imgs = append(imgs, fmt.Sprintf("%v/%v-%v:%v", cfg.ImageRepository, constants.KubeProxy, runtime.GOARCH, kubeadmutil.KubernetesVersionToImageTag(cfg.KubernetesVersion)))
|
||||||
|
imgs = append(imgs, fmt.Sprintf("%v/pause-%v:%v", cfg.ImageRepository, runtime.GOARCH, "3.1"))
|
||||||
|
|
||||||
|
// if etcd is not external then add the image as it will be required
|
||||||
|
if len(cfg.Etcd.Endpoints) == 0 {
|
||||||
|
imgs = append(imgs, GetCoreImage(constants.Etcd, cfg.ImageRepository, cfg.KubernetesVersion, cfg.Etcd.Image))
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsImage := fmt.Sprintf("%v/k8s-dns-kube-dns-%v:%v", cfg.ImageRepository, runtime.GOARCH, dns.GetDNSVersion(nil, constants.KubeDNS))
|
||||||
|
if features.Enabled(cfg.FeatureGates, features.CoreDNS) {
|
||||||
|
dnsImage = fmt.Sprintf("coredns/coredns:%v", dns.GetDNSVersion(nil, constants.CoreDNS))
|
||||||
|
}
|
||||||
|
imgs = append(imgs, dnsImage)
|
||||||
|
return imgs
|
||||||
|
}
|
||||||
|
@ -58,6 +58,7 @@ docs/admin/kubeadm_alpha_phase_selfhosting_convert-from-staticpods.md
|
|||||||
docs/admin/kubeadm_alpha_phase_upload-config.md
|
docs/admin/kubeadm_alpha_phase_upload-config.md
|
||||||
docs/admin/kubeadm_completion.md
|
docs/admin/kubeadm_completion.md
|
||||||
docs/admin/kubeadm_config.md
|
docs/admin/kubeadm_config.md
|
||||||
|
docs/admin/kubeadm_config_list-images.md
|
||||||
docs/admin/kubeadm_config_upload.md
|
docs/admin/kubeadm_config_upload.md
|
||||||
docs/admin/kubeadm_config_upload_from-file.md
|
docs/admin/kubeadm_config_upload_from-file.md
|
||||||
docs/admin/kubeadm_config_upload_from-flags.md
|
docs/admin/kubeadm_config_upload_from-flags.md
|
||||||
@ -132,6 +133,7 @@ docs/man/man1/kubeadm-alpha-phase-upload-config.1
|
|||||||
docs/man/man1/kubeadm-alpha-phase.1
|
docs/man/man1/kubeadm-alpha-phase.1
|
||||||
docs/man/man1/kubeadm-alpha.1
|
docs/man/man1/kubeadm-alpha.1
|
||||||
docs/man/man1/kubeadm-completion.1
|
docs/man/man1/kubeadm-completion.1
|
||||||
|
docs/man/man1/kubeadm-config-list-images.1
|
||||||
docs/man/man1/kubeadm-config-upload-from-file.1
|
docs/man/man1/kubeadm-config-upload-from-file.1
|
||||||
docs/man/man1/kubeadm-config-upload-from-flags.1
|
docs/man/man1/kubeadm-config-upload-from-flags.1
|
||||||
docs/man/man1/kubeadm-config-upload.1
|
docs/man/man1/kubeadm-config-upload.1
|
||||||
|
3
docs/admin/kubeadm_config_list-images.md
Normal file
3
docs/admin/kubeadm_config_list-images.md
Normal file
@ -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.
|
3
docs/man/man1/kubeadm-config-list-images.1
Normal file
3
docs/man/man1/kubeadm-config-list-images.1
Normal file
@ -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.
|
Loading…
Reference in New Issue
Block a user