AmitKumarDas / fun-with-programming

ABC - Always Be Coding
2 stars 2 forks source link

k8s code 0001 #88

Closed AmitKumarDas closed 2 years ago

AmitKumarDas commented 3 years ago
// step 1 - tools.go file must be present
// step 2 - `go mod tidy` should install controller-gen at $(GOPATH)/bin/controller-gen
// step 3 - `go mod vendor` to create the vendor folder
//go:build tools
// +build tools

// This package contains code generation utilities
// This package imports things required by build scripts, to force `go mod` to
// see them as dependencies
package main

import (
    _ "sigs.k8s.io/controller-tools/cmd/controller-gen"
)
$ controller-gen --help
Generate Kubernetes API extension resources and code.

Usage:
  controller-gen [flags]

Examples:
    # Generate RBAC manifests and crds for all types under apis/,
    # outputting crds to /tmp/crds and everything else to stdout
    controller-gen rbac:roleName=<role name> crd paths=./apis/... output:crd:dir=/tmp/crds output:stdout

    # Generate deepcopy/runtime.Object implementations for a particular file
    controller-gen object paths=./apis/v1beta1/some_types.go

    # Generate OpenAPI v3 schemas for API packages and merge them into existing CRD manifests
    controller-gen schemapatch:manifests=./manifests output:dir=./manifests paths=./pkg/apis/...

    # Run all the generators for a given project
    controller-gen paths=./apis/...

    # Explain the markers for generating CRDs, and their arguments
    controller-gen crd -ww
# generate zz_generated_deepcopy.go via controller-gen
# if types.go contain +kubebuilder annotations
# in other words deepcopy-gen does not work for above annotations

controller-gen object \
  paths=./vendor/github.com/AmitKumarDas/my-operator/api/v1/... \
  output:dir=./vendor/github.com/AmitKumarDas/my-operator/api/v1/
AmitKumarDas commented 3 years ago
// testing // dsl // fluent api // method chaining

func TestClientPact_Broker(t *testing.T) {
    pact := dsl.Pact{
        Consumer: "example-client",
        Provider: "example-server",
    }

    t.Run("get user by id", func(t *testing.T) {
        id := "1"

        pact.
            AddInteraction().
            Given("User Alice exists").
            UponReceiving("User 'Alice' is requested").
            WithRequest(dsl.Request{
                Method: "GET",
                Path:   dsl.Term("/users/1", "/users/[0-9]+"),
            }).
    //
        // PACT verification
        //
    })

    if err := pact.WritePact(); err != nil {
        t.Fatal(err)
    }

    // specify PACT publisher
    publisher := dsl.Publisher{}
    err := publisher.Publish(types.PublishRequest{
        PactURLs:        []string{"./pacts/"}, // a folder with all PACT test
        PactBroker:      "<PACT BROKER>", // PACT broker
        BrokerToken:     "<API TOKEN>", // API token for PACT broker
        ConsumerVersion: "1.0.0",
        Tags:            []string{"1.0.0", "latest"},
    })
    if err != nil {
        t.Fatal(err)
    }

    pact.Teardown()
}
AmitKumarDas commented 3 years ago
// testing // sampling // before each
// k8s // generate name // rand // busybox
//
// https://github.com/kubernetes-sigs/controller-runtime/blob/master/pkg/controller/controllerutil/controllerutil_test.go

import (
  appsv1 "k8s.io/api/apps/v1"
  "k8s.io/apimachinery/pkg/types"
)

var deploy *appsv1.Deployment
var deplSpec appsv1.DeploymentSpec
var deplKey types.NamespacedName
var specr controllerutil.MutateFn

BeforeEach(func() {
    deploy = &appsv1.Deployment{
        ObjectMeta: metav1.ObjectMeta{
            Name:      fmt.Sprintf("deploy-%d", rand.Int31()), //nolint:gosec
            Namespace: "default",
        },
    }

    deplSpec = appsv1.DeploymentSpec{
        Selector: &metav1.LabelSelector{
            MatchLabels: map[string]string{"foo": "bar"},
        },
        Template: corev1.PodTemplateSpec{
            ObjectMeta: metav1.ObjectMeta{
                Labels: map[string]string{
                    "foo": "bar",
                },
            },
            Spec: corev1.PodSpec{
                Containers: []corev1.Container{
                    {
                        Name:  "busybox",
                        Image: "busybox",
                    },
                },
            },
        },
    }

    deplKey = types.NamespacedName{
        Name:      deploy.Name,
        Namespace: deploy.Namespace,
    }

    specr = deploymentSpecr(deploy, deplSpec)
})
AmitKumarDas commented 3 years ago
// import // k8s // util // common
// types // pointer // scheme // runtime

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
AmitKumarDas commented 3 years ago
// banzaicloud // object matcher
// strategic merge patch // merge patch // two way merge // three way merge 

package patch

import (
    "fmt"

    "emperror.dev/errors"
    json "github.com/json-iterator/go"
    "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/util/jsonmergepatch"
    "k8s.io/apimachinery/pkg/util/strategicpatch"
)

var DefaultPatchMaker = NewPatchMaker(DefaultAnnotator)

type PatchMaker struct {
    annotator *Annotator
}

func NewPatchMaker(annotator *Annotator) *PatchMaker {
    return &PatchMaker{
        annotator: annotator,
    }
}

func (p *PatchMaker) Calculate(currentObject, modifiedObject runtime.Object, opts ...CalculateOption) (*PatchResult, error) {
    current, err := json.ConfigCompatibleWithStandardLibrary.Marshal(currentObject)
    if err != nil {
        return nil, errors.Wrap(err, "Failed to convert current object to byte sequence")
    }

    modified, err := json.ConfigCompatibleWithStandardLibrary.Marshal(modifiedObject)
    if err != nil {
        return nil, errors.Wrap(err, "Failed to convert current object to byte sequence")
    }

    for _, opt := range opts {
        current, modified, err = opt(current, modified)
        if err != nil {
            return nil, errors.Wrap(err, "Failed to apply option function")
        }
    }

    current, _, err = DeleteNullInJson(current)
    if err != nil {
        return nil, errors.Wrap(err, "Failed to delete null from current object")
    }

    modified, _, err = DeleteNullInJson(modified)
    if err != nil {
        return nil, errors.Wrap(err, "Failed to delete null from modified object")
    }

    original, err := p.annotator.GetOriginalConfiguration(currentObject)
    if err != nil {
        return nil, errors.Wrap(err, "Failed to get original configuration")
    }

    var patch []byte

    switch currentObject.(type) {
    default:
        lookupPatchMeta, err := strategicpatch.NewPatchMetaFromStruct(currentObject)
        if err != nil {
            return nil, errors.WrapWithDetails(err, "Failed to lookup patch meta", "current object", currentObject)
        }
        patch, err = strategicpatch.CreateThreeWayMergePatch(original, modified, current, lookupPatchMeta, true)
        if err != nil {
            return nil, errors.Wrap(err, "Failed to generate strategic merge patch")
        }
        // $setElementOrder can make it hard to decide whether there is an actual diff or not.
        // In cases like that trying to apply the patch locally on current will make it clear.
        if string(patch) != "{}" {
            patchCurrent, err := strategicpatch.StrategicMergePatch(current, patch, currentObject)
            if err != nil {
                return nil, errors.Wrap(err, "Failed to apply patch again to check for an actual diff")
            }
            patch, err = strategicpatch.CreateTwoWayMergePatch(current, patchCurrent, currentObject)
            if err != nil {
                return nil, errors.Wrap(err, "Failed to create patch again to check for an actual diff")
            }
        }
    case *unstructured.Unstructured:
        patch, err = jsonmergepatch.CreateThreeWayJSONMergePatch(original, modified, current)
        if err != nil {
            return nil, errors.Wrap(err, "Failed to generate merge patch")
        }
    }

    return &PatchResult{
        Patch:    patch,
        Current:  current,
        Modified: modified,
        Original: original,
    }, nil
}

type PatchResult struct {
    Patch    []byte
    Current  []byte
    Modified []byte
    Original []byte
}

func (p *PatchResult) IsEmpty() bool {
    return string(p.Patch) == "{}"
}

func (p *PatchResult) String() string {
    return fmt.Sprintf("\nPatch: %s \nCurrent: %s\nModified: %s\nOriginal: %s\n", p.Patch, p.Current, p.Modified, p.Original)
}
AmitKumarDas commented 3 years ago
// MergeFrom creates a Patch that patches using the merge-patch strategy with the given object as base.
// The difference between MergeFrom and StrategicMergeFrom lays in the handling of modified list fields.
// When using MergeFrom, existing lists will be completely replaced by new lists.
// When using StrategicMergeFrom, the list field's `patchStrategy` is respected if specified in the API type,
// e.g. the existing list is not replaced completely but rather merged with the new one using the list's `patchMergeKey`.
// See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ for more details on
// the difference between merge-patch and strategic-merge-patch.
// StrategicMergeFrom creates a Patch that patches using the strategic-merge-patch strategy with the given object as base.
// The difference between MergeFrom and StrategicMergeFrom lays in the handling of modified list fields.
// When using MergeFrom, existing lists will be completely replaced by new lists.
// When using StrategicMergeFrom, the list field's `patchStrategy` is respected if specified in the API type,
// e.g. the existing list is not replaced completely but rather merged with the new one using the list's `patchMergeKey`.
// See https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/ for more details on
// the difference between merge-patch and strategic-merge-patch.
// Please note, that CRDs don't support strategic-merge-patch, see
// https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#advanced-features-and-flexibility
func createMergePatch(originalJSON, modifiedJSON []byte, _ interface{}) ([]byte, error) {
    return jsonpatch.CreateMergePatch(originalJSON, modifiedJSON)
}

func createStrategicMergePatch(originalJSON, modifiedJSON []byte, dataStruct interface{}) ([]byte, error) {
    return strategicpatch.CreateTwoWayMergePatch(originalJSON, modifiedJSON, dataStruct)
}
AmitKumarDas commented 3 years ago
// immutable // create or update // label selector
// https://github.com/kubernetes-sigs/controller-runtime/blob/master/pkg/controller/controllerutil/example_test.go 

import (
    "context"

    appsv1 "k8s.io/api/apps/v1"
    corev1 "k8s.io/api/core/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

    "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    logf "sigs.k8s.io/controller-runtime/pkg/log"
)

var (
    log = logf.Log.WithName("controllerutil-examples")
)

// This example creates or updates an existing deployment.
func ExampleCreateOrUpdate() {
    // c is client.Client

    // Create or Update the deployment default/foo
    deploy := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"}}

    op, err := controllerutil.CreateOrUpdate(context.TODO(), c, deploy, func() error {
        // Deployment selector is immutable so we set this value only if
        // a new object is going to be created
        if deploy.ObjectMeta.CreationTimestamp.IsZero() {
            deploy.Spec.Selector = &metav1.LabelSelector{
                MatchLabels: map[string]string{"foo": "bar"},
            }
        }

        // update the Deployment pod template
        deploy.Spec.Template = corev1.PodTemplateSpec{
            ObjectMeta: metav1.ObjectMeta{
                Labels: map[string]string{
                    "foo": "bar",
                },
            },
            Spec: corev1.PodSpec{
                Containers: []corev1.Container{
                    {
                        Name:  "busybox",
                        Image: "busybox",
                    },
                },
            },
        }

        return nil
    })

    if err != nil {
        log.Error(err, "Deployment reconcile failed")
    } else {
        log.Info("Deployment successfully reconciled", "operation", op)
    }
}
AmitKumarDas commented 3 years ago
// create or patch // status // diff // merge // patch // unstructured
//
// https://github.com/kubernetes-sigs/controller-runtime/blob/master/pkg/controller/controllerutil/controllerutil.go

// CreateOrPatch creates or patches the given object in the Kubernetes
// cluster. The object's desired state must be reconciled with the before
// state inside the passed in callback MutateFn.
//
// The MutateFn is called regardless of creating or updating an object.
//
// It returns the executed operation and an error.
func CreateOrPatch(ctx context.Context, c client.Client, obj client.Object, f MutateFn) (OperationResult, error) {
    key := client.ObjectKeyFromObject(obj)
    if err := c.Get(ctx, key, obj); err != nil {
        if !apierrors.IsNotFound(err) {
            return OperationResultNone, err
        }
        if f != nil {
            if err := mutate(f, key, obj); err != nil {
                return OperationResultNone, err
            }
        }
        if err := c.Create(ctx, obj); err != nil {
            return OperationResultNone, err
        }
        return OperationResultCreated, nil
    }

    // Create patches for the object and its possible status.
    objPatch := client.MergeFrom(obj.DeepCopyObject().(client.Object))
    statusPatch := client.MergeFrom(obj.DeepCopyObject().(client.Object))

    // Create a copy of the original object as well as converting that copy to
    // unstructured data.
    before, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj.DeepCopyObject())
    if err != nil {
        return OperationResultNone, err
    }

    // Attempt to extract the status from the resource for easier comparison later
    beforeStatus, hasBeforeStatus, err := unstructured.NestedFieldCopy(before, "status")
    if err != nil {
        return OperationResultNone, err
    }

    // If the resource contains a status then remove it from the unstructured
    // copy to avoid unnecessary patching later.
    if hasBeforeStatus {
        unstructured.RemoveNestedField(before, "status")
    }

    // Mutate the original object.
    if f != nil {
        if err := mutate(f, key, obj); err != nil {
            return OperationResultNone, err
        }
    }

    // Convert the resource to unstructured to compare against our before copy.
    after, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
    if err != nil {
        return OperationResultNone, err
    }

    // Attempt to extract the status from the resource for easier comparison later
    afterStatus, hasAfterStatus, err := unstructured.NestedFieldCopy(after, "status")
    if err != nil {
        return OperationResultNone, err
    }

    // If the resource contains a status then remove it from the unstructured
    // copy to avoid unnecessary patching later.
    if hasAfterStatus {
        unstructured.RemoveNestedField(after, "status")
    }

    result := OperationResultNone

    if !reflect.DeepEqual(before, after) {
        // Only issue a Patch if the before and after resources (minus status) differ
        if err := c.Patch(ctx, obj, objPatch); err != nil {
            return result, err
        }
        result = OperationResultUpdated
    }

    if (hasBeforeStatus || hasAfterStatus) && !reflect.DeepEqual(beforeStatus, afterStatus) {
        // Only issue a Status Patch if the resource has a status and the beforeStatus
        // and afterStatus copies differ
        if result == OperationResultUpdated {
            // If Status was replaced by Patch before, set it to afterStatus
            objectAfterPatch, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
            if err != nil {
                return result, err
            }
            if err = unstructured.SetNestedField(objectAfterPatch, afterStatus, "status"); err != nil {
                return result, err
            }
            // If Status was replaced by Patch before, restore patched structure to the obj
            if err = runtime.DefaultUnstructuredConverter.FromUnstructured(objectAfterPatch, obj); err != nil {
                return result, err
            }
        }
        if err := c.Status().Patch(ctx, obj, statusPatch); err != nil {
            return result, err
        }
        if result == OperationResultUpdated {
            result = OperationResultUpdatedStatus
        } else {
            result = OperationResultUpdatedStatusOnly
        }
    }

    return result, nil
}