diff options
author | Vladimir Shushlin <vshushlin@gitlab.com> | 2020-09-25 15:38:18 +0300 |
---|---|---|
committer | Vladimir Shushlin <vshushlin@gitlab.com> | 2020-09-25 15:38:18 +0300 |
commit | 8bc20438cb8f84bec72247ebcb1b75d8024f8c0c (patch) | |
tree | 47a5c1f40859ca5bcb5187c8524dec485c2cf486 | |
parent | 7802bb75e8edafe05855fcbdb72aeea7bb906ae7 (diff) | |
parent | eecf8c867d276470c6db93f26878a03b56ef4689 (diff) |
Merge branch '443-implement-vfs-interface' into 'master'
VFS implementation for zip serving
See merge request gitlab-org/gitlab-pages!351
-rw-r--r-- | acceptance_test.go | 89 | ||||
-rw-r--r-- | helpers_test.go | 28 | ||||
-rw-r--r-- | internal/serving/disk/local/serving.go | 2 | ||||
-rw-r--r-- | internal/serving/disk/reader.go | 6 | ||||
-rw-r--r-- | internal/serving/disk/symlink/path_test.go | 2 | ||||
-rw-r--r-- | internal/serving/disk/zip/serving.go | 16 | ||||
-rw-r--r-- | internal/serving/disk/zip/serving_test.go | 59 | ||||
-rw-r--r-- | internal/source/gitlab/factory.go | 7 | ||||
-rw-r--r-- | internal/testhelpers/tmpdir.go | 2 | ||||
-rw-r--r-- | internal/vfs/local/vfs.go | 4 | ||||
-rw-r--r-- | internal/vfs/vfs.go | 18 | ||||
-rw-r--r-- | internal/vfs/zip/archive.go | 8 | ||||
-rw-r--r-- | internal/vfs/zip/vfs.go | 90 | ||||
-rw-r--r-- | internal/vfs/zip/vfs_test.go | 97 | ||||
-rw-r--r-- | metrics/metrics.go | 6 | ||||
-rw-r--r-- | shared/lookups/zip.gitlab.io.json | 16 |
16 files changed, 427 insertions, 23 deletions
diff --git a/acceptance_test.go b/acceptance_test.go index f8c2a546..02c71f1d 100644 --- a/acceptance_test.go +++ b/acceptance_test.go @@ -21,6 +21,10 @@ import ( var pagesBinary = flag.String("gitlab-pages-binary", "./gitlab-pages", "Path to the gitlab-pages binary") +const ( + objectStorageMockServer = "127.0.0.1:37003" +) + // TODO: Use TCP port 0 everywhere to avoid conflicts. The binary could output // the actual port (and type of listener) for us to read in place of the // hardcoded values below. @@ -448,11 +452,14 @@ func TestHttpsOnlyDomainDisabled(t *testing.T) { func TestPrometheusMetricsCanBeScraped(t *testing.T) { skipUnlessEnabled(t) + _, cleanup := newZipFileServerURL(t, "shared/pages/group/zip.gitlab.io/public.zip") + defer cleanup() + teardown := RunPagesProcessWithStubGitLabServer(t, true, *pagesBinary, listeners, ":42345", []string{}) defer teardown() // need to call an actual resource to populate certain metrics e.g. gitlab_pages_domains_source_api_requests_total - res, err := GetPageFromListener(t, httpListener, "new-source-test.gitlab.io", "/my/pages/project/") + res, err := GetPageFromListener(t, httpListener, "zip.gitlab.io", "/index.html/") require.NoError(t, err) require.Equal(t, http.StatusOK, res.StatusCode) @@ -480,9 +487,8 @@ func TestPrometheusMetricsCanBeScraped(t *testing.T) { require.Contains(t, string(body), "gitlab_pages_serving_time_seconds_sum") require.Contains(t, string(body), `gitlab_pages_domains_source_api_requests_total{status_code="200"}`) require.Contains(t, string(body), `gitlab_pages_domains_source_api_call_duration{status_code="200"}`) - // TODO: add test when Zip is enabled https://gitlab.com/gitlab-org/gitlab-pages/-/issues/443 - // require.Contains(t, string(body), `gitlab_pages_httprange_zip_reader_requests_total{status_code="200"}`) - // require.Contains(t, string(body), `gitlab_pages_httprange_zip_reader_requests_duration{status_code="200"}`) + require.Contains(t, string(body), `gitlab_pages_object_storage_backend_requests_total{status_code="206"}`) + require.Contains(t, string(body), `gitlab_pages_object_storage_backend_requests_duration{status_code="206"}`) } func TestDisabledRedirects(t *testing.T) { @@ -1925,3 +1931,78 @@ func TestDomainsSource(t *testing.T) { }) } } + +func TestZipServing(t *testing.T) { + skipUnlessEnabled(t) + + var apiCalled bool + source := NewGitlabDomainsSourceStub(t, &apiCalled) + defer source.Close() + + gitLabAPISecretKey := CreateGitLabAPISecretKeyFixtureFile(t) + + pagesArgs := []string{"-gitlab-server", source.URL, "-api-secret-key", gitLabAPISecretKey, "-domain-config-source", "gitlab"} + teardown := RunPagesProcessWithEnvs(t, true, *pagesBinary, listeners, "", []string{}, pagesArgs...) + defer teardown() + + _, cleanup := newZipFileServerURL(t, "shared/pages/group/zip.gitlab.io/public.zip") + defer cleanup() + + tests := map[string]struct { + urlSuffix string + expectedStatusCode int + expectedContent string + }{ + "base_domain_no_suffix": { + urlSuffix: "/", + expectedStatusCode: http.StatusOK, + expectedContent: "zip.gitlab.io/project/index.html\n", + }, + "file_exists": { + urlSuffix: "/index.html", + expectedStatusCode: http.StatusOK, + expectedContent: "zip.gitlab.io/project/index.html\n", + }, + "file_exists_in_subdir": { + urlSuffix: "/subdir/hello.html", + expectedStatusCode: http.StatusOK, + expectedContent: "zip.gitlab.io/project/subdir/hello.html\n", + }, + "file_exists_symlink": { + urlSuffix: "/symlink.html", + expectedStatusCode: http.StatusOK, + expectedContent: "symlink.html->subdir/linked.html\n", + }, + "dir": { + urlSuffix: "/subdir/", + expectedStatusCode: http.StatusNotFound, + expectedContent: "zip.gitlab.io/project/404.html\n", + }, + "file_does_not_exist": { + urlSuffix: "/unknown.html", + expectedStatusCode: http.StatusNotFound, + expectedContent: "zip.gitlab.io/project/404.html\n", + }, + "bad_symlink": { + urlSuffix: "/bad-symlink.html", + expectedStatusCode: http.StatusNotFound, + expectedContent: "zip.gitlab.io/project/404.html\n", + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + response, err := GetPageFromListener(t, httpListener, "zip.gitlab.io", tt.urlSuffix) + require.NoError(t, err) + defer response.Body.Close() + + require.Equal(t, tt.expectedStatusCode, response.StatusCode) + if tt.expectedStatusCode == http.StatusOK || tt.expectedStatusCode == http.StatusNotFound { + body, err := ioutil.ReadAll(response.Body) + require.NoError(t, err) + + require.Equal(t, tt.expectedContent, string(body), "content mismatch") + } + }) + } +} diff --git a/helpers_test.go b/helpers_test.go index d08869fd..6ee11b86 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -522,3 +522,31 @@ func copyFile(dest, src string) error { _, err = io.Copy(destFile, srcFile) return err } + +func newZipFileServerURL(t *testing.T, zipFilePath string) (string, func()) { + t.Helper() + + m := http.NewServeMux() + m.HandleFunc("/public.zip", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, zipFilePath) + })) + + // create a listener with the desired port. + l, err := net.Listen("tcp", objectStorageMockServer) + require.NoError(t, err) + + testServer := httptest.NewUnstartedServer(m) + + // NewUnstartedServer creates a listener. Close that listener and replace + // with the one we created. + testServer.Listener.Close() + testServer.Listener = l + + // Start the server. + testServer.Start() + + return testServer.URL, func() { + // Cleanup. + testServer.Close() + } +} diff --git a/internal/serving/disk/local/serving.go b/internal/serving/disk/local/serving.go index 230a71da..3b23b8e9 100644 --- a/internal/serving/disk/local/serving.go +++ b/internal/serving/disk/local/serving.go @@ -7,7 +7,7 @@ import ( "gitlab.com/gitlab-org/gitlab-pages/internal/vfs/local" ) -var instance = disk.New(vfs.Instrumented(&local.VFS{}, "local")) +var instance = disk.New(vfs.Instrumented(&local.VFS{})) // Instance returns a serving instance that is capable of reading files // from the disk diff --git a/internal/serving/disk/reader.go b/internal/serving/disk/reader.go index 5b34c556..559d332c 100644 --- a/internal/serving/disk/reader.go +++ b/internal/serving/disk/reader.go @@ -20,7 +20,7 @@ import ( // Reader is a disk access driver type Reader struct { - fileSizeMetric prometheus.Histogram + fileSizeMetric *prometheus.HistogramVec vfs vfs.VFS } @@ -196,7 +196,7 @@ func (reader *Reader) serveFile(ctx context.Context, w http.ResponseWriter, r *h w.Header().Set("Content-Type", contentType) - reader.fileSizeMetric.Observe(float64(fi.Size())) + reader.fileSizeMetric.WithLabelValues(reader.vfs.Name()).Observe(float64(fi.Size())) // Support vfs.SeekableFile if available (uncompressed files) if rs, ok := file.(vfs.SeekableFile); ok { @@ -231,7 +231,7 @@ func (reader *Reader) serveCustomFile(ctx context.Context, w http.ResponseWriter return err } - reader.fileSizeMetric.Observe(float64(fi.Size())) + reader.fileSizeMetric.WithLabelValues(reader.vfs.Name()).Observe(float64(fi.Size())) w.Header().Set("Content-Type", contentType) w.Header().Set("Content-Length", strconv.FormatInt(fi.Size(), 10)) diff --git a/internal/serving/disk/symlink/path_test.go b/internal/serving/disk/symlink/path_test.go index 8c1d19b3..ef9726c1 100644 --- a/internal/serving/disk/symlink/path_test.go +++ b/internal/serving/disk/symlink/path_test.go @@ -21,7 +21,7 @@ import ( "gitlab.com/gitlab-org/gitlab-pages/internal/vfs/local" ) -var fs = vfs.Instrumented(&local.VFS{}, "local") +var fs = vfs.Instrumented(&local.VFS{}) type EvalSymlinksTest struct { // If dest is empty, the path is created; otherwise the dest is symlinked to the path. diff --git a/internal/serving/disk/zip/serving.go b/internal/serving/disk/zip/serving.go new file mode 100644 index 00000000..95894fc9 --- /dev/null +++ b/internal/serving/disk/zip/serving.go @@ -0,0 +1,16 @@ +package zip + +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/vfs" + "gitlab.com/gitlab-org/gitlab-pages/internal/vfs/zip" +) + +var instance = disk.New(vfs.Instrumented(zip.New())) + +// Instance returns a serving instance that is capable of reading files +// from a zip archives opened from a URL, most likely stored in object storage +func Instance() serving.Serving { + return instance +} diff --git a/internal/serving/disk/zip/serving_test.go b/internal/serving/disk/zip/serving_test.go new file mode 100644 index 00000000..14fdabdf --- /dev/null +++ b/internal/serving/disk/zip/serving_test.go @@ -0,0 +1,59 @@ +package zip + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" + + "gitlab.com/gitlab-org/gitlab-pages/internal/serving" + "gitlab.com/gitlab-org/gitlab-pages/internal/testhelpers" +) + +func TestZip_ServeFileHTTP(t *testing.T) { + testServerURL, cleanup := newZipFileServerURL(t, "group/zip.gitlab.io/public.zip") + defer cleanup() + + s := Instance() + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "http://zip.gitlab.io/zip/index.html", nil) + handler := serving.Handler{ + Writer: w, + Request: r, + LookupPath: &serving.LookupPath{ + Prefix: "", + Path: testServerURL + "/public.zip", + }, + SubPath: "/index.html", + } + + require.True(t, s.ServeFileHTTP(handler)) + + resp := w.Result() + defer resp.Body.Close() + + require.Equal(t, http.StatusOK, resp.StatusCode) + body, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + + require.Contains(t, string(body), "zip.gitlab.io/project/index.html\n") +} + +var chdirSet = false + +func newZipFileServerURL(t *testing.T, zipFilePath string) (string, func()) { + t.Helper() + + chdir := testhelpers.ChdirInPath(t, "../../../../shared/pages", &chdirSet) + + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, zipFilePath) + })) + + return testServer.URL, func() { + chdir() + testServer.Close() + } +} diff --git a/internal/source/gitlab/factory.go b/internal/source/gitlab/factory.go index b5ab367b..39193a16 100644 --- a/internal/source/gitlab/factory.go +++ b/internal/source/gitlab/factory.go @@ -1,12 +1,11 @@ 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/local" + "gitlab.com/gitlab-org/gitlab-pages/internal/serving/disk/zip" "gitlab.com/gitlab-org/gitlab-pages/internal/serving/serverless" "gitlab.com/gitlab-org/gitlab-pages/internal/source/gitlab/api" ) @@ -17,7 +16,7 @@ import ( func fabricateLookupPath(size int, lookup api.LookupPath) *serving.LookupPath { return &serving.LookupPath{ Prefix: lookup.Prefix, - Path: strings.TrimPrefix(lookup.Source.Path, "/"), + Path: lookup.Source.Path, IsNamespaceProject: (lookup.Prefix == "/" && size > 1), IsHTTPSOnly: lookup.HTTPSOnly, HasAccessControl: lookup.AccessControl, @@ -32,6 +31,8 @@ func fabricateServing(lookup api.LookupPath) serving.Serving { switch source.Type { case "file": return local.Instance() + case "zip": + return zip.Instance() case "serverless": serving, err := serverless.NewFromAPISource(source.Serverless) if err != nil { diff --git a/internal/testhelpers/tmpdir.go b/internal/testhelpers/tmpdir.go index 85201238..81f0781b 100644 --- a/internal/testhelpers/tmpdir.go +++ b/internal/testhelpers/tmpdir.go @@ -13,7 +13,7 @@ import ( "gitlab.com/gitlab-org/gitlab-pages/internal/vfs/local" ) -var fs = vfs.Instrumented(&local.VFS{}, "local") +var fs = vfs.Instrumented(&local.VFS{}) func TmpDir(t *testing.T, pattern string) (vfs.Root, string, func()) { tmpDir, err := ioutil.TempDir("", pattern) diff --git a/internal/vfs/local/vfs.go b/internal/vfs/local/vfs.go index a2eb14f7..ca74dfbe 100644 --- a/internal/vfs/local/vfs.go +++ b/internal/vfs/local/vfs.go @@ -35,3 +35,7 @@ func (fs VFS) Root(ctx context.Context, path string) (vfs.Root, error) { return &Root{rootPath: rootPath}, nil } + +func (fs *VFS) Name() string { + return "local" +} diff --git a/internal/vfs/vfs.go b/internal/vfs/vfs.go index 9d9551a1..7bd51db2 100644 --- a/internal/vfs/vfs.go +++ b/internal/vfs/vfs.go @@ -12,23 +12,23 @@ import ( // VFS abstracts the things Pages needs to serve a static site from disk. type VFS interface { Root(ctx context.Context, path string) (Root, error) + Name() string } -func Instrumented(fs VFS, name string) VFS { - return &instrumentedVFS{fs: fs, name: name} +func Instrumented(fs VFS) VFS { + return &instrumentedVFS{fs: fs} } type instrumentedVFS struct { - fs VFS - name string + fs VFS } func (i *instrumentedVFS) increment(operation string, err error) { - metrics.VFSOperations.WithLabelValues(i.name, operation, strconv.FormatBool(err == nil)).Inc() + metrics.VFSOperations.WithLabelValues(i.fs.Name(), operation, strconv.FormatBool(err == nil)).Inc() } func (i *instrumentedVFS) log() *log.Entry { - return log.WithField("vfs", i.name) + return log.WithField("vfs", i.fs.Name()) } func (i *instrumentedVFS) Root(ctx context.Context, path string) (Root, error) { @@ -44,5 +44,9 @@ func (i *instrumentedVFS) Root(ctx context.Context, path string) (Root, error) { return nil, err } - return &instrumentedRoot{root: root, name: i.name, rootPath: path}, nil + return &instrumentedRoot{root: root, name: i.fs.Name(), rootPath: path}, nil +} + +func (i *instrumentedVFS) Name() string { + return i.fs.Name() } diff --git a/internal/vfs/zip/archive.go b/internal/vfs/zip/archive.go index ca9b778f..ce7d2ec8 100644 --- a/internal/vfs/zip/archive.go +++ b/internal/vfs/zip/archive.go @@ -59,6 +59,14 @@ func newArchive(path string, openTimeout time.Duration) *zipArchive { } func (a *zipArchive) openArchive(parentCtx context.Context) error { + // return early if openArchive was done already in a concurrent request + select { + case <-a.done: + return a.err + + default: + } + ctx, cancel := context.WithTimeout(parentCtx, a.openTimeout) defer cancel() diff --git a/internal/vfs/zip/vfs.go b/internal/vfs/zip/vfs.go new file mode 100644 index 00000000..283e01c2 --- /dev/null +++ b/internal/vfs/zip/vfs.go @@ -0,0 +1,90 @@ +package zip + +import ( + "context" + "errors" + "net/url" + "time" + + "github.com/patrickmn/go-cache" + + "gitlab.com/gitlab-org/gitlab-pages/internal/vfs" +) + +const ( + // TODO: make these configurable https://gitlab.com/gitlab-org/gitlab-pages/-/issues/464 + defaultCacheExpirationInterval = time.Minute + defaultCacheCleanupInterval = time.Minute / 2 + defaultCacheRefreshInterval = time.Minute / 2 +) + +var ( + errAlreadyCached = errors.New("archive already cached") +) + +// zipVFS is a simple cached implementation of the vfs.VFS interface +type zipVFS struct { + cache *cache.Cache +} + +// New creates a zipVFS instance that can be used by a serving request +func New() vfs.VFS { + return &zipVFS{ + // TODO: add cache operation callbacks https://gitlab.com/gitlab-org/gitlab-pages/-/issues/465 + cache: cache.New(defaultCacheExpirationInterval, defaultCacheCleanupInterval), + } +} + +// Root opens an archive given a URL path and returns an instance of zipArchive +// that implements the vfs.VFS interface. +// To avoid using locks, the findOrOpenArchive function runs inside of a for +// loop until an archive is either found or created and saved. +// If findOrOpenArchive returns errAlreadyCached, the for loop will continue +// to try and find the cached archive or return if there's an error, for example +// if the context is canceled. +func (fs *zipVFS) Root(ctx context.Context, path string) (vfs.Root, error) { + urlPath, err := url.Parse(path) + if err != nil { + return nil, err + } + + // we do it in loop to not use any additional locks + for { + root, err := fs.findOrOpenArchive(ctx, urlPath.String()) + if err == errAlreadyCached { + continue + } + + return root, err + } +} + +func (fs *zipVFS) Name() string { + return "zip" +} + +// findOrOpenArchive if found in fs.cache refresh if needed and return it. +// otherwise open the archive and try to save it, if saving fails it's because +// the archive has already been cached (e.g. by another concurrent request) +func (fs *zipVFS) findOrOpenArchive(ctx context.Context, path string) (*zipArchive, error) { + archive, expiry, found := fs.cache.GetWithExpiration(path) + if found { + // TODO: do not refreshed errored archives https://gitlab.com/gitlab-org/gitlab-pages/-/merge_requests/351 + if time.Until(expiry) < defaultCacheRefreshInterval { + // refresh item + fs.cache.SetDefault(path, archive) + } + } else { + archive = newArchive(path, DefaultOpenTimeout) + + // if adding the archive to the cache fails it means it's already been added before + // this is done to find concurrent additions. + if fs.cache.Add(path, archive, cache.DefaultExpiration) != nil { + return nil, errAlreadyCached + } + } + + zipArchive := archive.(*zipArchive) + err := zipArchive.openArchive(ctx) + return zipArchive, err +} diff --git a/internal/vfs/zip/vfs_test.go b/internal/vfs/zip/vfs_test.go new file mode 100644 index 00000000..62b5f62c --- /dev/null +++ b/internal/vfs/zip/vfs_test.go @@ -0,0 +1,97 @@ +package zip + +import ( + "context" + "io/ioutil" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestVFSRoot(t *testing.T) { + url, cleanup := newZipFileServerURL(t, "group/zip.gitlab.io/public.zip") + defer cleanup() + + tests := map[string]struct { + path string + expectedErrMsg string + }{ + "zip_file_exists": { + path: "/public.zip", + }, + "zip_file_does_not_exist": { + path: "/unknown", + expectedErrMsg: "404 Not Found", + }, + "invalid_url": { + path: "/%", + expectedErrMsg: "invalid URL", + }, + } + + vfs := New() + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + root, err := vfs.Root(context.Background(), url+tt.path) + if tt.expectedErrMsg != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tt.expectedErrMsg) + return + } + + require.NoError(t, err) + require.IsType(t, &zipArchive{}, root) + + f, err := root.Open(context.Background(), "index.html") + require.NoError(t, err) + + content, err := ioutil.ReadAll(f) + require.NoError(t, err) + require.Equal(t, "zip.gitlab.io/project/index.html\n", string(content)) + + fi, err := root.Lstat(context.Background(), "index.html") + require.NoError(t, err) + require.Equal(t, "index.html", fi.Name()) + + link, err := root.Readlink(context.Background(), "symlink.html") + require.NoError(t, err) + require.Equal(t, "subdir/linked.html", link) + }) + } +} + +func TestVFSFindOrOpenArchiveConcurrentAccess(t *testing.T) { + testServerURL, cleanup := newZipFileServerURL(t, "group/zip.gitlab.io/public.zip") + defer cleanup() + + path := testServerURL + "/public.zip" + + vfs := New().(*zipVFS) + root, err := vfs.Root(context.Background(), path) + require.NoError(t, err) + + done := make(chan struct{}) + defer close(done) + + // Try to hit a condition between the invocation + // of cache.GetWithExpiration and cache.Add + go func() { + for { + select { + case <-done: + return + + default: + vfs.cache.Flush() + vfs.cache.SetDefault(path, root) + } + } + }() + + require.Eventually(t, func() bool { + _, err := vfs.findOrOpenArchive(context.Background(), path) + return err == errAlreadyCached + }, time.Second, time.Nanosecond) +} diff --git a/metrics/metrics.go b/metrics/metrics.go index cb4287c8..dcfac76d 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -78,13 +78,13 @@ var ( Help: "The time (in seconds) it takes to get a response from the GitLab domains API", }, []string{"status_code"}) - // DiskServingFileSize metric for file size serving. serving_types: disk and object_storage - DiskServingFileSize = prometheus.NewHistogram(prometheus.HistogramOpts{ + // DiskServingFileSize metric for file size serving. Includes a vfs_name (local or zip). + DiskServingFileSize = prometheus.NewHistogramVec(prometheus.HistogramOpts{ Name: "gitlab_pages_disk_serving_file_size_bytes", Help: "The size in bytes for each file that has been served", // From 1B to 100MB in *10 increments (1 10 100 1,000 10,000 100,000 1'000,000 10'000,000 100'000,000) Buckets: prometheus.ExponentialBuckets(1.0, 10.0, 9), - }) + }, []string{"vfs_name"}) // ServingTime metric for time taken to find a file serving it or not found. ServingTime = prometheus.NewHistogram(prometheus.HistogramOpts{ diff --git a/shared/lookups/zip.gitlab.io.json b/shared/lookups/zip.gitlab.io.json new file mode 100644 index 00000000..cf755a58 --- /dev/null +++ b/shared/lookups/zip.gitlab.io.json @@ -0,0 +1,16 @@ +{ + "certificate": "", + "key": "", + "lookup_paths": [ + { + "access_control": false, + "https_only": false, + "prefix": "/", + "project_id": 123, + "source": { + "path": "http://127.0.0.1:37003/public.zip", + "type": "zip" + } + } + ] +} |