From 095503f29a362f314aacbf03a3830e35322b6393 Mon Sep 17 00:00:00 2001 From: Roberto Hidalgo Date: Wed, 16 Nov 2022 01:38:04 -0600 Subject: [PATCH] implement remote loading via op cli --- cmd/get.go | 31 ++++++++++++---- internal/op-client/op.go | 50 +++++++++++++++++++++++++ pkg/config/config.go | 8 +++- pkg/config/entry.go | 80 +++++++++++++++++++++++----------------- 4 files changed, 127 insertions(+), 42 deletions(-) create mode 100644 internal/op-client/op.go diff --git a/cmd/get.go b/cmd/get.go index a1a2105..e973f76 100644 --- a/cmd/get.go +++ b/cmd/get.go @@ -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) diff --git a/internal/op-client/op.go b/internal/op-client/op.go new file mode 100644 index 0000000..69e7363 --- /dev/null +++ b/internal/op-client/op.go @@ -0,0 +1,50 @@ +// Copyright © 2022 Roberto Hidalgo +// +// 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 +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 4d40fed..23314a0 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -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) { diff --git a/pkg/config/entry.go b/pkg/config/entry.go index bfe5c29..d672a0e 100644 --- a/pkg/config/entry.go +++ b/pkg/config/entry.go @@ -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 } } }