From a7519e30860143be03d5162c148205d0b354afab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Mon, 16 Mar 2026 09:32:05 +0100 Subject: [PATCH 01/19] feat: enable Gitea installation in e2e test cluster - Configure Gitea with NodePort on ports 30000 (HTTP) and 30022 (SSH) - Set admin credentials for test usage - Create ConfigMap with Gitea endpoint for test discovery - Disable persistence for ephemeral test environment --- hack/create-kind-cluster.sh | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/hack/create-kind-cluster.sh b/hack/create-kind-cluster.sh index a0331f0..9920c2e 100755 --- a/hack/create-kind-cluster.sh +++ b/hack/create-kind-cluster.sh @@ -164,6 +164,40 @@ function install_keda() { kubectl wait deployment --all --timeout=-1s --for=condition=Available --namespace keda } +function install_gitea() { + header_text "Installing Gitea" + + helm repo add gitea-charts https://dl.gitea.com/charts/ + helm repo update + helm install gitea gitea-charts/gitea --namespace gitea --create-namespace \ + --set service.http.type=NodePort \ + --set service.http.nodePort=30000 \ + --set service.ssh.type=NodePort \ + --set service.ssh.nodePort=30022 \ + --set gitea.admin.username=giteaadmin \ + --set gitea.admin.password=giteapass \ + --set gitea.admin.email=admin@gitea.local \ + --set persistence.enabled=false + + header_text "Waiting for Gitea to become ready" + kubectl wait deployment --all --timeout=-1s --for=condition=Available --namespace gitea + + # Get Gitea endpoint for tests + GITEA_NODE_IP=$(docker inspect kind-control-plane --format '{{.NetworkSettings.Networks.kind.IPAddress}}') + + # Create ConfigMap with Gitea endpoint info + kubectl apply -f - < Date: Mon, 16 Mar 2026 09:37:35 +0100 Subject: [PATCH 02/19] feat: add GiteaClient structure with ConfigMap discovery - Implement NewGiteaClient() that discovers Gitea endpoint from ConfigMap - Create Gitea SDK client with admin authentication - Add Gitea SDK dependency to go.mod --- go.mod | 5 +++ go.sum | 19 +++++++++++ test/utils/gitea.go | 83 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 test/utils/gitea.go diff --git a/go.mod b/go.mod index 30769a0..82757c0 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/functions-dev/func-operator go 1.25.0 require ( + code.gitea.io/sdk/gitea v0.23.2 github.com/go-git/go-git/v6 v6.0.0-20260114124804-a8db3a6585a6 github.com/go-logr/logr v1.4.3 github.com/onsi/ginkgo/v2 v2.27.5 @@ -21,6 +22,7 @@ require ( require ( cel.dev/expr v0.24.0 // indirect dario.cat/mergo v1.0.2 // indirect + github.com/42wim/httpsig v1.2.3 // indirect github.com/BurntSushi/toml v1.5.0 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect @@ -37,6 +39,7 @@ require ( github.com/coreos/go-semver v0.3.1 // indirect github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/davidmz/go-pageant v1.0.2 // indirect github.com/docker/cli v28.3.0+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.9.3 // indirect @@ -46,6 +49,7 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg/v2 v2.0.2 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect @@ -77,6 +81,7 @@ require ( github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/go.sum b/go.sum index 6148abb..9146fd2 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,12 @@ cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +code.gitea.io/sdk/gitea v0.23.2 h1:iJB1FDmLegwfwjX8gotBDHdPSbk/ZR8V9VmEJaVsJYg= +code.gitea.io/sdk/gitea v0.23.2/go.mod h1:yyF5+GhljqvA30sRDreoyHILruNiy4ASufugzYg0VHM= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs= +github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= @@ -72,6 +76,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= +github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/cli v28.3.0+incompatible h1:s+ttruVLhB5ayeuf2BciwDVxYdKi+RoUlxmwNHV3Vfo= @@ -112,6 +118,8 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= +github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo= @@ -204,6 +212,8 @@ github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJr github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -402,6 +412,9 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= @@ -409,6 +422,8 @@ golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5Z golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= @@ -416,6 +431,8 @@ golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -427,6 +444,8 @@ golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= diff --git a/test/utils/gitea.go b/test/utils/gitea.go new file mode 100644 index 0000000..a1c44f1 --- /dev/null +++ b/test/utils/gitea.go @@ -0,0 +1,83 @@ +/* +Copyright 2025. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package utils + +import ( + "context" + "fmt" + + "code.gitea.io/sdk/gitea" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" +) + +const ( + giteaAdminUser = "giteaadmin" + giteaAdminPass = "giteapass" +) + +// GiteaClient wraps the Gitea SDK client and provides helper methods +type GiteaClient struct { + client *gitea.Client + baseURL string + adminUser string + adminPass string +} + +// NewGiteaClient discovers Gitea endpoint from ConfigMap and creates client +func NewGiteaClient() (*GiteaClient, error) { + // Load kubeconfig + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + configOverrides := &clientcmd.ConfigOverrides{} + kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides) + + cfg, err := kubeConfig.ClientConfig() + if err != nil { + return nil, fmt.Errorf("failed to load kubeconfig: %w", err) + } + + // Create Kubernetes client + clientset, err := kubernetes.NewForConfig(cfg) + if err != nil { + return nil, fmt.Errorf("failed to create kubernetes client: %w", err) + } + + // Get gitea-endpoint ConfigMap + cm, err := clientset.CoreV1().ConfigMaps("kube-public").Get(context.Background(), "gitea-endpoint", metav1.GetOptions{}) + if err != nil { + return nil, fmt.Errorf("failed to get gitea-endpoint configmap: %w", err) + } + + baseURL, ok := cm.Data["http"] + if !ok { + return nil, fmt.Errorf("gitea-endpoint configmap missing 'http' key") + } + + // Create Gitea SDK client + giteaClient, err := gitea.NewClient(baseURL, gitea.SetBasicAuth(giteaAdminUser, giteaAdminPass)) + if err != nil { + return nil, fmt.Errorf("failed to create gitea client: %w", err) + } + + return &GiteaClient{ + client: giteaClient, + baseURL: baseURL, + adminUser: giteaAdminUser, + adminPass: giteaAdminPass, + }, nil +} From b013090e7be5c1a1da78fbae03418b1a910366fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Mon, 16 Mar 2026 09:42:29 +0100 Subject: [PATCH 03/19] feat: add RepositoryProvider interface for abstraction - Define RepositoryProvider interface for git server operations - Allows switching between Gitea/GitHub/GitLab without test code changes - GiteaClient will implement this interface --- test/utils/gitea.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/utils/gitea.go b/test/utils/gitea.go index a1c44f1..aec03f1 100644 --- a/test/utils/gitea.go +++ b/test/utils/gitea.go @@ -31,6 +31,22 @@ const ( giteaAdminPass = "giteapass" ) +// RepositoryProvider defines the interface for interacting with Git repository hosting providers +type RepositoryProvider interface { + // User management + CreateUser(username, password, email string) error + DeleteUser(username string) error + CreateRandomUser() (username, password, email string, err error) + + // Repository management + CreateRepo(owner, name string, private bool) (string, error) + DeleteRepo(owner, name string) error + CreateRandomRepo(owner string, private bool) (name, url string, err error) + + // Authentication + CreateAccessToken(username, password, tokenName string) (string, error) +} + // GiteaClient wraps the Gitea SDK client and provides helper methods type GiteaClient struct { client *gitea.Client From 078c281f36d5f5907ee8173115b003ec441c5849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Mon, 16 Mar 2026 09:46:00 +0100 Subject: [PATCH 04/19] feat: add user management methods to GiteaClient - Implement CreateUser() for creating users with specific credentials - Implement DeleteUser() for cleanup - Implement CreateRandomUser() for test isolation - GiteaClient now partially implements RepositoryProvider interface --- test/utils/gitea.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/utils/gitea.go b/test/utils/gitea.go index aec03f1..5798cb8 100644 --- a/test/utils/gitea.go +++ b/test/utils/gitea.go @@ -22,6 +22,7 @@ import ( "code.gitea.io/sdk/gitea" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" ) @@ -97,3 +98,35 @@ func NewGiteaClient() (*GiteaClient, error) { adminPass: giteaAdminPass, }, nil } + +// CreateUser creates a new Gitea user +func (g *GiteaClient) CreateUser(username, password, email string) error { + _, _, err := g.client.AdminCreateUser(gitea.CreateUserOption{ + Username: username, + Password: password, + Email: email, + }) + if err != nil { + return fmt.Errorf("failed to create user %s: %w", username, err) + } + return nil +} + +// DeleteUser deletes a Gitea user +func (g *GiteaClient) DeleteUser(username string) error { + _, err := g.client.AdminDeleteUser(username) + if err != nil { + return fmt.Errorf("failed to delete user %s: %w", username, err) + } + return nil +} + +// CreateRandomUser creates a user with random credentials +func (g *GiteaClient) CreateRandomUser() (username, password, email string, err error) { + username = "user-" + rand.String(8) + password = "pass-" + rand.String(8) + email = username + "@test.local" + + err = g.CreateUser(username, password, email) + return username, password, email, err +} From 676f8bd91db03960210bfb36228cc356cfda90a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Mon, 16 Mar 2026 09:48:02 +0100 Subject: [PATCH 05/19] feat: add repository management methods to GiteaClient - Implement CreateRepo() for creating repositories - Implement DeleteRepo() for cleanup - Implement CreateRandomRepo() for test isolation - Build and return repository URLs automatically --- test/utils/gitea.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/utils/gitea.go b/test/utils/gitea.go index 5798cb8..2a4ff94 100644 --- a/test/utils/gitea.go +++ b/test/utils/gitea.go @@ -130,3 +130,34 @@ func (g *GiteaClient) CreateRandomUser() (username, password, email string, err err = g.CreateUser(username, password, email) return username, password, email, err } + +// CreateRepo creates a new repository and returns its URL +func (g *GiteaClient) CreateRepo(owner, name string, private bool) (string, error) { + _, _, err := g.client.CreateRepo(gitea.CreateRepoOption{ + Name: name, + Private: private, + }) + if err != nil { + return "", fmt.Errorf("failed to create repo %s/%s: %w", owner, name, err) + } + + // Build repository URL + repoURL := fmt.Sprintf("%s/%s/%s.git", g.baseURL, owner, name) + return repoURL, nil +} + +// DeleteRepo deletes a repository +func (g *GiteaClient) DeleteRepo(owner, name string) error { + _, err := g.client.DeleteRepo(owner, name) + if err != nil { + return fmt.Errorf("failed to delete repo %s/%s: %w", owner, name, err) + } + return nil +} + +// CreateRandomRepo creates a repo with a random name +func (g *GiteaClient) CreateRandomRepo(owner string, private bool) (name, url string, err error) { + name = "repo-" + rand.String(8) + url, err = g.CreateRepo(owner, name, private) + return name, url, err +} From 73c428ee97a697ccc86faa10c6d6f0172caee9c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Mon, 16 Mar 2026 09:51:08 +0100 Subject: [PATCH 06/19] feat: add access token creation to GiteaClient - Implement CreateAccessToken() for generating user tokens - Support authentication for private repositories - GiteaClient now fully implements RepositoryProvider interface --- test/utils/gitea.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/utils/gitea.go b/test/utils/gitea.go index 2a4ff94..d68da40 100644 --- a/test/utils/gitea.go +++ b/test/utils/gitea.go @@ -161,3 +161,22 @@ func (g *GiteaClient) CreateRandomRepo(owner string, private bool) (name, url st url, err = g.CreateRepo(owner, name, private) return name, url, err } + +// CreateAccessToken creates a personal access token for a user +func (g *GiteaClient) CreateAccessToken(username, password, tokenName string) (string, error) { + // Create a client authenticated as the user + userClient, err := gitea.NewClient(g.baseURL, gitea.SetBasicAuth(username, password)) + if err != nil { + return "", fmt.Errorf("failed to create user client: %w", err) + } + + // Create token + token, _, err := userClient.CreateAccessToken(gitea.CreateAccessTokenOption{ + Name: tokenName, + }) + if err != nil { + return "", fmt.Errorf("failed to create access token for %s: %w", username, err) + } + + return token.Token, nil +} From 1b684b4553bdcb2baca2abddc0b5254131a4c8d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Mon, 16 Mar 2026 09:59:19 +0100 Subject: [PATCH 07/19] feat: add e2e helper functions for Gitea integration - Add buildAuthURL() for embedding credentials in git URLs - Add InitializeRepoWithFunction() for setting up function repos - Add CommitAndPush() for flexible commit/push with proper error handling - Helpers simplify test code and promote reusability --- test/e2e/gitea_helpers.go | 95 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 test/e2e/gitea_helpers.go diff --git a/test/e2e/gitea_helpers.go b/test/e2e/gitea_helpers.go new file mode 100644 index 0000000..f7086fc --- /dev/null +++ b/test/e2e/gitea_helpers.go @@ -0,0 +1,95 @@ +/* +Copyright 2025. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package e2e + +import ( + "fmt" + "os" + "os/exec" + "strings" + + "github.com/functions-dev/func-operator/test/utils" + "k8s.io/apimachinery/pkg/util/rand" +) + +// buildAuthURL embeds credentials into git URL for authenticated operations +func buildAuthURL(repoURL, username, password string) string { + return strings.Replace(repoURL, "http://", + fmt.Sprintf("http://%s:%s@", username, password), 1) +} + +// InitializeRepoWithFunction clones an empty Gitea repo, initializes a function, and pushes it +func InitializeRepoWithFunction(repoURL, username, password, language string) (repoDir string, err error) { + repoDir = fmt.Sprintf("%s/func-test-%s", os.TempDir(), rand.String(10)) + + // Build authenticated URL + authURL := buildAuthURL(repoURL, username, password) + + // Clone empty repo + cmd := exec.Command("git", "clone", authURL, repoDir) + if _, err = utils.Run(cmd); err != nil { + return "", fmt.Errorf("failed to clone repo: %w", err) + } + + // Initialize function + cmd = exec.Command("func", "init", "-l", language) + cmd.Dir = repoDir + if _, err = utils.Run(cmd); err != nil { + return "", fmt.Errorf("failed to init function: %w", err) + } + + // Commit and push + if err = exec.Command("git", "-C", repoDir, "add", ".").Run(); err != nil { + return "", fmt.Errorf("failed to git add: %w", err) + } + if err = exec.Command("git", "-C", repoDir, "commit", "-m", "Initial function").Run(); err != nil { + return "", fmt.Errorf("failed to git commit: %w", err) + } + if err = exec.Command("git", "-C", repoDir, "push").Run(); err != nil { + return "", fmt.Errorf("failed to push initial commit: %w", err) + } + + return repoDir, nil +} + +// CommitAndPush commits and pushes specified files with a custom message +// Requires at least one file to be specified +func CommitAndPush(repoDir string, msg string, file string, otherFiles ...string) error { + // Add first file + if err := exec.Command("git", "-C", repoDir, "add", file).Run(); err != nil { + return fmt.Errorf("failed to git add %s: %w", file, err) + } + + // Add other files if provided + for _, f := range otherFiles { + if err := exec.Command("git", "-C", repoDir, "add", f).Run(); err != nil { + return fmt.Errorf("failed to git add %s: %w", f, err) + } + } + + // Commit + if err := exec.Command("git", "-C", repoDir, "commit", "-m", msg).Run(); err != nil { + return fmt.Errorf("failed to git commit: %w", err) + } + + // Push + if err := exec.Command("git", "-C", repoDir, "push").Run(); err != nil { + return fmt.Errorf("failed to push: %w", err) + } + + return nil +} \ No newline at end of file From d2f98e20040754c2cd7a4ea64f658f709ef1dcb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Mon, 16 Mar 2026 10:04:18 +0100 Subject: [PATCH 08/19] feat: initialize RepositoryProvider in e2e test suite - Add repoProvider variable using RepositoryProvider interface - Initialize with GiteaClient in BeforeSuite - Verify provider is not nil before tests run - Interface allows easy switching to other providers --- test/e2e/e2e_suite_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index 1604222..a6ebafd 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -30,6 +30,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" functionsdevv1alpha1 "github.com/functions-dev/func-operator/api/v1alpha1" + "github.com/functions-dev/func-operator/test/utils" ) var ( @@ -38,6 +39,8 @@ var ( registry string registryInsecure bool + + repoProvider utils.RepositoryProvider ) // TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated, @@ -79,4 +82,9 @@ var _ = BeforeSuite(func() { if sec := os.Getenv("REGISTRY_INSECURE"); strings.ToLower(sec) == "true" { registryInsecure = true } + + // Initialize repository provider (Gitea) + repoProvider, err = utils.NewGiteaClient() + Expect(err).NotTo(HaveOccurred()) + Expect(repoProvider).NotTo(BeNil()) }) From a0e4232494c6e77b40d1a868b28d8316ba563ae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Mon, 16 Mar 2026 10:28:55 +0100 Subject: [PATCH 09/19] feat: migrate func_deploy_test.go to use RepositoryProvider - Replace GitHub cloning with RepositoryProvider workflow in BeforeEach - Use InitializeRepoWithFunction() helper for repo setup - Update Function specs to use repository provider URLs - Add proper cleanup of repository resources in AfterEach - Tests now isolated and independent of external GitHub - Keep abstraction by using generic terminology --- test/e2e/func_deploy_test.go | 80 +++++++++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 15 deletions(-) diff --git a/test/e2e/func_deploy_test.go b/test/e2e/func_deploy_test.go index 699f259..bb59267 100644 --- a/test/e2e/func_deploy_test.go +++ b/test/e2e/func_deploy_test.go @@ -29,7 +29,6 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/rand" ) var _ = Describe("Operator", Ordered, func() { @@ -38,26 +37,39 @@ var _ = Describe("Operator", Ordered, func() { SetDefaultEventuallyPollingInterval(time.Second) Context("with a deployed function", func() { - var tempDir string + var username string + var password string + var repoName string + var repoURL string + var repoDir string var functionName, functionNamespace string BeforeEach(func() { var err error - // deploy function - tempDir = fmt.Sprintf("%s/func-operator-e2e-%s", os.TempDir(), rand.String(10)) + + // Create Gitea resources + username, password, _, err = repoProvider.CreateRandomUser() + Expect(err).NotTo(HaveOccurred()) + + repoName, repoURL, err = repoProvider.CreateRandomRepo(username, false) Expect(err).NotTo(HaveOccurred()) - cmd := exec.Command("git", "clone", "https://github.com/creydr/func-go-hello-world", tempDir) - _, err = utils.Run(cmd) + // Initialize repository with function code + repoDir, err = InitializeRepoWithFunction(repoURL, username, password, "go") Expect(err).NotTo(HaveOccurred()) - cmd = exec.Command("func", "deploy", - "--path", tempDir, + // Deploy function using func CLI + cmd := exec.Command("func", "deploy", + "--path", repoDir, "--registry", registry, "--registry-insecure", strconv.FormatBool(registryInsecure)) out, err := utils.Run(cmd) Expect(err).NotTo(HaveOccurred()) _, _ = fmt.Fprint(GinkgoWriter, out) + + // Commit func.yaml changes + err = CommitAndPush(repoDir, "Update func.yaml after deploy", "func.yaml") + Expect(err).NotTo(HaveOccurred()) }) AfterEach(func() { @@ -83,15 +95,30 @@ var _ = Describe("Operator", Ordered, func() { } } - if tempDir != "" { - cmd := exec.Command("func", "delete", "--path", tempDir) + // Cleanup func deployment + if repoDir != "" { + cmd := exec.Command("func", "delete", "--path", repoDir) _, err := utils.Run(cmd) Expect(err).NotTo(HaveOccurred()) } - cmd := exec.Command("kubectl", "delete", "function", functionName, "-n", functionNamespace, "--ignore-not-found") - _, err := utils.Run(cmd) - Expect(err).NotTo(HaveOccurred()) + // Cleanup repository resources + if repoDir != "" { + _ = os.RemoveAll(repoDir) + } + if username != "" && repoName != "" { + _ = repoProvider.DeleteRepo(username, repoName) + } + if username != "" { + _ = repoProvider.DeleteUser(username) + } + + // Cleanup function resource + if functionName != "" { + cmd := exec.Command("kubectl", "delete", "function", functionName, "-n", functionNamespace, "--ignore-not-found") + _, err := utils.Run(cmd) + Expect(err).NotTo(HaveOccurred()) + } }) It("should mark the function as ready", func() { @@ -103,7 +130,7 @@ var _ = Describe("Operator", Ordered, func() { }, Spec: functionsdevv1alpha1.FunctionSpec{ Source: functionsdevv1alpha1.FunctionSpecSource{ - RepositoryURL: "https://github.com/creydr/func-go-hello-world", + RepositoryURL: repoURL, }, Registry: functionsdevv1alpha1.FunctionSpecRegistry{ Path: registry, @@ -137,9 +164,32 @@ var _ = Describe("Operator", Ordered, func() { }) }) Context("with a not yet deployed function", func() { + var username string + var repoName string + var repoURL string var functionName, functionNamespace string + BeforeEach(func() { + var err error + + // Create repository but don't deploy + username, _, _, err = repoProvider.CreateRandomUser() + Expect(err).NotTo(HaveOccurred()) + + repoName, repoURL, err = repoProvider.CreateRandomRepo(username, false) + Expect(err).NotTo(HaveOccurred()) + }) + AfterEach(func() { + // Cleanup repository resources + if username != "" && repoName != "" { + _ = repoProvider.DeleteRepo(username, repoName) + } + if username != "" { + _ = repoProvider.DeleteUser(username) + } + + // Cleanup function resource cmd := exec.Command("kubectl", "delete", "function", functionName, "-n", functionNamespace, "--ignore-not-found") _, err := utils.Run(cmd) Expect(err).NotTo(HaveOccurred()) @@ -154,7 +204,7 @@ var _ = Describe("Operator", Ordered, func() { }, Spec: functionsdevv1alpha1.FunctionSpec{ Source: functionsdevv1alpha1.FunctionSpecSource{ - RepositoryURL: "https://github.com/creydr/func-go-hello-world", + RepositoryURL: repoURL, }, Registry: functionsdevv1alpha1.FunctionSpecRegistry{ Path: registry, From b123d07e0cdcbf389560eb9fe9e26a96b74dc60f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Mon, 16 Mar 2026 10:43:00 +0100 Subject: [PATCH 10/19] refactor: use DeferCleanup for automatic resource cleanup - Update RepositoryProvider interface to return cleanup functions - Implement cleanup functions in CreateUser, CreateRepo, etc. - Use Ginkgo's DeferCleanup for automatic cleanup in tests - Simplify AfterEach blocks to only handle failure logging - Much cleaner and less error-prone test code --- test/e2e/func_deploy_test.go | 60 +++++++++++++----------------------- test/utils/gitea.go | 46 +++++++++++++++------------ 2 files changed, 48 insertions(+), 58 deletions(-) diff --git a/test/e2e/func_deploy_test.go b/test/e2e/func_deploy_test.go index bb59267..3afa482 100644 --- a/test/e2e/func_deploy_test.go +++ b/test/e2e/func_deploy_test.go @@ -37,9 +37,6 @@ var _ = Describe("Operator", Ordered, func() { SetDefaultEventuallyPollingInterval(time.Second) Context("with a deployed function", func() { - var username string - var password string - var repoName string var repoURL string var repoDir string var functionName, functionNamespace string @@ -47,16 +44,19 @@ var _ = Describe("Operator", Ordered, func() { BeforeEach(func() { var err error - // Create Gitea resources - username, password, _, err = repoProvider.CreateRandomUser() + // Create repository provider resources with automatic cleanup + username, password, _, cleanup, err := repoProvider.CreateRandomUser() Expect(err).NotTo(HaveOccurred()) + DeferCleanup(cleanup) - repoName, repoURL, err = repoProvider.CreateRandomRepo(username, false) + _, repoURL, cleanup, err = repoProvider.CreateRandomRepo(username, false) Expect(err).NotTo(HaveOccurred()) + DeferCleanup(cleanup) // Initialize repository with function code repoDir, err = InitializeRepoWithFunction(repoURL, username, password, "go") Expect(err).NotTo(HaveOccurred()) + DeferCleanup(os.RemoveAll, repoDir) // Deploy function using func CLI cmd := exec.Command("func", "deploy", @@ -67,6 +67,12 @@ var _ = Describe("Operator", Ordered, func() { Expect(err).NotTo(HaveOccurred()) _, _ = fmt.Fprint(GinkgoWriter, out) + // Cleanup func deployment + DeferCleanup(func() { + cmd := exec.Command("func", "delete", "--path", repoDir) + _, _ = utils.Run(cmd) + }) + // Commit func.yaml changes err = CommitAndPush(repoDir, "Update func.yaml after deploy", "func.yaml") Expect(err).NotTo(HaveOccurred()) @@ -95,24 +101,6 @@ var _ = Describe("Operator", Ordered, func() { } } - // Cleanup func deployment - if repoDir != "" { - cmd := exec.Command("func", "delete", "--path", repoDir) - _, err := utils.Run(cmd) - Expect(err).NotTo(HaveOccurred()) - } - - // Cleanup repository resources - if repoDir != "" { - _ = os.RemoveAll(repoDir) - } - if username != "" && repoName != "" { - _ = repoProvider.DeleteRepo(username, repoName) - } - if username != "" { - _ = repoProvider.DeleteUser(username) - } - // Cleanup function resource if functionName != "" { cmd := exec.Command("kubectl", "delete", "function", functionName, "-n", functionNamespace, "--ignore-not-found") @@ -164,8 +152,6 @@ var _ = Describe("Operator", Ordered, func() { }) }) Context("with a not yet deployed function", func() { - var username string - var repoName string var repoURL string var functionName, functionNamespace string @@ -173,26 +159,22 @@ var _ = Describe("Operator", Ordered, func() { var err error // Create repository but don't deploy - username, _, _, err = repoProvider.CreateRandomUser() + username, _, _, cleanup, err := repoProvider.CreateRandomUser() Expect(err).NotTo(HaveOccurred()) + DeferCleanup(cleanup) - repoName, repoURL, err = repoProvider.CreateRandomRepo(username, false) + _, repoURL, cleanup, err = repoProvider.CreateRandomRepo(username, false) Expect(err).NotTo(HaveOccurred()) + DeferCleanup(cleanup) }) AfterEach(func() { - // Cleanup repository resources - if username != "" && repoName != "" { - _ = repoProvider.DeleteRepo(username, repoName) - } - if username != "" { - _ = repoProvider.DeleteUser(username) - } - // Cleanup function resource - cmd := exec.Command("kubectl", "delete", "function", functionName, "-n", functionNamespace, "--ignore-not-found") - _, err := utils.Run(cmd) - Expect(err).NotTo(HaveOccurred()) + if functionName != "" { + cmd := exec.Command("kubectl", "delete", "function", functionName, "-n", functionNamespace, "--ignore-not-found") + _, err := utils.Run(cmd) + Expect(err).NotTo(HaveOccurred()) + } }) It("should mark the function as not ready", func() { diff --git a/test/utils/gitea.go b/test/utils/gitea.go index d68da40..e4311df 100644 --- a/test/utils/gitea.go +++ b/test/utils/gitea.go @@ -35,14 +35,14 @@ const ( // RepositoryProvider defines the interface for interacting with Git repository hosting providers type RepositoryProvider interface { // User management - CreateUser(username, password, email string) error + CreateUser(username, password, email string) (cleanup func(), err error) DeleteUser(username string) error - CreateRandomUser() (username, password, email string, err error) + CreateRandomUser() (username, password, email string, cleanup func(), err error) // Repository management - CreateRepo(owner, name string, private bool) (string, error) + CreateRepo(owner, name string, private bool) (url string, cleanup func(), err error) DeleteRepo(owner, name string) error - CreateRandomRepo(owner string, private bool) (name, url string, err error) + CreateRandomRepo(owner string, private bool) (name, url string, cleanup func(), err error) // Authentication CreateAccessToken(username, password, tokenName string) (string, error) @@ -100,16 +100,20 @@ func NewGiteaClient() (*GiteaClient, error) { } // CreateUser creates a new Gitea user -func (g *GiteaClient) CreateUser(username, password, email string) error { - _, _, err := g.client.AdminCreateUser(gitea.CreateUserOption{ +func (g *GiteaClient) CreateUser(username, password, email string) (cleanup func(), err error) { + _, _, err = g.client.AdminCreateUser(gitea.CreateUserOption{ Username: username, Password: password, Email: email, }) if err != nil { - return fmt.Errorf("failed to create user %s: %w", username, err) + return nil, fmt.Errorf("failed to create user %s: %w", username, err) } - return nil + + cleanup = func() { + _ = g.DeleteUser(username) + } + return cleanup, nil } // DeleteUser deletes a Gitea user @@ -122,28 +126,32 @@ func (g *GiteaClient) DeleteUser(username string) error { } // CreateRandomUser creates a user with random credentials -func (g *GiteaClient) CreateRandomUser() (username, password, email string, err error) { +func (g *GiteaClient) CreateRandomUser() (username, password, email string, cleanup func(), err error) { username = "user-" + rand.String(8) password = "pass-" + rand.String(8) email = username + "@test.local" - err = g.CreateUser(username, password, email) - return username, password, email, err + cleanup, err = g.CreateUser(username, password, email) + return username, password, email, cleanup, err } // CreateRepo creates a new repository and returns its URL -func (g *GiteaClient) CreateRepo(owner, name string, private bool) (string, error) { - _, _, err := g.client.CreateRepo(gitea.CreateRepoOption{ +func (g *GiteaClient) CreateRepo(owner, name string, private bool) (url string, cleanup func(), err error) { + _, _, err = g.client.CreateRepo(gitea.CreateRepoOption{ Name: name, Private: private, }) if err != nil { - return "", fmt.Errorf("failed to create repo %s/%s: %w", owner, name, err) + return "", nil, fmt.Errorf("failed to create repo %s/%s: %w", owner, name, err) } // Build repository URL - repoURL := fmt.Sprintf("%s/%s/%s.git", g.baseURL, owner, name) - return repoURL, nil + url = fmt.Sprintf("%s/%s/%s.git", g.baseURL, owner, name) + + cleanup = func() { + _ = g.DeleteRepo(owner, name) + } + return url, cleanup, nil } // DeleteRepo deletes a repository @@ -156,10 +164,10 @@ func (g *GiteaClient) DeleteRepo(owner, name string) error { } // CreateRandomRepo creates a repo with a random name -func (g *GiteaClient) CreateRandomRepo(owner string, private bool) (name, url string, err error) { +func (g *GiteaClient) CreateRandomRepo(owner string, private bool) (name, url string, cleanup func(), err error) { name = "repo-" + rand.String(8) - url, err = g.CreateRepo(owner, name, private) - return name, url, err + url, cleanup, err = g.CreateRepo(owner, name, private) + return name, url, cleanup, err } // CreateAccessToken creates a personal access token for a user From c53a5cdcf3887e6448027a713710edc731e644e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Mon, 16 Mar 2026 11:00:28 +0100 Subject: [PATCH 11/19] feat: migrate bundle_test.go to use RepositoryProvider - Update helper functions to accept and use repoURL parameter - Replace GitHub URLs with RepositoryProvider workflow in setup functions - Track repoURLs alongside namespaces for all tests - Use DeferCleanup for automatic resource cleanup - Tests now use in-cluster repository provider for all git operations - Remove unused rand import --- test/e2e/bundle_test.go | 87 ++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 41 deletions(-) diff --git a/test/e2e/bundle_test.go b/test/e2e/bundle_test.go index 726c3d0..e315ca9 100644 --- a/test/e2e/bundle_test.go +++ b/test/e2e/bundle_test.go @@ -29,7 +29,6 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/rand" ) // The bundle e2e test run with a dedicated build tag to not infer with the other tests, as the bundle offers different @@ -41,6 +40,7 @@ var _ = Describe("Bundle", Label("bundle"), Ordered, func() { bundleImage string // set in BeforeAll namespaces []string + repoURLs []string ) SetDefaultEventuallyTimeout(5 * time.Minute) @@ -81,7 +81,7 @@ var _ = Describe("Bundle", Label("bundle"), Ordered, func() { Context("with OwnNamespace installMode", func() { BeforeAll(func() { - namespaces = createMultipleNamespaceAndDeployFunction(2) + namespaces, repoURLs = createMultipleNamespaceAndDeployFunction(2) By("Installing the operator into " + namespaces[0]) out, err := utils.OperatorSdkRun("run", "bundle", @@ -109,17 +109,17 @@ var _ = Describe("Bundle", Label("bundle"), Ordered, func() { }) It("should reconcile function in own namespace", func() { - CreateFunctionAndWaitForReady(namespaces[0]) + CreateFunctionAndWaitForReady(namespaces[0], repoURLs[0]) }) It("should not reconcile function in other namespace", func() { - CreateFunctionAndWaitForConsistentlyNotReconciled(namespaces[1]) + CreateFunctionAndWaitForConsistentlyNotReconciled(namespaces[1], repoURLs[1]) }) }) Context("with SingleNamespace installMode", func() { BeforeAll(func() { By("Setting up test namespaces") - namespaces = createMultipleNamespaceAndDeployFunction(3) + namespaces, repoURLs = createMultipleNamespaceAndDeployFunction(3) By("Installing the operator into " + namespaces[0] + " for " + namespaces[1]) out, err := utils.OperatorSdkRun("run", "bundle", @@ -147,18 +147,18 @@ var _ = Describe("Bundle", Label("bundle"), Ordered, func() { }) It("should reconcile function in dedicated namespace", func() { - CreateFunctionAndWaitForReady(namespaces[1]) + CreateFunctionAndWaitForReady(namespaces[1], repoURLs[1]) }) It("should not reconcile function in other namespace", func() { - CreateFunctionAndWaitForConsistentlyNotReconciled(namespaces[0]) - CreateFunctionAndWaitForConsistentlyNotReconciled(namespaces[2]) + CreateFunctionAndWaitForConsistentlyNotReconciled(namespaces[0], repoURLs[0]) + CreateFunctionAndWaitForConsistentlyNotReconciled(namespaces[2], repoURLs[2]) }) }) Context("with MultiNamespace installMode", func() { BeforeAll(func() { By("Setting up test namespaces") - namespaces = createMultipleNamespaceAndDeployFunction(4) + namespaces, repoURLs = createMultipleNamespaceAndDeployFunction(4) By("Installing the operator into " + namespaces[0] + " for " + namespaces[1] + " and " + namespaces[2]) out, err := utils.OperatorSdkRun("run", "bundle", @@ -186,19 +186,19 @@ var _ = Describe("Bundle", Label("bundle"), Ordered, func() { }) It("should reconcile function in dedicated namespaces", func() { - CreateFunctionAndWaitForReady(namespaces[1]) - CreateFunctionAndWaitForReady(namespaces[2]) + CreateFunctionAndWaitForReady(namespaces[1], repoURLs[1]) + CreateFunctionAndWaitForReady(namespaces[2], repoURLs[2]) }) It("should not reconcile function in other namespace", func() { CreateFunctionAndWaitForConsistentlyNotReconciled(namespaces[0]) - CreateFunctionAndWaitForConsistentlyNotReconciled(namespaces[3]) + CreateFunctionAndWaitForConsistentlyNotReconciled(namespaces[3], repoURLs[3]) }) }) Context("with two instances with SingleNamespace installMode installed into two distinct namespaces", func() { BeforeAll(func() { By("Setting up test namespaces") - namespaces = createMultipleNamespaceAndDeployFunction(4) + namespaces, repoURLs = createMultipleNamespaceAndDeployFunction(4) By("Installing the operator into " + namespaces[0] + " for " + namespaces[1]) out, err := utils.OperatorSdkRun("run", "bundle", @@ -243,19 +243,19 @@ var _ = Describe("Bundle", Label("bundle"), Ordered, func() { }) It("should reconcile function in dedicated namespaces", func() { - CreateFunctionAndWaitForReady(namespaces[1]) - CreateFunctionAndWaitForReady(namespaces[3]) + CreateFunctionAndWaitForReady(namespaces[1], repoURLs[1]) + CreateFunctionAndWaitForReady(namespaces[3], repoURLs[3]) }) It("should not reconcile function in other namespace", func() { - CreateFunctionAndWaitForConsistentlyNotReconciled(namespaces[0]) - CreateFunctionAndWaitForConsistentlyNotReconciled(namespaces[2]) + CreateFunctionAndWaitForConsistentlyNotReconciled(namespaces[0], repoURLs[0]) + CreateFunctionAndWaitForConsistentlyNotReconciled(namespaces[2], repoURLs[2]) }) }) Context("with AllNamespace installMode", func() { BeforeAll(func() { By("Setting up test namespaces") - namespaces = createMultipleNamespaceAndDeployFunction(2) + namespaces, repoURLs = createMultipleNamespaceAndDeployFunction(2) By("Installing the operator into " + namespaces[0]) out, err := utils.OperatorSdkRun("run", "bundle", @@ -283,13 +283,13 @@ var _ = Describe("Bundle", Label("bundle"), Ordered, func() { }) It("should reconcile function in all namespaces", func() { - CreateFunctionAndWaitForReady(namespaces[0]) - CreateFunctionAndWaitForReady(namespaces[1]) + CreateFunctionAndWaitForReady(namespaces[0], repoURLs[0]) + CreateFunctionAndWaitForReady(namespaces[1], repoURLs[1]) }) }) }) -func CreateFunctionAndWaitForReady(namespace string) { +func CreateFunctionAndWaitForReady(namespace, repoURL string) { // Create a Function resource function := &functionsdevv1alpha1.Function{ ObjectMeta: metav1.ObjectMeta{ @@ -298,7 +298,7 @@ func CreateFunctionAndWaitForReady(namespace string) { }, Spec: functionsdevv1alpha1.FunctionSpec{ Source: functionsdevv1alpha1.FunctionSpecSource{ - RepositoryURL: "https://github.com/creydr/func-go-hello-world", + RepositoryURL: repoURL, }, Registry: functionsdevv1alpha1.FunctionSpecRegistry{ Path: registry, @@ -327,7 +327,7 @@ func CreateFunctionAndWaitForReady(namespace string) { Eventually(funcBecomeReady, 5*time.Minute).Should(Succeed()) } -func CreateFunctionAndWaitForConsistentlyNotReconciled(namespace string) { +func CreateFunctionAndWaitForConsistentlyNotReconciled(namespace, repoURL string) { // Create a Function resource function := &functionsdevv1alpha1.Function{ ObjectMeta: metav1.ObjectMeta{ @@ -336,7 +336,7 @@ func CreateFunctionAndWaitForConsistentlyNotReconciled(namespace string) { }, Spec: functionsdevv1alpha1.FunctionSpec{ Source: functionsdevv1alpha1.FunctionSpecSource{ - RepositoryURL: "https://github.com/creydr/func-go-hello-world", + RepositoryURL: repoURL, }, Registry: functionsdevv1alpha1.FunctionSpecRegistry{ Path: registry, @@ -360,19 +360,28 @@ func CreateFunctionAndWaitForConsistentlyNotReconciled(namespace string) { Consistently(funcNotReconciled, time.Minute).Should(Succeed()) } -func createNamespaceAndDeployFunction() string { - ns, err := utils.GetTestNamespace() +func createNamespaceAndDeployFunction() (ns, repoURL string) { + var err error + ns, err = utils.GetTestNamespace() + Expect(err).NotTo(HaveOccurred()) + + // Create repository provider resources + username, password, _, cleanup, err := repoProvider.CreateRandomUser() Expect(err).NotTo(HaveOccurred()) + DeferCleanup(cleanup) - tempDir := fmt.Sprintf("%s/func-operator-e2e-%s", os.TempDir(), rand.String(10)) + _, repoURL, cleanup, err = repoProvider.CreateRandomRepo(username, false) Expect(err).NotTo(HaveOccurred()) + DeferCleanup(cleanup) - cmd := exec.Command("git", "clone", "https://github.com/creydr/func-go-hello-world", tempDir) - _, err = utils.Run(cmd) + // Initialize repo with function code + repoDir, err := InitializeRepoWithFunction(repoURL, username, password, "go") Expect(err).NotTo(HaveOccurred()) + DeferCleanup(os.RemoveAll, repoDir) - cmd = exec.Command("func", "deploy", - "--path", tempDir, + // Deploy function + cmd := exec.Command("func", "deploy", + "--path", repoDir, "--registry", registry, "--registry-insecure", strconv.FormatBool(registryInsecure), "--namespace", ns) @@ -380,24 +389,20 @@ func createNamespaceAndDeployFunction() string { Expect(err).NotTo(HaveOccurred()) _, _ = fmt.Fprint(GinkgoWriter, out) - // cleanup the repo to not run into resource issues - cmd = exec.Command("rm", "-rf", tempDir) - _, err = utils.Run(cmd) - Expect(err).NotTo(HaveOccurred()) - - return ns + return ns, repoURL } // createMultipleNamespaceAndDeployFunction creates multiple namespaces with functions -func createMultipleNamespaceAndDeployFunction(count int) []string { - namespaces := make([]string, count) +func createMultipleNamespaceAndDeployFunction(count int) (namespaces, repoURLs []string) { + namespaces = make([]string, count) + repoURLs = make([]string, count) for i := 0; i < count; i++ { // parallelizing this via goroutines seems to lead to resource issues, therefore keeping it sequential - namespaces[i] = createNamespaceAndDeployFunction() + namespaces[i], repoURLs[i] = createNamespaceAndDeployFunction() } - return namespaces + return namespaces, repoURLs } func cleanupNamespaces(namespaces []string) { From b5b2c1e4d36e46c0d01ef81830cfa1fe3d46fce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Mon, 16 Mar 2026 11:18:51 +0100 Subject: [PATCH 12/19] refactor: use TestNamespace struct for better API - Define TestNamespace struct with Name and RepoURL fields - Replace separate namespace and repoURL slices with single slice - Update all functions to accept/return TestNamespace - Prevents misalignment of parallel slices - Much safer and cleaner API --- test/e2e/bundle_test.go | 148 +++++++++++++++++++++------------------- 1 file changed, 76 insertions(+), 72 deletions(-) diff --git a/test/e2e/bundle_test.go b/test/e2e/bundle_test.go index e315ca9..436e8dc 100644 --- a/test/e2e/bundle_test.go +++ b/test/e2e/bundle_test.go @@ -34,13 +34,18 @@ import ( // The bundle e2e test run with a dedicated build tag to not infer with the other tests, as the bundle offers different // installation modes and also can make the operator to run in multiple namespaces +// TestNamespace represents a test namespace with its associated repository +type TestNamespace struct { + Name string + RepoURL string +} + var _ = Describe("Bundle", Label("bundle"), Ordered, func() { var ( bundleImage string // set in BeforeAll - namespaces []string - repoURLs []string + testNamespaces []TestNamespace ) SetDefaultEventuallyTimeout(5 * time.Minute) @@ -56,9 +61,9 @@ var _ = Describe("Bundle", Label("bundle"), Ordered, func() { if specReport.Failed() { // collect logs in case it failed By("Collecting logs from deployed operators") - for _, ns := range namespaces { - By("Logs from operator in namespace " + ns) - cmd := exec.Command("kubectl", "logs", "-l", "control-plane=controller-manager", "--namespace", ns) + for _, testNs := range testNamespaces { + By("Logs from operator in namespace " + testNs.Name) + cmd := exec.Command("kubectl", "logs", "-l", "control-plane=controller-manager", "--namespace", testNs.Name) controllerLogs, err := utils.Run(cmd) if err == nil { _, _ = fmt.Fprintf(GinkgoWriter, "Controller logs:\n %s", controllerLogs) @@ -81,11 +86,11 @@ var _ = Describe("Bundle", Label("bundle"), Ordered, func() { Context("with OwnNamespace installMode", func() { BeforeAll(func() { - namespaces, repoURLs = createMultipleNamespaceAndDeployFunction(2) + testNamespaces = createMultipleNamespaceAndDeployFunction(2) - By("Installing the operator into " + namespaces[0]) + By("Installing the operator into " + testNamespaces[0].Name) out, err := utils.OperatorSdkRun("run", "bundle", - "--namespace", namespaces[0], + "--namespace", testNamespaces[0].Name, "--install-mode", "OwnNamespace", fmt.Sprintf("--skip-tls-verify=%v", registryInsecure), bundleImage) @@ -99,32 +104,32 @@ var _ = Describe("Bundle", Label("bundle"), Ordered, func() { By("Uninstalling the operator") out, err := utils.OperatorSdkRun("cleanup", "func-operator", - "--namespace", namespaces[0]) + "--namespace", testNamespaces[0].Name) Expect(err).NotTo(HaveOccurred()) _, _ = fmt.Fprint(GinkgoWriter, out) By("Cleanup resources") - cleanupNamespaces(namespaces) + cleanupNamespaces(testNamespaces) } }) It("should reconcile function in own namespace", func() { - CreateFunctionAndWaitForReady(namespaces[0], repoURLs[0]) + CreateFunctionAndWaitForReady(testNamespaces[0]) }) It("should not reconcile function in other namespace", func() { - CreateFunctionAndWaitForConsistentlyNotReconciled(namespaces[1], repoURLs[1]) + CreateFunctionAndWaitForConsistentlyNotReconciled(testNamespaces[1]) }) }) Context("with SingleNamespace installMode", func() { BeforeAll(func() { By("Setting up test namespaces") - namespaces, repoURLs = createMultipleNamespaceAndDeployFunction(3) + testNamespaces = createMultipleNamespaceAndDeployFunction(3) - By("Installing the operator into " + namespaces[0] + " for " + namespaces[1]) + By("Installing the operator into " + testNamespaces[0].Name + " for " + testNamespaces[1].Name) out, err := utils.OperatorSdkRun("run", "bundle", - "--namespace", namespaces[0], - "--install-mode", fmt.Sprintf("SingleNamespace=%s", namespaces[1]), + "--namespace", testNamespaces[0].Name, + "--install-mode", fmt.Sprintf("SingleNamespace=%s", testNamespaces[1].Name), fmt.Sprintf("--skip-tls-verify=%v", registryInsecure), bundleImage) Expect(err).NotTo(HaveOccurred()) @@ -137,33 +142,33 @@ var _ = Describe("Bundle", Label("bundle"), Ordered, func() { By("Uninstalling the operator") out, err := utils.OperatorSdkRun("cleanup", "func-operator", - "--namespace", namespaces[0]) + "--namespace", testNamespaces[0].Name) Expect(err).NotTo(HaveOccurred()) _, _ = fmt.Fprint(GinkgoWriter, out) By("Cleanup resources") - cleanupNamespaces(namespaces) + cleanupNamespaces(testNamespaces) } }) It("should reconcile function in dedicated namespace", func() { - CreateFunctionAndWaitForReady(namespaces[1], repoURLs[1]) + CreateFunctionAndWaitForReady(testNamespaces[1]) }) It("should not reconcile function in other namespace", func() { - CreateFunctionAndWaitForConsistentlyNotReconciled(namespaces[0], repoURLs[0]) - CreateFunctionAndWaitForConsistentlyNotReconciled(namespaces[2], repoURLs[2]) + CreateFunctionAndWaitForConsistentlyNotReconciled(testNamespaces[0]) + CreateFunctionAndWaitForConsistentlyNotReconciled(testNamespaces[2]) }) }) Context("with MultiNamespace installMode", func() { BeforeAll(func() { By("Setting up test namespaces") - namespaces, repoURLs = createMultipleNamespaceAndDeployFunction(4) + testNamespaces = createMultipleNamespaceAndDeployFunction(4) - By("Installing the operator into " + namespaces[0] + " for " + namespaces[1] + " and " + namespaces[2]) + By("Installing the operator into " + testNamespaces[0].Name + " for " + testNamespaces[1].Name + " and " + testNamespaces[2].Name) out, err := utils.OperatorSdkRun("run", "bundle", - "--namespace", namespaces[0], - "--install-mode", fmt.Sprintf("MultiNamespace=%s,%s", namespaces[1], namespaces[2]), + "--namespace", testNamespaces[0].Name, + "--install-mode", fmt.Sprintf("MultiNamespace=%s,%s", testNamespaces[1].Name, testNamespaces[2].Name), fmt.Sprintf("--skip-tls-verify=%v", registryInsecure), bundleImage) Expect(err).NotTo(HaveOccurred()) @@ -176,43 +181,43 @@ var _ = Describe("Bundle", Label("bundle"), Ordered, func() { By("Uninstalling the operator") out, err := utils.OperatorSdkRun("cleanup", "func-operator", - "--namespace", namespaces[0]) + "--namespace", testNamespaces[0].Name) Expect(err).NotTo(HaveOccurred()) _, _ = fmt.Fprint(GinkgoWriter, out) By("Cleanup resources") - cleanupNamespaces(namespaces) + cleanupNamespaces(testNamespaces) } }) It("should reconcile function in dedicated namespaces", func() { - CreateFunctionAndWaitForReady(namespaces[1], repoURLs[1]) - CreateFunctionAndWaitForReady(namespaces[2], repoURLs[2]) + CreateFunctionAndWaitForReady(testNamespaces[1]) + CreateFunctionAndWaitForReady(testNamespaces[2]) }) It("should not reconcile function in other namespace", func() { - CreateFunctionAndWaitForConsistentlyNotReconciled(namespaces[0]) - CreateFunctionAndWaitForConsistentlyNotReconciled(namespaces[3], repoURLs[3]) + CreateFunctionAndWaitForConsistentlyNotReconciled(testNamespaces[0]) + CreateFunctionAndWaitForConsistentlyNotReconciled(testNamespaces[3]) }) }) Context("with two instances with SingleNamespace installMode installed into two distinct namespaces", func() { BeforeAll(func() { By("Setting up test namespaces") - namespaces, repoURLs = createMultipleNamespaceAndDeployFunction(4) + testNamespaces = createMultipleNamespaceAndDeployFunction(4) - By("Installing the operator into " + namespaces[0] + " for " + namespaces[1]) + By("Installing the operator into " + testNamespaces[0].Name + " for " + testNamespaces[1].Name) out, err := utils.OperatorSdkRun("run", "bundle", - "--namespace", namespaces[0], - "--install-mode", fmt.Sprintf("SingleNamespace=%s", namespaces[1]), + "--namespace", testNamespaces[0].Name, + "--install-mode", fmt.Sprintf("SingleNamespace=%s", testNamespaces[1].Name), fmt.Sprintf("--skip-tls-verify=%v", registryInsecure), bundleImage) Expect(err).NotTo(HaveOccurred()) _, _ = fmt.Fprint(GinkgoWriter, out) - By("Installing the operator into " + namespaces[2] + " for " + namespaces[3]) + By("Installing the operator into " + testNamespaces[2].Name + " for " + testNamespaces[3].Name) out, err = utils.OperatorSdkRun("run", "bundle", - "--namespace", namespaces[2], - "--install-mode", fmt.Sprintf("SingleNamespace=%s", namespaces[3]), + "--namespace", testNamespaces[2].Name, + "--install-mode", fmt.Sprintf("SingleNamespace=%s", testNamespaces[3].Name), fmt.Sprintf("--skip-tls-verify=%v", registryInsecure), bundleImage) Expect(err).NotTo(HaveOccurred()) @@ -222,44 +227,44 @@ var _ = Describe("Bundle", Label("bundle"), Ordered, func() { AfterAll(func() { specReport := CurrentSpecReport() if !specReport.Failed() { - By("Uninstalling the operator from " + namespaces[0]) + By("Uninstalling the operator from " + testNamespaces[0].Name) out, err := utils.OperatorSdkRun("cleanup", "func-operator", - "--namespace", namespaces[0], + "--namespace", testNamespaces[0].Name, "--delete-operator-groups") // dont delete CRDs, as operator in ns3 still has them Expect(err).NotTo(HaveOccurred()) _, _ = fmt.Fprint(GinkgoWriter, out) - By("Uninstalling the operator from " + namespaces[2]) + By("Uninstalling the operator from " + testNamespaces[2].Name) out, err = utils.OperatorSdkRun("cleanup", "func-operator", - "--namespace", namespaces[2]) + "--namespace", testNamespaces[2].Name) Expect(err).NotTo(HaveOccurred()) _, _ = fmt.Fprint(GinkgoWriter, out) By("Cleanup resources") - cleanupNamespaces(namespaces) + cleanupNamespaces(testNamespaces) } }) It("should reconcile function in dedicated namespaces", func() { - CreateFunctionAndWaitForReady(namespaces[1], repoURLs[1]) - CreateFunctionAndWaitForReady(namespaces[3], repoURLs[3]) + CreateFunctionAndWaitForReady(testNamespaces[1]) + CreateFunctionAndWaitForReady(testNamespaces[3]) }) It("should not reconcile function in other namespace", func() { - CreateFunctionAndWaitForConsistentlyNotReconciled(namespaces[0], repoURLs[0]) - CreateFunctionAndWaitForConsistentlyNotReconciled(namespaces[2], repoURLs[2]) + CreateFunctionAndWaitForConsistentlyNotReconciled(testNamespaces[0]) + CreateFunctionAndWaitForConsistentlyNotReconciled(testNamespaces[2]) }) }) Context("with AllNamespace installMode", func() { BeforeAll(func() { By("Setting up test namespaces") - namespaces, repoURLs = createMultipleNamespaceAndDeployFunction(2) + testNamespaces = createMultipleNamespaceAndDeployFunction(2) - By("Installing the operator into " + namespaces[0]) + By("Installing the operator into " + testNamespaces[0].Name) out, err := utils.OperatorSdkRun("run", "bundle", - "--namespace", namespaces[0], + "--namespace", testNamespaces[0].Name, "--install-mode", "AllNamespaces", fmt.Sprintf("--skip-tls-verify=%v", registryInsecure), bundleImage) @@ -273,32 +278,32 @@ var _ = Describe("Bundle", Label("bundle"), Ordered, func() { By("Uninstalling the operator") out, err := utils.OperatorSdkRun("cleanup", "func-operator", - "--namespace", namespaces[0]) + "--namespace", testNamespaces[0].Name) Expect(err).NotTo(HaveOccurred()) _, _ = fmt.Fprint(GinkgoWriter, out) By("Cleanup resources") - cleanupNamespaces(namespaces) + cleanupNamespaces(testNamespaces) } }) It("should reconcile function in all namespaces", func() { - CreateFunctionAndWaitForReady(namespaces[0], repoURLs[0]) - CreateFunctionAndWaitForReady(namespaces[1], repoURLs[1]) + CreateFunctionAndWaitForReady(testNamespaces[0]) + CreateFunctionAndWaitForReady(testNamespaces[1]) }) }) }) -func CreateFunctionAndWaitForReady(namespace, repoURL string) { +func CreateFunctionAndWaitForReady(testNs TestNamespace) { // Create a Function resource function := &functionsdevv1alpha1.Function{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "my-function-", - Namespace: namespace, + Namespace: testNs.Name, }, Spec: functionsdevv1alpha1.FunctionSpec{ Source: functionsdevv1alpha1.FunctionSpecSource{ - RepositoryURL: repoURL, + RepositoryURL: testNs.RepoURL, }, Registry: functionsdevv1alpha1.FunctionSpecRegistry{ Path: registry, @@ -327,16 +332,16 @@ func CreateFunctionAndWaitForReady(namespace, repoURL string) { Eventually(funcBecomeReady, 5*time.Minute).Should(Succeed()) } -func CreateFunctionAndWaitForConsistentlyNotReconciled(namespace, repoURL string) { +func CreateFunctionAndWaitForConsistentlyNotReconciled(testNs TestNamespace) { // Create a Function resource function := &functionsdevv1alpha1.Function{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "my-function-", - Namespace: namespace, + Namespace: testNs.Name, }, Spec: functionsdevv1alpha1.FunctionSpec{ Source: functionsdevv1alpha1.FunctionSpecSource{ - RepositoryURL: repoURL, + RepositoryURL: testNs.RepoURL, }, Registry: functionsdevv1alpha1.FunctionSpecRegistry{ Path: registry, @@ -360,9 +365,9 @@ func CreateFunctionAndWaitForConsistentlyNotReconciled(namespace, repoURL string Consistently(funcNotReconciled, time.Minute).Should(Succeed()) } -func createNamespaceAndDeployFunction() (ns, repoURL string) { +func createNamespaceAndDeployFunction() TestNamespace { var err error - ns, err = utils.GetTestNamespace() + ns, err := utils.GetTestNamespace() Expect(err).NotTo(HaveOccurred()) // Create repository provider resources @@ -370,7 +375,7 @@ func createNamespaceAndDeployFunction() (ns, repoURL string) { Expect(err).NotTo(HaveOccurred()) DeferCleanup(cleanup) - _, repoURL, cleanup, err = repoProvider.CreateRandomRepo(username, false) + _, repoURL, cleanup, err := repoProvider.CreateRandomRepo(username, false) Expect(err).NotTo(HaveOccurred()) DeferCleanup(cleanup) @@ -389,26 +394,25 @@ func createNamespaceAndDeployFunction() (ns, repoURL string) { Expect(err).NotTo(HaveOccurred()) _, _ = fmt.Fprint(GinkgoWriter, out) - return ns, repoURL + return TestNamespace{Name: ns, RepoURL: repoURL} } // createMultipleNamespaceAndDeployFunction creates multiple namespaces with functions -func createMultipleNamespaceAndDeployFunction(count int) (namespaces, repoURLs []string) { - namespaces = make([]string, count) - repoURLs = make([]string, count) +func createMultipleNamespaceAndDeployFunction(count int) []TestNamespace { + testNamespaces := make([]TestNamespace, count) for i := 0; i < count; i++ { // parallelizing this via goroutines seems to lead to resource issues, therefore keeping it sequential - namespaces[i], repoURLs[i] = createNamespaceAndDeployFunction() + testNamespaces[i] = createNamespaceAndDeployFunction() } - return namespaces, repoURLs + return testNamespaces } -func cleanupNamespaces(namespaces []string) { +func cleanupNamespaces(testNamespaces []TestNamespace) { By("Cleaning up all resources") - for _, ns := range namespaces { - cmd := exec.Command("kubectl", "delete", "namespace", ns, "--ignore-not-found") + for _, testNs := range testNamespaces { + cmd := exec.Command("kubectl", "delete", "namespace", testNs.Name, "--ignore-not-found") _, err := utils.Run(cmd) Expect(err).NotTo(HaveOccurred()) } From 958b5b0800c7e815c5c470a3a970c177dc209202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Mon, 16 Mar 2026 11:24:25 +0100 Subject: [PATCH 13/19] docs: add Gitea integration documentation for e2e tests - Comprehensive guide for using RepositoryProvider in tests - Document test patterns and helper methods - Explain DeferCleanup pattern for automatic cleanup - Add troubleshooting section - Describe implementation details and architecture - Update README with Gitea test information --- README.md | 8 +- docs/development/gitea-integration.md | 200 ++++++++++++++++++++++++++ 2 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 docs/development/gitea-integration.md diff --git a/README.md b/README.md index 131195b..a3b2606 100644 --- a/README.md +++ b/README.md @@ -184,10 +184,16 @@ You can also connect using your IDE's remote debugging features (VS Code, GoLand # Unit tests make test -# E2E tests +# E2E tests (requires Kind cluster with Gitea) +make create-kind-cluster # Sets up cluster with Gitea make test-e2e + +# Bundle tests +make test-e2e-bundle ``` +E2E tests use an in-cluster Gitea instance instead of GitHub, providing complete test isolation. See [Gitea Integration](docs/development/gitea-integration.md) for details on the test infrastructure. + ### Linting ```bash diff --git a/docs/development/gitea-integration.md b/docs/development/gitea-integration.md new file mode 100644 index 0000000..034e782 --- /dev/null +++ b/docs/development/gitea-integration.md @@ -0,0 +1,200 @@ +# Gitea Integration for E2E Tests + +## Overview + +The e2e test suite uses an in-cluster Gitea instance to provide git repository functionality. This eliminates external dependencies on GitHub and provides complete isolation for testing. + +## Architecture + +- **Gitea Installation**: Deployed via Helm during cluster setup +- **Network Access**: NodePort on port 30000 (HTTP) and 30022 (SSH) +- **Service Discovery**: ConfigMap in kube-public namespace contains endpoint +- **Authentication**: Admin user (giteaadmin/giteapass) for test operations +- **Provider Abstraction**: Tests use `RepositoryProvider` interface, allowing easy switching between Gitea/GitHub/GitLab + +## Using Repository Provider in Tests + +### Basic Pattern + +```go +var ( + repoURL string + repoDir string +) + +BeforeEach(func() { + var err error + + // Create repository provider resources with automatic cleanup + username, password, _, cleanup, err := repoProvider.CreateRandomUser() + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(cleanup) + + _, repoURL, cleanup, err = repoProvider.CreateRandomRepo(username, false) + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(cleanup) + + // Initialize with function code + repoDir, err = InitializeRepoWithFunction(repoURL, username, password, "go") + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(os.RemoveAll, repoDir) +}) +``` + +### Available Helper Methods + +**RepositoryProvider Interface:** +- `CreateUser(username, password, email string) (cleanup func(), err error)` - Create user with cleanup function +- `CreateRandomUser() (username, password, email string, cleanup func(), err error)` - Create user with random credentials +- `CreateRepo(owner, name string, private bool) (url string, cleanup func(), err error)` - Create repository +- `CreateRandomRepo(owner string, private bool) (name, url string, cleanup func(), err error)` - Create repo with random name +- `CreateAccessToken(username, password, tokenName string) (string, error)` - Generate access token + +**E2E Helper Functions:** +- `InitializeRepoWithFunction(url, user, pass, lang)` - Clone, init function, push +- `CommitAndPush(repoDir, msg, file, ...otherFiles)` - Commit and push files + +### DeferCleanup Pattern + +All Create methods return cleanup functions that can be used with Ginkgo's `DeferCleanup`: + +```go +username, password, _, cleanup, err := repoProvider.CreateRandomUser() +Expect(err).NotTo(HaveOccurred()) +DeferCleanup(cleanup) // Automatically deletes user after test +``` + +This ensures resources are cleaned up even if tests fail. + +## Accessing Gitea UI + +During development, you can access the Gitea web UI: + +1. Get the Gitea endpoint: + ```bash + kubectl get configmap gitea-endpoint -n kube-public -o jsonpath='{.data.http}' + ``` + +2. Open in browser and login: + - Username: `giteaadmin` + - Password: `giteapass` + +## Testing Private Repositories + +```go +// Create private repo +_, repoURL, cleanup, err := repoProvider.CreateRandomRepo(username, true) +Expect(err).NotTo(HaveOccurred()) +DeferCleanup(cleanup) + +// Create access token +token, err := repoProvider.CreateAccessToken(username, password, "test-token") + +// Use token in Function spec +Spec: functionsdevv1alpha1.FunctionSpec{ + Source: functionsdevv1alpha1.FunctionSpecSource{ + RepositoryURL: repoURL, + Credentials: &functionsdevv1alpha1.Credentials{ + Token: token, + }, + }, +} +``` + +## Bundle Test Pattern + +Bundle tests use the `TestNamespace` struct for better organization: + +```go +type TestNamespace struct { + Name string + RepoURL string +} + +func createNamespaceAndDeployFunction() TestNamespace { + // Creates namespace and deploys function + // Returns TestNamespace with both namespace and repo URL +} + +// Usage +testNs := createNamespaceAndDeployFunction() +CreateFunctionAndWaitForReady(testNs) +``` + +## Troubleshooting + +### Gitea not accessible + +```bash +kubectl get pods -n gitea +kubectl logs -n gitea deployment/gitea +``` + +### ConfigMap missing + +```bash +kubectl get configmap -n kube-public +./hack/create-kind-cluster.sh # Recreate cluster +``` + +### Test failures with git operations + +- Check Gitea pod is running +- Verify network connectivity to NodePort: + ```bash + GITEA_IP=$(kubectl get configmap gitea-endpoint -n kube-public -o jsonpath='{.data.http}') + curl $GITEA_IP + ``` +- Check git credentials in test + +### Repository cleanup issues + +If tests leave behind repositories, you can clean them manually: + +```bash +# List all users +curl -u giteaadmin:giteapass http:///api/v1/admin/users + +# Delete user (includes their repos) +curl -X DELETE -u giteaadmin:giteapass http:///api/v1/admin/users/ +``` + +## Implementation Details + +### Cluster Setup + +Gitea is installed during cluster creation in `hack/create-kind-cluster.sh`: + +```bash +helm install gitea gitea-charts/gitea --namespace gitea --create-namespace \ + --set service.http.type=NodePort \ + --set service.http.nodePort=30000 \ + --set gitea.admin.username=giteaadmin \ + --set gitea.admin.password=giteapass \ + --set persistence.enabled=false +``` + +### Service Discovery + +The cluster setup creates a ConfigMap with the Gitea endpoint: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: gitea-endpoint + namespace: kube-public +data: + http: "http://:30000" + ssh: ":30022" +``` + +Tests read this ConfigMap to discover where Gitea is running. + +### Networking + +- **Kind Node IP**: Retrieved via Docker inspect of control-plane node +- **NodePort**: Fixed ports 30000 (HTTP) and 30022 (SSH) +- **Accessibility**: Node IP is reachable from both host machine and cluster pods + +This allows the same repository URL to work in both test code (running on host) and Function resources (running in cluster). \ No newline at end of file From 0758249f3a683dc349c4dceab10d788979525bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Mon, 16 Mar 2026 13:19:37 +0100 Subject: [PATCH 14/19] fix: complete Gitea integration for e2e tests Fixes multiple issues preventing e2e tests from running with in-cluster Gitea instead of external GitHub: - Set MustChangePassword to false when creating users to prevent auth errors - Use AdminCreateRepo to create repos under correct user account - Rewrite function initialization to avoid git/func init conflicts - Add git user configuration for commits - Use main branch instead of master for modern git compatibility - Improve error messages by using utils.Run for all git commands - Add function code to "not yet deployed" test scenario --- test/e2e/func_deploy_test.go | 10 +++++-- test/e2e/gitea_helpers.go | 52 +++++++++++++++++++++++++----------- test/utils/gitea.go | 11 +++++--- 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/test/e2e/func_deploy_test.go b/test/e2e/func_deploy_test.go index 3afa482..9b1f2ed 100644 --- a/test/e2e/func_deploy_test.go +++ b/test/e2e/func_deploy_test.go @@ -153,19 +153,25 @@ var _ = Describe("Operator", Ordered, func() { }) Context("with a not yet deployed function", func() { var repoURL string + var repoDir string var functionName, functionNamespace string BeforeEach(func() { var err error - // Create repository but don't deploy - username, _, _, cleanup, err := repoProvider.CreateRandomUser() + // Create repository with function code but don't deploy + username, password, _, cleanup, err := repoProvider.CreateRandomUser() Expect(err).NotTo(HaveOccurred()) DeferCleanup(cleanup) _, repoURL, cleanup, err = repoProvider.CreateRandomRepo(username, false) Expect(err).NotTo(HaveOccurred()) DeferCleanup(cleanup) + + // Initialize repository with function code + repoDir, err = InitializeRepoWithFunction(repoURL, username, password, "go") + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(os.RemoveAll, repoDir) }) AfterEach(func() { diff --git a/test/e2e/gitea_helpers.go b/test/e2e/gitea_helpers.go index f7086fc..da2c110 100644 --- a/test/e2e/gitea_helpers.go +++ b/test/e2e/gitea_helpers.go @@ -32,34 +32,52 @@ func buildAuthURL(repoURL, username, password string) string { fmt.Sprintf("http://%s:%s@", username, password), 1) } -// InitializeRepoWithFunction clones an empty Gitea repo, initializes a function, and pushes it +// InitializeRepoWithFunction creates a function project and pushes it to the Gitea repo func InitializeRepoWithFunction(repoURL, username, password, language string) (repoDir string, err error) { repoDir = fmt.Sprintf("%s/func-test-%s", os.TempDir(), rand.String(10)) // Build authenticated URL authURL := buildAuthURL(repoURL, username, password) - // Clone empty repo - cmd := exec.Command("git", "clone", authURL, repoDir) + // Initialize function (func init creates the directory) + cmd := exec.Command("func", "init", "-l", language, repoDir) if _, err = utils.Run(cmd); err != nil { - return "", fmt.Errorf("failed to clone repo: %w", err) + return "", fmt.Errorf("failed to init function: %w", err) } - // Initialize function - cmd = exec.Command("func", "init", "-l", language) - cmd.Dir = repoDir + // Initialize git repo + cmd = exec.Command("git", "-C", repoDir, "init") if _, err = utils.Run(cmd); err != nil { - return "", fmt.Errorf("failed to init function: %w", err) + return "", fmt.Errorf("failed to git init: %w", err) + } + + // Configure git user + cmd = exec.Command("git", "-C", repoDir, "config", "user.name", "Test User") + if _, err = utils.Run(cmd); err != nil { + return "", fmt.Errorf("failed to set git user.name: %w", err) + } + cmd = exec.Command("git", "-C", repoDir, "config", "user.email", "test@example.com") + if _, err = utils.Run(cmd); err != nil { + return "", fmt.Errorf("failed to set git user.email: %w", err) + } + + // Add remote + cmd = exec.Command("git", "-C", repoDir, "remote", "add", "origin", authURL) + if _, err = utils.Run(cmd); err != nil { + return "", fmt.Errorf("failed to add remote: %w", err) } // Commit and push - if err = exec.Command("git", "-C", repoDir, "add", ".").Run(); err != nil { + cmd = exec.Command("git", "-C", repoDir, "add", ".") + if _, err = utils.Run(cmd); err != nil { return "", fmt.Errorf("failed to git add: %w", err) } - if err = exec.Command("git", "-C", repoDir, "commit", "-m", "Initial function").Run(); err != nil { + cmd = exec.Command("git", "-C", repoDir, "commit", "-m", "Initial function") + if _, err = utils.Run(cmd); err != nil { return "", fmt.Errorf("failed to git commit: %w", err) } - if err = exec.Command("git", "-C", repoDir, "push").Run(); err != nil { + cmd = exec.Command("git", "-C", repoDir, "push", "-u", "origin", "main") + if _, err = utils.Run(cmd); err != nil { return "", fmt.Errorf("failed to push initial commit: %w", err) } @@ -70,24 +88,28 @@ func InitializeRepoWithFunction(repoURL, username, password, language string) (r // Requires at least one file to be specified func CommitAndPush(repoDir string, msg string, file string, otherFiles ...string) error { // Add first file - if err := exec.Command("git", "-C", repoDir, "add", file).Run(); err != nil { + cmd := exec.Command("git", "-C", repoDir, "add", file) + if _, err := utils.Run(cmd); err != nil { return fmt.Errorf("failed to git add %s: %w", file, err) } // Add other files if provided for _, f := range otherFiles { - if err := exec.Command("git", "-C", repoDir, "add", f).Run(); err != nil { + cmd = exec.Command("git", "-C", repoDir, "add", f) + if _, err := utils.Run(cmd); err != nil { return fmt.Errorf("failed to git add %s: %w", f, err) } } // Commit - if err := exec.Command("git", "-C", repoDir, "commit", "-m", msg).Run(); err != nil { + cmd = exec.Command("git", "-C", repoDir, "commit", "-m", msg) + if _, err := utils.Run(cmd); err != nil { return fmt.Errorf("failed to git commit: %w", err) } // Push - if err := exec.Command("git", "-C", repoDir, "push").Run(); err != nil { + cmd = exec.Command("git", "-C", repoDir, "push") + if _, err := utils.Run(cmd); err != nil { return fmt.Errorf("failed to push: %w", err) } diff --git a/test/utils/gitea.go b/test/utils/gitea.go index e4311df..e80cf99 100644 --- a/test/utils/gitea.go +++ b/test/utils/gitea.go @@ -101,10 +101,12 @@ func NewGiteaClient() (*GiteaClient, error) { // CreateUser creates a new Gitea user func (g *GiteaClient) CreateUser(username, password, email string) (cleanup func(), err error) { + mustChangePassword := false _, _, err = g.client.AdminCreateUser(gitea.CreateUserOption{ - Username: username, - Password: password, - Email: email, + Username: username, + Password: password, + Email: email, + MustChangePassword: &mustChangePassword, }) if err != nil { return nil, fmt.Errorf("failed to create user %s: %w", username, err) @@ -137,7 +139,8 @@ func (g *GiteaClient) CreateRandomUser() (username, password, email string, clea // CreateRepo creates a new repository and returns its URL func (g *GiteaClient) CreateRepo(owner, name string, private bool) (url string, cleanup func(), err error) { - _, _, err = g.client.CreateRepo(gitea.CreateRepoOption{ + // Use admin client to create repo for the specified owner + _, _, err = g.client.AdminCreateRepo(owner, gitea.CreateRepoOption{ Name: name, Private: private, }) From dc8815348b53ca8d08ee1de83e295114473f8db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Mon, 16 Mar 2026 13:24:47 +0100 Subject: [PATCH 15/19] Run gofmt --- test/e2e/func_deploy_test.go | 8 ++++---- test/e2e/gitea_helpers.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/e2e/func_deploy_test.go b/test/e2e/func_deploy_test.go index 9b1f2ed..e8f1c2c 100644 --- a/test/e2e/func_deploy_test.go +++ b/test/e2e/func_deploy_test.go @@ -37,8 +37,8 @@ var _ = Describe("Operator", Ordered, func() { SetDefaultEventuallyPollingInterval(time.Second) Context("with a deployed function", func() { - var repoURL string - var repoDir string + var repoURL string + var repoDir string var functionName, functionNamespace string BeforeEach(func() { @@ -152,8 +152,8 @@ var _ = Describe("Operator", Ordered, func() { }) }) Context("with a not yet deployed function", func() { - var repoURL string - var repoDir string + var repoURL string + var repoDir string var functionName, functionNamespace string BeforeEach(func() { diff --git a/test/e2e/gitea_helpers.go b/test/e2e/gitea_helpers.go index da2c110..77f7538 100644 --- a/test/e2e/gitea_helpers.go +++ b/test/e2e/gitea_helpers.go @@ -114,4 +114,4 @@ func CommitAndPush(repoDir string, msg string, file string, otherFiles ...string } return nil -} \ No newline at end of file +} From 99fa8bae476949792983be42ac776551d93f8888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Mon, 16 Mar 2026 13:30:42 +0100 Subject: [PATCH 16/19] Fix linter issues --- test/e2e/bundle_test.go | 3 ++- test/utils/gitea.go | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/test/e2e/bundle_test.go b/test/e2e/bundle_test.go index 436e8dc..a90bf38 100644 --- a/test/e2e/bundle_test.go +++ b/test/e2e/bundle_test.go @@ -165,7 +165,8 @@ var _ = Describe("Bundle", Label("bundle"), Ordered, func() { By("Setting up test namespaces") testNamespaces = createMultipleNamespaceAndDeployFunction(4) - By("Installing the operator into " + testNamespaces[0].Name + " for " + testNamespaces[1].Name + " and " + testNamespaces[2].Name) + By("Installing the operator into " + testNamespaces[0].Name + + " for " + testNamespaces[1].Name + " and " + testNamespaces[2].Name) out, err := utils.OperatorSdkRun("run", "bundle", "--namespace", testNamespaces[0].Name, "--install-mode", fmt.Sprintf("MultiNamespace=%s,%s", testNamespaces[1].Name, testNamespaces[2].Name), diff --git a/test/utils/gitea.go b/test/utils/gitea.go index e80cf99..32ede3b 100644 --- a/test/utils/gitea.go +++ b/test/utils/gitea.go @@ -75,7 +75,9 @@ func NewGiteaClient() (*GiteaClient, error) { } // Get gitea-endpoint ConfigMap - cm, err := clientset.CoreV1().ConfigMaps("kube-public").Get(context.Background(), "gitea-endpoint", metav1.GetOptions{}) + cm, err := clientset.CoreV1(). + ConfigMaps("kube-public"). + Get(context.Background(), "gitea-endpoint", metav1.GetOptions{}) if err != nil { return nil, fmt.Errorf("failed to get gitea-endpoint configmap: %w", err) } From d91d7d1eaba19f0ce565096899c287e9cdf06076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Mon, 16 Mar 2026 15:13:56 +0100 Subject: [PATCH 17/19] fix: explicitly set main branch on git init In CI environments, git may not have a default branch name configured, causing 'git init' to create a branch with a different name (e.g., master). This causes the subsequent 'git push -u origin main' to fail with 'src refspec main does not match any'. Use 'git init -b main' to explicitly create the main branch, ensuring consistent behavior across all environments. --- test/e2e/gitea_helpers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/gitea_helpers.go b/test/e2e/gitea_helpers.go index 77f7538..abc6263 100644 --- a/test/e2e/gitea_helpers.go +++ b/test/e2e/gitea_helpers.go @@ -45,8 +45,8 @@ func InitializeRepoWithFunction(repoURL, username, password, language string) (r return "", fmt.Errorf("failed to init function: %w", err) } - // Initialize git repo - cmd = exec.Command("git", "-C", repoDir, "init") + // Initialize git repo with main as default branch + cmd = exec.Command("git", "-C", repoDir, "init", "-b", "main") if _, err = utils.Run(cmd); err != nil { return "", fmt.Errorf("failed to git init: %w", err) } From 2be11b88ce9d91f775f0880a002642ba62793766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Mon, 16 Mar 2026 15:41:25 +0100 Subject: [PATCH 18/19] Removed ordered test execution --- test/e2e/e2e_test.go | 2 +- test/e2e/func_deploy_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index b500437..bcd5ba3 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -39,7 +39,7 @@ const metricsServiceName = "func-operator-controller-manager-metrics-service" // metricsPort is the port of the metrics service providing the managers metrics const metricsPort = "8080" -var _ = Describe("Manager", Ordered, func() { +var _ = Describe("Manager", func() { var controllerPodName string // After each test, check for failures and collect logs, events, diff --git a/test/e2e/func_deploy_test.go b/test/e2e/func_deploy_test.go index e8f1c2c..108fb80 100644 --- a/test/e2e/func_deploy_test.go +++ b/test/e2e/func_deploy_test.go @@ -31,7 +31,7 @@ import ( "k8s.io/apimachinery/pkg/types" ) -var _ = Describe("Operator", Ordered, func() { +var _ = Describe("Operator", func() { SetDefaultEventuallyTimeout(2 * time.Minute) SetDefaultEventuallyPollingInterval(time.Second) From 7dbfc08daaed5203a7701b6e31dc15843561634a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Mon, 16 Mar 2026 15:41:43 +0100 Subject: [PATCH 19/19] Move deploy tests into a test namespace too instead of default --- test/e2e/bundle_test.go | 23 +++++++++++++++-------- test/e2e/func_deploy_test.go | 17 ++++++++++++----- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/test/e2e/bundle_test.go b/test/e2e/bundle_test.go index a90bf38..a0f5a70 100644 --- a/test/e2e/bundle_test.go +++ b/test/e2e/bundle_test.go @@ -109,7 +109,7 @@ var _ = Describe("Bundle", Label("bundle"), Ordered, func() { _, _ = fmt.Fprint(GinkgoWriter, out) By("Cleanup resources") - cleanupNamespaces(testNamespaces) + cleanupTestNamespaces(testNamespaces...) } }) @@ -147,7 +147,7 @@ var _ = Describe("Bundle", Label("bundle"), Ordered, func() { _, _ = fmt.Fprint(GinkgoWriter, out) By("Cleanup resources") - cleanupNamespaces(testNamespaces) + cleanupTestNamespaces(testNamespaces...) } }) @@ -187,7 +187,7 @@ var _ = Describe("Bundle", Label("bundle"), Ordered, func() { _, _ = fmt.Fprint(GinkgoWriter, out) By("Cleanup resources") - cleanupNamespaces(testNamespaces) + cleanupTestNamespaces(testNamespaces...) } }) @@ -244,7 +244,7 @@ var _ = Describe("Bundle", Label("bundle"), Ordered, func() { _, _ = fmt.Fprint(GinkgoWriter, out) By("Cleanup resources") - cleanupNamespaces(testNamespaces) + cleanupTestNamespaces(testNamespaces...) } }) @@ -284,7 +284,7 @@ var _ = Describe("Bundle", Label("bundle"), Ordered, func() { _, _ = fmt.Fprint(GinkgoWriter, out) By("Cleanup resources") - cleanupNamespaces(testNamespaces) + cleanupTestNamespaces(testNamespaces...) } }) @@ -410,10 +410,17 @@ func createMultipleNamespaceAndDeployFunction(count int) []TestNamespace { return testNamespaces } -func cleanupNamespaces(testNamespaces []TestNamespace) { - By("Cleaning up all resources") +func cleanupTestNamespaces(testNamespaces ...TestNamespace) { + By("Cleaning up test namespaces resources") for _, testNs := range testNamespaces { - cmd := exec.Command("kubectl", "delete", "namespace", testNs.Name, "--ignore-not-found") + cleanupNamespaces(testNs.Name) + } +} + +func cleanupNamespaces(namespaces ...string) { + for _, ns := range namespaces { + By("Cleaning up namespace " + ns) + cmd := exec.Command("kubectl", "delete", "namespace", ns, "--ignore-not-found") _, err := utils.Run(cmd) Expect(err).NotTo(HaveOccurred()) } diff --git a/test/e2e/func_deploy_test.go b/test/e2e/func_deploy_test.go index 108fb80..9ea5115 100644 --- a/test/e2e/func_deploy_test.go +++ b/test/e2e/func_deploy_test.go @@ -58,8 +58,13 @@ var _ = Describe("Operator", func() { Expect(err).NotTo(HaveOccurred()) DeferCleanup(os.RemoveAll, repoDir) + functionNamespace, err = utils.GetTestNamespace() + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(cleanupNamespaces, functionNamespace) + // Deploy function using func CLI cmd := exec.Command("func", "deploy", + "--namespace", functionNamespace, "--path", repoDir, "--registry", registry, "--registry-insecure", strconv.FormatBool(registryInsecure)) @@ -69,7 +74,7 @@ var _ = Describe("Operator", func() { // Cleanup func deployment DeferCleanup(func() { - cmd := exec.Command("func", "delete", "--path", repoDir) + cmd := exec.Command("func", "delete", "--path", repoDir, "--namespace", functionNamespace) _, _ = utils.Run(cmd) }) @@ -114,7 +119,7 @@ var _ = Describe("Operator", func() { function := &functionsdevv1alpha1.Function{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "my-function-", - Namespace: "default", + Namespace: functionNamespace, }, Spec: functionsdevv1alpha1.FunctionSpec{ Source: functionsdevv1alpha1.FunctionSpecSource{ @@ -131,7 +136,6 @@ var _ = Describe("Operator", func() { Expect(err).NotTo(HaveOccurred()) functionName = function.Name - functionNamespace = function.Namespace funcBecomeReady := func(g Gomega) { fn := &functionsdevv1alpha1.Function{} @@ -172,6 +176,10 @@ var _ = Describe("Operator", func() { repoDir, err = InitializeRepoWithFunction(repoURL, username, password, "go") Expect(err).NotTo(HaveOccurred()) DeferCleanup(os.RemoveAll, repoDir) + + functionNamespace, err = utils.GetTestNamespace() + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(cleanupNamespaces, functionNamespace) }) AfterEach(func() { @@ -188,7 +196,7 @@ var _ = Describe("Operator", func() { function := &functionsdevv1alpha1.Function{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "my-undeployed-function-", - Namespace: "default", + Namespace: functionNamespace, }, Spec: functionsdevv1alpha1.FunctionSpec{ Source: functionsdevv1alpha1.FunctionSpecSource{ @@ -205,7 +213,6 @@ var _ = Describe("Operator", func() { Expect(err).NotTo(HaveOccurred()) functionName = function.Name - functionNamespace = function.Namespace funcBecomeReady := func(g Gomega) { fn := &functionsdevv1alpha1.Function{}