© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2023
P. MartinKubernetes Programming with Gohttps://doi.org/10.1007/978-1-4842-9026-2_5

5. The API Machinery

Philippe Martin1  
(1)
Blanquefort, France
 

The previous chapters explored how the Kubernetes API works at the HTTP level. They also explored the Kubernetes API Library, which defines the resources served by the Kubenretes API in Go.

This chapter explores the Kubernetes API Machinery Library, which provides utilities for working with API objects that follow the Kubernetes API object conventions. These conventions include:
  • The API objects embed a common metadata structure, TypeMeta, containing two fields: APIVersion and Kind.

  • The API objects are provided in a separate package.

  • The API objects are versioned.

  • Conversion functions are provided to convert between versions.

The API Machinery will provide the following utilities:
  • A Scheme abstraction, used to:
    • Register the API objects as Group-Version-Kinds

    • Convert between API Objects of different versions

    • Serialize/deserialize API Objects

  • A RESTMapper, mapping between API Objects (based on embedded APIVersion and Kind) and resource names (in the REST sense).

This chapter details the functions provided by the API Machinery.

The Schema Package

The schema package of the API Machinery Library defines useful structures and functions to work with Group, Versions, Kinds, and Resources.
import (
    "k8s.io/apimachinery/pkg/runtime/schema"
)

The structures GroupVersionResource, GroupVersionKind, GroupVersion, GroupResource, and GroupKind are defined, with methods to pass from one to another.

Also, functions to convert between GroupVersionKind and (apiVersion, kind) are provided: ToAPIVersionAndKind and FromAPIVersionAndKind.

A model diagram depicts the A P I machinery schema. 1. Group version. 2. Group resource. 3. Group version resource. 4. Group version kind. 5. Group kind. 6. A P I version kind. The arrows between 1 and 3 indicate with resource and group version. 2 and 3, with version and group resource. 1 and 4, with kind and group version. 4 and 5, with version and group kind.

Figure 5-1

GVK and GVR related structures and methods

Scheme

A Scheme is an abstraction used to register the API objects as Group-Version-Kinds, convert between API Objects of various versions, and serialize/deserialize API Objects. The Scheme is a structure provided by the API Machinery in the runtime package. All the fields of this structure are unexported.

Initialization

The Scheme structure can be initialized with the NewScheme function:
import (
    "k8s.io/apimachinery/pkg/runtime"
)
Scheme := runtime.NewScheme()
After the structure is initialized, you can register new API objects with the AddKnownTypes method as follows:
func (s *Scheme) AddKnownTypes(gv schema.GroupVersion, types ...Object)
For example, to register the Pod and ConfigMap objects into the core/v1 group, you could use:
import (
    corev1 "k8s.io/api/core/v1"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/runtime/schema"
)
Scheme := runtime.NewScheme()
func init() {
     Scheme.AddKnownTypes(
        schema.GroupVersion{
            Group:   "",
            Version: "v1",
        },
        &corev1.Pod{},
        &corev1.ConfigMap{},
     )
}

By doing this, the API Machinery will be able to know that the Group-Version-Kind core-v1-Pod to be used when executing requests related to pods must be the corev1.Pod structure, and the core-v1-ConfigMap to be used when executing requests related to configmaps must be the corev1.ConfigMap structure.

It has been shown that the API objects can be versioned. You can register a same kind for various versions this way—for example, use the following to add the v1 and v1beta1 versions of the Deployment object:
import (
    appsv1 "k8s.io/api/apps/v1"
    appsv1beta1 "k8s.io/api/apps/v1beta1"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/runtime/schema"
)
Scheme := runtime.NewScheme()
func init() {
   Scheme.AddKnownTypes(
      schema.GroupVersion{
         Group:   "apps",
         Version: "v1",
      },
      &appsv1.Deployment{},
   )
   Scheme.AddKnownTypes(
      schema.GroupVersion{
         Group:   "apps",
         Version: "v1beta1",
      },
      &appsv1beta1.Deployment{},
   )
}

It is advisable to initialize the Scheme structure and to add known types to it at the very beginning of the execution—for example, using the init functions.

Mapping

After initialization, you can use various methods on the structure to map between Goup-Version-Kinds and Go Types:
  • KnownTypes(gv schema.GroupVersion) map[string]reflect.Type – gets all the Go types registered for a specific Group-Version—here apps/v1:

types := Scheme.KnownTypes(schema.GroupVersion{
    Group:   "apps",
    Version: "v1",
})
-> ["Deployment": appsv1.Deployment]
  • VersionsForGroupKind(gk schema.GroupKind) []schema.GroupVersion – gets all the Group-Versions registered for a specific Kind—here the Deployment:

groupVersions := Scheme.VersionsForGroupKind(
schema.GroupKind{
        Group: "apps",
        Kind:  "Deployment",
})
-> ["apps/v1" "apps/v1beta1"]
  • ObjectKinds(obj Object) ([]schema.GroupVersionKind, bool, error) – gets all the possible Group-Version-Kinds for a given object—here an appsv1.Deployment:

gvks, notVersioned, err := Scheme.ObjectKinds(&appsv1.Deployment{})
-> ["apps/v1 Deployment"]
  • New(kind schema.GroupVersionKind) (Object, error) – builds an object, given a Group-Version-Kind:

obj, err := Scheme.New(schema.GroupVersionKind{
    Group:   "apps",
    Version: "v1",
    Kind:    "Deployment",
})
  • This method returns a value of type runtime.Object, which is an interface implemented by all the API objects. The concrete type of the value will be the object mapping the Group-Version-Kind—here appsv1.Deployment.

Conversion

The Scheme structure registers Kinds by Group-Version. By providing to the Scheme conversion functions between kinds of the same Group and different Versions, it is then possible to convert between any kinds of the same Group.

It is possible to define conversion functions of two levels: conversion functions and generated conversion functions. Conversion functions are functions written by hand, when generated conversion functions are generated using the conversion-gen tool.

When converting between two versions, the conversion function, if it exists, will take priority over the generated conversion function.

Adding Conversion Functions

These two methods add a conversion function between a and b, which are two objects of types belonging to the same Group.
AddConversionFunc(
    a, b interface{},
    fn conversion.ConversionFunc,
) error
AddGeneratedConversionFunc(
    a, b interface{},
    fn conversion.ConversionFunc,
) error
The a and b values must be pointers to structures and can be nil pointers. The signature of the conversion function is defined as follows:
type ConversionFunc func(
    a, b interface{},
    scope Scope,
) error
Here is an example, to add a conversion function between apps/v1 and apps/v1beta1 deployments:
Scheme.AddConversionFunc(
    (*appsv1.Deployment)(nil),
    (*appsv1beta1.Deployment)(nil),
    func(a, b interface{}, scope conversion.Scope) error{
        v1deploy := a.(*appsv1.Deployment)
        v1beta1deploy := b.(*appsv1beta1.Deployment)
        // make conversion here
        return nil
    })

As for registering known types to the scheme, the recommendation is to register conversion functions at the very beginning of the execution—for example, using init functions.

Converting

Once conversion functions have been registered, it is possible to convert between two versions of the same kind with the Convert function.
Convert(in, out interface{}, context interface{}) error
This example defines a v1.Deployment, then converts it to the v1beta1 version:
v1deployment := appsv1.Deployment{
    [...]
}
v1deployment.SetName("myname")
var v1beta1Deployment appsv1beta1.Deployment
scheme.Convert(&v1deployment, &v1beta1Deployment, nil)

Serialization

Packages of the API Machinery Library provide serializers for various formats: JSON, YAML, and Protobuf. These serializers implement the Serializer interface, which embeds the Encoder and Decoder interfaces. First, you can see how to instantiate serializers for different formats, then how to use them to encode and decode API objects.

JSON and YAML Serializer

The json package provides a serializer for both JSON and YAML formats.
import (
     "k8s.io/apimachinery/pkg/runtime/serializer/json"
)
The NewSerializerWithOptions function is used to create a new serializer.
NewSerializerWithOptions(
    meta      MetaFactory,
    creater   runtime.ObjectCreater,
    typer     runtime.ObjectTyper,
    options   SerializerOptions,
) *Serializer
The options give the possibility to choose between a JSON and a YAML serializer (Yaml field) to choose a human-readable output for a JSON output (Pretty field) and to check for duplicate fields in JSON and YAML (Strict fields).
type SerializerOptions struct {
    Yaml     bool
    Pretty   bool
    Strict   bool
}
The Scheme can be used for creator and typer because it implements these two interfaces, and the SimpleMetaFactory structure can be used as meta.
serializer := jsonserializer.NewSerializerWithOptions(
    jsonserializer.SimpleMetaFactory{},
    Scheme,
    Scheme,
    jsonserializer.SerializerOptions{
        Yaml: false, // or true for YAML serializer
        Pretty: true, // or false for one-line JSON
        Strict: false, // or true to check duplicates
     },
)

Protobuf Serializer

The protobuf package provides a serializer for a Protobuf format.
import (
     "k8s.io/apimachinery/pkg/runtime/serializer/protobuf"
)
The NewSerializer function is used to create a new serializer.
NewSerializer(
     creater     runtime.ObjectCreater,
     typer     runtime.ObjectTyper,
) *Serializer
The Scheme can be used for creator and typer because it implements these two interfaces.
serializer := protobuf.NewSerializer(Scheme, Scheme)

Encoding and Decoding

The various serializers implement the Serializer interface, which embeds the Decoder and Encoder interfaces, defining the Encode and Decode methods.
  • Encode(obj Object, w io.Writer) error – the Encode function takes an API object as a parameter, encodes the object, and writes the result using the writer.

  • Decode(

    data []byte,
    defaults *schema.GroupVersionKind,
    into Object,
) (
    Object,
    *schema.GroupVersionKind,
    error,
)
  • - this function takes an array of bytes as a parameter and tries to decode its content. If the content to decode does not specify apiVersion and Kind, the default GroupVersionKind (GVK) will be used.

  • The result will be placed in the into object if not nil and if the concrete type of into matches the content GVK (either the initial one, or the defaults one). In any case, the result will be returned as an Object, and the GVK applied to it will be returned as a GroupVersionKind structure.

RESTMapper

The API Machinery provides a concept of RESTMapper, used to map between REST resources and Kinds.
import (
    "k8s.io/apimachinery/pkg/api/meta"
)
The RESTMapping type provides the result of a mapping using the RESTMapper:
type RESTMapping struct {
    Resource            schema.GroupVersionResource
    GroupVersionKind    schema.GroupVersionKind
    Scope               RESTScope
}

As Chapter 1 discussed, a GVR (Group-Version-Resource, or Resource for short) is used to build the path to which to make a request. For example, to get the list of deployments in all namespaces, you will use the path /apis/apps/v1/deployments, where apps is the Group, v1 is the Version, and deployments is the (plural) Resource name. So, a resource managed by an API can be uniquely identified by its GVR.

When making requests to this path, generally you want to exchange data, either in the request to create or update a resource, or in the response to get or list resources. The format of this exchanged data is called the Kind (or GroupVersionKind), associated with the resource.

The RESTMapping structure brings together a Resource and its associated GroupVersionKind. The API machinery provides a RESTMapper interface, and a default implementation, DefaultRESTMapper.
type RESTMapper interface {
    RESTMapping(gk schema.GroupKind, versions ...string)
          (*RESTMapping, error)
    RESTMappings(gk schema.GroupKind, versions ...string)
           ([]*RESTMapping, error)
    KindFor(resource schema.GroupVersionResource)
           (schema.GroupVersionKind, error)
    KindsFor(resource schema.GroupVersionResource)
           ([]schema.GroupVersionKind, error)
    ResourceFor(input schema.GroupVersionResource)
           (schema.GroupVersionResource, error)
    ResourcesFor(input schema.GroupVersionResource)
           ([]schema.GroupVersionResource, error)
    ResourceSingularizer(resource string)
           (singular string, err error)
}

Kind to Resource

The RESTMapping and RESTMappings methods return an element or an array of RESTMapping structures as a result, given a Group and Kind. An optional list of versions indicates the preferred versions.

The RESTMappings method returns all matches, the RESTMapping method returns a single match or an error if there are multiple matches. The resulting RESTMapping elements will contain the fully qualified Kind (including the version) and the fully qualified Resource.

To sum up, these methods are used to map a Kind to a Resource.

Resource to Kind

The KindFor and KindsFor methods return an element or an array of GroupVersionKind, given a partial Group-Version-Resource. Partial means that you can omit the group, the version, or both. The resource name can be the singular or the plural name of the resource.

The KindsFor method returns all matches, the KindFor method returns a single match or an error if there are multiple matches.

To sum up, these methods are used to map a Resource to a Kind.

Finding Resources

The ResourceFor and ResourcesFor methods return an element or an array of GroupVersionResource, given a partial Group-Version-Resource. Partial means that you can omit the group, the version, or both. The resource name can be the singular or the plural name of the resource.

The ResourcesFor method returns all matches, the ResourceFor method returns a single match or an error if there are multiple matches.

To sum up, these methods are used to find fully qualified resources based on a singular or plural resource name.

The DefaultRESTMapper Implementation

The API Machinery provides a default implementation of a RESTMapper.
  • NewDefaultRESTMapper( 

    defaultGroupVersions []schema.GroupVersion,
) *DefaultRESTMapper
  • - this factory method is used to build a new DefaultRESTMapper, and accepts a list of default Group-Versions, which will be used to find Resources or Kinds when the provided GVR is partial.

  • Add(kind schema.GroupVersionKind, scope RESTScope) – this method is used to add a mapping between a Kind and a Resource. The resource name will be guessed from the Kind, by getting the lowercase word, and by pluralizing it (adding “es” to words ending with “s,” replacing terminal “y” with “ies” to words ending with “y,” and adding “s” to other words).

  • AddSpecific(
        kind schema.GroupVersionKind,
        plural, singular schema.GroupVersionResource,
        scope RESTScope)

    - this method is used to add a mapping between a Kind and a Resource, by giving the singular and plural names explicitly.

After creating a DefaultRESTMapper instance, you can use it as a RESTMapper by calling the methods defined in the interface of the same name.

Conclusion

This chapter has explored the API Machinery, introducing the Scheme abstraction used to serialize resources between Go and JSON or YAML, and to convert resources between several versions. The chapter also covered the RESTMapper interface to help map between resources and kinds.

The next chapter covers the Client-go Library, a high-level one used by developers to call the Kubernetes API without needing to work with HTTP calls.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.188.11