mirror of
https://github.com/1Password/onepassword-operator.git
synced 2025-10-22 23:48:05 +00:00
Webhook that injects secrets into pods
This commit is contained in:
14
vendor/github.com/suborbital/vektor/vk/README.md
generated
vendored
14
vendor/github.com/suborbital/vektor/vk/README.md
generated
vendored
@@ -1,14 +0,0 @@
|
||||
# vektor API
|
||||
|
||||
`vk` is the vektor component that allows for easy development of API servers in Go.
|
||||
|
||||
Features:
|
||||
|
||||
- HTTPS by default using LetsEncrypt
|
||||
- Easy configuration of CORS
|
||||
- Built in logging
|
||||
- Authentication plug-in point
|
||||
- Fast HTTP router built in
|
||||
|
||||
Planned:
|
||||
- Rate limiter
|
76
vendor/github.com/suborbital/vektor/vk/context.go
generated
vendored
76
vendor/github.com/suborbital/vektor/vk/context.go
generated
vendored
@@ -1,76 +0,0 @@
|
||||
package vk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/suborbital/vektor/vlog"
|
||||
)
|
||||
|
||||
// ctxKey is a type to represent a key in the Ctx context.
|
||||
type ctxKey string
|
||||
|
||||
// Ctx serves a similar purpose to context.Context, but has some typed fields
|
||||
type Ctx struct {
|
||||
Context context.Context
|
||||
Log *vlog.Logger
|
||||
Params httprouter.Params
|
||||
RespHeaders http.Header
|
||||
requestID string
|
||||
scope interface{}
|
||||
}
|
||||
|
||||
// NewCtx creates a new Ctx
|
||||
func NewCtx(log *vlog.Logger, params httprouter.Params, headers http.Header) *Ctx {
|
||||
ctx := &Ctx{
|
||||
Context: context.Background(),
|
||||
Log: log,
|
||||
Params: params,
|
||||
RespHeaders: headers,
|
||||
}
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
// Set sets a value on the Ctx's embedded Context (a la key/value store)
|
||||
func (c *Ctx) Set(key string, val interface{}) {
|
||||
realKey := ctxKey(key)
|
||||
c.Context = context.WithValue(c.Context, realKey, val)
|
||||
}
|
||||
|
||||
// Get gets a value from the Ctx's embedded Context (a la key/value store)
|
||||
func (c *Ctx) Get(key string) interface{} {
|
||||
realKey := ctxKey(key)
|
||||
val := c.Context.Value(realKey)
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
// UseScope sets an object to be the scope of the request, including setting the logger's scope
|
||||
// the scope can be retrieved later with the Scope() method
|
||||
func (c *Ctx) UseScope(scope interface{}) {
|
||||
c.Log = c.Log.CreateScoped(scope)
|
||||
|
||||
c.scope = scope
|
||||
}
|
||||
|
||||
// Scope retrieves the context's scope
|
||||
func (c *Ctx) Scope() interface{} {
|
||||
return c.scope
|
||||
}
|
||||
|
||||
// UseRequestID is a setter for the request ID
|
||||
func (c *Ctx) UseRequestID(id string) {
|
||||
c.requestID = id
|
||||
}
|
||||
|
||||
// RequestID returns the request ID of the current request, generating one if none exists.
|
||||
func (c *Ctx) RequestID() string {
|
||||
if c.requestID == "" {
|
||||
c.requestID = uuid.New().String()
|
||||
}
|
||||
|
||||
return c.requestID
|
||||
}
|
91
vendor/github.com/suborbital/vektor/vk/error.go
generated
vendored
91
vendor/github.com/suborbital/vektor/vk/error.go
generated
vendored
@@ -1,91 +0,0 @@
|
||||
package vk
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/suborbital/vektor/vlog"
|
||||
)
|
||||
|
||||
// Error is an interface representing a failed request
|
||||
type Error interface {
|
||||
Error() string // this ensures all Errors will also conform to the normal error interface
|
||||
|
||||
Message() string
|
||||
Status() int
|
||||
}
|
||||
|
||||
// ErrorResponse is a concrete implementation of Error,
|
||||
// representing a failed HTTP request
|
||||
type ErrorResponse struct {
|
||||
StatusCode int `json:"status"`
|
||||
MessageText string `json:"message"`
|
||||
}
|
||||
|
||||
// Error returns a full error string
|
||||
func (e *ErrorResponse) Error() string {
|
||||
return fmt.Sprintf("%d: %s", e.StatusCode, e.MessageText)
|
||||
}
|
||||
|
||||
// Status returns the error status code
|
||||
func (e *ErrorResponse) Status() int {
|
||||
return e.StatusCode
|
||||
}
|
||||
|
||||
// Message returns the error's message
|
||||
func (e *ErrorResponse) Message() string {
|
||||
return e.MessageText
|
||||
}
|
||||
|
||||
// Err returns an error with status and message
|
||||
func Err(status int, message string) Error {
|
||||
e := &ErrorResponse{
|
||||
StatusCode: status,
|
||||
MessageText: message,
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// E is Err for those who like terse code
|
||||
func E(status int, message string) Error {
|
||||
return Err(status, message)
|
||||
}
|
||||
|
||||
// Wrap wraps an error in vk.Error
|
||||
func Wrap(status int, err error) Error {
|
||||
return Err(status, err.Error())
|
||||
}
|
||||
|
||||
var (
|
||||
genericErrorResponseBytes = []byte("Internal Server Error")
|
||||
genericErrorResponseCode = 500
|
||||
)
|
||||
|
||||
// converts _something_ into bytes, best it can:
|
||||
// if data is Error type, returns (status, {status: status, message: message})
|
||||
// if other error, returns (500, []byte(err.Error()))
|
||||
func errorOrOtherToBytes(l *vlog.Logger, err error) (int, []byte, contentType) {
|
||||
statusCode := genericErrorResponseCode
|
||||
|
||||
// first, check if it's vk.Error interface type, and unpack it for further processing
|
||||
if e, ok := err.(Error); ok {
|
||||
statusCode = e.Status() // grab this in case anything fails
|
||||
|
||||
errResp := Err(e.Status(), e.Message()) // create a concrete instance that can be marshalled
|
||||
|
||||
errJSON, marshalErr := json.Marshal(errResp)
|
||||
if marshalErr != nil {
|
||||
// any failure results in the generic response body being used
|
||||
l.ErrorString("failed to marshal vk.Error:", marshalErr.Error(), "original error:", err.Error())
|
||||
|
||||
return statusCode, genericErrorResponseBytes, contentTypeTextPlain
|
||||
}
|
||||
|
||||
return statusCode, errJSON, contentTypeJSON
|
||||
}
|
||||
|
||||
l.Warn("redacting potential unsafe error response, original error:", err.Error())
|
||||
|
||||
return statusCode, genericErrorResponseBytes, contentTypeTextPlain
|
||||
}
|
140
vendor/github.com/suborbital/vektor/vk/group.go
generated
vendored
140
vendor/github.com/suborbital/vektor/vk/group.go
generated
vendored
@@ -1,140 +0,0 @@
|
||||
package vk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RouteGroup represents a group of routes
|
||||
type RouteGroup struct {
|
||||
prefix string
|
||||
routes []routeHandler
|
||||
middleware []Middleware
|
||||
afterware []Afterware
|
||||
}
|
||||
|
||||
type routeHandler struct {
|
||||
Method string
|
||||
Path string
|
||||
Handler HandlerFunc
|
||||
}
|
||||
|
||||
// Group creates a group of routes with a common prefix and middlewares
|
||||
func Group(prefix string) *RouteGroup {
|
||||
rg := &RouteGroup{
|
||||
prefix: prefix,
|
||||
routes: []routeHandler{},
|
||||
middleware: []Middleware{},
|
||||
afterware: []Afterware{},
|
||||
}
|
||||
|
||||
return rg
|
||||
}
|
||||
|
||||
// GET is a shortcut for server.Handle(http.MethodGet, path, handler)
|
||||
func (g *RouteGroup) GET(path string, handler HandlerFunc) {
|
||||
g.addRouteHandler(http.MethodGet, path, handler)
|
||||
}
|
||||
|
||||
// HEAD is a shortcut for server.Handle(http.MethodHead, path, handler)
|
||||
func (g *RouteGroup) HEAD(path string, handler HandlerFunc) {
|
||||
g.addRouteHandler(http.MethodHead, path, handler)
|
||||
}
|
||||
|
||||
// OPTIONS is a shortcut for server.Handle(http.MethodOptions, path, handler)
|
||||
func (g *RouteGroup) OPTIONS(path string, handler HandlerFunc) {
|
||||
g.addRouteHandler(http.MethodOptions, path, handler)
|
||||
}
|
||||
|
||||
// POST is a shortcut for server.Handle(http.MethodPost, path, handler)
|
||||
func (g *RouteGroup) POST(path string, handler HandlerFunc) {
|
||||
g.addRouteHandler(http.MethodPost, path, handler)
|
||||
}
|
||||
|
||||
// PUT is a shortcut for server.Handle(http.MethodPut, path, handler)
|
||||
func (g *RouteGroup) PUT(path string, handler HandlerFunc) {
|
||||
g.addRouteHandler(http.MethodPut, path, handler)
|
||||
}
|
||||
|
||||
// PATCH is a shortcut for server.Handle(http.MethodPatch, path, handler)
|
||||
func (g *RouteGroup) PATCH(path string, handler HandlerFunc) {
|
||||
g.addRouteHandler(http.MethodPatch, path, handler)
|
||||
}
|
||||
|
||||
// DELETE is a shortcut for server.Handle(http.MethodDelete, path, handler)
|
||||
func (g *RouteGroup) DELETE(path string, handler HandlerFunc) {
|
||||
g.addRouteHandler(http.MethodDelete, path, handler)
|
||||
}
|
||||
|
||||
// Handle adds a route to be handled
|
||||
func (g *RouteGroup) Handle(method, path string, handler HandlerFunc) {
|
||||
g.addRouteHandler(method, path, handler)
|
||||
}
|
||||
|
||||
// AddGroup adds a group of routes to this group as a subgroup.
|
||||
// the subgroup's prefix is added to all of the routes it contains,
|
||||
// with the resulting path being "/group.prefix/subgroup.prefix/route/path/here"
|
||||
func (g *RouteGroup) AddGroup(group *RouteGroup) {
|
||||
g.routes = append(g.routes, group.routeHandlers()...)
|
||||
}
|
||||
|
||||
// Before adds middleware to the group, which are applied to every handler in the group (called before the handler)
|
||||
func (g *RouteGroup) Before(middleware ...Middleware) *RouteGroup {
|
||||
g.middleware = append(g.middleware, middleware...)
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
// After adds afterware to the group, which are applied to every handler in the group (called after the handler)
|
||||
func (g *RouteGroup) After(afterware ...Afterware) *RouteGroup {
|
||||
g.afterware = append(g.afterware, afterware...)
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
// routeHandlers computes the "full" path for each handler, and creates
|
||||
// a HandlerFunc that chains together the group's middlewares
|
||||
// before calling the inner HandlerFunc. It can be called 'recursively'
|
||||
// since groups can be added to groups
|
||||
func (g *RouteGroup) routeHandlers() []routeHandler {
|
||||
routes := make([]routeHandler, len(g.routes))
|
||||
|
||||
for i, r := range g.routes {
|
||||
fullPath := fmt.Sprintf("%s%s", ensureLeadingSlash(g.prefix), ensureLeadingSlash(r.Path))
|
||||
augR := routeHandler{
|
||||
Method: r.Method,
|
||||
Path: fullPath,
|
||||
Handler: augmentHandler(r.Handler, g.middleware, g.afterware),
|
||||
}
|
||||
|
||||
routes[i] = augR
|
||||
}
|
||||
|
||||
return routes
|
||||
}
|
||||
|
||||
func (g *RouteGroup) addRouteHandler(method string, path string, handler HandlerFunc) {
|
||||
rh := routeHandler{
|
||||
Method: method,
|
||||
Path: path,
|
||||
Handler: handler,
|
||||
}
|
||||
|
||||
g.routes = append(g.routes, rh)
|
||||
}
|
||||
|
||||
func (g *RouteGroup) routePrefix() string {
|
||||
return g.prefix
|
||||
}
|
||||
|
||||
func ensureLeadingSlash(path string) string {
|
||||
if path == "" {
|
||||
// handle the "root group" case
|
||||
return ""
|
||||
} else if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
80
vendor/github.com/suborbital/vektor/vk/middleware.go
generated
vendored
80
vendor/github.com/suborbital/vektor/vk/middleware.go
generated
vendored
@@ -1,80 +0,0 @@
|
||||
package vk
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Middleware represents a handler that runs on a request before reaching its handler
|
||||
type Middleware func(*http.Request, *Ctx) error
|
||||
|
||||
// Afterware represents a handler that runs on a request after the handler has dealt with the request
|
||||
type Afterware func(*http.Request, *Ctx)
|
||||
|
||||
// ContentTypeMiddleware allows the content-type to be set
|
||||
func ContentTypeMiddleware(contentType string) Middleware {
|
||||
return func(r *http.Request, ctx *Ctx) error {
|
||||
ctx.RespHeaders.Set(contentTypeHeaderKey, contentType)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// CORSMiddleware enables CORS with the given domain for a route
|
||||
// pass "*" to allow all domains, or empty string to allow none
|
||||
func CORSMiddleware(domain string) Middleware {
|
||||
return func(r *http.Request, ctx *Ctx) error {
|
||||
enableCors(ctx, domain)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// CORSHandler enables CORS for a route
|
||||
// pass "*" to allow all domains, or empty string to allow none
|
||||
func CORSHandler(domain string) HandlerFunc {
|
||||
return func(r *http.Request, ctx *Ctx) (interface{}, error) {
|
||||
enableCors(ctx, domain)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func enableCors(ctx *Ctx, domain string) {
|
||||
if domain != "" {
|
||||
ctx.RespHeaders.Set("Access-Control-Allow-Origin", domain)
|
||||
ctx.RespHeaders.Set("X-Requested-With", "XMLHttpRequest")
|
||||
ctx.RespHeaders.Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, Authorization, cache-control")
|
||||
}
|
||||
}
|
||||
|
||||
func loggerMiddleware() Middleware {
|
||||
return func(r *http.Request, ctx *Ctx) error {
|
||||
ctx.Log.Info(r.Method, r.URL.String())
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// generate a HandlerFunc that passes the request through a set of Middleware first and Afterware after
|
||||
func augmentHandler(inner HandlerFunc, middleware []Middleware, afterware []Afterware) HandlerFunc {
|
||||
return func(r *http.Request, ctx *Ctx) (interface{}, error) {
|
||||
defer func() {
|
||||
// run the afterware (which cannot affect the response)
|
||||
// even if something in the request chain fails
|
||||
for _, a := range afterware {
|
||||
a(r, ctx)
|
||||
}
|
||||
}()
|
||||
|
||||
// run the middleware (which can error to stop progression)
|
||||
for _, m := range middleware {
|
||||
if err := m(r, ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := inner(r, ctx)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
}
|
73
vendor/github.com/suborbital/vektor/vk/optionmodifiers.go
generated
vendored
73
vendor/github.com/suborbital/vektor/vk/optionmodifiers.go
generated
vendored
@@ -1,73 +0,0 @@
|
||||
package vk
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
|
||||
"github.com/suborbital/vektor/vlog"
|
||||
)
|
||||
|
||||
// OptionsModifier takes an options struct and returns a modified Options struct
|
||||
type OptionsModifier func(*Options)
|
||||
|
||||
// UseDomain sets the server to use a particular domain for TLS
|
||||
func UseDomain(domain string) OptionsModifier {
|
||||
return func(o *Options) {
|
||||
o.Domain = domain
|
||||
}
|
||||
}
|
||||
|
||||
// UseTLSConfig sets a TLS config that will be used for HTTPS
|
||||
// This will take precedence over the Domain option in all cases
|
||||
func UseTLSConfig(config *tls.Config) OptionsModifier {
|
||||
return func(o *Options) {
|
||||
o.TLSConfig = config
|
||||
}
|
||||
}
|
||||
|
||||
// UseTLSPort sets the HTTPS port to be used:
|
||||
func UseTLSPort(port int) OptionsModifier {
|
||||
return func(o *Options) {
|
||||
o.TLSPort = port
|
||||
}
|
||||
}
|
||||
|
||||
// UseHTTPPort sets the HTTP port to be used:
|
||||
// If domain is set, HTTP port will be used for LetsEncrypt challenge server
|
||||
// If domain is NOT set, this option will put VK in insecure HTTP mode
|
||||
func UseHTTPPort(port int) OptionsModifier {
|
||||
return func(o *Options) {
|
||||
o.HTTPPort = port
|
||||
}
|
||||
}
|
||||
|
||||
// UseLogger allows a custom logger to be used
|
||||
func UseLogger(logger *vlog.Logger) OptionsModifier {
|
||||
return func(o *Options) {
|
||||
o.Logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
// UseAppName allows an app name to be set (for vanity only, really....)
|
||||
func UseAppName(name string) OptionsModifier {
|
||||
return func(o *Options) {
|
||||
o.AppName = name
|
||||
}
|
||||
}
|
||||
|
||||
// UseEnvPrefix uses the provided env prefix (default VK) when looking up other options such as `VK_HTTP_PORT`
|
||||
func UseEnvPrefix(prefix string) OptionsModifier {
|
||||
return func(o *Options) {
|
||||
o.EnvPrefix = prefix
|
||||
}
|
||||
}
|
||||
|
||||
// UseInspector sets a function that will be allowed to inspect every HTTP request
|
||||
// before it reaches VK's internal router, but cannot modify said request or affect
|
||||
// the handling of said request in any way. Use at your own risk, as it may introduce
|
||||
// performance issues if not used correctly.
|
||||
func UseInspector(isp func(http.Request)) OptionsModifier {
|
||||
return func(o *Options) {
|
||||
o.PreRouterInspector = isp
|
||||
}
|
||||
}
|
95
vendor/github.com/suborbital/vektor/vk/options.go
generated
vendored
95
vendor/github.com/suborbital/vektor/vk/options.go
generated
vendored
@@ -1,95 +0,0 @@
|
||||
package vk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sethvargo/go-envconfig"
|
||||
"github.com/suborbital/vektor/vlog"
|
||||
)
|
||||
|
||||
// Options are the available options for Server
|
||||
type Options struct {
|
||||
AppName string `env:"_APP_NAME"`
|
||||
Domain string `env:"_DOMAIN"`
|
||||
HTTPPort int `env:"_HTTP_PORT"`
|
||||
TLSPort int `env:"_TLS_PORT"`
|
||||
TLSConfig *tls.Config `env:"-"`
|
||||
EnvPrefix string `env:"-"`
|
||||
Logger *vlog.Logger `env:"-"`
|
||||
|
||||
PreRouterInspector func(http.Request) `env:"-"`
|
||||
}
|
||||
|
||||
func newOptsWithModifiers(mods ...OptionsModifier) *Options {
|
||||
options := &Options{}
|
||||
// loop through the provided options and apply the
|
||||
// modifier function to the options object
|
||||
for _, mod := range mods {
|
||||
mod(options)
|
||||
}
|
||||
|
||||
envPrefix := defaultEnvPrefix
|
||||
if options.EnvPrefix != "" {
|
||||
envPrefix = options.EnvPrefix
|
||||
}
|
||||
|
||||
options.finalize(envPrefix)
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
// ShouldUseTLS returns true if domain is set and/or TLS is configured
|
||||
func (o *Options) ShouldUseTLS() bool {
|
||||
return o.Domain != "" || o.TLSConfig != nil
|
||||
}
|
||||
|
||||
// HTTPPortSet returns true if the HTTP port is set
|
||||
func (o *Options) HTTPPortSet() bool {
|
||||
return o.HTTPPort != 0
|
||||
}
|
||||
|
||||
// ShouldUseHTTP returns true if insecure HTTP should be used
|
||||
func (o *Options) ShouldUseHTTP() bool {
|
||||
return !o.ShouldUseTLS() && o.HTTPPortSet()
|
||||
}
|
||||
|
||||
// finalize "locks in" the options by overriding any existing options with the version from the environment, and setting the default logger if needed
|
||||
func (o *Options) finalize(prefix string) {
|
||||
if o.Logger == nil {
|
||||
o.Logger = vlog.Default(vlog.EnvPrefix(prefix))
|
||||
}
|
||||
|
||||
// if no inspector was set, create an empty one
|
||||
if o.PreRouterInspector == nil {
|
||||
o.PreRouterInspector = func(_ http.Request) {}
|
||||
}
|
||||
|
||||
envOpts := Options{}
|
||||
if err := envconfig.ProcessWith(context.Background(), &envOpts, envconfig.PrefixLookuper(prefix, envconfig.OsLookuper())); err != nil {
|
||||
o.Logger.Error(errors.Wrap(err, "[vk] failed to ProcessWith environment config"))
|
||||
return
|
||||
}
|
||||
|
||||
o.replaceFieldsIfNeeded(&envOpts)
|
||||
}
|
||||
|
||||
func (o *Options) replaceFieldsIfNeeded(replacement *Options) {
|
||||
if replacement.AppName != "" {
|
||||
o.AppName = replacement.AppName
|
||||
}
|
||||
|
||||
if replacement.Domain != "" {
|
||||
o.Domain = replacement.Domain
|
||||
}
|
||||
|
||||
if replacement.HTTPPort != 0 {
|
||||
o.HTTPPort = replacement.HTTPPort
|
||||
}
|
||||
|
||||
if replacement.TLSPort != 0 {
|
||||
o.TLSPort = replacement.TLSPort
|
||||
}
|
||||
}
|
77
vendor/github.com/suborbital/vektor/vk/response.go
generated
vendored
77
vendor/github.com/suborbital/vektor/vk/response.go
generated
vendored
@@ -1,77 +0,0 @@
|
||||
package vk
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/suborbital/vektor/vlog"
|
||||
)
|
||||
|
||||
// Response represents a non-error HTTP response
|
||||
type Response struct {
|
||||
status int
|
||||
body interface{}
|
||||
}
|
||||
|
||||
// Respond returns a filled-in response
|
||||
func Respond(status int, body interface{}) Response {
|
||||
r := Response{
|
||||
status: status,
|
||||
body: body,
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// R is `Respond` for those who prefer terse code
|
||||
func R(status int, body interface{}) Response {
|
||||
return Respond(status, body)
|
||||
}
|
||||
|
||||
// TODO: add convenience helpers for status codes
|
||||
|
||||
const (
|
||||
contentTypeJSON contentType = "application/json"
|
||||
contentTypeTextPlain contentType = "text/plain"
|
||||
contentTypeOctetStream contentType = "application/octet-stream"
|
||||
)
|
||||
|
||||
// converts _something_ into bytes, best it can:
|
||||
// if data is Response type, returns (status, body processed as below)
|
||||
// if bytes, return (200, bytes)
|
||||
// if string, return (200, []byte(string))
|
||||
// if struct, return (200, json(struct))
|
||||
// otherwise, return (500, nil)
|
||||
func responseOrOtherToBytes(l *vlog.Logger, data interface{}) (int, []byte, contentType) {
|
||||
if data == nil {
|
||||
return http.StatusNoContent, []byte{}, contentTypeTextPlain
|
||||
}
|
||||
|
||||
statusCode := http.StatusOK
|
||||
realData := data
|
||||
|
||||
// first, check if it's response type, and unpack it for further processing
|
||||
if r, ok := data.(Response); ok {
|
||||
statusCode = r.status
|
||||
realData = r.body
|
||||
}
|
||||
|
||||
// if data is []byte or string, return it as-is
|
||||
if b, ok := realData.([]byte); ok {
|
||||
return statusCode, b, contentTypeOctetStream
|
||||
} else if s, ok := realData.(string); ok {
|
||||
return statusCode, []byte(s), contentTypeTextPlain
|
||||
}
|
||||
|
||||
// otherwise, assume it's a struct of some kind,
|
||||
// so JSON marshal it and return it
|
||||
json, err := json.Marshal(realData)
|
||||
if err != nil {
|
||||
l.Error(errors.Wrap(err, "failed to Marshal response struct"))
|
||||
|
||||
return genericErrorResponseCode, []byte(genericErrorResponseBytes), contentTypeTextPlain
|
||||
}
|
||||
|
||||
return statusCode, json, contentTypeJSON
|
||||
}
|
144
vendor/github.com/suborbital/vektor/vk/router.go
generated
vendored
144
vendor/github.com/suborbital/vektor/vk/router.go
generated
vendored
@@ -1,144 +0,0 @@
|
||||
package vk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/suborbital/vektor/vlog"
|
||||
)
|
||||
|
||||
const contentTypeHeaderKey = "Content-Type"
|
||||
|
||||
// used internally to convey content types
|
||||
type contentType string
|
||||
|
||||
// HandlerFunc is the vk version of http.HandlerFunc
|
||||
// instead of exposing the ResponseWriter, the function instead returns
|
||||
// an object and an error, which are handled as described in `With` below
|
||||
type HandlerFunc func(*http.Request, *Ctx) (interface{}, error)
|
||||
|
||||
// Router handles the responses on behalf of the server
|
||||
type Router struct {
|
||||
*RouteGroup // the "root" RouteGroup that is mounted at server start
|
||||
hrouter *httprouter.Router // the internal 'actual' router
|
||||
finalizeOnce sync.Once // ensure that the root only gets mounted once
|
||||
|
||||
log *vlog.Logger
|
||||
}
|
||||
|
||||
type defaultScope struct {
|
||||
RequestID string `json:"request_id"`
|
||||
}
|
||||
|
||||
// NewRouter creates a new Router
|
||||
func NewRouter(logger *vlog.Logger) *Router {
|
||||
// add the logger middleware
|
||||
middleware := []Middleware{loggerMiddleware()}
|
||||
|
||||
r := &Router{
|
||||
RouteGroup: Group("").Before(middleware...),
|
||||
hrouter: httprouter.New(),
|
||||
finalizeOnce: sync.Once{},
|
||||
log: logger,
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// HandleHTTP handles a classic Go HTTP handlerFunc
|
||||
func (rt *Router) HandleHTTP(method, path string, handler http.HandlerFunc) {
|
||||
rt.hrouter.Handle(method, path, func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
handler(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// Finalize mounts the root group to prepare the Router to handle requests
|
||||
func (rt *Router) Finalize() {
|
||||
rt.finalizeOnce.Do(func() {
|
||||
rt.mountGroup(rt.RouteGroup)
|
||||
})
|
||||
}
|
||||
|
||||
//ServeHTTP serves HTTP requests
|
||||
func (rt *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// check to see if the router has a handler for this path
|
||||
handler, params, _ := rt.hrouter.Lookup(r.Method, r.URL.Path)
|
||||
|
||||
if handler != nil {
|
||||
handler(w, r, params)
|
||||
} else {
|
||||
rt.log.Debug("not handled:", r.Method, r.URL.String())
|
||||
|
||||
// let httprouter handle the fallthrough cases
|
||||
rt.hrouter.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// mountGroup adds a group of handlers to the httprouter
|
||||
func (rt *Router) mountGroup(group *RouteGroup) {
|
||||
for _, r := range group.routeHandlers() {
|
||||
rt.log.Debug("mounting route", r.Method, r.Path)
|
||||
rt.hrouter.Handle(r.Method, r.Path, rt.handleWrap(r.Handler))
|
||||
}
|
||||
}
|
||||
|
||||
// handleWrap returns an httprouter.Handle that uses the `inner` vk.HandleFunc to handle the request
|
||||
//
|
||||
// inner returns a body and an error;
|
||||
// the body can can be:
|
||||
// - a vk.Response object (status and body are written to w)
|
||||
// - []byte (written directly to w, status 200)
|
||||
// - a struct (marshalled to JSON and written to w, status 200)
|
||||
//
|
||||
// the error can be:
|
||||
// - a vk.Error type (status and message are written to w)
|
||||
// - any other error object (status 500 and error.Error() are written to w)
|
||||
//
|
||||
func (rt *Router) handleWrap(inner HandlerFunc) httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
var status int
|
||||
var body []byte
|
||||
var detectedCType contentType
|
||||
|
||||
// create a context handleWrap the configured logger
|
||||
// (and use the ctx.Log for all remaining logging
|
||||
// in case a scope was set on it)
|
||||
ctx := NewCtx(rt.log, params, w.Header())
|
||||
ctx.UseScope(defaultScope{ctx.RequestID()})
|
||||
|
||||
resp, err := inner(r, ctx)
|
||||
if err != nil {
|
||||
status, body, detectedCType = errorOrOtherToBytes(ctx.Log, err)
|
||||
} else {
|
||||
status, body, detectedCType = responseOrOtherToBytes(ctx.Log, resp)
|
||||
}
|
||||
|
||||
// check if anything in the handler chain set the content type
|
||||
// header, and only use the auto-detected value if it wasn't
|
||||
headerCType := w.Header().Get(contentTypeHeaderKey)
|
||||
shouldSetCType := headerCType == ""
|
||||
|
||||
ctx.Log.Debug("post-handler contenttype:", string(headerCType))
|
||||
|
||||
// if no contentType was set in the middleware chain,
|
||||
// then set it here based on the type detected
|
||||
if shouldSetCType {
|
||||
ctx.Log.Debug("setting auto-detected contenttype:", string(detectedCType))
|
||||
w.Header().Set(contentTypeHeaderKey, string(detectedCType))
|
||||
}
|
||||
|
||||
w.WriteHeader(status)
|
||||
w.Write(body)
|
||||
|
||||
ctx.Log.Info(r.Method, r.URL.String(), fmt.Sprintf("completed (%d: %s)", status, http.StatusText(status)))
|
||||
}
|
||||
}
|
||||
|
||||
// canHandle returns true if there's a registered handler that can
|
||||
// handle the method and path provided or not
|
||||
func (rt *Router) canHandle(method, path string) bool {
|
||||
handler, _, _ := rt.hrouter.Lookup(method, path)
|
||||
return handler != nil
|
||||
}
|
289
vendor/github.com/suborbital/vektor/vk/server.go
generated
vendored
289
vendor/github.com/suborbital/vektor/vk/server.go
generated
vendored
@@ -1,289 +0,0 @@
|
||||
package vk
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
)
|
||||
|
||||
const defaultEnvPrefix = "VK"
|
||||
|
||||
// Server represents a vektor API server
|
||||
type Server struct {
|
||||
router *Router
|
||||
lock sync.RWMutex
|
||||
started atomic.Value
|
||||
|
||||
server *http.Server
|
||||
options *Options
|
||||
}
|
||||
|
||||
// New creates a new vektor API server
|
||||
func New(opts ...OptionsModifier) *Server {
|
||||
options := newOptsWithModifiers(opts...)
|
||||
|
||||
router := NewRouter(options.Logger)
|
||||
|
||||
s := &Server{
|
||||
router: router,
|
||||
lock: sync.RWMutex{},
|
||||
started: atomic.Value{},
|
||||
options: options,
|
||||
}
|
||||
|
||||
s.started.Store(false)
|
||||
|
||||
// yes this creates a circular reference,
|
||||
// but the VK server and HTTP server are
|
||||
// extremely tightly wound together so
|
||||
// we have to make this compromise
|
||||
s.server = createGoServer(options, s)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Start starts the server listening
|
||||
func (s *Server) Start() error {
|
||||
if s.started.Load().(bool) {
|
||||
err := errors.New("server already started")
|
||||
s.options.Logger.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// lock the router modifiers (GET, POST etc.)
|
||||
s.started.Store(true)
|
||||
|
||||
// mount the root set of routes before starting
|
||||
s.router.Finalize()
|
||||
|
||||
if s.options.AppName != "" {
|
||||
s.options.Logger.Info("starting", s.options.AppName, "...")
|
||||
}
|
||||
|
||||
s.options.Logger.Info("serving on", s.server.Addr)
|
||||
|
||||
if !s.options.HTTPPortSet() && !s.options.ShouldUseTLS() {
|
||||
s.options.Logger.ErrorString("domain and HTTP port options are both unset, server will start up but fail to acquire a certificate. reconfigure and restart")
|
||||
} else if s.options.ShouldUseHTTP() {
|
||||
return s.server.ListenAndServe()
|
||||
}
|
||||
|
||||
return s.server.ListenAndServeTLS("", "")
|
||||
}
|
||||
|
||||
// TestStart "starts" the server for automated testing with vtest
|
||||
func (s *Server) TestStart() error {
|
||||
if s.started.Load().(bool) {
|
||||
err := errors.New("server already started")
|
||||
s.options.Logger.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// lock the router modifiers (GET, POST etc.)
|
||||
s.started.Store(true)
|
||||
|
||||
// mount the root set of routes before starting
|
||||
s.router.Finalize()
|
||||
|
||||
if s.options.AppName != "" {
|
||||
s.options.Logger.Info("starting", s.options.AppName, "in Test Mode...")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServeHTTP serves HTTP requests using the internal router while allowing
|
||||
// said router to be swapped out underneath at any time in a thread-safe way
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// run the inspector with a dereferenced pointer
|
||||
// so that it can view but not change said request
|
||||
//
|
||||
// we intentionally run this before the lock as it's
|
||||
// possible the inspector may trigger a router-swap
|
||||
// and that would cause a nasty deadlock
|
||||
s.options.PreRouterInspector(*r)
|
||||
|
||||
// now lock to ensure the router isn't being swapped
|
||||
// out from underneath us while we're serving this req
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
s.router.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// SwapRouter allows swapping VK's router out in realtime while
|
||||
// continuing to serve requests in the background
|
||||
func (s *Server) SwapRouter(router *Router) {
|
||||
router.Finalize()
|
||||
|
||||
// lock after Finalizing the router so
|
||||
// the lock is released as quickly as possible
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.router = router
|
||||
}
|
||||
|
||||
// CanHandle returns true if the server can handle a given method and path
|
||||
func (s *Server) CanHandle(method, path string) bool {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
return s.router.canHandle(method, path)
|
||||
}
|
||||
|
||||
// GET is a shortcut for router.Handle(http.MethodGet, path, handle)
|
||||
func (s *Server) GET(path string, handler HandlerFunc) {
|
||||
if s.started.Load().(bool) {
|
||||
return
|
||||
}
|
||||
|
||||
s.router.GET(path, handler)
|
||||
}
|
||||
|
||||
// HEAD is a shortcut for router.Handle(http.MethodHead, path, handle)
|
||||
func (s *Server) HEAD(path string, handler HandlerFunc) {
|
||||
if s.started.Load().(bool) {
|
||||
return
|
||||
}
|
||||
|
||||
s.router.HEAD(path, handler)
|
||||
}
|
||||
|
||||
// OPTIONS is a shortcut for router.Handle(http.MethodOptions, path, handle)
|
||||
func (s *Server) OPTIONS(path string, handler HandlerFunc) {
|
||||
if s.started.Load().(bool) {
|
||||
return
|
||||
}
|
||||
|
||||
s.router.OPTIONS(path, handler)
|
||||
}
|
||||
|
||||
// POST is a shortcut for router.Handle(http.MethodPost, path, handle)
|
||||
func (s *Server) POST(path string, handler HandlerFunc) {
|
||||
if s.started.Load().(bool) {
|
||||
return
|
||||
}
|
||||
|
||||
s.router.POST(path, handler)
|
||||
}
|
||||
|
||||
// PUT is a shortcut for router.Handle(http.MethodPut, path, handle)
|
||||
func (s *Server) PUT(path string, handler HandlerFunc) {
|
||||
if s.started.Load().(bool) {
|
||||
return
|
||||
}
|
||||
|
||||
s.router.PUT(path, handler)
|
||||
}
|
||||
|
||||
// PATCH is a shortcut for router.Handle(http.MethodPatch, path, handle)
|
||||
func (s *Server) PATCH(path string, handler HandlerFunc) {
|
||||
if s.started.Load().(bool) {
|
||||
return
|
||||
}
|
||||
|
||||
s.router.PATCH(path, handler)
|
||||
}
|
||||
|
||||
// DELETE is a shortcut for router.Handle(http.MethodDelete, path, handle)
|
||||
func (s *Server) DELETE(path string, handler HandlerFunc) {
|
||||
if s.started.Load().(bool) {
|
||||
return
|
||||
}
|
||||
|
||||
s.router.DELETE(path, handler)
|
||||
}
|
||||
|
||||
// Handle adds a route to be handled
|
||||
func (s *Server) Handle(method, path string, handler HandlerFunc) {
|
||||
if s.started.Load().(bool) {
|
||||
return
|
||||
}
|
||||
|
||||
s.router.Handle(method, path, handler)
|
||||
}
|
||||
|
||||
// AddGroup adds a RouteGroup to be handled
|
||||
func (s *Server) AddGroup(group *RouteGroup) {
|
||||
if s.started.Load().(bool) {
|
||||
return
|
||||
}
|
||||
|
||||
s.router.AddGroup(group)
|
||||
}
|
||||
|
||||
// HandleHTTP allows vk to handle a standard http.HandlerFunc
|
||||
func (s *Server) HandleHTTP(method, path string, handler http.HandlerFunc) {
|
||||
if s.started.Load().(bool) {
|
||||
return
|
||||
}
|
||||
|
||||
s.router.HandleHTTP(method, path, handler)
|
||||
}
|
||||
|
||||
func createGoServer(options *Options, handler http.Handler) *http.Server {
|
||||
if useHTTP := options.ShouldUseHTTP(); useHTTP {
|
||||
return goHTTPServerWithPort(options, handler)
|
||||
}
|
||||
|
||||
return goTLSServerWithDomain(options, handler)
|
||||
}
|
||||
|
||||
func goTLSServerWithDomain(options *Options, handler http.Handler) *http.Server {
|
||||
if options.TLSConfig != nil {
|
||||
options.Logger.Info("configured for HTTPS with custom configuration")
|
||||
} else if options.Domain != "" {
|
||||
options.Logger.Info("configured for HTTPS using domain", options.Domain)
|
||||
}
|
||||
|
||||
tlsConfig := options.TLSConfig
|
||||
|
||||
if tlsConfig == nil {
|
||||
m := &autocert.Manager{
|
||||
Cache: autocert.DirCache("~/.autocert"),
|
||||
Prompt: autocert.AcceptTOS,
|
||||
HostPolicy: autocert.HostWhitelist(options.Domain),
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf(":%d", options.HTTPPort)
|
||||
if options.HTTPPort == 0 {
|
||||
addr = ":8080"
|
||||
}
|
||||
|
||||
options.Logger.Info("serving TLS challenges on", addr)
|
||||
|
||||
go http.ListenAndServe(addr, m.HTTPHandler(nil))
|
||||
|
||||
tlsConfig = &tls.Config{GetCertificate: m.GetCertificate}
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf(":%d", options.TLSPort)
|
||||
if options.TLSPort == 0 {
|
||||
addr = ":443"
|
||||
}
|
||||
|
||||
s := &http.Server{
|
||||
Addr: addr,
|
||||
TLSConfig: tlsConfig,
|
||||
Handler: handler,
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func goHTTPServerWithPort(options *Options, handler http.Handler) *http.Server {
|
||||
options.Logger.Warn("configured to use HTTP with no TLS")
|
||||
|
||||
s := &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", options.HTTPPort),
|
||||
Handler: handler,
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
Reference in New Issue
Block a user