mirror of
https://github.com/niusmallnan/steve.git
synced 2025-04-28 11:16:29 +00:00
Shuffle around code and use rancher/apiserver
This commit is contained in:
parent
2543926f99
commit
d1a7dbb0b9
5
main.go
5
main.go
@ -35,6 +35,9 @@ func main() {
|
||||
func run(_ *cli.Context) error {
|
||||
ctx := signals.SetupSignalHandler(context.Background())
|
||||
debugconfig.MustSetupDebug()
|
||||
s := config.MustServer(ctx)
|
||||
s, err := config.ToServer(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.ListenAndServe(ctx, config.HTTPSListenPort, config.HTTPListenPort, nil)
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
package accesscontrol
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/server"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/apiserver/pkg/server"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
)
|
||||
|
||||
type AccessControl struct {
|
||||
@ -16,9 +15,11 @@ func NewAccessControl() *AccessControl {
|
||||
}
|
||||
|
||||
func (a *AccessControl) CanWatch(apiOp *types.APIRequest, schema *types.APISchema) error {
|
||||
if attributes.GVK(schema).Kind != "" {
|
||||
access := GetAccessListMap(schema)
|
||||
if _, ok := access["watch"]; ok {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("watch not allowed")
|
||||
}
|
||||
return a.SchemaBasedAccess.CanWatch(apiOp, schema)
|
||||
}
|
||||
|
@ -3,8 +3,8 @@ package accesscontrol
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
@ -3,7 +3,7 @@ package attributes
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/wrangler/pkg/data/convert"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
@ -5,8 +5,8 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
@ -5,9 +5,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/schema"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/merr"
|
||||
"github.com/rancher/wrangler/pkg/summary/client"
|
||||
"github.com/rancher/wrangler/pkg/summary/informer"
|
||||
|
@ -6,11 +6,11 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/resources/common"
|
||||
schema2 "github.com/rancher/steve/pkg/schema"
|
||||
"github.com/rancher/steve/pkg/schema/converter"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/steve/pkg/server/resources/common"
|
||||
apiextcontrollerv1beta1 "github.com/rancher/wrangler-api/pkg/generated/controllers/apiextensions.k8s.io/v1beta1"
|
||||
v1 "github.com/rancher/wrangler-api/pkg/generated/controllers/apiregistration.k8s.io/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -7,8 +7,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rancher/steve/pkg/responsewriter"
|
||||
"github.com/rancher/steve/pkg/schemaserver/parse"
|
||||
"github.com/rancher/apiserver/pkg/middleware"
|
||||
"github.com/rancher/apiserver/pkg/parse"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@ -28,15 +28,17 @@ func content(uiSetting func() string) http.Handler {
|
||||
}
|
||||
|
||||
func Route(next http.Handler, uiSetting func() string) http.Handler {
|
||||
uiContent := responsewriter.NewMiddlewareChain(responsewriter.Gzip,
|
||||
responsewriter.DenyFrameOptions,
|
||||
responsewriter.CacheMiddleware("json", "js", "css")).Handler(content(uiSetting))
|
||||
uiContent := middleware.NewMiddlewareChain(middleware.Gzip,
|
||||
middleware.DenyFrameOptions,
|
||||
middleware.CacheMiddleware("json", "js", "css")).Handler(content(uiSetting))
|
||||
|
||||
root := mux.NewRouter()
|
||||
root.UseEncodedPath()
|
||||
root.Path("/").HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
http.Redirect(rw, req, "/dashboard/", http.StatusFound)
|
||||
})
|
||||
root.Path("/dashboard").HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Add("Location", "/dashboard/")
|
||||
rw.WriteHeader(http.StatusFound)
|
||||
http.Redirect(rw, req, "/dashboard/", http.StatusFound)
|
||||
})
|
||||
root.PathPrefix("/dashboard/assets").Handler(uiContent)
|
||||
root.PathPrefix("/dashboard/translations").Handler(uiContent)
|
||||
|
@ -5,8 +5,8 @@ import (
|
||||
|
||||
"github.com/rancher/steve/pkg/schema"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/store/empty"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/apiserver/pkg/store/empty"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/discovery"
|
||||
)
|
@ -5,12 +5,12 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/store/empty"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/clustercache"
|
||||
"github.com/rancher/steve/pkg/schemaserver/store/empty"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/steve/pkg/server/store/proxy"
|
||||
"github.com/rancher/steve/pkg/server/store/switchschema"
|
||||
"github.com/rancher/steve/pkg/server/store/switchstore"
|
||||
"github.com/rancher/steve/pkg/stores/proxy"
|
||||
"github.com/rancher/steve/pkg/stores/switchschema"
|
||||
"github.com/rancher/steve/pkg/stores/switchstore"
|
||||
"github.com/rancher/wrangler/pkg/data"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
@ -7,7 +7,7 @@ import (
|
||||
"net/http/httputil"
|
||||
"time"
|
||||
|
||||
"github.com/rancher/steve/pkg/server/store/proxy"
|
||||
"github.com/rancher/steve/pkg/stores/proxy"
|
||||
"github.com/rancher/wrangler/pkg/condition"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
"github.com/sirupsen/logrus"
|
@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/ratelimit"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
@ -3,10 +3,10 @@ package common
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/accesscontrol"
|
||||
"github.com/rancher/steve/pkg/schema"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/steve/pkg/server/store/proxy"
|
||||
"github.com/rancher/steve/pkg/stores/proxy"
|
||||
"github.com/rancher/wrangler/pkg/data"
|
||||
"github.com/rancher/wrangler/pkg/summary"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
@ -5,11 +5,11 @@ import (
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/store/empty"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/accesscontrol"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/clustercache"
|
||||
"github.com/rancher/steve/pkg/schemaserver/store/empty"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/summary"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
@ -1,9 +1,9 @@
|
||||
package helm
|
||||
|
||||
import (
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/schema/converter"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
@ -3,8 +3,8 @@ package helm
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/steve/pkg/server/store/partition"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/stores/partition"
|
||||
)
|
||||
|
||||
func Register(schemas *types.APISchemas) {
|
@ -3,11 +3,11 @@ package helm
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/store/empty"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/steve/pkg/server/store/partition"
|
||||
"github.com/rancher/steve/pkg/server/store/selector"
|
||||
"github.com/rancher/steve/pkg/server/store/switchschema"
|
||||
"github.com/rancher/apiserver/pkg/store/empty"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/stores/partition"
|
||||
"github.com/rancher/steve/pkg/stores/selector"
|
||||
"github.com/rancher/steve/pkg/stores/switchschema"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
@ -3,27 +3,27 @@ package resources
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/store/apiroot"
|
||||
"github.com/rancher/apiserver/pkg/subscribe"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/accesscontrol"
|
||||
"github.com/rancher/steve/pkg/client"
|
||||
"github.com/rancher/steve/pkg/clustercache"
|
||||
"github.com/rancher/steve/pkg/resources/apigroups"
|
||||
"github.com/rancher/steve/pkg/resources/clusters"
|
||||
"github.com/rancher/steve/pkg/resources/common"
|
||||
"github.com/rancher/steve/pkg/resources/counts"
|
||||
"github.com/rancher/steve/pkg/resources/helm"
|
||||
"github.com/rancher/steve/pkg/resources/userpreferences"
|
||||
"github.com/rancher/steve/pkg/schema"
|
||||
"github.com/rancher/steve/pkg/schemaserver/store/apiroot"
|
||||
"github.com/rancher/steve/pkg/schemaserver/subscribe"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/steve/pkg/server/resources/apigroups"
|
||||
"github.com/rancher/steve/pkg/server/resources/clusters"
|
||||
"github.com/rancher/steve/pkg/server/resources/common"
|
||||
"github.com/rancher/steve/pkg/server/resources/counts"
|
||||
"github.com/rancher/steve/pkg/server/resources/helm"
|
||||
"github.com/rancher/steve/pkg/server/resources/userpreferences"
|
||||
"github.com/rancher/steve/pkg/server/store/proxy"
|
||||
"github.com/rancher/steve/pkg/stores/proxy"
|
||||
"k8s.io/client-go/discovery"
|
||||
)
|
||||
|
||||
func DefaultSchemas(ctx context.Context, baseSchema *types.APISchemas, ccache clustercache.ClusterCache, cg proxy.ClientGetter) (*types.APISchemas, error) {
|
||||
counts.Register(baseSchema, ccache)
|
||||
subscribe.Register(baseSchema)
|
||||
apiroot.Register(baseSchema, []string{"v1"}, []string{"proxy:/apis"})
|
||||
apiroot.Register(baseSchema, []string{"v1"}, "proxy:/apis")
|
||||
userpreferences.Register(baseSchema, cg)
|
||||
helm.Register(baseSchema)
|
||||
|
@ -5,12 +5,12 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/builtin"
|
||||
"github.com/rancher/apiserver/pkg/builtin"
|
||||
|
||||
schemastore "github.com/rancher/apiserver/pkg/store/schema"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/accesscontrol"
|
||||
"github.com/rancher/steve/pkg/schema"
|
||||
schemastore "github.com/rancher/steve/pkg/schemaserver/store/schema"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/broadcast"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
"github.com/sirupsen/logrus"
|
@ -1,10 +1,10 @@
|
||||
package userpreferences
|
||||
|
||||
import (
|
||||
"github.com/rancher/apiserver/pkg/store/empty"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/norman/types/convert"
|
||||
"github.com/rancher/steve/pkg/schemaserver/store/empty"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/steve/pkg/server/store/proxy"
|
||||
"github.com/rancher/steve/pkg/stores/proxy"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
@ -1,10 +1,10 @@
|
||||
package userpreferences
|
||||
|
||||
import (
|
||||
"github.com/rancher/apiserver/pkg/store/empty"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/schemaserver/store/empty"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/steve/pkg/server/store/proxy"
|
||||
"github.com/rancher/steve/pkg/stores/proxy"
|
||||
"github.com/rancher/wrangler/pkg/data/convert"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
@ -3,9 +3,9 @@ package userpreferences
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/store/empty"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/steve/pkg/server/store/proxy"
|
||||
"github.com/rancher/apiserver/pkg/store/empty"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/stores/proxy"
|
||||
"github.com/rancher/wrangler/pkg/name"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
@ -1,49 +0,0 @@
|
||||
package responsewriter
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func CacheMiddleware(suffixes ...string) mux.MiddlewareFunc {
|
||||
return mux.MiddlewareFunc(func(handler http.Handler) http.Handler {
|
||||
return Cache(handler, suffixes...)
|
||||
})
|
||||
}
|
||||
|
||||
func Cache(handler http.Handler, suffixes ...string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
i := strings.LastIndex(r.URL.Path, ".")
|
||||
if i >= 0 {
|
||||
for _, suffix := range suffixes {
|
||||
if suffix == r.URL.Path[i+1:] {
|
||||
w.Header().Set("Cache-Control", "max-age=31536000, public")
|
||||
}
|
||||
}
|
||||
}
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func NoCache(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func DenyFrameOptions(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Frame-Options", "deny")
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func ContentTypeOptions(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
package responsewriter
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ContentTypeWriter struct {
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func (c ContentTypeWriter) Write(b []byte) (int, error) {
|
||||
found := false
|
||||
for k := range c.Header() {
|
||||
if strings.EqualFold(k, "Content-Type") {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
c.Header().Set("Content-Type", http.DetectContentType(b))
|
||||
}
|
||||
return c.ResponseWriter.Write(b)
|
||||
}
|
||||
|
||||
func ContentType(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
writer := ContentTypeWriter{ResponseWriter: w}
|
||||
handler.ServeHTTP(writer, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (c ContentTypeWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if hijacker, ok := c.ResponseWriter.(http.Hijacker); ok {
|
||||
return hijacker.Hijack()
|
||||
}
|
||||
return nil, nil, fmt.Errorf("Upstream ResponseWriter of type %v does not implement http.Hijacker", reflect.TypeOf(c.ResponseWriter))
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
package responsewriter
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type wrapWriter struct {
|
||||
gzipResponseWriter
|
||||
|
||||
code int
|
||||
}
|
||||
|
||||
type gzipResponseWriter struct {
|
||||
io.Writer
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func (g gzipResponseWriter) Write(b []byte) (int, error) {
|
||||
// Header logic is kept here in case the user does not use WriteHeader
|
||||
g.Header().Set("Content-Encoding", "gzip")
|
||||
g.Header().Del("Content-Length")
|
||||
|
||||
return g.Writer.Write(b)
|
||||
}
|
||||
|
||||
// Close uses gzip to write gzip footer if message is gzip encoded
|
||||
func (g gzipResponseWriter) Close(writer *gzip.Writer) {
|
||||
if g.Header().Get("Content-Encoding") == "gzip" {
|
||||
writer.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// WriteHeader sets gzip encoding and removes length. Should always be used when using gzip writer.
|
||||
func (g gzipResponseWriter) WriteHeader(statusCode int) {
|
||||
g.Header().Set("Content-Encoding", "gzip")
|
||||
g.Header().Del("Content-Length")
|
||||
g.ResponseWriter.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
// Gzip creates a gzip writer if gzip encoding is accepted.
|
||||
func Gzip(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||
handler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
gz := gzip.NewWriter(w)
|
||||
|
||||
gzw := &wrapWriter{gzipResponseWriter{Writer: gz, ResponseWriter: w}, http.StatusOK}
|
||||
defer gzw.Close(gz)
|
||||
|
||||
// Content encoding will be set once Write or WriteHeader is called, to avoid gzipping empty messages
|
||||
handler.ServeHTTP(gzw, r)
|
||||
})
|
||||
}
|
||||
|
||||
// Hijack must be implemented to properly chain with handlers expecting a hijacker handler to be passed
|
||||
func (g *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if hijacker, ok := g.ResponseWriter.(http.Hijacker); ok {
|
||||
return hijacker.Hijack()
|
||||
}
|
||||
return nil, nil, fmt.Errorf("Upstream ResponseWriter of type %v does not implement http.Hijacker", reflect.TypeOf(g.ResponseWriter))
|
||||
}
|
@ -1,174 +0,0 @@
|
||||
package responsewriter
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// All other writers will attempt additional unnecessary logic
|
||||
// Implements http.responseWriter and io.Writer
|
||||
type DummyWriter struct {
|
||||
header map[string][]string
|
||||
buffer []byte
|
||||
}
|
||||
|
||||
type DummyHandler struct {
|
||||
}
|
||||
|
||||
type DummyHandlerWithWrite struct {
|
||||
DummyHandler
|
||||
next http.Handler
|
||||
}
|
||||
|
||||
func NewDummyWriter() *DummyWriter {
|
||||
return &DummyWriter{map[string][]string{}, []byte{}}
|
||||
}
|
||||
|
||||
func NewRequest(accept string) *http.Request {
|
||||
return &http.Request{
|
||||
Header: map[string][]string{"Accept-Encoding": {accept}},
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DummyWriter) Header() http.Header {
|
||||
return d.header
|
||||
}
|
||||
|
||||
func (d *DummyWriter) Write(p []byte) (n int, err error) {
|
||||
d.buffer = append(d.buffer, p...)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (d *DummyWriter) WriteHeader(int) {
|
||||
}
|
||||
|
||||
func (d *DummyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (d *DummyHandlerWithWrite) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte{0, 0})
|
||||
if d.next != nil {
|
||||
d.next.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWriteHeader asserts content-length header is deleted and content-encoding header is set to gzip
|
||||
func TestWriteHeader(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
w := NewDummyWriter()
|
||||
gz := &gzipResponseWriter{gzip.NewWriter(w), w}
|
||||
|
||||
gz.Header().Set("Content-Length", "80")
|
||||
gz.WriteHeader(400)
|
||||
// Content-Length should have been deleted in WriterHeader, resulting in empty string
|
||||
assert.Equal("", gz.Header().Get("Content-Length"))
|
||||
assert.Equal(1, len(w.header["Content-Encoding"]))
|
||||
assert.Equal("gzip", gz.Header().Get("Content-Encoding"))
|
||||
}
|
||||
|
||||
// TestSetContentWithoutWrite asserts content-encoding is NOT "gzip" if accept-encoding header does not contain gzip
|
||||
func TestSetContentWithoutWrite(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// Test content encoding header when write is not used
|
||||
handlerFunc := Gzip(&DummyHandler{})
|
||||
|
||||
// Test when accept-encoding only contains gzip
|
||||
rw := NewDummyWriter()
|
||||
req := NewRequest("gzip")
|
||||
handlerFunc.ServeHTTP(rw, req)
|
||||
// Content encoding should be empty since write has not been used
|
||||
assert.Equal(0, len(rw.header["Content-Encoding"]))
|
||||
assert.Equal("", rw.Header().Get("Content-Encoding"))
|
||||
|
||||
// Test when accept-encoding contains multiple options, including gzip
|
||||
rw = NewDummyWriter()
|
||||
req = NewRequest("json, xml, gzip")
|
||||
handlerFunc.ServeHTTP(rw, req)
|
||||
assert.Equal(0, len(rw.header["Content-Encoding"]))
|
||||
assert.Equal("", rw.Header().Get("Content-Encoding"))
|
||||
|
||||
// Test when accept-encoding is empty
|
||||
req = NewRequest("")
|
||||
rw = NewDummyWriter()
|
||||
handlerFunc.ServeHTTP(rw, req)
|
||||
assert.Equal(0, len(rw.header["Content-Encoding"]))
|
||||
assert.Equal("", rw.Header().Get("Content-Encoding"))
|
||||
|
||||
// Test when accept-encoding is is not empty but does not include gzip
|
||||
req = NewRequest("json, xml")
|
||||
rw = NewDummyWriter()
|
||||
handlerFunc.ServeHTTP(rw, req)
|
||||
assert.Equal(0, len(rw.header["Content-Encoding"]))
|
||||
assert.Equal("", rw.Header().Get("Content-Encoding"))
|
||||
}
|
||||
|
||||
// TestSetContentWithWrite asserts content-encoding is "gzip" if accept-encoding header contains gzip
|
||||
func TestSetContentWithWrite(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// Test content encoding header when write is used
|
||||
handlerFunc := Gzip(&DummyHandlerWithWrite{})
|
||||
|
||||
// Test when accept-encoding only contains gzip
|
||||
req := NewRequest("gzip")
|
||||
rw := NewDummyWriter()
|
||||
handlerFunc.ServeHTTP(rw, req)
|
||||
// Content encoding should be gzip since write has been used
|
||||
assert.Equal(1, len(rw.header["Content-Encoding"]))
|
||||
assert.Equal("gzip", rw.Header().Get("Content-Encoding"))
|
||||
|
||||
// Test when accept-encoding contains multiple options, including gzip
|
||||
req = NewRequest("json, xml, gzip")
|
||||
rw = NewDummyWriter()
|
||||
handlerFunc.ServeHTTP(rw, req)
|
||||
// Content encoding should be gzip since write has been used
|
||||
assert.Equal(1, len(rw.header["Content-Encoding"]))
|
||||
assert.Equal("gzip", rw.Header().Get("Content-Encoding"))
|
||||
|
||||
// Test when accept-encoding is empty
|
||||
req = NewRequest("")
|
||||
rw = NewDummyWriter()
|
||||
handlerFunc.ServeHTTP(rw, req)
|
||||
// Content encoding should be empty since gzip is not an accepted encoding
|
||||
assert.Equal(0, len(rw.header["Content-Encoding"]))
|
||||
assert.Equal("", rw.Header().Get("Content-Encoding"))
|
||||
|
||||
// Test when accept-encoding is is not empty but does not include gzip
|
||||
req = NewRequest("json, xml")
|
||||
rw = NewDummyWriter()
|
||||
handlerFunc.ServeHTTP(rw, req)
|
||||
// Content encoding should be empty since gzip is not an accepted encoding
|
||||
assert.Equal(0, len(rw.header["Content-Encoding"]))
|
||||
assert.Equal("", rw.Header().Get("Content-Encoding"))
|
||||
}
|
||||
|
||||
// TestMultipleWrites ensures that Write can be used multiple times
|
||||
func TestMultipleWrites(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// Handler function that contains one writing handler
|
||||
handlerFuncOneWrite := Gzip(&DummyHandlerWithWrite{})
|
||||
|
||||
// Handler function that contains a chain of two writing handlers
|
||||
handlerFuncTwoWrites := Gzip(&DummyHandlerWithWrite{next: &DummyHandlerWithWrite{}})
|
||||
|
||||
req := NewRequest("gzip")
|
||||
rw := NewDummyWriter()
|
||||
handlerFuncOneWrite.ServeHTTP(rw, req)
|
||||
oneWriteResult := rw.buffer
|
||||
|
||||
req = NewRequest("gzip")
|
||||
rw = NewDummyWriter()
|
||||
handlerFuncTwoWrites.ServeHTTP(rw, req)
|
||||
multiWriteResult := rw.buffer
|
||||
|
||||
// Content encoding should be gzip since write has been used (twice)
|
||||
assert.Equal(1, len(rw.header["Content-Encoding"]))
|
||||
assert.Equal("gzip", rw.Header().Get("Content-Encoding"))
|
||||
assert.NotEqual(multiWriteResult, oneWriteResult)
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package responsewriter
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type MiddlewareChain struct {
|
||||
middleWares []mux.MiddlewareFunc
|
||||
}
|
||||
|
||||
func NewMiddlewareChain(middleWares ...mux.MiddlewareFunc) *MiddlewareChain {
|
||||
return &MiddlewareChain{middleWares: middleWares}
|
||||
}
|
||||
|
||||
func (m *MiddlewareChain) Handler(handler http.Handler) http.Handler {
|
||||
rtn := handler
|
||||
for i := len(m.middleWares) - 1; i >= 0; i-- {
|
||||
w := m.middleWares[i]
|
||||
rtn = w.Middleware(rtn)
|
||||
}
|
||||
return rtn
|
||||
}
|
@ -6,10 +6,10 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/server"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/accesscontrol"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/schemaserver/server"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/name"
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
@ -1,9 +1,9 @@
|
||||
package converter
|
||||
|
||||
import (
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/schema/table"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler-api/pkg/generated/controllers/apiextensions.k8s.io/v1beta1"
|
||||
"github.com/rancher/wrangler/pkg/schemas"
|
||||
beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
|
@ -3,8 +3,8 @@ package converter
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/merr"
|
||||
"github.com/rancher/wrangler/pkg/schemas"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/wrangler-api/pkg/generated/controllers/apiextensions.k8s.io/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/discovery"
|
||||
|
@ -1,8 +1,8 @@
|
||||
package converter
|
||||
|
||||
import (
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/data/convert"
|
||||
"github.com/rancher/wrangler/pkg/schemas"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -1,7 +1,7 @@
|
||||
package converter
|
||||
|
||||
import (
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/wrangler/pkg/schemas"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
)
|
||||
|
@ -5,10 +5,10 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/builtin"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/accesscontrol"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/schemaserver/builtin"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
package table
|
||||
|
||||
import (
|
||||
types2 "github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
types2 "github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/data"
|
||||
types "github.com/rancher/wrangler/pkg/schemas"
|
||||
"github.com/rancher/wrangler/pkg/schemas/mappers"
|
||||
|
@ -1,88 +0,0 @@
|
||||
package builtin
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/store/schema"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/schemas"
|
||||
"github.com/rancher/wrangler/pkg/slice"
|
||||
)
|
||||
|
||||
var (
|
||||
Schema = types.APISchema{
|
||||
Schema: &schemas.Schema{
|
||||
ID: "schema",
|
||||
PluralName: "schemas",
|
||||
CollectionMethods: []string{"GET"},
|
||||
ResourceMethods: []string{"GET"},
|
||||
ResourceFields: map[string]schemas.Field{
|
||||
"collectionActions": {Type: "map[json]"},
|
||||
"collectionFields": {Type: "map[json]"},
|
||||
"collectionFilters": {Type: "map[json]"},
|
||||
"collectionMethods": {Type: "array[string]"},
|
||||
"pluralName": {Type: "string"},
|
||||
"resourceActions": {Type: "map[json]"},
|
||||
"attributes": {Type: "map[json]"},
|
||||
"resourceFields": {Type: "map[json]"},
|
||||
"resourceMethods": {Type: "array[string]"},
|
||||
"version": {Type: "map[json]"},
|
||||
},
|
||||
},
|
||||
Formatter: SchemaFormatter,
|
||||
Store: schema.NewSchemaStore(),
|
||||
}
|
||||
|
||||
Error = types.APISchema{
|
||||
Schema: &schemas.Schema{
|
||||
ID: "error",
|
||||
ResourceMethods: []string{},
|
||||
CollectionMethods: []string{},
|
||||
ResourceFields: map[string]schemas.Field{
|
||||
"code": {Type: "string"},
|
||||
"detail": {Type: "string", Nullable: true},
|
||||
"message": {Type: "string", Nullable: true},
|
||||
"fieldName": {Type: "string", Nullable: true},
|
||||
"status": {Type: "int"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Collection = types.APISchema{
|
||||
Schema: &schemas.Schema{
|
||||
ID: "collection",
|
||||
ResourceMethods: []string{},
|
||||
CollectionMethods: []string{},
|
||||
ResourceFields: map[string]schemas.Field{
|
||||
"data": {Type: "array[json]"},
|
||||
"pagination": {Type: "map[json]"},
|
||||
"sort": {Type: "map[json]"},
|
||||
"filters": {Type: "map[json]"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Schemas = types.EmptyAPISchemas().
|
||||
MustAddSchema(Schema).
|
||||
MustAddSchema(Error).
|
||||
MustAddSchema(Collection)
|
||||
)
|
||||
|
||||
func SchemaFormatter(apiOp *types.APIRequest, resource *types.RawResource) {
|
||||
schema := apiOp.Schemas.LookupSchema(resource.ID)
|
||||
if schema == nil {
|
||||
return
|
||||
}
|
||||
|
||||
collectionLink := getSchemaCollectionLink(apiOp, schema)
|
||||
if collectionLink != "" {
|
||||
resource.Links["collection"] = collectionLink
|
||||
}
|
||||
}
|
||||
|
||||
func getSchemaCollectionLink(apiOp *types.APIRequest, schema *types.APISchema) string {
|
||||
if schema != nil && slice.ContainsString(schema.CollectionMethods, http.MethodGet) {
|
||||
return apiOp.URLBuilder.Collection(schema)
|
||||
}
|
||||
return ""
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/rancher/steve/pkg/schemaserver/httperror"
|
||||
"github.com/rancher/steve/pkg/schemaserver/parse"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
)
|
||||
|
||||
func CreateHandler(apiOp *types.APIRequest) (types.APIObject, error) {
|
||||
var err error
|
||||
|
||||
if err := apiOp.AccessControl.CanCreate(apiOp, apiOp.Schema); err != nil {
|
||||
return types.APIObject{}, err
|
||||
}
|
||||
|
||||
data, err := parse.Body(apiOp.Request)
|
||||
if err != nil {
|
||||
return types.APIObject{}, err
|
||||
}
|
||||
|
||||
store := apiOp.Schema.Store
|
||||
if store == nil {
|
||||
return types.APIObject{}, httperror.NewAPIError(validation.NotFound, "no store found")
|
||||
}
|
||||
|
||||
data, err = store.Create(apiOp, apiOp.Schema, data)
|
||||
if err != nil {
|
||||
return types.APIObject{}, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/rancher/steve/pkg/schemaserver/httperror"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
)
|
||||
|
||||
func DeleteHandler(request *types.APIRequest) (types.APIObject, error) {
|
||||
if err := request.AccessControl.CanDelete(request, types.APIObject{}, request.Schema); err != nil {
|
||||
return types.APIObject{}, err
|
||||
}
|
||||
|
||||
store := request.Schema.Store
|
||||
if store == nil {
|
||||
return types.APIObject{}, httperror.NewAPIError(validation.NotFound, "no store found")
|
||||
}
|
||||
|
||||
return store.Delete(request, request.Schema, request.Name)
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/httperror"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func ErrorHandler(request *types.APIRequest, err error) {
|
||||
if err == validation.ErrComplete {
|
||||
return
|
||||
}
|
||||
|
||||
if ec, ok := err.(validation.ErrorCode); ok {
|
||||
err = httperror.NewAPIError(ec, "")
|
||||
}
|
||||
|
||||
var error *httperror.APIError
|
||||
if apiError, ok := err.(*httperror.APIError); ok {
|
||||
if apiError.Cause != nil {
|
||||
url, _ := url.PathUnescape(request.Request.URL.String())
|
||||
if url == "" {
|
||||
url = request.Request.URL.String()
|
||||
}
|
||||
logrus.Errorf("API error response %v for %v %v. Cause: %v", apiError.Code.Status, request.Request.Method,
|
||||
url, apiError.Cause)
|
||||
}
|
||||
error = apiError
|
||||
} else {
|
||||
logrus.Errorf("Unknown error: %v", err)
|
||||
error = &httperror.APIError{
|
||||
Code: validation.ServerError,
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
if error.Code.Status == http.StatusNoContent {
|
||||
request.Response.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
data := toError(error)
|
||||
request.WriteResponse(error.Code.Status, data)
|
||||
}
|
||||
|
||||
func toError(apiError *httperror.APIError) types.APIObject {
|
||||
e := map[string]interface{}{
|
||||
"type": "error",
|
||||
"status": apiError.Code.Status,
|
||||
"code": apiError.Code.Code,
|
||||
"message": apiError.Message,
|
||||
}
|
||||
if apiError.FieldName != "" {
|
||||
e["fieldName"] = apiError.FieldName
|
||||
}
|
||||
|
||||
return types.APIObject{
|
||||
Type: "error",
|
||||
Object: e,
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/rancher/steve/pkg/schemaserver/httperror"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
)
|
||||
|
||||
func ByIDHandler(request *types.APIRequest) (types.APIObject, error) {
|
||||
if err := request.AccessControl.CanGet(request, request.Schema); err != nil {
|
||||
return types.APIObject{}, err
|
||||
}
|
||||
|
||||
store := request.Schema.Store
|
||||
if store == nil {
|
||||
return types.APIObject{}, httperror.NewAPIError(validation.NotFound, "no store found")
|
||||
}
|
||||
|
||||
resp, err := store.ByID(request, request.Schema, request.Name)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if request.Link != "" {
|
||||
if handler, ok := request.Schema.LinkHandlers[request.Link]; ok {
|
||||
handler.ServeHTTP(request.Response, request.Request)
|
||||
return types.APIObject{}, validation.ErrComplete
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func ListHandler(request *types.APIRequest) (types.APIObjectList, error) {
|
||||
if request.Name == "" {
|
||||
if err := request.AccessControl.CanList(request, request.Schema); err != nil {
|
||||
return types.APIObjectList{}, err
|
||||
}
|
||||
} else {
|
||||
if err := request.AccessControl.CanGet(request, request.Schema); err != nil {
|
||||
return types.APIObjectList{}, err
|
||||
}
|
||||
}
|
||||
|
||||
store := request.Schema.Store
|
||||
if store == nil {
|
||||
return types.APIObjectList{}, httperror.NewAPIError(validation.NotFound, "no store found")
|
||||
}
|
||||
|
||||
return store.List(request, request.Schema)
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/httperror"
|
||||
"github.com/rancher/steve/pkg/schemaserver/parse"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
)
|
||||
|
||||
func UpdateHandler(apiOp *types.APIRequest) (types.APIObject, error) {
|
||||
if err := apiOp.AccessControl.CanUpdate(apiOp, types.APIObject{}, apiOp.Schema); err != nil {
|
||||
return types.APIObject{}, err
|
||||
}
|
||||
|
||||
var (
|
||||
data types.APIObject
|
||||
err error
|
||||
)
|
||||
if apiOp.Method != http.MethodPatch {
|
||||
data, err = parse.Body(apiOp.Request)
|
||||
if err != nil {
|
||||
return types.APIObject{}, err
|
||||
}
|
||||
}
|
||||
|
||||
store := apiOp.Schema.Store
|
||||
if store == nil {
|
||||
return types.APIObject{}, httperror.NewAPIError(validation.NotFound, "no store found")
|
||||
}
|
||||
|
||||
data, err = store.Update(apiOp, apiOp.Schema, data, apiOp.Name)
|
||||
if err != nil {
|
||||
return types.APIObject{}, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
package httperror
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
)
|
||||
|
||||
type APIError struct {
|
||||
Code validation.ErrorCode
|
||||
Message string
|
||||
Cause error
|
||||
FieldName string
|
||||
}
|
||||
|
||||
func NewAPIError(code validation.ErrorCode, message string) error {
|
||||
return &APIError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
func NewFieldAPIError(code validation.ErrorCode, fieldName, message string) error {
|
||||
return &APIError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
FieldName: fieldName,
|
||||
}
|
||||
}
|
||||
|
||||
// WrapFieldAPIError will cause the API framework to log the underlying err before returning the APIError as a response.
|
||||
// err WILL NOT be in the API response
|
||||
func WrapFieldAPIError(err error, code validation.ErrorCode, fieldName, message string) error {
|
||||
return &APIError{
|
||||
Cause: err,
|
||||
Code: code,
|
||||
Message: message,
|
||||
FieldName: fieldName,
|
||||
}
|
||||
}
|
||||
|
||||
// WrapAPIError will cause the API framework to log the underlying err before returning the APIError as a response.
|
||||
// err WILL NOT be in the API response
|
||||
func WrapAPIError(err error, code validation.ErrorCode, message string) error {
|
||||
return &APIError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
Cause: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *APIError) Error() string {
|
||||
if a.FieldName != "" {
|
||||
return fmt.Sprintf("%s=%s: %s", a.FieldName, a.Code, a.Message)
|
||||
}
|
||||
return fmt.Sprintf("%s: %s", a.Code, a.Message)
|
||||
}
|
||||
|
||||
func IsAPIError(err error) bool {
|
||||
_, ok := err.(*APIError)
|
||||
return ok
|
||||
}
|
||||
|
||||
func IsConflict(err error) bool {
|
||||
if apiError, ok := err.(*APIError); ok {
|
||||
return apiError.Code.Status == 409
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func IsBrowser(req *http.Request, checkAccepts bool) bool {
|
||||
accepts := strings.ToLower(req.Header.Get("Accept"))
|
||||
userAgent := strings.ToLower(req.Header.Get("User-Agent"))
|
||||
|
||||
if accepts == "" || !checkAccepts {
|
||||
accepts = "*/*"
|
||||
}
|
||||
|
||||
// User agent has Mozilla and browser accepts */*
|
||||
return strings.Contains(userAgent, "mozilla") && strings.Contains(accepts, "*/*")
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
)
|
||||
|
||||
type Vars struct {
|
||||
Type string
|
||||
Name string
|
||||
Namespace string
|
||||
Link string
|
||||
Prefix string
|
||||
Action string
|
||||
}
|
||||
|
||||
func Set(v Vars) mux.MatcherFunc {
|
||||
return func(request *http.Request, match *mux.RouteMatch) bool {
|
||||
if match.Vars == nil {
|
||||
match.Vars = map[string]string{}
|
||||
}
|
||||
if v.Type != "" {
|
||||
match.Vars["type"] = v.Type
|
||||
}
|
||||
if v.Name != "" {
|
||||
match.Vars["name"] = v.Name
|
||||
}
|
||||
if v.Link != "" {
|
||||
match.Vars["link"] = v.Link
|
||||
}
|
||||
if v.Prefix != "" {
|
||||
match.Vars["prefix"] = v.Prefix
|
||||
}
|
||||
if v.Action != "" {
|
||||
match.Vars["action"] = v.Action
|
||||
}
|
||||
if v.Namespace != "" {
|
||||
match.Vars["namespace"] = v.Namespace
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func MuxURLParser(rw http.ResponseWriter, req *http.Request, schemas *types.APISchemas) (ParsedURL, error) {
|
||||
vars := mux.Vars(req)
|
||||
url := ParsedURL{
|
||||
Type: vars["type"],
|
||||
Name: vars["name"],
|
||||
Namespace: vars["namespace"],
|
||||
Link: vars["link"],
|
||||
Prefix: vars["prefix"],
|
||||
Method: req.Method,
|
||||
Action: vars["action"],
|
||||
Query: req.URL.Query(),
|
||||
}
|
||||
|
||||
return url, nil
|
||||
}
|
@ -1,172 +0,0 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/steve/pkg/schemaserver/urlbuilder"
|
||||
)
|
||||
|
||||
const (
|
||||
maxFormSize = 2 * 1 << 20
|
||||
)
|
||||
|
||||
var (
|
||||
allowedFormats = map[string]bool{
|
||||
"html": true,
|
||||
"json": true,
|
||||
"yaml": true,
|
||||
}
|
||||
)
|
||||
|
||||
type ParsedURL struct {
|
||||
Type string
|
||||
Name string
|
||||
Namespace string
|
||||
Link string
|
||||
Method string
|
||||
Action string
|
||||
Prefix string
|
||||
SubContext map[string]string
|
||||
Query url.Values
|
||||
}
|
||||
|
||||
type URLParser func(rw http.ResponseWriter, req *http.Request, schemas *types.APISchemas) (ParsedURL, error)
|
||||
|
||||
type Parser func(apiOp *types.APIRequest, urlParser URLParser) error
|
||||
|
||||
func Parse(apiOp *types.APIRequest, urlParser URLParser) error {
|
||||
var err error
|
||||
|
||||
if apiOp.Request == nil {
|
||||
apiOp.Request, err = http.NewRequest("GET", "/", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
apiOp = types.StoreAPIContext(apiOp)
|
||||
|
||||
if apiOp.Method == "" {
|
||||
apiOp.Method = parseMethod(apiOp.Request)
|
||||
}
|
||||
if apiOp.ResponseFormat == "" {
|
||||
apiOp.ResponseFormat = parseResponseFormat(apiOp.Request)
|
||||
}
|
||||
|
||||
// The response format is guaranteed to be set even in the event of an error
|
||||
parsedURL, err := urlParser(apiOp.Response, apiOp.Request, apiOp.Schemas)
|
||||
// wait to check error, want to set as much as possible
|
||||
|
||||
if apiOp.Type == "" {
|
||||
apiOp.Type = parsedURL.Type
|
||||
}
|
||||
if apiOp.Name == "" {
|
||||
apiOp.Name = parsedURL.Name
|
||||
}
|
||||
if apiOp.Link == "" {
|
||||
apiOp.Link = parsedURL.Link
|
||||
}
|
||||
if apiOp.Action == "" {
|
||||
apiOp.Action = parsedURL.Action
|
||||
}
|
||||
if apiOp.Query == nil {
|
||||
apiOp.Query = parsedURL.Query
|
||||
}
|
||||
if apiOp.Method == "" && parsedURL.Method != "" {
|
||||
apiOp.Method = parsedURL.Method
|
||||
}
|
||||
if apiOp.URLPrefix == "" {
|
||||
apiOp.URLPrefix = parsedURL.Prefix
|
||||
}
|
||||
if apiOp.Namespace == "" {
|
||||
apiOp.Namespace = parsedURL.Namespace
|
||||
}
|
||||
|
||||
if apiOp.URLBuilder == nil {
|
||||
// make error local to not override the outer error we have yet to check
|
||||
var err error
|
||||
apiOp.URLBuilder, err = urlbuilder.New(apiOp.Request, &urlbuilder.DefaultPathResolver{
|
||||
Prefix: apiOp.URLPrefix,
|
||||
}, apiOp.Schemas)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if apiOp.Schema == nil && apiOp.Schemas != nil {
|
||||
apiOp.Schema = apiOp.Schemas.LookupSchema(apiOp.Type)
|
||||
}
|
||||
|
||||
if apiOp.Schema != nil && apiOp.Type == "" {
|
||||
apiOp.Type = apiOp.Schema.ID
|
||||
}
|
||||
|
||||
if err := ValidateMethod(apiOp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseResponseFormat(req *http.Request) string {
|
||||
format := req.URL.Query().Get("_format")
|
||||
|
||||
if format != "" {
|
||||
format = strings.TrimSpace(strings.ToLower(format))
|
||||
}
|
||||
|
||||
/* Format specified */
|
||||
if allowedFormats[format] {
|
||||
return format
|
||||
}
|
||||
|
||||
// User agent has Mozilla and browser accepts */*
|
||||
if IsBrowser(req, true) {
|
||||
return "html"
|
||||
}
|
||||
|
||||
if isYaml(req) {
|
||||
return "yaml"
|
||||
}
|
||||
return "json"
|
||||
}
|
||||
|
||||
func isYaml(req *http.Request) bool {
|
||||
return strings.Contains(req.Header.Get("Accept"), "application/yaml")
|
||||
}
|
||||
|
||||
func parseMethod(req *http.Request) string {
|
||||
method := req.URL.Query().Get("_method")
|
||||
if method == "" {
|
||||
method = req.Method
|
||||
}
|
||||
return method
|
||||
}
|
||||
|
||||
func Body(req *http.Request) (types.APIObject, error) {
|
||||
req.ParseMultipartForm(maxFormSize)
|
||||
if req.MultipartForm != nil {
|
||||
return valuesToBody(req.MultipartForm.Value), nil
|
||||
}
|
||||
|
||||
if req.PostForm != nil && len(req.PostForm) > 0 {
|
||||
return valuesToBody(map[string][]string(req.Form)), nil
|
||||
}
|
||||
|
||||
return ReadBody(req)
|
||||
}
|
||||
|
||||
func valuesToBody(input map[string][]string) types.APIObject {
|
||||
result := map[string]interface{}{}
|
||||
for k, v := range input {
|
||||
result[k] = v
|
||||
}
|
||||
return toAPI(result)
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/httperror"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/data/convert"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
)
|
||||
|
||||
const reqMaxSize = (2 * 1 << 20) + 1
|
||||
|
||||
var bodyMethods = map[string]bool{
|
||||
http.MethodPut: true,
|
||||
http.MethodPost: true,
|
||||
}
|
||||
|
||||
type Decode func(interface{}) error
|
||||
|
||||
func ReadBody(req *http.Request) (types.APIObject, error) {
|
||||
if !bodyMethods[req.Method] {
|
||||
return types.APIObject{}, nil
|
||||
}
|
||||
|
||||
decode := getDecoder(req, io.LimitReader(req.Body, maxFormSize))
|
||||
|
||||
data := map[string]interface{}{}
|
||||
if err := decode(&data); err != nil {
|
||||
return types.APIObject{}, httperror.NewAPIError(validation.InvalidBodyContent,
|
||||
fmt.Sprintf("Failed to parse body: %v", err))
|
||||
}
|
||||
|
||||
return toAPI(data), nil
|
||||
}
|
||||
|
||||
func toAPI(data map[string]interface{}) types.APIObject {
|
||||
return types.APIObject{
|
||||
Type: convert.ToString(data["type"]),
|
||||
ID: convert.ToString(data["id"]),
|
||||
Object: data,
|
||||
}
|
||||
}
|
||||
|
||||
func getDecoder(req *http.Request, reader io.Reader) Decode {
|
||||
if req.Header.Get("Content-type") == "application/yaml" {
|
||||
return yaml.NewYAMLToJSONDecoder(reader).Decode
|
||||
}
|
||||
decoder := json.NewDecoder(reader)
|
||||
decoder.UseNumber()
|
||||
return decoder.Decode
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/httperror"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
)
|
||||
|
||||
var (
|
||||
supportedMethods = map[string]bool{
|
||||
http.MethodPost: true,
|
||||
http.MethodGet: true,
|
||||
http.MethodPut: true,
|
||||
http.MethodPatch: true,
|
||||
http.MethodDelete: true,
|
||||
}
|
||||
)
|
||||
|
||||
func ValidateMethod(request *types.APIRequest) error {
|
||||
if request.Action != "" && request.Method == http.MethodPost {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !supportedMethods[request.Method] {
|
||||
return httperror.NewAPIError(validation.MethodNotAllowed, fmt.Sprintf("Invalid method %s not supported", request.Method))
|
||||
}
|
||||
|
||||
if request.Type == "" || request.Schema == nil || request.Link != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
allowed := request.Schema.ResourceMethods
|
||||
if request.Name == "" {
|
||||
allowed = request.Schema.CollectionMethods
|
||||
}
|
||||
|
||||
for _, method := range allowed {
|
||||
if method == request.Method {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return httperror.NewAPIError(validation.MethodNotAllowed, fmt.Sprintf("Method %s not supported", request.Method))
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/httperror"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
"github.com/rancher/wrangler/pkg/slice"
|
||||
)
|
||||
|
||||
type SchemaBasedAccess struct {
|
||||
}
|
||||
|
||||
func (*SchemaBasedAccess) CanCreate(apiOp *types.APIRequest, schema *types.APISchema) error {
|
||||
if slice.ContainsString(schema.CollectionMethods, http.MethodPost) {
|
||||
return nil
|
||||
}
|
||||
return httperror.NewAPIError(validation.PermissionDenied, "can not create "+schema.ID)
|
||||
}
|
||||
|
||||
func (*SchemaBasedAccess) CanGet(apiOp *types.APIRequest, schema *types.APISchema) error {
|
||||
if slice.ContainsString(schema.ResourceMethods, http.MethodGet) {
|
||||
return nil
|
||||
}
|
||||
return httperror.NewAPIError(validation.PermissionDenied, "can not get "+schema.ID)
|
||||
}
|
||||
|
||||
func (*SchemaBasedAccess) CanList(apiOp *types.APIRequest, schema *types.APISchema) error {
|
||||
if slice.ContainsString(schema.CollectionMethods, http.MethodGet) {
|
||||
return nil
|
||||
}
|
||||
return httperror.NewAPIError(validation.PermissionDenied, "can not list "+schema.ID)
|
||||
}
|
||||
|
||||
func (*SchemaBasedAccess) CanUpdate(apiOp *types.APIRequest, obj types.APIObject, schema *types.APISchema) error {
|
||||
if slice.ContainsString(schema.ResourceMethods, http.MethodPut) {
|
||||
return nil
|
||||
}
|
||||
return httperror.NewAPIError(validation.PermissionDenied, "can not update "+schema.ID)
|
||||
}
|
||||
|
||||
func (*SchemaBasedAccess) CanDelete(apiOp *types.APIRequest, obj types.APIObject, schema *types.APISchema) error {
|
||||
if slice.ContainsString(schema.ResourceMethods, http.MethodDelete) {
|
||||
return nil
|
||||
}
|
||||
return httperror.NewAPIError(validation.PermissionDenied, "can not delete "+schema.ID)
|
||||
}
|
||||
|
||||
func (a *SchemaBasedAccess) CanWatch(apiOp *types.APIRequest, schema *types.APISchema) error {
|
||||
return a.CanList(apiOp, schema)
|
||||
}
|
||||
|
||||
func (*SchemaBasedAccess) CanAction(apiOp *types.APIRequest, schema *types.APISchema, name string) error {
|
||||
if _, ok := schema.ActionHandlers[name]; ok {
|
||||
return httperror.NewAPIError(validation.PermissionDenied, "no such action "+name)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,220 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/builtin"
|
||||
"github.com/rancher/steve/pkg/schemaserver/handlers"
|
||||
"github.com/rancher/steve/pkg/schemaserver/parse"
|
||||
"github.com/rancher/steve/pkg/schemaserver/subscribe"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/steve/pkg/schemaserver/writer"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
)
|
||||
|
||||
type RequestHandler interface {
|
||||
http.Handler
|
||||
|
||||
GetSchemas() *types.APISchemas
|
||||
Handle(apiOp *types.APIRequest)
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
ResponseWriters map[string]types.ResponseWriter
|
||||
Schemas *types.APISchemas
|
||||
Defaults Defaults
|
||||
AccessControl types.AccessControl
|
||||
Parser parse.Parser
|
||||
URLParser parse.URLParser
|
||||
}
|
||||
|
||||
type Defaults struct {
|
||||
ByIDHandler types.RequestHandler
|
||||
ListHandler types.RequestListHandler
|
||||
CreateHandler types.RequestHandler
|
||||
DeleteHandler types.RequestHandler
|
||||
UpdateHandler types.RequestHandler
|
||||
ErrorHandler types.ErrorHandler
|
||||
}
|
||||
|
||||
func DefaultAPIServer() *Server {
|
||||
s := &Server{
|
||||
Schemas: types.EmptyAPISchemas().MustAddSchemas(builtin.Schemas),
|
||||
ResponseWriters: map[string]types.ResponseWriter{
|
||||
"json": &writer.GzipWriter{
|
||||
ResponseWriter: &writer.EncodingResponseWriter{
|
||||
ContentType: "application/json",
|
||||
Encoder: types.JSONEncoder,
|
||||
},
|
||||
},
|
||||
"html": &writer.GzipWriter{
|
||||
ResponseWriter: &writer.HTMLResponseWriter{
|
||||
EncodingResponseWriter: writer.EncodingResponseWriter{
|
||||
Encoder: types.JSONEncoder,
|
||||
ContentType: "application/json",
|
||||
},
|
||||
},
|
||||
},
|
||||
"yaml": &writer.GzipWriter{
|
||||
ResponseWriter: &writer.EncodingResponseWriter{
|
||||
ContentType: "application/yaml",
|
||||
Encoder: types.YAMLEncoder,
|
||||
},
|
||||
},
|
||||
},
|
||||
AccessControl: &SchemaBasedAccess{},
|
||||
Defaults: Defaults{
|
||||
ByIDHandler: handlers.ByIDHandler,
|
||||
CreateHandler: handlers.CreateHandler,
|
||||
DeleteHandler: handlers.DeleteHandler,
|
||||
UpdateHandler: handlers.UpdateHandler,
|
||||
ListHandler: handlers.ListHandler,
|
||||
ErrorHandler: handlers.ErrorHandler,
|
||||
},
|
||||
Parser: parse.Parse,
|
||||
URLParser: parse.MuxURLParser,
|
||||
}
|
||||
|
||||
subscribe.Register(s.Schemas)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Server) setDefaults(ctx *types.APIRequest) {
|
||||
if ctx.ResponseWriter == nil {
|
||||
ctx.ResponseWriter = s.ResponseWriters[ctx.ResponseFormat]
|
||||
if ctx.ResponseWriter == nil {
|
||||
ctx.ResponseWriter = s.ResponseWriters["json"]
|
||||
}
|
||||
}
|
||||
|
||||
ctx.AccessControl = s.AccessControl
|
||||
|
||||
if ctx.Schemas == nil {
|
||||
ctx.Schemas = s.Schemas
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
s.Handle(&types.APIRequest{
|
||||
Request: req,
|
||||
Response: rw,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) Handle(apiOp *types.APIRequest) {
|
||||
s.handle(apiOp, s.Parser)
|
||||
}
|
||||
|
||||
func (s *Server) handle(apiOp *types.APIRequest, parser parse.Parser) {
|
||||
if apiOp.Schemas == nil {
|
||||
apiOp.Schemas = s.Schemas
|
||||
}
|
||||
|
||||
if err := parser(apiOp, parse.MuxURLParser); err != nil {
|
||||
// ensure defaults set so writer is assigned
|
||||
s.setDefaults(apiOp)
|
||||
s.handleError(apiOp, err)
|
||||
return
|
||||
}
|
||||
|
||||
s.setDefaults(apiOp)
|
||||
|
||||
if code, data, err := s.handleOp(apiOp); err != nil {
|
||||
s.handleError(apiOp, err)
|
||||
} else if obj, ok := data.(types.APIObject); ok {
|
||||
apiOp.WriteResponse(code, obj)
|
||||
} else if list, ok := data.(types.APIObjectList); ok {
|
||||
apiOp.WriteResponseList(code, list)
|
||||
} else if code > http.StatusOK {
|
||||
apiOp.Response.WriteHeader(code)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleOp(apiOp *types.APIRequest) (int, interface{}, error) {
|
||||
if err := CheckCSRF(apiOp); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
action, err := ValidateAction(apiOp)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
if apiOp.Schema == nil {
|
||||
return http.StatusNotFound, nil, nil
|
||||
}
|
||||
|
||||
if action != nil {
|
||||
return http.StatusOK, nil, handleAction(apiOp)
|
||||
}
|
||||
|
||||
switch apiOp.Method {
|
||||
case http.MethodGet:
|
||||
if apiOp.Name == "" {
|
||||
data, err := handleList(apiOp, apiOp.Schema.ListHandler, s.Defaults.ListHandler)
|
||||
return http.StatusOK, data, err
|
||||
}
|
||||
data, err := handle(apiOp, apiOp.Schema.ByIDHandler, s.Defaults.ByIDHandler)
|
||||
return http.StatusOK, data, err
|
||||
case http.MethodPatch:
|
||||
fallthrough
|
||||
case http.MethodPut:
|
||||
data, err := handle(apiOp, apiOp.Schema.UpdateHandler, s.Defaults.UpdateHandler)
|
||||
return http.StatusOK, data, err
|
||||
case http.MethodPost:
|
||||
data, err := handle(apiOp, apiOp.Schema.CreateHandler, s.Defaults.CreateHandler)
|
||||
return http.StatusCreated, data, err
|
||||
case http.MethodDelete:
|
||||
data, err := handle(apiOp, apiOp.Schema.DeleteHandler, s.Defaults.DeleteHandler)
|
||||
return http.StatusOK, data, err
|
||||
}
|
||||
|
||||
return http.StatusNotFound, nil, nil
|
||||
}
|
||||
|
||||
func handleList(apiOp *types.APIRequest, custom types.RequestListHandler, handler types.RequestListHandler) (types.APIObjectList, error) {
|
||||
if custom != nil {
|
||||
return custom(apiOp)
|
||||
}
|
||||
return handler(apiOp)
|
||||
}
|
||||
|
||||
func handle(apiOp *types.APIRequest, custom types.RequestHandler, handler types.RequestHandler) (types.APIObject, error) {
|
||||
if custom != nil {
|
||||
return custom(apiOp)
|
||||
}
|
||||
return handler(apiOp)
|
||||
}
|
||||
|
||||
func handleAction(context *types.APIRequest) error {
|
||||
if err := context.AccessControl.CanAction(context, context.Schema, context.Action); err != nil {
|
||||
return err
|
||||
}
|
||||
if handler, ok := context.Schema.ActionHandlers[context.Action]; ok {
|
||||
handler.ServeHTTP(context.Response, context.Request)
|
||||
return validation.ErrComplete
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) handleError(apiOp *types.APIRequest, err error) {
|
||||
if apiOp.Schema != nil && apiOp.Schema.ErrorHandler != nil {
|
||||
apiOp.Schema.ErrorHandler(apiOp, err)
|
||||
} else if s.Defaults.ErrorHandler != nil {
|
||||
s.Defaults.ErrorHandler(apiOp, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) CustomAPIUIResponseWriter(cssURL, jsURL, version writer.StringGetter) {
|
||||
wi, ok := s.ResponseWriters["html"]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
w, ok := wi.(*writer.HTMLResponseWriter)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
w.CSSURL = cssURL
|
||||
w.JSURL = jsURL
|
||||
w.APIUIVersion = version
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/httperror"
|
||||
"github.com/rancher/steve/pkg/schemaserver/parse"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/schemas"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
)
|
||||
|
||||
const (
|
||||
csrfCookie = "CSRF"
|
||||
csrfHeader = "X-API-CSRF"
|
||||
)
|
||||
|
||||
func ValidateAction(request *types.APIRequest) (*schemas.Action, error) {
|
||||
if request.Action == "" || request.Link != "" || request.Method != http.MethodPost {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err := request.AccessControl.CanAction(request, request.Schema, request.Action); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
actions := request.Schema.CollectionActions
|
||||
if request.Name != "" {
|
||||
actions = request.Schema.ResourceActions
|
||||
}
|
||||
|
||||
action, ok := actions[request.Action]
|
||||
if !ok {
|
||||
return nil, httperror.NewAPIError(validation.InvalidAction, fmt.Sprintf("Invalid action: %s", request.Action))
|
||||
}
|
||||
|
||||
return &action, nil
|
||||
}
|
||||
|
||||
func CheckCSRF(apiOp *types.APIRequest) error {
|
||||
if !parse.IsBrowser(apiOp.Request, false) {
|
||||
return nil
|
||||
}
|
||||
|
||||
cookie, err := apiOp.Request.Cookie(csrfCookie)
|
||||
if err == http.ErrNoCookie {
|
||||
bytes := make([]byte, 5)
|
||||
_, err := rand.Read(bytes)
|
||||
if err != nil {
|
||||
return httperror.WrapAPIError(err, validation.ServerError, "Failed in CSRF processing")
|
||||
}
|
||||
|
||||
cookie = &http.Cookie{
|
||||
Name: csrfCookie,
|
||||
Value: hex.EncodeToString(bytes),
|
||||
Path: "/",
|
||||
Secure: true,
|
||||
}
|
||||
|
||||
http.SetCookie(apiOp.Response, cookie)
|
||||
} else if err != nil {
|
||||
return httperror.NewAPIError(validation.InvalidCSRFToken, "Failed to parse cookies")
|
||||
} else if apiOp.Method != http.MethodGet {
|
||||
/*
|
||||
* Very important to use apiOp.Method and not apiOp.Request.Method. The client can override the HTTP method with _method
|
||||
*/
|
||||
if cookie.Value == apiOp.Request.Header.Get(csrfHeader) {
|
||||
// Good
|
||||
} else if cookie.Value == apiOp.Request.URL.Query().Get(csrfCookie) {
|
||||
// Good
|
||||
} else {
|
||||
return httperror.NewAPIError(validation.InvalidCSRFToken, "Invalid CSRF token")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
package apiroot
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/store/empty"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/schemas"
|
||||
)
|
||||
|
||||
func Register(apiSchemas *types.APISchemas, versions, roots []string) {
|
||||
apiSchemas.MustAddSchema(types.APISchema{
|
||||
Schema: &schemas.Schema{
|
||||
ID: "apiRoot",
|
||||
CollectionMethods: []string{"GET"},
|
||||
ResourceMethods: []string{"GET"},
|
||||
ResourceFields: map[string]schemas.Field{
|
||||
"apiVersion": {Type: "map[json]"},
|
||||
"path": {Type: "string"},
|
||||
},
|
||||
},
|
||||
Formatter: Formatter,
|
||||
Store: NewAPIRootStore(versions, roots),
|
||||
})
|
||||
}
|
||||
|
||||
func Formatter(apiOp *types.APIRequest, resource *types.RawResource) {
|
||||
data := resource.APIObject.Data()
|
||||
path, _ := data["path"].(string)
|
||||
if path == "" {
|
||||
return
|
||||
}
|
||||
delete(data, "path")
|
||||
|
||||
resource.Links["root"] = apiOp.URLBuilder.RelativeToRoot(path)
|
||||
|
||||
if data, isAPIRoot := data["apiVersion"].(map[string]interface{}); isAPIRoot {
|
||||
apiVersion := apiVersionFromMap(apiOp.Schemas, data)
|
||||
resource.Links["self"] = apiOp.URLBuilder.RelativeToRoot(apiVersion)
|
||||
|
||||
resource.Links["schemas"] = apiOp.URLBuilder.RelativeToRoot(path)
|
||||
for _, schema := range apiOp.Schemas.Schemas {
|
||||
addCollectionLink(apiOp, schema, resource.Links)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func addCollectionLink(apiOp *types.APIRequest, schema *types.APISchema, links map[string]string) {
|
||||
collectionLink := getSchemaCollectionLink(apiOp, schema)
|
||||
if collectionLink != "" {
|
||||
links[schema.PluralName] = collectionLink
|
||||
}
|
||||
}
|
||||
|
||||
func getSchemaCollectionLink(apiOp *types.APIRequest, schema *types.APISchema) string {
|
||||
if schema != nil && contains(schema.CollectionMethods, http.MethodGet) {
|
||||
return apiOp.URLBuilder.Collection(schema)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Store struct {
|
||||
empty.Store
|
||||
roots []string
|
||||
versions []string
|
||||
}
|
||||
|
||||
func NewAPIRootStore(versions []string, roots []string) types.Store {
|
||||
return &Store{
|
||||
roots: roots,
|
||||
versions: versions,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Store) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) {
|
||||
return types.DefaultByID(a, apiOp, schema, id)
|
||||
}
|
||||
|
||||
func (a *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.APIObjectList, error) {
|
||||
var roots types.APIObjectList
|
||||
|
||||
versions := a.versions
|
||||
|
||||
for _, version := range versions {
|
||||
roots.Objects = append(roots.Objects, types.APIObject{
|
||||
Type: "apiRoot",
|
||||
ID: version,
|
||||
Object: apiVersionToAPIRootMap(version),
|
||||
})
|
||||
}
|
||||
|
||||
for _, root := range a.roots {
|
||||
parts := strings.SplitN(root, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
roots.Objects = append(roots.Objects, types.APIObject{
|
||||
Type: "apiRoot",
|
||||
ID: parts[0],
|
||||
Object: map[string]interface{}{
|
||||
"id": parts[0],
|
||||
"path": parts[1],
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return roots, nil
|
||||
}
|
||||
|
||||
func apiVersionToAPIRootMap(version string) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"id": version,
|
||||
"type": "apiRoot",
|
||||
"apiVersion": map[string]interface{}{
|
||||
"version": version,
|
||||
},
|
||||
"path": "/" + version,
|
||||
}
|
||||
}
|
||||
|
||||
func apiVersionFromMap(schemas *types.APISchemas, apiVersion map[string]interface{}) string {
|
||||
version, _ := apiVersion["version"].(string)
|
||||
return version
|
||||
}
|
||||
|
||||
func contains(list []string, needle string) bool {
|
||||
for _, v := range list {
|
||||
if v == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package empty
|
||||
|
||||
import (
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
}
|
||||
|
||||
func (e *Store) Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) {
|
||||
return types.APIObject{}, validation.NotFound
|
||||
}
|
||||
|
||||
func (e *Store) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) {
|
||||
return types.APIObject{}, validation.NotFound
|
||||
}
|
||||
|
||||
func (e *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.APIObjectList, error) {
|
||||
return types.APIObjectList{}, validation.NotFound
|
||||
}
|
||||
|
||||
func (e *Store) Create(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject) (types.APIObject, error) {
|
||||
return types.APIObject{}, validation.NotFound
|
||||
}
|
||||
|
||||
func (e *Store) Update(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject, id string) (types.APIObject, error) {
|
||||
return types.APIObject{}, validation.NotFound
|
||||
}
|
||||
|
||||
func (e *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, wr types.WatchRequest) (chan types.APIEvent, error) {
|
||||
return nil, nil
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/httperror"
|
||||
"github.com/rancher/steve/pkg/schemaserver/store/empty"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/schemas/definition"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
empty.Store
|
||||
}
|
||||
|
||||
func NewSchemaStore() types.Store {
|
||||
return &Store{}
|
||||
}
|
||||
|
||||
func toAPIObject(schema *types.APISchema) types.APIObject {
|
||||
s := schema.DeepCopy()
|
||||
delete(s.Schema.Attributes, "access")
|
||||
return types.APIObject{
|
||||
Type: "schema",
|
||||
ID: schema.ID,
|
||||
Object: s,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) {
|
||||
schema = apiOp.Schemas.LookupSchema(id)
|
||||
if schema == nil {
|
||||
return types.APIObject{}, httperror.NewAPIError(validation.NotFound, "no such schema")
|
||||
}
|
||||
return toAPIObject(schema), nil
|
||||
}
|
||||
|
||||
func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.APIObjectList, error) {
|
||||
return FilterSchemas(apiOp, apiOp.Schemas.Schemas), nil
|
||||
}
|
||||
|
||||
func FilterSchemas(apiOp *types.APIRequest, schemaMap map[string]*types.APISchema) types.APIObjectList {
|
||||
schemas := types.APIObjectList{}
|
||||
|
||||
included := map[string]bool{}
|
||||
for _, schema := range schemaMap {
|
||||
if included[schema.ID] {
|
||||
continue
|
||||
}
|
||||
|
||||
if apiOp.AccessControl.CanList(apiOp, schema) == nil || apiOp.AccessControl.CanGet(apiOp, schema) == nil {
|
||||
schemas = addSchema(apiOp, schema, schemaMap, schemas, included)
|
||||
}
|
||||
}
|
||||
|
||||
return schemas
|
||||
}
|
||||
|
||||
func addSchema(apiOp *types.APIRequest, schema *types.APISchema, schemaMap map[string]*types.APISchema, schemas types.APIObjectList, included map[string]bool) types.APIObjectList {
|
||||
included[schema.ID] = true
|
||||
schemas = traverseAndAdd(apiOp, schema, schemaMap, schemas, included)
|
||||
schemas.Objects = append(schemas.Objects, toAPIObject(schema))
|
||||
return schemas
|
||||
}
|
||||
|
||||
func traverseAndAdd(apiOp *types.APIRequest, schema *types.APISchema, schemaMap map[string]*types.APISchema, schemas types.APIObjectList, included map[string]bool) types.APIObjectList {
|
||||
for _, field := range schema.ResourceFields {
|
||||
t := ""
|
||||
subType := field.Type
|
||||
for subType != t {
|
||||
t = subType
|
||||
subType = definition.SubType(t)
|
||||
}
|
||||
|
||||
if refSchema, ok := schemaMap[t]; ok && !included[t] {
|
||||
schemas = addSchema(apiOp, refSchema, schemaMap, schemas, included)
|
||||
}
|
||||
}
|
||||
|
||||
for _, action := range schema.ResourceActions {
|
||||
for _, t := range []string{action.Output, action.Input} {
|
||||
if t == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if refSchema, ok := schemaMap[t]; ok && !included[t] {
|
||||
schemas = addSchema(apiOp, refSchema, schemaMap, schemas, included)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, action := range schema.CollectionActions {
|
||||
for _, t := range []string{action.Output, action.Input} {
|
||||
if t == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if refSchema, ok := schemaMap[t]; ok && !included[t] {
|
||||
schemas = addSchema(apiOp, refSchema, schemaMap, schemas, included)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return schemas
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
package subscribe
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/steve/pkg/schemaserver/writer"
|
||||
)
|
||||
|
||||
type Converter struct {
|
||||
writer.EncodingResponseWriter
|
||||
apiOp *types.APIRequest
|
||||
obj interface{}
|
||||
}
|
||||
|
||||
func MarshallObject(apiOp *types.APIRequest, event types.APIEvent) types.APIEvent {
|
||||
if event.Error != nil {
|
||||
return event
|
||||
}
|
||||
|
||||
data, err := newConverter(apiOp).ToAPIObject(event.Object)
|
||||
if err != nil {
|
||||
event.Error = err
|
||||
return event
|
||||
}
|
||||
|
||||
event.Data = data
|
||||
return event
|
||||
}
|
||||
|
||||
func newConverter(apiOp *types.APIRequest) *Converter {
|
||||
c := &Converter{
|
||||
apiOp: apiOp,
|
||||
}
|
||||
c.EncodingResponseWriter = writer.EncodingResponseWriter{
|
||||
ContentType: "application/json",
|
||||
Encoder: c.Encoder,
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Converter) ToAPIObject(data types.APIObject) (interface{}, error) {
|
||||
c.obj = nil
|
||||
if err := c.Body(c.apiOp, nil, data); err != nil {
|
||||
return types.APIObject{}, err
|
||||
}
|
||||
return c.obj, nil
|
||||
}
|
||||
|
||||
func (c *Converter) Encoder(_ io.Writer, obj interface{}) error {
|
||||
c.obj = obj
|
||||
return nil
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
package subscribe
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
HandshakeTimeout: 60 * time.Second,
|
||||
EnableCompression: true,
|
||||
}
|
||||
|
||||
type Subscribe struct {
|
||||
Stop bool `json:"stop,omitempty"`
|
||||
ResourceType string `json:"resourceType,omitempty"`
|
||||
ResourceVersion string `json:"resourceVersion,omitempty"`
|
||||
}
|
||||
|
||||
func Handler(apiOp *types.APIRequest) (types.APIObjectList, error) {
|
||||
err := handler(apiOp)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error during subscribe %v", err)
|
||||
}
|
||||
return types.APIObjectList{}, validation.ErrComplete
|
||||
}
|
||||
|
||||
func handler(apiOp *types.APIRequest) error {
|
||||
c, err := upgrader.Upgrade(apiOp.Response, apiOp.Request, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
watches := NewWatchSession(apiOp)
|
||||
defer watches.Close()
|
||||
|
||||
events := watches.Watch(c)
|
||||
t := time.NewTicker(30 * time.Second)
|
||||
defer t.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-events:
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if err := writeData(apiOp, c, event); err != nil {
|
||||
return err
|
||||
}
|
||||
case <-t.C:
|
||||
if err := writeData(apiOp, c, types.APIEvent{Name: "ping"}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeData(apiOp *types.APIRequest, c *websocket.Conn, event types.APIEvent) error {
|
||||
event = MarshallObject(apiOp, event)
|
||||
if event.Error != nil {
|
||||
event.Name = "resource.error"
|
||||
event.Data = map[string]interface{}{
|
||||
"error": event.Error.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
messageWriter, err := c.NextWriter(websocket.TextMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer messageWriter.Close()
|
||||
|
||||
return json.NewEncoder(messageWriter).Encode(event)
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package subscribe
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
)
|
||||
|
||||
func Register(schemas *types.APISchemas) {
|
||||
schemas.MustImportAndCustomize(Subscribe{}, func(schema *types.APISchema) {
|
||||
schema.CollectionMethods = []string{http.MethodGet}
|
||||
schema.ResourceMethods = []string{}
|
||||
schema.ListHandler = Handler
|
||||
schema.PluralName = "subscribe"
|
||||
})
|
||||
}
|
@ -1,145 +0,0 @@
|
||||
package subscribe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
)
|
||||
|
||||
type WatchSession struct {
|
||||
sync.Mutex
|
||||
|
||||
apiOp *types.APIRequest
|
||||
watchers map[string]func()
|
||||
wg sync.WaitGroup
|
||||
ctx context.Context
|
||||
cancel func()
|
||||
}
|
||||
|
||||
func (s *WatchSession) stop(id string, resp chan<- types.APIEvent) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
if cancel, ok := s.watchers[id]; ok {
|
||||
cancel()
|
||||
resp <- types.APIEvent{
|
||||
Name: "resource.stop",
|
||||
ResourceType: id,
|
||||
}
|
||||
}
|
||||
delete(s.watchers, id)
|
||||
}
|
||||
|
||||
func (s *WatchSession) add(resourceType, revision string, resp chan<- types.APIEvent) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
ctx, cancel := context.WithCancel(s.ctx)
|
||||
s.watchers[resourceType] = cancel
|
||||
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
defer s.wg.Done()
|
||||
defer s.stop(resourceType, resp)
|
||||
|
||||
if err := s.stream(ctx, resourceType, revision, resp); err != nil {
|
||||
sendErr(resp, err, resourceType)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *WatchSession) stream(ctx context.Context, resourceType, revision string, result chan<- types.APIEvent) error {
|
||||
schema := s.apiOp.Schemas.LookupSchema(resourceType)
|
||||
if schema == nil {
|
||||
return fmt.Errorf("failed to find schema %s", resourceType)
|
||||
} else if schema.Store == nil {
|
||||
return fmt.Errorf("schema %s does not support watching", resourceType)
|
||||
}
|
||||
|
||||
if err := s.apiOp.AccessControl.CanWatch(s.apiOp, schema); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := schema.Store.Watch(s.apiOp.WithContext(ctx), schema, types.WatchRequest{Revision: revision})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result <- types.APIEvent{
|
||||
Name: "resource.start",
|
||||
ResourceType: resourceType,
|
||||
}
|
||||
|
||||
for event := range c {
|
||||
result <- event
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewWatchSession(apiOp *types.APIRequest) *WatchSession {
|
||||
ws := &WatchSession{
|
||||
apiOp: apiOp,
|
||||
watchers: map[string]func(){},
|
||||
}
|
||||
|
||||
ws.ctx, ws.cancel = context.WithCancel(apiOp.Request.Context())
|
||||
return ws
|
||||
}
|
||||
|
||||
func (s *WatchSession) Watch(conn *websocket.Conn) <-chan types.APIEvent {
|
||||
result := make(chan types.APIEvent, 100)
|
||||
go func() {
|
||||
defer close(result)
|
||||
|
||||
if err := s.watch(conn, result); err != nil {
|
||||
sendErr(result, err, "")
|
||||
}
|
||||
}()
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *WatchSession) Close() {
|
||||
s.cancel()
|
||||
s.wg.Wait()
|
||||
}
|
||||
|
||||
func (s *WatchSession) watch(conn *websocket.Conn, resp chan types.APIEvent) error {
|
||||
defer s.wg.Wait()
|
||||
defer s.cancel()
|
||||
|
||||
for {
|
||||
_, r, err := conn.NextReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var sub Subscribe
|
||||
|
||||
if err := json.NewDecoder(r).Decode(&sub); err != nil {
|
||||
sendErr(resp, err, "")
|
||||
continue
|
||||
}
|
||||
|
||||
if sub.Stop {
|
||||
s.stop(sub.ResourceType, resp)
|
||||
} else {
|
||||
s.Lock()
|
||||
_, ok := s.watchers[sub.ResourceType]
|
||||
s.Unlock()
|
||||
if !ok {
|
||||
s.add(sub.ResourceType, sub.ResourceVersion, resp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendErr(resp chan<- types.APIEvent, err error, resourceType string) {
|
||||
resp <- types.APIEvent{
|
||||
ResourceType: resourceType,
|
||||
Error: err,
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
)
|
||||
|
||||
func JSONEncoder(writer io.Writer, v interface{}) error {
|
||||
return json.NewEncoder(writer).Encode(v)
|
||||
}
|
||||
|
||||
func YAMLEncoder(writer io.Writer, v interface{}) error {
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf, err := yaml.JSONToYAML(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = writer.Write(buf)
|
||||
return err
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/wrangler/pkg/schemas"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type APISchemas struct {
|
||||
InternalSchemas *schemas.Schemas
|
||||
Schemas map[string]*APISchema
|
||||
index map[string]*APISchema
|
||||
}
|
||||
|
||||
func EmptyAPISchemas() *APISchemas {
|
||||
return &APISchemas{
|
||||
InternalSchemas: schemas.EmptySchemas(),
|
||||
Schemas: map[string]*APISchema{},
|
||||
index: map[string]*APISchema{},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *APISchemas) MustAddSchema(obj APISchema) *APISchemas {
|
||||
err := a.AddSchema(obj)
|
||||
if err != nil {
|
||||
logrus.Fatalf("failed to add schema: %v", err)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *APISchemas) addInternalSchema(schema *schemas.Schema) *APISchema {
|
||||
apiSchema := &APISchema{
|
||||
Schema: schema,
|
||||
}
|
||||
a.Schemas[schema.ID] = apiSchema
|
||||
a.addToIndex(apiSchema)
|
||||
|
||||
for _, f := range schema.ResourceFields {
|
||||
if subType := a.InternalSchemas.Schema(f.Type); subType == nil {
|
||||
continue
|
||||
} else if _, ok := a.Schemas[subType.ID]; !ok {
|
||||
a.addInternalSchema(subType)
|
||||
}
|
||||
}
|
||||
|
||||
return apiSchema
|
||||
}
|
||||
|
||||
func (a *APISchemas) MustImportAndCustomize(obj interface{}, f func(*APISchema)) {
|
||||
schema, err := a.InternalSchemas.Import(obj)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
apiSchema := a.addInternalSchema(schema)
|
||||
f(apiSchema)
|
||||
}
|
||||
|
||||
func (a *APISchemas) MustAddSchemas(schemas *APISchemas) *APISchemas {
|
||||
if err := a.AddSchemas(schemas); err != nil {
|
||||
logrus.Fatalf("failed to add schemas: %v", err)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *APISchemas) AddSchemas(schema *APISchemas) error {
|
||||
for _, schema := range schema.Schemas {
|
||||
if err := a.AddSchema(*schema); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *APISchemas) addToIndex(schema *APISchema) {
|
||||
a.index[strings.ToLower(schema.ID)] = schema
|
||||
a.index[strings.ToLower(schema.PluralName)] = schema
|
||||
}
|
||||
|
||||
func (a *APISchemas) AddSchema(schema APISchema) error {
|
||||
if err := a.InternalSchemas.AddSchema(*schema.Schema); err != nil {
|
||||
return err
|
||||
}
|
||||
schema.Schema = a.InternalSchemas.Schema(schema.ID)
|
||||
a.Schemas[schema.ID] = &schema
|
||||
a.addToIndex(&schema)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *APISchemas) LookupSchema(name string) *APISchema {
|
||||
s, ok := a.Schemas[name]
|
||||
if ok {
|
||||
return s
|
||||
}
|
||||
return a.index[strings.ToLower(name)]
|
||||
}
|
@ -1,295 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/rancher/wrangler/pkg/data"
|
||||
"github.com/rancher/wrangler/pkg/data/convert"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
meta2 "k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
)
|
||||
|
||||
type RawResource struct {
|
||||
ID string `json:"id,omitempty" yaml:"id,omitempty"`
|
||||
Type string `json:"type,omitempty" yaml:"type,omitempty"`
|
||||
Schema *APISchema `json:"-" yaml:"-"`
|
||||
Links map[string]string `json:"links" yaml:"links,omitempty"`
|
||||
Actions map[string]string `json:"actions,omitempty" yaml:"actions,omitempty"`
|
||||
ActionLinks bool `json:"-" yaml:"-"`
|
||||
APIObject APIObject `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
type Pagination struct {
|
||||
Limit int `json:"limit,omitempty"`
|
||||
First string `json:"first,omitempty"`
|
||||
Next string `json:"next,omitempty"`
|
||||
Partial bool `json:"partial,omitempty"`
|
||||
}
|
||||
|
||||
func (r *RawResource) MarshalJSON() ([]byte, error) {
|
||||
type r_ RawResource
|
||||
outer, err := json.Marshal((*r_)(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
last := len(outer) - 1
|
||||
if len(outer) < 2 || outer[last] != '}' {
|
||||
return outer, nil
|
||||
}
|
||||
|
||||
data, err := json.Marshal(r.APIObject.Object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(data) < 3 || data[0] != '{' || data[len(data)-1] != '}' {
|
||||
return outer, nil
|
||||
}
|
||||
|
||||
if outer[last-1] == '{' {
|
||||
outer[last] = ' '
|
||||
} else {
|
||||
outer[last] = ','
|
||||
}
|
||||
|
||||
return append(outer, data[1:]...), nil
|
||||
}
|
||||
|
||||
func (r *RawResource) AddAction(apiOp *APIRequest, name string) {
|
||||
r.Actions[name] = apiOp.URLBuilder.Action(r.Schema, r.ID, name)
|
||||
}
|
||||
|
||||
type RequestHandler func(request *APIRequest) (APIObject, error)
|
||||
|
||||
type RequestListHandler func(request *APIRequest) (APIObjectList, error)
|
||||
|
||||
type Formatter func(request *APIRequest, resource *RawResource)
|
||||
|
||||
type CollectionFormatter func(request *APIRequest, collection *GenericCollection)
|
||||
|
||||
type ErrorHandler func(request *APIRequest, err error)
|
||||
|
||||
type ResponseWriter interface {
|
||||
Write(apiOp *APIRequest, code int, obj APIObject)
|
||||
WriteList(apiOp *APIRequest, code int, obj APIObjectList)
|
||||
}
|
||||
|
||||
type AccessControl interface {
|
||||
CanAction(apiOp *APIRequest, schema *APISchema, name string) error
|
||||
CanCreate(apiOp *APIRequest, schema *APISchema) error
|
||||
CanList(apiOp *APIRequest, schema *APISchema) error
|
||||
CanGet(apiOp *APIRequest, schema *APISchema) error
|
||||
CanUpdate(apiOp *APIRequest, obj APIObject, schema *APISchema) error
|
||||
CanDelete(apiOp *APIRequest, obj APIObject, schema *APISchema) error
|
||||
CanWatch(apiOp *APIRequest, schema *APISchema) error
|
||||
}
|
||||
|
||||
type APIRequest struct {
|
||||
Action string
|
||||
Name string
|
||||
Type string
|
||||
Link string
|
||||
Method string
|
||||
Namespace string
|
||||
Schema *APISchema
|
||||
Schemas *APISchemas
|
||||
Query url.Values
|
||||
ResponseFormat string
|
||||
ResponseWriter ResponseWriter
|
||||
URLPrefix string
|
||||
URLBuilder URLBuilder
|
||||
AccessControl AccessControl
|
||||
|
||||
Request *http.Request
|
||||
Response http.ResponseWriter
|
||||
}
|
||||
|
||||
type apiOpKey struct{}
|
||||
|
||||
func GetAPIContext(ctx context.Context) *APIRequest {
|
||||
apiOp, _ := ctx.Value(apiOpKey{}).(*APIRequest)
|
||||
return apiOp
|
||||
}
|
||||
|
||||
func StoreAPIContext(apiOp *APIRequest) *APIRequest {
|
||||
ctx := context.WithValue(apiOp.Request.Context(), apiOpKey{}, apiOp)
|
||||
apiOp.Request = apiOp.Request.WithContext(ctx)
|
||||
return apiOp
|
||||
}
|
||||
|
||||
func (r *APIRequest) WithContext(ctx context.Context) *APIRequest {
|
||||
result := *r
|
||||
result.Request = result.Request.WithContext(ctx)
|
||||
return &result
|
||||
}
|
||||
|
||||
func (r *APIRequest) Context() context.Context {
|
||||
return r.Request.Context()
|
||||
}
|
||||
|
||||
func (r *APIRequest) GetUser() string {
|
||||
user, ok := request.UserFrom(r.Request.Context())
|
||||
if ok {
|
||||
return user.GetName()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (r *APIRequest) GetUserInfo() (user.Info, bool) {
|
||||
return request.UserFrom(r.Request.Context())
|
||||
}
|
||||
|
||||
func (r *APIRequest) Option(key string) string {
|
||||
return r.Query.Get("_" + key)
|
||||
}
|
||||
|
||||
func (r *APIRequest) WriteResponse(code int, obj APIObject) {
|
||||
r.ResponseWriter.Write(r, code, obj)
|
||||
}
|
||||
|
||||
func (r *APIRequest) WriteResponseList(code int, list APIObjectList) {
|
||||
r.ResponseWriter.WriteList(r, code, list)
|
||||
}
|
||||
|
||||
type URLBuilder interface {
|
||||
Current() string
|
||||
|
||||
Collection(schema *APISchema) string
|
||||
CollectionAction(schema *APISchema, action string) string
|
||||
ResourceLink(schema *APISchema, id string) string
|
||||
Link(schema *APISchema, id string, linkName string) string
|
||||
Action(schema *APISchema, id string, action string) string
|
||||
Marker(marker string) string
|
||||
|
||||
RelativeToRoot(path string) string
|
||||
}
|
||||
|
||||
type Store interface {
|
||||
ByID(apiOp *APIRequest, schema *APISchema, id string) (APIObject, error)
|
||||
List(apiOp *APIRequest, schema *APISchema) (APIObjectList, error)
|
||||
Create(apiOp *APIRequest, schema *APISchema, data APIObject) (APIObject, error)
|
||||
Update(apiOp *APIRequest, schema *APISchema, data APIObject, id string) (APIObject, error)
|
||||
Delete(apiOp *APIRequest, schema *APISchema, id string) (APIObject, error)
|
||||
Watch(apiOp *APIRequest, schema *APISchema, w WatchRequest) (chan APIEvent, error)
|
||||
}
|
||||
|
||||
func DefaultByID(store Store, apiOp *APIRequest, schema *APISchema, id string) (APIObject, error) {
|
||||
list, err := store.List(apiOp, schema)
|
||||
if err != nil {
|
||||
return APIObject{}, err
|
||||
}
|
||||
|
||||
for _, item := range list.Objects {
|
||||
if item.ID == id {
|
||||
return item, nil
|
||||
}
|
||||
}
|
||||
|
||||
return APIObject{}, validation.NotFound
|
||||
}
|
||||
|
||||
type WatchRequest struct {
|
||||
Revision string
|
||||
}
|
||||
|
||||
var (
|
||||
ChangeAPIEvent = "resource.change"
|
||||
RemoveAPIEvent = "resource.remove"
|
||||
CreateAPIEvent = "resource.create"
|
||||
)
|
||||
|
||||
type APIEvent struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
ResourceType string `json:"resourceType,omitempty"`
|
||||
Revision string `json:"revision,omitempty"`
|
||||
Object APIObject `json:"-"`
|
||||
Error error `json:"-"`
|
||||
// Data is the output format of the object
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type APIObject struct {
|
||||
Type string
|
||||
ID string
|
||||
Object interface{}
|
||||
}
|
||||
|
||||
type APIObjectList struct {
|
||||
Revision string
|
||||
Continue string
|
||||
Objects []APIObject
|
||||
}
|
||||
|
||||
func (a *APIObject) Data() data.Object {
|
||||
if unstr, ok := a.Object.(*unstructured.Unstructured); ok {
|
||||
return unstr.Object
|
||||
}
|
||||
data, err := convert.EncodeToMap(a.Object)
|
||||
if err != nil {
|
||||
return convert.ToMapInterface(a.Object)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (a *APIObject) Name() string {
|
||||
if ro, ok := a.Object.(runtime.Object); ok {
|
||||
meta, err := meta2.Accessor(ro)
|
||||
if err == nil {
|
||||
return meta.GetName()
|
||||
}
|
||||
}
|
||||
return Name(a.Data())
|
||||
}
|
||||
|
||||
func (a *APIObject) Namespace() string {
|
||||
if ro, ok := a.Object.(runtime.Object); ok {
|
||||
meta, err := meta2.Accessor(ro)
|
||||
if err == nil {
|
||||
return meta.GetNamespace()
|
||||
}
|
||||
}
|
||||
return Namespace(a.Data())
|
||||
}
|
||||
|
||||
func Name(d map[string]interface{}) string {
|
||||
return convert.ToString(data.GetValueN(d, "metadata", "name"))
|
||||
}
|
||||
|
||||
func Namespace(d map[string]interface{}) string {
|
||||
return convert.ToString(data.GetValueN(d, "metadata", "namespace"))
|
||||
}
|
||||
|
||||
func APIChan(c <-chan APIEvent, f func(APIObject) APIObject) chan APIEvent {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
result := make(chan APIEvent)
|
||||
go func() {
|
||||
for data := range c {
|
||||
data.Object = f(data.Object)
|
||||
result <- data
|
||||
}
|
||||
close(result)
|
||||
}()
|
||||
return result
|
||||
}
|
||||
|
||||
func FormatterChain(formatter Formatter, next Formatter) Formatter {
|
||||
return func(request *APIRequest, resource *RawResource) {
|
||||
formatter(request, resource)
|
||||
next(request, resource)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *APIRequest) Clone() *APIRequest {
|
||||
clone := *r
|
||||
return &clone
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/rancher/wrangler/pkg/schemas"
|
||||
)
|
||||
|
||||
type Collection struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Links map[string]string `json:"links"`
|
||||
CreateTypes map[string]string `json:"createTypes,omitempty"`
|
||||
Actions map[string]string `json:"actions"`
|
||||
ResourceType string `json:"resourceType"`
|
||||
Pagination *Pagination `json:"pagination,omitempty"`
|
||||
Revision string `json:"revision,omitempty"`
|
||||
Continue string `json:"continue,omitempty"`
|
||||
}
|
||||
|
||||
type GenericCollection struct {
|
||||
Collection
|
||||
Data []*RawResource `json:"data"`
|
||||
}
|
||||
|
||||
var (
|
||||
ModifierEQ ModifierType = "eq"
|
||||
ModifierNE ModifierType = "ne"
|
||||
ModifierNull ModifierType = "null"
|
||||
ModifierNotNull ModifierType = "notnull"
|
||||
ModifierIn ModifierType = "in"
|
||||
ModifierNotIn ModifierType = "notin"
|
||||
)
|
||||
|
||||
type ModifierType string
|
||||
|
||||
type Condition struct {
|
||||
Modifier ModifierType `json:"modifier,omitempty"`
|
||||
Value interface{} `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
type Resource struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Links map[string]string `json:"links"`
|
||||
Actions map[string]string `json:"actions"`
|
||||
}
|
||||
|
||||
type NamedResource struct {
|
||||
Resource
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type NamedResourceCollection struct {
|
||||
Collection
|
||||
Data []NamedResource `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
var ReservedFields = map[string]bool{
|
||||
"id": true,
|
||||
"type": true,
|
||||
"links": true,
|
||||
"actions": true,
|
||||
}
|
||||
|
||||
type APISchema struct {
|
||||
*schemas.Schema
|
||||
|
||||
ActionHandlers map[string]http.Handler `json:"-"`
|
||||
LinkHandlers map[string]http.Handler `json:"-"`
|
||||
ListHandler RequestListHandler `json:"-"`
|
||||
ByIDHandler RequestHandler `json:"-"`
|
||||
CreateHandler RequestHandler `json:"-"`
|
||||
DeleteHandler RequestHandler `json:"-"`
|
||||
UpdateHandler RequestHandler `json:"-"`
|
||||
Formatter Formatter `json:"-"`
|
||||
CollectionFormatter CollectionFormatter `json:"-"`
|
||||
ErrorHandler ErrorHandler `json:"-"`
|
||||
Store Store `json:"-"`
|
||||
}
|
||||
|
||||
func copyHandlers(m map[string]http.Handler) map[string]http.Handler {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
result := make(map[string]http.Handler, len(m))
|
||||
for k, v := range m {
|
||||
result[k] = v
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
func (a *APISchema) DeepCopy() *APISchema {
|
||||
r := *a
|
||||
r.ActionHandlers = copyHandlers(a.ActionHandlers)
|
||||
r.LinkHandlers = copyHandlers(a.ActionHandlers)
|
||||
r.Schema = r.Schema.DeepCopy()
|
||||
return &r
|
||||
}
|
||||
|
||||
func (c *Collection) AddAction(apiOp *APIRequest, name string) {
|
||||
c.Actions[name] = apiOp.URLBuilder.CollectionAction(apiOp.Schema, name)
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
package urlbuilder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ParseRequestURL(r *http.Request) string {
|
||||
scheme := GetScheme(r)
|
||||
host := GetHost(r, scheme)
|
||||
return fmt.Sprintf("%s://%s%s%s", scheme, host, r.Header.Get(PrefixHeader), r.URL.Path)
|
||||
}
|
||||
|
||||
func GetHost(r *http.Request, scheme string) string {
|
||||
host := r.Header.Get(ForwardedAPIHostHeader)
|
||||
if host != "" {
|
||||
return host
|
||||
}
|
||||
|
||||
host = strings.Split(r.Header.Get(ForwardedHostHeader), ",")[0]
|
||||
if host != "" {
|
||||
return host
|
||||
}
|
||||
|
||||
return r.Host
|
||||
}
|
||||
|
||||
func GetScheme(r *http.Request) string {
|
||||
scheme := r.Header.Get(ForwardedProtoHeader)
|
||||
if scheme != "" {
|
||||
switch scheme {
|
||||
case "ws":
|
||||
return "http"
|
||||
case "wss":
|
||||
return "https"
|
||||
default:
|
||||
return scheme
|
||||
}
|
||||
} else if r.TLS != nil {
|
||||
return "https"
|
||||
}
|
||||
return "http"
|
||||
}
|
||||
|
||||
func ParseResponseURLBase(currentURL string, r *http.Request) (string, error) {
|
||||
path := r.URL.Path
|
||||
|
||||
index := strings.LastIndex(currentURL, path)
|
||||
if index == -1 {
|
||||
// Fallback, if we can't find path in currentURL, then we just assume the base is the root of the web request
|
||||
u, err := url.Parse(currentURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buffer := bytes.Buffer{}
|
||||
buffer.WriteString(u.Scheme)
|
||||
buffer.WriteString("://")
|
||||
buffer.WriteString(u.Host)
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
return currentURL[0:index], nil
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
package urlbuilder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func RedirectRewrite(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
prefix := req.Header.Get(PrefixHeader)
|
||||
if prefix == "" {
|
||||
next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
r := &redirector{
|
||||
ResponseWriter: rw,
|
||||
prefix: prefix,
|
||||
}
|
||||
if h, ok := rw.(http.Hijacker); ok {
|
||||
r.Hijacker = h
|
||||
}
|
||||
next.ServeHTTP(r, req)
|
||||
r.Close()
|
||||
})
|
||||
}
|
||||
|
||||
type redirector struct {
|
||||
http.ResponseWriter
|
||||
http.Hijacker
|
||||
prefix string
|
||||
from, to string
|
||||
tempBuffer *bytes.Buffer
|
||||
}
|
||||
|
||||
func (r *redirector) Write(content []byte) (int, error) {
|
||||
if r.tempBuffer == nil {
|
||||
return r.ResponseWriter.Write(content)
|
||||
}
|
||||
return r.tempBuffer.Write(content)
|
||||
}
|
||||
|
||||
func (r *redirector) Close() error {
|
||||
if r.tempBuffer == nil || r.from == "" || r.to == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
content := bytes.Replace(r.tempBuffer.Bytes(), []byte(r.from), []byte(r.to), -1)
|
||||
_, err := r.ResponseWriter.Write(content)
|
||||
r.tempBuffer = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *redirector) WriteHeader(statusCode int) {
|
||||
defer func() {
|
||||
// the anonymous func is so that we take the new value of statusCode,
|
||||
// not copy it at invocation
|
||||
r.ResponseWriter.WriteHeader(statusCode)
|
||||
}()
|
||||
|
||||
if statusCode != http.StatusMovedPermanently && statusCode != http.StatusFound {
|
||||
return
|
||||
}
|
||||
|
||||
l := r.Header().Get("Location")
|
||||
if l == "" {
|
||||
return
|
||||
}
|
||||
|
||||
u, _ := url.Parse(l)
|
||||
if !strings.HasPrefix(u.Path, r.prefix) {
|
||||
r.from = u.Path
|
||||
u.Path = r.prefix + u.Path
|
||||
r.Header().Set("Location", u.String())
|
||||
r.to = u.Path
|
||||
r.tempBuffer = &bytes.Buffer{}
|
||||
}
|
||||
|
||||
statusCode = http.StatusFound
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
package urlbuilder
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/name"
|
||||
)
|
||||
|
||||
const (
|
||||
PrefixHeader = "X-API-URL-Prefix"
|
||||
ForwardedAPIHostHeader = "X-API-Host"
|
||||
ForwardedHostHeader = "X-Forwarded-Host"
|
||||
ForwardedProtoHeader = "X-Forwarded-Proto"
|
||||
ForwardedPortHeader = "X-Forwarded-Port"
|
||||
)
|
||||
|
||||
func NewPrefixed(r *http.Request, schemas *types.APISchemas, prefix string) (types.URLBuilder, error) {
|
||||
return New(r, &DefaultPathResolver{
|
||||
Prefix: prefix,
|
||||
}, schemas)
|
||||
}
|
||||
|
||||
func New(r *http.Request, resolver PathResolver, schemas *types.APISchemas) (types.URLBuilder, error) {
|
||||
requestURL := ParseRequestURL(r)
|
||||
responseURLBase, err := ParseResponseURLBase(requestURL, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
builder := &DefaultURLBuilder{
|
||||
schemas: schemas,
|
||||
currentURL: requestURL,
|
||||
responseURLBase: responseURLBase,
|
||||
pathResolver: resolver,
|
||||
query: r.URL.Query(),
|
||||
}
|
||||
|
||||
return builder, nil
|
||||
}
|
||||
|
||||
type PathResolver interface {
|
||||
Schema(base string, schema *types.APISchema) string
|
||||
}
|
||||
|
||||
type DefaultPathResolver struct {
|
||||
Prefix string
|
||||
}
|
||||
|
||||
func (d *DefaultPathResolver) Schema(base string, schema *types.APISchema) string {
|
||||
return ConstructBasicURL(base, d.Prefix, schema.PluralName)
|
||||
}
|
||||
|
||||
type DefaultURLBuilder struct {
|
||||
pathResolver PathResolver
|
||||
schemas *types.APISchemas
|
||||
currentURL string
|
||||
responseURLBase string
|
||||
query url.Values
|
||||
}
|
||||
|
||||
func (u *DefaultURLBuilder) Marker(marker string) string {
|
||||
newValues := url.Values{}
|
||||
for k, v := range u.query {
|
||||
newValues[k] = v
|
||||
}
|
||||
newValues.Set("continue", marker)
|
||||
return u.Current() + "?" + newValues.Encode()
|
||||
}
|
||||
|
||||
func (u *DefaultURLBuilder) Link(schema *types.APISchema, id string, linkName string) string {
|
||||
if strings.Contains(id, "/") {
|
||||
return u.schemaURL(schema, id, linkName)
|
||||
}
|
||||
return u.schemaURL(schema, id) + "?link=" + url.QueryEscape(linkName)
|
||||
}
|
||||
|
||||
func (u *DefaultURLBuilder) ResourceLink(schema *types.APISchema, id string) string {
|
||||
return u.schemaURL(schema, id)
|
||||
}
|
||||
|
||||
func (u *DefaultURLBuilder) Current() string {
|
||||
return u.currentURL
|
||||
}
|
||||
|
||||
func (u *DefaultURLBuilder) RelativeToRoot(path string) string {
|
||||
if len(path) > 0 && path[0] != '/' {
|
||||
return u.responseURLBase + "/" + path
|
||||
}
|
||||
return u.responseURLBase + path
|
||||
}
|
||||
|
||||
func (u *DefaultURLBuilder) Collection(schema *types.APISchema) string {
|
||||
return u.schemaURL(schema)
|
||||
}
|
||||
|
||||
func (u *DefaultURLBuilder) schemaURL(schema *types.APISchema, parts ...string) string {
|
||||
base := []string{
|
||||
u.pathResolver.Schema(u.responseURLBase, schema),
|
||||
}
|
||||
return ConstructBasicURL(append(base, parts...)...)
|
||||
}
|
||||
|
||||
func ConstructBasicURL(parts ...string) string {
|
||||
switch len(parts) {
|
||||
case 0:
|
||||
return ""
|
||||
case 1:
|
||||
return parts[0]
|
||||
default:
|
||||
base := parts[0]
|
||||
rest := path.Join(parts[1:]...)
|
||||
if !strings.HasSuffix(base, "/") && !strings.HasPrefix(rest, "/") {
|
||||
return base + "/" + rest
|
||||
}
|
||||
return base + rest
|
||||
}
|
||||
}
|
||||
|
||||
func (u *DefaultURLBuilder) getPluralName(schema *types.APISchema) string {
|
||||
if schema.PluralName == "" {
|
||||
return strings.ToLower(name.GuessPluralName(schema.ID))
|
||||
}
|
||||
return strings.ToLower(schema.PluralName)
|
||||
}
|
||||
|
||||
func (u *DefaultURLBuilder) Action(schema *types.APISchema, id, action string) string {
|
||||
return u.schemaURL(schema, id) + "?action=" + url.QueryEscape(action)
|
||||
}
|
||||
|
||||
func (u *DefaultURLBuilder) CollectionAction(schema *types.APISchema, action string) string {
|
||||
return u.schemaURL(schema) + "?action=" + url.QueryEscape(action)
|
||||
}
|
@ -1,158 +0,0 @@
|
||||
package writer
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
)
|
||||
|
||||
type EncodingResponseWriter struct {
|
||||
ContentType string
|
||||
Encoder func(io.Writer, interface{}) error
|
||||
}
|
||||
|
||||
func (j *EncodingResponseWriter) start(apiOp *types.APIRequest, code int) {
|
||||
AddCommonResponseHeader(apiOp)
|
||||
apiOp.Response.Header().Set("content-type", j.ContentType)
|
||||
apiOp.Response.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (j *EncodingResponseWriter) Write(apiOp *types.APIRequest, code int, obj types.APIObject) {
|
||||
j.start(apiOp, code)
|
||||
j.Body(apiOp, apiOp.Response, obj)
|
||||
}
|
||||
|
||||
func (j *EncodingResponseWriter) WriteList(apiOp *types.APIRequest, code int, list types.APIObjectList) {
|
||||
j.start(apiOp, code)
|
||||
j.BodyList(apiOp, apiOp.Response, list)
|
||||
}
|
||||
|
||||
func (j *EncodingResponseWriter) Body(apiOp *types.APIRequest, writer io.Writer, obj types.APIObject) error {
|
||||
return j.Encoder(writer, j.convert(apiOp, obj))
|
||||
}
|
||||
|
||||
func (j *EncodingResponseWriter) BodyList(apiOp *types.APIRequest, writer io.Writer, list types.APIObjectList) error {
|
||||
return j.Encoder(writer, j.convertList(apiOp, list))
|
||||
}
|
||||
|
||||
func (j *EncodingResponseWriter) convertList(apiOp *types.APIRequest, input types.APIObjectList) *types.GenericCollection {
|
||||
collection := newCollection(apiOp, input)
|
||||
for _, value := range input.Objects {
|
||||
converted := j.convert(apiOp, value)
|
||||
collection.Data = append(collection.Data, converted)
|
||||
}
|
||||
|
||||
if apiOp.Schema.CollectionFormatter != nil {
|
||||
apiOp.Schema.CollectionFormatter(apiOp, collection)
|
||||
}
|
||||
|
||||
if collection.Data == nil {
|
||||
collection.Data = []*types.RawResource{}
|
||||
}
|
||||
|
||||
return collection
|
||||
}
|
||||
|
||||
func (j *EncodingResponseWriter) convert(context *types.APIRequest, input types.APIObject) *types.RawResource {
|
||||
schema := context.Schemas.LookupSchema(input.Type)
|
||||
if schema == nil {
|
||||
schema = context.Schema
|
||||
}
|
||||
if schema == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
rawResource := &types.RawResource{
|
||||
ID: input.ID,
|
||||
Type: schema.ID,
|
||||
Schema: schema,
|
||||
Links: map[string]string{},
|
||||
Actions: map[string]string{},
|
||||
ActionLinks: context.Request.Header.Get("X-API-Action-Links") != "",
|
||||
APIObject: input,
|
||||
}
|
||||
|
||||
j.addLinks(schema, context, input, rawResource)
|
||||
|
||||
if schema.Formatter != nil {
|
||||
schema.Formatter(context, rawResource)
|
||||
}
|
||||
|
||||
return rawResource
|
||||
}
|
||||
|
||||
func (j *EncodingResponseWriter) addLinks(schema *types.APISchema, context *types.APIRequest, input types.APIObject, rawResource *types.RawResource) {
|
||||
if rawResource.ID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
self := context.URLBuilder.ResourceLink(rawResource.Schema, rawResource.ID)
|
||||
if _, ok := rawResource.Links["self"]; !ok {
|
||||
rawResource.Links["self"] = self
|
||||
}
|
||||
if _, ok := rawResource.Links["update"]; !ok {
|
||||
if context.AccessControl.CanUpdate(context, input, schema) == nil {
|
||||
rawResource.Links["update"] = self
|
||||
}
|
||||
}
|
||||
if _, ok := rawResource.Links["remove"]; !ok {
|
||||
if context.AccessControl.CanDelete(context, input, schema) == nil {
|
||||
rawResource.Links["remove"] = self
|
||||
}
|
||||
}
|
||||
for link := range schema.LinkHandlers {
|
||||
rawResource.Links[link] = context.URLBuilder.Link(schema, rawResource.ID, link)
|
||||
}
|
||||
for action := range schema.ActionHandlers {
|
||||
if rawResource.Actions == nil {
|
||||
rawResource.Actions = map[string]string{}
|
||||
}
|
||||
rawResource.Actions[action] = context.URLBuilder.Action(schema, rawResource.ID, action)
|
||||
}
|
||||
}
|
||||
|
||||
func getLimit(req *http.Request) int {
|
||||
limit, err := strconv.Atoi(req.Header.Get("limit"))
|
||||
if err == nil && limit > 0 {
|
||||
return limit
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func newCollection(apiOp *types.APIRequest, list types.APIObjectList) *types.GenericCollection {
|
||||
result := &types.GenericCollection{
|
||||
Collection: types.Collection{
|
||||
Type: "collection",
|
||||
ResourceType: apiOp.Type,
|
||||
CreateTypes: map[string]string{},
|
||||
Links: map[string]string{
|
||||
"self": apiOp.URLBuilder.Current(),
|
||||
},
|
||||
Actions: map[string]string{},
|
||||
Continue: list.Continue,
|
||||
Revision: list.Revision,
|
||||
},
|
||||
}
|
||||
|
||||
partial := list.Continue != "" || apiOp.Query.Get("continue") != ""
|
||||
if partial {
|
||||
result.Pagination = &types.Pagination{
|
||||
Limit: getLimit(apiOp.Request),
|
||||
First: apiOp.URLBuilder.Current(),
|
||||
Partial: true,
|
||||
}
|
||||
if list.Continue != "" {
|
||||
result.Pagination.Next = apiOp.URLBuilder.Marker(list.Continue)
|
||||
}
|
||||
}
|
||||
|
||||
if apiOp.Method == http.MethodGet {
|
||||
if apiOp.AccessControl.CanCreate(apiOp, apiOp.Schema) == nil {
|
||||
result.CreateTypes[apiOp.Schema.ID] = apiOp.URLBuilder.Collection(apiOp.Schema)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
package writer
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
)
|
||||
|
||||
type GzipWriter struct {
|
||||
types.ResponseWriter
|
||||
}
|
||||
|
||||
func setup(apiOp *types.APIRequest) (*types.APIRequest, io.Closer) {
|
||||
if !strings.Contains(apiOp.Request.Header.Get("Accept-Encoding"), "gzip") {
|
||||
return apiOp, ioutil.NopCloser(nil)
|
||||
}
|
||||
|
||||
apiOp.Response.Header().Set("Content-Encoding", "gzip")
|
||||
apiOp.Response.Header().Del("Content-Length")
|
||||
|
||||
gz := gzip.NewWriter(apiOp.Response)
|
||||
gzw := &gzipResponseWriter{Writer: gz, ResponseWriter: apiOp.Response}
|
||||
|
||||
newOp := *apiOp
|
||||
newOp.Response = gzw
|
||||
return &newOp, gz
|
||||
}
|
||||
|
||||
func (g *GzipWriter) Write(apiOp *types.APIRequest, code int, obj types.APIObject) {
|
||||
apiOp, closer := setup(apiOp)
|
||||
defer closer.Close()
|
||||
g.ResponseWriter.Write(apiOp, code, obj)
|
||||
}
|
||||
|
||||
func (g *GzipWriter) WriteList(apiOp *types.APIRequest, code int, obj types.APIObjectList) {
|
||||
apiOp, closer := setup(apiOp)
|
||||
defer closer.Close()
|
||||
g.ResponseWriter.WriteList(apiOp, code, obj)
|
||||
}
|
||||
|
||||
type gzipResponseWriter struct {
|
||||
io.Writer
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func (g gzipResponseWriter) Write(b []byte) (int, error) {
|
||||
return g.Writer.Write(b)
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package writer
|
||||
|
||||
import (
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
)
|
||||
|
||||
func AddCommonResponseHeader(apiOp *types.APIRequest) error {
|
||||
addExpires(apiOp)
|
||||
return addSchemasHeader(apiOp)
|
||||
}
|
||||
|
||||
func addSchemasHeader(apiOp *types.APIRequest) error {
|
||||
schema := apiOp.Schemas.Schemas["schema"]
|
||||
if schema == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
apiOp.Response.Header().Set("X-Api-Schemas", apiOp.URLBuilder.Collection(schema))
|
||||
return nil
|
||||
}
|
||||
|
||||
func addExpires(apiOp *types.APIRequest) {
|
||||
apiOp.Response.Header().Set("Expires", "Wed 24 Feb 1982 18:42:00 GMT")
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
package writer
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
)
|
||||
|
||||
const (
|
||||
JSURL = "https://releases.rancher.com/api-ui/%API_UI_VERSION%/ui.min.js"
|
||||
CSSURL = "https://releases.rancher.com/api-ui/%API_UI_VERSION%/ui.min.css"
|
||||
DefaultVersion = "1.1.8"
|
||||
)
|
||||
|
||||
var (
|
||||
start = `
|
||||
<!DOCTYPE html>
|
||||
<!-- If you are reading this, there is a good chance you would prefer sending an
|
||||
"Accept: application/json" header and receiving actual JSON responses. -->
|
||||
<link rel="stylesheet" type="text/css" href="%CSSURL%" />
|
||||
<script src="%JSURL%"></script>
|
||||
<script>
|
||||
var user = "admin";
|
||||
var curlUser='${CATTLE_ACCESS_KEY}:${CATTLE_SECRET_KEY}';
|
||||
var schemas="%SCHEMAS%";
|
||||
var data =
|
||||
`
|
||||
end = []byte(`</script>
|
||||
`)
|
||||
)
|
||||
|
||||
type StringGetter func() string
|
||||
|
||||
type HTMLResponseWriter struct {
|
||||
EncodingResponseWriter
|
||||
CSSURL StringGetter
|
||||
JSURL StringGetter
|
||||
APIUIVersion StringGetter
|
||||
}
|
||||
|
||||
func (h *HTMLResponseWriter) start(apiOp *types.APIRequest, code int) {
|
||||
AddCommonResponseHeader(apiOp)
|
||||
apiOp.Response.Header().Set("content-type", "text/html")
|
||||
apiOp.Response.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (h *HTMLResponseWriter) Write(apiOp *types.APIRequest, code int, obj types.APIObject) {
|
||||
h.write(apiOp, code, obj)
|
||||
}
|
||||
|
||||
func (h *HTMLResponseWriter) WriteList(apiOp *types.APIRequest, code int, list types.APIObjectList) {
|
||||
h.write(apiOp, code, list)
|
||||
}
|
||||
|
||||
func (h *HTMLResponseWriter) write(apiOp *types.APIRequest, code int, obj interface{}) {
|
||||
h.start(apiOp, code)
|
||||
schemaSchema := apiOp.Schemas.Schemas["schema"]
|
||||
headerString := start
|
||||
if schemaSchema != nil {
|
||||
headerString = strings.Replace(headerString, "%SCHEMAS%", apiOp.URLBuilder.Collection(schemaSchema), 1)
|
||||
}
|
||||
var jsurl, cssurl string
|
||||
if h.CSSURL != nil && h.JSURL != nil && h.CSSURL() != "" && h.JSURL() != "" {
|
||||
jsurl = h.JSURL()
|
||||
cssurl = h.CSSURL()
|
||||
} else if h.APIUIVersion != nil && h.APIUIVersion() != "" {
|
||||
jsurl = strings.Replace(JSURL, "%API_UI_VERSION%", h.APIUIVersion(), 1)
|
||||
cssurl = strings.Replace(CSSURL, "%API_UI_VERSION%", h.APIUIVersion(), 1)
|
||||
} else {
|
||||
jsurl = strings.Replace(JSURL, "%API_UI_VERSION%", DefaultVersion, 1)
|
||||
cssurl = strings.Replace(CSSURL, "%API_UI_VERSION%", DefaultVersion, 1)
|
||||
}
|
||||
headerString = strings.Replace(headerString, "%JSURL%", jsurl, 1)
|
||||
headerString = strings.Replace(headerString, "%CSSURL%", cssurl, 1)
|
||||
|
||||
apiOp.Response.Write([]byte(headerString))
|
||||
if apiObj, ok := obj.(types.APIObject); ok {
|
||||
h.Body(apiOp, apiOp.Response, apiObj)
|
||||
} else if list, ok := obj.(types.APIObjectList); ok {
|
||||
h.BodyList(apiOp, apiOp.Response, list)
|
||||
}
|
||||
if schemaSchema != nil {
|
||||
apiOp.Response.Write(end)
|
||||
}
|
||||
}
|
@ -5,11 +5,11 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/accesscontrol"
|
||||
"github.com/rancher/steve/pkg/auth"
|
||||
"github.com/rancher/steve/pkg/client"
|
||||
"github.com/rancher/steve/pkg/schema"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/steve/pkg/server/router"
|
||||
"github.com/rancher/wrangler-api/pkg/generated/controllers/apiextensions.k8s.io"
|
||||
apiextensionsv1beta1 "github.com/rancher/wrangler-api/pkg/generated/controllers/apiextensions.k8s.io/v1beta1"
|
||||
|
@ -3,13 +3,13 @@ package handler
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/server"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/apiserver/pkg/urlbuilder"
|
||||
"github.com/rancher/steve/pkg/accesscontrol"
|
||||
"github.com/rancher/steve/pkg/auth"
|
||||
k8sproxy "github.com/rancher/steve/pkg/proxy"
|
||||
"github.com/rancher/steve/pkg/schema"
|
||||
"github.com/rancher/steve/pkg/schemaserver/server"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/steve/pkg/schemaserver/urlbuilder"
|
||||
"github.com/rancher/steve/pkg/server/router"
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
|
@ -2,9 +2,9 @@ package handler
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/schema"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
)
|
||||
|
||||
func k8sAPI(sf schema.Factory, apiOp *types.APIRequest) {
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rancher/steve/pkg/schemaserver/urlbuilder"
|
||||
"github.com/rancher/apiserver/pkg/urlbuilder"
|
||||
)
|
||||
|
||||
type RouterFunc func(h Handlers) http.Handler
|
||||
|
@ -5,18 +5,18 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/dynamiclistener/server"
|
||||
"github.com/rancher/steve/pkg/accesscontrol"
|
||||
"github.com/rancher/steve/pkg/client"
|
||||
"github.com/rancher/steve/pkg/clustercache"
|
||||
schemacontroller "github.com/rancher/steve/pkg/controllers/schema"
|
||||
"github.com/rancher/steve/pkg/dashboard"
|
||||
"github.com/rancher/steve/pkg/resources"
|
||||
"github.com/rancher/steve/pkg/resources/common"
|
||||
"github.com/rancher/steve/pkg/resources/schemas"
|
||||
"github.com/rancher/steve/pkg/schema"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/steve/pkg/server/handler"
|
||||
"github.com/rancher/steve/pkg/server/resources"
|
||||
"github.com/rancher/steve/pkg/server/resources/common"
|
||||
"github.com/rancher/steve/pkg/server/resources/schemas"
|
||||
)
|
||||
|
||||
var ErrConfigRequired = errors.New("rest config is required")
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
@ -5,7 +5,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
@ -1,8 +1,8 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"github.com/rancher/steve/pkg/schemaserver/httperror"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/apiserver/pkg/apierror"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
)
|
||||
@ -47,7 +47,7 @@ func (e *errorStore) Watch(apiOp *types.APIRequest, schema *types.APISchema, wr
|
||||
func translateError(err error) error {
|
||||
if apiError, ok := err.(errors.APIStatus); ok {
|
||||
status := apiError.Status()
|
||||
return httperror.NewAPIError(validation.ErrorCode{
|
||||
return apierror.NewAPIError(validation.ErrorCode{
|
||||
Status: int(status.Code),
|
||||
Code: string(status.Reason),
|
||||
}, status.Message)
|
@ -10,10 +10,10 @@ import (
|
||||
"regexp"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/accesscontrol"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/steve/pkg/server/store/partition"
|
||||
"github.com/rancher/steve/pkg/stores/partition"
|
||||
"github.com/rancher/wrangler/pkg/data"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
"github.com/sirupsen/logrus"
|
@ -6,10 +6,10 @@ import (
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/accesscontrol"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/steve/pkg/server/store/partition"
|
||||
"github.com/rancher/steve/pkg/stores/partition"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/accesscontrol"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package selector
|
||||
|
||||
import (
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package switchschema
|
||||
|
||||
import (
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
)
|
||||
|
||||
type Store struct {
|
@ -1,7 +1,7 @@
|
||||
package switchstore
|
||||
|
||||
import (
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
)
|
||||
|
||||
type StorePicker func(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) (types.Store, error)
|
Loading…
Reference in New Issue
Block a user