1/*
2Copyright 2017 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package util
18
19import (
20	"bufio"
21	"bytes"
22	"io"
23
24	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
25	"k8s.io/kubernetes/cmd/kubeadm/app/constants"
26
27	"k8s.io/apimachinery/pkg/runtime"
28	"k8s.io/apimachinery/pkg/runtime/schema"
29	"k8s.io/apimachinery/pkg/runtime/serializer"
30	yamlserializer "k8s.io/apimachinery/pkg/runtime/serializer/yaml"
31	errorsutil "k8s.io/apimachinery/pkg/util/errors"
32	utilyaml "k8s.io/apimachinery/pkg/util/yaml"
33	clientsetscheme "k8s.io/client-go/kubernetes/scheme"
34
35	"github.com/pkg/errors"
36)
37
38// MarshalToYaml marshals an object into yaml.
39func MarshalToYaml(obj runtime.Object, gv schema.GroupVersion) ([]byte, error) {
40	return MarshalToYamlForCodecs(obj, gv, clientsetscheme.Codecs)
41}
42
43// MarshalToYamlForCodecs marshals an object into yaml using the specified codec
44// TODO: Is specifying the gv really needed here?
45// TODO: Can we support json out of the box easily here?
46func MarshalToYamlForCodecs(obj runtime.Object, gv schema.GroupVersion, codecs serializer.CodecFactory) ([]byte, error) {
47	const mediaType = runtime.ContentTypeYAML
48	info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType)
49	if !ok {
50		return []byte{}, errors.Errorf("unsupported media type %q", mediaType)
51	}
52
53	encoder := codecs.EncoderForVersion(info.Serializer, gv)
54	return runtime.Encode(encoder, obj)
55}
56
57// UnmarshalFromYaml unmarshals yaml into an object.
58func UnmarshalFromYaml(buffer []byte, gv schema.GroupVersion) (runtime.Object, error) {
59	return UnmarshalFromYamlForCodecs(buffer, gv, clientsetscheme.Codecs)
60}
61
62// UnmarshalFromYamlForCodecs unmarshals yaml into an object using the specified codec
63// TODO: Is specifying the gv really needed here?
64// TODO: Can we support json out of the box easily here?
65func UnmarshalFromYamlForCodecs(buffer []byte, gv schema.GroupVersion, codecs serializer.CodecFactory) (runtime.Object, error) {
66	const mediaType = runtime.ContentTypeYAML
67	info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType)
68	if !ok {
69		return nil, errors.Errorf("unsupported media type %q", mediaType)
70	}
71
72	decoder := codecs.DecoderToVersion(info.Serializer, gv)
73	return runtime.Decode(decoder, buffer)
74}
75
76// SplitYAMLDocuments reads the YAML bytes per-document, unmarshals the TypeMeta information from each document
77// and returns a map between the GroupVersionKind of the document and the document bytes
78func SplitYAMLDocuments(yamlBytes []byte) (kubeadmapi.DocumentMap, error) {
79	gvkmap := kubeadmapi.DocumentMap{}
80	knownKinds := map[string]bool{}
81	errs := []error{}
82	buf := bytes.NewBuffer(yamlBytes)
83	reader := utilyaml.NewYAMLReader(bufio.NewReader(buf))
84	for {
85		// Read one YAML document at a time, until io.EOF is returned
86		b, err := reader.Read()
87		if err == io.EOF {
88			break
89		} else if err != nil {
90			return nil, err
91		}
92		if len(b) == 0 {
93			break
94		}
95		// Deserialize the TypeMeta information of this byte slice
96		gvk, err := yamlserializer.DefaultMetaFactory.Interpret(b)
97		if err != nil {
98			return nil, err
99		}
100		if len(gvk.Group) == 0 || len(gvk.Version) == 0 || len(gvk.Kind) == 0 {
101			return nil, errors.Errorf("invalid configuration for GroupVersionKind %+v: kind and apiVersion is mandatory information that must be specified", gvk)
102		}
103
104		// Check whether the kind has been registered before. If it has, throw an error
105		if known := knownKinds[gvk.Kind]; known {
106			errs = append(errs, errors.Errorf("invalid configuration: kind %q is specified twice in YAML file", gvk.Kind))
107			continue
108		}
109		knownKinds[gvk.Kind] = true
110
111		// Save the mapping between the gvk and the bytes that object consists of
112		gvkmap[*gvk] = b
113	}
114	if err := errorsutil.NewAggregate(errs); err != nil {
115		return nil, err
116	}
117	return gvkmap, nil
118}
119
120// GroupVersionKindsFromBytes parses the bytes and returns a gvk slice
121func GroupVersionKindsFromBytes(b []byte) ([]schema.GroupVersionKind, error) {
122	gvkmap, err := SplitYAMLDocuments(b)
123	if err != nil {
124		return nil, err
125	}
126	gvks := []schema.GroupVersionKind{}
127	for gvk := range gvkmap {
128		gvks = append(gvks, gvk)
129	}
130	return gvks, nil
131}
132
133// GroupVersionKindsHasKind returns whether the following gvk slice contains the kind given as a parameter
134func GroupVersionKindsHasKind(gvks []schema.GroupVersionKind, kind string) bool {
135	for _, gvk := range gvks {
136		if gvk.Kind == kind {
137			return true
138		}
139	}
140	return false
141}
142
143// GroupVersionKindsHasClusterConfiguration returns whether the following gvk slice contains a ClusterConfiguration object
144func GroupVersionKindsHasClusterConfiguration(gvks ...schema.GroupVersionKind) bool {
145	return GroupVersionKindsHasKind(gvks, constants.ClusterConfigurationKind)
146}
147
148// GroupVersionKindsHasInitConfiguration returns whether the following gvk slice contains a InitConfiguration object
149func GroupVersionKindsHasInitConfiguration(gvks ...schema.GroupVersionKind) bool {
150	return GroupVersionKindsHasKind(gvks, constants.InitConfigurationKind)
151}
152
153// GroupVersionKindsHasJoinConfiguration returns whether the following gvk slice contains a JoinConfiguration object
154func GroupVersionKindsHasJoinConfiguration(gvks ...schema.GroupVersionKind) bool {
155	return GroupVersionKindsHasKind(gvks, constants.JoinConfigurationKind)
156}
157