diff --git a/cmd/diff.go b/cmd/diff.go index edd61ca..78bc399 100644 --- a/cmd/diff.go +++ b/cmd/diff.go @@ -11,7 +11,7 @@ import ( var Diff = &command.Command{ Path: []string{"diff"}, Summary: "Shows differences between local and remote configs", - Description: `Fetches remote and compares against local, ignoring comments but respecting order.`, + Description: `Fetches remote and compares against local, ignoring comments but respecting order. The diff output shows what would happen upon running ﹅joao fetch﹅. Specify ﹅--remote﹅ to show what would happen upon ﹅joao flush﹅`, Arguments: command.Arguments{ { Name: "config", @@ -26,7 +26,7 @@ var Diff = &command.Command{ Options: command.Options{ "output": { Description: "How to format the differences", - Type: "string", + Type: command.ValueTypeString, Default: "auto", Values: &command.ValueSource{ Static: &[]string{ @@ -34,15 +34,21 @@ var Diff = &command.Command{ }, }, }, + "remote": { + Description: "Shows what would happen on `flush` instead of `fetch`", + Type: command.ValueTypeString, + Default: false, + }, "redacted": { Description: "Compare redacted versions", - Type: "bool", + Type: command.ValueTypeBoolean, Default: false, }, }, Action: func(cmd *command.Command) error { paths := cmd.Arguments[0].ToValue().([]string) redacted := cmd.Options["redacted"].ToValue().(bool) + remote := cmd.Options["remote"].ToValue().(bool) for _, path := range paths { local, err := config.Load(path, false) @@ -50,7 +56,7 @@ var Diff = &command.Command{ return err } - if err := local.DiffRemote(path, redacted, cmd.Cobra.OutOrStdout(), cmd.Cobra.OutOrStderr()); err != nil { + if err := local.DiffRemote(path, redacted, remote, cmd.Cobra.OutOrStdout(), cmd.Cobra.OutOrStderr()); err != nil { return err } } diff --git a/cmd/fetch.go b/cmd/fetch.go index ee5d783..b00c9ba 100644 --- a/cmd/fetch.go +++ b/cmd/fetch.go @@ -32,11 +32,21 @@ var Fetch = &command.Command{ Action: func(cmd *command.Command) error { paths := cmd.Arguments[0].ToValue().([]string) for _, path := range paths { - remote, err := config.Load(path, true) + local, err := config.Load(path, false) if err != nil { return err } - local, err := config.Load(path, false) + + if dryRun := cmd.Options["dry-run"].ToValue().(bool); dryRun { + logrus.Warnf("dry-run: comparing %s to %s", local.OPURL(), path) + if err := local.DiffRemote(path, false, true, cmd.Cobra.OutOrStdout(), cmd.Cobra.OutOrStderr()); err != nil { + return err + } + logrus.Warnf("dry-run: did not update %s", path) + continue + } + + remote, err := config.Load(path, true) if err != nil { return err } @@ -45,20 +55,11 @@ var Fetch = &command.Command{ return err } - if dryRun := cmd.Options["dry-run"].ToValue().(bool); dryRun { - b, err := local.AsYAML() - if err != nil { - return err - } - logrus.Warnf("dry-run: would write to %s", path) - _, _ = cmd.Cobra.OutOrStdout().Write(b) - } else { - if err := local.AsFile(path); err != nil { - return err - } + if err := local.AsFile(path); err != nil { + return err } - logrus.Infof("Updated %s", path) + logrus.Infof("Fetched %s => %s", remote.OPURL(), path) } logrus.Info("Done") diff --git a/cmd/fetch_test.go b/cmd/fetch_test.go index 2090586..753f1df 100644 --- a/cmd/fetch_test.go +++ b/cmd/fetch_test.go @@ -69,36 +69,26 @@ func TestFetch(t *testing.T) { t.Fatalf("could not get: %s", err) } - expected := `_config: !!joao - name: some:test - vault: example -# not sorted on purpose -int: 1 # line -# foot -string: pato -bool: false -secret: !!secret very secret -nested: - int: 1 - bool: true - list: - - 1 - - 2 - - 3 - secret: !!secret very secret - second_secret: !!secret very secret - string: quem -list: - - one - - two - - three -o: - ganso: - gosto: da dupla -e-fez-tambem: - - quém! - - quém! - - quém!` + expected := `--- /Users/roberto/src/joao/test.yaml ++++ op://example/some:test +@@ -1,4 +1,8 @@ + bool: false ++e-fez-tambem: ++ - quém! ++ - quém! ++ - quém! + int: 1 + list: + - one +@@ -14,5 +18,8 @@ + second_secret: !!secret very secret + secret: !!secret very secret + string: quem ++o: ++ ganso: ++ gosto: da dupla + secret: !!secret very secret + string: pato` if got := out.String(); strings.TrimSpace(got) != expected { t.Fatalf("did not get expected output:\nwanted: %s\ngot: %s", expected, got) diff --git a/cmd/flush.go b/cmd/flush.go index ec38874..6acad7d 100644 --- a/cmd/flush.go +++ b/cmd/flush.go @@ -38,8 +38,9 @@ var Flush = &command.Command{ }, Action: func(cmd *command.Command) error { paths := cmd.Arguments[0].ToValue().([]string) + dryRun := cmd.Options["dry-run"].ToValue().(bool) - if dryRun := cmd.Options["dry-run"].ToValue().(bool); dryRun { + if dryRun { opclient.Use(&opclient.CLI{DryRun: true}) } @@ -49,6 +50,15 @@ var Flush = &command.Command{ return err } + if dryRun { + logrus.Warnf("dry-run: comparing %s to %s", path, cfg.OPURL()) + if err := cfg.DiffRemote(path, false, false, cmd.Cobra.OutOrStdout(), cmd.Cobra.OutOrStderr()); err != nil { + return err + } + logrus.Warnf("dry-run: did not update %s", cfg.OPURL()) + continue + } + if err := opclient.Update(cfg.Vault, cfg.Name, cfg.ToOP()); err != nil { return fmt.Errorf("could not flush to 1password: %w", err) } @@ -58,6 +68,7 @@ var Flush = &command.Command{ return err } } + logrus.Infof("Flushed %s to %s", path, cfg.OPURL()) } logrus.Info("Done") diff --git a/internal/op-client/op.go b/internal/op-client/op.go index 70753ca..bf5d1dc 100644 --- a/internal/op-client/op.go +++ b/internal/op-client/op.go @@ -34,7 +34,7 @@ func Get(vault, name string) (*op.Item, error) { func Update(vault, name string, item *op.Item) error { remote, err := client.Get(vault, name) if err != nil { - if strings.Contains(err.Error(), fmt.Sprintf("\"%s\" isn't an item in the ", name)) { + if strings.Contains(err.Error(), fmt.Sprintf("\"%s\" isn't an item in ", name)) { return client.Create(item) } diff --git a/pkg/config/config.go b/pkg/config/config.go index 6958d23..64506ff 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -156,11 +156,21 @@ func (cfg *Config) Merge(other *Config) error { return cfg.Tree.Merge(other.Tree) } -func (cfg *Config) DiffRemote(path string, redacted bool, stdout, stderr io.Writer) error { +func (cfg *Config) OPURL() string { + return fmt.Sprintf("op://%s/%s", cfg.Vault, cfg.Name) +} + +func (cfg *Config) DiffRemote(path string, redacted, asFetch bool, stdout, stderr io.Writer) error { logrus.Debugf("loading remote for %s", path) remote, err := Load(path, true) if err != nil { - return err + if asFetch { + return err + } + + if !strings.Contains(err.Error(), " isn't an item in ") { + return fmt.Errorf("could not fetch remote item: %w", err) + } } modes := []OutputMode{OutputModeNoComments, OutputModeSorted, OutputModeNoConfig, OutputModeStandardYAML} @@ -168,6 +178,7 @@ func (cfg *Config) DiffRemote(path string, redacted bool, stdout, stderr io.Writ modes = append(modes, OutputModeRedacted) } + logrus.Debugf("loading local for %s", path) localBytes, err := cfg.AsYAML(modes...) if err != nil { return err @@ -179,18 +190,30 @@ func (cfg *Config) DiffRemote(path string, redacted bool, stdout, stderr io.Writ } defer cleanupLocalDiff() - remoteBytes, err := remote.AsYAML(modes...) - if err != nil { - return err - } - file2, cleanupRemoteDiff, err := tempfile(remoteBytes) - if err != nil { - return err - } - defer cleanupRemoteDiff() + file2 := "/dev/null" + opPath := "(new) " + cfg.OPURL() + + if remote != nil { + remoteBytes, err := remote.AsYAML(modes...) + if err != nil { + return err + } + f2, cleanupRemoteDiff, err := tempfile(remoteBytes) + if err != nil { + return err + } + file2 = f2 + opPath = remote.OPURL() + defer cleanupRemoteDiff() + } + + var diff *exec.Cmd + if asFetch { + diff = exec.Command("diff", "-u", "-L", path, file1, "-L", opPath, file2) + } else { + diff = exec.Command("diff", "-u", "-L", opPath, file2, "-L", path, file1) + } - opPath := fmt.Sprintf("op://%s/%s", cfg.Vault, remote.Name) - diff := exec.Command("diff", "-u", "-L", path, file1, "-L", opPath, file2) diff.Env = os.Environ() diff.Stdout = stdout diff --git a/pkg/config/entry.go b/pkg/config/entry.go index d3fc4bb..a63e584 100644 --- a/pkg/config/entry.go +++ b/pkg/config/entry.go @@ -197,7 +197,7 @@ func (e *Entry) asNode() *yaml.Node { if yamlOutput.Has(OutputModeStandardYAML) { if e.IsScalar() { - if len(e.Path) >= 0 { + if len(e.Path) > 0 { if !strings.Contains(n.Value, "\n") { n.Style &= yaml.FoldedStyle } else { diff --git a/pkg/config/input.go b/pkg/config/input.go index e4bdf49..f95cb39 100644 --- a/pkg/config/input.go +++ b/pkg/config/input.go @@ -24,8 +24,11 @@ func Load(ref string, preferRemote bool) (*Config, error) { vault := "" if argIsYAMLFile(ref) { - var err error - name, vault, err = VaultAndNameFrom(ref, nil) + path, err := filepath.Abs(ref) + if err != nil { + return nil, fmt.Errorf("could not find asbolute path to file %s: %w", ref, err) + } + name, vault, err = VaultAndNameFrom(path, nil) if err != nil { return nil, err } diff --git a/pkg/config/output.go b/pkg/config/output.go index f01e469..5b7189a 100644 --- a/pkg/config/output.go +++ b/pkg/config/output.go @@ -43,7 +43,7 @@ const ( OutputModeSorted OutputMode = 4 // OutputModeNoConfig does not output the _config key if any. OutputModeNoConfig OutputMode = 8 - // OutputModeStandardYAML formats strings and arrays uniformly + // OutputModeStandardYAML formats strings and arrays uniformly. OutputModeStandardYAML OutputMode = 16 )