Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-pages.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlessio Caiazza <acaiazza@gitlab.com>2020-02-28 18:51:48 +0300
committerAlessio Caiazza <acaiazza@gitlab.com>2020-02-28 18:51:48 +0300
commit487aaebc4184020c1f7695c55b631fe7989f437d (patch)
treeb2499ae291b9283e0691ce43b0a0ab1091c4acbb
parent70879969bde8aaf97d5ed97d15b904747a44bfb0 (diff)
parent2f086a69431d0095ec2e46c69d3bcea187a67085 (diff)
Merge branch 'feature/gb/serverless-serving-enable' into 'master'
Enable serverless serving See merge request gitlab-org/gitlab-pages!232
-rw-r--r--internal/domain/domain.go50
-rw-r--r--internal/domain/domain_test.go9
-rw-r--r--internal/domain/resolver.go8
-rw-r--r--internal/serving/disk/serving.go22
-rw-r--r--internal/serving/handler.go3
-rw-r--r--internal/serving/request.go35
-rw-r--r--internal/serving/serverless/director.go8
-rw-r--r--internal/serving/serverless/function.go18
-rw-r--r--internal/serving/serverless/function_test.go17
-rw-r--r--internal/serving/serverless/serverless.go31
-rw-r--r--internal/serving/serverless/serverless_test.go24
-rw-r--r--internal/source/disk/custom.go9
-rw-r--r--internal/source/disk/group.go13
-rw-r--r--internal/source/domains.go27
-rw-r--r--internal/source/domains_test.go31
-rw-r--r--internal/source/gitlab/api/lookup_path.go27
-rw-r--r--internal/source/gitlab/factory.go51
-rw-r--r--internal/source/gitlab/factory_test.go64
-rw-r--r--internal/source/gitlab/gitlab.go31
-rw-r--r--internal/source/gitlab/gitlab_test.go46
20 files changed, 367 insertions, 157 deletions
diff --git a/internal/domain/domain.go b/internal/domain/domain.go
index 28eb3196..17a0e1d3 100644
--- a/internal/domain/domain.go
+++ b/internal/domain/domain.go
@@ -19,8 +19,6 @@ type Domain struct {
Resolver Resolver
- serving serving.Serving
-
certificate *tls.Certificate
certificateError error
certificateOnce sync.Once
@@ -39,42 +37,28 @@ func (d *Domain) isUnconfigured() bool {
return d.Resolver == nil
}
-func (d *Domain) resolve(r *http.Request) (*serving.LookupPath, string) {
- lookupPath, subpath, _ := d.Resolver.Resolve(r)
+func (d *Domain) resolve(r *http.Request) *serving.Request {
+ request, _ := d.Resolver.Resolve(r)
- // Current implementation does not return errors in any case
- if lookupPath == nil {
- return nil, ""
+ // TODO improve code around default serving, when `disk` serving gets removed
+ // https://gitlab.com/gitlab-org/gitlab-pages/issues/353
+ if request == nil {
+ return &serving.Request{Serving: disk.New()}
}
- return lookupPath, subpath
+ return request
}
-// GetLookupPath returns a project details based on the request
+// GetLookupPath returns a project details based on the request. It returns nil
+// if project does not exist.
func (d *Domain) GetLookupPath(r *http.Request) *serving.LookupPath {
- lookupPath, _ := d.resolve(r)
-
- return lookupPath
-}
+ request := d.resolve(r)
-// Serving returns domain serving driver
-func (d *Domain) Serving() serving.Serving {
- if d.serving == nil {
- d.serving = disk.New()
+ if request == nil {
+ return nil
}
- return d.serving
-}
-
-func (d *Domain) toHandler(w http.ResponseWriter, r *http.Request) serving.Handler {
- project, subpath := d.resolve(r)
-
- return serving.Handler{
- Writer: w,
- Request: r,
- LookupPath: project,
- SubPath: subpath,
- }
+ return request.LookupPath
}
// IsHTTPSOnly figures out if the request should be handled with HTTPS
@@ -168,7 +152,9 @@ func (d *Domain) ServeFileHTTP(w http.ResponseWriter, r *http.Request) bool {
return true
}
- return d.Serving().ServeFileHTTP(d.toHandler(w, r))
+ request := d.resolve(r)
+
+ return request.ServeFileHTTP(w, r)
}
// ServeNotFoundHTTP serves the not found pages from the projects.
@@ -178,5 +164,7 @@ func (d *Domain) ServeNotFoundHTTP(w http.ResponseWriter, r *http.Request) {
return
}
- d.Serving().ServeNotFoundHTTP(d.toHandler(w, r))
+ request := d.resolve(r)
+
+ request.ServeNotFoundHTTP(w, r)
}
diff --git a/internal/domain/domain_test.go b/internal/domain/domain_test.go
index a43d3c9a..aa95eb95 100644
--- a/internal/domain/domain_test.go
+++ b/internal/domain/domain_test.go
@@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/gitlab-pages/internal/serving"
+ "gitlab.com/gitlab-org/gitlab-pages/internal/serving/disk"
"gitlab.com/gitlab-org/gitlab-pages/internal/testhelpers"
)
@@ -21,8 +22,12 @@ type stubbedResolver struct {
err error
}
-func (resolver *stubbedResolver) Resolve(*http.Request) (*serving.LookupPath, string, error) {
- return resolver.project, resolver.subpath, resolver.err
+func (resolver *stubbedResolver) Resolve(*http.Request) (*serving.Request, error) {
+ return &serving.Request{
+ Serving: disk.New(),
+ LookupPath: resolver.project,
+ SubPath: resolver.subpath,
+ }, resolver.err
}
func serveFileOrNotFound(domain *Domain) http.HandlerFunc {
diff --git a/internal/domain/resolver.go b/internal/domain/resolver.go
index 6bc10f8c..4b93baed 100644
--- a/internal/domain/resolver.go
+++ b/internal/domain/resolver.go
@@ -6,9 +6,9 @@ import (
"gitlab.com/gitlab-org/gitlab-pages/internal/serving"
)
-// Resolver represents an interface responsible for resolving a project
-// per-request
+// Resolver represents an interface responsible for resolving a pages serving
+// request for each HTTP request
type Resolver interface {
- // Resolve returns a project with a file path and an error if it occurred
- Resolve(*http.Request) (*serving.LookupPath, string, error)
+ // Resolve returns a serving request and an error if it occurred
+ Resolve(*http.Request) (*serving.Request, error)
}
diff --git a/internal/serving/disk/serving.go b/internal/serving/disk/serving.go
index db184d3c..682791fe 100644
--- a/internal/serving/disk/serving.go
+++ b/internal/serving/disk/serving.go
@@ -5,15 +5,17 @@ import (
"gitlab.com/gitlab-org/gitlab-pages/internal/serving"
)
-// Serving describes a disk access serving
-type Serving struct {
- Reader
+var disk *Disk = &Disk{}
+
+// Disk describes a disk access serving
+type Disk struct {
+ reader Reader
}
// ServeFileHTTP serves a file from disk and returns true. It returns false
// when a file could not been found.
-func (s *Serving) ServeFileHTTP(h serving.Handler) bool {
- if s.tryFile(h) == nil {
+func (s *Disk) ServeFileHTTP(h serving.Handler) bool {
+ if s.reader.tryFile(h) == nil {
return true
}
@@ -21,8 +23,8 @@ func (s *Serving) ServeFileHTTP(h serving.Handler) bool {
}
// ServeNotFoundHTTP tries to read a custom 404 page
-func (s *Serving) ServeNotFoundHTTP(h serving.Handler) {
- if s.tryNotFound(h) == nil {
+func (s *Disk) ServeNotFoundHTTP(h serving.Handler) {
+ if s.reader.tryNotFound(h) == nil {
return
}
@@ -33,5 +35,9 @@ func (s *Serving) ServeNotFoundHTTP(h serving.Handler) {
// New returns a serving instance that is capable of reading files
// from the disk
func New() serving.Serving {
- return &Serving{}
+ if disk == nil {
+ disk = &Disk{}
+ }
+
+ return disk
}
diff --git a/internal/serving/handler.go b/internal/serving/handler.go
index 99c4ca2f..a0d66ecb 100644
--- a/internal/serving/handler.go
+++ b/internal/serving/handler.go
@@ -8,6 +8,5 @@ type Handler struct {
Writer http.ResponseWriter
Request *http.Request
LookupPath *LookupPath
- // Parsed representation of Request.URI that is part of LookupPath.Prefix
- SubPath string
+ SubPath string
}
diff --git a/internal/serving/request.go b/internal/serving/request.go
new file mode 100644
index 00000000..694d0df4
--- /dev/null
+++ b/internal/serving/request.go
@@ -0,0 +1,35 @@
+package serving
+
+import "net/http"
+
+// Request is a type that aggregates a serving itself, project lookup path and
+// a request subpath based on an incoming request to serve page.
+type Request struct {
+ Serving Serving // Serving chosen to serve this request
+ LookupPath *LookupPath // LookupPath contains pages project details
+ SubPath string // Subpath is a URL path subcomponent for this request
+}
+
+// ServeFileHTTP forwards serving request handler to the serving itself
+func (s *Request) ServeFileHTTP(w http.ResponseWriter, r *http.Request) bool {
+ handler := Handler{
+ Writer: w,
+ Request: r,
+ LookupPath: s.LookupPath,
+ SubPath: s.SubPath,
+ }
+
+ return s.Serving.ServeFileHTTP(handler)
+}
+
+// ServeNotFoundHTTP forwards serving request handler to the serving itself
+func (s *Request) ServeNotFoundHTTP(w http.ResponseWriter, r *http.Request) {
+ handler := Handler{
+ Writer: w,
+ Request: r,
+ LookupPath: s.LookupPath,
+ SubPath: s.SubPath,
+ }
+
+ s.Serving.ServeNotFoundHTTP(handler)
+}
diff --git a/internal/serving/serverless/director.go b/internal/serving/serverless/director.go
index 4478b104..3f1bc99a 100644
--- a/internal/serving/serverless/director.go
+++ b/internal/serving/serverless/director.go
@@ -8,12 +8,10 @@ import (
// NewDirectorFunc returns a director function capable of configuring a proxy
// request
-func NewDirectorFunc(function Function) func(*http.Request) {
+func NewDirectorFunc(service string) func(*http.Request) {
return func(request *http.Request) {
- host := function.Host()
-
- request.Host = host
- request.URL.Host = host
+ request.Host = service
+ request.URL.Host = service
request.URL.Scheme = "https"
request.Header.Set("User-Agent", "GitLab Pages Daemon")
request.Header.Set("X-Forwarded-For", realip.FromRequest(request))
diff --git a/internal/serving/serverless/function.go b/internal/serving/serverless/function.go
deleted file mode 100644
index 20d4ec2c..00000000
--- a/internal/serving/serverless/function.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package serverless
-
-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
-}
-
-// 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}, ".")
-}
diff --git a/internal/serving/serverless/function_test.go b/internal/serving/serverless/function_test.go
deleted file mode 100644
index 65d84eb7..00000000
--- a/internal/serving/serverless/function_test.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package serverless
-
-import (
- "testing"
-
- "github.com/stretchr/testify/require"
-)
-
-func TestFunctionHost(t *testing.T) {
- function := Function{
- Name: "my-func",
- Namespace: "my-namespace-123",
- BaseDomain: "knative.example.com",
- }
-
- 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 a73affeb..e1881362 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"
"gitlab.com/gitlab-org/gitlab-pages/metrics"
)
@@ -14,10 +16,35 @@ 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) {
+ if len(config.Service) == 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(config.Service, cluster), nil
+}
+
// New returns a new serving instance
-func New(function Function, cluster Cluster) serving.Serving {
+func New(service string, cluster Cluster) serving.Serving {
proxy := httputil.ReverseProxy{
- Director: NewDirectorFunc(function),
+ Director: NewDirectorFunc(service),
Transport: NewTransport(cluster),
ErrorHandler: NewErrorHandler(),
}
diff --git a/internal/serving/serverless/serverless_test.go b/internal/serving/serverless/serverless_test.go
index c330cbda..ebd14343 100644
--- a/internal/serving/serverless/serverless_test.go
+++ b/internal/serving/serverless/serverless_test.go
@@ -39,11 +39,7 @@ func TestServeFileHTTP(t *testing.T) {
t.Run("when proxying simple request to a cluster", func(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",
- },
+ "my-func.my-namespace-123.knative.example.com",
Cluster{
Name: "knative.gitlab-example.com",
Address: server.Hostname(),
@@ -75,11 +71,7 @@ func TestServeFileHTTP(t *testing.T) {
t.Run("when proxying request with invalid hostname", func(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",
- },
+ "my-func.my-namespace-123.knative.example.com",
Cluster{
Name: "knative.invalid-gitlab-example.com",
Address: server.Hostname(),
@@ -110,11 +102,7 @@ func TestServeFileHTTP(t *testing.T) {
t.Run("when a cluster responds with an error", func(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",
- },
+ "my-func.my-namespace-123.knative.example.com",
Cluster{
Name: "knative.gitlab-example.com",
Address: server.Hostname(),
@@ -146,11 +134,7 @@ func TestServeFileHTTP(t *testing.T) {
t.Run("when a cluster responds correctly", func(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",
- },
+ "my-func.my-namespace-123.knative.example.com",
Cluster{
Name: "knative.gitlab-example.com",
Address: server.Hostname(),
diff --git a/internal/source/disk/custom.go b/internal/source/disk/custom.go
index cc4f3f4c..2668ed81 100644
--- a/internal/source/disk/custom.go
+++ b/internal/source/disk/custom.go
@@ -4,6 +4,7 @@ import (
"net/http"
"gitlab.com/gitlab-org/gitlab-pages/internal/serving"
+ "gitlab.com/gitlab-org/gitlab-pages/internal/serving/disk"
)
type customProjectResolver struct {
@@ -12,7 +13,7 @@ type customProjectResolver struct {
path string
}
-func (p *customProjectResolver) Resolve(r *http.Request) (*serving.LookupPath, string, error) {
+func (p *customProjectResolver) Resolve(r *http.Request) (*serving.Request, error) {
lookupPath := &serving.LookupPath{
Prefix: "/",
Path: p.path,
@@ -22,5 +23,9 @@ func (p *customProjectResolver) Resolve(r *http.Request) (*serving.LookupPath, s
ProjectID: p.config.ID,
}
- return lookupPath, r.URL.Path, nil
+ return &serving.Request{
+ Serving: disk.New(),
+ LookupPath: lookupPath,
+ SubPath: r.URL.Path,
+ }, nil
}
diff --git a/internal/source/disk/group.go b/internal/source/disk/group.go
index 9f466bc4..e0365bbd 100644
--- a/internal/source/disk/group.go
+++ b/internal/source/disk/group.go
@@ -8,6 +8,7 @@ import (
"gitlab.com/gitlab-org/gitlab-pages/internal/host"
"gitlab.com/gitlab-org/gitlab-pages/internal/serving"
+ "gitlab.com/gitlab-org/gitlab-pages/internal/serving/disk"
)
const (
@@ -77,11 +78,13 @@ func (g *Group) getProjectConfigWithSubpath(r *http.Request) (*projectConfig, st
// Resolve tries to find project and its config recursively for a given request
// to a group domain
-func (g *Group) Resolve(r *http.Request) (*serving.LookupPath, string, error) {
+func (g *Group) Resolve(r *http.Request) (*serving.Request, error) {
projectConfig, prefix, projectPath, subPath := g.getProjectConfigWithSubpath(r)
if projectConfig == nil {
- return nil, "", nil // it is not an error when project does not exist
+ // it is not an error when project does not exist, in that case
+ // serving.Request.LookupPath is nil.
+ return &serving.Request{Serving: disk.New()}, nil
}
lookupPath := &serving.LookupPath{
@@ -93,5 +96,9 @@ func (g *Group) Resolve(r *http.Request) (*serving.LookupPath, string, error) {
ProjectID: projectConfig.ID,
}
- return lookupPath, subPath, nil
+ return &serving.Request{
+ Serving: disk.New(),
+ LookupPath: lookupPath,
+ SubPath: subPath,
+ }, nil
}
diff --git a/internal/source/domains.go b/internal/source/domains.go
index 79357766..11794b91 100644
--- a/internal/source/domains.go
+++ b/internal/source/domains.go
@@ -2,6 +2,7 @@ package source
import (
"errors"
+ "regexp"
"time"
log "github.com/sirupsen/logrus"
@@ -13,7 +14,14 @@ import (
"gitlab.com/gitlab-org/gitlab-pages/internal/source/gitlab"
)
-var gitlabSourceConfig gitlabsourceconfig.GitlabSourceConfig
+var (
+ gitlabSourceConfig gitlabsourceconfig.GitlabSourceConfig
+
+ // serverlessDomainRegex is a regular expression we use to check if a domain
+ // is a serverless domain, to short circut gitlab source rollout. It can be
+ // removed after the rollout is done
+ serverlessDomainRegex = regexp.MustCompile(`^[^.]+-[[:xdigit:]]{2}a1[[:xdigit:]]{10}f2[[:xdigit:]]{2}[[:xdigit:]]+-?.*`)
+)
func init() {
// Start watching the config file for domains that will use the new `gitlab` source,
@@ -78,6 +86,13 @@ func (d *Domains) source(domain string) Source {
return d.disk
}
+ // This check is only needed until we enable `d.gitlab` source in all
+ // environments (including on-premises installations) followed by removal of
+ // `d.disk` source. This can be safely removed afterwards.
+ if IsServerlessDomain(domain) {
+ return d.gitlab
+ }
+
for _, name := range gitlabSourceConfig.Domains.Enabled {
if domain == name {
return d.gitlab
@@ -98,3 +113,13 @@ func (d *Domains) source(domain string) Source {
return d.disk
}
+
+// IsServerlessDomain checks if a domain requested is a serverless domain we
+// need to handle differently.
+//
+// Domain is a serverless domain when it matches `serverlessDomainRegex`. The
+// regular expression is also defined on the gitlab-rails side, see
+// https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/models/serverless/domain.rb#L7
+func IsServerlessDomain(domain string) bool {
+ return serverlessDomainRegex.MatchString(domain)
+}
diff --git a/internal/source/domains_test.go b/internal/source/domains_test.go
index d639b02d..9afde412 100644
--- a/internal/source/domains_test.go
+++ b/internal/source/domains_test.go
@@ -109,6 +109,37 @@ func TestGetDomain(t *testing.T) {
require.Nil(t, domain)
require.NoError(t, err)
})
+
+ t.Run("when requesting a serverless domain", func(t *testing.T) {
+ testDomain := "func-aba1aabbccddeef2abaabbcc.serverless.gitlab.io"
+
+ newSource := NewMockSource()
+ newSource.On("GetDomain", testDomain).
+ Return(&domain.Domain{Name: testDomain}, nil).
+ Once()
+ defer newSource.AssertExpectations(t)
+
+ domains := &Domains{
+ disk: disk.New(),
+ gitlab: newSource,
+ }
+
+ domains.GetDomain(testDomain)
+ })
+}
+
+func TestIsServerlessDomain(t *testing.T) {
+ t.Run("when a domain is serverless domain", func(t *testing.T) {
+ require.True(t, IsServerlessDomain("some-function-aba1aabbccddeef2abaabbcc.serverless.gitlab.io"))
+ })
+
+ t.Run("when a domain is serverless domain with environment", func(t *testing.T) {
+ require.True(t, IsServerlessDomain("some-function-aba1aabbccddeef2abaabbcc-testing.serverless.gitlab.io"))
+ })
+
+ t.Run("when a domain is not a serverless domain", func(t *testing.T) {
+ require.False(t, IsServerlessDomain("somedomain.gitlab.io"))
+ })
}
func TestGetDomainWithIncrementalrolloutOfGitLabSource(t *testing.T) {
diff --git a/internal/source/gitlab/api/lookup_path.go b/internal/source/gitlab/api/lookup_path.go
index b0407638..77b264ff 100644
--- a/internal/source/gitlab/api/lookup_path.go
+++ b/internal/source/gitlab/api/lookup_path.go
@@ -6,8 +6,27 @@ type LookupPath struct {
AccessControl bool `json:"access_control,omitempty"`
HTTPSOnly bool `json:"https_only,omitempty"`
Prefix string `json:"prefix,omitempty"`
- Source struct {
- Type string `json:"type,omitempty"`
- Path string `json:"path,omitempty"`
- }
+ Source Source `json:"source,omitempty"`
+}
+
+// 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 describes serverless serving configuration
+type Serverless struct {
+ Service string `json:"service,omitempty"`
+ Cluster Cluster `json:"cluster,omitempty"`
+}
+
+// Cluster describes serverless cluster configuration
+type Cluster struct {
+ Address string `json:"address,omitempty"`
+ Port string `json:"port,omitempty"`
+ Hostname string `json:"hostname,omitempty"`
+ CertificateCert string `json:"cert,omitempty"`
+ CertificateKey string `json:"key,omitempty"`
}
diff --git a/internal/source/gitlab/factory.go b/internal/source/gitlab/factory.go
new file mode 100644
index 00000000..d526994f
--- /dev/null
+++ b/internal/source/gitlab/factory.go
@@ -0,0 +1,51 @@
+package gitlab
+
+import (
+ "strings"
+
+ log "github.com/sirupsen/logrus"
+
+ "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"
+)
+
+// fabricateLookupPath fabricates a serving LookupPath based on the API LookupPath
+// `size` argument is DEPRECATED, see
+// https://gitlab.com/gitlab-org/gitlab-pages/issues/272
+func fabricateLookupPath(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),
+ }
+}
+
+// fabricateServing fabricates serving based on the GitLab API response
+func fabricateServing(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 {
+ log.WithError(err).Errorf("could not fabricate serving for project %d", lookup.ProjectID)
+
+ break
+ }
+
+ return serving
+ }
+
+ return defaultServing()
+}
+
+func defaultServing() serving.Serving {
+ return disk.New()
+}
diff --git a/internal/source/gitlab/factory_test.go b/internal/source/gitlab/factory_test.go
new file mode 100644
index 00000000..2f3e1994
--- /dev/null
+++ b/internal/source/gitlab/factory_test.go
@@ -0,0 +1,64 @@
+package gitlab
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "gitlab.com/gitlab-org/gitlab-pages/internal/fixture"
+ "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"
+)
+
+func TestFabricateLookupPath(t *testing.T) {
+ t.Run("when lookup path is not a namespace project", func(t *testing.T) {
+ lookup := api.LookupPath{Prefix: "/something"}
+
+ path := fabricateLookupPath(1, lookup)
+
+ require.Equal(t, path.Prefix, "/something")
+ require.False(t, path.IsNamespaceProject)
+ })
+
+ t.Run("when lookup path is a namespace project", func(t *testing.T) {
+ lookup := api.LookupPath{Prefix: "/"}
+
+ path := fabricateLookupPath(2, lookup)
+
+ require.Equal(t, path.Prefix, "/")
+ require.True(t, path.IsNamespaceProject)
+ })
+}
+
+func TestFabricateServing(t *testing.T) {
+ t.Run("when lookup path requires disk serving", func(t *testing.T) {
+ lookup := api.LookupPath{
+ Prefix: "/",
+ Source: api.Source{Type: "file"},
+ }
+
+ require.IsType(t, &disk.Disk{}, fabricateServing(lookup))
+ })
+
+ t.Run("when lookup path requires serverless serving", func(t *testing.T) {
+ lookup := api.LookupPath{
+ Prefix: "/",
+ Source: api.Source{
+ Type: "serverless",
+ Serverless: api.Serverless{
+ Service: "my-func.knative.example.com",
+ Cluster: api.Cluster{
+ Address: "127.0.0.10",
+ Port: "443",
+ Hostname: "my-cluster.example.com",
+ CertificateCert: fixture.Certificate,
+ CertificateKey: fixture.Key,
+ },
+ },
+ },
+ }
+
+ require.IsType(t, &serverless.Serverless{}, fabricateServing(lookup))
+ })
+}
diff --git a/internal/source/gitlab/gitlab.go b/internal/source/gitlab/gitlab.go
index cce70733..6260200a 100644
--- a/internal/source/gitlab/gitlab.go
+++ b/internal/source/gitlab/gitlab.go
@@ -45,6 +45,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,
@@ -55,40 +57,39 @@ func (g *Gitlab) GetDomain(name string) (*domain.Domain, error) {
return &domain, nil
}
-// Resolve is supposed to get the serving lookup path based on the request from
-// the GitLab source
-func (g *Gitlab) Resolve(r *http.Request) (*serving.LookupPath, string, error) {
+// Resolve is supposed to return the serving request containing lookup path,
+// subpath for a given lookup and the serving itself created based on a request
+// from GitLab pages domains source
+func (g *Gitlab) Resolve(r *http.Request) (*serving.Request, error) {
host := request.GetHostWithoutPort(r)
response := g.client.Resolve(r.Context(), host)
if response.Error != nil {
- return nil, "", response.Error
+ return &serving.Request{Serving: defaultServing()}, response.Error
}
urlPath := path.Clean(r.URL.Path)
+ 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 == "/" && len(response.Domain.LookupPaths) > 1),
- IsHTTPSOnly: lookup.HTTPSOnly,
- HasAccessControl: lookup.AccessControl,
- ProjectID: uint64(lookup.ProjectID),
- }
-
subPath := ""
if isSubPath {
subPath = strings.TrimPrefix(urlPath, lookup.Prefix)
}
- return lookupPath, subPath, nil
+ return &serving.Request{
+ Serving: fabricateServing(lookup),
+ LookupPath: fabricateLookupPath(size, lookup),
+ SubPath: subPath}, nil
}
}
- return nil, "", errors.New("could not match lookup path")
+ // TODO improve code around default serving, when `disk` serving gets removed
+ // https://gitlab.com/gitlab-org/gitlab-pages/issues/353
+ return &serving.Request{Serving: defaultServing()},
+ errors.New("could not match lookup path")
}
diff --git a/internal/source/gitlab/gitlab_test.go b/internal/source/gitlab/gitlab_test.go
index 0e855f10..e6f194ee 100644
--- a/internal/source/gitlab/gitlab_test.go
+++ b/internal/source/gitlab/gitlab_test.go
@@ -39,62 +39,62 @@ func TestResolve(t *testing.T) {
target := "https://test.gitlab.io:443/my/pages/project/"
request := httptest.NewRequest("GET", target, nil)
- lookup, subpath, err := source.Resolve(request)
+ response, err := source.Resolve(request)
require.NoError(t, err)
- require.Equal(t, "/my/pages/project/", lookup.Prefix)
- require.Equal(t, "some/path/to/project/", lookup.Path)
- require.Equal(t, "", subpath)
- require.False(t, lookup.IsNamespaceProject)
+ require.Equal(t, "/my/pages/project/", response.LookupPath.Prefix)
+ require.Equal(t, "some/path/to/project/", response.LookupPath.Path)
+ require.Equal(t, "", response.SubPath)
+ require.False(t, response.LookupPath.IsNamespaceProject)
})
t.Run("when requesting a nested group project with full path", func(t *testing.T) {
target := "https://test.gitlab.io:443/my/pages/project/path/index.html"
request := httptest.NewRequest("GET", target, nil)
- lookup, subpath, err := source.Resolve(request)
+ response, err := source.Resolve(request)
require.NoError(t, err)
- require.Equal(t, "/my/pages/project/", lookup.Prefix)
- require.Equal(t, "some/path/to/project/", lookup.Path)
- require.Equal(t, "path/index.html", subpath)
- require.False(t, lookup.IsNamespaceProject)
+ require.Equal(t, "/my/pages/project/", response.LookupPath.Prefix)
+ require.Equal(t, "some/path/to/project/", response.LookupPath.Path)
+ require.Equal(t, "path/index.html", response.SubPath)
+ require.False(t, response.LookupPath.IsNamespaceProject)
})
t.Run("when requesting the group root project with root path", func(t *testing.T) {
target := "https://test.gitlab.io:443/"
request := httptest.NewRequest("GET", target, nil)
- lookup, subpath, err := source.Resolve(request)
+ response, err := source.Resolve(request)
require.NoError(t, err)
- require.Equal(t, "/", lookup.Prefix)
- require.Equal(t, "some/path/to/project-3/", lookup.Path)
- require.Equal(t, "", subpath)
- require.True(t, lookup.IsNamespaceProject)
+ require.Equal(t, "/", response.LookupPath.Prefix)
+ require.Equal(t, "some/path/to/project-3/", response.LookupPath.Path)
+ require.Equal(t, "", response.SubPath)
+ require.True(t, response.LookupPath.IsNamespaceProject)
})
t.Run("when requesting the group root project with full path", func(t *testing.T) {
target := "https://test.gitlab.io:443/path/to/index.html"
request := httptest.NewRequest("GET", target, nil)
- lookup, subpath, err := source.Resolve(request)
+ response, err := source.Resolve(request)
require.NoError(t, err)
- require.Equal(t, "/", lookup.Prefix)
- require.Equal(t, "path/to/index.html", subpath)
- require.Equal(t, "some/path/to/project-3/", lookup.Path)
- require.True(t, lookup.IsNamespaceProject)
+ require.Equal(t, "/", response.LookupPath.Prefix)
+ require.Equal(t, "path/to/index.html", response.SubPath)
+ require.Equal(t, "some/path/to/project-3/", response.LookupPath.Path)
+ require.True(t, response.LookupPath.IsNamespaceProject)
})
t.Run("when request path has not been sanitized", func(t *testing.T) {
target := "https://test.gitlab.io:443/something/../something/../my/pages/project/index.html"
request := httptest.NewRequest("GET", target, nil)
- lookup, subpath, err := source.Resolve(request)
+ response, err := source.Resolve(request)
require.NoError(t, err)
- require.Equal(t, "/my/pages/project/", lookup.Prefix)
- require.Equal(t, "index.html", subpath)
+ require.Equal(t, "/my/pages/project/", response.LookupPath.Prefix)
+ require.Equal(t, "index.html", response.SubPath)
})
}