func/pkg/oci/pusher_test.go

165 lines
4.4 KiB
Go

package oci
import (
"context"
"errors"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"testing"
"time"
fn "knative.dev/func/pkg/functions"
"knative.dev/func/pkg/oci/mock"
. "knative.dev/func/pkg/testing"
"github.com/google/go-containerregistry/pkg/registry"
)
// TestPusher_Push ensures the base case that the pusher contacts the
// registry with a correctly formed request.
func TestPusher_Push(t *testing.T) {
var (
root, done = Mktemp(t)
verbose = false
insecure = true
anon = true
success = false
err error
)
defer done()
// Start a handler on an OS-chosen port which confirms that an incoming
// requests looks for the most part like what we'd expect to see from a
// container push.
regLog := io.Discard
if verbose {
regLog = os.Stderr
}
regHandler := registry.New(registry.Logger(log.New(regLog, "img reg handler: ", log.LstdFlags)))
handler := http.NewServeMux()
handler.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
regHandler.ServeHTTP(res, req)
if req.Method == http.MethodPut && req.URL.Path == "/v2/funcs/f/manifests/latest" {
success = true
}
})
l, err := net.Listen("tcp4", "127.0.0.1:")
if err != nil {
t.Fatal(err)
}
s := http.Server{Handler: handler}
go func() {
if err = s.Serve(l); err != nil && !errors.Is(err, http.ErrServerClosed) {
fmt.Fprintf(os.Stderr, "error serving: %v", err)
}
}()
defer s.Close()
// Create and push a function
client := fn.New(
fn.WithBuilder(NewBuilder("", false)),
fn.WithPusher(NewPusher(insecure, anon, verbose)))
f := fn.Function{Root: root, Runtime: "go", Name: "f", Registry: l.Addr().String() + "/funcs"}
if f, err = client.Init(f); err != nil {
t.Fatal(err)
}
if f, err = client.Build(context.Background(), f); err != nil {
t.Fatal(err)
}
if _, _, err = client.Push(context.Background(), f); err != nil {
t.Fatal(err)
}
// Confirm the handler received at least on PUT request containing
// an image index.
if !success {
t.Fatal("did not receive the image index JSON")
}
}
// TestPusher_BasicAuth ensures that the pusher authenticates via basic auth when
// supplied with a username/password via the context.
func TestPusher_BasicAuth(t *testing.T) {
var (
root, done = Mktemp(t)
username = "username"
password = "password"
verbose = false
successCh = make(chan bool, 100) // Many successes are queued on push
err error
)
defer done()
// A mock registry with middleware which performs basic auth
server := mock.NewRegistry()
server.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
u, p, ok := r.BasicAuth()
if !ok {
// no header. ask for auth
w.Header().Add("www-authenticate", "Basic realm=\"Registry Realm\"")
http.Error(w, "Unauthorized", http.StatusUnauthorized)
} else if u != username || p != password {
// header exists, but creds are either missing or incorrect
t.Fatalf("Unauthorized. Expected user %q pass %q, got user %q pass %q", username, password, u, p)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
} else {
// (at least one) request indicates authentication worked.
// The channel has a large buffer because many are queued before
// the receive instruction.
successCh <- true
}
// always delegate to the registry impl which implements the protocol
server.RegistryImpl.ServeHTTP(w, r)
}
defer server.Close()
// Client
// initialized with an OCI builder and pusher.
client := fn.New(
fn.WithBuilder(NewBuilder("", verbose)),
fn.WithPusher(NewPusher(false, false, verbose)))
// Function
// Built and tagged to push to the mock registry
f := fn.Function{
Root: root,
Runtime: "go",
Name: "f",
Registry: server.Addr().String() + "/funcs"}
if f, err = client.Init(f); err != nil {
t.Fatal(err)
}
if f, err = client.Build(context.Background(), f); err != nil {
t.Fatal(err)
}
// Push
// Enables optional basic authentication via the push context to use instead
// of the default behavior of using the multi-auth chain of config files
// and various known credentials managers.
ctx := context.Background()
ctx = context.WithValue(ctx, fn.PushUsernameKey{}, username)
ctx = context.WithValue(ctx, fn.PushPasswordKey{}, password)
if _, _, err = client.Push(ctx, f); err != nil {
t.Fatal(err)
}
select {
case <-successCh:
case <-time.NewTimer(10 * time.Second).C:
t.Fatal("timed out waiting for a successful basic auth request")
}
}