Closed AmitKumarDas closed 2 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()
}
// 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)
})
// 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"
// 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)
}
// 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)
}
// 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)
}
}
// 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
}