package internal import ( "bytes" "context" "encoding/json" "fmt" "html/template" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" ) var CILIUM_GROUP_VERSION = schema.GroupVersion{ Group: "cilium.io", Version: "v2alpha1", } var IP_POOL_TEMPLATE = ` { "apiVersion": "cilium.io/v2alpha1", "kind": "CiliumLoadBalancerIPPool", "metadata": { "name": "{{ .Name }}", "annotations": { "argocd.argoproj.io/tracking-id": "cilium-lb:cilium.io/CiliumLoadBalancerIPPool:kube-system/covidnetes-pool", "managed-by": "canada-kaktus" } }, "spec": { "blocks": [ {{- range $i, $ip := .IPs }} {{- if $i}},{{ end }} { "cidr": "{{ $ip }}/32" } {{- end }} ], "disabled": false } } ` type CrdConfig struct { Name string IPs []string } func RecreateIPPoolCrd(cfg *Config, name string, ips []string) error { if len(ips) == 0 { return fmt.Errorf("no IPs provided to create IP Pool CRD") } routeclient, err := createRestClient() if err != nil { return fmt.Errorf("error creating REST Client: %v", err.Error()) } resourceVersion, err := getResourceVersion(routeclient, name) if err != nil { return fmt.Errorf("error getting resourceVersion: %v", err.Error()) } body, err := generateIpPool(name, ips) if err != nil { return fmt.Errorf("error generating CRD: %v", err.Error()) } // Inject resourceVersion into the JSON var obj map[string]interface{} if err := json.Unmarshal([]byte(body), &obj); err != nil { return fmt.Errorf("could not unmarshal generated CRD: %v", err) } if meta, ok := obj["metadata"].(map[string]interface{}); ok { meta["resourceVersion"] = resourceVersion } finalBody, err := json.Marshal(obj) if err != nil { return fmt.Errorf("could not marshal final CRD: %v", err) } res := routeclient.Put(). Resource("ciliumloadbalancerippools"). Name(name). Body(finalBody). Do(context.TODO()) var status int res.StatusCode(&status) if status < 200 || status >= 400 { return fmt.Errorf("failed to post CRD to kube api: %v", res.Error()) } raw, rawErr := res.Raw() log.WithFields(log.Fields{ "Caller": "RecreateIPPoolCrd", }).Infof("Response from k8s api server: %s", string(raw)) if rawErr != nil { log.WithFields(log.Fields{ "Caller": "RecreateIPPoolCrd", }).Warnf("Could not get raw response from k8s api server: %v", rawErr) } return nil } func createRestClient() (*rest.RESTClient, error) { k8s_config, err := rest.InClusterConfig() if err != nil { return nil, fmt.Errorf("could not create in cluster k8s config: %v", err) } k8s_config.APIPath = "/apis" k8s_config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() k8s_config.GroupVersion = &CILIUM_GROUP_VERSION routeclient, err := rest.RESTClientFor(k8s_config) if err != nil { return nil, fmt.Errorf("could not create k8s client: %v", err) } return routeclient, nil } func generateIpPool(name string, ips []string) (string, error) { config := CrdConfig{ Name: name, IPs: ips, } tmpl, err := template.New("ippool").Parse(IP_POOL_TEMPLATE) if err != nil { return "", fmt.Errorf("errors in ippool template: %s", err.Error()) } var buf bytes.Buffer err = tmpl.Execute(&buf, &config) if err != nil { return "", fmt.Errorf("could not render ippool template: %s", err.Error()) } return buf.String(), nil } func getResourceVersion(client *rest.RESTClient, name string) (string, error) { res := client.Get(). Resource("ciliumloadbalancerippools"). Name(name). Do(context.TODO()) raw, err := res.Raw() if err != nil { return "", fmt.Errorf("could not fetch CRD: %v", err) } var obj map[string]interface{} if err := json.Unmarshal(raw, &obj); err != nil { return "", fmt.Errorf("could not unmarshal CRD: %v", err) } meta, ok := obj["metadata"].(map[string]interface{}) if !ok { return "", fmt.Errorf("metadata missing in CRD") } rv, ok := meta["resourceVersion"].(string) if !ok { return "", fmt.Errorf("resourceVersion missing in metadata") } return rv, nil }