Shuffle around code and use rancher/apiserver

This commit is contained in:
Darren Shepherd 2020-06-11 21:50:59 -07:00
parent 2543926f99
commit d1a7dbb0b9
86 changed files with 105 additions and 3224 deletions

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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"
)

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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)

View File

@ -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"
)

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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) {

View File

@ -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"
)

View File

@ -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)

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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)
})
}

View File

@ -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))
}

View File

@ -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))
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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"

View File

@ -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 ""
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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,
}
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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, "*/*")
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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))
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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"
})
}

View File

@ -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,
}
}

View File

@ -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
}

View File

@ -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)]
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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")
}

View File

@ -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)
}
}

View File

@ -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"

View File

@ -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"

View File

@ -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) {

View File

@ -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

View File

@ -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")

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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)

View File

@ -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"

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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"
)

View File

@ -1,7 +1,7 @@
package switchschema
import (
"github.com/rancher/steve/pkg/schemaserver/types"
"github.com/rancher/apiserver/pkg/types"
)
type Store struct {

View File

@ -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)