Skip to content

Instantly share code, notes, and snippets.

@RafalSkolasinski
Created October 17, 2024 23:25
Show Gist options
  • Save RafalSkolasinski/3a3c9e41fc4991f7f988741712d59e7c to your computer and use it in GitHub Desktop.
Save RafalSkolasinski/3a3c9e41fc4991f7f988741712d59e7c to your computer and use it in GitHub Desktop.
Go: CreateOrUpdate from multi-resource YAML
package main
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"log"
"os"
"github.com/cisco-open/k8s-objectmatcher/patch"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
yaml "k8s.io/apimachinery/pkg/util/yaml"
ctrl "sigs.k8s.io/controller-runtime"
client "sigs.k8s.io/controller-runtime/pkg/client"
"k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
)
var (
scheme = runtime.NewScheme()
annotator = patch.NewAnnotator("skolasinski.me/last-applied")
patchMaker = patch.NewPatchMaker(
annotator,
&patch.K8sStrategicMergePatcher{},
&patch.BaseJSONMergePatcher{},
)
)
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
}
var namespace = "default"
func main() {
// Prepare Kubernetes Client
kubeconfig := ctrl.GetConfigOrDie()
dynamic, err := client.New(kubeconfig, client.Options{Scheme: scheme})
if err != nil {
log.Fatal(err)
}
// Open File
file, err := os.Open("manifests/bundle.yaml")
if err != nil {
panic(err)
}
reader := bufio.NewReader(file)
objects, err := splitYAML(reader, scheme)
if err != nil {
panic(err)
}
for _, obj := range objects {
obj.SetNamespace(namespace)
result, err := CreateOrUpdate(context.Background(), dynamic, obj)
if err != nil {
panic(err)
}
fmt.Printf("Installed (%s) %s: %s.%s\n",
result,
obj.GetObjectKind().GroupVersionKind().Kind,
obj.GetName(),
obj.GetNamespace(),
)
}
}
func splitYAML(reader io.Reader, scheme *runtime.Scheme) ([]client.Object, error) {
decode := serializer.NewCodecFactory(scheme).UniversalDeserializer().Decode
yamlDecoder := yaml.NewYAMLOrJSONDecoder(reader, 4096)
var objects []client.Object
for {
// Read object
unk := &runtime.Unknown{}
if err := yamlDecoder.Decode(unk); err != nil {
if errors.Is(err, io.EOF) {
break
}
return nil, err
}
// Convert Object
gvk := unk.GetObjectKind().GroupVersionKind()
object, _, err := decode(unk.Raw, &gvk, nil)
if err != nil {
return nil, err
}
// Append read object
objects = append(objects, object.(client.Object))
}
return objects, nil
}
type OperationResult string
const (
OperationResultNone OperationResult = "unchanged"
OperationResultCreated OperationResult = "created"
OperationResultUpdated OperationResult = "updated"
)
func CreateOrUpdate(ctx context.Context, c client.Client, obj client.Object) (OperationResult, error) {
existing := obj.DeepCopyObject().(client.Object)
key := client.ObjectKeyFromObject(obj)
if err := c.Get(ctx, key, existing); err != nil {
if !apierrors.IsNotFound(err) {
return OperationResultNone, err
}
if err := annotator.SetLastAppliedAnnotation(obj); err != nil {
return OperationResultNone, err
}
if err := c.Create(context.Background(), obj); err != nil {
return OperationResultNone, err
}
return OperationResultCreated, nil
}
patchOpts := []patch.CalculateOption{
patch.IgnoreField("apiVersion"),
patch.IgnoreField("kind"),
}
patchResult, err := patchMaker.Calculate(existing, obj, patchOpts...)
if err != nil {
return OperationResultNone, err
}
if !patchResult.IsEmpty() {
if err := annotator.SetLastAppliedAnnotation(obj); err != nil {
return OperationResultNone, err
}
obj.SetResourceVersion(existing.GetResourceVersion())
if err := c.Update(context.Background(), obj); err != nil {
return OperationResultNone, err
}
return OperationResultUpdated, nil
}
return OperationResultNone, nil
}
@RafalSkolasinski
Copy link
Author

Cute little snippet that load multi-resource YAML file and "apply" it to Kubernetes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment