From 28c9403702fa586ac936cbe5df967bdf9f52bcf3 Mon Sep 17 00:00:00 2001 From: Egon Elbre Date: Thu, 10 Mar 2022 17:28:10 +0200 Subject: [PATCH] multinode,web/multinode: 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 multinode. Change-Id: I53ac3d5ac76e44f740d95221acf0da99fc256d42 --- Makefile | 5 --- multinode/console/consoleassets/assets.go | 13 ------ multinode/console/server/server.go | 53 +++++------------------ multinode/peer.go | 16 ++++--- web/multinode/assets.go | 23 ++++++++++ web/multinode/dist/.keep | 0 web/multinode/public/.keep | 0 web/multinode/vue.config.js | 6 +-- 8 files changed, 46 insertions(+), 70 deletions(-) delete mode 100644 multinode/console/consoleassets/assets.go create mode 100644 web/multinode/assets.go create mode 100644 web/multinode/dist/.keep create mode 100644 web/multinode/public/.keep diff --git a/Makefile b/Makefile index eea51446d..0991a2dcb 100644 --- a/Makefile +++ b/Makefile @@ -175,11 +175,6 @@ multinode-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/multinode/ -fs -o multinode/console/consoleassets/bindata.resource.go -pkg consoleassets web/multinode/dist/... web/multinode/static/... - # configure existing go code to know about the new assets - /usr/bin/env echo -e '\nfunc init() { FileSystem = AssetFile() }' >> multinode/console/consoleassets/bindata.resource.go - gofmt -w -s multinode/console/consoleassets/bindata.resource.go .PHONY: satellite-admin-ui satellite-admin-ui: diff --git a/multinode/console/consoleassets/assets.go b/multinode/console/consoleassets/assets.go deleted file mode 100644 index db6fd5da9..000000000 --- a/multinode/console/consoleassets/assets.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (C) 2021 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/multinode/console/server/server.go b/multinode/console/server/server.go index c7f1a7617..a6f6548b8 100644 --- a/multinode/console/server/server.go +++ b/multinode/console/server/server.go @@ -6,8 +6,8 @@ package server import ( "context" "errors" - "html/template" - "io/ioutil" + "io" + "io/fs" "net" "net/http" @@ -54,7 +54,7 @@ type Server struct { log *zap.Logger listener net.Listener http http.Server - assets http.FileSystem + assets fs.FS nodes *nodes.Service payouts *payouts.Service @@ -62,12 +62,10 @@ type Server struct { bandwidth *bandwidth.Service storage *storage.Service reputation *reputation.Service - - index *template.Template } // NewServer returns new instance of Multinode Dashboard http server. -func NewServer(log *zap.Logger, listener net.Listener, assets http.FileSystem, services Services) (*Server, error) { +func NewServer(log *zap.Logger, listener net.Listener, assets fs.FS, services Services) (*Server, error) { server := Server{ log: log, listener: listener, @@ -134,11 +132,8 @@ func NewServer(log *zap.Logger, listener net.Listener, assets http.FileSystem, s reputationRouter := apiRouter.PathPrefix("/reputation").Subrouter() reputationRouter.HandleFunc("/satellites/{satelliteID}", reputationController.Stats) - if server.assets != nil { - fs := http.FileServer(server.assets) - router.PathPrefix("/static/").Handler(http.StripPrefix("/static", fs)) - router.PathPrefix("/").HandlerFunc(server.appHandler) - } + router.PathPrefix("/static").Handler(http.FileServer(http.FS(server.assets))) + router.PathPrefix("/").HandlerFunc(server.appHandler) server.http = http.Server{ Handler: router, @@ -155,24 +150,18 @@ func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) { header.Set("X-Content-Type-Options", "nosniff") header.Set("Referrer-Policy", "same-origin") - if server.index == nil { - server.log.Error("index template is not set") + f, err := server.assets.Open("index.html") + if err != nil { + http.Error(w, `web/multinode unbuilt, run "npm install && npm run build" in web/multinode.`, http.StatusNotFound) return } + defer func() { _ = f.Close() }() - if err := server.index.Execute(w, nil); err != nil { - server.log.Error("index template could not be executed", zap.Error(Error.Wrap(err))) - return - } + _, _ = io.Copy(w, f) } // Run starts the server that host webapp and api endpoints. func (server *Server) Run(ctx context.Context) (err error) { - err = server.initializeTemplates() - if err != nil { - return Error.Wrap(err) - } - ctx, cancel := context.WithCancel(ctx) var group errgroup.Group @@ -196,23 +185,3 @@ func (server *Server) Run(ctx context.Context) (err error) { func (server *Server) Close() error { return Error.Wrap(server.http.Close()) } - -// initializeTemplates is used to initialize all templates. -func (server *Server) initializeTemplates() (err error) { - f, err := server.assets.Open("/dist/index.html") - if err != nil { - server.log.Error("dist folder is not generated. use 'npm run build' command", zap.Error(err)) - return nil - } - b, err := ioutil.ReadAll(f) - if err != nil { - return Error.Wrap(err) - } - - server.index, err = template.New("index.html").Parse(string(b)) - if err != nil { - return Error.Wrap(err) - } - - return nil -} diff --git a/multinode/peer.go b/multinode/peer.go index f97153514..747221e03 100644 --- a/multinode/peer.go +++ b/multinode/peer.go @@ -5,8 +5,10 @@ package multinode import ( "context" + "io/fs" "net" - "net/http" + "os" + "path/filepath" "github.com/spacemonkeygo/monkit/v3" "go.uber.org/zap" @@ -17,7 +19,6 @@ import ( "storj.io/common/rpc" "storj.io/private/debug" "storj.io/storj/multinode/bandwidth" - "storj.io/storj/multinode/console/consoleassets" "storj.io/storj/multinode/console/server" "storj.io/storj/multinode/nodes" "storj.io/storj/multinode/operators" @@ -25,6 +26,7 @@ import ( "storj.io/storj/multinode/reputation" "storj.io/storj/multinode/storage" "storj.io/storj/private/lifecycle" + multinodeweb "storj.io/storj/web/multinode" ) var ( @@ -175,10 +177,14 @@ func New(log *zap.Logger, full *identity.FullIdentity, config Config, db DB) (_ return nil, err } - assets := consoleassets.FileSystem + var assets fs.FS + assets = multinodeweb.Assets + // Use a custom directory instead of the embedded 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/multinode, + // 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, err = server.NewServer( diff --git a/web/multinode/assets.go b/web/multinode/assets.go new file mode 100644 index 000000000..c00e114c8 --- /dev/null +++ b/web/multinode/assets.go @@ -0,0 +1,23 @@ +// Copyright (C) 2022 Storj Labs, Inc. +// See LICENSE for copying information. + +package multinodeweb + +import ( + "embed" + "fmt" + "io/fs" +) + +//go:embed dist/* +var assets embed.FS + +// Assets contains either the built multinode from web/multinode/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/multinode/dist/.keep b/web/multinode/dist/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/web/multinode/public/.keep b/web/multinode/public/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/web/multinode/vue.config.js b/web/multinode/vue.config.js index 70baf73c6..f4e2ab2f0 100644 --- a/web/multinode/vue.config.js +++ b/web/multinode/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')); @@ -22,7 +19,6 @@ module.exports = { .plugin('html') .tap(args => { args[0].template = './index.html'; - return args; });