Skip to content

Instantly share code, notes, and snippets.

@x893675
Last active April 23, 2023 09:37
Show Gist options
  • Save x893675/ff20ddfcb03bef01faa8a8993c711325 to your computer and use it in GitHub Desktop.
Save x893675/ff20ddfcb03bef01faa8a8993c711325 to your computer and use it in GitHub Desktop.
use client-go emulate kubectl apply
package main
import (
"bufio"
"bytes"
"context"
"encoding/json"
"github.com/pkg/errors"
"io"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
"k8s.io/apimachinery/pkg/types"
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/discovery"
"k8s.io/client-go/discovery/cached/memory"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/restmapper"
"k8s.io/client-go/tools/clientcmd"
"net/http"
)
const deploymentYAML = `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
namespace: default
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
`
var kubeconfig = `apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMvakNDQWVhZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJek1ETXhOVEEzTURVd09Gb1hEVE16TURNeE1qQTNNRFV3T0Zvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTHF3Ck84R2M2cU5RUlJiMHJIYUJFalNld1BSRzlQWG5udTd0dk9oTlpWd1U5TmltdjZac0xrb2R1OU1UUXNWUGkxOFYKSnRTazlvbktVRnU2ZG9IaGVJOEJnS0pDVXNpbmpnUkxKZGhvSTFYdTk1Qy9qMVF0YmdodGQyem54MnJNL0tWbwpUU1F4NmQ1aVdEYW9NNitrKzlHWjVrekJEZzJ6c0YxeDJRMnhpeTJjSSt3WXRaNDFteTY0clZON21WbGJlWFY2Clh3M3ZQZWMzUG5yKytJWnZPc1dYekU5a3Q1Y01jM3NHbTJIRFF1L3FiRW9Tc1ozRGpyWGw4c3BzMlBXWklnaGkKUElMeEs5WFMrZ3BIRlR6TGsvcTRLSGJ1S2xFZTVBMXk4dFRKZ2tkWnBHN3RVeVMyVm5FY0E1YjJPcDZQdC90Vwp5eC9QMlovQXBkcG43SWk1cXVVQ0F3RUFBYU5aTUZjd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0hRWURWUjBPQkJZRUZOemg0OTVqcEU5VEFzSWVsZ1FPRnAyaExWZlhNQlVHQTFVZEVRUU8KTUF5Q0NtdDFZbVZ5Ym1WMFpYTXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBSU1XMzYxUjYrbk5jRHJWWnVLTwo2ckFXbE9RNUE4UHRUQi9vR2dKTGI5QjNvTXFseE0waUJPOUtGcFdmZHloc21BdmxEUHg5VWJKbXo1ZTAwL3oxCncrZTRrVGQvSFhLYmxkdXFMNkNtNkJhK3N5bHNLU0UwWVVFdURNTjdKUHFNeUh1WFZ4SUZjSUNlRCsyZFZ0dEEKRXhXRDQrZzh0S3ZWVkxrWHhQTWplMDkxVmlSamRVTzdRMzlDeUhtcnZZTkZYK2VWYkZuVGRrSW5VOHJzL1pCagplTmkrNzNwU0FOT2thdEJBYXhjblRsVnhuZGwzczlScnlBMmxLQS9Cak5RNkF0TWRuaUV5NGlvdUlGUlpocFRHCnZLcVU4K2Rpb3JCWEcva3N1bVJud3ppb3FpZ2tOQVM0OFpseWVHa2lzaW5ScHBHOTFZSzNnNTZXUWlRR2cyb2UKei9nPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
server: https://192.168.234.43:6443
name: test
contexts:
- context:
cluster: test
user: kubernetes-admin
name: kubernetes-admin@test
current-context: kubernetes-admin@test
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJVENDQWdtZ0F3SUJBZ0lJSlVPUkVSZXRNOTh3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TXpBek1UVXdOekExTURoYUZ3MHlOREF6TVRRd056QTFNVEJhTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXBBRkFZUkNxMitzSnNRc3IKYXB5ajFrYW1KdEZwYXpHZUVQVWgvUDdiaktaL0F1KzZHUVl5Y256MVdBUENrd3RGemorSlFkbnhPc08xWnFhYwpSWjRDUFVSci9QeEJBS2ZxNGIxZ2hSWDFxK3FvdTJ5V2NkUTFtR3B3RWY5SmdOdXZPakJLZUNlN2FzVVNKUSs3Cm81TXZRcXo0NjFadlE1QXk1aG4rMDhML2xERG1XazdUZDIrRm9NZzRrNVZRTWJlbWxmSE9tMGpsV0l6V2xMa3AKTW5wV1JZWTJCNzVOOXNNY1cwYkxIVC9WNldCSmpEaGxQL3k1allldzk4Y3FlVTF2SFNPL1RTekxWS015N1d5Nwo2ZnNkeGFLWDlmUytleEdFbzF5bmh1L2krSGQzRkpaV2loZlJ0d2NDc2VHUzhKYk5PMFJDbkxPUnltWUpGRElKCjFaaWxsUUlEQVFBQm8xWXdWREFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RBWURWUjBUQVFIL0JBSXdBREFmQmdOVkhTTUVHREFXZ0JUYzRlUGVZNlJQVXdMQ0hwWUVEaGFkb1MxWAoxekFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBazFUVSt1aEVzOXBab2pTdXBZMDRMcHpFc0F4Qno5OHo0L3F0CnRHMkZiQnM5ZU9zZC9LMGVHYTlDc3NxSll6UHVmVDlLdDJvSzdHN3k0RXdwazJhWGd5NzR0dXpVajlISTB6eHAKR2xkaWh0RlFMakhKRjk1Ni9UK1RSZnBCUUQxU253OU5mc0NtYTBVREtHM1FzK2dIMGprRXFVSFBvYk9VWjJLbgovN3E3Z21lVHN5a2p3cnZjT3VMcU85RDJlbXlEWmthVjM5UFlIbmtaelRUN2tUZWdaQ3VWRmNsd2lqY281NXQzCkRSTWJudHBFc0wrTE9odFQ2T250RXB3aGVqWDROdklubllzcGVabFBhQlRsZTN5YjVBUXlobnVoSzBIb1lHKzYKajlXWXE5YlprdC9CY2owTmxxSVBlY21sY0tybm55UDVsL3V2a01qNW4vb24zNHZyVkE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBcEFGQVlSQ3EyK3NKc1FzcmFweWoxa2FtSnRGcGF6R2VFUFVoL1A3YmpLWi9BdSs2CkdRWXljbnoxV0FQQ2t3dEZ6aitKUWRueE9zTzFacWFjUlo0Q1BVUnIvUHhCQUtmcTRiMWdoUlgxcStxb3UyeVcKY2RRMW1HcHdFZjlKZ051dk9qQktlQ2U3YXNVU0pRKzdvNU12UXF6NDYxWnZRNUF5NWhuKzA4TC9sRERtV2s3VApkMitGb01nNGs1VlFNYmVtbGZIT20wamxXSXpXbExrcE1ucFdSWVkyQjc1TjlzTWNXMGJMSFQvVjZXQkpqRGhsClAveTVqWWV3OThjcWVVMXZIU08vVFN6TFZLTXk3V3k3NmZzZHhhS1g5ZlMrZXhHRW8xeW5odS9pK0hkM0ZKWlcKaWhmUnR3Y0NzZUdTOEpiTk8wUkNuTE9SeW1ZSkZESUoxWmlsbFFJREFRQUJBb0lCQUNjWC9VYWwrSFJ2dEdHMQpiZVg5N0xmNUkwWUJpUVY2VUZsdWRXbWZQcXBxVGNCZmNKYmllVWY5TFdhTGp1Z0tPZHVPSGFQSzFjdlNOdjEvCkhhVWlveG9EbzFJc3R6bjJ6UEIrZWZGL2FJNVFZMlE1NENyR0VQaUI5ckR6ajR2ZDBna20xYm1Land4R2FhclgKOHp4c3EwbFRmZ3ByRWg2aTB0OHl0eUczUzhFQS9VU3ZPR3JVdi9mRjdBRWptS0liRmNnbThuNWM0Z3hpUStTMgpDT1RmN3VpNlE4RVBUOGlJajlaSHM4ZHQ2UWNrS2xJbjVKdXA0WGtEUVRrSnFwc1JTblh5TW9uZkpId1lKVStJCncxYTBsSER5QXZlNTRuL1dNcmlLRU45b2YzYnVTWVFhVW9ERFp6Y21IS0g3akJGOU5ERG56Tloxc2VSdE8xNlAKVHhtZGU4a0NnWUVBeFBtL1A3THZPNVBFWHQ1elIwbHdJRTVDRnNYeXBIN3FvNnZLTUhYa0RCWi9ta0tsc1RxUQpNRDJLamN0cE4vYzY1d08zZlA3NVBvYWRYYWFUVFFUV2VFa2tISUNDZWk2aEZCY09qNHJBZk53QnB3OEhjNjI5CkMyYWVyeHVBbDk3QmRweWhtTlg0ZERQbHJJRmV6aEI0YUdCakE2QVBsUzFSN0dqcm9YSGNUbk1DZ1lFQTFTWkgKdDhraXpjdUFRK2xodUE3Yi9uRnR6R1RtQndQNUM5T3lhVVRJa2ErMkV2c29BSm5ZaGxhU3o5WENqVGE1RzRHTgpTSTFXUC9QK0hnVjZ1cHFWT1BxcFZWSGw1UkpFa0U2bDk2MzhxcGE4TFludXFqN1E0Q3RUZHF6RG85elNCRHN4CjlCZFU4NXdFVmNNS3J1WlRNdGJ5ZE8zWnczeHF2TitKOWc3R2NkY0NnWUVBcU9taVgzV0tRWVROK0t5NkhuK1QKYjZDc2UrcnowS3dsZW9WdXRXL2pSNXBqZ2wxMHlTNTNSMmIrMGVtRktVRVJlZGx1Ri9wdXNuZXRxd21WZDQ2VwpmRC84ZjVTVVQwSjlUMFVXYjNHRTc0MmF2Uy9lTEF6bytFWGYvam1QWkt6WkttWFhEY0V2ZGphcTFldDY2U3FtCmxwdFJUV0tNK2xnSzBMaUlsOEdVTXBFQ2dZQlVPRDdvTUhJdkRIU080TjA2bzZ4ck9oaGZSWkxhUE1pZjByTFIKQTF1WXJPdnRUZzI5Und5VjhBa3NVOUwraHh2VzhYYUlyMGZnRnlGV2JXWFQ2NWFBb0JKZVl2REtkVkJnRUtObApYUHJUMEtGa0FQY3JqNHhxdHRQUXRXek1CMGw0WUlkWllncXdBdnAzaEl4cEZSclVvTGZHV2VETWJCR2t0VkxHCnhZbkZtd0tCZ0UyWG9SM1Z6eVhpRXJ4TkxtN0dTc1ViTDJVUXJ0V0hsTmlIakliZXJlQ2pnd1R4SHg4MmJ4eUUKM01EcmNvdmNjaTZNOEI5bFlIcm0vL3lTTi90aGcrSTFhUzByc1B4SE8vYUYzQ3AyM0tOcFVabGxoU0dtM2hVRgozVzE1K0Y1eTVyTGJXLzdJWnRWam1IWWljeVhTQ3JnSkIvYTA0M29VRWM2K2N0VU9WVG11Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==`
var decUnstructured = yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
func main() {
restConfig, err := clientcmd.RESTConfigFromKubeConfig([]byte(kubeconfig))
if err != nil {
panic(err)
}
dc, err := discovery.NewDiscoveryClientForConfig(restConfig)
if err != nil {
panic(err)
}
dyn, err := dynamic.NewForConfig(restConfig)
if err != nil {
panic(err)
}
applyfn := func(ctx context.Context, dc *discovery.DiscoveryClient, dyn dynamic.Interface, obj *Object) error {
mapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(dc))
mapping, err := mapper.RESTMapping(obj.GVK.GroupKind(), obj.GVK.Version)
if err != nil {
return err
}
var dr dynamic.ResourceInterface
if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
dr = dyn.Resource(mapping.Resource).Namespace(obj.Data.GetNamespace())
} else {
dr = dyn.Resource(mapping.Resource)
}
data, err := json.Marshal(obj.Data)
if err != nil {
return err
}
_, err = dr.Patch(ctx, obj.Data.GetName(), types.ApplyPatchType, data, metav1.PatchOptions{
FieldManager: "kubeclipper",
})
return err
}
deletefn := func(ctx context.Context, dc *discovery.DiscoveryClient, dyn dynamic.Interface, obj *Object) error {
mapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(dc))
mapping, err := mapper.RESTMapping(obj.GVK.GroupKind(), obj.GVK.Version)
if err != nil {
return err
}
var dr dynamic.ResourceInterface
if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
dr = dyn.Resource(mapping.Resource).Namespace(obj.Data.GetNamespace())
} else {
dr = dyn.Resource(mapping.Resource)
}
err = dr.Delete(ctx, obj.Data.GetName(), metav1.DeleteOptions{})
return err
}
http.HandleFunc("/apply", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "only POST is allowed", http.StatusMethodNotAllowed)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := ForEachObjectInYAML(context.Background(), dc, dyn, body, applyfn); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})
http.HandleFunc("/delete", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "only POST is allowed", http.StatusMethodNotAllowed)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := ForEachObjectInYAML(context.Background(), dc, dyn, body, deletefn); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})
http.ListenAndServe(":8080", nil)
}
type Object struct {
Data *unstructured.Unstructured
GVK *schema.GroupVersionKind
}
func DecodeYAML(data []byte) (<-chan *Object, <-chan error) {
var (
chanErr = make(chan error)
chanObj = make(chan *Object)
multidocReader = utilyaml.NewYAMLReader(bufio.NewReader(bytes.NewReader(data)))
)
go func() {
defer close(chanErr)
defer close(chanObj)
for {
buf, err := multidocReader.Read()
if err != nil {
if err == io.EOF {
return
}
chanErr <- errors.Wrap(err, "failed to read yaml data")
return
}
obj := &unstructured.Unstructured{}
_, gvk, err := decUnstructured.Decode(buf, nil, obj)
if err != nil {
chanErr <- errors.Wrap(err, "failed to unmarshal yaml data")
return
}
chanObj <- &Object{
Data: obj,
GVK: gvk,
}
}
}()
return chanObj, chanErr
}
type ForEachObjectInYAMLActionFunc func(context.Context, *discovery.DiscoveryClient, dynamic.Interface, *Object) error
func ForEachObjectInYAML(
ctx context.Context,
dc *discovery.DiscoveryClient,
dyn dynamic.Interface,
data []byte,
actionFn ForEachObjectInYAMLActionFunc) error {
chanObj, chanErr := DecodeYAML(data)
for {
select {
case obj := <-chanObj:
if obj == nil {
return nil
}
if err := actionFn(ctx, dc, dyn, obj); err != nil {
return err
}
case err := <-chanErr:
if err == nil {
return nil
}
return errors.Wrap(err, "received error while decoding yaml")
}
}
}
### post yaml to kubernetes
POST http://localhost:8080/apply
Content-Type: text/plain
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
namespace: default
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
### delete yaml from kubernetes
POST http://localhost:8080/delete
Content-Type: text/plain
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
namespace: default
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment