test: integration test for tekton on cluster build (#1789)

* test: integration tests for Tekton builds

Signed-off-by: Matej Vasek <mvasek@redhat.com>

* fixup: disable s2i integration test for now

Signed-off-by: Matej Vasek <mvasek@redhat.com>

---------

Signed-off-by: Matej Vasek <mvasek@redhat.com>
This commit is contained in:
Matej Vasek 2023-06-07 23:47:57 +02:00 committed by GitHub
parent 5034bd31d2
commit 6b97fb5a3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 274 additions and 2 deletions

View File

@ -24,10 +24,13 @@ jobs:
run: ./hack/registry.sh
- name: Allocate Cluster
run: ./hack/allocate.sh
- name: Patch S2I Task
run: ./hack/patch-s2i-task.sh
- name: Install Tekton
run: ./hack/tekton.sh
- name: Set up environment variables
run: |
echo "TEKTON_TESTS_ENABLED=1" >> "$GITHUB_ENV"
echo "GITLAB_TESTS_ENABLED=1" >> "$GITHUB_ENV"
echo "GITLAB_HOSTNAME=gitlab.127.0.0.1.sslip.io" >> "$GITHUB_ENV"
echo "GITLAB_ROOT_PASSWORD=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32})" >> "$GITHUB_ENV"

View File

@ -71,6 +71,8 @@ containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:50000"]
endpoint = ["http://func-registry:5000"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."registry.default.svc.cluster.local:5000"]
endpoint = ["http://func-registry:5000"]
EOF
sleep 10
kubectl wait pod --for=condition=Ready -l '!job-name' -n kube-system --timeout=5m
@ -206,6 +208,19 @@ data:
host: "localhost:50000"
help: "https://kind.sigs.k8s.io/docs/user/local-registry/"
EOF
# Make the registry available in cluster under registry.default.svc.cluster.local:5000.
# This is useful since for "*.local" registries HTTP (not HTTPS) is used by default by some applications.
kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: registry
namespace: default
spec:
type: ExternalName
externalName: func-registry
EOF
}
namespace() {

21
hack/patch-s2i-task.sh Executable file
View File

@ -0,0 +1,21 @@
#!/usr/bin/env bash
# This script patches the s2i Tekton task, so it recognizes registry.default.svc.cluster.local:5000 as insecure.
echo "Patching s2i Tekton task to use 'registry.default.svc.cluster.local:5000' as an insecure registry."
patch pkg/pipelines/resources/tekton/task/func-s2i/0.1/func-s2i.yaml <<EOF
diff --git a/pkg/pipelines/resources/tekton/task/func-s2i/0.1/func-s2i.yaml b/pkg/pipelines/resources/tekton/task/func-s2i/0.1/func-s2i.yaml
index a6973d70..f2bdb5d6 100644
--- a/pkg/pipelines/resources/tekton/task/func-s2i/0.1/func-s2i.yaml
+++ b/pkg/pipelines/resources/tekton/task/func-s2i/0.1/func-s2i.yaml
@@ -102,6 +102,8 @@ spec:
image: quay.io/buildah/stable:v1.27.0
workingDir: /gen-source
script: |
+ export BUILD_REGISTRY_SOURCES='{"insecureRegistries": ["registry.default.svc.cluster.local:5000"]}'
+
TLS_VERIFY_FLAG=""
if [ "\$(params.TLSVERIFY)" = "false" ] || [ "\$(params.TLSVERIFY)" = "0" ]; then
TLS_VERIFY_FLAG="--tls-verify=false"
EOF

View File

@ -102,15 +102,20 @@ spec:
image: quay.io/buildah/stable:v1.27.0
workingDir: /gen-source
script: |
TLS_VERIFY_FLAG=""
if [ "$(params.TLSVERIFY)" = "false" ] || [ "$(params.TLSVERIFY)" = "0" ]; then
TLS_VERIFY_FLAG="--tls-verify=false"
fi
[[ "$(workspaces.sslcertdir.bound)" == "true" ]] && CERT_DIR_FLAG="--cert-dir $(workspaces.sslcertdir.path)"
ARTIFACTS_CACHE_PATH="$(workspaces.cache.path)/mvn-artifacts"
[ -d "${ARTIFACTS_CACHE_PATH}" ] || mkdir "${ARTIFACTS_CACHE_PATH}"
buildah ${CERT_DIR_FLAG} bud --storage-driver=vfs --tls-verify=$(params.TLSVERIFY) --layers \
buildah ${CERT_DIR_FLAG} bud --storage-driver=vfs ${TLS_VERIFY_FLAG} --layers \
-v "${ARTIFACTS_CACHE_PATH}:/tmp/artifacts/:rw,z,U" \
-f /gen-source/Dockerfile.gen -t $(params.IMAGE) .
[[ "$(workspaces.dockerconfig.bound)" == "true" ]] && export DOCKER_CONFIG="$(workspaces.dockerconfig.path)"
buildah ${CERT_DIR_FLAG} push --storage-driver=vfs --tls-verify=$(params.TLSVERIFY) --digestfile $(workspaces.source.path)/image-digest \
buildah ${CERT_DIR_FLAG} push --storage-driver=vfs ${TLS_VERIFY_FLAG} --digestfile $(workspaces.source.path)/image-digest \
$(params.IMAGE) docker://$(params.IMAGE)
cat $(workspaces.source.path)/image-digest | tee /tekton/results/IMAGE_DIGEST

View File

@ -0,0 +1,228 @@
//go:build integration
// +build integration
package tekton_test
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"path/filepath"
"runtime"
"strconv"
"strings"
"testing"
"time"
"knative.dev/func/pkg/docker"
fn "knative.dev/func/pkg/functions"
"knative.dev/func/pkg/pipelines/tekton"
"knative.dev/func/pkg/random"
)
func TestOnClusterBuild(t *testing.T) {
checkTestEnabled(t)
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
ns := "default"
credentialsProvider := func(ctx context.Context, image string) (docker.Credentials, error) {
return docker.Credentials{
Username: "",
Password: "",
}, nil
}
tests := []struct {
Builder string
}{
{Builder: "s2i"},
{Builder: "pack"},
}
for _, test := range tests {
t.Run(test.Builder, func(t *testing.T) {
if test.Builder == "s2i" {
t.Skip("Skipping because this causes 'no space left on device' in GH Action.")
}
ctx, cancel := context.WithCancel(ctx)
defer cancel()
urlChan := make(chan string, 1)
pp := tekton.NewPipelinesProvider(
tekton.WithCredentialsProvider(credentialsProvider),
tekton.WithNamespace(ns),
tekton.WithProgressListener(pl{urlChan: urlChan}))
f := createSimpleGoProject(t, ns)
f.Build.Builder = test.Builder
go func() {
err := pp.Run(ctx, f)
if err != nil {
t.Error(err)
cancel()
}
}()
select {
case u := <-urlChan:
resp, err := http.Get(u)
if err != nil {
t.Error(err)
return
}
_ = resp.Body.Close()
if resp.StatusCode != 200 {
t.Error("bad HTTP response code")
return
}
t.Log("call to knative service successful")
case <-time.After(time.Minute * 10):
t.Error("timeout while waiting for service to start")
case <-ctx.Done():
t.Error("cancelled")
}
})
}
}
func checkTestEnabled(t *testing.T) {
val := os.Getenv("TEKTON_TESTS_ENABLED")
enabled, _ := strconv.ParseBool(val)
if !enabled {
t.Skip("tekton tests are not enabled")
}
}
type pl struct {
urlChan chan<- string
}
func (p pl) log(args ...any) {
_, file, line, ok := runtime.Caller(2)
if ok {
prefix := fmt.Sprintf("%s:%d", filepath.Base(file), line)
args = append([]any{prefix}, args...)
}
fmt.Fprintln(os.Stderr, args...)
}
func (p pl) SetTotal(i int) {
p.log("ProgressListener::SetTotal: ", i)
}
func (p pl) Increment(message string) {
p.log("ProgressListener::Increment: ", message)
if strings.Contains(message, "URL:") {
parts := strings.Split(message, "URL:")
if len(parts) < 2 {
p.log("bad output message: %q", message)
return
}
u := strings.TrimSpace(parts[1])
p.urlChan <- u
}
}
func (p pl) Complete(message string) {
p.log("ProgressListener::Complete: ", message)
}
func (p pl) Stopping() {
p.log("ProgressListener::Stopping")
}
func (p pl) Done() {
p.log("ProgressListener::Done")
}
func createSimpleGoProject(t *testing.T, ns string) fn.Function {
var err error
funcName := "fn-" + strings.ToLower(random.AlphaString(5))
projDir := filepath.Join(t.TempDir(), funcName)
err = os.Mkdir(projDir, 0755)
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(projDir, "main.go"), []byte(simpleGOSvc), 0644)
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(projDir, "go.mod"), []byte("module web\n\ngo 1.20\n"), 0644)
if err != nil {
t.Fatal(err)
}
f := fn.Function{
Root: projDir,
Name: funcName,
Runtime: "none",
Template: "none",
Image: "registry.default.svc.cluster.local:5000/" + funcName,
Created: time.Now(),
Invoke: "none",
Build: fn.BuildSpec{
BuilderImages: map[string]string{
"pack": "docker.io/paketobuildpacks/builder:base",
"s2i": "registry.access.redhat.com/ubi8/go-toolset",
},
},
Deploy: fn.DeploySpec{
Namespace: ns,
},
}
f = fn.NewFunctionWith(f)
err = f.Write()
if err != nil {
t.Fatal(err)
}
return f
}
const simpleGOSvc = `package main
import (
"context"
"net"
"net/http"
"os"
"os/signal"
"syscall"
)
func main() {
sigs := make(chan os.Signal, 5)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
s := http.Server{
Handler: http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
resp.Header().Add("Content-Type", "text/plain")
resp.WriteHeader(200)
_, _ = resp.Write([]byte("OK"))
}),
}
go func() {
<-sigs
_ = s.Shutdown(context.Background())
}()
port := "8080"
if p, ok := os.LookupEnv("PORT"); ok {
port = p
}
l, err := net.Listen("tcp4", ":"+port)
if err != nil {
panic(err)
}
_ = s.Serve(l)
}
`