func/pkg/pipelines/tekton/pipelines_integration_test.go

261 lines
6.5 KiB
Go

//go:build integration
// +build integration
package tekton_test
import (
"context"
"fmt"
"io"
"net/http"
"net/http/httputil"
"os"
"os/signal"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
corev1 "k8s.io/api/core/v1"
rbacV1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"knative.dev/func/pkg/k8s"
"knative.dev/func/pkg/knative"
"knative.dev/func/pkg/builders/buildpacks"
pack "knative.dev/func/pkg/builders/buildpacks"
"knative.dev/func/pkg/docker"
fn "knative.dev/func/pkg/functions"
"knative.dev/func/pkg/pipelines/tekton"
"knative.dev/func/pkg/random"
. "knative.dev/func/pkg/testing"
)
var testCP = func(_ context.Context, _ string) (docker.Credentials, error) {
return docker.Credentials{
Username: "",
Password: "",
}, nil
}
const (
TestRegistry = "registry.default.svc.cluster.local:5000"
// TestRegistry = "docker.io/alice"
TestNamespace = "default"
)
func newRemoteTestClient(verbose bool) *fn.Client {
return fn.New(
fn.WithBuilder(pack.NewBuilder(pack.WithVerbose(verbose))),
fn.WithPusher(docker.NewPusher(docker.WithCredentialsProvider(testCP))),
fn.WithDeployer(knative.NewDeployer(knative.WithDeployerVerbose(verbose))),
fn.WithRemover(knative.NewRemover(verbose)),
fn.WithDescriber(knative.NewDescriber(verbose)),
fn.WithRemover(knative.NewRemover(verbose)),
fn.WithPipelinesProvider(tekton.NewPipelinesProvider(tekton.WithCredentialsProvider(testCP), tekton.WithVerbose(verbose))),
)
}
// assertFunctionEchoes returns without error when the function of the given
// name echoes a parameter sent via a Get request.
func assertFunctionEchoes(url string) (err error) {
token := time.Now().Format("20060102150405.000000000")
// res, err := http.Get("http://testremote-default.default.127.0.0.1.sslip.io?token=" + token)
res, err := http.Get(url + "?token=" + token)
if err != nil {
return
}
if res.StatusCode != 200 {
return fmt.Errorf("unexpected status code %v", res.StatusCode)
}
body, err := io.ReadAll(res.Body)
if err != nil {
return fmt.Errorf("error parsing response. %w", err)
}
defer res.Body.Close()
if !strings.Contains(string(body), token) {
err = fmt.Errorf("response did not contain token. url: %v", url)
_, _ = httputil.DumpResponse(res, true)
}
return
}
func tektonTestsEnabled(t *testing.T) (enabled bool) {
enabled, _ = strconv.ParseBool(os.Getenv("TEKTON_TESTS_ENABLED"))
if !enabled {
t.Log("Tekton tests not enabled. Enable with TEKTON_TESTS_ENABLED=true")
}
return
}
// fromCleanEnvironment of everything except KUBECONFIG. Create a temp directory.
// Change to that temp directory. Return the curent path as a convenience.
func fromCleanEnvironment(t *testing.T) (root string) {
// FromTempDirectory clears envs, but sets KUBECONFIG to ./tempdata, so
// we have to preserve that one value.
t.Helper()
kubeconfig := os.Getenv("KUBECONFIG")
root = FromTempDirectory(t)
os.Setenv("KUBECONFIG", kubeconfig)
return
}
func TestRemote_Default(t *testing.T) {
if !tektonTestsEnabled(t) {
t.Skip()
}
_ = fromCleanEnvironment(t)
var (
err error
url string
verbose = false
ctx, cancel = signal.NotifyContext(context.Background(), os.Interrupt)
client = newRemoteTestClient(verbose)
)
defer cancel()
f := fn.Function{
Name: "testremote-default",
Runtime: "node",
Registry: TestRegistry,
Namespace: TestNamespace,
Build: fn.BuildSpec{
Builder: "pack", // TODO: test "s2i". Currently it causes a 'no space left on device' error in GH actions.
},
}
if f, err = client.Init(f); err != nil {
t.Fatal(err)
}
if url, f, err = client.RunPipeline(ctx, f); err != nil {
t.Fatal(err)
}
defer func() {
_ = client.Remove(ctx, "", "", f, true)
}()
if err := assertFunctionEchoes(url); err != nil {
t.Fatal(err)
}
}
func setupNS(t *testing.T) string {
name := "pipeline-integration-test-" + strings.ToLower(random.AlphaString(5))
cliSet, err := k8s.NewKubernetesClientset()
if err != nil {
t.Fatal(err)
}
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
}
_, err = cliSet.CoreV1().Namespaces().Create(context.Background(), ns, metav1.CreateOptions{})
if err != nil && !errors.IsAlreadyExists(err) {
t.Fatal(err)
}
t.Cleanup(func() {
pp := metav1.DeletePropagationForeground
_ = cliSet.CoreV1().Namespaces().Delete(context.Background(), name, metav1.DeleteOptions{
PropagationPolicy: &pp,
})
})
crb := &rbacV1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: name + ":knative-serving-namespaced-admin",
},
Subjects: []rbacV1.Subject{
{
Kind: "ServiceAccount",
Name: "default",
Namespace: name,
},
},
RoleRef: rbacV1.RoleRef{
Name: "knative-serving-namespaced-admin",
Kind: "ClusterRole",
APIGroup: "rbac.authorization.k8s.io",
},
}
_, err = cliSet.RbacV1().ClusterRoleBindings().Create(context.Background(), crb, metav1.CreateOptions{})
if err != nil {
t.Fatal(err)
}
t.Log("created namespace: ", name)
return name
}
func checkTestEnabled(t *testing.T) {
val := os.Getenv("TEKTON_TESTS_ENABLED")
enabled, _ := strconv.ParseBool(val)
if !enabled {
t.Skip("tekton tests are not enabled")
}
}
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, "handle.go"), []byte(simpleGOSvc), 0644)
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(projDir, "go.mod"), []byte("module function\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": buildpacks.DefaultTinyBuilder,
"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 function
import (
"context"
"net/http"
)
func Handle(ctx context.Context, resp http.ResponseWriter, req *http.Request) {
resp.Header().Add("Content-Type", "text/plain")
resp.WriteHeader(200)
_, _ = resp.Write([]byte("Hello World!\n"))
}
`