rename phoenix -> ocis-phoenix

This commit is contained in:
A.Unger
2020-09-18 13:36:01 +02:00
parent aadf516151
commit d6694bcc3e
81 changed files with 152 additions and 159 deletions
+61
View File
@@ -0,0 +1,61 @@
package assets
import (
"net/http"
"os"
"path"
"github.com/owncloud/ocis/ocis-phoenix/pkg/config"
"github.com/owncloud/ocis-pkg/v2/log"
)
//go:generate go run github.com/UnnoTed/fileb0x embed.yml
// assets gets initialized by New and provides the handler.
type assets struct {
logger log.Logger
config *config.Config
}
// Open just implements the HTTP filesystem interface.
func (a assets) Open(original string) (http.File, error) {
if a.config.Asset.Path != "" {
if stat, err := os.Stat(a.config.Asset.Path); err == nil && stat.IsDir() {
custom := path.Join(
a.config.Asset.Path,
original,
)
if _, err := os.Stat(custom); !os.IsNotExist(err) {
f, err := os.Open(custom)
if err != nil {
return nil, err
}
return f, nil
}
} else {
a.logger.Fatal().
Str("path", a.config.Asset.Path).
Msg("assets directory doesn't exist")
}
}
return FS.OpenFile(
CTX,
original,
os.O_RDONLY,
0644,
)
}
// New returns a new http filesystem to serve assets.
func New(opts ...Option) http.FileSystem {
options := newOptions(opts...)
return assets{
logger: options.Logger,
config: options.Config,
}
}
+9
View File
@@ -0,0 +1,9 @@
package assets
import (
// Fake the import to make the dep tree happy.
_ "golang.org/x/net/context"
// Fake the import to make the dep tree happy.
_ "golang.org/x/net/webdav"
)
File diff suppressed because one or more lines are too long
+23
View File
@@ -0,0 +1,23 @@
---
pkg: "assets"
dest: "."
output: "embed.go"
fmt: true
noprefix: true
compression:
compress: true
custom:
- files:
- "../../assets/"
base: "../../assets/"
prefix: ""
exclude:
- "appinfo/**"
- "LICENSE/**"
- "CHANGELOG.md"
- "README.md"
- "config.json"
...
+40
View File
@@ -0,0 +1,40 @@
package assets
import (
"github.com/owncloud/ocis/ocis-phoenix/pkg/config"
"github.com/owncloud/ocis-pkg/v2/log"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger log.Logger
Config *config.Config
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}
+49
View File
@@ -0,0 +1,49 @@
package command
import (
"fmt"
"net/http"
"github.com/micro/cli/v2"
"github.com/owncloud/ocis/ocis-phoenix/pkg/config"
"github.com/owncloud/ocis/ocis-phoenix/pkg/flagset"
)
// Health is the entrypoint for the health command.
func Health(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "health",
Usage: "Check health status",
Flags: flagset.HealthWithConfig(cfg),
Action: func(c *cli.Context) error {
logger := NewLogger(cfg)
resp, err := http.Get(
fmt.Sprintf(
"http://%s/healthz",
cfg.Debug.Addr,
),
)
if err != nil {
logger.Fatal().
Err(err).
Msg("Failed to request health check")
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
logger.Fatal().
Int("code", resp.StatusCode).
Msg("Health seems to be in bad state")
}
logger.Debug().
Int("code", resp.StatusCode).
Msg("Health got a good state")
return nil
},
}
}
+108
View File
@@ -0,0 +1,108 @@
package command
import (
"os"
"strings"
"github.com/micro/cli/v2"
"github.com/owncloud/ocis/ocis-phoenix/pkg/config"
"github.com/owncloud/ocis/ocis-phoenix/pkg/flagset"
"github.com/owncloud/ocis/ocis-phoenix/pkg/version"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/spf13/viper"
)
// Execute is the entry point for the ocis-phoenix command.
func Execute() error {
cfg := config.New()
app := &cli.App{
Name: "ocis-phoenix",
Version: version.String,
Usage: "Serve Phoenix for oCIS",
Compiled: version.Compiled(),
Authors: []*cli.Author{
{
Name: "ownCloud GmbH",
Email: "support@owncloud.com",
},
},
Flags: flagset.RootWithConfig(cfg),
Before: func(c *cli.Context) error {
return ParseConfig(c, cfg)
},
Commands: []*cli.Command{
Server(cfg),
Health(cfg),
},
}
cli.HelpFlag = &cli.BoolFlag{
Name: "help,h",
Usage: "Show the help",
}
cli.VersionFlag = &cli.BoolFlag{
Name: "version,v",
Usage: "Print the version",
}
return app.Run(os.Args)
}
// NewLogger initializes a service-specific logger instance.
func NewLogger(cfg *config.Config) log.Logger {
return log.NewLogger(
log.Name("phoenix"),
log.Level(cfg.Log.Level),
log.Pretty(cfg.Log.Pretty),
log.Color(cfg.Log.Color),
)
}
// ParseConfig loads proxy configuration from Viper known paths.
func ParseConfig(c *cli.Context, cfg *config.Config) error {
logger := NewLogger(cfg)
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.SetEnvPrefix("PHOENIX")
viper.AutomaticEnv()
if c.IsSet("config-file") {
viper.SetConfigFile(c.String("config-file"))
} else {
viper.SetConfigName("phoenix")
viper.AddConfigPath("/etc/ocis")
viper.AddConfigPath("$HOME/.ocis")
viper.AddConfigPath("./config")
}
if err := viper.ReadInConfig(); err != nil {
switch err.(type) {
case viper.ConfigFileNotFoundError:
logger.Info().
Msg("Continue without config")
case viper.UnsupportedConfigError:
logger.Fatal().
Err(err).
Msg("Unsupported config type")
default:
logger.Fatal().
Err(err).
Msg("Failed to read config")
}
}
if err := viper.Unmarshal(&cfg); err != nil {
logger.Fatal().
Err(err).
Msg("Failed to parse config")
}
return nil
}
+240
View File
@@ -0,0 +1,240 @@
package command
import (
"context"
"encoding/json"
"io/ioutil"
"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-phoenix/pkg/config"
"github.com/owncloud/ocis/ocis-phoenix/pkg/flagset"
"github.com/owncloud/ocis/ocis-phoenix/pkg/metrics"
"github.com/owncloud/ocis/ocis-phoenix/pkg/server/debug"
"github.com/owncloud/ocis/ocis-phoenix/pkg/server/http"
"go.opencensus.io/stats/view"
"go.opencensus.io/trace"
)
// Server is the entrypoint for the server command.
func Server(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "server",
Usage: "Start integrated server",
Flags: flagset.ServerWithConfig(cfg),
Before: func(c *cli.Context) error {
if cfg.HTTP.Root != "/" {
cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/")
}
cfg.Phoenix.Config.Apps = c.StringSlice("web-config-app")
return ParseConfig(c, cfg)
},
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,
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")
}
// actually read the contents of the config file and override defaults
if cfg.File != "" {
contents, err := ioutil.ReadFile(cfg.File)
if err != nil {
logger.Err(err).Msg("error opening config file")
return err
}
json.Unmarshal(contents, &cfg.Phoenix.Config)
}
var (
gr = run.Group{}
ctx, cancel = context.WithCancel(context.Background())
metrics = metrics.New()
)
defer cancel()
{
server, err := http.Server(
http.Logger(logger),
http.Context(ctx),
http.Namespace(cfg.HTTP.Namespace),
http.Config(cfg),
http.Metrics(metrics),
http.Flags(flagset.RootWithConfig(config.New())),
)
if err != nil {
logger.Info().
Err(err).
Str("transport", "http").
Msg("Failed to initialize server")
return err
}
gr.Add(func() error {
err := server.Run()
if err != nil {
logger.Error().
Err(err).
Str("transport", "http").
Msg("Failed to start server")
}
return err
}, func(_ error) {
logger.Info().
Str("transport", "http").
Msg("Shutting down server")
cancel()
})
}
{
server, err := debug.Server(
debug.Logger(logger),
debug.Context(ctx),
debug.Config(cfg),
)
if err != nil {
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")
}
})
}
{
stop := make(chan os.Signal, 1)
gr.Add(func() error {
signal.Notify(stop, os.Interrupt)
<-stop
return nil
}, func(err error) {
close(stop)
cancel()
})
}
return gr.Run()
},
}
}
+100
View File
@@ -0,0 +1,100 @@
package config
// Log defines the available logging configuration.
type Log struct {
Level string
Pretty bool
Color bool
}
// Debug defines the available debug configuration.
type Debug struct {
Addr string
Token string
Pprof bool
Zpages bool
}
// HTTP defines the available http configuration.
type HTTP struct {
Addr string
Root string
Namespace string
}
// Tracing defines the available tracing configuration.
type Tracing struct {
Enabled bool
Type string
Endpoint string
Collector string
Service string
}
// Asset defines the available asset configuration.
type Asset struct {
Path string
}
// PhoenixConfig defines the available phoenix configuration for a dynamically rendered config.json.
type PhoenixConfig struct {
Server string `json:"server,omitempty"`
Theme string `json:"theme,omitempty"`
Version string `json:"version,omitempty"` // TODO what is version used for?
OpenIDConnect OIDC `json:"openIdConnect,omitempty"`
Apps []string `json:"apps"` // TODO add nilasempty when https://go-review.googlesource.com/c/go/+/205897/ is released
ExternalApps []ExternalApp `json:"external_apps,omitempty"`
Options map[string]interface{} `json:"options,omitempty"`
}
// OIDC defines the available oidc configuration
type OIDC struct {
MetadataURL string `json:"metadata_url,omitempty"`
Authority string `json:"authority,omitempty"`
ClientID string `json:"client_id,omitempty"`
ResponseType string `json:"response_type,omitempty"`
Scope string `json:"scope,omitempty"`
}
// ExternalApp defines an external phoenix app.
// {
// "name": "hello",
// "path": "http://localhost:9105/hello.js",
// "config": {
// "url": "http://localhost:9105"
// }
// }
type ExternalApp struct {
ID string `json:"id,omitempty"`
Path string `json:"path,omitempty"`
// Config is completely dynamic, because it depends on the extension
Config map[string]interface{} `json:"config,omitempty"`
}
// ExternalAppConfig defines an external phoenix app configuration.
type ExternalAppConfig struct {
URL string `json:"url,omitempty"`
}
// Phoenix defines the available phoenix configuration.
type Phoenix struct {
Path string
Config PhoenixConfig
}
// Config combines all available configuration parts.
type Config struct {
File string
Log Log
Debug Debug
HTTP HTTP
Tracing Tracing
Asset Asset
OIDC OIDC
Phoenix Phoenix
}
// New initializes a new configuration with or without defaults.
func New() *Config {
return &Config{}
}
+216
View File
@@ -0,0 +1,216 @@
package flagset
import (
"github.com/micro/cli/v2"
"github.com/owncloud/ocis/ocis-phoenix/pkg/config"
)
// RootWithConfig applies cfg to the root flagset
func RootWithConfig(cfg *config.Config) []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "config-file",
Value: "",
Usage: "Path to config file",
EnvVars: []string{"PHOENIX_CONFIG_FILE"},
Destination: &cfg.File,
},
&cli.StringFlag{
Name: "log-level",
Value: "info",
Usage: "Set logging level",
EnvVars: []string{"PHOENIX_LOG_LEVEL"},
Destination: &cfg.Log.Level,
},
&cli.BoolFlag{
Name: "log-pretty",
Value: true,
Usage: "Enable pretty logging",
EnvVars: []string{"PHOENIX_LOG_PRETTY"},
Destination: &cfg.Log.Pretty,
},
&cli.BoolFlag{
Name: "log-color",
Value: true,
Usage: "Enable colored logging",
EnvVars: []string{"PHOENIX_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:9104",
Usage: "Address to debug endpoint",
EnvVars: []string{"PHOENIX_DEBUG_ADDR"},
Destination: &cfg.Debug.Addr,
},
}
}
// ServerWithConfig applies cfg to the root flagset
func ServerWithConfig(cfg *config.Config) []cli.Flag {
return []cli.Flag{
&cli.BoolFlag{
Name: "tracing-enabled",
Usage: "Enable sending traces",
EnvVars: []string{"PHOENIX_TRACING_ENABLED"},
Destination: &cfg.Tracing.Enabled,
},
&cli.StringFlag{
Name: "tracing-type",
Value: "jaeger",
Usage: "Tracing backend type",
EnvVars: []string{"PHOENIX_TRACING_TYPE"},
Destination: &cfg.Tracing.Type,
},
&cli.StringFlag{
Name: "tracing-endpoint",
Value: "",
Usage: "Endpoint for the agent",
EnvVars: []string{"PHOENIX_TRACING_ENDPOINT"},
Destination: &cfg.Tracing.Endpoint,
},
&cli.StringFlag{
Name: "tracing-collector",
Value: "",
Usage: "Endpoint for the collector",
EnvVars: []string{"PHOENIX_TRACING_COLLECTOR"},
Destination: &cfg.Tracing.Collector,
},
&cli.StringFlag{
Name: "tracing-service",
Value: "phoenix",
Usage: "Service name for tracing",
EnvVars: []string{"PHOENIX_TRACING_SERVICE"},
Destination: &cfg.Tracing.Service,
},
&cli.StringFlag{
Name: "debug-addr",
Value: "0.0.0.0:9104",
Usage: "Address to bind debug server",
EnvVars: []string{"PHOENIX_DEBUG_ADDR"},
Destination: &cfg.Debug.Addr,
},
&cli.StringFlag{
Name: "debug-token",
Value: "",
Usage: "Token to grant metrics access",
EnvVars: []string{"PHOENIX_DEBUG_TOKEN"},
Destination: &cfg.Debug.Token,
},
&cli.BoolFlag{
Name: "debug-pprof",
Usage: "Enable pprof debugging",
EnvVars: []string{"PHOENIX_DEBUG_PPROF"},
Destination: &cfg.Debug.Pprof,
},
&cli.BoolFlag{
Name: "debug-zpages",
Usage: "Enable zpages debugging",
EnvVars: []string{"PHOENIX_DEBUG_ZPAGES"},
Destination: &cfg.Debug.Zpages,
},
&cli.StringFlag{
Name: "http-addr",
Value: "0.0.0.0:9100",
Usage: "Address to bind http server",
EnvVars: []string{"PHOENIX_HTTP_ADDR"},
Destination: &cfg.HTTP.Addr,
},
&cli.StringFlag{
Name: "http-root",
Value: "/",
Usage: "Root path of http server",
EnvVars: []string{"PHOENIX_HTTP_ROOT"},
Destination: &cfg.HTTP.Root,
},
&cli.StringFlag{
Name: "http-namespace",
Value: "com.owncloud.web",
Usage: "Set the base namespace for the http namespace",
EnvVars: []string{"PHOENIX_NAMESPACE"},
Destination: &cfg.HTTP.Namespace,
},
&cli.StringFlag{
Name: "asset-path",
Value: "",
Usage: "Path to custom assets",
EnvVars: []string{"PHOENIX_ASSET_PATH"},
Destination: &cfg.Asset.Path,
},
&cli.StringFlag{
Name: "web-config",
Value: "",
Usage: "Path to phoenix config",
EnvVars: []string{"PHOENIX_WEB_CONFIG"},
Destination: &cfg.Phoenix.Path,
},
&cli.StringFlag{
Name: "web-config-server",
Value: "https://localhost:9200",
Usage: "Server URL",
EnvVars: []string{"PHOENIX_WEB_CONFIG_SERVER"},
Destination: &cfg.Phoenix.Config.Server,
},
&cli.StringFlag{
Name: "web-config-theme",
Value: "owncloud",
Usage: "Theme",
EnvVars: []string{"PHOENIX_WEB_CONFIG_THEME"},
Destination: &cfg.Phoenix.Config.Theme,
},
&cli.StringFlag{
Name: "web-config-version",
Value: "0.1.0",
Usage: "Version",
EnvVars: []string{"PHOENIX_WEB_CONFIG_VERSION"},
Destination: &cfg.Phoenix.Config.Version,
},
&cli.StringSliceFlag{
Name: "web-config-app",
Value: cli.NewStringSlice("files", "draw-io", "markdown-editor", "media-viewer"),
Usage: `--web-config-app files [--web-config-app draw-io]`,
EnvVars: []string{"PHOENIX_WEB_CONFIG_APPS"},
},
&cli.StringFlag{
Name: "oidc-metadata-url",
Value: "https://localhost:9200/.well-known/openid-configuration",
Usage: "OpenID Connect metadata URL",
EnvVars: []string{"PHOENIX_OIDC_METADATA_URL"},
Destination: &cfg.Phoenix.Config.OpenIDConnect.MetadataURL,
},
&cli.StringFlag{
Name: "oidc-authority",
Value: "https://localhost:9200",
Usage: "OpenID Connect authority", // TODO rename to Issuer
EnvVars: []string{"PHOENIX_OIDC_AUTHORITY"},
Destination: &cfg.Phoenix.Config.OpenIDConnect.Authority,
},
&cli.StringFlag{
Name: "oidc-client-id",
Value: "phoenix",
Usage: "OpenID Connect client ID",
EnvVars: []string{"PHOENIX_OIDC_CLIENT_ID"},
Destination: &cfg.Phoenix.Config.OpenIDConnect.ClientID,
},
&cli.StringFlag{
Name: "oidc-response-type",
Value: "code",
Usage: "OpenID Connect response type",
EnvVars: []string{"PHOENIX_OIDC_RESPONSE_TYPE"},
Destination: &cfg.Phoenix.Config.OpenIDConnect.ResponseType,
},
&cli.StringFlag{
Name: "oidc-scope",
Value: "openid profile email",
Usage: "OpenID Connect scope",
EnvVars: []string{"PHOENIX_OIDC_SCOPE"},
Destination: &cfg.Phoenix.Config.OpenIDConnect.Scope,
},
}
}
+32
View File
@@ -0,0 +1,32 @@
package metrics
var (
// Namespace defines the namespace for the defines metrics.
Namespace = "ocis"
// Subsystem defines the subsystem for the defines metrics.
Subsystem = "phoenix"
)
// Metrics defines the available metrics of this service.
type Metrics struct {
// Counter *prometheus.CounterVec
}
// New initializes the available metrics.
func New() *Metrics {
m := &Metrics{
// Counter: prometheus.NewCounterVec(prometheus.CounterOpts{
// Namespace: Namespace,
// Subsystem: Subsystem,
// Name: "greet_total",
// Help: "How many greeting requests processed",
// }, []string{}),
}
// prometheus.Register(
// m.Counter,
// )
return m
}
@@ -0,0 +1,13 @@
package middleware
import (
"net/http"
)
// SilentRefresh allows the oidc client lib to silently refresh the token in an iframe
func SilentRefresh(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
next.ServeHTTP(w, r)
})
}
+50
View File
@@ -0,0 +1,50 @@
package debug
import (
"context"
"github.com/owncloud/ocis/ocis-phoenix/pkg/config"
"github.com/owncloud/ocis-pkg/v2/log"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger log.Logger
Context context.Context
Config *config.Config
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Context provides a function to set the context option.
func Context(val context.Context) Option {
return func(o *Options) {
o.Context = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}
+51
View File
@@ -0,0 +1,51 @@
package debug
import (
"io"
"net/http"
"github.com/owncloud/ocis/ocis-phoenix/pkg/config"
"github.com/owncloud/ocis/ocis-phoenix/pkg/version"
"github.com/owncloud/ocis-pkg/v2/service/debug"
)
// Server initializes the debug service and server.
func Server(opts ...Option) (*http.Server, error) {
options := newOptions(opts...)
return debug.NewService(
debug.Logger(options.Logger),
debug.Name("phoenix"),
debug.Version(version.String),
debug.Address(options.Config.Debug.Addr),
debug.Token(options.Config.Debug.Token),
debug.Pprof(options.Config.Debug.Pprof),
debug.Zpages(options.Config.Debug.Zpages),
debug.Health(health(options.Config)),
debug.Ready(ready(options.Config)),
), nil
}
// health implements the health check.
func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
// TODO(tboerger): check if services are up and running
io.WriteString(w, http.StatusText(http.StatusOK))
}
}
// ready implements the ready check.
func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
// TODO(tboerger): check if services are up and running
io.WriteString(w, http.StatusText(http.StatusOK))
}
}
+76
View File
@@ -0,0 +1,76 @@
package http
import (
"context"
"github.com/micro/cli/v2"
"github.com/owncloud/ocis/ocis-phoenix/pkg/config"
"github.com/owncloud/ocis/ocis-phoenix/pkg/metrics"
"github.com/owncloud/ocis-pkg/v2/log"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger log.Logger
Context context.Context
Config *config.Config
Metrics *metrics.Metrics
Flags []cli.Flag
Namespace string
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Context provides a function to set the context option.
func Context(val context.Context) Option {
return func(o *Options) {
o.Context = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}
// Metrics provides a function to set the metrics option.
func Metrics(val *metrics.Metrics) Option {
return func(o *Options) {
o.Metrics = val
}
}
// Flags provides a function to set the flags option.
func Flags(val []cli.Flag) Option {
return func(o *Options) {
o.Flags = append(o.Flags, val...)
}
}
// Namespace provides a function to set the Namespace option.
func Namespace(val string) Option {
return func(o *Options) {
o.Namespace = val
}
}
+58
View File
@@ -0,0 +1,58 @@
package http
import (
phoenixmid "github.com/owncloud/ocis/ocis-phoenix/pkg/middleware"
svc "github.com/owncloud/ocis/ocis-phoenix/pkg/service/v0"
"github.com/owncloud/ocis/ocis-phoenix/pkg/version"
"github.com/owncloud/ocis-pkg/v2/middleware"
"github.com/owncloud/ocis-pkg/v2/service/http"
)
// Server initializes the http service and server.
func Server(opts ...Option) (http.Service, error) {
options := newOptions(opts...)
service := http.NewService(
http.Logger(options.Logger),
http.Namespace(options.Namespace),
http.Name("phoenix"),
http.Version(version.String),
http.Address(options.Config.HTTP.Addr),
http.Context(options.Context),
http.Flags(options.Flags...),
)
handle := svc.NewService(
svc.Logger(options.Logger),
svc.Config(options.Config),
svc.Middleware(
middleware.RealIP,
middleware.RequestID,
middleware.Cache,
middleware.Cors,
middleware.Secure,
phoenixmid.SilentRefresh,
middleware.Version(
"phoenix",
version.String,
),
middleware.Logger(
options.Logger,
),
),
)
{
handle = svc.NewInstrument(handle, options.Metrics)
handle = svc.NewLogging(handle, options.Logger)
handle = svc.NewTracing(handle)
}
service.Handle(
"/",
handle,
)
service.Init()
return service, nil
}
+30
View File
@@ -0,0 +1,30 @@
package svc
import (
"net/http"
"github.com/owncloud/ocis/ocis-phoenix/pkg/metrics"
)
// NewInstrument returns a service that instruments metrics.
func NewInstrument(next Service, metrics *metrics.Metrics) Service {
return instrument{
next: next,
metrics: metrics,
}
}
type instrument struct {
next Service
metrics *metrics.Metrics
}
// ServeHTTP implements the Service interface.
func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) {
i.next.ServeHTTP(w, r)
}
// Config implements the Service interface.
func (i instrument) Config(w http.ResponseWriter, r *http.Request) {
i.next.Config(w, r)
}
+30
View File
@@ -0,0 +1,30 @@
package svc
import (
"net/http"
"github.com/owncloud/ocis-pkg/v2/log"
)
// NewLogging returns a service that logs messages.
func NewLogging(next Service, logger log.Logger) Service {
return logging{
next: next,
logger: logger,
}
}
type logging struct {
next Service
logger log.Logger
}
// ServeHTTP implements the Service interface.
func (l logging) ServeHTTP(w http.ResponseWriter, r *http.Request) {
l.next.ServeHTTP(w, r)
}
// Config implements the Service interface.
func (l logging) Config(w http.ResponseWriter, r *http.Request) {
l.next.Config(w, r)
}
+50
View File
@@ -0,0 +1,50 @@
package svc
import (
"net/http"
"github.com/owncloud/ocis/ocis-phoenix/pkg/config"
"github.com/owncloud/ocis-pkg/v2/log"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger log.Logger
Config *config.Config
Middleware []func(http.Handler) http.Handler
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}
// Middleware provides a function to set the middleware option.
func Middleware(val ...func(http.Handler) http.Handler) Option {
return func(o *Options) {
o.Middleware = val
}
}
+167
View File
@@ -0,0 +1,167 @@
package svc
import (
"encoding/json"
"io/ioutil"
"net/http"
"os"
"strings"
"github.com/go-chi/chi"
"github.com/owncloud/ocis/ocis-phoenix/pkg/assets"
"github.com/owncloud/ocis/ocis-phoenix/pkg/config"
"github.com/owncloud/ocis-pkg/v2/log"
)
var (
// ErrConfigInvalid is returned when the config parse is invalid.
ErrConfigInvalid = `Invalid or missing config`
)
// Service defines the extension handlers.
type Service interface {
ServeHTTP(http.ResponseWriter, *http.Request)
Config(http.ResponseWriter, *http.Request)
}
// NewService returns a service implementation for Service.
func NewService(opts ...Option) Service {
options := newOptions(opts...)
m := chi.NewMux()
m.Use(options.Middleware...)
svc := Phoenix{
logger: options.Logger,
config: options.Config,
mux: m,
}
m.Route(options.Config.HTTP.Root, func(r chi.Router) {
r.Get("/config.json", svc.Config)
r.Mount("/", svc.Static())
})
return svc
}
// Phoenix defines implements the business logic for Service.
type Phoenix struct {
logger log.Logger
config *config.Config
mux *chi.Mux
}
// ServeHTTP implements the Service interface.
func (p Phoenix) ServeHTTP(w http.ResponseWriter, r *http.Request) {
p.mux.ServeHTTP(w, r)
}
func (p Phoenix) getPayload() (payload []byte, err error) {
if p.config.Phoenix.Path == "" {
// render dynamically using config
// provide default ocis-web options
if p.config.Phoenix.Config.Options == nil {
p.config.Phoenix.Config.Options = make(map[string]interface{})
p.config.Phoenix.Config.Options["hideSearchBar"] = true
}
if p.config.Phoenix.Config.ExternalApps == nil {
p.config.Phoenix.Config.ExternalApps = []config.ExternalApp{
{
ID: "accounts",
Path: "/accounts.js",
},
{
ID: "settings",
Path: "/settings.js",
},
}
}
// make apps render as empty array if it is empty
// TODO remove once https://github.com/golang/go/issues/27589 is fixed
if len(p.config.Phoenix.Config.Apps) == 0 {
p.config.Phoenix.Config.Apps = make([]string, 0)
}
return json.Marshal(p.config.Phoenix.Config)
}
// try loading from file
if _, err = os.Stat(p.config.Phoenix.Path); os.IsNotExist(err) {
p.logger.Fatal().
Err(err).
Str("config", p.config.Phoenix.Path).
Msg("phoenix config doesn't exist")
}
payload, err = ioutil.ReadFile(p.config.Phoenix.Path)
if err != nil {
p.logger.Fatal().
Err(err).
Str("config", p.config.Phoenix.Path).
Msg("failed to read custom config")
}
return
}
// Config implements the Service interface.
func (p Phoenix) Config(w http.ResponseWriter, r *http.Request) {
payload, err := p.getPayload()
if err != nil {
http.Error(w, ErrConfigInvalid, http.StatusUnprocessableEntity)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(payload)
}
// Static simply serves all static files.
func (p Phoenix) Static() http.HandlerFunc {
rootWithSlash := p.config.HTTP.Root
if !strings.HasSuffix(rootWithSlash, "/") {
rootWithSlash = rootWithSlash + "/"
}
static := http.StripPrefix(
rootWithSlash,
http.FileServer(
assets.New(
assets.Logger(p.logger),
assets.Config(p.config),
),
),
)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if rootWithSlash != "/" && r.URL.Path == p.config.HTTP.Root {
http.Redirect(
w,
r,
rootWithSlash,
http.StatusMovedPermanently,
)
return
}
if r.URL.Path != rootWithSlash && strings.HasSuffix(r.URL.Path, "/") {
http.NotFound(
w,
r,
)
return
}
static.ServeHTTP(w, r)
})
}
+26
View File
@@ -0,0 +1,26 @@
package svc
import (
"net/http"
)
// NewTracing returns a service that instruments traces.
func NewTracing(next Service) Service {
return tracing{
next: next,
}
}
type tracing struct {
next Service
}
// ServeHTTP implements the Service interface.
func (t tracing) ServeHTTP(w http.ResponseWriter, r *http.Request) {
t.next.ServeHTTP(w, r)
}
// Config implements the Service interface.
func (t tracing) Config(w http.ResponseWriter, r *http.Request) {
t.next.Config(w, r)
}
+19
View File
@@ -0,0 +1,19 @@
package version
import (
"time"
)
var (
// String gets defined by the build system.
String = "0.0.0"
// Date indicates the build date.
Date = "00000000"
)
// Compiled returns the compile time of this service.
func Compiled() time.Time {
t, _ := time.Parse("20060102", Date)
return t
}