Change Kubernetes client to official go-client in the events binding. (#436)

* Change Kubernetes client to official go-client in the events binding.

* Change adding flags to init

* go mod tidy

* Proper call for handler

* Remove commented out code

* Refactor indentation
This commit is contained in:
Mukundan Sundararajan 2020-08-18 11:49:06 -07:00 committed by GitHub
parent 467375c254
commit c6623da87e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 146 additions and 106 deletions

View File

@ -10,22 +10,27 @@ import (
"path/filepath"
"k8s.io/client-go/rest"
"k8s.io/client-go/util/homedir"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
)
// GetKubeClient returns a kubernetes client
func GetKubeClient() (*kubernetes.Clientset, error) {
var kubeconfig *string
// nolint:gochecknoglobals
var kubeconfig *string
// nolint:gochecknoinits
func init() {
if home := homedir.HomeDir(); home != "" {
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
} else {
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
}
flag.Parse()
}
// GetKubeClient returns a kubernetes client
func GetKubeClient() (*kubernetes.Clientset, error) {
flag.Parse()
conf, err := rest.InClusterConfig()
if err != nil {
conf, err = clientcmd.BuildConfigFromFlags("", *kubeconfig)

View File

@ -6,20 +6,35 @@
package kubernetes
import (
"context"
"encoding/json"
"errors"
"os"
"os/signal"
"strconv"
"syscall"
"time"
kubeclient "github.com/dapr/components-contrib/authentication/kubernetes"
"github.com/dapr/components-contrib/bindings"
"github.com/dapr/dapr/pkg/logger"
"github.com/kubernetes-client/go/kubernetes/client"
"github.com/kubernetes-client/go/kubernetes/config"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
)
type kubernetesInput struct {
config *client.Configuration
client *client.APIClient
namespace string
logger logger.Logger
kubeClient kubernetes.Interface
namespace string
resyncPeriodInSec time.Duration // nolint:stylecheck
logger logger.Logger
}
type EventResponse struct {
Event string `json:"event"`
OldVal v1.Event `json:"oldVal"`
NewVal v1.Event `json:"newVal"`
}
var _ = bindings.InputBinding(&kubernetesInput{})
@ -30,55 +45,98 @@ func NewKubernetes(logger logger.Logger) bindings.InputBinding {
}
func (k *kubernetesInput) Init(metadata bindings.Metadata) error {
c, err := config.LoadKubeConfig()
client, err := kubeclient.GetKubeClient()
if err != nil {
return err
}
k.config = c
k.client = client.NewAPIClient(c)
k.kubeClient = client
return k.parseMetadata(metadata)
}
func (k *kubernetesInput) parseMetadata(metadata bindings.Metadata) error {
ns, found := metadata.Properties["namespace"]
if found {
k.namespace = ns
if val, ok := metadata.Properties["namespace"]; ok && val != "" {
k.namespace = val
} else {
k.namespace = "default"
return errors.New("namespace is missing in metadata")
}
return nil
}
func (k *kubernetesInput) Read(handler func(*bindings.ReadResponse) error) error {
watch := client.WatchClient{
Cfg: k.config,
Client: k.client,
Path: "/api/v1/namespaces/" + k.namespace + "/events",
MakerFn: func() interface{} { return &client.V1Event{} },
}
resultChan, errChan, err := watch.Connect(context.Background(), "")
if err != nil {
return err
}
done := false
for !done {
select {
case obj, ok := <-resultChan:
if !ok {
done = true
break
}
data, err := json.Marshal(obj)
if err != nil {
return err
}
handler(&bindings.ReadResponse{
Data: data,
})
case err := <-errChan:
return err
if val, ok := metadata.Properties["resyncPeriodInSec"]; ok && val != "" {
intval, err := strconv.ParseInt(val, 10, 64)
if err != nil {
k.logger.Warnf("invalid resyncPeriodInSec %s; %v; defaulting to 10s", val, err)
k.resyncPeriodInSec = time.Second * 10
} else {
k.resyncPeriodInSec = time.Second * time.Duration(intval)
}
}
return nil
}
func (k *kubernetesInput) Read(handler func(*bindings.ReadResponse) error) error {
watchlist := cache.NewListWatchFromClient(
k.kubeClient.CoreV1().RESTClient(),
"events",
k.namespace,
fields.Everything())
var resultChan chan EventResponse = make(chan EventResponse)
_, controller := cache.NewInformer(
watchlist,
&v1.Event{},
k.resyncPeriodInSec,
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
if obj != nil {
resultChan <- EventResponse{
Event: "add",
NewVal: *(obj.(*v1.Event)),
OldVal: v1.Event{},
}
} else {
k.logger.Warnf("Nil Object in Add handle %v", obj)
}
},
DeleteFunc: func(obj interface{}) {
if obj != nil {
resultChan <- EventResponse{
Event: "delete",
OldVal: *(obj.(*v1.Event)),
NewVal: v1.Event{},
}
} else {
k.logger.Warnf("Nil Object in Delete handle %v", obj)
}
},
UpdateFunc: func(oldObj, newObj interface{}) {
if oldObj != nil && newObj != nil {
resultChan <- EventResponse{
Event: "update",
OldVal: *(oldObj.(*v1.Event)),
NewVal: *(newObj.(*v1.Event)),
}
} else {
k.logger.Warnf("Nil Objects in Update handle %v %v", oldObj, newObj)
}
},
},
)
stopCh := make(chan struct{})
sigterm := make(chan os.Signal, 1)
signal.Notify(sigterm, syscall.SIGINT, syscall.SIGTERM)
go controller.Run(stopCh)
done := false
for !done {
select {
case obj := <-resultChan:
data, err := json.Marshal(obj)
if err != nil {
k.logger.Errorf("Error marshalling event %w", err)
} else {
handler(&bindings.ReadResponse{
Data: data,
})
}
case <-sigterm:
done = true
close(stopCh)
}
}
return nil

View File

@ -6,72 +6,46 @@
package kubernetes
import (
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"
"github.com/dapr/components-contrib/bindings"
"github.com/dapr/dapr/pkg/logger"
"github.com/kubernetes-client/go/kubernetes/client"
"github.com/stretchr/testify/assert"
)
type staticHandler struct {
Code int
Body string
QueryParams url.Values
}
func (s *staticHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
res.WriteHeader(s.Code)
res.Write([]byte(s.Body))
s.QueryParams = req.URL.Query()
}
func TestParseMetadata(t *testing.T) {
nsName := "fooNamespace"
m := bindings.Metadata{}
m.Properties = map[string]string{"namespace": nsName}
t.Run("parse metadata", func(t *testing.T) {
resyncPeriod := time.Second * 15
m := bindings.Metadata{}
m.Properties = map[string]string{"namespace": nsName, "resyncPeriodInSec": "15"}
i := kubernetesInput{logger: logger.NewLogger("test")}
i.parseMetadata(m)
i := kubernetesInput{logger: logger.NewLogger("test")}
i.parseMetadata(m)
assert.Equal(t, nsName, i.namespace, "The namespaces should be the same.")
}
func TestReadItem(t *testing.T) {
server := httptest.NewServer(&staticHandler{
Code: 200,
Body: `{"type":"ADDED","object":{"kind":"Event","apiVersion":"v1","metadata":{"name":"kube-system","selfLink":"/api/v1/namespaces/default/test","uid":"164931a7-3d75-11e9-a0a0-2683b9459238","resourceVersion":"227","creationTimestamp":"2019-03-03T05:27:50Z","annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Namespace\",\"metadata\":{\"annotations\":{},\"name\":\"kube-system\",\"namespace\":\"\"}}\n"}},"spec":{"finalizers":["kubernetes"]},"status":{"phase":"Active"}}}\n`,
assert.Equal(t, nsName, i.namespace, "The namespaces should be the same.")
assert.Equal(t, resyncPeriod, i.resyncPeriodInSec, "The resyncPeriod should be the same.")
})
defer server.Close()
t.Run("parse metadata no namespace", func(t *testing.T) {
m := bindings.Metadata{}
m.Properties = map[string]string{"resyncPeriodInSec": "15"}
u, err := url.Parse(server.URL)
if !assert.NoError(t, err, "URL Parsing failed!") {
t.FailNow()
}
i := kubernetesInput{logger: logger.NewLogger("test")}
err := i.parseMetadata(m)
cfg := &client.Configuration{}
cfg.Host = u.Host
cfg.Scheme = u.Scheme
i := &kubernetesInput{
config: cfg,
client: client.NewAPIClient(cfg),
logger: logger.NewLogger("test"),
}
count := 0
i.Read(func(res *bindings.ReadResponse) error {
count++
result := client.Result{}
json.Unmarshal(res.Data, &result)
assert.Equal(t, "ADDED", result.Type, "Unexpected watch event type: %v", result.Type)
return nil
assert.NotNil(t, err, "Expected err to be returned.")
assert.Equal(t, "namespace is missing in metadata", err.Error(), "Error message not same.")
})
t.Run("parse metadata invalid resync period", func(t *testing.T) {
m := bindings.Metadata{}
m.Properties = map[string]string{"namespace": nsName, "resyncPeriodInSec": "invalid"}
assert.Equal(t, 1, count, "Expected 1 item, saw %v\n", count)
i := kubernetesInput{logger: logger.NewLogger("test")}
err := i.parseMetadata(m)
assert.Nil(t, err, "Expected err to be nil.")
assert.Equal(t, nsName, i.namespace, "The namespaces should be the same.")
assert.Equal(t, time.Second*10, i.resyncPeriodInSec, "The resyncPeriod should be the same.")
})
}

2
go.mod
View File

@ -47,7 +47,6 @@ require (
github.com/influxdata/influxdb-client-go v1.4.0
github.com/jackc/pgx/v4 v4.6.0
github.com/json-iterator/go v1.1.8
github.com/kubernetes-client/go v0.0.0-20190625181339-cd8e39e789c7
github.com/mitchellh/mapstructure v1.3.2 // indirect
github.com/nats-io/gnatsd v1.4.1
github.com/nats-io/go-nats v1.7.2
@ -81,6 +80,7 @@ require (
gopkg.in/couchbase/gocb.v1 v1.6.4
gopkg.in/couchbase/gocbcore.v7 v7.1.16 // indirect
gopkg.in/couchbaselabs/gojcbmock.v1 v1.0.4 // indirect
k8s.io/api v0.17.0
k8s.io/apimachinery v0.17.0
k8s.io/client-go v0.17.0
)

2
go.sum
View File

@ -247,6 +247,7 @@ github.com/emicklei/go-restful v2.10.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQm
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fasthttp-contrib/sessions v0.0.0-20160905201309-74f6ac73d5d5 h1:M4CVMQ5ueVmGZAtkW2bsO+ftesCYpfxl27JTqtzKBzE=
github.com/fasthttp-contrib/sessions v0.0.0-20160905201309-74f6ac73d5d5/go.mod h1:MQXNGeXkpojWTxbN7vXoE3f7EmlA11MlJbsrJpVBINA=
@ -1210,6 +1211,7 @@ k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/utils v0.0.0-20190221042446-c2654d5206da h1:ElyM7RPonbKnQqOcw7dG2IK5uvQQn3b/WPHqD5mBvP4=
k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0=

View File

@ -8,6 +8,7 @@ package kubernetes
import (
"errors"
kubeclient "github.com/dapr/components-contrib/authentication/kubernetes"
"github.com/dapr/components-contrib/secretstores"
"github.com/dapr/dapr/pkg/logger"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -26,7 +27,7 @@ func NewKubernetesSecretStore(logger logger.Logger) secretstores.SecretStore {
// Init creates a Kubernetes client
func (k *kubernetesSecretStore) Init(metadata secretstores.Metadata) error {
client, err := GetKubeClient()
client, err := kubeclient.GetKubeClient()
if err != nil {
return err
}