From dc0f7b5f77c99a166ca5ea77ff81f27dd5017818 Mon Sep 17 00:00:00 2001 From: Egon Elbre Date: Fri, 11 Mar 2022 15:47:20 +0200 Subject: [PATCH] storagenode,web/storagenode: use go:embed for assets Go can now directly embed files without relying on external tools. This makes code use go:embed and avoid the external tooling. go:embed requires files to be present in the embedded directory, hence we need to add .keep to "dist" folder. We also add one to public/.keep, such that it won't be deleted when building storagenode. Change-Id: I8bef81236be6829ed37ed4c16ef693677b93a631 --- Makefile | 6 ---- storagenode/console/consoleassets/assets.go | 13 -------- storagenode/console/consoleserver/server.go | 36 +++++++++++++++------ storagenode/peer.go | 14 +++++--- web/storagenode/assets.go | 23 +++++++++++++ web/storagenode/dist/.keep | 0 web/storagenode/public/.keep | 0 web/storagenode/vue.config.js | 5 +-- 8 files changed, 59 insertions(+), 38 deletions(-) delete mode 100644 storagenode/console/consoleassets/assets.go create mode 100644 web/storagenode/assets.go create mode 100644 web/storagenode/dist/.keep create mode 100644 web/storagenode/public/.keep diff --git a/Makefile b/Makefile index 0991a2dcb..76e36ac23 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,6 @@ help: .PHONY: build-dev-deps build-dev-deps: ## Install dependencies for builds go get golang.org/x/tools/cover - go get github.com/go-bindata/go-bindata/go-bindata go get github.com/josephspurrier/goversioninfo/cmd/goversioninfo go get github.com/github-release/github-release @@ -157,11 +156,6 @@ storagenode-console: -u $(shell id -u):$(shell id -g) \ node:${NODE_VERSION} \ /bin/bash -c "npm ci && npm run build" - # embed web assets into go - go-bindata -prefix web/storagenode/ -fs -o storagenode/console/consoleassets/bindata.resource.go -pkg consoleassets web/storagenode/dist/... web/storagenode/static/... - # configure existing go code to know about the new assets - /usr/bin/env echo -e '\nfunc init() { FileSystem = AssetFile() }' >> storagenode/console/consoleassets/bindata.resource.go - gofmt -w -s storagenode/console/consoleassets/bindata.resource.go .PHONY: multinode-console multinode-console: diff --git a/storagenode/console/consoleassets/assets.go b/storagenode/console/consoleassets/assets.go deleted file mode 100644 index d708b601e..000000000 --- a/storagenode/console/consoleassets/assets.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (C) 2019 Storj Labs, Inc. -// See LICENSE for copying information. - -package consoleassets - -import ( - "net/http" -) - -// FileSystem is nil by default, but when our Makefile generates -// embedded resources via go-bindata, it will also drop an init method -// that sets this to not nil. -var FileSystem http.FileSystem diff --git a/storagenode/console/consoleserver/server.go b/storagenode/console/consoleserver/server.go index a6362af38..3596c9c5a 100644 --- a/storagenode/console/consoleserver/server.go +++ b/storagenode/console/consoleserver/server.go @@ -6,6 +6,8 @@ package consoleserver import ( "context" "errors" + "io" + "io/fs" "net" "net/http" @@ -45,16 +47,18 @@ type Server struct { notifications *notifications.Service payout *payouts.Service listener net.Listener + assets fs.FS server http.Server } // NewServer creates new instance of storagenode console web server. -func NewServer(logger *zap.Logger, assets http.FileSystem, notifications *notifications.Service, service *console.Service, payout *payouts.Service, listener net.Listener) *Server { +func NewServer(logger *zap.Logger, assets fs.FS, notifications *notifications.Service, service *console.Service, payout *payouts.Service, listener net.Listener) *Server { server := Server{ log: logger, service: service, listener: listener, + assets: assets, notifications: notifications, payout: payout, } @@ -86,15 +90,9 @@ func NewServer(logger *zap.Logger, assets http.FileSystem, notifications *notifi payoutRouter.HandleFunc("/periods", payoutController.HeldAmountPeriods).Methods(http.MethodGet) payoutRouter.HandleFunc("/payout-history/{period}", payoutController.PayoutHistory).Methods(http.MethodGet) - if assets != nil { - fs := http.FileServer(assets) - router.PathPrefix("/static/").Handler(web.CacheHandler(http.StripPrefix("/static", fs))) - router.PathPrefix("/").Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - req := r.Clone(r.Context()) - req.URL.Path = "/dist/" - fs.ServeHTTP(w, req) - })) - } + staticServer := http.FileServer(http.FS(server.assets)) + router.PathPrefix("/static/").Handler(web.CacheHandler(staticServer)) + router.PathPrefix("/").HandlerFunc(server.appHandler) server.server = http.Server{ Handler: router, @@ -103,6 +101,24 @@ func NewServer(logger *zap.Logger, assets http.FileSystem, notifications *notifi return &server } +// appHandler is web app http handler function. +func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) { + header := w.Header() + + header.Set("Content-Type", "text/html; charset=UTF-8") + header.Set("X-Content-Type-Options", "nosniff") + header.Set("Referrer-Policy", "same-origin") + + f, err := server.assets.Open("index.html") + if err != nil { + http.Error(w, `web/storagenode unbuilt, run "npm install && npm run build" in web/storagenode.`, http.StatusNotFound) + return + } + defer func() { _ = f.Close() }() + + _, _ = io.Copy(w, f) +} + // Run starts the server that host webapp and api endpoints. func (server *Server) Run(ctx context.Context) (err error) { defer mon.Task()(&ctx)(&err) diff --git a/storagenode/peer.go b/storagenode/peer.go index fc1f41064..410762185 100644 --- a/storagenode/peer.go +++ b/storagenode/peer.go @@ -7,8 +7,9 @@ import ( "context" "errors" "fmt" + "io/fs" "net" - "net/http" + "os" "path/filepath" "time" @@ -36,7 +37,6 @@ import ( "storj.io/storj/storagenode/bandwidth" "storj.io/storj/storagenode/collector" "storj.io/storj/storagenode/console" - "storj.io/storj/storagenode/console/consoleassets" "storj.io/storj/storagenode/console/consoleserver" "storj.io/storj/storagenode/contact" "storj.io/storj/storagenode/gracefulexit" @@ -63,6 +63,7 @@ import ( "storj.io/storj/storagenode/storageusage" "storj.io/storj/storagenode/trust" version2 "storj.io/storj/storagenode/version" + storagenodeweb "storj.io/storj/web/storagenode" ) var ( @@ -666,10 +667,13 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB, revocationDB exten return nil, errs.Combine(err, peer.Close()) } - assets := consoleassets.FileSystem + var assets fs.FS + assets = storagenodeweb.Assets if config.Console.StaticDir != "" { - // a specific directory has been configured. use it - assets = http.Dir(config.Console.StaticDir) + // HACKFIX: Previous setups specify the directory for web/storagenode, + // instead of the actual built data. This is for backwards compatibility. + distDir := filepath.Join(config.Console.StaticDir, "dist") + assets = os.DirFS(distDir) } peer.Console.Endpoint = consoleserver.NewServer( diff --git a/web/storagenode/assets.go b/web/storagenode/assets.go new file mode 100644 index 000000000..54f2ffac9 --- /dev/null +++ b/web/storagenode/assets.go @@ -0,0 +1,23 @@ +// Copyright (C) 2022 Storj Labs, Inc. +// See LICENSE for copying information. + +package storagenodeweb + +import ( + "embed" + "fmt" + "io/fs" +) + +//go:embed dist/* +var assets embed.FS + +// Assets contains either the built storagenode from web/storagenode/dist directory +// or it is empty. +var Assets = func() fs.FS { + dist, err := fs.Sub(assets, "dist") + if err != nil { + panic(fmt.Errorf("invalid embedding: %w", err)) + } + return dist +}() diff --git a/web/storagenode/dist/.keep b/web/storagenode/dist/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/web/storagenode/public/.keep b/web/storagenode/public/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/web/storagenode/vue.config.js b/web/storagenode/vue.config.js index a526ecaea..332e0eccb 100644 --- a/web/storagenode/vue.config.js +++ b/web/storagenode/vue.config.js @@ -4,17 +4,14 @@ const path = require('path'); module.exports = { - publicPath: "/static/dist", productionSourceMap: false, parallel: true, lintOnSave: false, // disables eslint for builds + assetsDir: "static", configureWebpack: { plugins: [], }, chainWebpack: config => { - config.output.chunkFilename(`js/vendors_[hash].js`); - config.output.filename(`js/app_[hash].js`); - config.resolve.alias .set('@', path.resolve('src'));