diff --git a/acceptance_test.go b/acceptance_test.go index 9497511a498089931b40d79894d8340dc0cd059a..e612b678ac9355092491a7eb33f4dc7d2b285661 100644 --- a/acceptance_test.go +++ b/acceptance_test.go @@ -1538,6 +1538,7 @@ func TestGitlabDomainsSource(t *testing.T) { domains: enabled: - new-source-test.gitlab.io + - zip.gitlab.io broken: pages-broken-poc.gitlab.io ` gitlabSourceConfigFile, cleanupGitlabSourceConfigFile := CreateGitlabSourceConfigFixtureFile(t, gitlabSourceConfig) @@ -1579,4 +1580,28 @@ domains: require.Equal(t, http.StatusBadGateway, response.StatusCode) }) + + t.Run("zip source", func(t *testing.T) { + response, err := GetPageFromListener(t, httpListener, "zip.gitlab.io", "/my/pages/zip/project/") + require.NoError(t, err) + + defer response.Body.Close() + body, err := ioutil.ReadAll(response.Body) + require.NoError(t, err) + + require.Equal(t, http.StatusOK, response.StatusCode) + require.Equal(t, "zip/index.html\n", string(body)) + }) + + t.Run("zip source custom not found", func(t *testing.T) { + response, err := GetPageFromListener(t, httpListener, "zip.gitlab.io", "/my/pages/zip/project/unknown") + require.NoError(t, err) + + defer response.Body.Close() + body, err := ioutil.ReadAll(response.Body) + require.NoError(t, err) + + require.Equal(t, http.StatusNotFound, response.StatusCode) + require.Equal(t, "zip custom not found\n", string(body)) + }) } diff --git a/app.go b/app.go index 7b1749a6c2cdc624401961caffd9596a88eb9f05..7e3c74bfc2a8e1f24abab12931cfe0cb5cec3f76 100644 --- a/app.go +++ b/app.go @@ -91,6 +91,11 @@ func (a *theApp) getHostAndDomain(r *http.Request) (string, *domain.Domain, erro } func (a *theApp) domain(host string) (*domain.Domain, error) { + // TODO https://gitlab.com/gitlab-org/gitlab-pages/-/issues/419 + if host == a.Domain { + return nil, nil + } + return a.domains.GetDomain(host) } diff --git a/go.mod b/go.mod index 1200d689d09ce0c26c52421027a629375b415a99..a2611b9b3a381309db2b00bf1719f4026509c5a2 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/gorilla/handlers v1.4.2 github.com/gorilla/securecookie v1.1.1 github.com/gorilla/sessions v1.2.0 + github.com/jfbus/httprs v0.0.0-20190827093123-b0af8319bb15 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 github.com/karrick/godirwalk v1.10.12 github.com/kr/text v0.2.0 // indirect diff --git a/go.sum b/go.sum index 550d6a7d8dea7cab48e58ed8089dc973ae5f54f3..8b37a3d1ea6ed2e90b0a48ec9b593097a579baf8 100644 --- a/go.sum +++ b/go.sum @@ -47,6 +47,7 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/reopen v1.0.0 h1:8tpLVR74DLpLObrn2KvsyxJY++2iORGR17WLUdSzUws= github.com/client9/reopen v1.0.0/go.mod h1:caXVCEr+lUtoN1FlsRiOWdfQtdRHIYfcb0ai8qKWtkQ= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -133,6 +134,7 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= @@ -150,6 +152,7 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= @@ -159,11 +162,14 @@ github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbk github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= +github.com/jfbus/httprs v0.0.0-20190827093123-b0af8319bb15 h1:HPqgCwRiChGXITjjipDuTJYVPkAUpM4lp0mfo7ONpjo= +github.com/jfbus/httprs v0.0.0-20190827093123-b0af8319bb15/go.mod h1:hve3GCzwH1IcxgpZ3UN4XKAPSKoIqJhsYF2ZifruodQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= @@ -196,6 +202,7 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/lightstep/lightstep-tracer-go v0.15.6 h1:D0GGa7afJ7GcQvu5as6ssLEEKYXvRgKI5d5cevtz8r4= github.com/lightstep/lightstep-tracer-go v0.15.6/go.mod h1:6AMpwZpsyCFwSovxzM78e+AsYxE8sGwiM6C3TytaWeI= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -209,10 +216,14 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg= github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -227,13 +238,17 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= @@ -278,7 +293,10 @@ github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= @@ -302,11 +320,15 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/tinylib/msgp v1.0.2 h1:DfdQrzQa7Yh2es9SuLkixqxuXS2SxsdYn0KbdrOGWD8= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= +github.com/uber-go/atomic v1.3.2 h1:Azu9lPBWRNKzYXSIwRfgRuDuS0YKsK4NFhiQv98gkxo= github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= +github.com/uber/jaeger-client-go v2.15.0+incompatible h1:NP3qsSqNxh8VYr956ur1N/1C1PjvOJnJykCzcD5QHbk= github.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v1.5.0 h1:OHbgr8l656Ub3Fw5k9SWnBfIEwvoHQ+W2y+Aa9D1Uyo= github.com/uber/jaeger-lib v1.5.0/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= @@ -336,6 +358,7 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -502,6 +525,7 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +gopkg.in/DataDog/dd-trace-go.v1 v1.7.0 h1:7wbMayb6JXcbAS95RN7MI42W3o1BCxCcdIzZfVWBAiE= gopkg.in/DataDog/dd-trace-go.v1 v1.7.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= @@ -512,10 +536,12 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/internal/serving/objectstorage/cache.go b/internal/serving/objectstorage/cache.go new file mode 100644 index 0000000000000000000000000000000000000000..16b807ed14f46002ead3897f5d404f2528128ae0 --- /dev/null +++ b/internal/serving/objectstorage/cache.go @@ -0,0 +1,56 @@ +package objectstorage + +import ( + "context" + "errors" + "sync" + + "github.com/sirupsen/logrus" + + "gitlab.com/gitlab-org/gitlab-pages/internal/zip/reader" +) + +var ( + errNotExists = errors.New("domain does not exist") +) + +type inMemory struct { + mu *sync.Mutex + reader *reader.Reader +} + +func newInMemoryCache() *inMemory { + return &inMemory{ + mu: new(sync.Mutex), + } +} +func (i *inMemory) Set(ctx context.Context, cancel func(), reader *reader.Reader) { + i.mu.Lock() + defer i.mu.Unlock() + + i.reader = reader + + // clears the reader when the ctx is done or cancelled + go i.clear(ctx, cancel) +} +func (i *inMemory) Reader() (*reader.Reader, error) { + i.mu.Lock() + defer i.mu.Unlock() + + if i.reader == nil { + return nil, errNotExists + } + + return i.reader, nil +} + +func (i *inMemory) clear(ctx context.Context, cancel func()) { + <-ctx.Done() + cancel() + + i.mu.Lock() + defer i.mu.Unlock() + + logrus.Debug("removing expired reader") + i.reader = nil +} diff --git a/internal/serving/objectstorage/objectstorage.go b/internal/serving/objectstorage/objectstorage.go new file mode 100644 index 0000000000000000000000000000000000000000..801fd997c653cbf6b6428bf145af415ebe890ba4 --- /dev/null +++ b/internal/serving/objectstorage/objectstorage.go @@ -0,0 +1,143 @@ +package objectstorage + +import ( + "context" + "fmt" + "io" + "mime" + "net/http" + "path/filepath" + "strings" + "time" + + "github.com/sirupsen/logrus" + + "gitlab.com/gitlab-org/gitlab-pages/internal/httperrors" + "gitlab.com/gitlab-org/gitlab-pages/internal/serving" + "gitlab.com/gitlab-org/gitlab-pages/internal/zip" + "gitlab.com/gitlab-org/gitlab-pages/internal/zip/reader" +) + +type cache interface { + Set(ctx context.Context, cancel func(), reader *reader.Reader) + Reader() (*reader.Reader, error) +} + +// ObjecStorage implements the serving.Serving interface. +// Contains a cache to store zip.Reader +type ObjectStorage struct { + cache cache +} + +// New gets called 6+ times per request so need to just init once. +// Think about reworking during https://gitlab.com/gitlab-org/gitlab-pages/-/issues/371 +var oss = &ObjectStorage{} + +func New() *ObjectStorage { + if oss.cache == nil { + oss.cache = newInMemoryCache() + } + + return oss +} + +func (os *ObjectStorage) ServeFileHTTP(handler serving.Handler) bool { + zipReader, err := os.getOrSetReader(handler) + if err != nil { + logrus.WithError(err).Error("failed so serve from zip file") + return false + } + + // TODO implement the logic from the disk reader to solve paths + filename := handler.SubPath + if filename == "" || filename == "/" { + filename = "index.html" + } + + err = os.handleZipFile(zipReader, filename, handler, http.StatusOK) + if err != nil { + if strings.Contains(err.Error(), "not found") { + return false + } + // TODO add metrics + logrus.WithError(err).Error("failed to serve from zip file") + return false + } + + return true +} + +func (os *ObjectStorage) ServeNotFoundHTTP(handler serving.Handler) { + zipReader, err := os.getOrSetReader(handler) + if err != nil { + logrus.WithError(err).Error("failed so serve from zip file") + httperrors.Serve404(handler.Writer) + return + } + + // try to serve custom 404 + err = os.handleZipFile(zipReader, "404.html", handler, http.StatusNotFound) + if err != nil { + if !strings.Contains(err.Error(), "not found") { + logrus.WithError(err).Error("failed to read zip file") + } + + httperrors.Serve404(handler.Writer) + return + } +} + +func (os *ObjectStorage) getOrSetReader(handler serving.Handler) (*reader.Reader, error) { + zipReader, err := os.cache.Reader() + if err != nil { + // let the context be canceled on the timeout so that the zipReader stays open for a while + // this context is used by the os.cache and zipartifacts.OpenArchive + // Setting to the same value as the internal/source/gitlab/cache entryRefreshTimeout: 60 * time.Second, + // TODO make it configurable + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + + zipReader, err = zip.OpenArchive(ctx, handler.LookupPath.Path) + if err != nil { + cancel() + return nil, fmt.Errorf("failed to open zip archive: %v", err) + } + go os.cache.Set(ctx, cancel, zipReader) + } + + return zipReader, nil +} + +func (os *ObjectStorage) handleZipFile(reader *reader.Reader, filename string, handler serving.Handler, status int) error { + file, stat, err := reader.Open(filename) + if err != nil { + return fmt.Errorf("failed to open file: %v", err) + } + defer file.Close() + + contentType := mime.TypeByExtension(filepath.Ext(stat.Name())) + return writeContent(handler, file, stat.ModTime(), contentType, status) +} + +func writeContent(handler serving.Handler, content io.ReadCloser, modTime time.Time, contentType string, status int) error { + if content == nil { + return fmt.Errorf("content is nil") + } + + w := handler.Writer + w.WriteHeader(status) + if !handler.LookupPath.HasAccessControl { + // Set caching headers + w.Header().Set("Cache-Control", "max-age=600") + w.Header().Set("Expires", time.Now().Add(10*time.Minute).Format(time.RFC1123)) + } + w.Header().Set("Content-Type", contentType) + w.Header().Set("Last-Modified", modTime.UTC().Format(http.TimeFormat)) + + var err error + _, err = io.Copy(w, content) + if err != nil { + return fmt.Errorf("failed to write response: %v", err) + } + + return nil +} diff --git a/internal/source/domains.go b/internal/source/domains.go index 77e1aa1d41704f57ca44c6c30ed33fcd2a8b28a4..632eba659e46d59a2fa5591d15cb336ce77dca0e 100644 --- a/internal/source/domains.go +++ b/internal/source/domains.go @@ -3,6 +3,7 @@ package source import ( "errors" "regexp" + "strings" "time" log "github.com/sirupsen/logrus" @@ -89,6 +90,9 @@ func (d *Domains) source(domain string) Source { return d.disk } + if strings.Contains(domain, "objectstorage.pages.test") { + return d.gitlab + } // 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. diff --git a/internal/source/gitlab/client/client.go b/internal/source/gitlab/client/client.go index 3a805ce946a1194de746f938e41556eb0b9a93a7..a88c742a6739a3ad42e3982917c63d4f51bc3371 100644 --- a/internal/source/gitlab/client/client.go +++ b/internal/source/gitlab/client/client.go @@ -73,6 +73,35 @@ func (gc *Client) Resolve(ctx context.Context, host string) *api.Lookup { // GetLookup returns a VirtualDomain configuration wrapped into a Lookup for a // given host func (gc *Client) GetLookup(ctx context.Context, host string) api.Lookup { + if host == "objectstorage.pages.test" { + return api.Lookup{ + Name: host, + Error: nil, + Domain: &api.VirtualDomain{ + LookupPaths: []api.LookupPath{ + { + ProjectID: 42, + AccessControl: true, + HTTPSOnly: false, + Prefix: "", + Source: api.Source{ + Type: "zip", + /* + mc config host add gdk http://127.0.0.1:9000 minio gdk-minio + mc mb gdk/pages + mc cp --recursive public/ gdk/pages/objectstorage + mc cp --recursive public.zip gdk/pages/objectstorage/ + mc cp --recursive public/ gdk/pages/objectstorage/public/ + mc share download gdk/pages/objectstorage/public.zip # generates a pre-signed URL + */ + // expires on WED 2020-07-01 17:00 UTC+10, + Path: "http://127.0.0.1:9000/pages/objectstorage/public.zip?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minio%2F20200624%2Fgdk%2Fs3%2Faws4_request&X-Amz-Date=20200624T070923Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=9fb32ec8c5a9743a876dde192a11771c30ea17837b9f10bf1f22cde1ff7c7b73", + }, + }, + }, + }, + } + } params := url.Values{} params.Set("host", host) diff --git a/internal/source/gitlab/factory.go b/internal/source/gitlab/factory.go index d526994f8992edd9ebd581e756f060e973f6a2fe..1335c5c66cf948c3524b9933a7abff9b824954aa 100644 --- a/internal/source/gitlab/factory.go +++ b/internal/source/gitlab/factory.go @@ -7,6 +7,7 @@ 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/objectstorage" "gitlab.com/gitlab-org/gitlab-pages/internal/serving/serverless" "gitlab.com/gitlab-org/gitlab-pages/internal/source/gitlab/api" ) @@ -32,6 +33,8 @@ func fabricateServing(lookup api.LookupPath) serving.Serving { switch source.Type { case "file": return disk.New() + case "zip": + return objectstorage.New() case "serverless": serving, err := serverless.NewFromAPISource(source.Serverless) if err != nil { diff --git a/internal/zip/errors.go b/internal/zip/errors.go new file mode 100644 index 0000000000000000000000000000000000000000..0c2fcf7f6b1cf008c6decfb190958aad16fca8a3 --- /dev/null +++ b/internal/zip/errors.go @@ -0,0 +1,57 @@ +package zip + +import ( + "errors" +) + +// These are exit codes used by subprocesses in cmd/gitlab-zip-xxx. We also use +// them to map errors and error messages that we use as label in Prometheus. +const ( + CodeNotZip = 10 + iota + CodeEntryNotFound + CodeArchiveNotFound + CodeLimitsReached + CodeUnknownError +) + +var ( + ErrorCode = map[int]error{ + CodeNotZip: errors.New("zip archive format invalid"), + CodeEntryNotFound: errors.New("zip entry not found"), + CodeArchiveNotFound: errors.New("zip archive not found"), + CodeLimitsReached: errors.New("zip processing limits reached"), + CodeUnknownError: errors.New("zip processing unknown error"), + } + + ErrorLabel = map[int]string{ + CodeNotZip: "archive_invalid", + CodeEntryNotFound: "entry_not_found", + CodeArchiveNotFound: "archive_not_found", + CodeLimitsReached: "limits_reached", + CodeUnknownError: "unknown_error", + } + + ErrBadMetadata = errors.New("zip artifacts metadata invalid") +) + +// ExitCodeByError find an os.Exit code for a corresponding error. +// CodeUnkownError in case it can not be found. +func ExitCodeByError(err error) int { + for c, e := range ErrorCode { + if err == e { + return c + } + } + + return CodeUnknownError +} + +// ErrorLabelByCode returns a Prometheus counter label associated with an exit code. +func ErrorLabelByCode(code int) string { + label, ok := ErrorLabel[code] + if ok { + return label + } + + return ErrorLabel[CodeUnknownError] +} diff --git a/internal/zip/errors_test.go b/internal/zip/errors_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a1fc2ea9ad744ae4a35454ffd803c9e9c9d757ad --- /dev/null +++ b/internal/zip/errors_test.go @@ -0,0 +1,32 @@ +package zip + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestExitCodeByError(t *testing.T) { + t.Run("when error has been recognized", func(t *testing.T) { + code := ExitCodeByError(ErrorCode[CodeLimitsReached]) + + require.Equal(t, code, CodeLimitsReached) + require.Greater(t, code, 10) + }) + + t.Run("when error is an unknown one", func(t *testing.T) { + code := ExitCodeByError(errors.New("unknown error")) + + require.Equal(t, code, CodeUnknownError) + require.Greater(t, code, 10) + }) +} + +func TestErrorLabels(t *testing.T) { + for code := range ErrorCode { + _, ok := ErrorLabel[code] + + require.True(t, ok) + } +} diff --git a/internal/zip/open_archive.go b/internal/zip/open_archive.go new file mode 100644 index 0000000000000000000000000000000000000000..d1c2abe2c3a4009b3e5eac2a1f325d7eb99f6e77 --- /dev/null +++ b/internal/zip/open_archive.go @@ -0,0 +1,126 @@ +package zip + +import ( + "context" + "fmt" + "io" + "net" + "net/http" + "os" + "strings" + "time" + + "github.com/jfbus/httprs" + + "gitlab.com/gitlab-org/labkit/correlation" + "gitlab.com/gitlab-org/labkit/mask" + "gitlab.com/gitlab-org/labkit/tracing" + + "gitlab.com/gitlab-org/gitlab-pages/internal/zip/reader" +) + +var httpClient = &http.Client{ + Transport: tracing.NewRoundTripper(correlation.NewInstrumentedRoundTripper(&http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 10 * time.Second, + }).DialContext, + IdleConnTimeout: 30 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 10 * time.Second, + ResponseHeaderTimeout: 30 * time.Second, + DisableCompression: true, + }, correlation.WithClientName("gitlab-pages-zip-artifacts"))), +} + +type archive struct { + reader io.ReaderAt + size int64 +} + +// OpenArchive will open a zip.Reader from a local path or a remote object store URL +// in case of remote url it will make use of ranged requestes to support seeking. +// If the path do not exists error will be ErrArchiveNotFound, +// if the file isn't a zip archive error will be ErrNotAZip +func OpenArchive(ctx context.Context, archivePath string) (*reader.Reader, error) { + archive, err := openArchiveLocation(ctx, archivePath) + if err != nil { + return nil, err + } + + return openZipReader(archive.reader, archive.size) +} + +func openArchiveLocation(ctx context.Context, location string) (*archive, error) { + if isURL(location) { + return openHTTPArchive(ctx, location) + } + + return openFileArchive(ctx, location) +} + +func isURL(path string) bool { + return strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") +} + +func openHTTPArchive(ctx context.Context, archivePath string) (*archive, error) { + scrubbedArchivePath := mask.URL(archivePath) + req, err := http.NewRequest(http.MethodGet, archivePath, nil) + if err != nil { + return nil, fmt.Errorf("can't create HTTP GET %q: %v", scrubbedArchivePath, err) + } + req = req.WithContext(ctx) + + // nolint: bodyclose + // false positive, closing when the ctx is done + resp, err := httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("HTTP GET %q: %v", scrubbedArchivePath, err) + } else if resp.StatusCode == http.StatusNotFound { + return nil, ErrorCode[CodeArchiveNotFound] + } else if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("HTTP GET %q: %d: %v", scrubbedArchivePath, resp.StatusCode, resp.Status) + } + + rs := httprs.NewHttpReadSeeker(resp, httpClient) + + go func() { + <-ctx.Done() + resp.Body.Close() + rs.Close() + }() + + return &archive{reader: rs, size: resp.ContentLength}, nil +} + +func openFileArchive(ctx context.Context, archivePath string) (*archive, error) { + file, err := os.Open(archivePath) + if err != nil { + if os.IsNotExist(err) { + return nil, ErrorCode[CodeArchiveNotFound] + } + } + + go func() { + <-ctx.Done() + // We close the archive from this goroutine so that we can safely return a *zip.Reader instead of a *zip.ReadCloser + file.Close() + }() + + stat, err := file.Stat() + if err != nil { + return nil, err + } + + return &archive{reader: file, size: stat.Size()}, nil +} + +func openZipReader(archive io.ReaderAt, size int64) (*reader.Reader, error) { + reader, err := reader.New(archive, size) + if err != nil { + return nil, ErrorCode[CodeNotZip] + } + + return reader, nil +} diff --git a/internal/zip/open_archive_test.go b/internal/zip/open_archive_test.go new file mode 100644 index 0000000000000000000000000000000000000000..26cc40f629a27691b357788d87bd2a183c4755e9 --- /dev/null +++ b/internal/zip/open_archive_test.go @@ -0,0 +1,60 @@ +package zip + +import ( + "context" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOpenHTTPArchive(t *testing.T) { + const ( + zipFile = "public.zip" + entryName = "public/index.html" + contents = "zip/index.html\n" + testRoot = "../../shared/pages/group/zip.gitlab.io/" + ) + + srv := httptest.NewServer(http.FileServer(http.Dir(testRoot))) + defer srv.Close() + + zr, err := OpenArchive(context.Background(), srv.URL+"/"+zipFile) + require.NoError(t, err, "call OpenArchive") + require.Len(t, zr.Archive().File, 3) + + zf := zr.Archive().File[1] + require.Equal(t, entryName, zf.Name, "zip entry name") + + entry, err := zf.Open() + require.NoError(t, err, "get zip entry reader") + defer entry.Close() + + actualContents, err := ioutil.ReadAll(entry) + require.NoError(t, err, "read zip entry contents") + require.Equal(t, contents, string(actualContents), "compare zip entry contents") +} + +func TestOpenFileArchive(t *testing.T) { + const ( + entryName = "public/index.html" + contents = "zip/index.html\n" + testRoot = "../../shared/pages/group/zip.gitlab.io/public.zip" + ) + zr, err := OpenArchive(context.Background(), testRoot) + require.NoError(t, err, "call OpenArchive") + require.Len(t, zr.Archive().File, 3) + + zf := zr.Archive().File[1] + require.Equal(t, entryName, zf.Name, "zip entry name") + + entry, err := zf.Open() + require.NoError(t, err, "get zip entry reader") + defer entry.Close() + + actualContents, err := ioutil.ReadAll(entry) + require.NoError(t, err, "read zip entry contents") + require.Equal(t, contents, string(actualContents), "compare zip entry contents") +} diff --git a/internal/zip/reader/reader.go b/internal/zip/reader/reader.go new file mode 100644 index 0000000000000000000000000000000000000000..4900a46b06a7add52f413989c066d9a51e73de39 --- /dev/null +++ b/internal/zip/reader/reader.go @@ -0,0 +1,136 @@ +package reader + +import ( + "archive/zip" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" +) + +const zipDeployPath = "public" +const maxSymlinkSize = 4096 +const maxSymlinkDepth = 3 + +// Reader .. +type Reader struct { + archive *zip.Reader +} + +// New creates a new zip Reader. A reader can find a file inside an archive +// with a depth < `maxSymlinkDepth` +func New(readerAt io.ReaderAt, size int64) (*Reader, error) { + archive, err := zip.NewReader(readerAt, size) + if err != nil { + return nil, err + } + + return &Reader{archive: archive}, nil +} + +func (r *Reader) find(path string) *zip.File { + if r.archive == nil { + return nil + } + + // This is O(n) search, very, very, very slow + for _, file := range r.archive.File { + if file.Name == path || file.Name == path+"/" { + return file + } + } + + return nil +} + +func (r *Reader) readSymlink(file *zip.File) (string, error) { + fi := file.FileInfo() + + if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink { + return "", nil + } + + if fi.Size() > maxSymlinkSize { + return "", errors.New("symlink size too long") + } + + rc, err := file.Open() + if err != nil { + return "", err + } + defer rc.Close() + + data, err := ioutil.ReadAll(rc) + if err != nil { + return "", err + } + + // resolve symlink location relative to current file + targetPath, err := filepath.Rel(filepath.Dir(file.Name), string(data)) + if err != nil { + return "", err + } + + return targetPath, nil +} + +func (r *Reader) resolveUnchecked(path string) (*zip.File, error) { + // limit the resolve depth of symlink + for depth := 0; depth < maxSymlinkDepth; depth++ { + file := r.find(path) + if file == nil { + break + } + + targetPath, err := r.readSymlink(file) + if err != nil { + return nil, err + } + + // not a symlink + if targetPath == "" { + return file, nil + } + + path = targetPath + } + + return nil, fmt.Errorf("%q: not found", path) +} + +func (r *Reader) resolvePublic(path string) (string, *zip.File, error) { + path = filepath.Join(zipDeployPath, path) + file, err := r.resolveUnchecked(path) + if err != nil { + return "", nil, err + } + + if !strings.HasPrefix(file.Name, zipDeployPath+"/") { + return "", nil, fmt.Errorf("%q: is not in %s/", file.Name, zipDeployPath) + } + + return file.Name[len(zipDeployPath)+1:], file, nil +} + +// Open returns a ReadCloser to the caller which is responsible of closing. os.FileInfo is also returned in one go. +func (r *Reader) Open(path string) (io.ReadCloser, os.FileInfo, error) { + _, file, err := r.resolvePublic(path) + if err != nil { + return nil, nil, err + } + + rc, err := file.Open() + if err != nil { + return nil, nil, err + } + + return rc, file.FileInfo(), nil +} + +// Archive returns the zip.Reader +func (r *Reader) Archive() *zip.Reader { + return r.archive +} diff --git a/internal/zip/reader/reader_test.go b/internal/zip/reader/reader_test.go new file mode 100644 index 0000000000000000000000000000000000000000..7d49cd712a81c61984ef552ca03c7e6feb82d305 --- /dev/null +++ b/internal/zip/reader/reader_test.go @@ -0,0 +1,31 @@ +package reader + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewReader(t *testing.T) { + zipFile, err := os.Open("../../../shared/pages/group/zip.gitlab.io/public.zip") + require.NoError(t, err) + zfi, err := zipFile.Stat() + require.NoError(t, err) + + reader, err := New(zipFile, zfi.Size()) + require.NoError(t, err) + + f, fi, err := reader.Open("index.html") + require.NoError(t, err) + defer f.Close() + require.NotZero(t, fi.Size()) + + actualContents, err := ioutil.ReadAll(f) + require.NoError(t, err, "read zip entry contents") + require.Equal(t, "zip/index.html\n", string(actualContents), "compare zip entry contents") + + _, _, err = reader.Open("unknown.html") + require.EqualError(t, err, "\"public/unknown.html\": not found") +} diff --git a/shared/lookups/zip.gitlab.io.json b/shared/lookups/zip.gitlab.io.json new file mode 100644 index 0000000000000000000000000000000000000000..cf54d49e5bf5c91f12269647c3255c9d404b944a --- /dev/null +++ b/shared/lookups/zip.gitlab.io.json @@ -0,0 +1,16 @@ +{ + "certificate": "", + "key": "", + "lookup_paths": [ + { + "access_control": false, + "https_only": false, + "prefix": "/my/pages/zip/project/", + "project_id": 123, + "source": { + "path": "group/zip.gitlab.io/public.zip", + "type": "zip" + } + } + ] +} diff --git a/shared/pages/group/zip.gitlab.io/public.zip b/shared/pages/group/zip.gitlab.io/public.zip new file mode 100644 index 0000000000000000000000000000000000000000..0d43cfadcee4f93b5e758a2a5f3077edb7379a76 Binary files /dev/null and b/shared/pages/group/zip.gitlab.io/public.zip differ