Last active
February 4, 2025 15:55
-
-
Save erdii/439fe05b4c29f7087fa6543db0687be2 to your computer and use it in GitHub Desktop.
Example: using dynamic client to parse and apply generic object types
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"fmt" | |
"os" | |
"k8s.io/apimachinery/pkg/api/meta" | |
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | |
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | |
"k8s.io/client-go/dynamic" | |
"k8s.io/client-go/kubernetes" | |
"k8s.io/client-go/restmapper" | |
"sigs.k8s.io/controller-runtime/pkg/client/config" | |
"sigs.k8s.io/yaml" | |
) | |
const FieldManager = "parse-and-apply-example" | |
func newClients() (kubernetes.Interface, dynamic.Interface) { | |
restConfig := config.GetConfigOrDie() | |
client := kubernetes.NewForConfigOrDie(restConfig) | |
dynamicClient := dynamic.NewForConfigOrDie(restConfig) | |
return client, dynamicClient | |
} | |
func discoverAPIsAndConstructRESTMapper(client kubernetes.Interface) (meta.RESTMapper, error) { | |
groupResources, err := restmapper.GetAPIGroupResources(client.Discovery()) | |
if err != nil { | |
return nil, fmt.Errorf("discovering api group resources: %w", err) | |
} | |
return restmapper.NewDiscoveryRESTMapper(groupResources), nil | |
} | |
func applyObject( | |
dynamicClient dynamic.Interface, | |
mapper meta.RESTMapper, | |
obj *unstructured.Unstructured, | |
) (*unstructured.Unstructured, error) { | |
// Find (API) mapping for the parsed resource's GVK. | |
gvk := obj.GetObjectKind().GroupVersionKind() | |
mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version) | |
must(err) | |
// Bail if the given object kind is not namespace scoped. | |
if mapping.Scope.Name() != meta.RESTScopeNameNamespace { | |
panic("this code only supports applying namespaced resources") | |
} | |
// Construct dynamic resource interface (aka the actual client we'll use.) | |
dri := dynamicClient.Resource(mapping.Resource).Namespace(obj.GetNamespace()) | |
// Apply the object. | |
appliedObj, err := dri.Apply(context.TODO(), obj.GetName(), obj, v1.ApplyOptions{ | |
FieldManager: FieldManager, | |
Force: false, // set to true to force ownership on contested fields | |
}) | |
if err != nil { | |
return nil, fmt.Errorf("applying object: %w", err) | |
} | |
return appliedObj, nil | |
} | |
func parseFile(name string) (*unstructured.Unstructured, error) { | |
bytes, err := os.ReadFile(name) | |
if err != nil { | |
return nil, fmt.Errorf("reading file: %w", err) | |
} | |
return parseBytes(bytes) | |
} | |
func parseBytes(bytes []byte) (*unstructured.Unstructured, error) { | |
obj := &unstructured.Unstructured{} | |
if err := yaml.Unmarshal(bytes, obj); err != nil { | |
return nil, fmt.Errorf("unmarshalling yaml: %w", err) | |
} | |
return obj, nil | |
} | |
func must(err error) { | |
if err != nil { | |
panic(err) | |
} | |
} | |
func main() { | |
// Set up clients - can be done once globally. | |
client, dynamicClient := newClients() | |
// Look up available APIs and construct rest mapper. | |
mapper, err := discoverAPIsAndConstructRESTMapper(client) | |
must(err) | |
// Parse object manifest. | |
obj, err := parseFile(os.Args[1]) | |
must(err) | |
// Default "default" namespace. | |
if obj.GetNamespace() == "" { | |
obj.SetNamespace("default") | |
} | |
// Apply object. | |
appliedObj, err := applyObject(dynamicClient, mapper, obj) | |
must(err) | |
fmt.Println(appliedObj) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment