feat: add e2e test with a sample dapr application
This commit is contained in:
parent
57c1c55910
commit
be366533c7
|
@ -71,24 +71,12 @@ jobs:
|
|||
registry: false
|
||||
node_image: kindest/node:v${{ matrix.kubernetes-version }}
|
||||
config: test/e2e/kind.yaml
|
||||
- name: 'SetUp Kind Ingress'
|
||||
run: |
|
||||
./hack/scripts/deploy_ingress.sh
|
||||
- name: "SetUp Dapr Kubernetes Operator OLM"
|
||||
run: |
|
||||
make olm/install
|
||||
|
||||
kubectl wait \
|
||||
--namespace=olm \
|
||||
--for=condition=ready \
|
||||
pod \
|
||||
--selector=app=olm-operator \
|
||||
--timeout=90s
|
||||
|
||||
kubectl wait \
|
||||
--namespace=olm \
|
||||
--for=condition=ready \
|
||||
pod \
|
||||
--selector=app=catalog-operator \
|
||||
--timeout=90s
|
||||
|
||||
./hack/scripts/deploy_olm.sh
|
||||
- name: "Run Dapr Kubernetes Operator OLM e2e"
|
||||
run: |
|
||||
make test/e2e/olm
|
|
@ -51,6 +51,9 @@ jobs:
|
|||
registry: false
|
||||
node_image: kindest/node:v${{ matrix.kubernetes-version }}
|
||||
config: test/e2e/kind.yaml
|
||||
- name: 'SetUp Kind Ingress'
|
||||
run: |
|
||||
./hack/scripts/deploy_ingress.sh
|
||||
- name: "SetUp Dapr Kubernetes Operator"
|
||||
run: |
|
||||
make deploy/e2e
|
||||
|
|
|
@ -8,5 +8,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1
|
|||
kind: Kustomization
|
||||
images:
|
||||
- name: controller
|
||||
newName: docker.io/daprio/dapr-kubernetes-operator
|
||||
newTag: 0.0.2
|
||||
newName: ttl.sh/57e2fb88-57e2-4344-8b69-4364dd4f8b9b
|
||||
newTag: 2h
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: example-ingress
|
||||
#annotations:
|
||||
# nginx.ingress.kubernetes.io/use-regex: "true"
|
||||
# nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: /
|
||||
backend:
|
||||
service:
|
||||
name: testing-app-service
|
||||
port:
|
||||
number: 80
|
90
go.mod
90
go.mod
|
@ -3,11 +3,12 @@ module github.com/dapr-sandbox/dapr-kubernetes-operator
|
|||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/anthhub/forwarder v1.1.1-0.20230315114022-63dcf7b46a1a
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible
|
||||
github.com/go-logr/logr v1.2.4
|
||||
github.com/onsi/gomega v1.27.10
|
||||
github.com/openshift/api v0.0.0-20230816181854-a7ca92db022a
|
||||
github.com/operator-framework/api v0.17.5
|
||||
github.com/operator-framework/api v0.17.7
|
||||
github.com/operator-framework/operator-lifecycle-manager v0.22.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rs/xid v1.5.0
|
||||
|
@ -27,71 +28,130 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||
github.com/MakeNowJust/heredoc v1.0.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/Masterminds/squirrel v1.5.4 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.2 // indirect
|
||||
github.com/containerd/containerd v1.7.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.10.1 // indirect
|
||||
github.com/docker/cli v23.0.1+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
||||
github.com/docker/docker v23.0.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-gorp/gorp/v3 v3.0.5 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-logr/zapr v1.2.4 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.20.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gosuri/uitable v0.0.4 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jmoiron/sqlx v1.3.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.16.0 // indirect
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/spdystream v0.2.0 // indirect
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.15.1 // indirect
|
||||
github.com/prometheus/client_golang v1.16.0 // indirect
|
||||
github.com/prometheus/client_model v0.4.0 // indirect
|
||||
github.com/prometheus/common v0.42.0 // indirect
|
||||
github.com/prometheus/procfs v0.9.0 // indirect
|
||||
github.com/prometheus/common v0.44.0 // indirect
|
||||
github.com/prometheus/procfs v0.10.1 // indirect
|
||||
github.com/rubenv/sql-migrate v1.3.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.2 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
github.com/xlab/treeprint v1.2.0 // indirect
|
||||
go.opentelemetry.io/otel v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.14.0 // indirect
|
||||
go.starlark.net v0.0.0-20230814145427-12f4cb8177e4 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/crypto v0.12.0 // indirect
|
||||
golang.org/x/net v0.14.0 // indirect
|
||||
golang.org/x/oauth2 v0.8.0 // indirect
|
||||
golang.org/x/oauth2 v0.11.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/term v0.11.0 // indirect
|
||||
golang.org/x/text v0.12.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect
|
||||
google.golang.org/grpc v1.54.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.27.3 // indirect
|
||||
k8s.io/component-base v0.27.3 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
|
||||
k8s.io/apiserver v0.28.0 // indirect
|
||||
k8s.io/cli-runtime v0.28.0 // indirect
|
||||
k8s.io/component-base v0.28.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20230816210353-14e408962443 // indirect
|
||||
k8s.io/kubectl v0.28.0 // indirect
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
|
||||
oras.land/oras-go v1.2.3 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/kustomize/api v0.14.0 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.14.3 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
|
||||
|
||||
kubectl wait \
|
||||
--namespace=ingress-nginx \
|
||||
--for=condition=ready \
|
||||
pod \
|
||||
--selector=app.kubernetes.io/component=controller \
|
||||
--timeout=90s
|
|
@ -0,0 +1,19 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
make olm/install
|
||||
|
||||
kubectl wait \
|
||||
--namespace=olm \
|
||||
--for=condition=ready \
|
||||
pod \
|
||||
--selector=app=olm-operator \
|
||||
--timeout=90s
|
||||
|
||||
kubectl wait \
|
||||
--namespace=olm \
|
||||
--for=condition=ready \
|
||||
pod \
|
||||
--selector=app=catalog-operator \
|
||||
--timeout=90s
|
|
@ -0,0 +1,94 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/pointer"
|
||||
netv1 "k8s.io/api/networking/v1"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/dapr-sandbox/dapr-kubernetes-operator/test/support"
|
||||
"github.com/dapr-sandbox/dapr-kubernetes-operator/test/support/helm"
|
||||
"github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func ValidateDaprApp(test support.Test, namespace string) {
|
||||
test.T().Helper()
|
||||
|
||||
//
|
||||
// Install Dapr Test App
|
||||
//
|
||||
|
||||
test.InstallChart(
|
||||
"oci://docker.io/salaboy/testing-app",
|
||||
helm.WithInstallName("testing-app"),
|
||||
helm.WithInstallNamespace(namespace),
|
||||
helm.WithInstallVersion("v0.1.0"),
|
||||
)
|
||||
|
||||
test.Eventually(support.Deployment(test, "testing-app-deployment", namespace), support.TestTimeoutShort).Should(
|
||||
gomega.WithTransform(support.ConditionStatus(appsv1.DeploymentAvailable), gomega.Equal(corev1.ConditionTrue)),
|
||||
)
|
||||
test.Eventually(support.Service(test, "testing-app-service", namespace), support.TestTimeoutShort).Should(
|
||||
gomega.Not(gomega.BeNil()),
|
||||
)
|
||||
|
||||
//
|
||||
// Expose app
|
||||
//
|
||||
|
||||
ing := test.SetUpIngress(namespace, netv1.HTTPIngressPath{
|
||||
PathType: pointer.Any(netv1.PathTypePrefix),
|
||||
Path: "/",
|
||||
Backend: netv1.IngressBackend{
|
||||
Service: &netv1.IngressServiceBackend{
|
||||
Name: "testing-app-service",
|
||||
Port: netv1.ServiceBackendPort{
|
||||
Number: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
test.Eventually(support.Ingress(test, ing.Name, ing.Namespace), support.TestTimeoutLong).Should(
|
||||
gomega.WithTransform(
|
||||
support.ExtractFirstLoadBalancerIngressHostname(),
|
||||
gomega.Equal("localhost")),
|
||||
"Failure to set-up ingress")
|
||||
|
||||
//
|
||||
// Test the app
|
||||
//
|
||||
|
||||
test.T().Log("test app")
|
||||
|
||||
base := fmt.Sprintf("http://localhost:%d", 8081)
|
||||
|
||||
//nolint:bodyclose
|
||||
test.Eventually(test.GET(base+"/read"), support.TestTimeoutLong).Should(
|
||||
gomega.And(
|
||||
gomega.HaveHTTPStatus(http.StatusOK),
|
||||
gomega.HaveHTTPBody(gomega.MatchJSON(`{ "Values": null }`)),
|
||||
),
|
||||
"Failure to invoke initial read",
|
||||
)
|
||||
|
||||
//nolint:bodyclose
|
||||
test.Eventually(test.POST(base+"/write?message=hello", "text/plain", nil), support.TestTimeoutLong).Should(
|
||||
gomega.HaveHTTPStatus(http.StatusOK),
|
||||
"Failure to invoke post",
|
||||
)
|
||||
|
||||
//nolint:bodyclose
|
||||
test.Eventually(test.GET(base+"/read"), support.TestTimeoutLong).Should(
|
||||
gomega.And(
|
||||
gomega.HaveHTTPStatus(http.StatusOK),
|
||||
gomega.HaveHTTPBody(gomega.MatchJSON(`{ "Values":["hello"] }`)),
|
||||
),
|
||||
"Failure to invoke read",
|
||||
)
|
||||
}
|
|
@ -10,8 +10,8 @@ nodes:
|
|||
node-labels: "ingress-ready=true"
|
||||
extraPortMappings:
|
||||
- containerPort: 80
|
||||
hostPort: 80
|
||||
hostPort: 8081
|
||||
protocol: TCP
|
||||
- containerPort: 443
|
||||
hostPort: 443
|
||||
hostPort: 8443
|
||||
protocol: TCP
|
|
@ -5,6 +5,11 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dapr-sandbox/dapr-kubernetes-operator/internal/controller/operator"
|
||||
daprAc "github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/client/operator/applyconfiguration/operator/v1alpha1"
|
||||
daprTC "github.com/dapr-sandbox/dapr-kubernetes-operator/test/e2e/common"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
|
@ -126,4 +131,30 @@ func TestDaprDeploy(t *testing.T) {
|
|||
test.Eventually(Deployment(test, "dapr-control-plane", sub.Namespace), TestTimeoutLong).Should(
|
||||
WithTransform(ConditionStatus(appsv1.DeploymentAvailable), Equal(corev1.ConditionTrue)))
|
||||
|
||||
//
|
||||
// Dapr
|
||||
//
|
||||
|
||||
instance := test.NewNamespacedNameDaprControlPlane(
|
||||
types.NamespacedName{
|
||||
Name: operator.DaprControlPlaneName,
|
||||
Namespace: sub.Namespace,
|
||||
},
|
||||
daprAc.DaprControlPlaneSpec().
|
||||
WithValues(nil),
|
||||
)
|
||||
|
||||
test.Eventually(Deployment(test, "dapr-operator", instance.Namespace), TestTimeoutLong).Should(
|
||||
WithTransform(ConditionStatus(appsv1.DeploymentAvailable), Equal(corev1.ConditionTrue)))
|
||||
test.Eventually(Deployment(test, "dapr-sentry", instance.Namespace), TestTimeoutLong).Should(
|
||||
WithTransform(ConditionStatus(appsv1.DeploymentAvailable), Equal(corev1.ConditionTrue)))
|
||||
test.Eventually(Deployment(test, "dapr-sidecar-injector", instance.Namespace), TestTimeoutLong).Should(
|
||||
WithTransform(ConditionStatus(appsv1.DeploymentAvailable), Equal(corev1.ConditionTrue)))
|
||||
|
||||
//
|
||||
// Dapr Application
|
||||
//
|
||||
|
||||
daprTC.ValidateDaprApp(test, instance.Namespace)
|
||||
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
daprCP "github.com/dapr-sandbox/dapr-kubernetes-operator/internal/controller/operator"
|
||||
"github.com/rs/xid"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
|
@ -14,7 +13,9 @@ import (
|
|||
. "github.com/dapr-sandbox/dapr-kubernetes-operator/test/support"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
daprCP "github.com/dapr-sandbox/dapr-kubernetes-operator/internal/controller/operator"
|
||||
daprAc "github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/client/operator/applyconfiguration/operator/v1alpha1"
|
||||
daprTC "github.com/dapr-sandbox/dapr-kubernetes-operator/test/e2e/common"
|
||||
)
|
||||
|
||||
func TestDaprDeploy(t *testing.T) {
|
||||
|
@ -31,6 +32,11 @@ func TestDaprDeploy(t *testing.T) {
|
|||
test.Eventually(Deployment(test, "dapr-sidecar-injector", instance.Namespace), TestTimeoutLong).Should(
|
||||
WithTransform(ConditionStatus(appsv1.DeploymentAvailable), Equal(corev1.ConditionTrue)))
|
||||
|
||||
//
|
||||
// Dapr Application
|
||||
//
|
||||
|
||||
daprTC.ValidateDaprApp(test, instance.Namespace)
|
||||
}
|
||||
|
||||
func TestDaprDeployWrongCR(t *testing.T) {
|
||||
|
|
|
@ -5,10 +5,9 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
|
||||
olmAC "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
|
||||
"github.com/dapr-sandbox/dapr-kubernetes-operator/test/support/helm"
|
||||
|
||||
daprCP "github.com/dapr-sandbox/dapr-kubernetes-operator/internal/controller/operator"
|
||||
daprAC "github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/client/operator/clientset/versioned/typed/operator/v1alpha1"
|
||||
olmAC "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
|
||||
|
||||
daprClient "github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/client/operator/clientset/versioned"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
@ -23,16 +22,16 @@ type Client struct {
|
|||
kubernetes.Interface
|
||||
|
||||
Dapr daprClient.Interface
|
||||
DaprCP daprAC.DaprControlPlaneInterface
|
||||
Discovery discovery.DiscoveryInterface
|
||||
OLM olmAC.Interface
|
||||
Helm *helm.Helm
|
||||
|
||||
//nolint:unused
|
||||
scheme *runtime.Scheme
|
||||
config *rest.Config
|
||||
}
|
||||
|
||||
func newClient() (*Client, error) {
|
||||
func newClient(logFn func(string, ...interface{})) (*Client, error) {
|
||||
kc := os.Getenv("KUBECONFIG")
|
||||
if kc == "" {
|
||||
home := homedir.HomeDir()
|
||||
|
@ -69,12 +68,17 @@ func newClient() (*Client, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
hClient, err := helm.New(helm.WithLog(logFn))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := Client{
|
||||
Interface: kubeClient,
|
||||
Discovery: discoveryClient,
|
||||
Dapr: dClient,
|
||||
DaprCP: dClient.OperatorV1alpha1().DaprControlPlanes(daprCP.DaprControlPlaneNamespaceDefault),
|
||||
OLM: oClient,
|
||||
Helm: hClient,
|
||||
config: cfg,
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package helm
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/cli"
|
||||
"helm.sh/helm/v3/pkg/registry"
|
||||
)
|
||||
|
||||
type ReleaseOptions[T any] struct {
|
||||
Client *T
|
||||
Values map[string]interface{}
|
||||
}
|
||||
|
||||
type ConfigurationOption func(*action.Configuration)
|
||||
|
||||
func WithLog(value func(string, ...interface{})) ConfigurationOption {
|
||||
return func(opt *action.Configuration) {
|
||||
opt.Log = value
|
||||
}
|
||||
}
|
||||
|
||||
func New(options ...ConfigurationOption) (*Helm, error) {
|
||||
|
||||
settings := cli.New()
|
||||
config := action.Configuration{}
|
||||
|
||||
for _, option := range options {
|
||||
option(&config)
|
||||
}
|
||||
|
||||
registryClient, err := registry.NewClient(
|
||||
registry.ClientOptDebug(settings.Debug),
|
||||
registry.ClientOptEnableCache(true),
|
||||
registry.ClientOptWriter(os.Stdout),
|
||||
registry.ClientOptCredentialsFile(settings.RegistryConfig),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.RegistryClient = registryClient
|
||||
|
||||
err = config.Init(settings.RESTClientGetter(), settings.Namespace(), "memory", config.Log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h := Helm{
|
||||
settings: settings,
|
||||
config: &config,
|
||||
}
|
||||
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
type Helm struct {
|
||||
settings *cli.EnvSettings
|
||||
config *action.Configuration
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package helm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/utils/mergemap"
|
||||
"github.com/rs/xid"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
)
|
||||
|
||||
type InstallOption func(*ReleaseOptions[action.Install])
|
||||
|
||||
func (h *Helm) Install(ctx context.Context, chart string, options ...InstallOption) (*release.Release, error) {
|
||||
client := action.NewInstall(h.config)
|
||||
client.ReleaseName = xid.New().String()
|
||||
client.CreateNamespace = true
|
||||
client.Devel = true
|
||||
client.IncludeCRDs = true
|
||||
client.Wait = true
|
||||
client.Namespace = xid.New().String()
|
||||
client.Timeout = 10 * time.Minute
|
||||
|
||||
io := ReleaseOptions[action.Install]{
|
||||
Client: client,
|
||||
Values: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
option(&io)
|
||||
}
|
||||
|
||||
cp, err := client.ChartPathOptions.LocateChart(chart, h.settings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check chart dependencies to make sure all are present in /charts
|
||||
chartRequested, err := loader.Load(cp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client.RunWithContext(
|
||||
ctx,
|
||||
chartRequested,
|
||||
mergemap.Merge(map[string]interface{}{}, io.Values),
|
||||
)
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package helm
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/utils/mergemap"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
)
|
||||
|
||||
func WithInstallName(value string) InstallOption {
|
||||
return func(install *ReleaseOptions[action.Install]) {
|
||||
install.Client.ReleaseName = value
|
||||
}
|
||||
}
|
||||
|
||||
func WithInstallNamespace(value string) InstallOption {
|
||||
return func(install *ReleaseOptions[action.Install]) {
|
||||
install.Client.Namespace = value
|
||||
}
|
||||
}
|
||||
|
||||
func WithInstallValue(name string, value interface{}) InstallOption {
|
||||
return func(install *ReleaseOptions[action.Install]) {
|
||||
install.Values[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
func WithInstallVersion(value string) InstallOption {
|
||||
return func(install *ReleaseOptions[action.Install]) {
|
||||
install.Client.Version = value
|
||||
}
|
||||
}
|
||||
|
||||
func WithInstallValues(values map[string]interface{}) InstallOption {
|
||||
return func(install *ReleaseOptions[action.Install]) {
|
||||
install.Values = mergemap.Merge(install.Values, values)
|
||||
}
|
||||
}
|
||||
|
||||
func WithInstallTimeout(value time.Duration) InstallOption {
|
||||
return func(install *ReleaseOptions[action.Install]) {
|
||||
install.Client.Timeout = value
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package helm
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
)
|
||||
|
||||
type UninstallOption func(*ReleaseOptions[action.Uninstall])
|
||||
|
||||
func (h *Helm) Uninstall(_ context.Context, name string, options ...UninstallOption) error {
|
||||
client := action.NewUninstall(h.config)
|
||||
client.DeletionPropagation = "foreground"
|
||||
client.KeepHistory = false
|
||||
|
||||
io := ReleaseOptions[action.Uninstall]{
|
||||
Client: client,
|
||||
Values: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
option(&io)
|
||||
}
|
||||
|
||||
_, err := client.Run(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package helm
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
)
|
||||
|
||||
func WithUninstallTimeout(value time.Duration) UninstallOption {
|
||||
return func(install *ReleaseOptions[action.Uninstall]) {
|
||||
install.Client.Timeout = value
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package support
|
||||
|
||||
func NewHelm() (*Helm, error) {
|
||||
|
||||
h := Helm{}
|
||||
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
type Helm struct {
|
||||
}
|
||||
|
||||
func (h *Helm) Install() error {
|
||||
|
||||
return nil
|
||||
}
|
|
@ -3,6 +3,8 @@ package support
|
|||
import (
|
||||
"github.com/onsi/gomega"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
netv1 "k8s.io/api/networking/v1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
@ -22,3 +24,61 @@ func Deployment(t Test, name string, namespace string) func(g gomega.Gomega) (*a
|
|||
return answer, err
|
||||
}
|
||||
}
|
||||
|
||||
func Pod(t Test, name string, namespace string) func(g gomega.Gomega) (*corev1.Pod, error) {
|
||||
return func(g gomega.Gomega) (*corev1.Pod, error) {
|
||||
answer, err := t.Client().CoreV1().Pods(namespace).Get(
|
||||
t.Ctx(),
|
||||
name,
|
||||
metav1.GetOptions{},
|
||||
)
|
||||
|
||||
if k8serrors.IsNotFound(err) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return answer, err
|
||||
}
|
||||
}
|
||||
|
||||
func Service(t Test, name string, namespace string) func(g gomega.Gomega) (*corev1.Service, error) {
|
||||
return func(g gomega.Gomega) (*corev1.Service, error) {
|
||||
answer, err := t.Client().CoreV1().Services(namespace).Get(
|
||||
t.Ctx(),
|
||||
name,
|
||||
metav1.GetOptions{},
|
||||
)
|
||||
|
||||
if k8serrors.IsNotFound(err) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return answer, err
|
||||
}
|
||||
}
|
||||
|
||||
func Ingress(t Test, name string, namespace string) func(g gomega.Gomega) (*netv1.Ingress, error) {
|
||||
return func(g gomega.Gomega) (*netv1.Ingress, error) {
|
||||
answer, err := t.Client().NetworkingV1().Ingresses(namespace).Get(
|
||||
t.Ctx(),
|
||||
name,
|
||||
metav1.GetOptions{},
|
||||
)
|
||||
|
||||
if k8serrors.IsNotFound(err) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return answer, err
|
||||
}
|
||||
}
|
||||
|
||||
func ExtractFirstLoadBalancerIngressHostname() func(*netv1.Ingress) string {
|
||||
return func(in *netv1.Ingress) string {
|
||||
if len(in.Status.LoadBalancer.Ingress) != 1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return in.Status.LoadBalancer.Ingress[0].Hostname
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,14 @@ func ConditionStatus[T conditionType](conditionType T) func(any) corev1.Conditio
|
|||
}
|
||||
}
|
||||
}
|
||||
case *corev1.Pod:
|
||||
if o != nil {
|
||||
for i := range o.Status.Conditions {
|
||||
if string(o.Status.Conditions[i].Type) == string(conditionType) {
|
||||
return o.Status.Conditions[i].Status
|
||||
}
|
||||
}
|
||||
}
|
||||
case *v1alpha1.DaprControlPlane:
|
||||
if o != nil {
|
||||
for i := range o.Status.Conditions {
|
||||
|
@ -55,6 +63,14 @@ func ConditionReason[T conditionType](conditionType T) func(any) string {
|
|||
}
|
||||
}
|
||||
}
|
||||
case *corev1.Pod:
|
||||
if o != nil {
|
||||
for i := range o.Status.Conditions {
|
||||
if string(o.Status.Conditions[i].Type) == string(conditionType) {
|
||||
return o.Status.Conditions[i].Reason
|
||||
}
|
||||
}
|
||||
}
|
||||
case *v1alpha1.DaprControlPlane:
|
||||
if o != nil {
|
||||
for i := range o.Status.Conditions {
|
|
@ -1,9 +1,20 @@
|
|||
package support
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net/http"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/anthhub/forwarder"
|
||||
"k8s.io/client-go/tools/portforward"
|
||||
|
||||
"github.com/rs/xid"
|
||||
netv1 "k8s.io/api/networking/v1"
|
||||
|
||||
"github.com/dapr-sandbox/dapr-kubernetes-operator/test/support/helm"
|
||||
|
||||
"github.com/dapr-sandbox/dapr-kubernetes-operator/api/operator/v1alpha1"
|
||||
daprCP "github.com/dapr-sandbox/dapr-kubernetes-operator/internal/controller/operator"
|
||||
|
@ -17,14 +28,21 @@ import (
|
|||
"github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
//nolint:interfacebloat
|
||||
type Test interface {
|
||||
T() *testing.T
|
||||
Ctx() context.Context
|
||||
Client() *Client
|
||||
|
||||
NewTestNamespace(...Option[*corev1.Namespace]) *corev1.Namespace
|
||||
NewDaprControlPlane(spec *daprAc.DaprControlPlaneSpecApplyConfiguration) *v1alpha1.DaprControlPlane
|
||||
NewDaprControlPlane(*daprAc.DaprControlPlaneSpecApplyConfiguration) *v1alpha1.DaprControlPlane
|
||||
NewNamespacedNameDaprControlPlane(types.NamespacedName, *daprAc.DaprControlPlaneSpecApplyConfiguration) *v1alpha1.DaprControlPlane
|
||||
InstallChart(string, ...helm.InstallOption)
|
||||
SetUpIngress(string, netv1.HTTPIngressPath) *netv1.Ingress
|
||||
Forward(string, string, int) (*forwarder.Result, [][]portforward.ForwardedPort, error)
|
||||
|
||||
GET(string) func(g gomega.Gomega) (*http.Response, error)
|
||||
POST(string, string, []byte) func(g gomega.Gomega) (*http.Response, error)
|
||||
|
||||
gomega.Gomega
|
||||
}
|
||||
|
@ -44,6 +62,8 @@ var _ Option[any] = errorOption[any](nil)
|
|||
|
||||
func With(t *testing.T) Test {
|
||||
t.Helper()
|
||||
|
||||
t.Log()
|
||||
ctx := context.Background()
|
||||
if deadline, ok := t.Deadline(); ok {
|
||||
withDeadline, cancel := context.WithDeadline(ctx, deadline)
|
||||
|
@ -51,11 +71,18 @@ func With(t *testing.T) Test {
|
|||
ctx = withDeadline
|
||||
}
|
||||
|
||||
return &T{
|
||||
answer := &T{
|
||||
WithT: gomega.NewWithT(t),
|
||||
t: t,
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
answer.SetDefaultEventuallyPollingInterval(500 * time.Millisecond)
|
||||
answer.SetDefaultEventuallyTimeout(TestTimeoutLong)
|
||||
answer.SetDefaultConsistentlyDuration(500 * time.Millisecond)
|
||||
answer.SetDefaultConsistentlyDuration(TestTimeoutLong)
|
||||
|
||||
return answer
|
||||
}
|
||||
|
||||
type T struct {
|
||||
|
@ -79,7 +106,7 @@ func (t *T) Ctx() context.Context {
|
|||
|
||||
func (t *T) Client() *Client {
|
||||
t.once.Do(func() {
|
||||
c, err := newClient()
|
||||
c, err := newClient(t.t.Logf)
|
||||
if err != nil {
|
||||
t.T().Fatalf("Error creating client: %v", err)
|
||||
}
|
||||
|
@ -118,7 +145,9 @@ func (t *T) NewNamespacedNameDaprControlPlane(
|
|||
spec *daprAc.DaprControlPlaneSpecApplyConfiguration,
|
||||
) *v1alpha1.DaprControlPlane {
|
||||
|
||||
cp := t.Client().DaprCP
|
||||
t.T().Logf("Setting up Dapr ControlPlane %s in namespace %s", nn.Name, nn.Namespace)
|
||||
|
||||
cp := t.Client().Dapr.OperatorV1alpha1().DaprControlPlanes(nn.Namespace)
|
||||
|
||||
instance, err := cp.Apply(
|
||||
t.Ctx(),
|
||||
|
@ -141,3 +170,129 @@ func (t *T) NewNamespacedNameDaprControlPlane(
|
|||
|
||||
return instance
|
||||
}
|
||||
|
||||
func (t *T) InstallChart(
|
||||
chart string,
|
||||
options ...helm.InstallOption,
|
||||
) {
|
||||
allopt := make([]helm.InstallOption, 0)
|
||||
allopt = append(allopt, options...)
|
||||
allopt = append(allopt, helm.WithInstallTimeout(TestTimeoutLong))
|
||||
|
||||
release, err := t.Client().Helm.Install(
|
||||
t.Ctx(),
|
||||
chart,
|
||||
allopt...)
|
||||
|
||||
t.Expect(err).
|
||||
ToNot(gomega.HaveOccurred())
|
||||
|
||||
t.T().Cleanup(func() {
|
||||
err := t.Client().Helm.Uninstall(
|
||||
t.Ctx(),
|
||||
release.Name,
|
||||
helm.WithUninstallTimeout(TestTimeoutLong))
|
||||
|
||||
t.Expect(err).
|
||||
ToNot(gomega.HaveOccurred())
|
||||
})
|
||||
}
|
||||
|
||||
func (t *T) SetUpIngress(
|
||||
namespace string,
|
||||
path netv1.HTTPIngressPath,
|
||||
) *netv1.Ingress {
|
||||
name := xid.New().String()
|
||||
|
||||
t.T().Logf("Setting up ingress %s in namespace %s", name, namespace)
|
||||
|
||||
ing, err := t.Client().NetworkingV1().Ingresses(namespace).Create(
|
||||
t.Ctx(),
|
||||
&netv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: netv1.IngressSpec{
|
||||
Rules: []netv1.IngressRule{{
|
||||
IngressRuleValue: netv1.IngressRuleValue{
|
||||
HTTP: &netv1.HTTPIngressRuleValue{
|
||||
Paths: []netv1.HTTPIngressPath{path},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
metav1.CreateOptions{},
|
||||
)
|
||||
|
||||
t.Expect(err).
|
||||
ToNot(gomega.HaveOccurred())
|
||||
t.Expect(ing).
|
||||
ToNot(gomega.BeNil())
|
||||
|
||||
t.T().Cleanup(func() {
|
||||
t.Expect(
|
||||
t.Client().NetworkingV1().Ingresses(namespace).Delete(
|
||||
t.Ctx(),
|
||||
ing.Name,
|
||||
metav1.DeleteOptions{
|
||||
PropagationPolicy: pointer.Any(metav1.DeletePropagationForeground),
|
||||
},
|
||||
),
|
||||
).ToNot(gomega.HaveOccurred())
|
||||
})
|
||||
|
||||
return ing
|
||||
}
|
||||
|
||||
func (t *T) Forward(service string, namespace string, remotePort int) (*forwarder.Result, [][]portforward.ForwardedPort, error) {
|
||||
options := []*forwarder.Option{{
|
||||
RemotePort: remotePort,
|
||||
ServiceName: service,
|
||||
Namespace: namespace,
|
||||
}}
|
||||
|
||||
ret, err := forwarder.WithRestConfig(t.Ctx(), options, t.Client().config)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
ports, err := ret.Ready()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return ret, ports, nil
|
||||
}
|
||||
|
||||
func (t *T) GET(url string) func(g gomega.Gomega) (*http.Response, error) {
|
||||
return func(g gomega.Gomega) (*http.Response, error) {
|
||||
req, err := http.NewRequestWithContext(t.Ctx(), http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return http.DefaultClient.Do(req)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *T) POST(url string, contentType string, content []byte) func(g gomega.Gomega) (*http.Response, error) {
|
||||
return func(g gomega.Gomega) (*http.Response, error) {
|
||||
data := content
|
||||
if data == nil {
|
||||
data = []byte{}
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(t.Ctx(), http.MethodPost, url, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if contentType != "" {
|
||||
req.Header.Add("Content-Type", contentType)
|
||||
}
|
||||
|
||||
return http.DefaultClient.Do(req)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue