@@ -4,9 +4,11 @@ import (
44 "encoding/base64"
55 "fmt"
66 "net/url"
7+ "reflect"
78 "strings"
89 "time"
910
11+ "github.com/goccy/go-yaml"
1012 "github.com/google/uuid"
1113 "github.com/inhies/go-bytesize"
1214 "github.com/spf13/cobra"
@@ -153,3 +155,109 @@ func ConvertStringMapToInterfaceMap(m *map[string]string) *map[string]interface{
153155 }
154156 return & result
155157}
158+
159+ // Base64Bytes implements yaml.Marshaler to convert []byte to base64 strings
160+ // ref: https://carlosbecker.com/posts/go-custom-marshaling
161+ type Base64Bytes []byte
162+
163+ // MarshalYAML implements yaml.Marshaler
164+ func (b Base64Bytes ) MarshalYAML () (interface {}, error ) {
165+ if len (b ) == 0 {
166+ return "" , nil
167+ }
168+ return base64 .StdEncoding .EncodeToString (b ), nil
169+ }
170+
171+ // MarshalToYAMLWithBase64Bytes converts any struct to YAML with []byte fields as base64 strings
172+ func MarshalToYAMLWithBase64Bytes (data interface {}) ([]byte , error ) {
173+ // Convert the data to a map and replace []byte fields with Base64Bytes
174+ converted := convertToMapWithBase64Bytes (data )
175+ return yaml .MarshalWithOptions (converted , yaml .IndentSequence (true ))
176+ }
177+
178+ // convertToMapWithBase64Bytes converts any data to a map, replacing []byte fields with Base64Bytes
179+ // using the custom type that implements yaml.Marshaler
180+ func convertToMapWithBase64Bytes (data interface {}) interface {} {
181+ if data == nil {
182+ return nil
183+ }
184+
185+ v := reflect .ValueOf (data )
186+ t := reflect .TypeOf (data )
187+
188+ // Handle pointers
189+ if v .Kind () == reflect .Ptr {
190+ if v .IsNil () {
191+ return nil
192+ }
193+ return convertToMapWithBase64Bytes (v .Elem ().Interface ())
194+ }
195+
196+ // Handle slices
197+ if v .Kind () == reflect .Slice {
198+ if v .IsNil () {
199+ return nil
200+ }
201+ result := make ([]interface {}, v .Len ())
202+ for i := 0 ; i < v .Len (); i ++ {
203+ result [i ] = convertToMapWithBase64Bytes (v .Index (i ).Interface ())
204+ }
205+ return result
206+ }
207+
208+ // Handle maps
209+ if v .Kind () == reflect .Map {
210+ if v .IsNil () {
211+ return nil
212+ }
213+ result := make (map [string ]interface {})
214+ for _ , key := range v .MapKeys () {
215+ result [fmt .Sprintf ("%v" , key )] = convertToMapWithBase64Bytes (v .MapIndex (key ).Interface ())
216+ }
217+ return result
218+ }
219+
220+ // Handle structs
221+ if v .Kind () == reflect .Struct {
222+ result := make (map [string ]interface {})
223+ for i := 0 ; i < v .NumField (); i ++ {
224+ field := v .Field (i )
225+ fieldType := t .Field (i )
226+
227+ if ! field .CanInterface () {
228+ continue
229+ }
230+
231+ fieldName := fieldType .Name
232+ if jsonTag := fieldType .Tag .Get ("json" ); jsonTag != "" {
233+ if parts := strings .Split (jsonTag , "," ); len (parts ) > 0 && parts [0 ] != "" {
234+ fieldName = parts [0 ]
235+ }
236+ }
237+
238+ // Check if this is a *[]byte field and convert to Base64Bytes
239+ if field .Kind () == reflect .Ptr &&
240+ field .Type ().Elem ().Kind () == reflect .Slice &&
241+ field .Type ().Elem ().Elem ().Kind () == reflect .Uint8 {
242+ if field .IsNil () {
243+ result [fieldName ] = Base64Bytes (nil )
244+ } else {
245+ result [fieldName ] = Base64Bytes (field .Elem ().Bytes ())
246+ }
247+ } else if field .Kind () == reflect .Slice && field .Type ().Elem ().Kind () == reflect .Uint8 {
248+ // Handle direct []byte fields
249+ if field .IsNil () {
250+ result [fieldName ] = Base64Bytes (nil )
251+ } else {
252+ result [fieldName ] = Base64Bytes (field .Bytes ())
253+ }
254+ } else {
255+ result [fieldName ] = convertToMapWithBase64Bytes (field .Interface ())
256+ }
257+ }
258+ return result
259+ }
260+
261+ // For primitive types, return as-is
262+ return data
263+ }
0 commit comments