Merge branch 'master' into no-additional-init
This commit is contained in:
@@ -1,21 +1,23 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/owncloud/ocis/ocis-pkg/sync"
|
||||
|
||||
"github.com/micro/cli/v2"
|
||||
ociscfg "github.com/owncloud/ocis/ocis-pkg/config"
|
||||
"github.com/owncloud/ocis/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/webdav/pkg/config"
|
||||
"github.com/owncloud/ocis/webdav/pkg/flagset"
|
||||
"github.com/owncloud/ocis/webdav/pkg/version"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/thejerf/suture/v4"
|
||||
)
|
||||
|
||||
// Execute is the entry point for the ocis-webdav command.
|
||||
func Execute() error {
|
||||
cfg := config.New()
|
||||
|
||||
func Execute(cfg *config.Config) error {
|
||||
app := &cli.App{
|
||||
Name: "webdav",
|
||||
Version: version.String,
|
||||
@@ -28,9 +30,6 @@ func Execute() error {
|
||||
Email: "support@owncloud.com",
|
||||
},
|
||||
},
|
||||
|
||||
Flags: flagset.RootWithConfig(cfg),
|
||||
|
||||
Before: func(c *cli.Context) error {
|
||||
cfg.Service.Version = version.String
|
||||
return nil
|
||||
@@ -62,11 +61,14 @@ func NewLogger(cfg *config.Config) log.Logger {
|
||||
log.Level(cfg.Log.Level),
|
||||
log.Pretty(cfg.Log.Pretty),
|
||||
log.Color(cfg.Log.Color),
|
||||
log.File(cfg.Log.File),
|
||||
)
|
||||
}
|
||||
|
||||
// ParseConfig loads webdav configuration from Viper known paths.
|
||||
func ParseConfig(c *cli.Context, cfg *config.Config) error {
|
||||
sync.ParsingViperConfig.Lock()
|
||||
defer sync.ParsingViperConfig.Unlock()
|
||||
logger := NewLogger(cfg)
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
@@ -107,3 +109,28 @@ func ParseConfig(c *cli.Context, cfg *config.Config) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SutureService allows for the webdav command to be embedded and supervised by a suture supervisor tree.
|
||||
type SutureService struct {
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
// NewSutureService creates a new webdav.SutureService
|
||||
func NewSutureService(cfg *ociscfg.Config) suture.Service {
|
||||
if cfg.Mode == 0 {
|
||||
cfg.WebDAV.Supervised = true
|
||||
}
|
||||
cfg.WebDAV.Log.File = cfg.Log.File
|
||||
return SutureService{
|
||||
cfg: cfg.WebDAV,
|
||||
}
|
||||
}
|
||||
|
||||
func (s SutureService) Serve(ctx context.Context) error {
|
||||
s.cfg.Context = ctx
|
||||
if err := Execute(s.cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+24
-136
@@ -2,25 +2,17 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"contrib.go.opencensus.io/exporter/jaeger"
|
||||
"contrib.go.opencensus.io/exporter/ocagent"
|
||||
"contrib.go.opencensus.io/exporter/zipkin"
|
||||
"github.com/micro/cli/v2"
|
||||
"github.com/oklog/run"
|
||||
openzipkin "github.com/openzipkin/zipkin-go"
|
||||
zipkinhttp "github.com/openzipkin/zipkin-go/reporter/http"
|
||||
"github.com/owncloud/ocis/ocis-pkg/sync"
|
||||
"github.com/owncloud/ocis/webdav/pkg/config"
|
||||
"github.com/owncloud/ocis/webdav/pkg/flagset"
|
||||
"github.com/owncloud/ocis/webdav/pkg/metrics"
|
||||
"github.com/owncloud/ocis/webdav/pkg/server/debug"
|
||||
"github.com/owncloud/ocis/webdav/pkg/server/http"
|
||||
"go.opencensus.io/stats/view"
|
||||
"go.opencensus.io/trace"
|
||||
"github.com/owncloud/ocis/webdav/pkg/tracing"
|
||||
)
|
||||
|
||||
// Server is the entrypoint for the server command.
|
||||
@@ -29,106 +21,34 @@ func Server(cfg *config.Config) *cli.Command {
|
||||
Name: "server",
|
||||
Usage: "Start integrated server",
|
||||
Flags: flagset.ServerWithConfig(cfg),
|
||||
Before: func(c *cli.Context) error {
|
||||
Before: func(ctx *cli.Context) error {
|
||||
logger := NewLogger(cfg)
|
||||
if cfg.HTTP.Root != "/" {
|
||||
cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/")
|
||||
}
|
||||
|
||||
return ParseConfig(c, cfg)
|
||||
if !cfg.Supervised {
|
||||
return ParseConfig(ctx, cfg)
|
||||
}
|
||||
logger.Debug().Str("service", "webdav").Msg("ignoring config file parsing when running supervised")
|
||||
return nil
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
logger := NewLogger(cfg)
|
||||
|
||||
if cfg.Tracing.Enabled {
|
||||
switch t := cfg.Tracing.Type; t {
|
||||
case "agent":
|
||||
exporter, err := ocagent.NewExporter(
|
||||
ocagent.WithReconnectionPeriod(5*time.Second),
|
||||
ocagent.WithAddress(cfg.Tracing.Endpoint),
|
||||
ocagent.WithServiceName(cfg.Tracing.Service),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.Error().
|
||||
Err(err).
|
||||
Str("endpoint", cfg.Tracing.Endpoint).
|
||||
Str("collector", cfg.Tracing.Collector).
|
||||
Msg("Failed to create agent tracing")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
trace.RegisterExporter(exporter)
|
||||
view.RegisterExporter(exporter)
|
||||
|
||||
case "jaeger":
|
||||
exporter, err := jaeger.NewExporter(
|
||||
jaeger.Options{
|
||||
AgentEndpoint: cfg.Tracing.Endpoint,
|
||||
CollectorEndpoint: cfg.Tracing.Collector,
|
||||
Process: jaeger.Process{
|
||||
ServiceName: cfg.Tracing.Service,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.Error().
|
||||
Err(err).
|
||||
Str("endpoint", cfg.Tracing.Endpoint).
|
||||
Str("collector", cfg.Tracing.Collector).
|
||||
Msg("Failed to create jaeger tracing")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
trace.RegisterExporter(exporter)
|
||||
|
||||
case "zipkin":
|
||||
endpoint, err := openzipkin.NewEndpoint(
|
||||
cfg.Tracing.Service,
|
||||
cfg.Tracing.Endpoint,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.Error().
|
||||
Err(err).
|
||||
Str("endpoint", cfg.Tracing.Endpoint).
|
||||
Str("collector", cfg.Tracing.Collector).
|
||||
Msg("Failed to create zipkin tracing")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
exporter := zipkin.NewExporter(
|
||||
zipkinhttp.NewReporter(
|
||||
cfg.Tracing.Collector,
|
||||
),
|
||||
endpoint,
|
||||
)
|
||||
|
||||
trace.RegisterExporter(exporter)
|
||||
|
||||
default:
|
||||
logger.Warn().
|
||||
Str("type", t).
|
||||
Msg("Unknown tracing backend")
|
||||
}
|
||||
|
||||
trace.ApplyConfig(
|
||||
trace.Config{
|
||||
DefaultSampler: trace.AlwaysSample(),
|
||||
},
|
||||
)
|
||||
} else {
|
||||
logger.Debug().
|
||||
Msg("Tracing is not enabled")
|
||||
if err := tracing.Configure(cfg, logger); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
gr = run.Group{}
|
||||
ctx, cancel = context.WithCancel(context.Background())
|
||||
metrics = metrics.New()
|
||||
ctx, cancel = func() (context.Context, context.CancelFunc) {
|
||||
if cfg.Context == nil {
|
||||
return context.WithCancel(context.Background())
|
||||
}
|
||||
return context.WithCancel(cfg.Context)
|
||||
}()
|
||||
metrics = metrics.New()
|
||||
)
|
||||
|
||||
defer cancel()
|
||||
@@ -141,8 +61,6 @@ func Server(cfg *config.Config) *cli.Command {
|
||||
http.Context(ctx),
|
||||
http.Config(cfg),
|
||||
http.Metrics(metrics),
|
||||
http.Flags(flagset.RootWithConfig(config.New())),
|
||||
http.Flags(flagset.ServerWithConfig(config.New())),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
@@ -173,48 +91,18 @@ func Server(cfg *config.Config) *cli.Command {
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.Info().
|
||||
Err(err).
|
||||
Str("transport", "debug").
|
||||
Msg("Failed to initialize server")
|
||||
|
||||
logger.Info().Err(err).Str("transport", "debug").Msg("Failed to initialize server")
|
||||
return err
|
||||
}
|
||||
|
||||
gr.Add(func() error {
|
||||
return server.ListenAndServe()
|
||||
}, func(_ error) {
|
||||
ctx, timeout := context.WithTimeout(ctx, 5*time.Second)
|
||||
|
||||
defer timeout()
|
||||
defer cancel()
|
||||
|
||||
if err := server.Shutdown(ctx); err != nil {
|
||||
logger.Info().
|
||||
Err(err).
|
||||
Str("transport", "debug").
|
||||
Msg("Failed to shutdown server")
|
||||
} else {
|
||||
logger.Info().
|
||||
Str("transport", "debug").
|
||||
Msg("Shutting down server")
|
||||
}
|
||||
gr.Add(server.ListenAndServe, func(_ error) {
|
||||
_ = server.Shutdown(ctx)
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
stop := make(chan os.Signal, 1)
|
||||
|
||||
gr.Add(func() error {
|
||||
signal.Notify(stop, os.Interrupt)
|
||||
|
||||
<-stop
|
||||
|
||||
return nil
|
||||
}, func(err error) {
|
||||
close(stop)
|
||||
cancel()
|
||||
})
|
||||
if !cfg.Supervised {
|
||||
sync.Trap(&gr, cancel)
|
||||
}
|
||||
|
||||
return gr.Run()
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package config
|
||||
|
||||
import "context"
|
||||
|
||||
// Log defines the available logging configuration.
|
||||
type Log struct {
|
||||
Level string
|
||||
Pretty bool
|
||||
Color bool
|
||||
File string
|
||||
}
|
||||
|
||||
// Debug defines the available debug configuration.
|
||||
@@ -17,15 +20,15 @@ type Debug struct {
|
||||
|
||||
// HTTP defines the available http configuration.
|
||||
type HTTP struct {
|
||||
Addr string
|
||||
Root string
|
||||
Addr string
|
||||
Root string
|
||||
}
|
||||
|
||||
// Service defines the available service configuration.
|
||||
type Service struct {
|
||||
Name string
|
||||
Name string
|
||||
Namespace string
|
||||
Version string
|
||||
Version string
|
||||
}
|
||||
|
||||
// Tracing defines the available tracing configuration.
|
||||
@@ -45,6 +48,11 @@ type Config struct {
|
||||
HTTP HTTP
|
||||
Tracing Tracing
|
||||
Service Service
|
||||
OcisPublicURL string
|
||||
WebdavNamespace string
|
||||
|
||||
Context context.Context
|
||||
Supervised bool
|
||||
}
|
||||
|
||||
// New initializes a new configuration with or without defaults.
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package requests
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultWidth defines the default width of a thumbnail
|
||||
DefaultWidth = 32
|
||||
// DefaultHeight defines the default height of a thumbnail
|
||||
DefaultHeight = 32
|
||||
)
|
||||
|
||||
// ThumbnailRequest combines all parameters provided when requesting a thumbnail
|
||||
type ThumbnailRequest struct {
|
||||
// The file path of the source file
|
||||
Filepath string
|
||||
// The file name of the source file including the extension
|
||||
Filename string
|
||||
// The file extension
|
||||
Extension string
|
||||
// The requested width of the thumbnail
|
||||
Width int32
|
||||
// The requested height of the thumbnail
|
||||
Height int32
|
||||
// In case of a public share the public link token.
|
||||
PublicLinkToken string
|
||||
}
|
||||
|
||||
// ParseThumbnailRequest extracts all required parameters from a http request.
|
||||
func ParseThumbnailRequest(r *http.Request) (*ThumbnailRequest, error) {
|
||||
fp, err := extractFilePath(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q := r.URL.Query()
|
||||
|
||||
width, height, err := parseDimensions(q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ThumbnailRequest{
|
||||
Filepath: fp,
|
||||
Filename: filepath.Base(fp),
|
||||
Extension: filepath.Ext(fp),
|
||||
Width: int32(width),
|
||||
Height: int32(height),
|
||||
PublicLinkToken: chi.URLParam(r, "token"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// the url looks as followed
|
||||
//
|
||||
// /remote.php/dav/files/<user>/<filepath>
|
||||
//
|
||||
// User and filepath are dynamic and filepath can contain slashes
|
||||
// So using the URLParam function is not possible.
|
||||
func extractFilePath(r *http.Request) (string, error) {
|
||||
user := chi.URLParam(r, "user")
|
||||
if user != "" {
|
||||
parts := strings.SplitN(r.URL.Path, user, 2)
|
||||
return parts[1], nil
|
||||
}
|
||||
token := chi.URLParam(r, "token")
|
||||
if token != "" {
|
||||
parts := strings.SplitN(r.URL.Path, token, 2)
|
||||
return parts[1], nil
|
||||
}
|
||||
return "", errors.New("could not extract file path")
|
||||
}
|
||||
|
||||
func parseDimensions(q url.Values) (int64, int64, error) {
|
||||
width, err := parseDimension(q.Get("x"), "width", DefaultWidth)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
height, err := parseDimension(q.Get("y"), "height", DefaultHeight)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return width, height, nil
|
||||
}
|
||||
|
||||
func parseDimension(d, name string, defaultValue int64) (int64, error) {
|
||||
if d == "" {
|
||||
return defaultValue, nil
|
||||
}
|
||||
result, err := strconv.ParseInt(d, 10, 32)
|
||||
if err != nil || result < 1 {
|
||||
// The error message doesn't fit but for OC10 API compatibility reasons we have to set this.
|
||||
return 0, fmt.Errorf("Cannot set %s of 0 or smaller!", name) //nolint:golint
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package thumbnail
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultWidth defines the default width of a thumbnail
|
||||
DefaultWidth = 32
|
||||
// DefaultHeight defines the default height of a thumbnail
|
||||
DefaultHeight = 32
|
||||
)
|
||||
|
||||
// Request combines all parameters provided when requesting a thumbnail
|
||||
type Request struct {
|
||||
Filepath string
|
||||
Filetype string
|
||||
Etag string
|
||||
Width int
|
||||
Height int
|
||||
Authorization string
|
||||
Username string
|
||||
}
|
||||
|
||||
// NewRequest extracts all required parameters from a http request.
|
||||
func NewRequest(r *http.Request) (Request, error) {
|
||||
path := extractFilePath(r)
|
||||
query := r.URL.Query()
|
||||
width, err := strconv.Atoi(query.Get("x"))
|
||||
if err != nil {
|
||||
width = DefaultWidth
|
||||
}
|
||||
height, err := strconv.Atoi(query.Get("y"))
|
||||
if err != nil {
|
||||
height = DefaultHeight
|
||||
}
|
||||
|
||||
etag := query.Get("c")
|
||||
if strings.TrimSpace(etag) == "" {
|
||||
return Request{}, fmt.Errorf("c (etag) is missing in query")
|
||||
}
|
||||
|
||||
authorization := r.Header.Get("Authorization")
|
||||
|
||||
tr := Request{
|
||||
Filepath: path,
|
||||
Filetype: strings.Replace(filepath.Ext(path), ".", "", 1),
|
||||
Etag: etag,
|
||||
Width: width,
|
||||
Height: height,
|
||||
Authorization: authorization,
|
||||
Username: chi.URLParam(r, "user"),
|
||||
}
|
||||
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
// the url looks as followed
|
||||
//
|
||||
// /remote.php/dav/files/<user>/<filepath>
|
||||
//
|
||||
// User and filepath are dynamic and filepath can contain slashes
|
||||
// So using the URLParam function is not possible.
|
||||
func extractFilePath(r *http.Request) string {
|
||||
user := chi.URLParam(r, "user")
|
||||
parts := strings.SplitN(r.URL.Path, user, 2)
|
||||
return parts[1]
|
||||
}
|
||||
@@ -2,42 +2,16 @@ package flagset
|
||||
|
||||
import (
|
||||
"github.com/micro/cli/v2"
|
||||
"github.com/owncloud/ocis/ocis-pkg/flags"
|
||||
"github.com/owncloud/ocis/webdav/pkg/config"
|
||||
)
|
||||
|
||||
// RootWithConfig applies cfg to the root flagset
|
||||
func RootWithConfig(cfg *config.Config) []cli.Flag {
|
||||
return []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "log-level",
|
||||
Value: "info",
|
||||
Usage: "Set logging level",
|
||||
EnvVars: []string{"WEBDAV_LOG_LEVEL"},
|
||||
Destination: &cfg.Log.Level,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Value: true,
|
||||
Name: "log-pretty",
|
||||
Usage: "Enable pretty logging",
|
||||
EnvVars: []string{"WEBDAV_LOG_PRETTY"},
|
||||
Destination: &cfg.Log.Pretty,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Value: true,
|
||||
Name: "log-color",
|
||||
Usage: "Enable colored logging",
|
||||
EnvVars: []string{"WEBDAV_LOG_COLOR"},
|
||||
Destination: &cfg.Log.Color,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// HealthWithConfig applies cfg to the root flagset
|
||||
func HealthWithConfig(cfg *config.Config) []cli.Flag {
|
||||
return []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "debug-addr",
|
||||
Value: "0.0.0.0:9119",
|
||||
Value: flags.OverrideDefaultString(cfg.Debug.Addr, "0.0.0.0:9119"),
|
||||
Usage: "Address to debug endpoint",
|
||||
EnvVars: []string{"WEBDAV_DEBUG_ADDR"},
|
||||
Destination: &cfg.Debug.Addr,
|
||||
@@ -48,6 +22,30 @@ func HealthWithConfig(cfg *config.Config) []cli.Flag {
|
||||
// ServerWithConfig applies cfg to the root flagset
|
||||
func ServerWithConfig(cfg *config.Config) []cli.Flag {
|
||||
return []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "log-file",
|
||||
Usage: "Enable log to file",
|
||||
EnvVars: []string{"WEBDAV_LOG_FILE", "OCIS_LOG_FILE"},
|
||||
Destination: &cfg.Log.File,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "log-level",
|
||||
Usage: "Set logging level",
|
||||
EnvVars: []string{"WEBDAV_LOG_LEVEL", "OCIS_LOG_LEVEL"},
|
||||
Destination: &cfg.Log.Level,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "log-pretty",
|
||||
Usage: "Enable pretty logging",
|
||||
EnvVars: []string{"WEBDAV_LOG_PRETTY", "OCIS_LOG_PRETTY"},
|
||||
Destination: &cfg.Log.Pretty,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "log-color",
|
||||
Usage: "Enable colored logging",
|
||||
EnvVars: []string{"WEBDAV_LOG_COLOR", "OCIS_LOG_COLOR"},
|
||||
Destination: &cfg.Log.Color,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "config-file",
|
||||
Value: "",
|
||||
@@ -63,42 +61,42 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag {
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "tracing-type",
|
||||
Value: "jaeger",
|
||||
Value: flags.OverrideDefaultString(cfg.Tracing.Type, "jaeger"),
|
||||
Usage: "Tracing backend type",
|
||||
EnvVars: []string{"WEBDAV_TRACING_TYPE"},
|
||||
Destination: &cfg.Tracing.Type,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "tracing-endpoint",
|
||||
Value: "",
|
||||
Value: flags.OverrideDefaultString(cfg.Tracing.Endpoint, ""),
|
||||
Usage: "Endpoint for the agent",
|
||||
EnvVars: []string{"WEBDAV_TRACING_ENDPOINT"},
|
||||
Destination: &cfg.Tracing.Endpoint,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "tracing-collector",
|
||||
Value: "",
|
||||
Value: flags.OverrideDefaultString(cfg.Tracing.Collector, ""),
|
||||
Usage: "Endpoint for the collector",
|
||||
EnvVars: []string{"WEBDAV_TRACING_COLLECTOR"},
|
||||
Destination: &cfg.Tracing.Collector,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "tracing-service",
|
||||
Value: "webdav",
|
||||
Value: flags.OverrideDefaultString(cfg.Tracing.Service, "webdav"),
|
||||
Usage: "Service name for tracing",
|
||||
EnvVars: []string{"WEBDAV_TRACING_SERVICE"},
|
||||
Destination: &cfg.Tracing.Service,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "debug-addr",
|
||||
Value: "0.0.0.0:9119",
|
||||
Value: flags.OverrideDefaultString(cfg.Debug.Addr, "0.0.0.0:9119"),
|
||||
Usage: "Address to bind debug server",
|
||||
EnvVars: []string{"WEBDAV_DEBUG_ADDR"},
|
||||
Destination: &cfg.Debug.Addr,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "debug-token",
|
||||
Value: "",
|
||||
Value: flags.OverrideDefaultString(cfg.Debug.Token, ""),
|
||||
Usage: "Token to grant metrics access",
|
||||
EnvVars: []string{"WEBDAV_DEBUG_TOKEN"},
|
||||
Destination: &cfg.Debug.Token,
|
||||
@@ -117,32 +115,50 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag {
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "http-addr",
|
||||
Value: "0.0.0.0:9115",
|
||||
Value: flags.OverrideDefaultString(cfg.HTTP.Addr, "0.0.0.0:9115"),
|
||||
Usage: "Address to bind http server",
|
||||
EnvVars: []string{"WEBDAV_HTTP_ADDR"},
|
||||
Destination: &cfg.HTTP.Addr,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "http-namespace",
|
||||
Value: "com.owncloud.web",
|
||||
Value: flags.OverrideDefaultString(cfg.Service.Namespace, "com.owncloud.web"),
|
||||
Usage: "Set the base namespace for service discovery",
|
||||
EnvVars: []string{"WEBDAV_HTTP_NAMESPACE"},
|
||||
Destination: &cfg.Service.Namespace,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "service-name",
|
||||
Value: "webdav",
|
||||
Value: flags.OverrideDefaultString(cfg.Service.Name, "webdav"),
|
||||
Usage: "Service name",
|
||||
EnvVars: []string{"WEBDAV_SERVICE_NAME"},
|
||||
Destination: &cfg.Service.Name,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "http-root",
|
||||
Value: "/",
|
||||
Value: flags.OverrideDefaultString(cfg.HTTP.Root, "/"),
|
||||
Usage: "Root path of http server",
|
||||
EnvVars: []string{"WEBDAV_HTTP_ROOT"},
|
||||
Destination: &cfg.HTTP.Root,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "ocis-public-url",
|
||||
Value: flags.OverrideDefaultString(cfg.OcisPublicURL, "https://127.0.0.1:9200"),
|
||||
Usage: "The domain under which oCIS is reachable",
|
||||
EnvVars: []string{"OCIS_PUBLIC_URL", "OCIS_URL"},
|
||||
Destination: &cfg.OcisPublicURL,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "webdav-namespace",
|
||||
Value: flags.OverrideDefaultString(cfg.WebdavNamespace, "/home"),
|
||||
Usage: "Namespace prefix for the /webdav endpoint",
|
||||
EnvVars: []string{"STORAGE_WEBDAV_NAMESPACE"},
|
||||
Destination: &cfg.WebdavNamespace,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "extensions",
|
||||
Usage: "Run specific extensions during supervised mode. This flag is set by the runtime",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,14 +167,14 @@ func ListWebdavWithConfig(cfg *config.Config) []cli.Flag {
|
||||
return []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "http-namespace",
|
||||
Value: "com.owncloud.web",
|
||||
Value: flags.OverrideDefaultString(cfg.Service.Namespace, "com.owncloud.web"),
|
||||
Usage: "Set the base namespace for service discovery",
|
||||
EnvVars: []string{"WEBDAV_HTTP_NAMESPACE"},
|
||||
Destination: &cfg.Service.Namespace,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "service-name",
|
||||
Value: "webdav",
|
||||
Value: flags.OverrideDefaultString(cfg.Service.Name, "webdav"),
|
||||
Usage: "Service name",
|
||||
EnvVars: []string{"WEBDAV_SERVICE_NAME"},
|
||||
Destination: &cfg.Service.Name,
|
||||
|
||||
@@ -33,7 +33,9 @@ func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) {
|
||||
|
||||
// TODO(tboerger): check if services are up and running
|
||||
|
||||
io.WriteString(w, http.StatusText(http.StatusOK))
|
||||
if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +47,8 @@ func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) {
|
||||
|
||||
// TODO(tboerger): check if services are up and running
|
||||
|
||||
io.WriteString(w, http.StatusText(http.StatusOK))
|
||||
if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,9 @@ func Server(opts ...Option) (http.Service, error) {
|
||||
handle = svc.NewTracing(handle)
|
||||
}
|
||||
|
||||
micro.RegisterHandler(service.Server(), handle)
|
||||
if err := micro.RegisterHandler(service.Server(), handle); err != nil {
|
||||
return http.Service{}, err
|
||||
}
|
||||
|
||||
return service, nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
merrors "github.com/asim/go-micro/v3/errors"
|
||||
"github.com/go-chi/render"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/owncloud/ocis/ocis-pkg/log"
|
||||
@@ -10,7 +14,20 @@ import (
|
||||
"github.com/go-chi/chi"
|
||||
thumbnails "github.com/owncloud/ocis/thumbnails/pkg/proto/v0"
|
||||
"github.com/owncloud/ocis/webdav/pkg/config"
|
||||
thumbnail "github.com/owncloud/ocis/webdav/pkg/dav/thumbnails"
|
||||
"github.com/owncloud/ocis/webdav/pkg/dav/requests"
|
||||
)
|
||||
|
||||
const (
|
||||
TokenHeader = "X-Access-Token"
|
||||
)
|
||||
|
||||
var (
|
||||
codesEnum = map[int]string{
|
||||
http.StatusBadRequest: "Sabre\\DAV\\Exception\\BadRequest",
|
||||
http.StatusUnauthorized: "Sabre\\DAV\\Exception\\NotAuthenticated",
|
||||
http.StatusNotFound: "Sabre\\DAV\\Exception\\NotFound",
|
||||
http.StatusMethodNotAllowed: "Sabre\\DAV\\Exception\\MethodNotAllowed",
|
||||
}
|
||||
)
|
||||
|
||||
// Service defines the extension handlers.
|
||||
@@ -30,10 +47,13 @@ func NewService(opts ...Option) Service {
|
||||
config: options.Config,
|
||||
log: options.Logger,
|
||||
mux: m,
|
||||
thumbnailsClient: thumbnails.NewThumbnailService("com.owncloud.api.thumbnails", grpc.DefaultClient),
|
||||
}
|
||||
|
||||
m.Route(options.Config.HTTP.Root, func(r chi.Router) {
|
||||
r.Get("/remote.php/dav/files/{user}/*", svc.Thumbnail)
|
||||
r.Get("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnail)
|
||||
r.Head("/remote.php/dav/public-files/{token}/*", svc.PublicThumbnailHead)
|
||||
})
|
||||
|
||||
return svc
|
||||
@@ -44,6 +64,7 @@ type Webdav struct {
|
||||
config *config.Config
|
||||
log log.Logger
|
||||
mux *chi.Mux
|
||||
thumbnailsClient thumbnails.ThumbnailService
|
||||
}
|
||||
|
||||
// ServeHTTP implements the Service interface.
|
||||
@@ -53,45 +74,210 @@ func (g Webdav) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Thumbnail implements the Service interface.
|
||||
func (g Webdav) Thumbnail(w http.ResponseWriter, r *http.Request) {
|
||||
tr, err := thumbnail.NewRequest(r)
|
||||
tr, err := requests.ParseThumbnailRequest(r)
|
||||
if err != nil {
|
||||
g.log.Error().Err(err).Msg("could not create Request")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
renderError(w, r, errBadRequest(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c := thumbnails.NewThumbnailService("com.owncloud.api.thumbnails", grpc.DefaultClient)
|
||||
rsp, err := c.GetThumbnail(r.Context(), &thumbnails.GetRequest{
|
||||
t := r.Header.Get(TokenHeader)
|
||||
rsp, err := g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnails.GetThumbnailRequest{
|
||||
Filepath: strings.TrimLeft(tr.Filepath, "/"),
|
||||
Filetype: extensionToFiletype(tr.Filetype),
|
||||
Etag: tr.Etag,
|
||||
Width: int32(tr.Width),
|
||||
Height: int32(tr.Height),
|
||||
Authorization: tr.Authorization,
|
||||
Username: tr.Username,
|
||||
ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")),
|
||||
Width: tr.Width,
|
||||
Height: tr.Height,
|
||||
Source: &thumbnails.GetThumbnailRequest_Cs3Source{
|
||||
Cs3Source: &thumbnails.CS3Source{
|
||||
Path: path.Join(g.config.WebdavNamespace, tr.Filepath),
|
||||
Authorization: t,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
g.log.Error().Err(err).Msg("could not get thumbnail")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
e := merrors.Parse(err.Error())
|
||||
switch e.Code {
|
||||
case http.StatusNotFound:
|
||||
renderError(w, r, errNotFound(notFoundMsg(tr.Filename)))
|
||||
case http.StatusBadRequest:
|
||||
renderError(w, r, errBadRequest(err.Error()))
|
||||
default:
|
||||
renderError(w, r, errInternalError(err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if len(rsp.Thumbnail) == 0 {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
renderError(w, r, errNotFound(""))
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", rsp.GetMimetype())
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(rsp.Thumbnail)
|
||||
g.mustRender(w, r, newThumbnailResponse(rsp))
|
||||
}
|
||||
|
||||
func extensionToFiletype(ext string) thumbnails.GetRequest_FileType {
|
||||
val, ok := thumbnails.GetRequest_FileType_value[strings.ToUpper(ext)]
|
||||
if !ok {
|
||||
return thumbnails.GetRequest_FileType(-1)
|
||||
func (g Webdav) PublicThumbnail(w http.ResponseWriter, r *http.Request) {
|
||||
tr, err := requests.ParseThumbnailRequest(r)
|
||||
if err != nil {
|
||||
g.log.Error().Err(err).Msg("could not create Request")
|
||||
renderError(w, r, errBadRequest(err.Error()))
|
||||
return
|
||||
}
|
||||
return thumbnails.GetRequest_FileType(val)
|
||||
|
||||
rsp, err := g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnails.GetThumbnailRequest{
|
||||
Filepath: strings.TrimLeft(tr.Filepath, "/"),
|
||||
ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")),
|
||||
Width: tr.Width,
|
||||
Height: tr.Height,
|
||||
Source: &thumbnails.GetThumbnailRequest_WebdavSource{
|
||||
WebdavSource: &thumbnails.WebdavSource{
|
||||
Url: g.config.OcisPublicURL + r.URL.RequestURI(),
|
||||
IsPublicLink: true,
|
||||
PublicLinkToken: tr.PublicLinkToken,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
g.log.Error().Err(err).Msg("could not get thumbnail")
|
||||
e := merrors.Parse(err.Error())
|
||||
switch e.Code {
|
||||
case http.StatusNotFound:
|
||||
renderError(w, r, errNotFound(notFoundMsg(tr.Filename)))
|
||||
case http.StatusBadRequest:
|
||||
renderError(w, r, errBadRequest(err.Error()))
|
||||
default:
|
||||
renderError(w, r, errInternalError(err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if len(rsp.Thumbnail) == 0 {
|
||||
renderError(w, r, errNotFound(""))
|
||||
return
|
||||
}
|
||||
|
||||
g.mustRender(w, r, newThumbnailResponse(rsp))
|
||||
}
|
||||
|
||||
func (g Webdav) PublicThumbnailHead(w http.ResponseWriter, r *http.Request) {
|
||||
tr, err := requests.ParseThumbnailRequest(r)
|
||||
if err != nil {
|
||||
g.log.Error().Err(err).Msg("could not create Request")
|
||||
renderError(w, r, errBadRequest(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
rsp, err := g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnails.GetThumbnailRequest{
|
||||
Filepath: strings.TrimLeft(tr.Filepath, "/"),
|
||||
ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")),
|
||||
Width: tr.Width,
|
||||
Height: tr.Height,
|
||||
Source: &thumbnails.GetThumbnailRequest_WebdavSource{
|
||||
WebdavSource: &thumbnails.WebdavSource{
|
||||
Url: g.config.OcisPublicURL + r.URL.RequestURI(),
|
||||
IsPublicLink: true,
|
||||
PublicLinkToken: tr.PublicLinkToken,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
e := merrors.Parse(err.Error())
|
||||
switch e.Code {
|
||||
case http.StatusNotFound:
|
||||
renderError(w, r, errNotFound(notFoundMsg(tr.Filename)))
|
||||
case http.StatusBadRequest:
|
||||
g.log.Error().Err(err).Msg("could not get thumbnail")
|
||||
renderError(w, r, errBadRequest(err.Error()))
|
||||
default:
|
||||
g.log.Error().Err(err).Msg("could not get thumbnail")
|
||||
renderError(w, r, errInternalError(err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if len(rsp.Thumbnail) == 0 {
|
||||
renderError(w, r, errNotFound(""))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func extensionToThumbnailType(ext string) thumbnails.GetThumbnailRequest_ThumbnailType {
|
||||
switch strings.ToUpper(ext) {
|
||||
case "GIF", "PNG":
|
||||
return thumbnails.GetThumbnailRequest_PNG
|
||||
default:
|
||||
return thumbnails.GetThumbnailRequest_JPG
|
||||
}
|
||||
}
|
||||
|
||||
func (g Webdav) mustRender(w http.ResponseWriter, r *http.Request, renderer render.Renderer) {
|
||||
if err := render.Render(w, r, renderer); err != nil {
|
||||
g.log.Err(err).Msg("failed to write response")
|
||||
}
|
||||
}
|
||||
|
||||
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_error
|
||||
type errResponse struct {
|
||||
HTTPStatusCode int `json:"-" xml:"-"`
|
||||
XMLName xml.Name `xml:"d:error"`
|
||||
Xmlnsd string `xml:"xmlns:d,attr"`
|
||||
Xmlnss string `xml:"xmlns:s,attr"`
|
||||
Exception string `xml:"s:exception"`
|
||||
Message string `xml:"s:message"`
|
||||
InnerXML []byte `xml:",innerxml"`
|
||||
}
|
||||
|
||||
func newErrResponse(statusCode int, msg string) *errResponse {
|
||||
rsp := &errResponse{
|
||||
HTTPStatusCode: statusCode,
|
||||
Xmlnsd: "DAV",
|
||||
Xmlnss: "http://sabredav.org/ns",
|
||||
Exception: codesEnum[statusCode],
|
||||
}
|
||||
if msg != "" {
|
||||
rsp.Message = msg
|
||||
}
|
||||
return rsp
|
||||
}
|
||||
|
||||
func errInternalError(msg string) *errResponse {
|
||||
return newErrResponse(http.StatusInternalServerError, msg)
|
||||
}
|
||||
|
||||
func errBadRequest(msg string) *errResponse {
|
||||
return newErrResponse(http.StatusBadRequest, msg)
|
||||
}
|
||||
|
||||
func errNotFound(msg string) *errResponse {
|
||||
return newErrResponse(http.StatusNotFound, msg)
|
||||
}
|
||||
|
||||
type thumbnailResponse struct {
|
||||
contentType string
|
||||
thumbnail []byte
|
||||
}
|
||||
|
||||
func (t *thumbnailResponse) Render(w http.ResponseWriter, _ *http.Request) error {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", t.contentType)
|
||||
_, err := w.Write(t.thumbnail)
|
||||
return err
|
||||
}
|
||||
|
||||
func newThumbnailResponse(rsp *thumbnails.GetThumbnailResponse) *thumbnailResponse {
|
||||
return &thumbnailResponse{
|
||||
contentType: rsp.Mimetype,
|
||||
thumbnail: rsp.Thumbnail,
|
||||
}
|
||||
}
|
||||
|
||||
func renderError(w http.ResponseWriter, r *http.Request, err *errResponse) {
|
||||
render.Status(r, err.HTTPStatusCode)
|
||||
render.XML(w, r, err)
|
||||
}
|
||||
|
||||
func notFoundMsg(name string) string {
|
||||
return "File with name " + name + " could not be located"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"contrib.go.opencensus.io/exporter/jaeger"
|
||||
"contrib.go.opencensus.io/exporter/ocagent"
|
||||
"contrib.go.opencensus.io/exporter/zipkin"
|
||||
openzipkin "github.com/openzipkin/zipkin-go"
|
||||
zipkinhttp "github.com/openzipkin/zipkin-go/reporter/http"
|
||||
"github.com/owncloud/ocis/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/webdav/pkg/config"
|
||||
"go.opencensus.io/stats/view"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
func Configure(cfg *config.Config, logger log.Logger) error {
|
||||
if cfg.Tracing.Enabled {
|
||||
switch t := cfg.Tracing.Type; t {
|
||||
case "agent":
|
||||
exporter, err := ocagent.NewExporter(
|
||||
ocagent.WithReconnectionPeriod(5*time.Second),
|
||||
ocagent.WithAddress(cfg.Tracing.Endpoint),
|
||||
ocagent.WithServiceName(cfg.Tracing.Service),
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error().
|
||||
Err(err).
|
||||
Str("endpoint", cfg.Tracing.Endpoint).
|
||||
Str("collector", cfg.Tracing.Collector).
|
||||
Msg("Failed to create agent tracing")
|
||||
return err
|
||||
}
|
||||
trace.RegisterExporter(exporter)
|
||||
view.RegisterExporter(exporter)
|
||||
case "jaeger":
|
||||
exporter, err := jaeger.NewExporter(
|
||||
jaeger.Options{
|
||||
AgentEndpoint: cfg.Tracing.Endpoint,
|
||||
CollectorEndpoint: cfg.Tracing.Collector,
|
||||
Process: jaeger.Process{
|
||||
ServiceName: cfg.Tracing.Service,
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error().
|
||||
Err(err).
|
||||
Str("endpoint", cfg.Tracing.Endpoint).
|
||||
Str("collector", cfg.Tracing.Collector).
|
||||
Msg("Failed to create jaeger tracing")
|
||||
return err
|
||||
}
|
||||
trace.RegisterExporter(exporter)
|
||||
case "zipkin":
|
||||
endpoint, err := openzipkin.NewEndpoint(
|
||||
cfg.Tracing.Service,
|
||||
cfg.Tracing.Endpoint,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error().
|
||||
Err(err).
|
||||
Str("endpoint", cfg.Tracing.Endpoint).
|
||||
Str("collector", cfg.Tracing.Collector).
|
||||
Msg("Failed to create zipkin tracing")
|
||||
return err
|
||||
}
|
||||
exporter := zipkin.NewExporter(
|
||||
zipkinhttp.NewReporter(
|
||||
cfg.Tracing.Collector,
|
||||
),
|
||||
endpoint,
|
||||
)
|
||||
trace.RegisterExporter(exporter)
|
||||
default:
|
||||
logger.Warn().
|
||||
Str("type", t).
|
||||
Msg("Unknown tracing backend")
|
||||
}
|
||||
trace.ApplyConfig(
|
||||
trace.Config{
|
||||
DefaultSampler: trace.AlwaysSample(),
|
||||
},
|
||||
)
|
||||
} else {
|
||||
logger.Debug().
|
||||
Msg("Tracing is not enabled")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user