implement remote loading via op cli

This commit is contained in:
Roberto Hidalgo 2022-11-16 01:38:04 -06:00
parent 2619181be4
commit 095503f29a
4 changed files with 127 additions and 42 deletions

View File

@ -18,6 +18,7 @@ import (
"io/ioutil"
"strings"
opClient "git.rob.mx/nidito/joao/internal/op-client"
"git.rob.mx/nidito/joao/pkg/config"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
@ -39,7 +40,10 @@ var getCommand = &cobra.Command{
query = args[1]
}
var cfg *config.Config
if strings.HasSuffix(path, ".yaml") || strings.HasSuffix(path, ".yml") {
remote, _ := cmd.Flags().GetBool("remote")
isYaml := strings.HasSuffix(path, ".yaml") || strings.HasSuffix(path, ".yml")
if !remote && isYaml {
buf, err := ioutil.ReadFile(path)
if err != nil {
return fmt.Errorf("could not read file %s", path)
@ -53,6 +57,22 @@ var getCommand = &cobra.Command{
if err != nil {
return err
}
} else {
name := path
if isYaml {
comps := strings.Split(path, "config/")
name = strings.ReplaceAll(strings.Replace(comps[len(comps)-1], ".yaml", "", 1), "/", ":")
}
item, err := opClient.Fetch("nidito-admin", name)
if err != nil {
return err
}
cfg, err = config.ConfigFromOP(item)
if err != nil {
return err
}
}
format, _ := cmd.Flags().GetString("output")
@ -93,12 +113,9 @@ var getCommand = &cobra.Command{
if len(entry.Children) > 0 {
val := entry.AsMap()
if format == "yaml" {
bytes, err = yaml.Marshal(val)
if err != nil {
return err
}
_, err = cmd.OutOrStdout().Write(bytes)
return err
enc := yaml.NewEncoder(cmd.OutOrStdout())
enc.SetIndent(2)
return enc.Encode(val)
}
bytes, err = json.Marshal(val)

50
internal/op-client/op.go Normal file
View File

@ -0,0 +1,50 @@
// 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 opClient
import (
"bytes"
"encoding/json"
"fmt"
"os"
"os/exec"
op "github.com/1Password/connect-sdk-go/onepassword"
)
func Fetch(vault, name string) (*op.Item, error) {
return fetchRemote(name, vault)
}
func fetchRemote(name, vault string) (*op.Item, error) {
cmd := exec.Command("op", "item", "--format", "json", "--vault", vault, "get", name)
cmd.Env = os.Environ()
var stdout bytes.Buffer
cmd.Stdout = &stdout
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return nil, err
}
if cmd.ProcessState.ExitCode() > 0 {
return nil, fmt.Errorf("op exited with %d: %s", cmd.ProcessState.ExitCode(), stderr.Bytes())
}
var item *op.Item
if err := json.Unmarshal(stdout.Bytes(), &item); err != nil {
return nil, err
}
return item, nil
}

View File

@ -13,6 +13,7 @@
package config
import (
"bytes"
"encoding/json"
"fmt"
@ -121,11 +122,14 @@ func (cfg *Config) MarshalYAML() (interface{}, error) {
func (cfg *Config) AsYAML(redacted bool) ([]byte, error) {
redactOutput = redacted
bytes, err := yaml.Marshal(cfg)
var out bytes.Buffer
enc := yaml.NewEncoder(&out)
enc.SetIndent(2)
err := enc.Encode(cfg)
if err != nil {
return nil, fmt.Errorf("could not serialize config as yaml: %w", err)
}
return bytes, nil
return out.Bytes(), nil
}
func (cfg *Config) AsJSON(redacted bool, item bool) ([]byte, error) {

View File

@ -130,7 +130,6 @@ func (e *Entry) MarshalYAML() (interface{}, error) {
func (e *Entry) FromOP(fields []*op.ItemField) error {
annotations := map[string]string{}
data := map[string]string{}
labels := []string{}
for i := 0; i < len(fields); i++ {
field := fields[i]
@ -143,58 +142,73 @@ func (e *Entry) FromOP(fields []*op.ItemField) error {
label = field.Section.Label + "." + label
}
}
labels = append(labels, label)
if label == "password" || label == "notesPlain" {
continue
}
data[label] = field.Value
}
for _, label := range labels {
var value interface{} = data[label]
if typeString, ok := annotations[label]; ok {
switch typeString {
case "bool":
value = value == "true"
case "int":
var err error
value, err = strconv.ParseInt(value.(string), 10, 64)
if err != nil {
return err
}
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 {
if !isNumeric(key) {
isSecretLabel := annotations[label]
container.Children[key] = &Entry{
Key: key,
Path: path,
Value: value,
isSecret: isSecretLabel == "!!secret",
}
break
isSecret := annotations[label] == "secret"
var style yaml.Style
var tag string
if isSecret {
style = yaml.TaggedStyle
tag = "!!secret"
}
holderI := container.Value
if container.Value == nil {
holderI = []interface{}{}
child := &Entry{
Key: key,
Path: path,
Value: value,
isSecret: isSecret,
node: &yaml.Node{
Kind: yaml.ScalarNode,
Value: valueStr,
Style: style,
Tag: tag,
},
}
holder := holderI.([]interface{})
container.Value = append(holder, value)
break
container.isSequence = isNumeric(key)
container.Children[key] = child
continue
}
subContainer, exists := container.Children[key]
if exists {
container = subContainer
} else {
container.Children[key] = NewEntry(key)
container = container.Children[key]
child := NewEntry(key)
child.Path = append(container.Path, key)
container.Children[key] = child
container = child
}
}
}