feat: add e2e test with a sample dapr application

This commit is contained in:
Luca Burgazzoli 2023-08-21 16:51:18 +02:00
parent 57c1c55910
commit be366533c7
22 changed files with 1696 additions and 88 deletions

View File

@ -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
run: |
./hack/scripts/deploy_olm.sh
- name: "Run Dapr Kubernetes Operator OLM e2e"
run: |
make test/e2e/olm

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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
)

1007
go.sum

File diff suppressed because it is too large Load Diff

13
hack/scripts/deploy_ingress.sh Executable file
View File

@ -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

19
hack/scripts/deploy_olm.sh Executable file
View File

@ -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

View File

@ -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",
)
}

View File

@ -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

View File

@ -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)
}

View File

@ -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) {

View File

@ -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,
}

61
test/support/helm/helm.go Normal file
View File

@ -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
}

View File

@ -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),
)
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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 {

View File

@ -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)
}
}