joao/pkg/config/entry.go

302 lines
6.5 KiB
Go

// Copyright © 2022 Roberto Hidalgo <joao@un.rob.mx>
//
// Licensed 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 config
import (
"fmt"
"strconv"
"strings"
op "github.com/1Password/connect-sdk-go/onepassword"
"gopkg.in/yaml.v3"
)
func isNumeric(s string) bool {
for _, v := range s {
if v < '0' || v > '9' {
return false
}
}
return true
}
type Entry struct {
Key string
Path []string
Value interface{}
Children map[string]*Entry
isSecret bool
isSequence bool
node *yaml.Node
}
func NewEntry(name string) *Entry {
return &Entry{Key: name, Children: map[string]*Entry{}}
}
func (e *Entry) SetKey(key string, parent []string) {
e.Path = append(parent, key)
e.Key = key
for k, child := range e.Children {
child.SetKey(k, e.Path)
}
}
func (e *Entry) UnmarshalYAML(node *yaml.Node) error {
e.node = node
switch node.Kind {
case yaml.DocumentNode, yaml.MappingNode:
if e.Children == nil {
e.Children = map[string]*Entry{}
}
err := node.Decode(&e.Children)
if err != nil {
return err
}
case yaml.SequenceNode:
list := []*Entry{}
err := node.Decode(&list)
if err != nil {
return err
}
if e.Children == nil {
e.Children = map[string]*Entry{}
}
for idx, child := range list {
child.Key = fmt.Sprintf("%d", idx)
e.Children[child.Key] = child
}
e.isSequence = true
case yaml.ScalarNode:
var val interface{}
err := node.Decode(&val)
if err != nil {
return err
}
e.Value = val
e.isSecret = node.Tag == "!!secret"
default:
return fmt.Errorf("unknown yaml type: %v", node.Kind)
}
return nil
}
func (e *Entry) MarshalYAML() (interface{}, error) {
if len(e.Children) == 0 {
if redactOutput && e.isSecret {
n := e.node
return &yaml.Node{
Kind: n.Kind,
Style: yaml.TaggedStyle,
Tag: n.Tag,
Value: "",
HeadComment: n.HeadComment,
LineComment: n.LineComment,
FootComment: n.FootComment,
Line: n.Line,
Column: n.Column,
}, nil
}
return e.node, nil
}
if e.isSequence {
ret := make([]*Entry, len(e.Children))
for k, child := range e.Children {
idx, _ := strconv.Atoi(k)
ret[idx] = child
}
return ret, nil
}
ret := map[string]*Entry{}
for k, child := range e.Children {
ret[k] = child
}
return ret, nil
}
func (e *Entry) FromOP(fields []*op.ItemField) error {
annotations := map[string]string{}
data := map[string]string{}
for i := 0; i < len(fields); i++ {
field := fields[i]
label := field.Label
if field.Section != nil {
if field.Section.Label == "~annotations" {
annotations[label] = field.Value
continue
} else {
label = field.Section.Label + "." + label
}
}
if label == "password" || label == "notesPlain" {
continue
}
data[label] = field.Value
}
for label, valueStr := range data {
var value interface{}
typeString := annotations[label]
switch typeString {
case "bool":
value = valueStr == "true"
case "int":
var err error
value, err = strconv.ParseInt(valueStr, 10, 64)
if err != nil {
return err
}
case "float":
var err error
value, err = strconv.ParseFloat(valueStr, 64)
if err != nil {
return err
}
default:
value = valueStr
}
// logrus.Warnf("processing: %s, %b", label, value)
path := strings.Split(label, ".")
container := e
for idx, key := range path {
if idx == len(path)-1 {
isSecret := annotations[label] == "secret"
var style yaml.Style
var tag string
if isSecret {
style = yaml.TaggedStyle
tag = "!!secret"
}
child := &Entry{
Key: key,
Path: path,
Value: value,
isSecret: isSecret,
node: &yaml.Node{
Kind: yaml.ScalarNode,
Value: valueStr,
Style: style,
Tag: tag,
},
}
container.isSequence = isNumeric(key)
container.Children[key] = child
continue
}
subContainer, exists := container.Children[key]
if exists {
container = subContainer
} else {
child := NewEntry(key)
child.Path = append(container.Path, key)
container.Children[key] = child
container = child
}
}
}
return nil
}
func (e *Entry) ToOP(annotationsSection *op.ItemSection) []*op.ItemField {
ret := []*op.ItemField{}
var section *op.ItemSection
name := e.Key
if len(e.Path) > 1 {
section = &op.ItemSection{ID: e.Path[0]}
name = strings.Join(e.Path[1:], ".")
}
if e.isSecret {
ret = append(ret, &op.ItemField{
ID: "~annotations." + strings.Join(e.Path, "."),
Section: annotationsSection,
Label: name,
Type: "STRING",
Value: "secret",
})
} else if _, ok := e.Value.(bool); ok {
ret = append(ret, &op.ItemField{
ID: "~annotations." + strings.Join(e.Path, "."),
Section: annotationsSection,
Label: name,
Type: "STRING",
Value: "bool",
})
} else if _, ok := e.Value.(int); ok {
ret = append(ret, &op.ItemField{
ID: "~annotations." + strings.Join(e.Path, "."),
Section: annotationsSection,
Label: name,
Type: "STRING",
Value: "int",
})
} else if _, ok := e.Value.(float64); ok {
ret = append(ret, &op.ItemField{
ID: "~annotations." + strings.Join(e.Path, "."),
Section: annotationsSection,
Label: name,
Type: "STRING",
Value: "float",
})
}
if len(e.Children) == 0 {
ret = append(ret, &op.ItemField{
ID: strings.Join(e.Path, "."),
Section: section,
Label: name,
Type: "STRING",
Value: fmt.Sprintf("%s", e.Value),
})
} else {
for _, child := range e.Children {
ret = append(ret, child.ToOP(annotationsSection)...)
}
}
return ret
}
func (e *Entry) AsMap() interface{} {
if len(e.Children) == 0 {
if redactOutput && e.isSecret {
return ""
}
return e.Value
}
if e.isSequence {
ret := make([]interface{}, len(e.Children))
for key, child := range e.Children {
idx, _ := strconv.Atoi(key)
ret[idx] = child.AsMap()
}
return ret
}
ret := map[string]interface{}{}
for key, child := range e.Children {
ret[key] = child.AsMap()
}
return ret
}