sigs.k8s.io/cluster-api@v1.7.1/util/yaml/yaml.go (about) 1 /* 2 Copyright 2019 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 yaml implements yaml utility functions. 18 package yaml 19 20 import ( 21 "bufio" 22 "bytes" 23 "io" 24 "os" 25 "strings" 26 27 "github.com/MakeNowJust/heredoc" 28 "github.com/pkg/errors" 29 corev1 "k8s.io/api/core/v1" 30 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 31 "k8s.io/apimachinery/pkg/runtime" 32 "k8s.io/apimachinery/pkg/runtime/schema" 33 "k8s.io/apimachinery/pkg/runtime/serializer/streaming" 34 apiyaml "k8s.io/apimachinery/pkg/util/yaml" 35 "k8s.io/client-go/kubernetes/scheme" 36 "sigs.k8s.io/yaml" 37 38 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 39 "sigs.k8s.io/cluster-api/util" 40 ) 41 42 // ExtractClusterReferences returns the references in a Cluster object. 43 func ExtractClusterReferences(out *ParseOutput, c *clusterv1.Cluster) (res []*unstructured.Unstructured) { 44 if c.Spec.InfrastructureRef == nil { 45 return nil 46 } 47 if obj := out.FindUnstructuredReference(c.Spec.InfrastructureRef); obj != nil { 48 res = append(res, obj) 49 } 50 return 51 } 52 53 // ExtractMachineReferences returns the references in a Machine object. 54 func ExtractMachineReferences(out *ParseOutput, m *clusterv1.Machine) (res []*unstructured.Unstructured) { 55 if obj := out.FindUnstructuredReference(&m.Spec.InfrastructureRef); obj != nil { 56 res = append(res, obj) 57 } 58 if m.Spec.Bootstrap.ConfigRef != nil { 59 if obj := out.FindUnstructuredReference(m.Spec.Bootstrap.ConfigRef); obj != nil { 60 res = append(res, obj) 61 } 62 } 63 return 64 } 65 66 // ParseOutput is the output given from the Parse function. 67 type ParseOutput struct { 68 Clusters []*clusterv1.Cluster 69 Machines []*clusterv1.Machine 70 MachineSets []*clusterv1.MachineSet 71 MachineDeployments []*clusterv1.MachineDeployment 72 UnstructuredObjects []*unstructured.Unstructured 73 } 74 75 // Add adds the other ParseOutput slices to this instance. 76 func (p *ParseOutput) Add(o *ParseOutput) *ParseOutput { 77 p.Clusters = append(p.Clusters, o.Clusters...) 78 p.Machines = append(p.Machines, o.Machines...) 79 p.MachineSets = append(p.MachineSets, o.MachineSets...) 80 p.MachineDeployments = append(p.MachineDeployments, o.MachineDeployments...) 81 p.UnstructuredObjects = append(p.UnstructuredObjects, o.UnstructuredObjects...) 82 return p 83 } 84 85 // FindUnstructuredReference takes in an ObjectReference and tries to find an Unstructured object. 86 func (p *ParseOutput) FindUnstructuredReference(ref *corev1.ObjectReference) *unstructured.Unstructured { 87 for _, obj := range p.UnstructuredObjects { 88 if obj.GroupVersionKind() == ref.GroupVersionKind() && 89 ref.Namespace == obj.GetNamespace() && 90 ref.Name == obj.GetName() { 91 return obj 92 } 93 } 94 return nil 95 } 96 97 // ParseInput is an input struct for the Parse function. 98 type ParseInput struct { 99 File string 100 } 101 102 // Parse extracts runtime objects from a file. 103 func Parse(input ParseInput) (*ParseOutput, error) { 104 output := &ParseOutput{} 105 106 // Open the input file. 107 reader, err := os.Open(input.File) 108 if err != nil { 109 return nil, err 110 } 111 112 // Create a new decoder. 113 decoder := NewYAMLDecoder(reader) 114 defer decoder.Close() 115 116 for { 117 u := &unstructured.Unstructured{} 118 _, gvk, err := decoder.Decode(nil, u) 119 if errors.Is(err, io.EOF) { 120 break 121 } 122 if runtime.IsNotRegisteredError(err) { 123 continue 124 } 125 if err != nil { 126 return nil, err 127 } 128 129 switch gvk.Kind { 130 case "Cluster": 131 obj := &clusterv1.Cluster{} 132 if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, obj); err != nil { 133 return nil, errors.Wrapf(err, "cannot convert object to %s", gvk.Kind) 134 } 135 output.Clusters = append(output.Clusters, obj) 136 case "Machine": 137 obj := &clusterv1.Machine{} 138 if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, obj); err != nil { 139 return nil, errors.Wrapf(err, "cannot convert object to %s", gvk.Kind) 140 } 141 output.Machines = append(output.Machines, obj) 142 case "MachineSet": 143 obj := &clusterv1.MachineSet{} 144 if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, obj); err != nil { 145 return nil, errors.Wrapf(err, "cannot convert object to %s", gvk.Kind) 146 } 147 output.MachineSets = append(output.MachineSets, obj) 148 case "MachineDeployment": 149 obj := &clusterv1.MachineDeployment{} 150 if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, obj); err != nil { 151 return nil, errors.Wrapf(err, "cannot convert object to %s", gvk.Kind) 152 } 153 output.MachineDeployments = append(output.MachineDeployments, obj) 154 default: 155 output.UnstructuredObjects = append(output.UnstructuredObjects, u) 156 } 157 } 158 159 return output, nil 160 } 161 162 type yamlDecoder struct { 163 reader *apiyaml.YAMLReader 164 decoder runtime.Decoder 165 close func() error 166 } 167 168 func (d *yamlDecoder) Decode(defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { 169 for { 170 doc, err := d.reader.Read() 171 if err != nil { 172 return nil, nil, err 173 } 174 175 // Skip over empty documents, i.e. a leading `---` 176 if len(bytes.TrimSpace(doc)) == 0 { 177 continue 178 } 179 180 return d.decoder.Decode(doc, defaults, into) 181 } 182 } 183 184 func (d *yamlDecoder) Close() error { 185 return d.close() 186 } 187 188 // NewYAMLDecoder returns a new streaming Decoded that supports YAML. 189 func NewYAMLDecoder(r io.ReadCloser) streaming.Decoder { 190 return &yamlDecoder{ 191 reader: apiyaml.NewYAMLReader(bufio.NewReader(r)), 192 decoder: scheme.Codecs.UniversalDeserializer(), 193 close: r.Close, 194 } 195 } 196 197 // ToUnstructured takes a YAML and converts it to a list of Unstructured objects. 198 func ToUnstructured(rawyaml []byte) ([]unstructured.Unstructured, error) { 199 var ret []unstructured.Unstructured 200 201 reader := apiyaml.NewYAMLReader(bufio.NewReader(bytes.NewReader(rawyaml))) 202 count := 1 203 for { 204 // Read one YAML document at a time, until io.EOF is returned 205 b, err := reader.Read() 206 if err != nil { 207 if errors.Is(err, io.EOF) { 208 break 209 } 210 return nil, errors.Wrapf(err, "failed to read yaml") 211 } 212 if len(b) == 0 { 213 break 214 } 215 216 var m map[string]interface{} 217 if err := yaml.Unmarshal(b, &m); err != nil { 218 return nil, errors.Wrapf(err, "failed to unmarshal the %s yaml document: %q", util.Ordinalize(count), string(b)) 219 } 220 221 var u unstructured.Unstructured 222 u.SetUnstructuredContent(m) 223 224 // Ignore empty objects. 225 // Empty objects are generated if there are weird things in manifest files like e.g. two --- in a row without a yaml doc in the middle 226 if u.Object == nil { 227 continue 228 } 229 230 ret = append(ret, u) 231 count++ 232 } 233 234 return ret, nil 235 } 236 237 // JoinYaml takes a list of YAML files and join them ensuring 238 // each YAML that the yaml separator goes on a new line by adding \n where necessary. 239 func JoinYaml(yamls ...[]byte) []byte { 240 var yamlSeparator = []byte("---") 241 242 var cr = []byte("\n") 243 var b [][]byte //nolint:prealloc 244 for _, y := range yamls { 245 if !bytes.HasPrefix(y, cr) { 246 y = append(cr, y...) 247 } 248 if !bytes.HasSuffix(y, cr) { 249 y = append(y, cr...) 250 } 251 b = append(b, y) 252 } 253 254 r := bytes.Join(b, yamlSeparator) 255 r = bytes.TrimPrefix(r, cr) 256 r = bytes.TrimSuffix(r, cr) 257 258 return r 259 } 260 261 // FromUnstructured takes a list of Unstructured objects and converts it into a YAML. 262 func FromUnstructured(objs []unstructured.Unstructured) ([]byte, error) { 263 var ret [][]byte //nolint:prealloc 264 for _, o := range objs { 265 content, err := yaml.Marshal(o.UnstructuredContent()) 266 if err != nil { 267 return nil, errors.Wrapf(err, "failed to marshal yaml for %s, %s/%s", o.GroupVersionKind(), o.GetNamespace(), o.GetName()) 268 } 269 ret = append(ret, content) 270 } 271 272 return JoinYaml(ret...), nil 273 } 274 275 // Raw returns un-indented yaml string; it also remove the first empty line, if any. 276 // While writing yaml, always use space instead of tabs for indentation. 277 func Raw(raw string) string { 278 return strings.TrimPrefix(heredoc.Doc(raw), "\n") 279 }