diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 44ab3617..e710af51 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -41,6 +41,19 @@ jobs: go-version: 1.19.x - name: Check out source code uses: actions/checkout@v3 + + - name: Push release tag to zui + if: github.event_name == 'release' && github.event.action == 'published' && matrix.os == 'linux' && matrix.arch == 'amd64' + uses: ncipollo/release-action@v1 + with: + token: ${{ secrets.ZUI_TOKEN }} + repo: zui + owner: project-zot + tag: ${{ github.event.release.tag_name }} + name: ${{ github.event.release.name }} + body: ${{ github.event.release.body }} + commit: main + - name: Cache go dependencies id: cache-go-dependencies uses: actions/cache@v3 @@ -105,7 +118,7 @@ jobs: make OS=$OS ARCH=$ARCH sudo env "PATH=$PATH" make privileged-test else - make OS=$OS ARCH=$ARCH binary binary-minimal binary-debug cli bench exporter-minimal + make OS=$OS ARCH=$ARCH binary binary-minimal binary-debug binary-ui cli bench exporter-minimal fi env: S3MOCK_ENDPOINT: localhost:4566 diff --git a/.gitignore b/.gitignore index f9825c76..22fc769a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out +# Output of the UI build +pkg/extensions/build/ + # Tooling used for blackbox testing hack/ .stacker/ diff --git a/Makefile b/Makefile index fbf63aa4..547d8fd6 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ hyphen:= - extended-name:= .PHONY: all -all: modcheck swagger binary binary-minimal binary-debug cli bench exporter-minimal verify-config test covhtml check check-gh-actions +all: modcheck swagger binary binary-minimal binary-debug binary-ui cli bench exporter-minimal verify-config test covhtml check check-gh-actions .PHONY: modcheck modcheck: @@ -45,7 +45,7 @@ ifdef EXTENSIONS endif .PHONY: build-metadata -build-metadata: +build-metadata: $(if $(findstring ui,$(EXTENSIONS)), ui) echo "Imports: \n" go list -tags $(EXTENSIONS) -f '{{ join .Imports "\n" }}' ./... | sort -u echo "\n Files: \n" @@ -57,13 +57,19 @@ binary-minimal: modcheck build-metadata env CGO_ENABLED=0 GOOS=$(OS) GOARCH=$(ARCH) go build -o bin/zot-$(OS)-$(ARCH)-minimal -buildmode=pie -tags containers_image_openpgp -v -trimpath -ldflags "-X zotregistry.io/zot/pkg/api/config.ReleaseTag=${RELEASE_TAG} -X zotregistry.io/zot/pkg/api/config.Commit=${COMMIT} -X zotregistry.io/zot/pkg/api/config.BinaryType=minimal -X zotregistry.io/zot/pkg/api/config.GoVersion=${GO_VERSION} -s -w" ./cmd/zot .PHONY: binary +binary: $(if $(findstring ui,$(EXTENSIONS)), ui) binary: modcheck create-name build-metadata env CGO_ENABLED=0 GOOS=$(OS) GOARCH=$(ARCH) go build -o bin/zot-$(OS)-$(ARCH) -buildmode=pie -tags $(EXTENSIONS),containers_image_openpgp -v -trimpath -ldflags "-X zotregistry.io/zot/pkg/api/config.ReleaseTag=${RELEASE_TAG} -X zotregistry.io/zot/pkg/api/config.Commit=${COMMIT} -X zotregistry.io/zot/pkg/api/config.BinaryType=$(extended-name) -X zotregistry.io/zot/pkg/api/config.GoVersion=${GO_VERSION} -s -w" ./cmd/zot .PHONY: binary-debug +binary-debug: $(if $(findstring ui,$(EXTENSIONS)), ui) binary-debug: modcheck swagger create-name build-metadata env CGO_ENABLED=0 GOOS=$(OS) GOARCH=$(ARCH) go build -o bin/zot-$(OS)-$(ARCH)-debug -buildmode=pie -tags $(EXTENSIONS),debug,containers_image_openpgp -v -gcflags all='-N -l' -ldflags "-X zotregistry.io/zot/pkg/api/config.ReleaseTag=${RELEASE_TAG} -X zotregistry.io/zot/pkg/api/config.Commit=${COMMIT} -X zotregistry.io/zot/pkg/api/config.BinaryType=$(extended-name) -X zotregistry.io/zot/pkg/api/config.GoVersion=${GO_VERSION}" ./cmd/zot +.PHONY: binary-ui +binary-ui: modcheck create-name build-metadata ui + env CGO_ENABLED=0 GOOS=$(OS) GOARCH=$(ARCH) go build -o bin/zot-$(OS)-$(ARCH)-ui -buildmode=pie -tags $(EXTENSIONS),ui,containers_image_openpgp -v -trimpath -ldflags "-X zotregistry.io/zot/pkg/api/config.Commit=${COMMIT} -X zotregistry.io/zot/pkg/api/config.BinaryType=$(extended-name) -X zotregistry.io/zot/pkg/api/config.GoVersion=${GO_VERSION} -s -w" ./cmd/zot + .PHONY: cli cli: modcheck create-name build-metadata env CGO_ENABLED=0 GOOS=$(OS) GOARCH=$(ARCH) go build -o bin/zli-$(OS)-$(ARCH) -buildmode=pie -tags $(EXTENSIONS),search,containers_image_openpgp -v -trimpath -ldflags "-X zotregistry.io/zot/pkg/api/config.Commit=${COMMIT} -X zotregistry.io/zot/pkg/api/config.BinaryType=$(extended-name) -X zotregistry.io/zot/pkg/api/config.GoVersion=${GO_VERSION} -s -w" ./cmd/zli @@ -78,6 +84,7 @@ exporter-minimal: modcheck build-metadata env CGO_ENABLED=0 GOOS=$(OS) GOARCH=$(ARCH) go build -o bin/zxp-$(OS)-$(ARCH) -buildmode=pie -tags containers_image_openpgp -v -trimpath ./cmd/zxp .PHONY: test +test: $(if $(findstring ui,$(EXTENSIONS)), ui) test: check-skopeo $(TESTDATA) $(NOTATION) $(ORAS) go test -failfast -tags $(EXTENSIONS),containers_image_openpgp -v -trimpath -race -timeout 15m -cover -coverpkg ./... -coverprofile=coverage-extended.txt -covermode=atomic ./... go test -failfast -tags containers_image_openpgp -v -trimpath -race -cover -coverpkg ./... -coverprofile=coverage-minimal.txt -covermode=atomic ./... @@ -87,6 +94,7 @@ test: check-skopeo $(TESTDATA) $(NOTATION) $(ORAS) go test -failfast -tags stress,$(EXTENSIONS),containers_image_openpgp -v -trimpath -race -timeout 15m ./pkg/cli/stress_test.go .PHONY: privileged-test +privileged-test: $(if $(findstring ui,$(EXTENSIONS)), ui) privileged-test: check-skopeo $(TESTDATA) $(NOTATION) go test -failfast -tags needprivileges,$(EXTENSIONS),containers_image_openpgp -v -trimpath -race -timeout 15m -cover -coverpkg ./... -coverprofile=coverage-dev-needprivileges.txt -covermode=atomic ./pkg/storage/... ./pkg/cli/... -run ^TestElevatedPrivileges @@ -151,12 +159,16 @@ $(GOLINTER): $(GOLINTER) version .PHONY: check +check: $(if $(findstring ui,$(EXTENSIONS)), ui) check: ./golangcilint.yaml $(GOLINTER) + mkdir -p pkg/extensions/build; touch pkg/extensions/build/.empty $(GOLINTER) --config ./golangcilint.yaml run --enable-all --out-format=colored-line-number --build-tags containers_image_openpgp ./... $(GOLINTER) --config ./golangcilint.yaml run --enable-all --out-format=colored-line-number --build-tags $(EXTENSIONS),containers_image_openpgp ./... + $(GOLINTER) --config ./golangcilint.yaml run --enable-all --out-format=colored-line-number --build-tags $(EXTENSIONS),containers_image_openpgp,ui,debug ./... $(GOLINTER) --config ./golangcilint.yaml run --enable-all --out-format=colored-line-number --build-tags dev,containers_image_openpgp ./... $(GOLINTER) --config ./golangcilint.yaml run --enable-all --out-format=colored-line-number --build-tags dev,$(EXTENSIONS),containers_image_openpgp ./... $(GOLINTER) --config ./golangcilint.yaml run --enable-all --out-format=colored-line-number --build-tags stress,$(EXTENSIONS),containers_image_openpgp ./... + rm pkg/extensions/build/.empty swagger/docs.go: swag -v || go install github.com/swaggo/swag/cmd/swag@1.6.3 @@ -204,6 +216,7 @@ clean: rm -rf hack rm -rf test/data/zot-test rm -rf test/data/zot-cve-test + rm -rf pkg/extensions/build .PHONY: run run: binary test @@ -262,12 +275,12 @@ run-container: .PHONY: binary-stacker binary-stacker: - ${STACKER} build \ + ${STACKER} --debug build \ -f build/stacker.yaml \ - --substitute COMMIT=$(PWD) \ - --substitute OS=$(OS) \ - --substitute ARCH=$(ARCH) \ - --substitute PWD=$(PWD) + --substitute PWD=$$PWD \ + --substitute COMMIT=$$COMMIT \ + --substitute ARCH=$$ARCH \ + --substitute OS=$$OS .PHONY: image image: @@ -369,3 +382,20 @@ $(COSIGN): mkdir -p $(TOOLSDIR)/bin curl -fsSL https://github.com/sigstore/cosign/releases/download/v1.13.0/cosign-linux-amd64 -o $@; \ chmod +x $@ + +.PHONY: ui +ui: + pwd=$$(pwd);\ + tdir=$$(mktemp -d);\ + cd $$tdir;\ + if [ -z $(RELEASE_UI) ]; then\ + git clone https://github.com/project-zot/zui.git;\ + else\ + git clone --depth 1 --branch $(RELEASE_TAG) https://github.com/project-zot/zui.git;\ + fi;\ + cd zui;\ + npm install;\ + npm run build;\ + cd $$pwd;\ + rm -rf ./pkg/extensions/build;\ + cp -R $$tdir/zui/build ./pkg/extensions/; diff --git a/build/stacker.yaml b/build/stacker.yaml index 279d23bf..65a26ef3 100644 --- a/build/stacker.yaml +++ b/build/stacker.yaml @@ -45,7 +45,7 @@ build: arch: ${{ARCH}} from: type: docker - url: docker://gcr.io/distroless/base:latest-${{ARCH}} + url: docker://zothub.io/c3/base:jammy overlay_dirs: - source: ../.build/${{REPO_NAME}}/binary dest: /usr/local/bin diff --git a/examples/config-ui.json b/examples/config-ui.json new file mode 100644 index 00000000..4dfed6e8 --- /dev/null +++ b/examples/config-ui.json @@ -0,0 +1,23 @@ +{ + "distSpecVersion": "1.1.0-dev", + "storage": { + "rootDirectory": "/tmp/zot" + }, + "http": { + "address": "0.0.0.0", + "port": "8080" + }, + "log": { + "level": "debug" + }, + "extensions": { + "search": { + "cve": { + "updateInterval": "2h" + } + }, + "ui": { + "enable": true + } + } +} diff --git a/pkg/api/routes.go b/pkg/api/routes.go index 5cc83337..aaff7e2c 100644 --- a/pkg/api/routes.go +++ b/pkg/api/routes.go @@ -58,7 +58,8 @@ func allowedMethods(method string) []string { } func (rh *RouteHandler) SetupRoutes() { - rh.c.Router.Use(AuthHandler(rh.c)) + prefixedRouter := rh.c.Router.PathPrefix(constants.RoutePrefix).Subrouter() + prefixedRouter.Use(AuthHandler(rh.c)) // authz is being enabled if AccessControl is specified // if Authn is not present AccessControl will have only default policies if rh.c.Config.AccessControl != nil && !isBearerAuthEnabled(rh.c.Config) { @@ -68,11 +69,10 @@ func (rh *RouteHandler) SetupRoutes() { rh.c.Log.Info().Msg("default policy only access control is being enabled") } - rh.c.Router.Use(AuthzHandler(rh.c)) + prefixedRouter.Use(AuthzHandler(rh.c)) } // https://github.com/opencontainers/distribution-spec/blob/main/spec.md#endpoints - prefixedRouter := rh.c.Router.PathPrefix(constants.RoutePrefix).Subrouter() { prefixedRouter.HandleFunc(fmt.Sprintf("/{name:%s}/tags/list", zreg.NameRegexp.String()), rh.ListTags).Methods(allowedMethods("GET")...) @@ -116,7 +116,7 @@ func (rh *RouteHandler) SetupRoutes() { constants.ArtifactSpecRoutePrefix, zreg.NameRegexp.String()), rh.GetOrasReferrers).Methods("GET") // swagger - debug.SetupSwaggerRoutes(rh.c.Config, rh.c.Router, rh.c.Log) + debug.SetupSwaggerRoutes(rh.c.Config, rh.c.Router, AuthHandler(rh.c), rh.c.Log) // Setup Extensions Routes if rh.c.Config != nil { @@ -125,9 +125,10 @@ func (rh *RouteHandler) SetupRoutes() { prefixedRouter.HandleFunc("/metrics", rh.GetMetrics).Methods("GET") } else { // extended build - ext.SetupMetricsRoutes(rh.c.Config, rh.c.Router, rh.c.StoreController, rh.c.Log) - ext.SetupSearchRoutes(rh.c.Config, rh.c.Router, rh.c.StoreController, rh.c.RepoDB, rh.c.CveInfo, rh.c.Log) - gqlPlayground.SetupGQLPlaygroundRoutes(rh.c.Config, rh.c.Router, rh.c.StoreController, rh.c.Log) + ext.SetupMetricsRoutes(rh.c.Config, rh.c.Router, rh.c.StoreController, AuthHandler(rh.c), rh.c.Log) + ext.SetupSearchRoutes(rh.c.Config, prefixedRouter, rh.c.StoreController, rh.c.RepoDB, rh.c.CveInfo, rh.c.Log) + ext.SetupUIRoutes(rh.c.Config, rh.c.Router, rh.c.StoreController, rh.c.Log) + gqlPlayground.SetupGQLPlaygroundRoutes(rh.c.Config, prefixedRouter, rh.c.StoreController, rh.c.Log) } } } diff --git a/pkg/cli/extensions_test.go b/pkg/cli/extensions_test.go index 2b714e94..5a8e005a 100644 --- a/pkg/cli/extensions_test.go +++ b/pkg/cli/extensions_test.go @@ -107,7 +107,8 @@ func TestServeExtensions(t *testing.T) { WaitTillServerReady(baseURL) data, err := os.ReadFile(logFile.Name()) So(err, ShouldBeNil) - So(string(data), ShouldContainSubstring, "\"Extensions\":{\"Search\":null,\"Sync\":null,\"Metrics\":null,\"Scrub\":null,\"Lint\":null") //nolint:lll // gofumpt conflicts with lll + So(string(data), ShouldContainSubstring, + "\"Extensions\":{\"Search\":null,\"Sync\":null,\"Metrics\":null,\"Scrub\":null,\"Lint\":null,\"UI\":null") //nolint:lll // gofumpt conflicts with lll }) } @@ -148,7 +149,7 @@ func testWithMetricsEnabled(cfgContentFormat string) { data, err := os.ReadFile(logFile.Name()) So(err, ShouldBeNil) So(string(data), ShouldContainSubstring, - "\"Extensions\":{\"Search\":null,\"Sync\":null,\"Metrics\":{\"Enable\":true,\"Prometheus\":{\"Path\":\"/metrics\"}},\"Scrub\":null,\"Lint\":null}") //nolint:lll // gofumpt conflicts with lll + "\"Metrics\":{\"Enable\":true,\"Prometheus\":{\"Path\":\"/metrics\"}}") } func TestServeMetricsExtension(t *testing.T) { @@ -272,7 +273,7 @@ func TestServeMetricsExtension(t *testing.T) { data, err := os.ReadFile(logFile.Name()) So(err, ShouldBeNil) So(string(data), ShouldContainSubstring, - "\"Extensions\":{\"Search\":null,\"Sync\":null,\"Metrics\":{\"Enable\":false,\"Prometheus\":{\"Path\":\"/metrics\"}},\"Scrub\":null,\"Lint\":null}}") //nolint:lll // gofumpt conflicts with lll + "\"Metrics\":{\"Enable\":false,\"Prometheus\":{\"Path\":\"/metrics\"}}") //nolint:lll // gofumpt conflicts with lll }) } @@ -472,8 +473,7 @@ func TestServeScrubExtension(t *testing.T) { defer os.Remove(logPath) // clean up // Even if in config we specified scrub interval=1h, the minimum interval is 2h dataStr := string(data) - So(dataStr, ShouldContainSubstring, - "\"Extensions\":{\"Search\":null,\"Sync\":null,\"Metrics\":null,\"Scrub\":{\"Enable\":true,\"Interval\":3600000000000},\"Lint\":null") //nolint:lll // gofumpt conflicts with lll + So(dataStr, ShouldContainSubstring, "\"Scrub\":{\"Enable\":true,\"Interval\":3600000000000}") So(dataStr, ShouldContainSubstring, "Scrub interval set to too-short interval < 2h, changing scrub duration to 2 hours and continuing.") }) @@ -536,8 +536,7 @@ func TestServeScrubExtension(t *testing.T) { So(err, ShouldBeNil) defer os.Remove(logPath) // clean up dataStr := string(data) - So(dataStr, ShouldContainSubstring, - "\"Extensions\":{\"Search\":null,\"Sync\":null,\"Metrics\":null,\"Scrub\":{\"Enable\":false,\"Interval\":86400000000000},\"Lint\":null}") //nolint:lll // gofumpt conflicts with lll + So(dataStr, ShouldContainSubstring, "\"Scrub\":{\"Enable\":false,\"Interval\":86400000000000}") So(dataStr, ShouldContainSubstring, "Scrub config not provided, skipping scrub") So(dataStr, ShouldNotContainSubstring, "Scrub interval set to too-short interval < 2h, changing scrub duration to 2 hours and continuing.") @@ -639,7 +638,8 @@ func TestServeSearchEnabled(t *testing.T) { // to avoid data race when multiple go routines write to trivy DB instance. defer os.Remove(logPath) // clean up - substring := "\"Extensions\":{\"Search\":{\"Enable\":true,\"CVE\":null},\"Sync\":null,\"Metrics\":null,\"Scrub\":null,\"Lint\":null}" //nolint:lll // gofumpt conflicts with lll + substring := `"Extensions":{"Search":{"Enable":true,"CVE":null}` + found, err := readLogFileAndSearchString(logPath, substring, readLogFileTimeout) So(found, ShouldBeTrue) So(err, ShouldBeNil) @@ -680,7 +680,8 @@ func TestServeSearchEnabledCVE(t *testing.T) { // to avoid data race when multiple go routines write to trivy DB instance. WaitTillTrivyDBDownloadStarted(tempDir) - substring := "\"Extensions\":{\"Search\":{\"Enable\":true,\"CVE\":{\"UpdateInterval\":3600000000000,\"Trivy\":null}},\"Sync\":null,\"Metrics\":null,\"Scrub\":null,\"Lint\":null}" //nolint:lll // gofumpt conflicts with lll + substring := "\"Search\":{\"Enable\":true,\"CVE\":{\"UpdateInterval\":3600000000000,\"Trivy\":null}}" + found, err := readLogFileAndSearchString(logPath, substring, readLogFileTimeout) So(found, ShouldBeTrue) So(err, ShouldBeNil) @@ -726,7 +727,7 @@ func TestServeSearchEnabledNoCVE(t *testing.T) { So(err, ShouldBeNil) defer os.Remove(logPath) // clean up - substring := "\"Extensions\":{\"Search\":{\"Enable\":true,\"CVE\":null},\"Sync\":null,\"Metrics\":null,\"Scrub\":null,\"Lint\":null}" //nolint:lll // gofumpt conflicts with lll + substring := `"Extensions":{"Search":{"Enable":true,"CVE":null}` //nolint:lll // gofumpt conflicts with lll found, err := readLogFileAndSearchString(logPath, substring, readLogFileTimeout) So(found, ShouldBeTrue) So(err, ShouldBeNil) @@ -768,7 +769,7 @@ func TestServeSearchDisabled(t *testing.T) { defer os.Remove(logPath) // clean up dataStr := string(data) So(dataStr, ShouldContainSubstring, - "\"Extensions\":{\"Search\":{\"Enable\":false,\"CVE\":{\"UpdateInterval\":10800000000000,\"Trivy\":null}},\"Sync\":null,\"Metrics\":null,\"Scrub\":null,\"Lint\":null}") //nolint:lll // gofumpt conflicts with lll + "\"Search\":{\"Enable\":false,\"CVE\":{\"UpdateInterval\":10800000000000,\"Trivy\":null}") So(dataStr, ShouldContainSubstring, "CVE config not provided, skipping CVE update") So(dataStr, ShouldNotContainSubstring, "CVE update interval set to too-short interval < 2h, changing update duration to 2 hours and continuing.") diff --git a/pkg/debug/gqlplayground/gqlplayground.go b/pkg/debug/gqlplayground/gqlplayground.go index 61bcc80e..3ce638ea 100644 --- a/pkg/debug/gqlplayground/gqlplayground.go +++ b/pkg/debug/gqlplayground/gqlplayground.go @@ -33,7 +33,7 @@ func SetupGQLPlaygroundRoutes(conf *config.Config, router *mux.Router, } //nolint:lll - router.PathPrefix(constants.RoutePrefix + debugCst.GQLPlaygroundEndpoint).HandlerFunc(func(writer http.ResponseWriter, req *http.Request) { + router.PathPrefix(debugCst.GQLPlaygroundEndpoint).HandlerFunc(func(writer http.ResponseWriter, req *http.Request) { writer.Header().Add("Content-Type", "text/html") proto := "" diff --git a/pkg/debug/swagger/swagger.go b/pkg/debug/swagger/swagger.go index 1087a650..d8a1ffa9 100644 --- a/pkg/debug/swagger/swagger.go +++ b/pkg/debug/swagger/swagger.go @@ -17,9 +17,12 @@ import ( _ "zotregistry.io/zot/swagger" ) -func SetupSwaggerRoutes(conf *config.Config, router *mux.Router, log log.Logger, +func SetupSwaggerRoutes(conf *config.Config, router *mux.Router, authFunc mux.MiddlewareFunc, + log log.Logger, ) { log.Info().Msg("setting up swagger route") // swagger swagger "/swagger/v2/index.html" - router.PathPrefix("/swagger/v2/").Methods("GET").Handler(httpSwagger.WrapHandler) + swgRouter := router.PathPrefix("/swagger/v2/").Subrouter() + swgRouter.Use(authFunc) + swgRouter.Methods("GET").Handler(httpSwagger.WrapHandler) } diff --git a/pkg/debug/swagger/swagger_disabled.go b/pkg/debug/swagger/swagger_disabled.go index 4fa187de..7d5b5167 100644 --- a/pkg/debug/swagger/swagger_disabled.go +++ b/pkg/debug/swagger/swagger_disabled.go @@ -16,7 +16,8 @@ import ( _ "zotregistry.io/zot/swagger" ) -func SetupSwaggerRoutes(conf *config.Config, router *mux.Router, log log.Logger, +func SetupSwaggerRoutes(conf *config.Config, router *mux.Router, authFunc mux.MiddlewareFunc, + log log.Logger, ) { // swagger swagger "/swagger/v2/index.html" log.Warn().Msg("skipping enabling swagger because given zot binary " + diff --git a/pkg/extensions/config/config.go b/pkg/extensions/config/config.go index 7dffea8b..043b5832 100644 --- a/pkg/extensions/config/config.go +++ b/pkg/extensions/config/config.go @@ -17,6 +17,7 @@ type ExtensionConfig struct { Metrics *MetricsConfig Scrub *ScrubConfig Lint *LintConfig + UI *UIConfig } type LintConfig struct { @@ -52,3 +53,7 @@ type ScrubConfig struct { BaseConfig `mapstructure:",squash"` Interval time.Duration } + +type UIConfig struct { + BaseConfig `mapstructure:",squash"` +} diff --git a/pkg/extensions/extension-ui-disabled.go b/pkg/extensions/extension-ui-disabled.go new file mode 100644 index 00000000..e4144f45 --- /dev/null +++ b/pkg/extensions/extension-ui-disabled.go @@ -0,0 +1,19 @@ +//go:build !search || !ui +// +build !search !ui + +package extensions + +import ( + "github.com/gorilla/mux" + + "zotregistry.io/zot/pkg/api/config" + "zotregistry.io/zot/pkg/log" + "zotregistry.io/zot/pkg/storage" +) + +func SetupUIRoutes(config *config.Config, router *mux.Router, storeController storage.StoreController, + log log.Logger, +) { + log.Warn().Msg("skipping setting up ui routes because given zot binary doesn't include this feature," + + "please build a binary that does so") +} diff --git a/pkg/extensions/extension-ui.go b/pkg/extensions/extension-ui.go new file mode 100644 index 00000000..361b5b3a --- /dev/null +++ b/pkg/extensions/extension-ui.go @@ -0,0 +1,51 @@ +//go:build search && ui +// +build search,ui + +package extensions + +import ( + "embed" + "io/fs" + "net/http" + + "github.com/gorilla/mux" + + "zotregistry.io/zot/pkg/api/config" + "zotregistry.io/zot/pkg/log" + "zotregistry.io/zot/pkg/storage" +) + +// content is our static web server content. +// +//go:embed build/* +var content embed.FS + +type uiHandler struct { + log log.Logger +} + +func (uih uiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + buf, _ := content.ReadFile("build/index.html") + + _, err := w.Write(buf) + if err != nil { + uih.log.Error().Err(err).Msg("unable to serve index.html") + } +} + +func SetupUIRoutes(config *config.Config, router *mux.Router, storeController storage.StoreController, + log log.Logger, +) { + if config.Extensions.UI != nil { + fsub, _ := fs.Sub(content, "build") + uih := uiHandler{log: log} + + router.PathPrefix("/login").Handler(uih) + router.PathPrefix("/home").Handler(uih) + router.PathPrefix("/explore").Handler(uih) + router.PathPrefix("/image").Handler(uih) + router.PathPrefix("/").Handler(http.FileServer(http.FS(fsub))) + + log.Info().Msg("setting up ui routes") + } +} diff --git a/pkg/extensions/extension_metrics.go b/pkg/extensions/extension_metrics.go index 28c54b74..77a37779 100644 --- a/pkg/extensions/extension_metrics.go +++ b/pkg/extensions/extension_metrics.go @@ -27,12 +27,13 @@ func EnableMetricsExtension(config *config.Config, log log.Logger, rootDir strin } func SetupMetricsRoutes(config *config.Config, router *mux.Router, storeController storage.StoreController, - log log.Logger, + authFunc mux.MiddlewareFunc, log log.Logger, ) { log.Info().Msg("setting up metrics routes") if config.Extensions.Metrics != nil && *config.Extensions.Metrics.Enable { - router.PathPrefix(config.Extensions.Metrics.Prometheus.Path). - Handler(promhttp.Handler()) + extRouter := router.PathPrefix(config.Extensions.Metrics.Prometheus.Path).Subrouter() + extRouter.Use(authFunc) + extRouter.Methods("GET").Handler(promhttp.Handler()) } } diff --git a/pkg/extensions/extension_metrics_disabled.go b/pkg/extensions/extension_metrics_disabled.go index 9d26781f..f6456330 100644 --- a/pkg/extensions/extension_metrics_disabled.go +++ b/pkg/extensions/extension_metrics_disabled.go @@ -19,7 +19,7 @@ func EnableMetricsExtension(config *config.Config, log log.Logger, rootDir strin // SetupMetricsRoutes ... func SetupMetricsRoutes(conf *config.Config, router *mux.Router, - storeController storage.StoreController, log log.Logger, + storeController storage.StoreController, authFunc mux.MiddlewareFunc, log log.Logger, ) { log.Warn().Msg("skipping setting up metrics routes because given zot binary doesn't include this feature," + "please build a binary that does so") diff --git a/pkg/extensions/extension_search.go b/pkg/extensions/extension_search.go index 402ac5d1..a18b4790 100644 --- a/pkg/extensions/extension_search.go +++ b/pkg/extensions/extension_search.go @@ -84,8 +84,9 @@ func SetupSearchRoutes(config *config.Config, router *mux.Router, storeControlle if config.Extensions.Search != nil && *config.Extensions.Search.Enable { resConfig := search.GetResolverConfig(log, storeController, repoDB, cveInfo) - graphqlPrefix := router.PathPrefix(constants.FullSearchPrefix).Methods("OPTIONS", "GET", "POST") - graphqlPrefix.Handler(gqlHandler.NewDefaultServer(gql_generated.NewExecutableSchema(resConfig))) + extRouter := router.PathPrefix(constants.ExtSearchPrefix).Subrouter() + extRouter.Methods("GET", "POST", "OPTIONS"). + Handler(gqlHandler.NewDefaultServer(gql_generated.NewExecutableSchema(resConfig))) } }