mirror of https://github.com/knative/func.git
384 lines
8.5 KiB
Go
384 lines
8.5 KiB
Go
package builders_test
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/sha1"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/pem"
|
|
"math/big"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"golang.org/x/term"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/image"
|
|
"github.com/docker/docker/client"
|
|
"github.com/docker/docker/pkg/jsonmessage"
|
|
coreV1 "k8s.io/api/core/v1"
|
|
v1 "k8s.io/api/networking/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/util/intstr"
|
|
|
|
"knative.dev/func/pkg/builders/buildpacks"
|
|
fn "knative.dev/func/pkg/functions"
|
|
"knative.dev/func/pkg/k8s"
|
|
)
|
|
|
|
func TestPrivateGitRepository(t *testing.T) {
|
|
t.Skip("tested functionality not implemented yet")
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
sigs := make(chan os.Signal, 1)
|
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
|
go func() {
|
|
<-sigs
|
|
cancel()
|
|
<-sigs // second sigint/sigterm is treated as sigkill
|
|
os.Exit(137)
|
|
}()
|
|
|
|
certDir := createCertificate(t)
|
|
t.Log("certDir:", certDir)
|
|
|
|
builderImage := buildPatchedBuilder(ctx, t, certDir)
|
|
t.Log("builder image:", builderImage)
|
|
|
|
servePrivateGit(ctx, t, certDir)
|
|
t.Log("git server initiated")
|
|
|
|
select {
|
|
case <-time.After(time.Second * 5):
|
|
break
|
|
case <-ctx.Done():
|
|
t.Fatal(ctx.Err())
|
|
}
|
|
|
|
builder := buildpacks.NewBuilder(buildpacks.WithVerbose(true))
|
|
f, err := fn.NewFunction(filepath.Join("testdata", "go-fn-with-private-deps"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
f.Build.Image = "localhost:50000/go-app:test"
|
|
f.Build.Builder = "pack"
|
|
f.Build.BuilderImages = map[string]string{"pack": builderImage}
|
|
err = builder.Build(ctx, f, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// Generates self-signed certificate used for our private git repository.
|
|
func createCertificate(t *testing.T) string {
|
|
dir := t.TempDir()
|
|
|
|
certPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ski := sha1.Sum(x509.MarshalPKCS1PublicKey(&certPrivKey.PublicKey))
|
|
|
|
cert := &x509.Certificate{
|
|
BasicConstraintsValid: true,
|
|
IsCA: true,
|
|
SerialNumber: randSN(),
|
|
// openssl hash of this subject is 712d4c9d
|
|
// do not update the subject without also updating the hash referred from another places (e.g. Dockerfile)
|
|
// See also: https://github.com/paketo-buildpacks/ca-certificates/blob/v1.0.1/cacerts/certs.go#L132
|
|
Subject: pkix.Name{
|
|
CommonName: "git-private.127.0.0.1.sslip.io",
|
|
},
|
|
DNSNames: []string{"git-private.127.0.0.1.sslip.io"},
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().AddDate(0, 0, 1),
|
|
SubjectKeyId: ski[:],
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
|
}
|
|
|
|
certBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, &certPrivKey.PublicKey, certPrivKey)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
certPEM := new(bytes.Buffer)
|
|
err = pem.Encode(certPEM, &pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: certBytes,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
certPrivKeyPEM := new(bytes.Buffer)
|
|
err = pem.Encode(certPrivKeyPEM, &pem.Block{
|
|
Type: "RSA PRIVATE KEY",
|
|
Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey),
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = os.WriteFile(filepath.Join(dir, "cert.pem"), certPEM.Bytes(), 0444)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = os.WriteFile(filepath.Join(dir, "key.pem"), certPrivKeyPEM.Bytes(), 0400)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return dir
|
|
}
|
|
|
|
var maxSN *big.Int = new(big.Int).Lsh(big.NewInt(1), 159)
|
|
|
|
func randSN() *big.Int {
|
|
i, err := rand.Int(rand.Reader, maxSN)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return i
|
|
}
|
|
|
|
// Builds a tiny paketo builder that trusts to our self-signed certificate (see createCertificate).
|
|
func buildPatchedBuilder(ctx context.Context, t *testing.T, certDir string) string {
|
|
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
dockerfile := `FROM ghcr.io/knative/builder-jammy-tiny:latest
|
|
COPY 712d4c9d.0 /etc/ssl/certs/
|
|
`
|
|
|
|
var buff bytes.Buffer
|
|
tw := tar.NewWriter(&buff)
|
|
|
|
err = tw.WriteHeader(&tar.Header{
|
|
Name: "Dockerfile",
|
|
Size: int64(len(dockerfile)),
|
|
Mode: 0644,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err = tw.Write([]byte(dockerfile))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cb, err := os.ReadFile(filepath.Join(certDir, "cert.pem"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = tw.WriteHeader(&tar.Header{
|
|
Name: "712d4c9d.0",
|
|
Size: int64(len(cb)),
|
|
Mode: 0644,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err = tw.Write(cb)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = tw.Close()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
tag := "localhost:50000/tiny-builder:test"
|
|
ibo := types.ImageBuildOptions{
|
|
Tags: []string{tag},
|
|
}
|
|
ibr, err := cli.ImageBuild(ctx, &buff, ibo)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer ibr.Body.Close()
|
|
|
|
fd := os.Stderr.Fd()
|
|
isTerminal := term.IsTerminal(int(fd))
|
|
err = jsonmessage.DisplayJSONMessagesStream(ibr.Body, os.Stderr, fd, isTerminal, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rc, err := cli.ImagePush(ctx, tag, image.PushOptions{RegistryAuth: "e30="})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer rc.Close()
|
|
|
|
return tag
|
|
}
|
|
|
|
// This sets up a private git repository for testing.
|
|
// The repository url is https://git-private.127.0.0.1.sslip.io/foo.git, and it is protected by basic authentication.
|
|
// The credentials are developer:nbusr123.
|
|
func servePrivateGit(ctx context.Context, t *testing.T, certDir string) {
|
|
const (
|
|
name = "git-private"
|
|
host = "git-private.127.0.0.1.sslip.io"
|
|
image = "ghcr.io/matejvasek/git-private:latest"
|
|
)
|
|
|
|
k8sClient, err := k8s.NewKubernetesClientset()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ns := coreV1.Namespace{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
}
|
|
_, err = k8sClient.CoreV1().Namespaces().Create(ctx, &ns, metav1.CreateOptions{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() {
|
|
_ = k8sClient.CoreV1().Namespaces().Delete(context.Background(), name, metav1.DeleteOptions{})
|
|
})
|
|
|
|
cert, err := os.ReadFile(filepath.Join(certDir, "cert.pem"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
key, err := os.ReadFile(filepath.Join(certDir, "key.pem"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
secret := coreV1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: name,
|
|
},
|
|
Immutable: ptr(true),
|
|
Data: map[string][]byte{
|
|
coreV1.TLSCertKey: cert,
|
|
coreV1.TLSPrivateKeyKey: key,
|
|
},
|
|
Type: coreV1.SecretTypeTLS,
|
|
}
|
|
|
|
_, err = k8sClient.CoreV1().Secrets(name).Create(ctx, &secret, metav1.CreateOptions{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
pod := coreV1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: name,
|
|
Labels: map[string]string{"app.kubernetes.io/name": name},
|
|
},
|
|
Spec: coreV1.PodSpec{
|
|
Containers: []coreV1.Container{
|
|
{
|
|
Name: name,
|
|
Image: image,
|
|
Ports: []coreV1.ContainerPort{
|
|
{
|
|
Name: "http",
|
|
ContainerPort: 8080,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
_, err = k8sClient.CoreV1().Pods(name).Create(ctx, &pod, metav1.CreateOptions{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
svc := coreV1.Service{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: name,
|
|
},
|
|
Spec: coreV1.ServiceSpec{
|
|
Selector: map[string]string{
|
|
"app.kubernetes.io/name": name,
|
|
},
|
|
Ports: []coreV1.ServicePort{
|
|
{
|
|
Name: "http",
|
|
Protocol: "TCP",
|
|
Port: 80,
|
|
TargetPort: intstr.FromString("http"),
|
|
},
|
|
},
|
|
Type: coreV1.ServiceTypeClusterIP,
|
|
},
|
|
}
|
|
_, err = k8sClient.CoreV1().Services(name).Create(ctx, &svc, metav1.CreateOptions{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ingress := v1.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: name,
|
|
},
|
|
Spec: v1.IngressSpec{
|
|
IngressClassName: ptr("contour-external"),
|
|
DefaultBackend: nil,
|
|
TLS: []v1.IngressTLS{
|
|
{
|
|
Hosts: []string{host},
|
|
SecretName: name,
|
|
},
|
|
},
|
|
Rules: []v1.IngressRule{
|
|
{
|
|
Host: host,
|
|
IngressRuleValue: v1.IngressRuleValue{
|
|
HTTP: &v1.HTTPIngressRuleValue{Paths: []v1.HTTPIngressPath{
|
|
{
|
|
Path: "/",
|
|
PathType: ptr(v1.PathTypePrefix),
|
|
Backend: v1.IngressBackend{
|
|
Service: &v1.IngressServiceBackend{
|
|
Name: name,
|
|
Port: v1.ServiceBackendPort{
|
|
Name: "http",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
_, err = k8sClient.NetworkingV1().Ingresses(name).Create(ctx, &ingress, metav1.CreateOptions{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func ptr[T any](val T) *T {
|
|
return &val
|
|
}
|