istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/model.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package config 16 17 import ( 18 "bytes" 19 "encoding/json" 20 "fmt" 21 "reflect" 22 "time" 23 24 gogojsonpb "github.com/gogo/protobuf/jsonpb" // nolint: depguard 25 gogoproto "github.com/gogo/protobuf/proto" // nolint: depguard 26 gogotypes "github.com/gogo/protobuf/types" // nolint: depguard 27 "google.golang.org/protobuf/proto" 28 "google.golang.org/protobuf/reflect/protoreflect" 29 "google.golang.org/protobuf/types/known/anypb" 30 "google.golang.org/protobuf/types/known/structpb" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/runtime/schema" 33 kubetypes "k8s.io/apimachinery/pkg/types" 34 "sigs.k8s.io/yaml" 35 36 "istio.io/api/label" 37 "istio.io/istio/pilot/pkg/util/protoconv" 38 "istio.io/istio/pkg/util/gogoprotomarshal" 39 "istio.io/istio/pkg/util/protomarshal" 40 ) 41 42 // Meta is metadata attached to each configuration unit. 43 // The revision is optional, and if provided, identifies the 44 // last update operation on the object. 45 type Meta struct { 46 // GroupVersionKind is a short configuration name that matches the content message type 47 // (e.g. "route-rule") 48 GroupVersionKind GroupVersionKind `json:"type,omitempty"` 49 50 // UID 51 UID string `json:"uid,omitempty"` 52 53 // Name is a unique immutable identifier in a namespace 54 Name string `json:"name,omitempty"` 55 56 // Namespace defines the space for names (optional for some types), 57 // applications may choose to use namespaces for a variety of purposes 58 // (security domains, fault domains, organizational domains) 59 Namespace string `json:"namespace,omitempty"` 60 61 // Domain defines the suffix of the fully qualified name past the namespace. 62 // Domain is not a part of the unique key unlike name and namespace. 63 Domain string `json:"domain,omitempty"` 64 65 // Map of string keys and values that can be used to organize and categorize 66 // (scope and select) objects. 67 Labels map[string]string `json:"labels,omitempty"` 68 69 // Annotations is an unstructured key value map stored with a resource that may be 70 // set by external tools to store and retrieve arbitrary metadata. They are not 71 // queryable and should be preserved when modifying objects. 72 Annotations map[string]string `json:"annotations,omitempty"` 73 74 // ResourceVersion is an opaque identifier for tracking updates to the config registry. 75 // The implementation may use a change index or a commit log for the revision. 76 // The config client should not make any assumptions about revisions and rely only on 77 // exact equality to implement optimistic concurrency of read-write operations. 78 // 79 // The lifetime of an object of a particular revision depends on the underlying data store. 80 // The data store may compactify old revisions in the interest of storage optimization. 81 // 82 // An empty revision carries a special meaning that the associated object has 83 // not been stored and assigned a revision. 84 ResourceVersion string `json:"resourceVersion,omitempty"` 85 86 // CreationTimestamp records the creation time 87 CreationTimestamp time.Time `json:"creationTimestamp,omitempty"` 88 89 // OwnerReferences allows specifying in-namespace owning objects. 90 OwnerReferences []metav1.OwnerReference `json:"ownerReferences,omitempty"` 91 92 // A sequence number representing a specific generation of the desired state. Populated by the system. Read-only. 93 Generation int64 `json:"generation,omitempty"` 94 } 95 96 // Config is a configuration unit consisting of the type of configuration, the 97 // key identifier that is unique per type, and the content represented as a 98 // protobuf message. 99 type Config struct { 100 Meta 101 102 // Spec holds the configuration object as a gogo protobuf message 103 Spec Spec 104 105 // Status holds long-running status. 106 Status Status 107 } 108 109 func LabelsInRevision(lbls map[string]string, rev string) bool { 110 configEnv, f := lbls[label.IoIstioRev.Name] 111 if !f { 112 // This is a global object, and always included 113 return true 114 } 115 // If the revision is empty, this means we don't specify a revision, and 116 // we should always include it 117 if rev == "" { 118 return true 119 } 120 // Otherwise, only return true if revisions equal 121 return configEnv == rev 122 } 123 124 func ObjectInRevision(o *Config, rev string) bool { 125 return LabelsInRevision(o.Labels, rev) 126 } 127 128 // Spec defines the spec for the config. In order to use below helper methods, 129 // this must be one of: 130 // * golang/protobuf Message 131 // * gogo/protobuf Message 132 // * Able to marshal/unmarshal using json 133 type Spec any 134 135 func ToProto(s Spec) (*anypb.Any, error) { 136 // golang protobuf. Use protoreflect.ProtoMessage to distinguish from gogo 137 // golang/protobuf 1.4+ will have this interface. Older golang/protobuf are gogo compatible 138 // but also not used by Istio at all. 139 if pb, ok := s.(protoreflect.ProtoMessage); ok { 140 return protoconv.MessageToAnyWithError(pb) 141 } 142 143 // gogo protobuf 144 if pb, ok := s.(gogoproto.Message); ok { 145 gogoany, err := gogotypes.MarshalAny(pb) 146 if err != nil { 147 return nil, err 148 } 149 return &anypb.Any{ 150 TypeUrl: gogoany.TypeUrl, 151 Value: gogoany.Value, 152 }, nil 153 } 154 155 js, err := json.Marshal(s) 156 if err != nil { 157 return nil, err 158 } 159 pbs := &structpb.Struct{} 160 if err := protomarshal.Unmarshal(js, pbs); err != nil { 161 return nil, err 162 } 163 return protoconv.MessageToAnyWithError(pbs) 164 } 165 166 func ToMap(s Spec) (map[string]any, error) { 167 js, err := ToJSON(s) 168 if err != nil { 169 return nil, err 170 } 171 172 // Unmarshal from json bytes to go map 173 var data map[string]any 174 err = json.Unmarshal(js, &data) 175 if err != nil { 176 return nil, err 177 } 178 179 return data, nil 180 } 181 182 func ToRaw(s Spec) (json.RawMessage, error) { 183 js, err := ToJSON(s) 184 if err != nil { 185 return nil, err 186 } 187 188 // Unmarshal from json bytes to go map 189 return js, nil 190 } 191 192 func ToJSON(s Spec) ([]byte, error) { 193 return toJSON(s, false) 194 } 195 196 func ToPrettyJSON(s Spec) ([]byte, error) { 197 return toJSON(s, true) 198 } 199 200 func toJSON(s Spec, pretty bool) ([]byte, error) { 201 indent := "" 202 if pretty { 203 indent = " " 204 } 205 206 // golang protobuf. Use protoreflect.ProtoMessage to distinguish from gogo 207 // golang/protobuf 1.4+ will have this interface. Older golang/protobuf are gogo compatible 208 // but also not used by Istio at all. 209 if _, ok := s.(protoreflect.ProtoMessage); ok { 210 if pb, ok := s.(proto.Message); ok { 211 b, err := protomarshal.MarshalIndent(pb, indent) 212 return b, err 213 } 214 } 215 216 b := &bytes.Buffer{} 217 // gogo protobuf 218 if pb, ok := s.(gogoproto.Message); ok { 219 err := (&gogojsonpb.Marshaler{Indent: indent}).Marshal(b, pb) 220 return b.Bytes(), err 221 } 222 if pretty { 223 return json.MarshalIndent(s, "", indent) 224 } 225 return json.Marshal(s) 226 } 227 228 type deepCopier interface { 229 DeepCopyInterface() any 230 } 231 232 func ApplyYAML(s Spec, yml string) error { 233 js, err := yaml.YAMLToJSON([]byte(yml)) 234 if err != nil { 235 return err 236 } 237 return ApplyJSON(s, string(js)) 238 } 239 240 func ApplyJSONStrict(s Spec, js string) error { 241 // golang protobuf. Use protoreflect.ProtoMessage to distinguish from gogo 242 // golang/protobuf 1.4+ will have this interface. Older golang/protobuf are gogo compatible 243 // but also not used by Istio at all. 244 if _, ok := s.(protoreflect.ProtoMessage); ok { 245 if pb, ok := s.(proto.Message); ok { 246 err := protomarshal.ApplyJSONStrict(js, pb) 247 return err 248 } 249 } 250 251 // gogo protobuf 252 if pb, ok := s.(gogoproto.Message); ok { 253 err := gogoprotomarshal.ApplyJSONStrict(js, pb) 254 return err 255 } 256 257 d := json.NewDecoder(bytes.NewReader([]byte(js))) 258 d.DisallowUnknownFields() 259 return d.Decode(&s) 260 } 261 262 func ApplyJSON(s Spec, js string) error { 263 // golang protobuf. Use protoreflect.ProtoMessage to distinguish from gogo 264 // golang/protobuf 1.4+ will have this interface. Older golang/protobuf are gogo compatible 265 // but also not used by Istio at all. 266 if _, ok := s.(protoreflect.ProtoMessage); ok { 267 if pb, ok := s.(proto.Message); ok { 268 err := protomarshal.ApplyJSON(js, pb) 269 return err 270 } 271 } 272 273 // gogo protobuf 274 if pb, ok := s.(gogoproto.Message); ok { 275 err := gogoprotomarshal.ApplyJSON(js, pb) 276 return err 277 } 278 279 return json.Unmarshal([]byte(js), &s) 280 } 281 282 func DeepCopy(s any) any { 283 if s == nil { 284 return nil 285 } 286 // If deep copy is defined, use that 287 if dc, ok := s.(deepCopier); ok { 288 return dc.DeepCopyInterface() 289 } 290 291 // golang protobuf. Use protoreflect.ProtoMessage to distinguish from gogo 292 // golang/protobuf 1.4+ will have this interface. Older golang/protobuf are gogo compatible 293 // but also not used by Istio at all. 294 if _, ok := s.(protoreflect.ProtoMessage); ok { 295 if pb, ok := s.(proto.Message); ok { 296 return proto.Clone(pb) 297 } 298 } 299 300 // gogo protobuf 301 if pb, ok := s.(gogoproto.Message); ok { 302 return gogoproto.Clone(pb) 303 } 304 305 // If we don't have a deep copy method, we will have to do some reflection magic. Its not ideal, 306 // but all Istio types have an efficient deep copy. 307 js, err := json.Marshal(s) 308 if err != nil { 309 return nil 310 } 311 312 data := reflect.New(reflect.TypeOf(s)).Interface() 313 if err := json.Unmarshal(js, data); err != nil { 314 return nil 315 } 316 data = reflect.ValueOf(data).Elem().Interface() 317 return data 318 } 319 320 type Status any 321 322 // Key function for the configuration objects 323 func Key(grp, ver, typ, name, namespace string) string { 324 return grp + "/" + ver + "/" + typ + "/" + namespace + "/" + name // Format: %s/%s/%s/%s/%s 325 } 326 327 // Key is the unique identifier for a configuration object 328 func (meta *Meta) Key() string { 329 return Key( 330 meta.GroupVersionKind.Group, meta.GroupVersionKind.Version, meta.GroupVersionKind.Kind, 331 meta.Name, meta.Namespace) 332 } 333 334 func (meta *Meta) ToObjectMeta() metav1.ObjectMeta { 335 return metav1.ObjectMeta{ 336 Name: meta.Name, 337 Namespace: meta.Namespace, 338 UID: kubetypes.UID(meta.UID), 339 ResourceVersion: meta.ResourceVersion, 340 Generation: meta.Generation, 341 CreationTimestamp: metav1.NewTime(meta.CreationTimestamp), 342 Labels: meta.Labels, 343 Annotations: meta.Annotations, 344 OwnerReferences: meta.OwnerReferences, 345 } 346 } 347 348 func (c Config) DeepCopy() Config { 349 var clone Config 350 clone.Meta = c.Meta 351 if c.Labels != nil { 352 clone.Labels = make(map[string]string, len(c.Labels)) 353 for k, v := range c.Labels { 354 clone.Labels[k] = v 355 } 356 } 357 if c.Annotations != nil { 358 clone.Annotations = make(map[string]string, len(c.Annotations)) 359 for k, v := range c.Annotations { 360 clone.Annotations[k] = v 361 } 362 } 363 clone.Spec = DeepCopy(c.Spec) 364 if c.Status != nil { 365 clone.Status = DeepCopy(c.Status) 366 } 367 return clone 368 } 369 370 func (c Config) GetName() string { 371 return c.Name 372 } 373 374 func (c Config) GetNamespace() string { 375 return c.Namespace 376 } 377 378 func (c Config) GetCreationTimestamp() time.Time { 379 return c.CreationTimestamp 380 } 381 382 func (c Config) NamespacedName() kubetypes.NamespacedName { 383 return kubetypes.NamespacedName{ 384 Namespace: c.Namespace, 385 Name: c.Name, 386 } 387 } 388 389 var _ fmt.Stringer = GroupVersionKind{} 390 391 type GroupVersionKind struct { 392 Group string `json:"group"` 393 Version string `json:"version"` 394 Kind string `json:"kind"` 395 } 396 397 func (g GroupVersionKind) String() string { 398 return g.CanonicalGroup() + "/" + g.Version + "/" + g.Kind 399 } 400 401 // GroupVersion returns the group/version similar to what would be found in the apiVersion field of a Kubernetes resource. 402 func (g GroupVersionKind) GroupVersion() string { 403 if g.Group == "" { 404 return g.Version 405 } 406 return g.Group + "/" + g.Version 407 } 408 409 func FromKubernetesGVK(gvk schema.GroupVersionKind) GroupVersionKind { 410 return GroupVersionKind{ 411 Group: gvk.Group, 412 Version: gvk.Version, 413 Kind: gvk.Kind, 414 } 415 } 416 417 // Kubernetes returns the same GVK, using the Kubernetes object type 418 func (g GroupVersionKind) Kubernetes() schema.GroupVersionKind { 419 return schema.GroupVersionKind{ 420 Group: g.Group, 421 Version: g.Version, 422 Kind: g.Kind, 423 } 424 } 425 426 func CanonicalGroup(group string) string { 427 if group != "" { 428 return group 429 } 430 return "core" 431 } 432 433 // CanonicalGroup returns the group with defaulting applied. This means an empty group will 434 // be treated as "core", following Kubernetes API standards 435 func (g GroupVersionKind) CanonicalGroup() string { 436 return CanonicalGroup(g.Group) 437 } 438 439 // PatchFunc provides the cached config as a base for modification. Only diff the between the cfg 440 // parameter and the returned Config will be applied. 441 type PatchFunc func(cfg Config) (Config, kubetypes.PatchType) 442 443 type Namer interface { 444 GetName() string 445 GetNamespace() string 446 } 447 448 func NamespacedName(o metav1.Object) kubetypes.NamespacedName { 449 return kubetypes.NamespacedName{ 450 Namespace: o.GetNamespace(), 451 Name: o.GetName(), 452 } 453 }