sigs.k8s.io/cluster-api@v1.7.1/exp/addons/internal/controllers/clusterresourceset_helpers.go (about) 1 /* 2 Copyright 2020 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 controllers 18 19 import ( 20 "bufio" 21 "bytes" 22 "context" 23 "crypto/sha256" 24 "encoding/base64" 25 "encoding/json" 26 "fmt" 27 "sort" 28 "unicode" 29 30 "github.com/pkg/errors" 31 corev1 "k8s.io/api/core/v1" 32 apierrors "k8s.io/apimachinery/pkg/api/errors" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 35 "k8s.io/apimachinery/pkg/runtime/schema" 36 "k8s.io/apimachinery/pkg/types" 37 "k8s.io/klog/v2" 38 "sigs.k8s.io/controller-runtime/pkg/client" 39 40 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 41 addonsv1 "sigs.k8s.io/cluster-api/exp/addons/api/v1beta1" 42 utilresource "sigs.k8s.io/cluster-api/util/resource" 43 utilyaml "sigs.k8s.io/cluster-api/util/yaml" 44 ) 45 46 var jsonListPrefix = []byte("[") 47 48 // objsFromYamlData parses a collection of yaml documents into Unstructured objects. 49 // The returned objects are sorted for creation priority within the objects defined 50 // in the same document. The flattening of the documents preserves the original order. 51 func objsFromYamlData(yamlDocs [][]byte) ([]unstructured.Unstructured, error) { 52 allObjs := []unstructured.Unstructured{} 53 for _, data := range yamlDocs { 54 isJSONList, err := isJSONList(data) 55 if err != nil { 56 return nil, err 57 } 58 objs := []unstructured.Unstructured{} 59 // If it is a json list, convert each list element to an unstructured object. 60 if isJSONList { 61 var results []map[string]interface{} 62 // Unmarshal the JSON to the interface. 63 if err = json.Unmarshal(data, &results); err == nil { 64 for i := range results { 65 var u unstructured.Unstructured 66 u.SetUnstructuredContent(results[i]) 67 objs = append(objs, u) 68 } 69 } 70 } else { 71 // If it is not a json list, data is either json or yaml format. 72 objs, err = utilyaml.ToUnstructured(data) 73 if err != nil { 74 return nil, errors.Wrapf(err, "failed converting data to unstructured objects") 75 } 76 } 77 78 allObjs = append(allObjs, utilresource.SortForCreate(objs)...) 79 } 80 81 return allObjs, nil 82 } 83 84 // isJSONList returns whether the data is in JSON list format. 85 func isJSONList(data []byte) (bool, error) { 86 const peekSize = 32 87 buffer := bufio.NewReaderSize(bytes.NewReader(data), peekSize) 88 b, err := buffer.Peek(peekSize) 89 if err != nil { 90 return false, err 91 } 92 trim := bytes.TrimLeftFunc(b, unicode.IsSpace) 93 return bytes.HasPrefix(trim, jsonListPrefix), nil 94 } 95 96 func createUnstructured(ctx context.Context, c client.Client, obj *unstructured.Unstructured) error { 97 if err := c.Create(ctx, obj); err != nil { 98 return errors.Wrapf( 99 err, 100 "creating object %s %s", 101 obj.GroupVersionKind(), 102 klog.KObj(obj), 103 ) 104 } 105 106 return nil 107 } 108 109 // getOrCreateClusterResourceSetBinding retrieves ClusterResourceSetBinding resource owned by the cluster or create a new one if not found. 110 func (r *ClusterResourceSetReconciler) getOrCreateClusterResourceSetBinding(ctx context.Context, cluster *clusterv1.Cluster, clusterResourceSet *addonsv1.ClusterResourceSet) (*addonsv1.ClusterResourceSetBinding, error) { 111 clusterResourceSetBinding := &addonsv1.ClusterResourceSetBinding{} 112 clusterResourceSetBindingKey := client.ObjectKey{ 113 Namespace: cluster.Namespace, 114 Name: cluster.Name, 115 } 116 117 if err := r.Client.Get(ctx, clusterResourceSetBindingKey, clusterResourceSetBinding); err != nil { 118 if !apierrors.IsNotFound(err) { 119 return nil, err 120 } 121 clusterResourceSetBinding.Name = cluster.Name 122 clusterResourceSetBinding.Namespace = cluster.Namespace 123 clusterResourceSetBinding.OwnerReferences = []metav1.OwnerReference{ 124 { 125 APIVersion: addonsv1.GroupVersion.String(), 126 Kind: "ClusterResourceSet", 127 Name: clusterResourceSet.Name, 128 UID: clusterResourceSet.UID, 129 }, 130 } 131 clusterResourceSetBinding.Spec.Bindings = []*addonsv1.ResourceSetBinding{} 132 clusterResourceSetBinding.Spec.ClusterName = cluster.Name 133 if err := r.Client.Create(ctx, clusterResourceSetBinding); err != nil { 134 if apierrors.IsAlreadyExists(err) { 135 if err = r.Client.Get(ctx, clusterResourceSetBindingKey, clusterResourceSetBinding); err != nil { 136 return nil, err 137 } 138 return clusterResourceSetBinding, nil 139 } 140 return nil, errors.Wrapf(err, "failed to create clusterResourceSetBinding for cluster: %s/%s", cluster.Namespace, cluster.Name) 141 } 142 } 143 return clusterResourceSetBinding, nil 144 } 145 146 // getConfigMap retrieves any ConfigMap from the given name and namespace. 147 func getConfigMap(ctx context.Context, c client.Client, configmapName types.NamespacedName) (*corev1.ConfigMap, error) { 148 configMap := &corev1.ConfigMap{} 149 configMapKey := client.ObjectKey{ 150 Namespace: configmapName.Namespace, 151 Name: configmapName.Name, 152 } 153 if err := c.Get(ctx, configMapKey, configMap); err != nil { 154 return nil, err 155 } 156 157 return configMap, nil 158 } 159 160 // getSecret retrieves any Secret from the given secret name and namespace. 161 func getSecret(ctx context.Context, c client.Client, secretName types.NamespacedName) (*corev1.Secret, error) { 162 secret := &corev1.Secret{} 163 secretKey := client.ObjectKey{ 164 Namespace: secretName.Namespace, 165 Name: secretName.Name, 166 } 167 if err := c.Get(ctx, secretKey, secret); err != nil { 168 return nil, err 169 } 170 171 return secret, nil 172 } 173 174 func computeHash(dataArr [][]byte) string { 175 hash := sha256.New() 176 for i := range dataArr { 177 _, err := hash.Write(dataArr[i]) 178 if err != nil { 179 continue 180 } 181 } 182 return fmt.Sprintf("sha256:%x", hash.Sum(nil)) 183 } 184 185 // normalizeData reads content of the resource (configmap or secret) and returns 186 // them serialized with constant order. Secret's data is base64 decoded. 187 // This is useful to achieve consistent data between runs, since the content 188 // of the data field is a map and its order is non-deterministic. 189 func normalizeData(resource *unstructured.Unstructured) ([][]byte, error) { 190 // Since maps are not ordered, we need to order them to get the same hash at each reconcile. 191 keys := make([]string, 0) 192 data, ok := resource.UnstructuredContent()["data"] 193 if !ok { 194 return nil, errors.Errorf("failed to get data field from resource %s", klog.KObj(resource)) 195 } 196 197 unstructuredData := data.(map[string]interface{}) 198 for key := range unstructuredData { 199 keys = append(keys, key) 200 } 201 sort.Strings(keys) 202 203 dataList := make([][]byte, 0) 204 for _, key := range keys { 205 val, ok, err := unstructured.NestedString(unstructuredData, key) 206 if err != nil { 207 return nil, errors.Wrapf(err, "getting value for field %s in data from resource %s", key, klog.KObj(resource)) 208 } 209 if !ok { 210 return nil, errors.Errorf("value for field %s not present in data from resource %s", key, klog.KObj(resource)) 211 } 212 213 byteArr := []byte(val) 214 // If the resource is a Secret, data needs to be decoded. 215 if resource.GetKind() == string(addonsv1.SecretClusterResourceSetResourceKind) { 216 byteArr, _ = base64.StdEncoding.DecodeString(val) 217 } 218 219 dataList = append(dataList, byteArr) 220 } 221 222 return dataList, nil 223 } 224 225 func getClusterNameFromOwnerRef(obj metav1.ObjectMeta) (string, error) { 226 for _, ref := range obj.GetOwnerReferences() { 227 if ref.Kind != "Cluster" { 228 continue 229 } 230 gv, err := schema.ParseGroupVersion(ref.APIVersion) 231 if err != nil { 232 return "", errors.Wrap(err, "failed to find cluster name in ownerRefs") 233 } 234 235 if gv.Group != clusterv1.GroupVersion.Group { 236 continue 237 } 238 if ref.Name == "" { 239 return "", errors.New("failed to find cluster name in ownerRefs: ref name is empty") 240 } 241 return ref.Name, nil 242 } 243 return "", errors.New("failed to find cluster name in ownerRefs: no cluster ownerRef") 244 } 245 246 // ensureKubernetesServiceCreated ensures that the Service for Kubernetes API Server has been created. 247 func ensureKubernetesServiceCreated(ctx context.Context, client client.Client) error { 248 err := client.Get(ctx, types.NamespacedName{ 249 Namespace: metav1.NamespaceDefault, 250 Name: "kubernetes", 251 }, &corev1.Service{}) 252 if err != nil { 253 return err 254 } 255 return nil 256 }