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
This commit is contained in:
Egon Elbre 2022-03-11 15:47:20 +02:00
parent 5f7ea1358d
commit dc0f7b5f77
8 changed files with 59 additions and 38 deletions

View File

@ -48,7 +48,6 @@ help:
.PHONY: build-dev-deps .PHONY: build-dev-deps
build-dev-deps: ## Install dependencies for builds build-dev-deps: ## Install dependencies for builds
go get golang.org/x/tools/cover 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/josephspurrier/goversioninfo/cmd/goversioninfo
go get github.com/github-release/github-release go get github.com/github-release/github-release
@ -157,11 +156,6 @@ storagenode-console:
-u $(shell id -u):$(shell id -g) \ -u $(shell id -u):$(shell id -g) \
node:${NODE_VERSION} \ node:${NODE_VERSION} \
/bin/bash -c "npm ci && npm run build" /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 .PHONY: multinode-console
multinode-console: multinode-console:

View File

@ -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

View File

@ -6,6 +6,8 @@ package consoleserver
import ( import (
"context" "context"
"errors" "errors"
"io"
"io/fs"
"net" "net"
"net/http" "net/http"
@ -45,16 +47,18 @@ type Server struct {
notifications *notifications.Service notifications *notifications.Service
payout *payouts.Service payout *payouts.Service
listener net.Listener listener net.Listener
assets fs.FS
server http.Server server http.Server
} }
// NewServer creates new instance of storagenode console web 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{ server := Server{
log: logger, log: logger,
service: service, service: service,
listener: listener, listener: listener,
assets: assets,
notifications: notifications, notifications: notifications,
payout: payout, 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("/periods", payoutController.HeldAmountPeriods).Methods(http.MethodGet)
payoutRouter.HandleFunc("/payout-history/{period}", payoutController.PayoutHistory).Methods(http.MethodGet) payoutRouter.HandleFunc("/payout-history/{period}", payoutController.PayoutHistory).Methods(http.MethodGet)
if assets != nil { staticServer := http.FileServer(http.FS(server.assets))
fs := http.FileServer(assets) router.PathPrefix("/static/").Handler(web.CacheHandler(staticServer))
router.PathPrefix("/static/").Handler(web.CacheHandler(http.StripPrefix("/static", fs))) router.PathPrefix("/").HandlerFunc(server.appHandler)
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)
}))
}
server.server = http.Server{ server.server = http.Server{
Handler: router, Handler: router,
@ -103,6 +101,24 @@ func NewServer(logger *zap.Logger, assets http.FileSystem, notifications *notifi
return &server 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. // Run starts the server that host webapp and api endpoints.
func (server *Server) Run(ctx context.Context) (err error) { func (server *Server) Run(ctx context.Context) (err error) {
defer mon.Task()(&ctx)(&err) defer mon.Task()(&ctx)(&err)

View File

@ -7,8 +7,9 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"io/fs"
"net" "net"
"net/http" "os"
"path/filepath" "path/filepath"
"time" "time"
@ -36,7 +37,6 @@ import (
"storj.io/storj/storagenode/bandwidth" "storj.io/storj/storagenode/bandwidth"
"storj.io/storj/storagenode/collector" "storj.io/storj/storagenode/collector"
"storj.io/storj/storagenode/console" "storj.io/storj/storagenode/console"
"storj.io/storj/storagenode/console/consoleassets"
"storj.io/storj/storagenode/console/consoleserver" "storj.io/storj/storagenode/console/consoleserver"
"storj.io/storj/storagenode/contact" "storj.io/storj/storagenode/contact"
"storj.io/storj/storagenode/gracefulexit" "storj.io/storj/storagenode/gracefulexit"
@ -63,6 +63,7 @@ import (
"storj.io/storj/storagenode/storageusage" "storj.io/storj/storagenode/storageusage"
"storj.io/storj/storagenode/trust" "storj.io/storj/storagenode/trust"
version2 "storj.io/storj/storagenode/version" version2 "storj.io/storj/storagenode/version"
storagenodeweb "storj.io/storj/web/storagenode"
) )
var ( var (
@ -666,10 +667,13 @@ func New(log *zap.Logger, full *identity.FullIdentity, db DB, revocationDB exten
return nil, errs.Combine(err, peer.Close()) return nil, errs.Combine(err, peer.Close())
} }
assets := consoleassets.FileSystem var assets fs.FS
assets = storagenodeweb.Assets
if config.Console.StaticDir != "" { if config.Console.StaticDir != "" {
// a specific directory has been configured. use it // HACKFIX: Previous setups specify the directory for web/storagenode,
assets = http.Dir(config.Console.StaticDir) // 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( peer.Console.Endpoint = consoleserver.NewServer(

23
web/storagenode/assets.go Normal file
View File

@ -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
}()

0
web/storagenode/dist/.keep vendored Normal file
View File

View File

View File

@ -4,17 +4,14 @@
const path = require('path'); const path = require('path');
module.exports = { module.exports = {
publicPath: "/static/dist",
productionSourceMap: false, productionSourceMap: false,
parallel: true, parallel: true,
lintOnSave: false, // disables eslint for builds lintOnSave: false, // disables eslint for builds
assetsDir: "static",
configureWebpack: { configureWebpack: {
plugins: [], plugins: [],
}, },
chainWebpack: config => { chainWebpack: config => {
config.output.chunkFilename(`js/vendors_[hash].js`);
config.output.filename(`js/app_[hash].js`);
config.resolve.alias config.resolve.alias
.set('@', path.resolve('src')); .set('@', path.resolve('src'));