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