Restructure based on hello reference

This commit is contained in:
Thomas Boerger
2019-12-06 13:18:26 +01:00
parent ac202d5458
commit dfa78aff7c
53 changed files with 774 additions and 3041 deletions
+11 -4
View File
@@ -5,7 +5,6 @@ import (
"net/http"
"github.com/micro/cli"
"github.com/micro/go-micro/util/log"
"github.com/owncloud/ocis-graph/pkg/config"
"github.com/owncloud/ocis-graph/pkg/flagset"
)
@@ -17,6 +16,8 @@ func Health(cfg *config.Config) cli.Command {
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",
@@ -25,16 +26,22 @@ func Health(cfg *config.Config) cli.Command {
)
if err != nil {
log.Fatalf("Failed to request health check: %w", err)
logger.Fatal().
Err(err).
Msg("Failed to request health check")
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
log.Fatalf("Health check responds with [%d]", resp.StatusCode)
logger.Fatal().
Int("code", resp.StatusCode).
Msg("Health seems to be in bad state")
}
log.Debugf("Health got good state with [%d]", resp.StatusCode)
logger.Debug().
Int("code", resp.StatusCode).
Msg("Health got a good state")
return nil
},
+23 -27
View File
@@ -5,10 +5,10 @@ import (
"strings"
"github.com/micro/cli"
"github.com/micro/go-micro/util/log"
"github.com/owncloud/ocis-graph/pkg/config"
"github.com/owncloud/ocis-graph/pkg/flagset"
"github.com/owncloud/ocis-graph/pkg/version"
"github.com/owncloud/ocis-pkg/log"
"github.com/spf13/viper"
)
@@ -19,7 +19,7 @@ func Execute() error {
app := &cli.App{
Name: "ocis-graph",
Version: version.String,
Usage: "Example service for Reva/oCIS",
Usage: "Serve Graph API for oCIS",
Compiled: version.Compiled(),
Authors: []cli.Author{
@@ -32,10 +32,10 @@ func Execute() error {
Flags: flagset.RootWithConfig(cfg),
Before: func(c *cli.Context) error {
NewLogger(cfg)
logger := NewLogger(cfg)
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.SetEnvPrefix("graph")
viper.SetEnvPrefix("GRAPH")
viper.AutomaticEnv()
if c.IsSet("config-file") {
@@ -51,16 +51,23 @@ func Execute() error {
if err := viper.ReadInConfig(); err != nil {
switch err.(type) {
case viper.ConfigFileNotFoundError:
log.Info("Continue without config")
logger.Info().
Msg("Continue without config")
case viper.UnsupportedConfigError:
log.Fatalf("Unsupported config type: %w", err)
logger.Fatal().
Err(err).
Msg("Unsupported config type")
default:
log.Fatalf("Failed to read config: %w", err)
logger.Fatal().
Err(err).
Msg("Failed to read config")
}
}
if err := viper.Unmarshal(&cfg); err != nil {
log.Fatalf("Failed to parse config: %w", err)
logger.Fatal().
Err(err).
Msg("Failed to parse config")
}
return nil
@@ -85,23 +92,12 @@ func Execute() error {
return app.Run(os.Args)
}
func NewLogger(cfg *config.Config) {
switch strings.ToLower(cfg.Log.Level) {
case "fatal":
log.SetLevel(log.LevelFatal)
case "error":
log.SetLevel(log.LevelError)
case "info":
log.SetLevel(log.LevelInfo)
case "warn":
log.SetLevel(log.LevelWarn)
case "debug":
log.SetLevel(log.LevelDebug)
case "trace":
log.SetLevel(log.LevelTrace)
default:
log.SetLevel(log.LevelInfo)
}
log.Name("graph")
// NewLogger initializes a service-specific logger instance.
func NewLogger(cfg *config.Config) log.Logger {
return log.NewLogger(
log.Name("graph"),
log.Level(cfg.Log.Level),
log.Pretty(cfg.Log.Pretty),
log.Color(cfg.Log.Color),
)
}
+50 -27
View File
@@ -10,12 +10,12 @@ import (
"contrib.go.opencensus.io/exporter/ocagent"
"contrib.go.opencensus.io/exporter/zipkin"
"github.com/micro/cli"
"github.com/micro/go-micro/util/log"
"github.com/oklog/run"
openzipkin "github.com/openzipkin/zipkin-go"
zipkinhttp "github.com/openzipkin/zipkin-go/reporter/http"
"github.com/owncloud/ocis-graph/pkg/config"
"github.com/owncloud/ocis-graph/pkg/flagset"
"github.com/owncloud/ocis-graph/pkg/metrics"
"github.com/owncloud/ocis-graph/pkg/server/debug"
"github.com/owncloud/ocis-graph/pkg/server/http"
"go.opencensus.io/stats/view"
@@ -29,6 +29,8 @@ func Server(cfg *config.Config) cli.Command {
Usage: "Start integrated server",
Flags: flagset.ServerWithConfig(cfg),
Action: func(c *cli.Context) error {
logger := NewLogger(cfg)
if cfg.Tracing.Enabled {
switch t := cfg.Tracing.Type; t {
case "agent":
@@ -39,12 +41,11 @@ func Server(cfg *config.Config) cli.Command {
)
if err != nil {
log.Error(
"Failed to create agent tracing on [%s] endpoint and [%s] collector: %w",
cfg.Tracing.Endpoint,
cfg.Tracing.Collector,
err,
)
logger.Error().
Err(err).
Str("endpoint", cfg.Tracing.Endpoint).
Str("collector", cfg.Tracing.Collector).
Msg("Failed to create agent tracing")
return err
}
@@ -62,12 +63,11 @@ func Server(cfg *config.Config) cli.Command {
)
if err != nil {
log.Error(
"Failed to create jaeger tracing on [%s] endpoint and [%s] collector: %w",
cfg.Tracing.Endpoint,
cfg.Tracing.Collector,
err,
)
logger.Error().
Err(err).
Str("endpoint", cfg.Tracing.Endpoint).
Str("collector", cfg.Tracing.Collector).
Msg("Failed to create jaeger tracing")
return err
}
@@ -81,12 +81,11 @@ func Server(cfg *config.Config) cli.Command {
)
if err != nil {
log.Error(
"Failed to create zipkin tracing on [%s] endpoint and [%s] collector: %w",
cfg.Tracing.Endpoint,
cfg.Tracing.Collector,
err,
)
logger.Error().
Err(err).
Str("endpoint", cfg.Tracing.Endpoint).
Str("collector", cfg.Tracing.Collector).
Msg("Failed to create zipkin tracing")
return err
}
@@ -101,7 +100,9 @@ func Server(cfg *config.Config) cli.Command {
trace.RegisterExporter(exporter)
default:
log.Warnf("Unknown tracing backend [%s]", t)
logger.Warn().
Str("type", t).
Msg("Unknown tracing backend")
}
trace.ApplyConfig(
@@ -110,42 +111,59 @@ func Server(cfg *config.Config) cli.Command {
},
)
} else {
log.Debug("Tracing is not enabled")
logger.Debug().
Msg("Tracing is not enabled")
}
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.Config(cfg),
http.Metrics(metrics),
)
if err != nil {
log.Errorf("Server [http] failed to initialize: %w", err)
cancel()
logger.Info().
Err(err).
Str("transport", "http").
Msg("Failed to initialize server")
return err
}
gr.Add(func() error {
return server.Run()
}, func(_ error) {
log.Infof("Server [http] shutting down")
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 {
log.Errorf("Server [debug] failed to initialize: %w", err)
logger.Info().
Err(err).
Str("transport", "debug").
Msg("Failed to initialize server")
return err
}
@@ -158,9 +176,14 @@ func Server(cfg *config.Config) cli.Command {
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Errorf("Server [debug] shutdown failed: %w", err)
logger.Info().
Err(err).
Str("transport", "debug").
Msg("Failed to shutdown server")
} else {
log.Infof("Server [debug] shutting down")
logger.Info().
Str("transport", "debug").
Msg("Shutting down server")
}
})
}
+9 -8
View File
@@ -1,9 +1,13 @@
package config
// Log defines the available logging configuration.
type Log struct {
Level string
Level string
Pretty bool
Color bool
}
// Debug defines the available debug configuration.
type Debug struct {
Addr string
Token string
@@ -11,16 +15,12 @@ type Debug struct {
Zpages bool
}
// HTTP defines the available http configuration.
type HTTP struct {
Addr string
Root string
}
type GRPC struct {
Addr string
Root string
}
// Tracing defines the available tracing configuration.
type Tracing struct {
Enabled bool
Type string
@@ -29,15 +29,16 @@ type Tracing struct {
Service string
}
// Config combines all available configuration parts.
type Config struct {
File string
Log Log
Debug Debug
HTTP HTTP
GRPC GRPC
Tracing Tracing
}
// New initializes a new configuration with or without defaults.
func New() *Config {
return &Config{}
}
+15 -3
View File
@@ -22,6 +22,18 @@ func RootWithConfig(cfg *config.Config) []cli.Flag {
EnvVar: "GRAPH_LOG_LEVEL",
Destination: &cfg.Log.Level,
},
&cli.BoolTFlag{
Name: "log-pretty",
Usage: "Enable pretty logging",
EnvVar: "GRAPH_LOG_PRETTY",
Destination: &cfg.Log.Pretty,
},
&cli.BoolTFlag{
Name: "log-color",
Usage: "Enable colored logging",
EnvVar: "GRAPH_LOG_COLOR",
Destination: &cfg.Log.Color,
},
}
}
@@ -30,7 +42,7 @@ func HealthWithConfig(cfg *config.Config) []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "debug-addr",
Value: "0.0.0.0:8390",
Value: "0.0.0.0:9124",
Usage: "Address to debug endpoint",
EnvVar: "GRAPH_DEBUG_ADDR",
Destination: &cfg.Debug.Addr,
@@ -77,7 +89,7 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag {
},
&cli.StringFlag{
Name: "debug-addr",
Value: "0.0.0.0:8390",
Value: "0.0.0.0:9124",
Usage: "Address to bind debug server",
EnvVar: "GRAPH_DEBUG_ADDR",
Destination: &cfg.Debug.Addr,
@@ -103,7 +115,7 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag {
},
&cli.StringFlag{
Name: "http-addr",
Value: "0.0.0.0:8380",
Value: "0.0.0.0:9120",
Usage: "Address to bind http server",
EnvVar: "GRAPH_HTTP_ADDR",
Destination: &cfg.HTTP.Addr,
+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 = "graph"
)
// 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
}
-60
View File
@@ -1,60 +0,0 @@
package middleware
import (
"net/http"
"time"
"github.com/owncloud/ocis-graph/pkg/version"
)
// Cache writes required cache headers to all requests.
func Cache(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value")
w.Header().Set("Expires", "Thu, 01 Jan 1970 00:00:00 GMT")
w.Header().Set("Last-Modified", time.Now().UTC().Format(http.TimeFormat))
next.ServeHTTP(w, r)
})
}
// Cors writes required cors headers to all requests.
func Cors(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "OPTIONS" {
next.ServeHTTP(w, r)
} else {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "authorization, origin, content-type, accept")
w.Header().Set("Allow", "HEAD, GET, POST, PUT, PATCH, DELETE, OPTIONS")
w.WriteHeader(http.StatusOK)
}
})
}
// Secure writes required access headers to all requests.
func Secure(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-XSS-Protection", "1; mode=block")
if r.TLS != nil {
w.Header().Set("Strict-Transport-Security", "max-age=31536000")
}
next.ServeHTTP(w, r)
})
}
// Version writes the current version to the headers.
func Version(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-graph-VERSION", version.String)
next.ServeHTTP(w, r)
})
}
-27
View File
@@ -1,27 +0,0 @@
package middleware
import (
"github.com/owncloud/ocis-graph/pkg/config"
)
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
type Option func(o *Options)
type Options struct {
Config *config.Config
}
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}
-18
View File
@@ -1,18 +0,0 @@
package middleware
import (
"net/http"
"github.com/tomasen/realip"
)
// RealIP is a middleware that sets a http.Request RemoteAddr.
func RealIP(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if ip := realip.RealIP(r); ip != "" {
r.RemoteAddr = ip
}
next.ServeHTTP(w, r)
})
}
-12
View File
@@ -1,12 +0,0 @@
package middleware
import (
"net/http"
"github.com/ascarter/requestid"
)
// RequestID is a convenient middleware to inject a request id.
func RequestID(next http.Handler) http.Handler {
return requestid.RequestIDHandler(next)
}
-37
View File
@@ -1,37 +0,0 @@
package middleware
import (
"errors"
"net/http"
)
var (
// ErrInvalidToken is returned when the request token is invalid.
ErrInvalidToken = errors.New("invalid or missing token")
)
// Token provides a middleware to check access secured by a static token.
func Token(token string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if token == "" {
next.ServeHTTP(w, r)
return
}
header := r.Header.Get("Authorization")
if header == "" {
http.Error(w, ErrInvalidToken.Error(), http.StatusUnauthorized)
return
}
if header != "Bearer "+token {
http.Error(w, ErrInvalidToken.Error(), http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
}
+19 -5
View File
@@ -4,8 +4,20 @@ import (
"context"
"github.com/owncloud/ocis-graph/pkg/config"
"github.com/owncloud/ocis-pkg/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{}
@@ -16,19 +28,21 @@ func newOptions(opts ...Option) Options {
return opt
}
type Option func(o *Options)
type Options struct {
Context context.Context
Config *config.Config
// 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
+33 -58
View File
@@ -3,74 +3,49 @@ package debug
import (
"io"
"net/http"
"net/http/pprof"
"github.com/justinas/alice"
"github.com/micro/go-micro/util/log"
"github.com/owncloud/ocis-graph/pkg/middleware"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.opencensus.io/zpages"
"github.com/owncloud/ocis-graph/pkg/config"
"github.com/owncloud/ocis-graph/pkg/version"
"github.com/owncloud/ocis-pkg/service/debug"
)
type Handler struct {
*http.ServeMux
}
func (h *Handler) healthz(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
io.WriteString(w, http.StatusText(http.StatusOK))
}
func (h *Handler) readyz(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
io.WriteString(w, http.StatusText(http.StatusOK))
}
// Server initializes the debug service and server.
func Server(opts ...Option) (*http.Server, error) {
options := newOptions(opts...)
log.Infof("Server [debug] listening on [%s]", options.Config.Debug.Addr)
mux := http.NewServeMux()
handler := &Handler{mux}
return debug.NewService(
debug.Logger(options.Logger),
debug.Name("graph"),
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
}
handler.Handle("/metrics", alice.New(
middleware.Token(
options.Config.Debug.Token,
),
).Then(
promhttp.Handler(),
))
// 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)
handler.HandleFunc("/healthz", handler.healthz)
handler.HandleFunc("/readyz", handler.readyz)
// TODO(tboerger): check if services are up and running
if options.Config.Debug.Pprof {
handler.HandleFunc("/debug/pprof/", pprof.Index)
handler.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
handler.HandleFunc("/debug/pprof/profile", pprof.Profile)
handler.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
handler.HandleFunc("/debug/pprof/trace", pprof.Trace)
io.WriteString(w, http.StatusText(http.StatusOK))
}
}
if options.Config.Debug.Zpages {
zpages.Handle(mux, "/debug")
// 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))
}
return &http.Server{
Addr: options.Config.Debug.Addr,
Handler: alice.New(
middleware.RealIP,
middleware.RequestID,
middleware.Cache,
middleware.Cors,
middleware.Secure,
middleware.Version,
).Then(
handler,
),
}, nil
}
+28 -5
View File
@@ -4,8 +4,22 @@ import (
"context"
"github.com/owncloud/ocis-graph/pkg/config"
"github.com/owncloud/ocis-graph/pkg/metrics"
"github.com/owncloud/ocis-pkg/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
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
@@ -16,21 +30,30 @@ func newOptions(opts ...Option) Options {
return opt
}
type Option func(o *Options)
type Options struct {
Context context.Context
Config *config.Config
// 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
}
}
+41 -105
View File
@@ -1,123 +1,59 @@
package http
import (
"encoding/json"
"net/http"
"time"
"github.com/micro/go-micro/util/log"
"github.com/micro/go-micro/web"
"github.com/owncloud/ocis-graph/pkg/config"
"github.com/owncloud/ocis-graph/pkg/flagset"
"github.com/owncloud/ocis-graph/pkg/service/v0"
"github.com/owncloud/ocis-graph/pkg/version"
msgraph "github.com/yaegashi/msgraph.go/v1.0"
ldap "gopkg.in/ldap.v3"
"github.com/owncloud/ocis-pkg/middleware"
"github.com/owncloud/ocis-pkg/service/http"
)
func createUserModel(displayName string, id string) *msgraph.User {
return &msgraph.User{
DisplayName: &displayName,
GivenName: &displayName,
DirectoryObject: msgraph.DirectoryObject{
Entity: msgraph.Entity{
ID: &id,
},
},
}
// Server initializes the http service and server.
func Server(opts ...Option) (http.Service, error) {
options := newOptions(opts...)
}
func writeResponse(v interface{}, writer http.ResponseWriter) {
js, err := json.Marshal(v)
if err != nil {
//p.srv.Logger().Errorf("owncloud-plugin: error encoding response as json %s", err)
writer.WriteHeader(http.StatusInternalServerError)
return
}
writer.Header().Set("Content-Type", "application/json")
writer.WriteHeader(http.StatusOK)
writer.Write(js)
}
func handleMe(writer http.ResponseWriter, req *http.Request) {
me := createUserModel("Alice", "1234-5678-9000-000")
writeResponse(me, writer)
}
func handleUsers(writer http.ResponseWriter, req *http.Request) {
con, err := ldap.Dial("tcp", "localhost:10389")
if err != nil {
//p.srv.Logger().Errorf("owncloud-plugin: error encoding response as json %s", err)
writer.WriteHeader(http.StatusInternalServerError)
writer.Write([]byte("ldap dail failed"))
return
}
err = con.Bind("cn=admin,dc=example,dc=org", "admin")
if err != nil {
//p.srv.Logger().Errorf("owncloud-plugin: error encoding response as json %s", err)
writer.WriteHeader(http.StatusInternalServerError)
writer.Write([]byte("ldap bind failed"))
return
}
// Search for the given username
searchRequest := ldap.NewSearchRequest(
"ou=groups,dc=example,dc=org",
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
"(objectclass=*)",
[]string{"dn", "uuid", "uid", "givenName", "mail"},
nil,
service := http.NewService(
http.Logger(options.Logger),
http.Namespace("go.micro.web"),
http.Name("graph"),
http.Version(version.String),
http.Address(options.Config.HTTP.Addr),
http.Context(options.Context),
http.Flags(flagset.RootWithConfig(config.New())...),
http.Flags(flagset.ServerWithConfig(config.New())...),
)
sr, err := con.Search(searchRequest)
if err != nil {
//p.srv.Logger().Errorf("owncloud-plugin: error encoding response as json %s", err)
writer.WriteHeader(http.StatusInternalServerError)
writer.Write([]byte("ldap search failed: " + err.Error()))
return
handle := svc.NewService(
svc.Logger(options.Logger),
svc.Config(options.Config),
svc.Middleware(
middleware.RealIP,
middleware.RequestID,
middleware.Cache,
middleware.Cors,
middleware.Secure,
middleware.Version(
"graph",
version.String,
),
middleware.Logger(
options.Logger,
),
),
)
{
handle = svc.NewInstrument(handle, options.Metrics)
handle = svc.NewLogging(handle, options.Logger)
handle = svc.NewTracing(handle)
}
users := make([]*msgraph.User, len(sr.Entries))
for i := 0; i < len(sr.Entries); i++ {
users[i] = createUserModel(sr.Entries[i].DN, "1234-5678-9000-000")
}
/*
users := make([]*msgraph.User, 4)
users[0] = createUserModel("Alice", "1234-5678-9000-000")
users[1] = createUserModel("Bob", "1234-5678-9000-001")
users[2] = createUserModel("Carol", "1234-5678-9000-002")
users[3] = createUserModel("Dave", "1234-5678-9000-003")
*/
// TODO: the response has to hold a root element named value ...
writeResponse(users, writer)
}
func Server(opts ...Option) (web.Service, error) {
options := newOptions(opts...)
log.Infof("Server [http] listening on [%s]", options.Config.HTTP.Addr)
// &cli.StringFlag{
// Name: "http-addr",
// Value: "0.0.0.0:8380",
// Usage: "Address to bind http server",
// EnvVar: "GRAPH_HTTP_ADDR",
// Destination: &cfg.HTTP.Addr,
// },
service := web.NewService(
web.Name("go.micro.web.graph"),
web.Version(version.String),
web.RegisterTTL(time.Second*30),
web.RegisterInterval(time.Second*10),
web.Context(options.Context),
web.Flags(append(
flagset.RootWithConfig(config.New()),
flagset.ServerWithConfig(config.New())...,
)...),
service.Handle(
"/",
handle,
)
service.Init()
service.HandleFunc("/v1.0/me", handleMe)
service.HandleFunc("/v1.0/users", handleUsers)
return service, nil
}
+35
View File
@@ -0,0 +1,35 @@
package svc
import (
"net/http"
"github.com/owncloud/ocis-graph/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)
}
// Me implements the Service interface.
func (i instrument) Me(w http.ResponseWriter, r *http.Request) {
i.next.Me(w, r)
}
// Users implements the Service interface.
func (i instrument) Users(w http.ResponseWriter, r *http.Request) {
i.next.Users(w, r)
}
+35
View File
@@ -0,0 +1,35 @@
package svc
import (
"net/http"
"github.com/owncloud/ocis-pkg/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)
}
// Me implements the Service interface.
func (l logging) Me(w http.ResponseWriter, r *http.Request) {
l.next.Me(w, r)
}
// Users implements the Service interface.
func (l logging) Users(w http.ResponseWriter, r *http.Request) {
l.next.Users(w, r)
}
+50
View File
@@ -0,0 +1,50 @@
package svc
import (
"net/http"
"github.com/owncloud/ocis-graph/pkg/config"
"github.com/owncloud/ocis-pkg/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
}
}
+133
View File
@@ -0,0 +1,133 @@
package svc
import (
"encoding/json"
"net/http"
"github.com/go-chi/chi"
"github.com/go-chi/render"
msgraph "github.com/yaegashi/msgraph.go/v1.0"
ldap "gopkg.in/ldap.v3"
)
// Service defines the extension handlers.
type Service interface {
ServeHTTP(http.ResponseWriter, *http.Request)
Me(http.ResponseWriter, *http.Request)
Users(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 := Graph{
mux: m,
}
m.HandleFunc("/v1.0/me", svc.Me)
m.HandleFunc("/v1.0/users", svc.Users)
return svc
}
// Graph defines implements the business logic for Service.
type Graph struct {
mux *chi.Mux
}
// ServeHTTP implements the Service interface.
func (g Graph) ServeHTTP(w http.ResponseWriter, r *http.Request) {
g.mux.ServeHTTP(w, r)
}
// Me implements the Service interface.
func (g Graph) Me(w http.ResponseWriter, r *http.Request) {
me := createUserModel(
"Alice",
"1234-5678-9000-000",
)
resp, err := json.Marshal(me)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
render.Status(r, http.StatusOK)
render.JSON(w, r, resp)
}
// Users implements the Service interface.
func (g Graph) Users(w http.ResponseWriter, r *http.Request) {
con, err := ldap.Dial("tcp", "localhost:10389")
if err != nil {
// TODO: we should not give this error out to users
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := con.Bind("cn=admin,dc=example,dc=org", "admin"); err != nil {
// TODO: we should not give this error out to users
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
search := ldap.NewSearchRequest(
"ou=groups,dc=example,dc=org",
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
"(objectclass=*)",
[]string{
"dn",
"uuid",
"uid",
"givenName",
"mail",
},
nil,
)
result, err := con.Search(search)
if err != nil {
// TODO: we should not give this error out to users
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
users := make([]*msgraph.User, len(result.Entries))
for _, user := range result.Entries {
users = append(
users,
createUserModel(
user.DN,
"1234-5678-9000-000",
),
)
}
render.Status(r, http.StatusOK)
render.JSON(w, r, users)
}
func createUserModel(displayName string, id string) *msgraph.User {
return &msgraph.User{
DisplayName: &displayName,
GivenName: &displayName,
DirectoryObject: msgraph.DirectoryObject{
Entity: msgraph.Entity{
ID: &id,
},
},
}
}
+31
View File
@@ -0,0 +1,31 @@
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)
}
// Me implements the Service interface.
func (t tracing) Me(w http.ResponseWriter, r *http.Request) {
t.next.Me(w, r)
}
// Users implements the Service interface.
func (t tracing) Users(w http.ResponseWriter, r *http.Request) {
t.next.Users(w, r)
}
+1
View File
@@ -12,6 +12,7 @@ var (
Date = "00000000"
)
// Compiled returns the compile time of this service.
func Compiled() time.Time {
t, _ := time.Parse("20060102", Date)
return t