[full-ci] enhancement: allow ocis to provide custom web applications (#8523)
* enhancement: allow ocis to provide custom web applications * enhancement: add an option to disable web apps * test: add default logger tests * test: add app loading tests * test: add asset server tests * enhancement: make use of dedicated app conf file and app asset paths * enhancement: adjust asset locations and deprecate WEB_ASSET_PATH * enhancement: get rid of default logger and use the service level logger instead * Apply suggestions from code review Co-authored-by: Benedikt Kulmann <benedikt@kulmann.biz> Co-authored-by: kobergj <juliankoberg@googlemail.com> * enhancement: use basename as app id * Apply suggestions from code review Co-authored-by: Martin <github@diemattels.at> * enhancement: use afero as fs abstraction * enhancement: simplify logo upload * enhancement: make use of introductionVersion field annotations --------- Co-authored-by: Benedikt Kulmann <benedikt@kulmann.biz> Co-authored-by: kobergj <juliankoberg@googlemail.com> Co-authored-by: Martin <github@diemattels.at>
This commit is contained in:
co-authored by
Benedikt Kulmann
kobergj
Martin
parent
6ba9e4adf7
commit
6814c61506
@@ -11,6 +11,7 @@ import (
|
||||
permissionsapi "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
revactx "github.com/cs3org/reva/v2/pkg/ctx"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -67,7 +68,7 @@ func (p Web) UploadLogo(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
fp := filepath.Join("branding", filepath.Join("/", fileHeader.Filename))
|
||||
err = p.storeAsset(fp, file)
|
||||
err = afero.WriteReader(p.coreFS, fp, file)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
@@ -109,7 +110,7 @@ func (p Web) ResetLogo(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
f, err := p.fs.OpenEmbedded(_themesConfigPath)
|
||||
f, err := p.coreFS.Secondary().Open(_themesConfigPath)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
@@ -128,17 +129,6 @@ func (p Web) ResetLogo(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (p Web) storeAsset(name string, asset io.Reader) error {
|
||||
dst, err := p.fs.Create(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
_, err = io.Copy(dst, asset)
|
||||
return err
|
||||
}
|
||||
|
||||
func (p Web) getLogoPath(r io.Reader) (string, error) {
|
||||
// This decoding of the themes.json file is not optimal. If we need to decode it for other
|
||||
// usecases as well we should consider decoding to a struct.
|
||||
@@ -159,7 +149,7 @@ func (p Web) getLogoPath(r io.Reader) (string, error) {
|
||||
}
|
||||
|
||||
func (p Web) updateLogoThemeConfig(logoPath string) error {
|
||||
f, err := p.fs.Open(_themesConfigPath)
|
||||
f, err := p.coreFS.Open(_themesConfigPath)
|
||||
if err == nil {
|
||||
defer f.Close()
|
||||
}
|
||||
@@ -184,10 +174,11 @@ func (p Web) updateLogoThemeConfig(logoPath string) error {
|
||||
logoCfg["login"] = logoPath
|
||||
logoCfg["topbar"] = logoPath
|
||||
|
||||
dst, err := p.fs.Create(_themesConfigPath)
|
||||
dst, err := p.coreFS.Create(_themesConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
return json.NewEncoder(dst).Encode(m)
|
||||
}
|
||||
|
||||
@@ -1,25 +1,31 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/v2/services/web/pkg/config"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/x/io/fsx"
|
||||
"github.com/owncloud/ocis/v2/services/web/pkg/config"
|
||||
)
|
||||
|
||||
// Option defines a single option function.
|
||||
type Option func(o *Options)
|
||||
|
||||
// Options defines the available options for this package.
|
||||
// Options define the available options for this package.
|
||||
type Options struct {
|
||||
Logger log.Logger
|
||||
Config *config.Config
|
||||
Middleware []func(http.Handler) http.Handler
|
||||
GatewaySelector pool.Selectable[gateway.GatewayAPIClient]
|
||||
TraceProvider trace.TracerProvider
|
||||
Logger log.Logger
|
||||
Config *config.Config
|
||||
Middleware []func(http.Handler) http.Handler
|
||||
GatewaySelector pool.Selectable[gateway.GatewayAPIClient]
|
||||
TraceProvider trace.TracerProvider
|
||||
AppFS fs.FS
|
||||
AppsHTTPEndpoint string
|
||||
CoreFS *fsx.FallbackFS
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
@@ -67,3 +73,24 @@ func TraceProvider(val trace.TracerProvider) Option {
|
||||
o.TraceProvider = val
|
||||
}
|
||||
}
|
||||
|
||||
// AppFS provides a function to set the appFS option.
|
||||
func AppFS(val fs.FS) Option {
|
||||
return func(o *Options) {
|
||||
o.AppFS = val
|
||||
}
|
||||
}
|
||||
|
||||
// AppsHTTPEndpoint provides a function to set the appsHTTPEndpoint option.
|
||||
func AppsHTTPEndpoint(val string) Option {
|
||||
return func(o *Options) {
|
||||
o.AppsHTTPEndpoint = val
|
||||
}
|
||||
}
|
||||
|
||||
// CoreFS provides a function to set the coreFS option.
|
||||
func CoreFS(val *fsx.FallbackFS) Option {
|
||||
return func(o *Options) {
|
||||
o.CoreFS = val
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ package svc
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -12,15 +14,15 @@ import (
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/riandyrn/otelchi"
|
||||
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/account"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/assetsfs"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/middleware"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/tracing"
|
||||
"github.com/owncloud/ocis/v2/services/web"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/x/io/fsx"
|
||||
"github.com/owncloud/ocis/v2/services/web/pkg/assets"
|
||||
"github.com/owncloud/ocis/v2/services/web/pkg/config"
|
||||
"github.com/riandyrn/otelchi"
|
||||
)
|
||||
|
||||
// ErrConfigInvalid is returned when the config parse is invalid.
|
||||
@@ -49,11 +51,12 @@ func NewService(opts ...Option) Service {
|
||||
otelchi.WithPropagators(tracing.GetPropagator()),
|
||||
),
|
||||
)
|
||||
|
||||
svc := Web{
|
||||
logger: options.Logger,
|
||||
config: options.Config,
|
||||
mux: m,
|
||||
fs: assetsfs.New(web.Assets, options.Config.Asset.Path, options.Logger),
|
||||
coreFS: options.CoreFS,
|
||||
gatewaySelector: options.GatewaySelector,
|
||||
}
|
||||
|
||||
@@ -67,7 +70,16 @@ func NewService(opts ...Option) Service {
|
||||
r.Post("/", svc.UploadLogo)
|
||||
r.Delete("/", svc.ResetLogo)
|
||||
})
|
||||
r.Mount("/", svc.Static(options.Config.HTTP.CacheTTL))
|
||||
r.Mount(options.AppsHTTPEndpoint, svc.Static(
|
||||
options.AppFS,
|
||||
path.Join(svc.config.HTTP.Root, options.AppsHTTPEndpoint),
|
||||
options.Config.HTTP.CacheTTL,
|
||||
))
|
||||
r.Mount("/", svc.Static(
|
||||
svc.coreFS.IOFS(),
|
||||
svc.config.HTTP.Root,
|
||||
options.Config.HTTP.CacheTTL,
|
||||
))
|
||||
})
|
||||
|
||||
_ = chi.Walk(m, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
|
||||
@@ -78,12 +90,12 @@ func NewService(opts ...Option) Service {
|
||||
return svc
|
||||
}
|
||||
|
||||
// Web defines implements the business logic for Service.
|
||||
// Web defines the handlers for the web service.
|
||||
type Web struct {
|
||||
logger log.Logger
|
||||
config *config.Config
|
||||
mux *chi.Mux
|
||||
fs *assetsfs.FileSystem
|
||||
coreFS *fsx.FallbackFS
|
||||
gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
|
||||
}
|
||||
|
||||
@@ -127,8 +139,8 @@ func (p Web) Config(w http.ResponseWriter, _ *http.Request) {
|
||||
}
|
||||
|
||||
// Static simply serves all static files.
|
||||
func (p Web) Static(ttl int) http.HandlerFunc {
|
||||
rootWithSlash := p.config.HTTP.Root
|
||||
func (p Web) Static(f fs.FS, root string, ttl int) http.HandlerFunc {
|
||||
rootWithSlash := root
|
||||
|
||||
if !strings.HasSuffix(rootWithSlash, "/") {
|
||||
rootWithSlash = rootWithSlash + "/"
|
||||
@@ -136,7 +148,7 @@ func (p Web) Static(ttl int) http.HandlerFunc {
|
||||
|
||||
static := http.StripPrefix(
|
||||
rootWithSlash,
|
||||
assets.FileServer(p.fs),
|
||||
assets.FileServer(f),
|
||||
)
|
||||
|
||||
lastModified := time.Now().UTC().Format(http.TimeFormat)
|
||||
|
||||
Reference in New Issue
Block a user