k8s.io/kubernetes@v1.29.3/pkg/kubectl/cmd/convert/convert.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package convert 18 19 import ( 20 "fmt" 21 22 "github.com/spf13/cobra" 23 "k8s.io/klog/v2" 24 25 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 26 "k8s.io/apimachinery/pkg/runtime" 27 "k8s.io/apimachinery/pkg/runtime/schema" 28 "k8s.io/cli-runtime/pkg/genericclioptions" 29 "k8s.io/cli-runtime/pkg/genericiooptions" 30 "k8s.io/cli-runtime/pkg/printers" 31 "k8s.io/cli-runtime/pkg/resource" 32 cmdutil "k8s.io/kubectl/pkg/cmd/util" 33 "k8s.io/kubectl/pkg/util/i18n" 34 "k8s.io/kubectl/pkg/util/templates" 35 "k8s.io/kubectl/pkg/validation" 36 scheme "k8s.io/kubernetes/pkg/api/legacyscheme" 37 api "k8s.io/kubernetes/pkg/apis/core" 38 ) 39 40 var ( 41 convertLong = templates.LongDesc(i18n.T(` 42 Convert config files between different API versions. Both YAML 43 and JSON formats are accepted. 44 45 The command takes filename, directory, or URL as input, and convert it into format 46 of version specified by --output-version flag. If target version is not specified or 47 not supported, convert to latest version. 48 49 The default output will be printed to stdout in YAML format. One can use -o option 50 to change to output destination.`)) 51 52 convertExample = templates.Examples(i18n.T(` 53 # Convert 'pod.yaml' to latest version and print to stdout. 54 kubectl convert -f pod.yaml 55 56 # Convert the live state of the resource specified by 'pod.yaml' to the latest version 57 # and print to stdout in JSON format. 58 kubectl convert -f pod.yaml --local -o json 59 60 # Convert all files under current directory to latest version and create them all. 61 kubectl convert -f . | kubectl create -f -`)) 62 ) 63 64 // ConvertOptions have the data required to perform the convert operation 65 type ConvertOptions struct { 66 PrintFlags *genericclioptions.PrintFlags 67 Printer printers.ResourcePrinter 68 69 OutputVersion string 70 Namespace string 71 72 builder func() *resource.Builder 73 local bool 74 validator func() (validation.Schema, error) 75 76 resource.FilenameOptions 77 genericiooptions.IOStreams 78 } 79 80 func NewConvertOptions(ioStreams genericiooptions.IOStreams) *ConvertOptions { 81 return &ConvertOptions{ 82 PrintFlags: genericclioptions.NewPrintFlags("converted").WithTypeSetter(scheme.Scheme).WithDefaultOutput("yaml"), 83 local: true, 84 IOStreams: ioStreams, 85 } 86 } 87 88 // NewCmdConvert creates a command object for the generic "convert" action, which 89 // translates the config file into a given version. 90 func NewCmdConvert(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { 91 o := NewConvertOptions(ioStreams) 92 93 cmd := &cobra.Command{ 94 Use: "convert -f FILENAME", 95 DisableFlagsInUseLine: true, 96 Short: i18n.T("Convert config files between different API versions"), 97 Long: convertLong, 98 Example: convertExample, 99 Run: func(cmd *cobra.Command, args []string) { 100 cmdutil.CheckErr(o.Complete(f, cmd)) 101 cmdutil.CheckErr(o.RunConvert()) 102 }, 103 } 104 105 cmd.Flags().BoolVar(&o.local, "local", o.local, "If true, convert will NOT try to contact api-server but run locally.") 106 cmd.Flags().StringVar(&o.OutputVersion, "output-version", o.OutputVersion, i18n.T("Output the formatted object with the given group version (for ex: 'extensions/v1beta1').")) 107 o.PrintFlags.AddFlags(cmd) 108 109 cmdutil.AddValidateFlags(cmd) 110 cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "to need to get converted.") 111 return cmd 112 } 113 114 // Complete collects information required to run Convert command from command line. 115 func (o *ConvertOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) (err error) { 116 err = o.FilenameOptions.RequireFilenameOrKustomize() 117 if err != nil { 118 return err 119 } 120 o.builder = f.NewBuilder 121 122 o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace() 123 if err != nil { 124 return err 125 } 126 127 o.validator = func() (validation.Schema, error) { 128 directive, err := cmdutil.GetValidationDirective(cmd) 129 if err != nil { 130 return nil, err 131 } 132 return f.Validator(directive) 133 } 134 135 // build the printer 136 o.Printer, err = o.PrintFlags.ToPrinter() 137 return err 138 } 139 140 // RunConvert implements the generic Convert command 141 func (o *ConvertOptions) RunConvert() error { 142 b := o.builder(). 143 WithScheme(scheme.Scheme). 144 LocalParam(o.local) 145 if !o.local { 146 schema, err := o.validator() 147 if err != nil { 148 return err 149 } 150 b.Schema(schema) 151 } 152 153 r := b.NamespaceParam(o.Namespace). 154 ContinueOnError(). 155 FilenameParam(false, &o.FilenameOptions). 156 Flatten(). 157 Do() 158 159 err := r.Err() 160 if err != nil { 161 return err 162 } 163 164 singleItemImplied := false 165 infos, err := r.IntoSingleItemImplied(&singleItemImplied).Infos() 166 if err != nil { 167 return err 168 } 169 170 if len(infos) == 0 { 171 return fmt.Errorf("no objects passed to convert") 172 } 173 174 var specifiedOutputVersion schema.GroupVersion 175 if len(o.OutputVersion) > 0 { 176 specifiedOutputVersion, err = schema.ParseGroupVersion(o.OutputVersion) 177 if err != nil { 178 return err 179 } 180 } 181 182 internalEncoder := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) 183 internalVersionJSONEncoder := unstructured.NewJSONFallbackEncoder(internalEncoder) 184 objects, err := asVersionedObject(infos, !singleItemImplied, specifiedOutputVersion, internalVersionJSONEncoder, o.IOStreams) 185 if err != nil { 186 return err 187 } 188 189 return o.Printer.PrintObj(objects, o.Out) 190 } 191 192 // asVersionedObject converts a list of infos into a single object - either a List containing 193 // the objects as children, or if only a single Object is present, as that object. The provided 194 // version will be preferred as the conversion target, but the Object's mapping version will be 195 // used if that version is not present. 196 func asVersionedObject(infos []*resource.Info, forceList bool, specifiedOutputVersion schema.GroupVersion, encoder runtime.Encoder, iostream genericclioptions.IOStreams) (runtime.Object, error) { 197 objects, err := asVersionedObjects(infos, specifiedOutputVersion, encoder, iostream) 198 if err != nil { 199 return nil, err 200 } 201 202 var object runtime.Object 203 if len(objects) == 1 && !forceList { 204 object = objects[0] 205 } else { 206 object = &api.List{Items: objects} 207 targetVersions := []schema.GroupVersion{} 208 if !specifiedOutputVersion.Empty() { 209 targetVersions = append(targetVersions, specifiedOutputVersion) 210 } 211 targetVersions = append(targetVersions, schema.GroupVersion{Group: "", Version: "v1"}) 212 213 converted, err := tryConvert(scheme.Scheme, object, targetVersions...) 214 if err != nil { 215 return nil, err 216 } 217 object = converted 218 } 219 220 actualVersion := object.GetObjectKind().GroupVersionKind() 221 if actualVersion.Version != specifiedOutputVersion.Version { 222 defaultVersionInfo := "" 223 if len(actualVersion.Version) > 0 { 224 defaultVersionInfo = fmt.Sprintf("Defaulting to %q", actualVersion.Version) 225 } 226 klog.V(1).Infof("info: the output version specified is invalid. %s\n", defaultVersionInfo) 227 } 228 return object, nil 229 } 230 231 // asVersionedObjects converts a list of infos into versioned objects. The provided 232 // version will be preferred as the conversion target, but the Object's mapping version will be 233 // used if that version is not present. 234 func asVersionedObjects(infos []*resource.Info, specifiedOutputVersion schema.GroupVersion, encoder runtime.Encoder, iostream genericclioptions.IOStreams) ([]runtime.Object, error) { 235 objects := []runtime.Object{} 236 for _, info := range infos { 237 if info.Object == nil { 238 continue 239 } 240 241 targetVersions := []schema.GroupVersion{} 242 // objects that are not part of api.Scheme must be converted to JSON 243 // TODO: convert to map[string]interface{}, attach to runtime.Unknown? 244 if !specifiedOutputVersion.Empty() { 245 if _, _, err := scheme.Scheme.ObjectKinds(info.Object); runtime.IsNotRegisteredError(err) { 246 // TODO: ideally this would encode to version, but we don't expose multiple codecs here. 247 data, err := runtime.Encode(encoder, info.Object) 248 if err != nil { 249 return nil, err 250 } 251 // TODO: Set ContentEncoding and ContentType. 252 objects = append(objects, &runtime.Unknown{Raw: data}) 253 continue 254 } 255 targetVersions = append(targetVersions, specifiedOutputVersion) 256 } else { 257 gvks, _, err := scheme.Scheme.ObjectKinds(info.Object) 258 if err == nil { 259 for _, gvk := range gvks { 260 targetVersions = append(targetVersions, scheme.Scheme.PrioritizedVersionsForGroup(gvk.Group)...) 261 } 262 } 263 } 264 265 converted, err := tryConvert(scheme.Scheme, info.Object, targetVersions...) 266 if err != nil { 267 // Dont fail on not registered error converting objects. 268 // Simply warn the user with the error returned from api-machinery and continue with the rest of the file 269 // fail on all other errors 270 if runtime.IsNotRegisteredError(err) { 271 fmt.Fprintln(iostream.ErrOut, err.Error()) 272 continue 273 } 274 275 return nil, err 276 } 277 objects = append(objects, converted) 278 } 279 return objects, nil 280 } 281 282 // tryConvert attempts to convert the given object to the provided versions in order. This function assumes 283 // the object is in internal version. 284 func tryConvert(converter runtime.ObjectConvertor, object runtime.Object, versions ...schema.GroupVersion) (runtime.Object, error) { 285 var last error 286 for _, version := range versions { 287 if version.Empty() { 288 return object, nil 289 } 290 obj, err := converter.ConvertToVersion(object, version) 291 if err != nil { 292 last = err 293 continue 294 } 295 return obj, nil 296 } 297 return nil, last 298 }