blob: cc988665bf44ee47f89170d8335f912dec17cb00 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package rest
import (
"encoding/json"
"fmt"
"net/url"
"strings"
)
import (
"github.com/pkg/errors"
"k8s.io/kube-openapi/pkg/validation/strfmt"
"k8s.io/kube-openapi/pkg/validation/validate"
"sigs.k8s.io/yaml"
)
import (
core_model "github.com/apache/dubbo-kubernetes/pkg/core/resources/model"
"github.com/apache/dubbo-kubernetes/pkg/core/resources/model/rest/v1alpha1"
"github.com/apache/dubbo-kubernetes/pkg/core/resources/registry"
"github.com/apache/dubbo-kubernetes/pkg/core/validators"
)
var YAML = &unmarshaler{unmarshalFn: func(bytes []byte, i interface{}) error {
return yaml.Unmarshal(bytes, i)
}}
var JSON = &unmarshaler{unmarshalFn: json.Unmarshal}
type unmarshaler struct {
unmarshalFn func([]byte, interface{}) error
}
type InvalidResourceError struct {
Reason string
}
func (e *InvalidResourceError) Error() string {
return e.Reason
}
func (e *InvalidResourceError) Is(target error) bool {
t, ok := target.(*InvalidResourceError)
if !ok {
return false
}
return t.Reason == e.Reason || t.Reason == ""
}
func (u *unmarshaler) UnmarshalCore(bytes []byte) (core_model.Resource, error) {
m := v1alpha1.ResourceMeta{}
if err := u.unmarshalFn(bytes, &m); err != nil {
return nil, &InvalidResourceError{Reason: fmt.Sprintf("invalid meta type: %q", err.Error())}
}
desc, err := registry.Global().DescriptorFor(core_model.ResourceType(m.Type))
if err != nil {
return nil, err
}
restResource, err := u.Unmarshal(bytes, desc)
if err != nil {
return nil, err
}
coreRes, err := To.Core(restResource)
if err != nil {
return nil, err
}
return coreRes, nil
}
func (u *unmarshaler) Unmarshal(bytes []byte, desc core_model.ResourceTypeDescriptor) (Resource, error) {
resource := desc.NewObject()
restResource := From.Resource(resource)
if desc.IsPluginOriginated {
// desc.Schema is set only for new plugin originated policies
rawObj := map[string]interface{}{}
// Unfortunately to validate new policies we must first unmarshal into a rawObj
if err := u.unmarshalFn(bytes, &rawObj); err != nil {
return nil, &InvalidResourceError{Reason: fmt.Sprintf("invalid %s object: %q", desc.Name, err.Error())}
}
validator := validate.NewSchemaValidator(desc.Schema, nil, "", strfmt.Default)
res := validator.Validate(rawObj)
if !res.IsValid() {
return nil, toValidationError(res)
}
}
if err := u.unmarshalFn(bytes, restResource); err != nil {
return nil, &InvalidResourceError{Reason: fmt.Sprintf("invalid %s object: %q", desc.Name, err.Error())}
}
if err := core_model.Validate(resource); err != nil {
return nil, err
}
return restResource, nil
}
func (u *unmarshaler) UnmarshalListToCore(b []byte, rs core_model.ResourceList) error {
rsr := &ResourceListReceiver{
NewResource: rs.NewItem,
}
if err := u.unmarshalFn(b, rsr); err != nil {
return err
}
for _, ri := range rsr.ResourceList.Items {
r := rs.NewItem()
if err := r.SetSpec(ri.GetSpec()); err != nil {
return err
}
r.SetMeta(ri.GetMeta())
_ = rs.AddItem(r)
}
if rsr.Next != nil {
uri, err := url.ParseRequestURI(*rsr.Next)
if err != nil {
return errors.Wrap(err, "invalid next URL from the server")
}
offset := uri.Query().Get("offset")
// we do not preserve here the size of the page, but since it is used in dubboctl
// user will rerun command with the page size of his choice
if offset != "" {
rs.GetPagination().SetNextOffset(offset)
}
}
rs.GetPagination().SetTotal(rsr.ResourceList.Total)
return nil
}
func toValidationError(res *validate.Result) *validators.ValidationError {
verr := &validators.ValidationError{}
for _, e := range res.Errors {
parts := strings.Split(e.Error(), " ")
if len(parts) > 1 && strings.HasPrefix(parts[0], "spec.") {
verr.AddViolation(parts[0], strings.Join(parts[1:], " "))
} else {
verr.AddViolation("", e.Error())
}
}
return verr
}