From efc9707333679aecf36b38f6f53dd0a0e52698eb Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 12 Feb 2020 12:47:36 +0100 Subject: Make it possible to fabricate a serverless serving --- internal/serving/serverless/function.go | 8 +++--- internal/serving/serverless/function_test.go | 6 ++--- internal/serving/serverless/serverless.go | 29 ++++++++++++++++++++++ internal/serving/serverless/serverless_test.go | 24 +++++++++--------- internal/source/gitlab/api/lookup_path.go | 10 +++++--- internal/source/gitlab/factory/lookup.go | 22 +++++++++++++++++ internal/source/gitlab/factory/serving.go | 34 ++++++++++++++++++++++++++ internal/source/gitlab/gitlab.go | 22 ++++++----------- 8 files changed, 118 insertions(+), 37 deletions(-) create mode 100644 internal/source/gitlab/factory/lookup.go create mode 100644 internal/source/gitlab/factory/serving.go diff --git a/internal/serving/serverless/function.go b/internal/serving/serverless/function.go index 20d4ec2c..c5d3d179 100644 --- a/internal/serving/serverless/function.go +++ b/internal/serving/serverless/function.go @@ -5,14 +5,14 @@ import "strings" // Function represents a Knative service that is going to be invoked by the // proxied request type Function struct { - Name string // Name is a function name, it includes a "service name" component too - Namespace string // Namespace is a kubernetes namespace this function has been deployed to - BaseDomain string // BaseDomain is a cluster base domain, used to route requests to apropriate service + Name string // Name is a function name, it includes a "service name" component too + Domain string // Domain is a cluster base domain, used to route requests to apropriate service + Namespace string // Namespace is a kubernetes namespace this function has been deployed to } // Host returns a function address that we are going to expose in the `Host:` // header to make it possible to route a proxied request to appropriate service // in a Knative cluster func (f Function) Host() string { - return strings.Join([]string{f.Name, f.Namespace, f.BaseDomain}, ".") + return strings.Join([]string{f.Name, f.Namespace, f.Domain}, ".") } diff --git a/internal/serving/serverless/function_test.go b/internal/serving/serverless/function_test.go index 65d84eb7..39f86025 100644 --- a/internal/serving/serverless/function_test.go +++ b/internal/serving/serverless/function_test.go @@ -8,9 +8,9 @@ import ( func TestFunctionHost(t *testing.T) { function := Function{ - Name: "my-func", - Namespace: "my-namespace-123", - BaseDomain: "knative.example.com", + Name: "my-func", + Domain: "knative.example.com", + Namespace: "my-namespace-123", } require.Equal(t, "my-func.my-namespace-123.knative.example.com", function.Host()) diff --git a/internal/serving/serverless/serverless.go b/internal/serving/serverless/serverless.go index a8d090da..3c360054 100644 --- a/internal/serving/serverless/serverless.go +++ b/internal/serving/serverless/serverless.go @@ -1,10 +1,12 @@ package serverless import ( + "errors" "net/http/httputil" "gitlab.com/gitlab-org/gitlab-pages/internal/httperrors" "gitlab.com/gitlab-org/gitlab-pages/internal/serving" + "gitlab.com/gitlab-org/gitlab-pages/internal/source/gitlab/api" ) // Serverless is a servering used to proxy requests between a client and @@ -13,6 +15,33 @@ type Serverless struct { proxy *httputil.ReverseProxy } +// NewFromAPISource returns a serverless serving instance built from GitLab API +// response +func NewFromAPISource(config api.Serverless) (serving.Serving, error) { + function := Function(config.Function) + + if len(function.Name) == 0 { + return nil, errors.New("incomplete serverless serving config") + } + + certs, err := NewClusterCerts( + config.Cluster.CertificateCert, + config.Cluster.CertificateKey, + ) + if err != nil { + return nil, err + } + + cluster := Cluster{ + Name: config.Cluster.Hostname, + Address: config.Cluster.Address, + Port: config.Cluster.Port, + Certs: certs, + } + + return New(function, cluster), nil +} + // New returns a new serving instance func New(function Function, cluster Cluster) serving.Serving { proxy := httputil.ReverseProxy{ diff --git a/internal/serving/serverless/serverless_test.go b/internal/serving/serverless/serverless_test.go index c330cbda..2dd933cc 100644 --- a/internal/serving/serverless/serverless_test.go +++ b/internal/serving/serverless/serverless_test.go @@ -40,9 +40,9 @@ func TestServeFileHTTP(t *testing.T) { withTestCluster(t, fixture.Certificate, fixture.Key, func(mux *http.ServeMux, server *url.URL, certs *Certs) { serverless := New( Function{ - Name: "my-func", - Namespace: "my-namespace-123", - BaseDomain: "knative.example.com", + Name: "my-func", + Namespace: "my-namespace-123", + Domain: "knative.example.com", }, Cluster{ Name: "knative.gitlab-example.com", @@ -76,9 +76,9 @@ func TestServeFileHTTP(t *testing.T) { withTestCluster(t, fixture.Certificate, fixture.Key, func(mux *http.ServeMux, server *url.URL, certs *Certs) { serverless := New( Function{ - Name: "my-func", - Namespace: "my-namespace-123", - BaseDomain: "knative.example.com", + Name: "my-func", + Namespace: "my-namespace-123", + Domain: "knative.example.com", }, Cluster{ Name: "knative.invalid-gitlab-example.com", @@ -111,9 +111,9 @@ func TestServeFileHTTP(t *testing.T) { withTestCluster(t, fixture.Certificate, fixture.Key, func(mux *http.ServeMux, server *url.URL, certs *Certs) { serverless := New( Function{ - Name: "my-func", - Namespace: "my-namespace-123", - BaseDomain: "knative.example.com", + Name: "my-func", + Namespace: "my-namespace-123", + Domain: "knative.example.com", }, Cluster{ Name: "knative.gitlab-example.com", @@ -147,9 +147,9 @@ func TestServeFileHTTP(t *testing.T) { withTestCluster(t, fixture.Certificate, fixture.Key, func(mux *http.ServeMux, server *url.URL, certs *Certs) { serverless := New( Function{ - Name: "my-func", - Namespace: "my-namespace-123", - BaseDomain: "knative.example.com", + Name: "my-func", + Namespace: "my-namespace-123", + Domain: "knative.example.com", }, Cluster{ Name: "knative.gitlab-example.com", diff --git a/internal/source/gitlab/api/lookup_path.go b/internal/source/gitlab/api/lookup_path.go index e9c0038d..de200e37 100644 --- a/internal/source/gitlab/api/lookup_path.go +++ b/internal/source/gitlab/api/lookup_path.go @@ -6,16 +6,18 @@ type LookupPath struct { AccessControl bool `json:"access_control,omitempty"` HTTPSOnly bool `json:"https_only,omitempty"` Prefix string `json:"prefix,omitempty"` - Source source `json:"source,omitempty"` + Source Source `json:"source,omitempty"` } -type source struct { +// Source describes GitLab Page serving variant +type Source struct { Type string `json:"type,omitempty"` Path string `json:"path,omitempty"` - Serverless serverless `json:"serverless,omitempty"` + Serverless Serverless `json:"serverless,omitempty"` } -type serverless struct { +// Serverless describeg serverless serving configuration +type Serverless struct { Function function `json:"function,omitempty"` Cluster cluster `json:"cluster,omitempty"` } diff --git a/internal/source/gitlab/factory/lookup.go b/internal/source/gitlab/factory/lookup.go new file mode 100644 index 00000000..71bb24bc --- /dev/null +++ b/internal/source/gitlab/factory/lookup.go @@ -0,0 +1,22 @@ +package factory + +import ( + "strings" + + "gitlab.com/gitlab-org/gitlab-pages/internal/serving" + "gitlab.com/gitlab-org/gitlab-pages/internal/source/gitlab/api" +) + +// LookupPath fabricates a serving LookupPath based on the API LookupPath +// `size` argument is DEPRECATED, see +// https://gitlab.com/gitlab-org/gitlab-pages/issues/272 +func LookupPath(size int, lookup api.LookupPath) *serving.LookupPath { + return &serving.LookupPath{ + Prefix: lookup.Prefix, + Path: strings.TrimPrefix(lookup.Source.Path, "/"), + IsNamespaceProject: (lookup.Prefix == "/" && size > 1), + IsHTTPSOnly: lookup.HTTPSOnly, + HasAccessControl: lookup.AccessControl, + ProjectID: uint64(lookup.ProjectID), + } +} diff --git a/internal/source/gitlab/factory/serving.go b/internal/source/gitlab/factory/serving.go new file mode 100644 index 00000000..22b99bc1 --- /dev/null +++ b/internal/source/gitlab/factory/serving.go @@ -0,0 +1,34 @@ +package factory + +import ( + "gitlab.com/gitlab-org/gitlab-pages/internal/serving" + "gitlab.com/gitlab-org/gitlab-pages/internal/serving/disk" + "gitlab.com/gitlab-org/gitlab-pages/internal/serving/serverless" + "gitlab.com/gitlab-org/gitlab-pages/internal/source/gitlab/api" +) + +// Serving fabricates serving based on the GitLab API response +func Serving(lookup api.LookupPath) serving.Serving { + source := lookup.Source + + switch source.Type { + case "file": + return disk.New() + case "serverless": + serving, err := serverless.NewFromAPISource(source.Serverless) + if err != nil { + break + } + + return serving + } + + return DefaultServing() +} + +// DefaultServing returns a serving that we will use as a default one, for +// example to show an error, if API response does not allow us to properly +// fabricate a serving +func DefaultServing() serving.Serving { + return disk.New() +} diff --git a/internal/source/gitlab/gitlab.go b/internal/source/gitlab/gitlab.go index 232239ae..67198232 100644 --- a/internal/source/gitlab/gitlab.go +++ b/internal/source/gitlab/gitlab.go @@ -10,10 +10,10 @@ import ( "gitlab.com/gitlab-org/gitlab-pages/internal/domain" "gitlab.com/gitlab-org/gitlab-pages/internal/request" "gitlab.com/gitlab-org/gitlab-pages/internal/serving" - "gitlab.com/gitlab-org/gitlab-pages/internal/serving/disk" "gitlab.com/gitlab-org/gitlab-pages/internal/source/gitlab/api" "gitlab.com/gitlab-org/gitlab-pages/internal/source/gitlab/cache" "gitlab.com/gitlab-org/gitlab-pages/internal/source/gitlab/client" + "gitlab.com/gitlab-org/gitlab-pages/internal/source/gitlab/factory" ) // Gitlab source represent a new domains configuration source. We fetch all the @@ -46,6 +46,8 @@ func (g *Gitlab) GetDomain(name string) (*domain.Domain, error) { return nil, nil } + // TODO introduce a second-level cache for domains, invalidate using etags + // from first-level cache domain := domain.Domain{ Name: name, CertificateCert: lookup.Domain.Certificate, @@ -68,33 +70,25 @@ func (g *Gitlab) Resolve(r *http.Request) (*serving.Request, error) { } urlPath := path.Clean(r.URL.Path) - lookups := len(response.Domain.LookupPaths) + size := len(response.Domain.LookupPaths) for _, lookup := range response.Domain.LookupPaths { isSubPath := strings.HasPrefix(urlPath, lookup.Prefix) isRootPath := urlPath == path.Clean(lookup.Prefix) if isSubPath || isRootPath { - lookupPath := &serving.LookupPath{ - Prefix: lookup.Prefix, - Path: strings.TrimPrefix(lookup.Source.Path, "/"), - IsNamespaceProject: (lookup.Prefix == "/" && lookups > 1), - IsHTTPSOnly: lookup.HTTPSOnly, - HasAccessControl: lookup.AccessControl, - ProjectID: uint64(lookup.ProjectID), - } - subPath := "" if isSubPath { subPath = strings.TrimPrefix(urlPath, lookup.Prefix) } return &serving.Request{ - Serving: disk.New(), - LookupPath: lookupPath, + Serving: factory.Serving(lookup), + LookupPath: factory.LookupPath(size, lookup), SubPath: subPath}, nil } } - return &serving.Request{Serving: disk.New()}, errors.New("could not match lookup path") + return &serving.Request{Serving: factory.DefaultServing()}, + errors.New("could not match lookup path") } -- cgit v1.2.3