diff options
-rw-r--r-- | app.go | 17 | ||||
-rw-r--r-- | helpers.go | 5 | ||||
-rw-r--r-- | helpers_test.go | 74 | ||||
-rw-r--r-- | internal/domain/domain.go (renamed from domain.go) | 72 | ||||
-rw-r--r-- | internal/domain/domain_config.go (renamed from domain_config.go) | 2 | ||||
-rw-r--r-- | internal/domain/domain_config_test.go (renamed from domain_config_test.go) | 2 | ||||
-rw-r--r-- | internal/domain/domain_test.go (renamed from domain_test.go) | 94 | ||||
-rw-r--r-- | internal/domain/map.go (renamed from domains.go) | 39 | ||||
-rw-r--r-- | internal/domain/map_test.go (renamed from domains_test.go) | 22 | ||||
-rw-r--r-- | internal/fixture/fixtures.go | 56 |
10 files changed, 200 insertions, 183 deletions
@@ -16,6 +16,7 @@ import ( log "github.com/sirupsen/logrus" "gitlab.com/gitlab-org/gitlab-pages/internal/artifact" + "gitlab.com/gitlab-org/gitlab-pages/internal/domain" "gitlab.com/gitlab-org/gitlab-pages/internal/httperrors" "gitlab.com/gitlab-org/gitlab-pages/metrics" ) @@ -29,7 +30,7 @@ var ( type theApp struct { appConfig - dm domainMap + dm domain.Map lock sync.RWMutex Artifact *artifact.Artifact } @@ -38,7 +39,7 @@ func (a *theApp) isReady() bool { return a.dm != nil } -func (a *theApp) domain(host string) *domain { +func (a *theApp) domain(host string) *domain.D { host = strings.ToLower(host) a.lock.RLock() defer a.lock.RUnlock() @@ -52,7 +53,7 @@ func (a *theApp) ServeTLS(ch *tls.ClientHelloInfo) (*tls.Certificate, error) { } if domain := a.domain(ch.ServerName); domain != nil { - tls, _ := domain.ensureCertificate() + tls, _ := domain.EnsureCertificate() return tls, nil } @@ -76,7 +77,7 @@ func (a *theApp) redirectToHTTPS(w http.ResponseWriter, r *http.Request, statusC http.Redirect(w, r, u.String(), statusCode) } -func (a *theApp) getHostAndDomain(r *http.Request) (host string, domain *domain) { +func (a *theApp) getHostAndDomain(r *http.Request) (host string, domain *domain.D) { host, _, err := net.SplitHostPort(r.Host) if err != nil { host = r.Host @@ -85,7 +86,7 @@ func (a *theApp) getHostAndDomain(r *http.Request) (host string, domain *domain) return host, a.domain(host) } -func (a *theApp) tryAuxiliaryHandlers(w http.ResponseWriter, r *http.Request, https bool, host string, domain *domain) bool { +func (a *theApp) tryAuxiliaryHandlers(w http.ResponseWriter, r *http.Request, https bool, host string, domain *domain.D) bool { // short circuit content serving to check for a status page if r.RequestURI == a.appConfig.StatusPath { a.healthCheck(w, r, https) @@ -114,7 +115,7 @@ func (a *theApp) tryAuxiliaryHandlers(w http.ResponseWriter, r *http.Request, ht return true } - if !https && domain.isHTTPSOnly(r) { + if !https && domain.IsHTTPSOnly(r) { a.redirectToHTTPS(w, r, http.StatusMovedPermanently) return true } @@ -157,7 +158,7 @@ func (a *theApp) ServeProxy(ww http.ResponseWriter, r *http.Request) { a.serveContent(ww, r, https) } -func (a *theApp) UpdateDomains(dm domainMap) { +func (a *theApp) UpdateDomains(dm domain.Map) { a.lock.Lock() defer a.lock.Unlock() a.dm = dm @@ -216,7 +217,7 @@ func (a *theApp) Run() { }(a.ListenMetrics) } - go watchDomains(a.Domain, a.UpdateDomains, time.Second) + go domain.Watch(a.Domain, a.UpdateDomains, time.Second) wg.Wait() } @@ -3,7 +3,6 @@ package main import ( "io/ioutil" "net" - "strings" ) func readFile(file string) (result []byte) { @@ -28,7 +27,3 @@ func createSocket(addr string) (l net.Listener, fd uintptr) { fd = f.Fd() return } - -func endsWithSlash(path string) bool { - return strings.HasSuffix(path, "/") -} diff --git a/helpers_test.go b/helpers_test.go index 3155e1f6..656585c8 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -15,8 +15,8 @@ import ( "testing" "time" - log "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitlab-pages/internal/fixture" ) type tWriter struct { @@ -29,21 +29,6 @@ func (t *tWriter) Write(b []byte) (int, error) { return len(b), nil } -var chdirSet = false - -func setUpTests() { - if chdirSet { - return - } - - err := os.Chdir("shared/pages") - if err != nil { - log.WithError(err).Print("chdir") - } else { - chdirSet = true - } -} - // The HTTPS certificate isn't signed by anyone. This http client is set up // so it can talk to servers using it. var ( @@ -62,62 +47,11 @@ var ( }, } - CertificateFixture = `-----BEGIN CERTIFICATE----- -MIIDZDCCAkygAwIBAgIRAOtN9/zy+gFjdsgpKq3QRdQwDQYJKoZIhvcNAQELBQAw -MzEUMBIGA1UEChMLTG9nIENvdXJpZXIxGzAZBgNVBAMTEmdpdGxhYi1leGFtcGxl -LmNvbTAgFw0xODAzMjMxODMwMDZaGA8yMTE4MDIyNzE4MzAwNlowMzEUMBIGA1UE -ChMLTG9nIENvdXJpZXIxGzAZBgNVBAMTEmdpdGxhYi1leGFtcGxlLmNvbTCCASIw -DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKULsxpnazXX5RsVzayrAQB+lWwr -Wef5L5eDhSsIsBLbelYp5YB4TmVRt5x7bWKOOJSBsOfwHZHKJXdu+uuX2RenZlhk -3Qpq9XGaPZjYm/NHi8gBHPAtz5sG5VaKNvkfTzRGnO9CWA9TM1XtYiOBq94dO+H3 -c+5jP5Yw+mJ+hA+i2058zF8nRlUHArEno2ofrHwE0LMZ11VskpXtWnVfs3voLs8p -r76KXPBFkMJR4qkWrMDF5Y5MbsQ0zisn6KXrTyV0S4MQh4vSyPdFHnEzvJ07rm5x -4RTWrjgQeQ2DjZjQvRmaDzlVBK9kaMkJ1Si3agK+gpji6d6WZ/Mb2el1GK8CAwEA -AaNxMG8wDgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud -EwEB/wQFMAMBAf8wNwYDVR0RBDAwLoIUKi5naXRsYWItZXhhbXBsZS5jb22HBH8A -AAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggEBAJ0NM8apK0xI -YxMstP/dCQXtR0wyREGSD/eOpeY3bWlqCbpRgMFUGjQlrsEozcPZOCSCKX5p+tym -7GsnYtXkwbsuURoSz+5IlhRPVHcUlUeGRdv3/gCd8fDXiigALCsB6GrkMG5cUfh+ -x5p52AC3eQdWTDoxNou+2gzwkAl8iJc13Ykusst0YUqcsXKqTuei2quxFv0pEBSO -p8wEixoicLFNqPnIDmgx5894DAn0bccNXgRWtq8lLbdhGUlBbpatevvFMgNvFUbe -eeGb9D0EfpxmzxUl+L0xZtfg3f7cu5AgLG8tb6l4AK6NPVuXN8DmUgvnauWJjZME -fgStI+IRNVg= ------END CERTIFICATE----- -` - - KeyFixture = `-----BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEApQuzGmdrNdflGxXNrKsBAH6VbCtZ5/kvl4OFKwiwEtt6Vinl -gHhOZVG3nHttYo44lIGw5/Adkcold27665fZF6dmWGTdCmr1cZo9mNib80eLyAEc -8C3PmwblVoo2+R9PNEac70JYD1MzVe1iI4Gr3h074fdz7mM/ljD6Yn6ED6LbTnzM -XydGVQcCsSejah+sfATQsxnXVWySle1adV+ze+guzymvvopc8EWQwlHiqRaswMXl -jkxuxDTOKyfopetPJXRLgxCHi9LI90UecTO8nTuubnHhFNauOBB5DYONmNC9GZoP -OVUEr2RoyQnVKLdqAr6CmOLp3pZn8xvZ6XUYrwIDAQABAoIBAHhP5QnUZeTkMtDh -vgKmzZ4sqIQnvexKTBUo/MR4GtJESBPTisdx68QUI8LgfsafYkNvnyQUd5m1QEam -Eif3k3uYvhSlwjQ78BwWEdz/2f8oIo9zsEKtQm+CQWAqdRR5bGVxLCmFtWfGgN+c -ojO77SuHKAX7OvmGQ+4aWgu+qkoyg/chIpPXMduAjLMtN3eg60ZqJ5KrKuIF63Bb -xkPQvzJueB9SfUurmKjUltDMx6G/9RZyS0OIRGyL9Qp8MZ8jE23cXOcDgm0HhkPq -W4LU++aWAOLYziTjnhjJ+4Iz9R7U8sCmk1wgnK/tapVcJf41R98WuGluyjXpsXgA -k7vmofECgYEAzuGun9lZ7xGwPifp6vGanWXnW+JiZgCTGuHWgQLIXWYcLfoI3kpH -eLBYINBwvjIQ7P6UxsGSSXd+T82t+8W2LLc2fiKFKE1LVySpH99+cfmIPXxrviOz -GBX9LTdSCdGkgb54m8aJCpNFnKw5wYgcW1L8CaXXly2Z/KNrGR9R/YUCgYEAzDs4 -19HqlutGLTC30/ziiiIfDaBbX9AzBdUfp9GdT53Mi/7bfxpW/sL4RjG2fGgmN6ua -fh5npT9AB1ldcEg2qfyOJPt1Ubdi6ek9lx8AB2RMhwdihgX+7bjVMFtjg4b8z5C1 -jQbEr1rhFdpaGyNehtAXDgCbDWQBYnBrmM0rCaMCgYBip1Qyfd9ZFcJJoZb2pofo -jvOo6Weq5JNBungjxUPu5gaCFj2sYxd6Af3EiCF7UTypBy3DKgOsbQMa4yYYbcvV -vviJZcTB1zoaMC1GObl+eFPzniVy4mtBDRtSOJMyg3pDNKUnA6HOHTSQ5cAU/ecn -1YbCwwbv3JsV0of7zue2UQKBgQCVc0j3dd9rLSQfcaUz9bx5RNrgh9YV2S9dN0aA -8f1iA6FpWMiazFWY/GfeRga6JyTAXE0juXAzFoPuXNDpl46Y+f2yxmhlsgMqFMpD -SiYlQppVvWu1k7GnmDg5uMarux5JbiXM24UWpTRNX4nMjidgE+qrDnpoZCQ3Ovkh -yhGSbQKBgD3VEnPiSUmXBo39kPcnPg93E3JfdAOiOwIB2qwfYzg9kpmuTWws+DFz -lKpMI27YkmnPqROQ2NTUfdxYmw3EHHMAsvnmHeMNGn3ijSUZVKmPfV436Qc8iVci -s4wKoCRhBUZ52sHki/ieb+5hycT3JnVXMDtbJxgXFW5a86usXEpO ------END RSA PRIVATE KEY-----` - TestCertPool = x509.NewCertPool() ) func init() { - if ok := TestCertPool.AppendCertsFromPEM([]byte(CertificateFixture)); !ok { + if ok := TestCertPool.AppendCertsFromPEM([]byte(fixture.Certificate)); !ok { fmt.Println("Failed to load cert!") } } @@ -133,8 +67,8 @@ func CreateHTTPSFixtureFiles(t *testing.T) (key string, cert string) { cert = certfile.Name() certfile.Close() - require.NoError(t, ioutil.WriteFile(key, []byte(KeyFixture), 0644)) - require.NoError(t, ioutil.WriteFile(cert, []byte(CertificateFixture), 0644)) + require.NoError(t, ioutil.WriteFile(key, []byte(fixture.Key), 0644)) + require.NoError(t, ioutil.WriteFile(cert, []byte(fixture.Certificate), 0644)) return keyfile.Name(), certfile.Name() } diff --git a/domain.go b/internal/domain/domain.go index 2d562d04..0333cebe 100644 --- a/domain.go +++ b/internal/domain/domain.go @@ -1,4 +1,4 @@ -package main +package domain import ( "crypto/tls" @@ -28,29 +28,31 @@ type project struct { type projects map[string]*project -type domain struct { - Group string +// D is a domain that gitlab-pages can serve. +type D struct { + group string // custom domains: - ProjectName string - Config *domainConfig + projectName string + config *domainConfig certificate *tls.Certificate certificateError error // group domains: - Projects projects + projects projects } -func (d *domain) String() string { - if d.Group != "" && d.ProjectName != "" { - return d.Group + "/" + d.ProjectName +// String implements Stringer. +func (d *D) String() string { + if d.group != "" && d.projectName != "" { + return d.group + "/" + d.projectName } - if d.Group != "" { - return d.Group + if d.group != "" { + return d.group } - return d.ProjectName + return d.projectName } func (l *locationDirectoryError) Error() string { @@ -92,9 +94,11 @@ func setContentType(w http.ResponseWriter, fullPath string) { } } -func (d *domain) isHTTPSOnly(r *http.Request) bool { - if d.Config != nil { - return d.Config.HTTPSOnly +// IsHTTPSOnly figures out if the request should be handled with HTTPS +// only by looking at group and project level config. +func (d *D) IsHTTPSOnly(r *http.Request) bool { + if d.config != nil { + return d.config.HTTPSOnly } split := strings.SplitN(r.URL.Path, "/", 3) @@ -102,7 +106,7 @@ func (d *domain) isHTTPSOnly(r *http.Request) bool { return false } - project := d.Projects[split[1]] + project := d.projects[split[1]] if project != nil { return project.HTTPSOnly @@ -111,7 +115,7 @@ func (d *domain) isHTTPSOnly(r *http.Request) bool { return false } -func (d *domain) serveFile(w http.ResponseWriter, r *http.Request, origPath string) error { +func (d *D) serveFile(w http.ResponseWriter, r *http.Request, origPath string) error { fullPath := handleGZip(w, r, origPath) file, err := os.Open(fullPath) @@ -134,7 +138,7 @@ func (d *domain) serveFile(w http.ResponseWriter, r *http.Request, origPath stri return nil } -func (d *domain) serveCustomFile(w http.ResponseWriter, r *http.Request, code int, origPath string) error { +func (d *D) serveCustomFile(w http.ResponseWriter, r *http.Request, code int, origPath string) error { fullPath := handleGZip(w, r, origPath) // Open and serve content of file @@ -163,8 +167,8 @@ func (d *domain) serveCustomFile(w http.ResponseWriter, r *http.Request, code in // Resolve the HTTP request to a path on disk, converting requests for // directories to requests for index.html inside the directory if appropriate. -func (d *domain) resolvePath(projectName string, subPath ...string) (string, error) { - publicPath := filepath.Join(d.Group, projectName, "public") +func (d *D) resolvePath(projectName string, subPath ...string) (string, error) { + publicPath := filepath.Join(d.group, projectName, "public") // Don't use filepath.Join as cleans the path, // where we want to traverse full path as supplied by user @@ -202,7 +206,7 @@ func (d *domain) resolvePath(projectName string, subPath ...string) (string, err return fullPath, nil } -func (d *domain) tryNotFound(w http.ResponseWriter, r *http.Request, projectName string) error { +func (d *D) tryNotFound(w http.ResponseWriter, r *http.Request, projectName string) error { page404, err := d.resolvePath(projectName, "404.html") if err != nil { return err @@ -215,7 +219,7 @@ func (d *domain) tryNotFound(w http.ResponseWriter, r *http.Request, projectName return nil } -func (d *domain) tryFile(w http.ResponseWriter, r *http.Request, projectName, pathSuffix string, subPath ...string) error { +func (d *D) tryFile(w http.ResponseWriter, r *http.Request, projectName, pathSuffix string, subPath ...string) error { fullPath, err := d.resolvePath(projectName, subPath...) if locationError, _ := err.(*locationDirectoryError); locationError != nil { @@ -241,7 +245,7 @@ func (d *domain) tryFile(w http.ResponseWriter, r *http.Request, projectName, pa return d.serveFile(w, r, fullPath) } -func (d *domain) serveFromGroup(w http.ResponseWriter, r *http.Request) { +func (d *D) serveFromGroup(w http.ResponseWriter, r *http.Request) { // The Path always contains "/" at the beginning split := strings.SplitN(r.URL.Path, "/", 3) @@ -269,14 +273,14 @@ func (d *domain) serveFromGroup(w http.ResponseWriter, r *http.Request) { httperrors.Serve404(w) } -func (d *domain) serveFromConfig(w http.ResponseWriter, r *http.Request) { +func (d *D) serveFromConfig(w http.ResponseWriter, r *http.Request) { // Try to serve file for http://host/... => /group/project/... - if d.tryFile(w, r, d.ProjectName, "", r.URL.Path) == nil { + if d.tryFile(w, r, d.projectName, "", r.URL.Path) == nil { return } // Try serving not found page for http://host/ => /group/project/404.html - if d.tryNotFound(w, r, d.ProjectName) == nil { + if d.tryNotFound(w, r, d.projectName) == nil { return } @@ -284,8 +288,9 @@ func (d *domain) serveFromConfig(w http.ResponseWriter, r *http.Request) { httperrors.Serve404(w) } -func (d *domain) ensureCertificate() (*tls.Certificate, error) { - if d.Config == nil { +// EnsureCertificate parses the PEM-encoded certificate for the domain +func (d *D) EnsureCertificate() (*tls.Certificate, error) { + if d.config == nil { return nil, errors.New("tls certificates can be loaded only for pages with configuration") } @@ -293,7 +298,7 @@ func (d *domain) ensureCertificate() (*tls.Certificate, error) { return d.certificate, d.certificateError } - tls, err := tls.X509KeyPair([]byte(d.Config.Certificate), []byte(d.Config.Key)) + tls, err := tls.X509KeyPair([]byte(d.config.Certificate), []byte(d.config.Key)) if err != nil { d.certificateError = err return nil, err @@ -303,10 +308,15 @@ func (d *domain) ensureCertificate() (*tls.Certificate, error) { return d.certificate, nil } -func (d *domain) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if d.Config != nil { +// ServeHTTP implements http.Handler. +func (d *D) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if d.config != nil { d.serveFromConfig(w, r) } else { d.serveFromGroup(w, r) } } + +func endsWithSlash(path string) bool { + return strings.HasSuffix(path, "/") +} diff --git a/domain_config.go b/internal/domain/domain_config.go index f5c3db79..ed7e0820 100644 --- a/domain_config.go +++ b/internal/domain/domain_config.go @@ -1,4 +1,4 @@ -package main +package domain import ( "encoding/json" diff --git a/domain_config_test.go b/internal/domain/domain_config_test.go index 22194ad4..05db0aa3 100644 --- a/domain_config_test.go +++ b/internal/domain/domain_config_test.go @@ -1,4 +1,4 @@ -package main +package domain import ( "io/ioutil" diff --git a/domain_test.go b/internal/domain/domain_test.go index b51e6d7a..130501d6 100644 --- a/domain_test.go +++ b/internal/domain/domain_test.go @@ -1,4 +1,4 @@ -package main +package domain import ( "compress/gzip" @@ -7,19 +7,22 @@ import ( "net/http" "net/http/httptest" "net/url" + "os" "testing" "time" + log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitlab-pages/internal/fixture" ) func TestGroupServeHTTP(t *testing.T) { setUpTests() - testGroup := &domain{ - Group: "group", - ProjectName: "", + testGroup := &D{ + group: "group", + projectName: "", } assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/", nil, "main-dir") @@ -47,10 +50,10 @@ func TestGroupServeHTTP(t *testing.T) { func TestDomainServeHTTP(t *testing.T) { setUpTests() - testDomain := &domain{ - Group: "group", - ProjectName: "project2", - Config: &domainConfig{ + testDomain := &D{ + group: "group", + projectName: "project2", + config: &domainConfig{ Domain: "test.domain.com", }, } @@ -94,9 +97,9 @@ func testHTTPGzip(t *testing.T, handler http.HandlerFunc, mode, url string, valu func TestGroupServeHTTPGzip(t *testing.T) { setUpTests() - testGroup := &domain{ - Group: "group", - ProjectName: "", + testGroup := &D{ + group: "group", + projectName: "", } testSet := []struct { @@ -157,9 +160,9 @@ func testHTTP404(t *testing.T, handler http.HandlerFunc, mode, url string, value func TestGroup404ServeHTTP(t *testing.T) { setUpTests() - testGroup := &domain{ - Group: "group.404", - ProjectName: "", + testGroup := &D{ + group: "group.404", + projectName: "", } testHTTP404(t, testGroup.ServeHTTP, "GET", "http://group.404.test.io/project.404/not/existing-file", nil, "Custom 404 project page") @@ -174,10 +177,10 @@ func TestGroup404ServeHTTP(t *testing.T) { func TestDomain404ServeHTTP(t *testing.T) { setUpTests() - testDomain := &domain{ - Group: "group.404", - ProjectName: "domain.404", - Config: &domainConfig{ + testDomain := &D{ + group: "group.404", + projectName: "domain.404", + config: &domainConfig{ Domain: "domain.404.com", }, } @@ -189,60 +192,60 @@ func TestDomain404ServeHTTP(t *testing.T) { func TestPredefined404ServeHTTP(t *testing.T) { setUpTests() - testDomain := &domain{ - Group: "group", + testDomain := &D{ + group: "group", } testHTTP404(t, testDomain.ServeHTTP, "GET", "http://group.test.io/not-existing-file", nil, "The page you're looking for could not be found") } func TestGroupCertificate(t *testing.T) { - testGroup := &domain{ - Group: "group", - ProjectName: "", + testGroup := &D{ + group: "group", + projectName: "", } - tls, err := testGroup.ensureCertificate() + tls, err := testGroup.EnsureCertificate() assert.Nil(t, tls) assert.Error(t, err) } func TestDomainNoCertificate(t *testing.T) { - testDomain := &domain{ - Group: "group", - ProjectName: "project2", - Config: &domainConfig{ + testDomain := &D{ + group: "group", + projectName: "project2", + config: &domainConfig{ Domain: "test.domain.com", }, } - tls, err := testDomain.ensureCertificate() + tls, err := testDomain.EnsureCertificate() assert.Nil(t, tls) assert.Error(t, err) - _, err2 := testDomain.ensureCertificate() + _, err2 := testDomain.EnsureCertificate() assert.Error(t, err) assert.Equal(t, err, err2) } func TestDomainCertificate(t *testing.T) { - testDomain := &domain{ - Group: "group", - ProjectName: "project2", - Config: &domainConfig{ + testDomain := &D{ + group: "group", + projectName: "project2", + config: &domainConfig{ Domain: "test.domain.com", - Certificate: CertificateFixture, - Key: KeyFixture, + Certificate: fixture.Certificate, + Key: fixture.Key, }, } - tls, err := testDomain.ensureCertificate() + tls, err := testDomain.EnsureCertificate() assert.NotNil(t, tls) require.NoError(t, err) } func TestCacheControlHeaders(t *testing.T) { - testGroup := &domain{Group: "group"} + testGroup := &D{group: "group"} w := httptest.NewRecorder() req, err := http.NewRequest("GET", "http://group.test.io/", nil) require.NoError(t, err) @@ -261,3 +264,18 @@ func TestCacheControlHeaders(t *testing.T) { assert.WithinDuration(t, now.UTC().Add(10*time.Minute), expiresTime.UTC(), time.Minute) } + +var chdirSet = false + +func setUpTests() { + if chdirSet { + return + } + + err := os.Chdir("../../shared/pages") + if err != nil { + log.WithError(err).Print("chdir") + } else { + chdirSet = true + } +} diff --git a/domains.go b/internal/domain/map.go index 291b0faa..ad38e84b 100644 --- a/domains.go +++ b/internal/domain/map.go @@ -1,4 +1,4 @@ -package main +package domain import ( "bytes" @@ -15,15 +15,16 @@ import ( "gitlab.com/gitlab-org/gitlab-pages/metrics" ) -type domainMap map[string]*domain +// Map maps domain names to D instances. +type Map map[string]*D -type domainsUpdater func(domainMap) +type domainsUpdater func(Map) -func (dm domainMap) addDomain(rootDomain, group, projectName string, config *domainConfig) { - newDomain := &domain{ - Group: group, - ProjectName: projectName, - Config: config, +func (dm Map) addDomain(rootDomain, group, projectName string, config *domainConfig) { + newDomain := &D{ + group: group, + projectName: projectName, + config: config, } var domainName string @@ -31,25 +32,25 @@ func (dm domainMap) addDomain(rootDomain, group, projectName string, config *dom dm[domainName] = newDomain } -func (dm domainMap) updateGroupDomain(rootDomain, group, projectName string, httpsOnly bool) { +func (dm Map) updateGroupDomain(rootDomain, group, projectName string, httpsOnly bool) { domainName := strings.ToLower(group + "." + rootDomain) groupDomain := dm[domainName] if groupDomain == nil { - groupDomain = &domain{ - Group: group, - Projects: make(projects), + groupDomain = &D{ + group: group, + projects: make(projects), } } - groupDomain.Projects[projectName] = &project{ + groupDomain.projects[projectName] = &project{ HTTPSOnly: httpsOnly, } dm[domainName] = groupDomain } -func (dm domainMap) readProjectConfig(rootDomain string, group, projectName string, config *domainsConfig) { +func (dm Map) readProjectConfig(rootDomain string, group, projectName string, config *domainsConfig) { if config == nil { // This is necessary to preserve the previous behaviour where a // group domain is created even if no config.json files are @@ -117,7 +118,8 @@ type jobResult struct { config *domainsConfig } -func (dm domainMap) ReadGroups(rootDomain string) error { +// ReadGroups walks the pages directory and populates dm with all the domains it finds. +func (dm Map) ReadGroups(rootDomain string) error { fis, err := godirwalk.ReadDirents(".", nil) if err != nil { return err @@ -180,7 +182,8 @@ const ( updateFile = ".update" ) -func watchDomains(rootDomain string, updater domainsUpdater, interval time.Duration) { +// Watch polls the filesystem and kicks off a new domain directory scan when needed. +func Watch(rootDomain string, updater domainsUpdater, interval time.Duration) { lastUpdate := []byte("no-update") for { @@ -200,7 +203,7 @@ func watchDomains(rootDomain string, updater domainsUpdater, interval time.Durat lastUpdate = update started := time.Now() - dm := make(domainMap) + dm := make(Map) if err := dm.ReadGroups(rootDomain); err != nil { log.WithError(err).Warn("domain scan failed") } @@ -234,7 +237,7 @@ func watchDomains(rootDomain string, updater domainsUpdater, interval time.Durat } } -func logConfiguredDomains(dm domainMap) { +func logConfiguredDomains(dm Map) { if log.GetLevel() == log.DebugLevel { return } diff --git a/domains_test.go b/internal/domain/map_test.go index e1196765..f20f98bd 100644 --- a/domains_test.go +++ b/internal/domain/map_test.go @@ -1,4 +1,4 @@ -package main +package domain import ( "crypto/rand" @@ -15,7 +15,7 @@ import ( func TestReadProjects(t *testing.T) { setUpTests() - dm := make(domainMap) + dm := make(Map) err := dm.ReadGroups("test.io") require.NoError(t, err) @@ -47,10 +47,10 @@ func TestReadProjects(t *testing.T) { // Check that multiple domains in the same project are recorded faithfully exp1 := &domainConfig{Domain: "test.domain.com"} - assert.Equal(t, exp1, dm["test.domain.com"].Config) + assert.Equal(t, exp1, dm["test.domain.com"].config) exp2 := &domainConfig{Domain: "other.domain.com", Certificate: "test", Key: "key"} - assert.Equal(t, exp2, dm["other.domain.com"].Config) + assert.Equal(t, exp2, dm["other.domain.com"].config) } // This write must be atomic, otherwise we cannot predict the state of the @@ -62,7 +62,7 @@ func writeRandomTimestamp(t *testing.T) { n, _ := rand.Read(b) require.True(t, n > 0, "read some random bytes") - temp, err := ioutil.TempFile(".", "TestWatchDomains") + temp, err := ioutil.TempFile(".", "TestWatch") require.NoError(t, err) _, err = temp.Write(b) require.NoError(t, err, "write to tempfile") @@ -71,13 +71,13 @@ func writeRandomTimestamp(t *testing.T) { require.NoError(t, os.Rename(temp.Name(), updateFile), "rename tempfile") } -func TestWatchDomains(t *testing.T) { +func TestWatch(t *testing.T) { setUpTests() require.NoError(t, os.RemoveAll(updateFile)) - update := make(chan domainMap) - go watchDomains("gitlab.io", func(dm domainMap) { + update := make(chan Map) + go Watch("gitlab.io", func(dm Map) { update <- dm }, time.Microsecond*50) @@ -95,7 +95,7 @@ func TestWatchDomains(t *testing.T) { assert.NotNil(t, domains, "if the domains are updated after the timestamp change") } -func recvTimeout(t *testing.T, ch <-chan domainMap) domainMap { +func recvTimeout(t *testing.T, ch <-chan Map) Map { timeout := 5 * time.Second select { @@ -138,9 +138,9 @@ func BenchmarkReadGroups(b *testing.B) { } b.Run("ReadGroups", func(b *testing.B) { - var dm domainMap + var dm Map for i := 0; i < 2; i++ { - dm = make(domainMap) + dm = make(Map) require.NoError(b, dm.ReadGroups("example.com")) } b.Logf("found %d domains", len(dm)) diff --git a/internal/fixture/fixtures.go b/internal/fixture/fixtures.go new file mode 100644 index 00000000..38bbd375 --- /dev/null +++ b/internal/fixture/fixtures.go @@ -0,0 +1,56 @@ +package fixture + +const ( + // Certificate is used for HTTPS tests + Certificate = `-----BEGIN CERTIFICATE----- +MIIDZDCCAkygAwIBAgIRAOtN9/zy+gFjdsgpKq3QRdQwDQYJKoZIhvcNAQELBQAw +MzEUMBIGA1UEChMLTG9nIENvdXJpZXIxGzAZBgNVBAMTEmdpdGxhYi1leGFtcGxl +LmNvbTAgFw0xODAzMjMxODMwMDZaGA8yMTE4MDIyNzE4MzAwNlowMzEUMBIGA1UE +ChMLTG9nIENvdXJpZXIxGzAZBgNVBAMTEmdpdGxhYi1leGFtcGxlLmNvbTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKULsxpnazXX5RsVzayrAQB+lWwr +Wef5L5eDhSsIsBLbelYp5YB4TmVRt5x7bWKOOJSBsOfwHZHKJXdu+uuX2RenZlhk +3Qpq9XGaPZjYm/NHi8gBHPAtz5sG5VaKNvkfTzRGnO9CWA9TM1XtYiOBq94dO+H3 +c+5jP5Yw+mJ+hA+i2058zF8nRlUHArEno2ofrHwE0LMZ11VskpXtWnVfs3voLs8p +r76KXPBFkMJR4qkWrMDF5Y5MbsQ0zisn6KXrTyV0S4MQh4vSyPdFHnEzvJ07rm5x +4RTWrjgQeQ2DjZjQvRmaDzlVBK9kaMkJ1Si3agK+gpji6d6WZ/Mb2el1GK8CAwEA +AaNxMG8wDgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud +EwEB/wQFMAMBAf8wNwYDVR0RBDAwLoIUKi5naXRsYWItZXhhbXBsZS5jb22HBH8A +AAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggEBAJ0NM8apK0xI +YxMstP/dCQXtR0wyREGSD/eOpeY3bWlqCbpRgMFUGjQlrsEozcPZOCSCKX5p+tym +7GsnYtXkwbsuURoSz+5IlhRPVHcUlUeGRdv3/gCd8fDXiigALCsB6GrkMG5cUfh+ +x5p52AC3eQdWTDoxNou+2gzwkAl8iJc13Ykusst0YUqcsXKqTuei2quxFv0pEBSO +p8wEixoicLFNqPnIDmgx5894DAn0bccNXgRWtq8lLbdhGUlBbpatevvFMgNvFUbe +eeGb9D0EfpxmzxUl+L0xZtfg3f7cu5AgLG8tb6l4AK6NPVuXN8DmUgvnauWJjZME +fgStI+IRNVg= +-----END CERTIFICATE----- +` + + // Key is used for HTTPS tests + Key = `-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEApQuzGmdrNdflGxXNrKsBAH6VbCtZ5/kvl4OFKwiwEtt6Vinl +gHhOZVG3nHttYo44lIGw5/Adkcold27665fZF6dmWGTdCmr1cZo9mNib80eLyAEc +8C3PmwblVoo2+R9PNEac70JYD1MzVe1iI4Gr3h074fdz7mM/ljD6Yn6ED6LbTnzM +XydGVQcCsSejah+sfATQsxnXVWySle1adV+ze+guzymvvopc8EWQwlHiqRaswMXl +jkxuxDTOKyfopetPJXRLgxCHi9LI90UecTO8nTuubnHhFNauOBB5DYONmNC9GZoP +OVUEr2RoyQnVKLdqAr6CmOLp3pZn8xvZ6XUYrwIDAQABAoIBAHhP5QnUZeTkMtDh +vgKmzZ4sqIQnvexKTBUo/MR4GtJESBPTisdx68QUI8LgfsafYkNvnyQUd5m1QEam +Eif3k3uYvhSlwjQ78BwWEdz/2f8oIo9zsEKtQm+CQWAqdRR5bGVxLCmFtWfGgN+c +ojO77SuHKAX7OvmGQ+4aWgu+qkoyg/chIpPXMduAjLMtN3eg60ZqJ5KrKuIF63Bb +xkPQvzJueB9SfUurmKjUltDMx6G/9RZyS0OIRGyL9Qp8MZ8jE23cXOcDgm0HhkPq +W4LU++aWAOLYziTjnhjJ+4Iz9R7U8sCmk1wgnK/tapVcJf41R98WuGluyjXpsXgA +k7vmofECgYEAzuGun9lZ7xGwPifp6vGanWXnW+JiZgCTGuHWgQLIXWYcLfoI3kpH +eLBYINBwvjIQ7P6UxsGSSXd+T82t+8W2LLc2fiKFKE1LVySpH99+cfmIPXxrviOz +GBX9LTdSCdGkgb54m8aJCpNFnKw5wYgcW1L8CaXXly2Z/KNrGR9R/YUCgYEAzDs4 +19HqlutGLTC30/ziiiIfDaBbX9AzBdUfp9GdT53Mi/7bfxpW/sL4RjG2fGgmN6ua +fh5npT9AB1ldcEg2qfyOJPt1Ubdi6ek9lx8AB2RMhwdihgX+7bjVMFtjg4b8z5C1 +jQbEr1rhFdpaGyNehtAXDgCbDWQBYnBrmM0rCaMCgYBip1Qyfd9ZFcJJoZb2pofo +jvOo6Weq5JNBungjxUPu5gaCFj2sYxd6Af3EiCF7UTypBy3DKgOsbQMa4yYYbcvV +vviJZcTB1zoaMC1GObl+eFPzniVy4mtBDRtSOJMyg3pDNKUnA6HOHTSQ5cAU/ecn +1YbCwwbv3JsV0of7zue2UQKBgQCVc0j3dd9rLSQfcaUz9bx5RNrgh9YV2S9dN0aA +8f1iA6FpWMiazFWY/GfeRga6JyTAXE0juXAzFoPuXNDpl46Y+f2yxmhlsgMqFMpD +SiYlQppVvWu1k7GnmDg5uMarux5JbiXM24UWpTRNX4nMjidgE+qrDnpoZCQ3Ovkh +yhGSbQKBgD3VEnPiSUmXBo39kPcnPg93E3JfdAOiOwIB2qwfYzg9kpmuTWws+DFz +lKpMI27YkmnPqROQ2NTUfdxYmw3EHHMAsvnmHeMNGn3ijSUZVKmPfV436Qc8iVci +s4wKoCRhBUZ52sHki/ieb+5hycT3JnVXMDtbJxgXFW5a86usXEpO +-----END RSA PRIVATE KEY-----` +) |