mirror of
https://github.com/rancher/norman.git
synced 2025-09-03 08:14:40 +00:00
add yaml support
This commit is contained in:
committed by
Darren Shepherd
parent
79b91ea33c
commit
59c4a298e8
@@ -2,7 +2,6 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/rancher/norman/api/access"
|
"github.com/rancher/norman/api/access"
|
||||||
@@ -51,8 +50,20 @@ func NewAPIServer() *Server {
|
|||||||
s := &Server{
|
s := &Server{
|
||||||
Schemas: types.NewSchemas(),
|
Schemas: types.NewSchemas(),
|
||||||
ResponseWriters: map[string]ResponseWriter{
|
ResponseWriters: map[string]ResponseWriter{
|
||||||
"json": &writer.JSONResponseWriter{},
|
"json": &writer.EncodingResponseWriter{
|
||||||
"html": &writer.HTMLResponseWriter{},
|
ContentType: "application/json",
|
||||||
|
Encoder: types.JSONEncoder,
|
||||||
|
},
|
||||||
|
"html": &writer.HTMLResponseWriter{
|
||||||
|
EncodingResponseWriter: writer.EncodingResponseWriter{
|
||||||
|
Encoder: types.JSONEncoder,
|
||||||
|
ContentType: "application/json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"yaml": &writer.EncodingResponseWriter{
|
||||||
|
ContentType: "application/yaml",
|
||||||
|
Encoder: types.YAMLEncoder,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
SubContextAttributeProvider: &parse.DefaultSubContextAttributeProvider{},
|
SubContextAttributeProvider: &parse.DefaultSubContextAttributeProvider{},
|
||||||
Resolver: parse.DefaultResolver,
|
Resolver: parse.DefaultResolver,
|
||||||
|
@@ -25,7 +25,7 @@ var data =
|
|||||||
)
|
)
|
||||||
|
|
||||||
type HTMLResponseWriter struct {
|
type HTMLResponseWriter struct {
|
||||||
JSONResponseWriter
|
EncodingResponseWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTMLResponseWriter) start(apiContext *types.APIContext, code int, obj interface{}) {
|
func (h *HTMLResponseWriter) start(apiContext *types.APIContext, code int, obj interface{}) {
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
package writer
|
package writer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -13,26 +12,28 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type JSONResponseWriter struct {
|
type EncodingResponseWriter struct {
|
||||||
|
ContentType string
|
||||||
|
Encoder func(io.Writer, interface{}) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *JSONResponseWriter) start(apiContext *types.APIContext, code int, obj interface{}) {
|
func (j *EncodingResponseWriter) start(apiContext *types.APIContext, code int, obj interface{}) {
|
||||||
AddCommonResponseHeader(apiContext)
|
AddCommonResponseHeader(apiContext)
|
||||||
apiContext.Response.Header().Set("content-type", "application/json")
|
apiContext.Response.Header().Set("content-type", j.ContentType)
|
||||||
apiContext.Response.WriteHeader(code)
|
apiContext.Response.WriteHeader(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *JSONResponseWriter) Write(apiContext *types.APIContext, code int, obj interface{}) {
|
func (j *EncodingResponseWriter) Write(apiContext *types.APIContext, code int, obj interface{}) {
|
||||||
j.start(apiContext, code, obj)
|
j.start(apiContext, code, obj)
|
||||||
j.Body(apiContext, apiContext.Response, obj)
|
j.Body(apiContext, apiContext.Response, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *JSONResponseWriter) Body(apiContext *types.APIContext, writer io.Writer, obj interface{}) error {
|
func (j *EncodingResponseWriter) Body(apiContext *types.APIContext, writer io.Writer, obj interface{}) error {
|
||||||
return j.VersionBody(apiContext, apiContext.Version, writer, obj)
|
return j.VersionBody(apiContext, apiContext.Version, writer, obj)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *JSONResponseWriter) VersionBody(apiContext *types.APIContext, version *types.APIVersion, writer io.Writer, obj interface{}) error {
|
func (j *EncodingResponseWriter) VersionBody(apiContext *types.APIContext, version *types.APIVersion, writer io.Writer, obj interface{}) error {
|
||||||
var output interface{}
|
var output interface{}
|
||||||
|
|
||||||
builder := builder.NewBuilder(apiContext)
|
builder := builder.NewBuilder(apiContext)
|
||||||
@@ -50,12 +51,12 @@ func (j *JSONResponseWriter) VersionBody(apiContext *types.APIContext, version *
|
|||||||
}
|
}
|
||||||
|
|
||||||
if output != nil {
|
if output != nil {
|
||||||
return json.NewEncoder(writer).Encode(output)
|
return j.Encoder(writer, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (j *JSONResponseWriter) writeMapSlice(builder *builder.Builder, apiContext *types.APIContext, input []map[string]interface{}) *types.GenericCollection {
|
func (j *EncodingResponseWriter) writeMapSlice(builder *builder.Builder, apiContext *types.APIContext, input []map[string]interface{}) *types.GenericCollection {
|
||||||
collection := newCollection(apiContext)
|
collection := newCollection(apiContext)
|
||||||
for _, value := range input {
|
for _, value := range input {
|
||||||
converted := j.convert(builder, apiContext, value)
|
converted := j.convert(builder, apiContext, value)
|
||||||
@@ -71,7 +72,7 @@ func (j *JSONResponseWriter) writeMapSlice(builder *builder.Builder, apiContext
|
|||||||
return collection
|
return collection
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *JSONResponseWriter) writeInterfaceSlice(builder *builder.Builder, apiContext *types.APIContext, input []interface{}) *types.GenericCollection {
|
func (j *EncodingResponseWriter) writeInterfaceSlice(builder *builder.Builder, apiContext *types.APIContext, input []interface{}) *types.GenericCollection {
|
||||||
collection := newCollection(apiContext)
|
collection := newCollection(apiContext)
|
||||||
for _, value := range input {
|
for _, value := range input {
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
@@ -99,7 +100,7 @@ func toString(val interface{}) string {
|
|||||||
return fmt.Sprint(val)
|
return fmt.Sprint(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *JSONResponseWriter) convert(b *builder.Builder, context *types.APIContext, input map[string]interface{}) *types.RawResource {
|
func (j *EncodingResponseWriter) convert(b *builder.Builder, context *types.APIContext, input map[string]interface{}) *types.RawResource {
|
||||||
schema := context.Schemas.Schema(context.Version, definition.GetFullType(input))
|
schema := context.Schemas.Schema(context.Version, definition.GetFullType(input))
|
||||||
if schema == nil {
|
if schema == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -133,7 +134,7 @@ func (j *JSONResponseWriter) convert(b *builder.Builder, context *types.APIConte
|
|||||||
return rawResource
|
return rawResource
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *JSONResponseWriter) addLinks(b *builder.Builder, schema *types.Schema, context *types.APIContext, input map[string]interface{}, rawResource *types.RawResource) {
|
func (j *EncodingResponseWriter) addLinks(b *builder.Builder, schema *types.Schema, context *types.APIContext, input map[string]interface{}, rawResource *types.RawResource) {
|
||||||
if rawResource.ID == "" {
|
if rawResource.ID == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -23,6 +23,7 @@ var (
|
|||||||
allowedFormats = map[string]bool{
|
allowedFormats = map[string]bool{
|
||||||
"html": true,
|
"html": true,
|
||||||
"json": true,
|
"json": true,
|
||||||
|
"yaml": true,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -259,9 +260,17 @@ func parseResponseFormat(req *http.Request) string {
|
|||||||
if IsBrowser(req, true) {
|
if IsBrowser(req, true) {
|
||||||
return "html"
|
return "html"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isYaml(req) {
|
||||||
|
return "yaml"
|
||||||
|
}
|
||||||
return "json"
|
return "json"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isYaml(req *http.Request) bool {
|
||||||
|
return strings.Contains(req.Header.Get("Accept"), "application/yaml")
|
||||||
|
}
|
||||||
|
|
||||||
func parseMethod(req *http.Request) string {
|
func parseMethod(req *http.Request) string {
|
||||||
method := req.URL.Query().Get("_method")
|
method := req.URL.Query().Get("_method")
|
||||||
if method == "" {
|
if method == "" {
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/rancher/norman/httperror"
|
"github.com/rancher/norman/httperror"
|
||||||
|
"k8s.io/apimachinery/pkg/util/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
const reqMaxSize = (2 * 1 << 20) + 1
|
const reqMaxSize = (2 * 1 << 20) + 1
|
||||||
@@ -16,19 +17,29 @@ var bodyMethods = map[string]bool{
|
|||||||
http.MethodPost: true,
|
http.MethodPost: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Decode func(interface{}) error
|
||||||
|
|
||||||
func ReadBody(req *http.Request) (map[string]interface{}, error) {
|
func ReadBody(req *http.Request) (map[string]interface{}, error) {
|
||||||
if !bodyMethods[req.Method] {
|
if !bodyMethods[req.Method] {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
dec := json.NewDecoder(io.LimitReader(req.Body, reqMaxSize))
|
decode := getDecoder(req, io.LimitReader(req.Body, maxFormSize))
|
||||||
dec.UseNumber()
|
|
||||||
|
|
||||||
data := map[string]interface{}{}
|
data := map[string]interface{}{}
|
||||||
if err := dec.Decode(&data); err != nil {
|
if err := decode(&data); err != nil {
|
||||||
return nil, httperror.NewAPIError(httperror.InvalidBodyContent,
|
return nil, httperror.NewAPIError(httperror.InvalidBodyContent,
|
||||||
fmt.Sprintf("Failed to parse body: %v", err))
|
fmt.Sprintf("Failed to parse body: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
@@ -85,7 +85,10 @@ func handler(apiContext *types.APIContext) error {
|
|||||||
close(events)
|
close(events)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
jsonWriter := writer.JSONResponseWriter{}
|
jsonWriter := writer.EncodingResponseWriter{
|
||||||
|
ContentType: "application/json",
|
||||||
|
Encoder: types.JSONEncoder,
|
||||||
|
}
|
||||||
t := time.NewTicker(5 * time.Second)
|
t := time.NewTicker(5 * time.Second)
|
||||||
defer t.Stop()
|
defer t.Stop()
|
||||||
|
|
||||||
|
25
types/encoder.go
Normal file
25
types/encoder.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
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
|
||||||
|
}
|
Reference in New Issue
Block a user