Following the k8s/controller-runtime/client example code (see here), which goes a bit like this
var c client.Client
func main() {
// Using a typed object.
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: "namespace",
Name: "name",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Image: "nginx",
Name: "nginx",
},
},
},
}
// c is a created client.
_ = c.Create(context.Background(), pod) // nil deref here
}
I get a nullptr dereference on _ = c.Create(context.Background(), pod). To me this makes sense, since I declared c, but never initialised it. However the example code also does that. What is going on here?
The correct way to initialise the client can be found here: https://pkg.go.dev/sigs.k8s.io/controller-runtime#v0.14.4/pkg/client#example-New
cl, err := client.New(config.GetConfigOrDie(), client.Options{})
if err != nil {
fmt.Println("failed to create client")
os.Exit(1)
}
Related
I am trying to create a secret using this function
func (ks *KubeSession) DeploySecret(secretName string, secretType string, secretMap map[string][]byte) error {
InfoLogger.Printf("deploying secret %s", secretName)
secret := corev1.Secret{
TypeMeta: metav1.TypeMeta{
Kind: "Secret",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
},
Data: secretMap,
Type: secretType,
}
_, err := ks.ClientSet.CoreV1().Secrets(JOB_NAMESPACE).Create(context.TODO(), &secret, metav1.CreateOptions{})
if err != nil {
return err
}
return nil
}
It is getting the following error:
cannot use secretType (variable of type string) as "k8s.io/api/core/v1".SecretType value in struct literal
Though, it works fine when I use a hardcoded string instead, like that:
...
Type: "generic"
...
How can I substitute this variable, so that I do not have to use hardcoded values instead?
Resolved.
Found the right way to declare the composite type:
Type: corev1.SecretType(secretType)
I have struct of configuration like this(in short version):
type Config struct {
Environment string
Service1 Service
Service2 Service
}
type Service struct {
CRC string
Cards Cards
}
type Cards struct {
GBP CardCfg
USD CardCfg
}
type CardCfg struct {
CRC string
}
func Cfg() *Config {
return &Config{
Environment: os.Getenv("ENVIRONMENT"),
Service1: Service{
CRC: os.Getenv("Service1_CRC"),
Cards: Cards{
GBP: CardCfg{
CRC: os.Getenv("Service1_CARD_GBP_CRC"),
},
USD: CardCfg{
CRC: os.Getenv("Service1_CARD_USD_CRC"),
},
},
},
Service2: Service{
CRC: os.Getenv("Service2_CRC"),
Cards: Cards{
GBP: CardCfg{
CRC: os.Getenv("Service2_CARD_GBP_CRC"),
},
USD: CardCfg{
CRC: os.Getenv("Service2_CARD_USD_CRC"),
},
},
},
}
}
I try to get access to service crc or service card crc by variable like this:
variable := "Service1"
currency := "EUR"
cfg := config.Cfg()
crc := cfg[variable].cards[currency] // DOESN'T WORK
I always tried with map, like this:
package main
import "fmt"
type Config map[string]interface{}
func main() {
config := Config{
"field": "value",
"service1": Config{
"crc": "secret1",
"cards": Config{
"crc": "secret2",
},
},
}
fmt.Println(config["WT"].(Config)["cards"].(Config)["crc"]) //WORK
}
but it looks wierd for me. Do you know better way to write config? It's possible to use struct? I come form Ruby planet, Golang is new for me.
edit:
I receive messages from rabbit queue, based on them I create a payment. Unfortunately, various payment methods require "own" authorization (crc and merchantId). Call looks like this:
trn, err := p24Client.RegisterTrn(context.Background(), &p24.RegisterTrnReq{
CRC: cfg[payinRequested.Service].cards[payinRequested.Currency].CRC,
MerchantId: cfg[payinRequested.Service].cards[payinRequested.Currency].MerchantId,
PosId: cfg[payinRequested.Service].cards[payinRequested.Currency].MerchantId,
SessionId: payinRequested.PaymentId,
Amount: payinRequested.Amount,
Currency: payinRequested.Currency,
Description: payinRequested.Desc,
Email: payinRequested.Email,
Method: payinRequested.BankId,
UrlReturn: payinRequested.ReturnUrl,
UrlStatus: cfg.StatusUri,
UrlCardPaymentNotification: cfg.CardStatusUri,
})
Any ideas on how to do it right?
Ignoring the reflect package, the simple answer is: you can't. You cannot access struct fields dynamically (using string variables). You can, use variables on a map, because accessing data in a map is a hashtable lookup. A struct isn't.
I will reiterate the main point of my comments though: What you're seemingly trying to do is using environment variables to set values on a config struct. This is very much a solved problem. We've been doing this for years at this point. I did a quick google search and found this repo which does exactly what you seem to want to do (and more): called configure
With this package, you can declare your config struct like this:
package config
type Config struct {
Environment string `env:"ENVIRONMENT" cli:"env" yaml:"environment"`
Services []*Service `env:"SERVICE" cli:"service" yaml:"service"`
serviceByName map[string]*Service
}
Then, to load from environment variables:
func LoadEnv() (*Config, err) {
c := Config{
serviceByName: map[string]*Service{},
} // set default values if needed
if err := configure.ParseEnv(&c); err != nil {
return nil, err
}
// initialise convenience fields like serviceByName:
for _, svc := range c.Services {
c.serviceByName[svc.Name] = svc
}
return &c, nil
}
// ServiceByName returns a COPY of the config for a given service
func (c Config) ServiceByName(n string) (Service, error) {
s, ok := c.serviceByName[n]
if !ok {
return nil, errrors.New("service with given name does not exist")
}
return *s, nil
}
You can also define a single Load function that will prioritise one type of config over the other. With these tags, we're supporting environment variables, a Yaml file, and command line arguments. Generally command line arguments override any of the other formats. As for Yaml vs environment variables, you could argue both ways: an environment variable like ENVIRONMENT isn't very specific, and could easily be used by multiple processes by mistake. Then again, if you deploy things properly, that shouldn't be an issue, so for that reason, I'd prioritise environment variables over the Yaml file:
func Load(args []string) (*Config, error) {
c := &Config{
Environment: "devel", // default
serviceByName: map[string]*Service{},
}
if err := configure.ParseYaml(c); err != nil {
return nil, err
}
if err := configure.ParseEnv(c); err != nil {
return nil, err
}
if len(args) > 0 {
if err := configure.ParseCommanLine(c, args); err != nil {
return nil, err
}
}
// initialise convenience fields like serviceByName:
for _, svc := range c.Services {
c.serviceByName[svc.Name] = svc
}
return &c, nil
}
Then in your main package:
func main() {
cfg, err := config.Load(os.Args[1:])
if err != nil {
fmt.Printf("Failed to load config: %v\n", err)
os.Exit(1)
}
wtCfg, err := config.ServiceByName("WT")
if err != nil {
fmt.Printf("WT service not found: %v\n", err)
return
}
fmt.Printf("%#v\n", wtCfg)
}
I want to create a custom kubernetes resource with go. The application is deployed in the kubernetes cluster. I want to create e.g. the followng resource:
apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
name: add-response-header
config:
add:
headers:
- "demo: injected-by-kong"
plugin: response-transformer
So far I always created the 'standard' resources e.g. a secret with the following code:
CreateSecret(name string, data map[string]string) error {
confs, err := rest.InClusterConfig()
if err != nil {
panic(err)
}
clientset, err = kubernetes.NewForConfig(confs)
i := clientset.CoreV1()
if _, err := i.Secrets(namespace).Create(&v1.Secret{
TypeMeta: metav1.TypeMeta{
Kind: "Secret",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
StringData: data,
Type: "Opaque",
}); err != nil {
return err
}
}
In addition I tried to get a resource with the following code:
b, err := clientset.RESTClient().Get().Namespace(namespace).Resource("KongPlugin").DoRaw()
I get the following err:
the server could not find the requested resource (get KongPlugin)
If I make a request at the command line k get KongPlugin I can see all the resources.
NAME PLUGIN-TYPE AGE
add-proxy-headers request-transformer 3h34m
So how can view the custom resoources?
For RESTClient 👇🏼
Get:
You have to fully specify path to you custom resource.
Use fluent interface
data, err := clientset.RESTClient().
Get().
AbsPath("/apis/<api>/<version>").
Namespace("<namespace>").
Resource("kongplugins").
Name("kongplugin-sample").
DoRaw(context.TODO())
or specify manually
data, err := clientset.RESTClient().
Get().
AbsPath("/apis/<api>/<version>/namespaces/<namespace>/kongplugins/kongplugin-sample").
DoRaw(context.TODO())
You can find AbsPath in selfLink of custom resource.
Create:
For example, you can post marshaled data use AbsPath
kongPlugin := &KongPlugin{
TypeMeta: metav1.TypeMeta{
APIVersion: "<api>/<version>",
Kind: "KongPlugin",
},
ObjectMeta: metav1.ObjectMeta{
Name: "kongplugin-sample",
Namespace: "<namespace>",
},
...}}
body, err := json.Marshal(kongPlugin)
data, err := clientset.RESTClient().
Post().
AbsPath("/apis/<api>/<version>/namespaces/<namespace>/kongplugins").
Body(body).
DoRaw(context.TODO())
because arg of the method Body(obj interface{}) is an empty interface, you can use different types of arguments according to documentation:
k8s.io/client-go/rest - func (*Request) Body
In a custom Kubernetes operator implemented with the operator-sdk in golang is it possible to call the custom API directly and retrieve the object as YAML?
For example. I have a custom resource
apiVersion: test.com/v1alpha1
kind: TEST
metadata::
name: example-test
spec:
replicas: 3
randomname: value
I don't know ahead of time what the fields in the spec are going to be apart from replicas. So I am not able to create a go type that includes structs to hold the entries.
So rather than doing:
instance := &testv1alpha1.Test{}
err := r.client.Get(context.TODO(), nameSpaceName, instance)
I want to be able to do something like:
instanceYAML := genericContainer{}
err := r.client.GetGeneric(context.TODO(), nameSpaceName, instance)
and then parse the instanceYAML to check the entries.
This is called the "unstructured" client. The docs are pretty light so I recommend looking over the tests as examples https://github.com/kubernetes-sigs/controller-runtime/blob/ea32729106c995d9df310ac4731c2061490addfb/pkg/client/client_test.go#L1536-L1566
Using the unstructured type client all the operation done on the CoreAPI resource can be done on the CustomResourceDefinition(CRD)
package util
import (
"context"
"encoding/json"
"strconv"
"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
_ "k8s.io/client-go/plugin/pkg/client/auth"
"sigs.k8s.io/controller-runtime/pkg/client"
)
var reqLogger logr.Logger
func CRUDonCRD(c client.Client) {
u := createUnstructuredObject(c)
GetCR(u, c)
PatchCR(u, c, 1)
DeleteCR(u, c)
}
func createUnstructuredObject(c client.Client) *unstructured.Unstructured {
u := &unstructured.Unstructured{}
u.SetGroupVersionKind(schema.GroupVersionKind{
// Group: "<crd group name>",
// Kind: "<crd kind>",
// Version: "<crd version>",
Group: "com.cumulus.netq.operator.transport",
Kind: "TransportOperator",
Version: "v1",
})
_ = c.Get(context.Background(), client.ObjectKey{
// Namespace: "<namespace>",
// Name: "cr name",
Namespace: "default",
Name: "transport-operator",
}, u)
return u
}
type patchStringValue struct {
Op string `json:"op"`
Path string `json:"path"`
Value string `json:"value"`
}
func PatchCR(u *unstructured.Unstructured, c client.Client, replicaCount int) error {
payload := []patchStringValue{{
Op: "replace",
Path: "/spec/replicas/replicasOpta",
Value: strconv.Itoa(replicaCount),
}}
payloadBytes, _ := json.Marshal(payload)
err := c.Patch(context.Background(), u, client.RawPatch(types.JSONPatchType, payloadBytes))
if err != nil {
reqLogger.Error(err, "error occured while patching")
}
reqLogger.Info("Patching is successful", "Patched Transport Operator Object", u)
return err
}
func GetCR(u *unstructured.Unstructured, c client.Client) {
// key := client.ObjectKey{Namespace: "default", Name: "<cr-name>"}
key := client.ObjectKey{Namespace: "default", Name: "transport-operator"}
err := c.Get(context.Background(), key, u)
if err != nil {
reqLogger.Error(err, "error occured while getting the resource")
}
reqLogger.Info("Got the resource", "Resource Object", u)
}
func DeleteCR(u *unstructured.Unstructured, c client.Client) {
err := c.Delete(context.Background(), u)
if err != nil {
reqLogger.Error(err, "error occured while deleting the resource")
}
reqLogger.Info("Resource deleted", "Resource ", u)
}
Here is what I did in the Operator SDK to get a object from kubernetes.
Ref: https://github.com/kubernetes-sigs/controller-runtime/blob/master/pkg/client/example_test.go#L57
func (r *ConfigDeploymentReconciler) findObjectsForConfigMap(configMap client.Object) []reconcile.Request {
pod := &corev1.Pod{}
_ = r.Client.Get(context.Background(), client.ObjectKey{
Namespace: "prometheus",
Name: "prometheus-node-exporter-7vxqs",
}, pod)
fmt.Println("Got this pod", pod.String())
return nil
}
I'm looking for the go equivalent of:
kubectl get some-custom-resource-kind some-custom-resource -o yaml > file.yaml
Modify the yaml file...
kubectl apply -f file.yaml
Kubernetes has a client go library for standard resource kinds.
And various vendors have client go libraries for their custom resources.
But I need to get/update a resource kind that doesn't have a publicly available client go library. The logic is implemented in bash script today and I'm trying to move that function to a go controller.
Seems like it there should be a straightforward way in go to do the equivalent of kubectl.
Thanks, Paul
For any type, including your CRDs, use client.Client.
From the documentation:
// Using a typed object.
pod := &corev1.Pod{}
// c is a created client.
_ = c.Get(context.Background(), client.ObjectKey{
Namespace: "namespace",
Name: "name",
}, pod)
pod.SetFinalizers(append(pod.GetFinalizers(), "new-finalizer"))
_ = c.Update(context.Background(), pod)
// Using a unstructured object.
u := &unstructured.Unstructured{}
u.SetGroupVersionKind(schema.GroupVersionKind{
Group: "apps",
Kind: "Deployment",
Version: "v1",
})
_ = c.Get(context.Background(), client.ObjectKey{
Namespace: "namespace",
Name: "name",
}, u)
u.SetFinalizers(append(u.GetFinalizers(), "new-finalizer"))
_ = c.Update(context.Background(), u)
You could just as easily swap in SomeCustomResourceKind:
myCR := &v1alpha1.SomeCustomResourceKind{}
// c is a created client.Client
_ = c.Get(context.TODO(), client.ObjectKey{
Namespace: "namespace",
Name: "some-custom-resource", }, myCR)
myCR.MyProperty = "NewValue"
_ = c.Update(context.TODO(), myCR)
You mentioned you're trying to move this functionality from a bash script to a Go controller, so it would be worth checking out the Kubebuilder project, which can scaffold out a controller for you (and any additional APIs you might need). It creates fully functional controllers with the controller-runtime Client and wires up all the reconciliation logic to manage your CRDs.
I am creating a CRD (securitycontextconstraint) as follows:
import(
v1 "github.com/openshift/api/security/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (c *ClusterClient) CreateSCC() {
name := "ssc-test"
scc := v1.SecurityContextConstraints{
TypeMeta: metav1.TypeMeta{
Kind: "SecurityContextConstraints",
APIVersion: "security.openshift.io/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
RequiredDropCapabilities: []corev1.Capability{"KILL", "MKNOD", "SETUID", "SETGID"},
AllowHostPorts: false,
AllowPrivilegedContainer: false,
AllowHostDirVolumePlugin: false,
AllowHostIPC: false,
ReadOnlyRootFilesystem: false,
DefaultAddCapabilities: nil,
AllowHostPID: false,
AllowHostNetwork: false,
AllowedCapabilities: nil,
Volumes: []v1.FSType{v1.FSTypeConfigMap, v1.FSTypeDownwardAPI, v1.FSTypeEmptyDir, v1.FSTypePersistentVolumeClaim, v1.FSProjected, v1.FSTypeSecret},
SELinuxContext: v1.SELinuxContextStrategyOptions{
Type: v1.SELinuxStrategyMustRunAs,
},
RunAsUser: v1.RunAsUserStrategyOptions{
Type: v1.RunAsUserStrategyMustRunAsRange,
},
SupplementalGroups: v1.SupplementalGroupsStrategyOptions{
Type: v1.SupplementalGroupsStrategyRunAsAny,
},
Users: []string{},
Groups: []string{"system:authenticated"},
}
// To check if scc is already created or not
_, err := c.kubeClient.CoreV1().RESTClient().
Get().
AbsPath("/apis/security.openshift.io/v1/securitycontextconstraints/ssc-test").
DoRaw(context.Background())
// scc is not present in cluster
if err != nil {
//Creating SCC
_, err = c.kubeClient.CoreV1().RESTClient().
Post().
AbsPath("/apis/security.openshift.io/v1").
Resource("securitycontextconstraints").
// VersionedParams(&metav1.CreateOptions{}, scheme.ParameterCodec).
Body(&scc).
DoRaw(context.Background())
if err != nil {
fmt.Printf("Failed to create SecurityContextConstraints: %v\n", err)
// return Failure, err
}
fmt.Printf("Successfully created SecurityContextConstraints %s\n", name)
}
}