diff --git a/cmd/fetch.go b/cmd/fetch.go new file mode 100644 index 0000000..1825cd6 --- /dev/null +++ b/cmd/fetch.go @@ -0,0 +1,13 @@ +// 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 cmd diff --git a/cmd/flush.go b/cmd/flush.go new file mode 100644 index 0000000..1825cd6 --- /dev/null +++ b/cmd/flush.go @@ -0,0 +1,13 @@ +// 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 cmd diff --git a/cmd/get.go b/cmd/get.go new file mode 100644 index 0000000..a1a2105 --- /dev/null +++ b/cmd/get.go @@ -0,0 +1,122 @@ +// 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 cmd + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "strings" + + "git.rob.mx/nidito/joao/pkg/config" + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" +) + +func init() { + getCommand.Flags().StringP("output", "o", "raw", "the format to output in") + getCommand.Flags().Bool("remote", false, "query 1password instead of the filesystem") + getCommand.Flags().Bool("redacted", false, "do not print secrets") +} + +var getCommand = &cobra.Command{ + Use: "get CONFIG [--output|-o=(raw|json|yaml)] [--remote] [--redacted] [jq expr]", + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + path := args[0] + query := "" + if len(args) > 1 { + query = args[1] + } + var cfg *config.Config + if strings.HasSuffix(path, ".yaml") || strings.HasSuffix(path, ".yml") { + buf, err := ioutil.ReadFile(path) + if err != nil { + return fmt.Errorf("could not read file %s", path) + } + + if len(buf) == 0 { + buf = []byte("{}") + } + + cfg, err = config.ConfigFromYAML(buf) + if err != nil { + return err + } + } + + format, _ := cmd.Flags().GetString("output") + redacted, _ := cmd.Flags().GetBool("redacted") + + if query == "" { + switch format { + case "yaml", "raw": + bytes, err := cfg.AsYAML(redacted) + if err != nil { + return err + } + _, err = cmd.OutOrStdout().Write(bytes) + return err + case "json", "json-op": + bytes, err := cfg.AsJSON(redacted, format == "json-op") + if err != nil { + return err + } + _, err = cmd.OutOrStdout().Write(bytes) + return err + } + return fmt.Errorf("unknown format %s", format) + } + + parts := strings.Split(query, ".") + + entry := cfg.Tree + for _, part := range parts { + entry = entry.Children[part] + if entry == nil { + return fmt.Errorf("value not found at %s of %s", part, query) + } + } + + var bytes []byte + var err error + 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 + } + + bytes, err = json.Marshal(val) + if err != nil { + return err + } + } else { + if valString, ok := entry.Value.(string); ok { + bytes = []byte(valString) + } else { + bytes, err = json.Marshal(entry.Value) + if err != nil { + return err + } + } + } + + _, err = cmd.OutOrStdout().Write(bytes) + return err + }, +} diff --git a/cmd/list.go b/cmd/list.go new file mode 100644 index 0000000..1825cd6 --- /dev/null +++ b/cmd/list.go @@ -0,0 +1,13 @@ +// 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 cmd diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..0c901ab --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,103 @@ +// 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 cmd + +import ( + "fmt" + "strings" + + "github.com/fatih/color" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var Root = &cobra.Command{ + Use: "joao [--silent|-v|--verbose] [--[no-]color] [-h|--help] [--version]", + Short: "does config", + Long: `does config with 1password and stuff`, + // DisableAutoGenTag: true, + // SilenceUsage: true, + // SilenceErrors: true, + ValidArgs: []string{""}, + Annotations: map[string]string{}, + Args: func(cmd *cobra.Command, args []string) error { + err := cobra.OnlyValidArgs(cmd, args) + if err != nil { + + suggestions := []string{} + bold := color.New(color.Bold) + for _, l := range cmd.SuggestionsFor(args[len(args)-1]) { + suggestions = append(suggestions, bold.Sprint(l)) + } + errMessage := fmt.Sprintf("Unknown subcommand %s", bold.Sprint(strings.Join(args, " "))) + if len(suggestions) > 0 { + errMessage += ". Perhaps you meant " + strings.Join(suggestions, ", ") + "?" + } + return fmt.Errorf("command not found") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + if ok, err := cmd.Flags().GetBool("version"); err == nil && ok { + vc, _, err := cmd.Root().Find([]string{"__version"}) + + if err != nil { + return err + } + return vc.RunE(vc, []string{}) + } + return fmt.Errorf("no command provided") + + } + + return nil + }, +} + +func RootCommand(version string) *cobra.Command { + Root.Annotations["version"] = version + rootFlagset := pflag.NewFlagSet("joao", pflag.ContinueOnError) + // for name, opt := range Root.Options { + // def, ok := opt.Default.(bool) + // if !ok { + // def = false + // } + + // if opt.ShortName != "" { + // rootFlagset.BoolP(name, opt.ShortName, def, opt.Description) + // } else { + // rootFlagset.Bool(name, def, opt.Description) + // } + // } + + rootFlagset.Usage = func() {} + rootFlagset.SortFlags = false + Root.PersistentFlags().AddFlagSet(rootFlagset) + + Root.Flags().Bool("version", false, "Display the version") + + // Root.CompletionOptions.DisableDefaultCmd = true + + Root.AddCommand(getCommand) + // Root.AddCommand(completionCommand) + // Root.AddCommand(generateDocumentationCommand) + // Root.AddCommand(doctorCommand) + + // Root.SetHelpCommand(helpCommand) + // helpCommand.AddCommand(docsCommand) + // docsCommand.SetHelpFunc(docs.HelpRenderer(Root.Options)) + // Root.SetHelpFunc(Root.HelpRenderer(Root.Options)) + + return Root +} diff --git a/cmd/set.go b/cmd/set.go new file mode 100644 index 0000000..1825cd6 --- /dev/null +++ b/cmd/set.go @@ -0,0 +1,13 @@ +// 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 cmd diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9ed6e59 --- /dev/null +++ b/go.mod @@ -0,0 +1,19 @@ +module git.rob.mx/nidito/joao + +go 1.18 + +require ( + github.com/1Password/connect-sdk-go v1.5.0 + github.com/fatih/color v1.13.0 + github.com/sirupsen/logrus v1.9.0 + github.com/spf13/cobra v1.6.1 + github.com/spf13/pflag v1.0.5 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/mattn/go-colorable v0.1.9 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..377a49a --- /dev/null +++ b/go.sum @@ -0,0 +1,96 @@ +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/1Password/connect-sdk-go v1.5.0 h1:F0WJcLSzGg3iXEDY49/ULdszYKsQLGTzn+2cyYXqiyk= +github.com/1Password/connect-sdk-go v1.5.0/go.mod h1:TdynFeyvaRoackENbJ8RfJokH+WAowAu1MLmUbdMq6s= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/main.go b/main.go new file mode 100644 index 0000000..c82f704 --- /dev/null +++ b/main.go @@ -0,0 +1,33 @@ +// 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 main + +import ( + "git.rob.mx/nidito/joao/cmd" + "github.com/sirupsen/logrus" +) + +var version = "dev" + +func main() { + logrus.SetFormatter(&logrus.TextFormatter{ + DisableLevelTruncation: true, + DisableTimestamp: true, + // ForceColors: runtime.ColorEnabled(), + }) + + err := cmd.RootCommand(version).Execute() + if err != nil { + logrus.Fatal(err) + } +} diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..4d40fed --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,145 @@ +// 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 config + +import ( + "encoding/json" + "fmt" + + op "github.com/1Password/connect-sdk-go/onepassword" + "gopkg.in/yaml.v3" +) + +type Config struct { + Path string + Vault string + Name string + Tree *Entry +} + +var redactOutput = false + +func (cfg *Config) ToMap() map[string]interface{} { + ret := map[string]interface{}{} + for _, child := range cfg.Tree.Children { + ret[child.Key] = child.AsMap() + } + return ret +} + +func (cfg *Config) ToOP() *op.Item { + annotationsSection := &op.ItemSection{ + ID: "~annotations", + Label: "~annotations", + } + sections := []*op.ItemSection{annotationsSection} + fields := []*op.ItemField{ + { + ID: "password", + Type: "CONCEALED", + Purpose: "PASSWORD", + Label: "password", + Value: "hash", + }, { + ID: "notesPlain", + Type: "STRING", + Purpose: "NOTES", + Label: "notesPlain", + Value: "flushed by joao", + }, + } + + for key, leaf := range cfg.Tree.Children { + if len(leaf.Children) == 0 { + fields = append(fields, leaf.ToOP(annotationsSection)...) + continue + } + + if !leaf.isSequence { + sections = append(sections, &op.ItemSection{ + ID: key, + Label: key, + }) + } else { + fmt.Printf("Found sequence for %s", leaf.Key) + } + + for _, child := range leaf.Children { + fields = append(fields, child.ToOP(annotationsSection)...) + } + } + + return &op.Item{ + Title: cfg.Name, + Sections: sections, + Vault: op.ItemVault{ID: "nidito-admin"}, + Category: op.Password, + Fields: fields, + } +} + +func ConfigFromYAML(data []byte) (*Config, error) { + cfg := &Config{ + Vault: "vault", + Name: "title", + Tree: NewEntry("root"), + } + + yaml.Unmarshal(data, cfg.Tree.Children) + + for k, leaf := range cfg.Tree.Children { + leaf.SetKey(k, []string{}) + } + + return cfg, nil +} + +func ConfigFromOP(item *op.Item) (*Config, error) { + cfg := &Config{ + Vault: item.Vault.ID, + Name: item.Title, + Tree: NewEntry("root"), + } + + err := cfg.Tree.FromOP(item.Fields) + return cfg, err +} + +func (cfg *Config) MarshalYAML() (interface{}, error) { + return cfg.Tree.MarshalYAML() +} + +func (cfg *Config) AsYAML(redacted bool) ([]byte, error) { + redactOutput = redacted + bytes, err := yaml.Marshal(cfg) + if err != nil { + return nil, fmt.Errorf("could not serialize config as yaml: %w", err) + } + return bytes, nil +} + +func (cfg *Config) AsJSON(redacted bool, item bool) ([]byte, error) { + var repr interface{} + if item { + repr = cfg.ToOP() + } else { + redactOutput = redacted + repr = cfg.ToMap() + } + + bytes, err := json.Marshal(repr) + if err != nil { + return nil, fmt.Errorf("could not serialize config as json: %w", err) + } + return bytes, nil +} diff --git a/pkg/config/entry.go b/pkg/config/entry.go new file mode 100644 index 0000000..bfe5c29 --- /dev/null +++ b/pkg/config/entry.go @@ -0,0 +1,287 @@ +// 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 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{} + labels := []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 + } + } + labels = append(labels, label) + 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 + } + } + } + + 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 + } + + holderI := container.Value + if container.Value == nil { + holderI = []interface{}{} + } + + holder := holderI.([]interface{}) + container.Value = append(holder, value) + break + } + + subContainer, exists := container.Children[key] + if exists { + container = subContainer + } else { + container.Children[key] = NewEntry(key) + container = container.Children[key] + } + } + } + + 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 +}