github.com/zntrio/harp/v2@v2.0.9/pkg/sdk/convert/yaml.go (about) 1 // Licensed to Elasticsearch B.V. under one or more contributor 2 // license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright 4 // ownership. Elasticsearch B.V. licenses this file to you under 5 // the Apache License, Version 2.0 (the "License"); you may 6 // not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, 12 // software distributed under the License is distributed on an 13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 // KIND, either express or implied. See the License for the 15 // specific language governing permissions and limitations 16 // under the License. 17 18 package convert 19 20 import ( 21 "bytes" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "io" 26 "reflect" 27 28 "google.golang.org/protobuf/encoding/protojson" 29 "google.golang.org/protobuf/proto" 30 "sigs.k8s.io/yaml" 31 32 "github.com/zntrio/harp/v2/pkg/sdk/types" 33 ) 34 35 var maxPayloadSize = int64(25 << 20) // 25MB 36 37 // YAMLtoJSON reads a given reader in order to extract a JSON representation. 38 func YAMLtoJSON(r io.Reader) (io.Reader, error) { 39 // Check arguments 40 if types.IsNil(r) { 41 return nil, fmt.Errorf("reader is nil") 42 } 43 44 // Drain the reader 45 jsonReader, err := loadFromYAML(r) 46 if err != nil { 47 return nil, fmt.Errorf("unable to parse YAML input: %w", err) 48 } 49 50 // No error 51 return jsonReader, nil 52 } 53 54 // PBtoYAML converts a protobuf object to a YAML representation. 55 func PBtoYAML(msg proto.Message) ([]byte, error) { 56 // Check arguments 57 if types.IsNil(msg) { 58 return nil, fmt.Errorf("msg is nil") 59 } 60 61 // Encode protbuf message as JSON 62 pb, err := protojson.Marshal(msg) 63 if err != nil { 64 return nil, fmt.Errorf("unable to encode protbuf message to JSON: %w", err) 65 } 66 67 // Decode input as JSON 68 var jsonObj interface{} 69 if errDecode := yaml.Unmarshal(pb, &jsonObj); errDecode != nil { 70 return nil, fmt.Errorf("unable to decode JSON input: %w", errDecode) 71 } 72 73 // Marshal as YAML 74 out, errEncode := yaml.Marshal(jsonObj) 75 if errEncode != nil { 76 return nil, fmt.Errorf("unable to produce YAML output: %w", errEncode) 77 } 78 79 // No error 80 return out, nil 81 } 82 83 // ----------------------------------------------------------------------------- 84 85 // loadFromYAML reads YAML definition and returns the PB struct. 86 // 87 // Protobuf doesn't contain YAML struct tags and json one are not symetric 88 // to protobuf. We need to export YAML as JSON, and then read JSON to Protobuf 89 // as done in k8s yaml loader. 90 func loadFromYAML(r io.Reader) (io.Reader, error) { 91 // Check arguments 92 if types.IsNil(r) { 93 return nil, fmt.Errorf("reader is nil") 94 } 95 96 // Drain input reader 97 in, err := io.ReadAll(io.LimitReader(r, maxPayloadSize)) 98 if err != nil && !errors.Is(err, io.EOF) { 99 return nil, fmt.Errorf("unable to drain input reader: %w", err) 100 } 101 102 // Decode as YAML any object 103 var specBody interface{} 104 if errYaml := yaml.Unmarshal(in, &specBody); errYaml != nil { 105 return nil, fmt.Errorf("unable to decode spec as YAML: %w", err) 106 } 107 108 // Convert map[interface{}]interface{} to a JSON serializable struct 109 specBody, err = convertMapStringInterface(specBody) 110 if err != nil { 111 return nil, fmt.Errorf("unable to prepare spec to json transformation: %w", err) 112 } 113 114 // Marshal as json 115 jsonData, err := json.Marshal(specBody) 116 if err != nil { 117 return nil, fmt.Errorf("unable ot marshal spec as JSON: %w", err) 118 } 119 120 // No error 121 return bytes.NewReader(jsonData), nil 122 } 123 124 // Converts map[interface{}]interface{} into map[string]interface{} for json.Marshaler. 125 func convertMapStringInterface(val interface{}) (interface{}, error) { 126 switch items := val.(type) { 127 case map[interface{}]interface{}: 128 result := map[string]interface{}{} 129 for k, v := range items { 130 key, ok := k.(string) 131 if !ok { 132 return nil, fmt.Errorf("TypeError: value %s (type `%s') can't be assigned to type 'string'", k, reflect.TypeOf(k)) 133 } 134 value, err := convertMapStringInterface(v) 135 if err != nil { 136 return nil, fmt.Errorf("unable to convert map[string] to map[interface{}]: %w", err) 137 } 138 result[key] = value 139 } 140 return result, nil 141 case []interface{}: 142 for k, v := range items { 143 value, err := convertMapStringInterface(v) 144 if err != nil { 145 return nil, fmt.Errorf("unable to convert map[string] to map[interface{}]: %w", err) 146 } 147 items[k] = value 148 } 149 } 150 return val, nil 151 }