mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 11:21:47 +00:00
Merge pull request #86810 from bart0sh/PR0087-kubeadm-output-images
kubeadm config images list: implement structured output
This commit is contained in:
commit
5bd719b6a6
@ -47,6 +47,7 @@ func Resource(resource string) schema.GroupResource {
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&BootstrapToken{},
|
||||
&Images{},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
@ -31,3 +31,12 @@ type BootstrapToken struct {
|
||||
|
||||
kubeadmapiv1beta2.BootstrapToken
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// Images represents information for the output produced by 'kubeadm config images list'
|
||||
type Images struct {
|
||||
metav1.TypeMeta
|
||||
|
||||
Images []string
|
||||
}
|
||||
|
@ -59,6 +59,7 @@ func Resource(resource string) schema.GroupResource {
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&BootstrapToken{},
|
||||
&Images{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
|
@ -31,3 +31,12 @@ type BootstrapToken struct {
|
||||
|
||||
kubeadmapiv1beta2.BootstrapToken
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// Images represents information for the output produced by 'kubeadm config images list'
|
||||
type Images struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
Images []string `json:"images"`
|
||||
}
|
||||
|
@ -21,6 +21,8 @@ limitations under the License.
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
unsafe "unsafe"
|
||||
|
||||
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
output "k8s.io/kubernetes/cmd/kubeadm/app/apis/output"
|
||||
@ -43,6 +45,16 @@ func RegisterConversions(s *runtime.Scheme) error {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*Images)(nil), (*output.Images)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_Images_To_output_Images(a.(*Images), b.(*output.Images), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*output.Images)(nil), (*Images)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_output_Images_To_v1alpha1_Images(a.(*output.Images), b.(*Images), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -65,3 +77,23 @@ func autoConvert_output_BootstrapToken_To_v1alpha1_BootstrapToken(in *output.Boo
|
||||
func Convert_output_BootstrapToken_To_v1alpha1_BootstrapToken(in *output.BootstrapToken, out *BootstrapToken, s conversion.Scope) error {
|
||||
return autoConvert_output_BootstrapToken_To_v1alpha1_BootstrapToken(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_Images_To_output_Images(in *Images, out *output.Images, s conversion.Scope) error {
|
||||
out.Images = *(*[]string)(unsafe.Pointer(&in.Images))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_Images_To_output_Images is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_Images_To_output_Images(in *Images, out *output.Images, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_Images_To_output_Images(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_output_Images_To_v1alpha1_Images(in *output.Images, out *Images, s conversion.Scope) error {
|
||||
out.Images = *(*[]string)(unsafe.Pointer(&in.Images))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_output_Images_To_v1alpha1_Images is an autogenerated conversion function.
|
||||
func Convert_output_Images_To_v1alpha1_Images(in *output.Images, out *Images, s conversion.Scope) error {
|
||||
return autoConvert_output_Images_To_v1alpha1_Images(in, out, s)
|
||||
}
|
||||
|
@ -49,3 +49,33 @@ func (in *BootstrapToken) DeepCopyObject() runtime.Object {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Images) DeepCopyInto(out *Images) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
if in.Images != nil {
|
||||
in, out := &in.Images, &out.Images
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Images.
|
||||
func (in *Images) DeepCopy() *Images {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Images)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Images) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
30
cmd/kubeadm/app/apis/output/zz_generated.deepcopy.go
generated
30
cmd/kubeadm/app/apis/output/zz_generated.deepcopy.go
generated
@ -49,3 +49,33 @@ func (in *BootstrapToken) DeepCopyObject() runtime.Object {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Images) DeepCopyInto(out *Images) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
if in.Images != nil {
|
||||
in, out := &in.Images, &out.Images
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Images.
|
||||
func (in *Images) DeepCopy() *Images {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Images)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Images) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/lithammer/dedent"
|
||||
"github.com/pkg/errors"
|
||||
@ -31,10 +32,14 @@ import (
|
||||
"k8s.io/klog"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
||||
kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2"
|
||||
outputapischeme "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/scheme"
|
||||
outputapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha1"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
|
||||
phaseutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases"
|
||||
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
|
||||
@ -45,6 +50,7 @@ import (
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig"
|
||||
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
||||
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/output"
|
||||
utilruntime "k8s.io/kubernetes/cmd/kubeadm/app/util/runtime"
|
||||
utilsexec "k8s.io/utils/exec"
|
||||
)
|
||||
@ -527,6 +533,8 @@ func NewCmdConfigImagesList(out io.Writer, mockK8sVersion *string) *cobra.Comman
|
||||
externalcfg.KubernetesVersion = *mockK8sVersion
|
||||
}
|
||||
|
||||
outputFlags := output.NewOutputFlags(&imageTextPrintFlags{}).WithTypeSetter(outputapischeme.Scheme).WithDefaultOutput(output.TextOutput)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "Print a list of images kubeadm will use. The configuration file is used in case any images or image repositories are customized",
|
||||
@ -536,14 +544,20 @@ func NewCmdConfigImagesList(out io.Writer, mockK8sVersion *string) *cobra.Comman
|
||||
return err
|
||||
}
|
||||
|
||||
printer, err := outputFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imagesList, err := NewImagesList(cfgPath, externalcfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return imagesList.Run(out)
|
||||
return imagesList.Run(out, printer)
|
||||
},
|
||||
}
|
||||
outputFlags.AddFlags(cmd)
|
||||
AddImagesCommonConfigFlags(cmd.PersistentFlags(), externalcfg, &cfgPath, &featureGatesString)
|
||||
return cmd
|
||||
}
|
||||
@ -572,11 +586,39 @@ type ImagesList struct {
|
||||
cfg *kubeadmapi.InitConfiguration
|
||||
}
|
||||
|
||||
// imageTextPrinter prints image info in a text form
|
||||
type imageTextPrinter struct {
|
||||
output.TextPrinter
|
||||
}
|
||||
|
||||
// PrintObj is an implementation of ResourcePrinter.PrintObj for plain text output
|
||||
func (itp *imageTextPrinter) PrintObj(obj runtime.Object, writer io.Writer) error {
|
||||
var err error
|
||||
if imgs, ok := obj.(*outputapiv1alpha1.Images); ok {
|
||||
_, err = fmt.Fprintln(writer, strings.Join(imgs.Images, "\n"))
|
||||
} else {
|
||||
err = errors.New("unexpected object type")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// imageTextPrintFlags provides flags necessary for printing image in a text form.
|
||||
type imageTextPrintFlags struct{}
|
||||
|
||||
// ToPrinter returns kubeadm printer for the text output format
|
||||
func (ipf *imageTextPrintFlags) ToPrinter(outputFormat string) (output.Printer, error) {
|
||||
if outputFormat == output.TextOutput {
|
||||
return &imageTextPrinter{}, nil
|
||||
}
|
||||
return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &outputFormat, AllowedFormats: []string{output.TextOutput}}
|
||||
}
|
||||
|
||||
// Run runs the images command and writes the result to the io.Writer passed in
|
||||
func (i *ImagesList) Run(out io.Writer) error {
|
||||
func (i *ImagesList) Run(out io.Writer, printer output.Printer) error {
|
||||
imgs := images.GetControlPlaneImages(&i.cfg.ClusterConfiguration)
|
||||
for _, img := range imgs {
|
||||
fmt.Fprintln(out, img)
|
||||
|
||||
if err := printer.PrintObj(&outputapiv1alpha1.Images{Images: imgs}, out); err != nil {
|
||||
return errors.Wrap(err, "unable to print images")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -31,9 +31,11 @@ import (
|
||||
"github.com/lithammer/dedent"
|
||||
"github.com/spf13/cobra"
|
||||
kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2"
|
||||
outputapischeme "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/scheme"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/output"
|
||||
utilruntime "k8s.io/kubernetes/cmd/kubeadm/app/util/runtime"
|
||||
"k8s.io/utils/exec"
|
||||
fakeexec "k8s.io/utils/exec/testing"
|
||||
@ -94,6 +96,12 @@ func TestImagesListRunWithCustomConfigPath(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
outputFlags := output.NewOutputFlags(&imageTextPrintFlags{}).WithTypeSetter(outputapischeme.Scheme).WithDefaultOutput(output.TextOutput)
|
||||
printer, err := outputFlags.ToPrinter()
|
||||
if err != nil {
|
||||
t.Fatalf("can't create printer for the output format %s: %+v", output.TextOutput, err)
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tmpDir, err := ioutil.TempDir("", "kubeadm-images-test")
|
||||
@ -114,7 +122,7 @@ func TestImagesListRunWithCustomConfigPath(t *testing.T) {
|
||||
t.Fatalf("Failed getting the kubeadm images command: %v", err)
|
||||
}
|
||||
var output bytes.Buffer
|
||||
if i.Run(&output) != nil {
|
||||
if err = i.Run(&output, printer); err != nil {
|
||||
t.Fatalf("Error from running the images command: %v", err)
|
||||
}
|
||||
actual := strings.Split(output.String(), "\n")
|
||||
@ -175,6 +183,11 @@ func TestConfigImagesListRunWithoutPath(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
outputFlags := output.NewOutputFlags(&imageTextPrintFlags{}).WithTypeSetter(outputapischeme.Scheme).WithDefaultOutput(output.TextOutput)
|
||||
printer, err := outputFlags.ToPrinter()
|
||||
if err != nil {
|
||||
t.Fatalf("can't create printer for the output format %s: %+v", output.TextOutput, err)
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
i, err := NewImagesList("", &tc.cfg)
|
||||
@ -183,7 +196,8 @@ func TestConfigImagesListRunWithoutPath(t *testing.T) {
|
||||
}
|
||||
|
||||
var output bytes.Buffer
|
||||
if i.Run(&output) != nil {
|
||||
|
||||
if err = i.Run(&output, printer); err != nil {
|
||||
t.Fatalf("did not expect an error running the Images command: %v", err)
|
||||
}
|
||||
|
||||
@ -195,6 +209,119 @@ func TestConfigImagesListRunWithoutPath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigImagesListOutput(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
cfg kubeadmapiv1beta2.ClusterConfiguration
|
||||
outputFormat string
|
||||
expectedOutput string
|
||||
}{
|
||||
{
|
||||
name: "text output",
|
||||
cfg: kubeadmapiv1beta2.ClusterConfiguration{
|
||||
KubernetesVersion: dummyKubernetesVersion,
|
||||
},
|
||||
outputFormat: "text",
|
||||
expectedOutput: `k8s.gcr.io/kube-apiserver:v1.16.0
|
||||
k8s.gcr.io/kube-controller-manager:v1.16.0
|
||||
k8s.gcr.io/kube-scheduler:v1.16.0
|
||||
k8s.gcr.io/kube-proxy:v1.16.0
|
||||
k8s.gcr.io/pause:3.2
|
||||
k8s.gcr.io/etcd:3.3.17-0
|
||||
k8s.gcr.io/coredns:1.6.5
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "JSON output",
|
||||
cfg: kubeadmapiv1beta2.ClusterConfiguration{
|
||||
KubernetesVersion: dummyKubernetesVersion,
|
||||
},
|
||||
outputFormat: "json",
|
||||
expectedOutput: `{
|
||||
"kind": "Images",
|
||||
"apiVersion": "output.kubeadm.k8s.io/v1alpha1",
|
||||
"images": [
|
||||
"k8s.gcr.io/kube-apiserver:v1.16.0",
|
||||
"k8s.gcr.io/kube-controller-manager:v1.16.0",
|
||||
"k8s.gcr.io/kube-scheduler:v1.16.0",
|
||||
"k8s.gcr.io/kube-proxy:v1.16.0",
|
||||
"k8s.gcr.io/pause:3.2",
|
||||
"k8s.gcr.io/etcd:3.3.17-0",
|
||||
"k8s.gcr.io/coredns:1.6.5"
|
||||
]
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "YAML output",
|
||||
cfg: kubeadmapiv1beta2.ClusterConfiguration{
|
||||
KubernetesVersion: dummyKubernetesVersion,
|
||||
},
|
||||
outputFormat: "yaml",
|
||||
expectedOutput: `apiVersion: output.kubeadm.k8s.io/v1alpha1
|
||||
images:
|
||||
- k8s.gcr.io/kube-apiserver:v1.16.0
|
||||
- k8s.gcr.io/kube-controller-manager:v1.16.0
|
||||
- k8s.gcr.io/kube-scheduler:v1.16.0
|
||||
- k8s.gcr.io/kube-proxy:v1.16.0
|
||||
- k8s.gcr.io/pause:3.2
|
||||
- k8s.gcr.io/etcd:3.3.17-0
|
||||
- k8s.gcr.io/coredns:1.6.5
|
||||
kind: Images
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "go-template output",
|
||||
cfg: kubeadmapiv1beta2.ClusterConfiguration{
|
||||
KubernetesVersion: dummyKubernetesVersion,
|
||||
},
|
||||
outputFormat: `go-template={{range .images}}{{.}}{{"\n"}}{{end}}`,
|
||||
expectedOutput: `k8s.gcr.io/kube-apiserver:v1.16.0
|
||||
k8s.gcr.io/kube-controller-manager:v1.16.0
|
||||
k8s.gcr.io/kube-scheduler:v1.16.0
|
||||
k8s.gcr.io/kube-proxy:v1.16.0
|
||||
k8s.gcr.io/pause:3.2
|
||||
k8s.gcr.io/etcd:3.3.17-0
|
||||
k8s.gcr.io/coredns:1.6.5
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "JSONPATH output",
|
||||
cfg: kubeadmapiv1beta2.ClusterConfiguration{
|
||||
KubernetesVersion: dummyKubernetesVersion,
|
||||
},
|
||||
outputFormat: `jsonpath={range.images[*]}{@} {end}`,
|
||||
expectedOutput: "k8s.gcr.io/kube-apiserver:v1.16.0 k8s.gcr.io/kube-controller-manager:v1.16.0 k8s.gcr.io/kube-scheduler:v1.16.0 " +
|
||||
"k8s.gcr.io/kube-proxy:v1.16.0 k8s.gcr.io/pause:3.2 k8s.gcr.io/etcd:3.3.17-0 k8s.gcr.io/coredns:1.6.5 ",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
outputFlags := output.NewOutputFlags(&imageTextPrintFlags{}).WithTypeSetter(outputapischeme.Scheme).WithDefaultOutput(tc.outputFormat)
|
||||
printer, err := outputFlags.ToPrinter()
|
||||
if err != nil {
|
||||
t.Fatalf("can't create printer for the output format %s: %+v", tc.outputFormat, err)
|
||||
}
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
i, err := NewImagesList("", &tc.cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("did not expect an error while creating the Images command: %v", err)
|
||||
}
|
||||
|
||||
var output bytes.Buffer
|
||||
|
||||
if err = i.Run(&output, printer); err != nil {
|
||||
t.Fatalf("did not expect an error running the Images command: %v", err)
|
||||
}
|
||||
|
||||
if output.String() != tc.expectedOutput {
|
||||
t.Fatalf("unexpected output:\n|%s|\nexpected:\n|%s|\n", output.String(), tc.expectedOutput)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestImagesPull(t *testing.T) {
|
||||
fcmd := fakeexec.FakeCmd{
|
||||
CombinedOutputScript: []fakeexec.FakeAction{
|
||||
|
Loading…
Reference in New Issue
Block a user