| /* |
| * 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 |
| } |