Skip to content
This repository has been archived by the owner on Dec 21, 2023. It is now read-only.

Commit

Permalink
chore: Introduce needed methods before deprecating kubernetes-utils (#…
Browse files Browse the repository at this point in the history
…477)

* chore: Introduce needed methods before deprecating kubernetes-utils

Signed-off-by: odubajDT <[email protected]>

* move to correct folder

Signed-off-by: odubajDT <[email protected]>

* fix

Signed-off-by: odubajDT <[email protected]>

* restructuring

Signed-off-by: odubajDT <[email protected]>

* go mod tidy

Signed-off-by: odubajDT <[email protected]>

* restructure

Signed-off-by: odubajDT <[email protected]>

* refactor code

Signed-off-by: odubajDT <[email protected]>

* remove unneeded methods

Signed-off-by: odubajDT <[email protected]>

* first version of tests

Signed-off-by: odubajDT <[email protected]>

* covered by tests

Signed-off-by: odubajDT <[email protected]>

* missing comments

Signed-off-by: odubajDT <[email protected]>

* polish

Signed-off-by: odubajDT <[email protected]>

* fix

Signed-off-by: odubajDT <[email protected]>

* pr review

Signed-off-by: odubajDT <[email protected]>
  • Loading branch information
odubajDT authored Jun 24, 2022
1 parent 0d19a1b commit 4d49101
Show file tree
Hide file tree
Showing 10 changed files with 1,058 additions and 23 deletions.
58 changes: 47 additions & 11 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,67 @@ require (
go.opentelemetry.io/otel/sdk v1.2.0
go.opentelemetry.io/otel/trace v1.2.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
k8s.io/api v0.24.2
k8s.io/apimachinery v0.24.2
k8s.io/client-go v0.24.2
)

require (
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/cenkalti/backoff/v4 v4.1.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/felixge/httpsnoop v1.0.2 // indirect
github.com/go-logr/logr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.5 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/json-iterator/go v1.1.10 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/onsi/ginkgo v1.16.4 // indirect
github.com/onsi/gomega v1.15.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.2.0 // indirect
go.opentelemetry.io/otel/internal/metric v0.25.0 // indirect
go.opentelemetry.io/otel/metric v0.25.0 // indirect
go.opentelemetry.io/proto/otlp v0.10.0 // indirect
go.uber.org/atomic v1.4.0 // indirect
go.uber.org/multierr v1.1.0 // indirect
go.uber.org/zap v1.10.0 // indirect
golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 // indirect
golang.org/x/text v0.3.6 // indirect
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
google.golang.org/grpc v1.42.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.19.0 // indirect
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect
google.golang.org/grpc v1.43.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.60.1 // indirect
k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)

replace golang.org/x/net => golang.org/x/net v0.0.0-20211205041911-012df41ee64c
523 changes: 512 additions & 11 deletions go.sum

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions pkg/common/kubeutils/apitoken_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package kubeutils

import (
"context"
"fmt"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)

// APITokenProvider wraps around the kubernetes interface to enhance testability
type APITokenProvider struct {
clientSet kubernetes.Interface
}

// NewAPITokenProvider creates new APITokenProvider
func NewAPITokenProvider(useInClusterConfig bool) (*APITokenProvider, error) {
clientSet, err := GetClientSet(useInClusterConfig)
if err != nil {
return nil, fmt.Errorf("could not create APITokenProvider: %s", err.Error())
}
return &APITokenProvider{clientSet: clientSet}, nil
}

// GetKeptnAPITokenFromSecret returns the `keptn-api-token` data secret from Keptn Installation
func (a *APITokenProvider) GetKeptnAPITokenFromSecret(ctx context.Context, namespace string, secretName string) (string, error) {
keptnSecret, err := a.clientSet.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{})
if err != nil {
return "", err
}
if apitoken, ok := keptnSecret.Data["keptn-api-token"]; ok {
return string(apitoken), nil
}
return "", fmt.Errorf("data 'keptn-api-token' not found")
}
51 changes: 51 additions & 0 deletions pkg/common/kubeutils/apitoken_provider_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package kubeutils

import (
"context"
"fmt"
"testing"

"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
k8stesting "k8s.io/client-go/testing"
)

func TestAPITokenProvider_GetKeptnAPITokenFromSecret_FailClientSet(t *testing.T) {
kubernetes := fake.NewSimpleClientset()
kubernetes.Fake.PrependReactor("get", "secrets", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, nil, fmt.Errorf("error retrieving kubernetes secret")
})
apiTokenProvider := &APITokenProvider{clientSet: kubernetes}
res, err := apiTokenProvider.GetKeptnAPITokenFromSecret(context.TODO(), "keptn", "secret")
require.Equal(t, "", res)
require.Error(t, err)
require.Equal(t, fmt.Errorf("error retrieving kubernetes secret"), err)

}

func TestAPITokenProvider_GetKeptnAPITokenFromSecret_InvalidData(t *testing.T) {
kubernetes := fake.NewSimpleClientset()
kubernetes.Fake.PrependReactor("get", "secrets", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, &v1.Secret{Data: map[string][]byte{"some-data": []byte("token")}}, nil
})
apiTokenProvider := &APITokenProvider{clientSet: kubernetes}
res, err := apiTokenProvider.GetKeptnAPITokenFromSecret(context.TODO(), "keptn", "secret")
require.Equal(t, "", res)
require.Error(t, err)
require.Equal(t, fmt.Errorf("data 'keptn-api-token' not found"), err)

}

func TestAPITokenProvider_GetKeptnAPITokenFromSecret_ValidData(t *testing.T) {
kubernetes := fake.NewSimpleClientset()
kubernetes.Fake.PrependReactor("get", "secrets", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, &v1.Secret{Data: map[string][]byte{"keptn-api-token": []byte("token")}}, nil
})
apiTokenProvider := &APITokenProvider{clientSet: kubernetes}
res, err := apiTokenProvider.GetKeptnAPITokenFromSecret(context.TODO(), "keptn", "secret")
require.Equal(t, "token", res)
require.Nil(t, err)

}
52 changes: 52 additions & 0 deletions pkg/common/kubeutils/endpoint_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package kubeutils

import (
"context"
"fmt"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)

// KeptnEndpointProvider wraps around the kubernetes interface to enhance testability
type KeptnEndpointProvider struct {
clientSet kubernetes.Interface
}

// NewKeptnEndpointProvider creates new KeptnEndpointProvider
func NewKeptnEndpointProvider(useInClusterConfig bool) (*KeptnEndpointProvider, error) {
clientSet, err := GetClientSet(useInClusterConfig)
if err != nil {
return nil, fmt.Errorf("could not create KeptnEndpointProvider: %s", err.Error())
}
return &KeptnEndpointProvider{clientSet: clientSet}, nil
}

// GetKeptnEndpointFromIngress returns the host of ingress object Keptn Installation
func (a *KeptnEndpointProvider) GetKeptnEndpointFromIngress(ctx context.Context, namespace string, ingressName string) (string, error) {
keptnIngress, err := a.clientSet.ExtensionsV1beta1().Ingresses(namespace).Get(ctx, ingressName, metav1.GetOptions{})
if err != nil {
return "", err
}
if keptnIngress.Spec.Rules != nil {
return keptnIngress.Spec.Rules[0].Host, nil
}
return "", fmt.Errorf("cannot retrieve ingress data: ingress rule does not exist")
}

// GetKeptnEndpointFromService returns the loadbalancer service IP from Keptn Installation
func (a *KeptnEndpointProvider) GetKeptnEndpointFromService(ctx context.Context, namespace string, serviceName string) (string, error) {
keptnService, err := a.clientSet.CoreV1().Services(namespace).Get(ctx, serviceName, metav1.GetOptions{})
if err != nil {
return "", err
}
switch keptnService.Spec.Type {
case "LoadBalancer":
if len(keptnService.Status.LoadBalancer.Ingress) > 0 {
return keptnService.Status.LoadBalancer.Ingress[0].IP, nil
}
return "", fmt.Errorf("Loadbalancer IP isn't found")
default:
return "", fmt.Errorf("it doesn't support ClusterIP & NodePort type service for fetching endpoint automatically")
}
}
119 changes: 119 additions & 0 deletions pkg/common/kubeutils/endpoint_provider_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package kubeutils

import (
"context"
"fmt"
"testing"

"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
k8stesting "k8s.io/client-go/testing"
)

func TestKeptnEndpointProvider_GetKeptnEndpointFromIngress_FailClientSet(t *testing.T) {
kubernetes := fake.NewSimpleClientset()
kubernetes.Fake.PrependReactor("get", "ingresses", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, nil, fmt.Errorf("error retrieving kubernetes ingress")
})
keptnEndpointProvider := &KeptnEndpointProvider{clientSet: kubernetes}
res, err := keptnEndpointProvider.GetKeptnEndpointFromIngress(context.TODO(), "keptn", "ingress")
require.Equal(t, "", res)
require.Error(t, err)
require.Equal(t, fmt.Errorf("error retrieving kubernetes ingress"), err)
}

func TestKeptnEndpointProvider_GetKeptnEndpointFromIngress_Invalid(t *testing.T) {
kubernetes := fake.NewSimpleClientset()
kubernetes.Fake.PrependReactor("get", "ingresses", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, &v1beta1.Ingress{Spec: v1beta1.IngressSpec{}}, nil
})
keptnEndpointProvider := &KeptnEndpointProvider{clientSet: kubernetes}
res, err := keptnEndpointProvider.GetKeptnEndpointFromIngress(context.TODO(), "keptn", "ingress")
require.Equal(t, "", res)
require.Error(t, err)
require.Equal(t, fmt.Errorf("cannot retrieve ingress data: ingress rule does not exist"), err)
}

func TestKeptnEndpointProvider_GetKeptnEndpointFromIngress_Valid(t *testing.T) {
kubernetes := fake.NewSimpleClientset()
kubernetes.Fake.PrependReactor("get", "ingresses", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, &v1beta1.Ingress{
Spec: v1beta1.IngressSpec{
Rules: []v1beta1.IngressRule{
{
Host: "1.1.1.1",
},
},
},
}, nil
})
keptnEndpointProvider := &KeptnEndpointProvider{clientSet: kubernetes}
res, err := keptnEndpointProvider.GetKeptnEndpointFromIngress(context.TODO(), "keptn", "api-keptn-ingress")
require.Equal(t, "1.1.1.1", res)
require.Nil(t, err)
}

func TestKeptnEndpointProvider_GetKeptnEndpointFromService_FailClientSet(t *testing.T) {
kubernetes := fake.NewSimpleClientset()
kubernetes.Fake.PrependReactor("get", "services", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, nil, fmt.Errorf("error retrieving kubernetes service")
})
keptnEndpointProvider := &KeptnEndpointProvider{clientSet: kubernetes}
res, err := keptnEndpointProvider.GetKeptnEndpointFromService(context.TODO(), "keptn", "service1")
require.Equal(t, "", res)
require.Error(t, err)
require.Equal(t, fmt.Errorf("error retrieving kubernetes service"), err)
}

func TestKeptnEndpointProvider_GetKeptnEndpointFromService_NoLoadBalancer(t *testing.T) {
kubernetes := fake.NewSimpleClientset()
kubernetes.Fake.PrependReactor("get", "services", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, &v1.Service{Spec: v1.ServiceSpec{Type: v1.ServiceTypeClusterIP}}, nil
})
keptnEndpointProvider := &KeptnEndpointProvider{clientSet: kubernetes}
res, err := keptnEndpointProvider.GetKeptnEndpointFromService(context.TODO(), "keptn22", "service")
require.Equal(t, "", res)
require.Error(t, err)
require.Equal(t, fmt.Errorf("it doesn't support ClusterIP & NodePort type service for fetching endpoint automatically"), err)
}

func TestKeptnEndpointProvider_GetKeptnEndpointFromService_LoadBalancerNoIngress(t *testing.T) {
kubernetes := fake.NewSimpleClientset()
kubernetes.Fake.PrependReactor("get", "services", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, &v1.Service{
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer},
Status: v1.ServiceStatus{
LoadBalancer: v1.LoadBalancerStatus{},
},
}, nil
})
keptnEndpointProvider := &KeptnEndpointProvider{clientSet: kubernetes}
res, err := keptnEndpointProvider.GetKeptnEndpointFromService(context.TODO(), "keptn22", "service")
require.Equal(t, "", res)
require.Error(t, err)
require.Equal(t, fmt.Errorf("Loadbalancer IP isn't found"), err)
}

func TestKeptnEndpointProvider_GetKeptnEndpointFromService_LoadBalancerIngressIP(t *testing.T) {
kubernetes := fake.NewSimpleClientset()
kubernetes.Fake.PrependReactor("get", "services", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
return true, &v1.Service{
Spec: v1.ServiceSpec{Type: v1.ServiceTypeLoadBalancer},
Status: v1.ServiceStatus{
LoadBalancer: v1.LoadBalancerStatus{
Ingress: []v1.LoadBalancerIngress{
{IP: "1.1.1.1"},
},
},
},
}, nil
})
keptnEndpointProvider := &KeptnEndpointProvider{clientSet: kubernetes}
res, err := keptnEndpointProvider.GetKeptnEndpointFromService(context.TODO(), "keptn22", "service")
require.Equal(t, "1.1.1.1", res)
require.Nil(t, err)
}
38 changes: 38 additions & 0 deletions pkg/common/kubeutils/kubeclientset_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package kubeutils

import (
"os"
"path/filepath"

"k8s.io/client-go/kubernetes"

"github.com/keptn/go-utils/pkg/common/fileutils"

"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)

// GetClientSet returns the kubernetes Clientset
func GetClientSet(useInClusterConfig bool) (*kubernetes.Clientset, error) {

var config *rest.Config
var err error
if useInClusterConfig {
config, err = rest.InClusterConfig()
} else {
var kubeconfig string
if os.Getenv("KUBECONFIG") != "" {
kubeconfig = fileutils.ExpandTilde(os.Getenv("KUBECONFIG"))
} else {
kubeconfig = filepath.Join(
fileutils.UserHomeDir(), ".kube", "config",
)
}
config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
}
if err != nil {
return nil, err
}

return kubernetes.NewForConfig(config)
}
Loading

0 comments on commit 4d49101

Please sign in to comment.