diff --git a/CHANGELOG.md b/CHANGELOG.md index 78fc081..ff507c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 3.0.0 / 2020-07-17 + +* [ENHANCEMENT] Multi processing now supports configurable KeyFunc and passes through tags, to enable downstream libraries to better customize xargs results + # 2.10.4 / 2020-06-05 * [ENHANCEMENT] Update deps diff --git a/cmd/profiles.go b/cmd/profiles.go index d4f4bab..4fd3c99 100644 --- a/cmd/profiles.go +++ b/cmd/profiles.go @@ -1,7 +1,7 @@ package cmd import ( - "github.com/akerl/voyager/v2/cartogram" + "github.com/akerl/voyager/v3/cartogram" "github.com/spf13/cobra" ) diff --git a/cmd/profiles_add.go b/cmd/profiles_add.go index 2e3c465..142805f 100644 --- a/cmd/profiles_add.go +++ b/cmd/profiles_add.go @@ -3,7 +3,7 @@ package cmd import ( "fmt" - "github.com/akerl/voyager/v2/profiles" + "github.com/akerl/voyager/v3/profiles" "github.com/akerl/input/list" "github.com/spf13/cobra" diff --git a/cmd/profiles_delete.go b/cmd/profiles_delete.go index ba1e88b..6594f23 100644 --- a/cmd/profiles_delete.go +++ b/cmd/profiles_delete.go @@ -3,8 +3,8 @@ package cmd import ( "fmt" - "github.com/akerl/voyager/v2/profiles" - "github.com/akerl/voyager/v2/utils" + "github.com/akerl/voyager/v3/profiles" + "github.com/akerl/voyager/v3/utils" "github.com/spf13/cobra" ) diff --git a/cmd/profiles_list.go b/cmd/profiles_list.go index 66ef13e..f311766 100644 --- a/cmd/profiles_list.go +++ b/cmd/profiles_list.go @@ -4,7 +4,7 @@ import ( "fmt" "sort" - "github.com/akerl/voyager/v2/profiles" + "github.com/akerl/voyager/v3/profiles" "github.com/spf13/cobra" ) diff --git a/cmd/profiles_rotate.go b/cmd/profiles_rotate.go index 63bb7c5..0ae098c 100644 --- a/cmd/profiles_rotate.go +++ b/cmd/profiles_rotate.go @@ -1,8 +1,8 @@ package cmd import ( - "github.com/akerl/voyager/v2/rotate" - "github.com/akerl/voyager/v2/yubikey" + "github.com/akerl/voyager/v3/rotate" + "github.com/akerl/voyager/v3/yubikey" "github.com/akerl/speculate/v2/creds" "github.com/spf13/cobra" diff --git a/cmd/profiles_show.go b/cmd/profiles_show.go index 000da6c..bbf8c56 100644 --- a/cmd/profiles_show.go +++ b/cmd/profiles_show.go @@ -3,7 +3,7 @@ package cmd import ( "fmt" - "github.com/akerl/voyager/v2/profiles" + "github.com/akerl/voyager/v3/profiles" "github.com/spf13/cobra" ) diff --git a/cmd/travel.go b/cmd/travel.go index 55365d1..98af20b 100644 --- a/cmd/travel.go +++ b/cmd/travel.go @@ -3,9 +3,9 @@ package cmd import ( "fmt" - "github.com/akerl/voyager/v2/cartogram" - "github.com/akerl/voyager/v2/travel" - "github.com/akerl/voyager/v2/yubikey" + "github.com/akerl/voyager/v3/cartogram" + "github.com/akerl/voyager/v3/travel" + "github.com/akerl/voyager/v3/yubikey" "github.com/akerl/input/list" "github.com/akerl/speculate/v2/creds" diff --git a/cmd/version.go b/cmd/version.go index ab735b7..7bd8b98 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -5,7 +5,7 @@ import ( "github.com/spf13/cobra" - "github.com/akerl/voyager/v2/version" + "github.com/akerl/voyager/v3/version" ) var versionCmd = &cobra.Command{ diff --git a/cmd/xargs.go b/cmd/xargs.go index 62ef8b0..6f65dea 100644 --- a/cmd/xargs.go +++ b/cmd/xargs.go @@ -4,10 +4,10 @@ import ( "encoding/json" "fmt" - "github.com/akerl/voyager/v2/cartogram" - "github.com/akerl/voyager/v2/multi" - "github.com/akerl/voyager/v2/travel" - "github.com/akerl/voyager/v2/yubikey" + "github.com/akerl/voyager/v3/cartogram" + "github.com/akerl/voyager/v3/multi" + "github.com/akerl/voyager/v3/travel" + "github.com/akerl/voyager/v3/yubikey" "github.com/akerl/input/list" "github.com/akerl/speculate/v2/creds" diff --git a/go.mod b/go.mod index ec7ad0f..2da64ee 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/akerl/voyager/v2 +module github.com/akerl/voyager/v3 go 1.14 @@ -12,9 +12,9 @@ require ( github.com/99designs/keyring v0.0.0-00010101000000-000000000000 github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69 github.com/akerl/input v0.0.13 - github.com/akerl/speculate/v2 v2.5.4 + github.com/akerl/speculate/v2 v2.5.5 github.com/akerl/timber/v2 v2.0.1 - github.com/aws/aws-sdk-go v1.31.12 + github.com/aws/aws-sdk-go v1.33.6 github.com/mdp/qrterminal/v3 v3.0.0 github.com/pquerna/otp v1.2.0 github.com/spf13/cobra v1.0.0 diff --git a/go.sum b/go.sum index 0276156..e688923 100644 --- a/go.sum +++ b/go.sum @@ -13,15 +13,15 @@ github.com/akerl/input v0.0.13 h1:pg/9xphhUiN6Vch1s/KTHkqkrtAmGVt1/0FTY0nk6Vg= github.com/akerl/input v0.0.13/go.mod h1:5AaWoVuZjlTiAcSz69C9xVIQ1SYRC9c6fBgn1jsWnWs= github.com/akerl/keyring v0.0.0-20200219084108-1f409e548abc h1:rgr2nWwLQ++doaXhUFx3ttJLPtWZGLCKzL8YnLFR5dw= github.com/akerl/keyring v0.0.0-20200219084108-1f409e548abc/go.mod h1:7Jt2k9tnyY3YKoQgGSxYkwBXKNZFZas/DwB1RhNoG1o= -github.com/akerl/speculate/v2 v2.5.4 h1:2+8n1uHBLQft0juDl/CkYFhMfnTZDug0WZydS7jgA8k= -github.com/akerl/speculate/v2 v2.5.4/go.mod h1:js10Uj+OHLseRukWKOGaC/DSfIMfSfVWR6ig5i4TzN4= +github.com/akerl/speculate/v2 v2.5.5 h1:oKyX44vxQ/XPqQw3CGSBrLkRmtDtXuVPMMvtJ7kVIj4= +github.com/akerl/speculate/v2 v2.5.5/go.mod h1:rDf7gUb/C3KFXqFzT/E2wFxW9u5e3mKJpkAL783wctg= github.com/akerl/timber/v2 v2.0.1 h1:hY4VCOJns7KsxwxP/ifSt3Rz9GZCfKewapaimObnA2E= github.com/akerl/timber/v2 v2.0.1/go.mod h1:jBjRGI2CWuvbZlrZkp1JO/X51pMlbg72NFy+Vnd59oI= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/aws/aws-sdk-go v1.31.12 h1:SxRRGyhlCagI0DYkhOg+FgdXGXzRTE3vEX/gsgFaiKQ= -github.com/aws/aws-sdk-go v1.31.12/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.33.6 h1:YLoUeMSx05kHwhS+HLDSpdYYpPzJMyp6hn1cWsJ6a+U= +github.com/aws/aws-sdk-go v1.33.6/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= diff --git a/main.go b/main.go index 93fb275..8b98ec0 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,7 @@ package main import ( "os" - "github.com/akerl/voyager/v2/cmd" + "github.com/akerl/voyager/v3/cmd" "github.com/akerl/speculate/v2/helpers" ) diff --git a/multi/main.go b/multi/main.go index b2ed951..202e051 100644 --- a/multi/main.go +++ b/multi/main.go @@ -7,7 +7,8 @@ import ( "strings" "time" - "github.com/akerl/voyager/v2/travel" + "github.com/akerl/voyager/v3/cartogram" + "github.com/akerl/voyager/v3/travel" "github.com/akerl/speculate/v2/creds" "github.com/akerl/timber/v2/log" @@ -25,28 +26,38 @@ type Processor struct { RoleNames []string ProfileNames []string SkipConfirm bool + KeyFunc func(cartogram.Account) (string, cartogram.Tags) +} + +// ExecResult is based on creds.ExecResult but adds account tags +type ExecResult struct { + Tags cartogram.Tags `json:"tags"` + Error error `json:"error"` + ExitCode int `json:"exitcode"` + StdOut string `json:"stdout"` + StdErr string `json:"stderr"` } // ExecString runs a command string against a set of accounts -func (p Processor) ExecString(cmd string) (map[string]creds.ExecResult, error) { +func (p Processor) ExecString(cmd string) (map[string]ExecResult, error) { args, err := creds.StringToCommand(cmd) if err != nil { - return map[string]creds.ExecResult{}, err + return map[string]ExecResult{}, err } return p.Exec(args) } // Exec runs a command against a set of accounts -func (p Processor) Exec(cmd []string) (map[string]creds.ExecResult, error) { +func (p Processor) Exec(cmd []string) (map[string]ExecResult, error) { logger.InfoMsgf("processing command: %v", cmd) paths, err := p.Grapher.ResolveAll(p.Args, p.RoleNames, p.ProfileNames) if err != nil { - return map[string]creds.ExecResult{}, err + return map[string]ExecResult{}, err } if !p.confirm(paths) { - return map[string]creds.ExecResult{}, fmt.Errorf("aborted by user") + return map[string]ExecResult{}, fmt.Errorf("aborted by user") } inputCh := make(chan workerInput, len(paths)) @@ -58,10 +69,15 @@ func (p Processor) Exec(cmd []string) (map[string]creds.ExecResult, error) { } for _, item := range paths { + account := item[len(item)-1] + key, tags := p.ParseKey(account.Account) + inputCh <- workerInput{ Path: item, Options: p.Options, Command: cmd, + Key: key, + Tags: tags, } } close(inputCh) @@ -80,10 +96,10 @@ func (p Processor) Exec(cmd []string) (map[string]creds.ExecResult, error) { ), ) - output := map[string]creds.ExecResult{} + output := map[string]ExecResult{} for i := 1; i <= len(paths); i++ { result := <-outputCh - output[result.AccountID] = result.ExecResult + output[result.Key] = result.ExecResult bar.Increment() refreshCh <- time.Now() } @@ -92,13 +108,26 @@ func (p Processor) Exec(cmd []string) (map[string]creds.ExecResult, error) { return output, nil } +// ParseKey derives an output key from an account +func (p Processor) ParseKey(account cartogram.Account) (string, cartogram.Tags) { + if p.KeyFunc == nil { + return DefaultKeyFunc(account) + } + return p.KeyFunc(account) +} + +// DefaultKeyFunc uses the account's ID as the key, and passes through its tags +func DefaultKeyFunc(account cartogram.Account) (string, cartogram.Tags) { + return account.Account, account.Tags +} + func (p Processor) confirm(paths []travel.Path) bool { if p.SkipConfirm { return true } fmt.Fprintln(os.Stderr, "Will run on the following accounts:") for _, item := range paths { - accountID := item[len(item)-1].Account + accountID := item[len(item)-1].Account.Account ok, account := p.Grapher.Pack.Lookup(accountID) if !ok { fmt.Fprintf(os.Stderr, "Failed account lookup: %s\n", accountID) @@ -124,28 +153,32 @@ type workerInput struct { Path travel.Path Options travel.TraverseOptions Command []string + Key string + Tags cartogram.Tags } type workerOutput struct { - AccountID string - ExecResult creds.ExecResult + Key string + ExecResult ExecResult } func execWorker(inputCh <-chan workerInput, outputCh chan<- workerOutput) { for item := range inputCh { c, err := item.Path.TraverseWithOptions(item.Options) if err != nil { - outputCh <- workerOutput{ExecResult: creds.ExecResult{Error: err}} - continue - } - accountID, err := c.AccountID() - if err != nil { - outputCh <- workerOutput{ExecResult: creds.ExecResult{Error: err}} + outputCh <- workerOutput{ExecResult: ExecResult{Error: err}} continue } + result := c.Exec(item.Command) outputCh <- workerOutput{ - AccountID: accountID, - ExecResult: c.Exec(item.Command), + Key: item.Key, + ExecResult: ExecResult{ + Tags: item.Tags, + Error: result.Error, + ExitCode: result.ExitCode, + StdOut: result.StdOut, + StdErr: result.StdErr, + }, } } } diff --git a/rotate/main.go b/rotate/main.go index a70117c..2a3554d 100644 --- a/rotate/main.go +++ b/rotate/main.go @@ -6,11 +6,11 @@ import ( "strings" "time" - "github.com/akerl/voyager/v2/cartogram" - "github.com/akerl/voyager/v2/profiles" - "github.com/akerl/voyager/v2/travel" - "github.com/akerl/voyager/v2/utils" - "github.com/akerl/voyager/v2/version" + "github.com/akerl/voyager/v3/cartogram" + "github.com/akerl/voyager/v3/profiles" + "github.com/akerl/voyager/v3/travel" + "github.com/akerl/voyager/v3/utils" + "github.com/akerl/voyager/v3/version" "github.com/akerl/input/list" "github.com/akerl/speculate/v2/creds" diff --git a/travel/cache.go b/travel/cache.go index cc1e05e..14c72db 100644 --- a/travel/cache.go +++ b/travel/cache.go @@ -87,5 +87,5 @@ func (mc *MapCache) hopToKey(h Hop) string { if h.Profile != "" { return fmt.Sprintf("profile--%s", h.Profile) } - return fmt.Sprintf("%s-%s-%t", h.Account, h.Role, h.Mfa) + return fmt.Sprintf("%s-%s-%t", h.Account.Account, h.Role, h.Mfa) } diff --git a/travel/grapher.go b/travel/grapher.go index e19bd19..d4790ed 100644 --- a/travel/grapher.go +++ b/travel/grapher.go @@ -1,7 +1,7 @@ package travel import ( - "github.com/akerl/voyager/v2/cartogram" + "github.com/akerl/voyager/v3/cartogram" "github.com/akerl/input/list" ) @@ -137,9 +137,8 @@ func (g *Grapher) findPathToRole(account cartogram.Account, role cartogram.Role) myHop := Hop{ Role: role.Name, - Account: account.Account, + Account: account, Mfa: role.Mfa, - Region: account.Region, } for i := range allPaths { diff --git a/travel/path.go b/travel/path.go index ee02630..c333460 100644 --- a/travel/path.go +++ b/travel/path.go @@ -3,8 +3,9 @@ package travel import ( "fmt" - "github.com/akerl/voyager/v2/profiles" - "github.com/akerl/voyager/v2/version" + "github.com/akerl/voyager/v3/cartogram" + "github.com/akerl/voyager/v3/profiles" + "github.com/akerl/voyager/v3/version" "github.com/BurntSushi/locker" "github.com/akerl/speculate/v2/creds" @@ -23,10 +24,9 @@ type Path []Hop // to the target role type Hop struct { Profile string - Account string + Account cartogram.Account Role string Mfa bool - Region string } // TraverseOptions defines the parameters for traversing a path @@ -106,7 +106,7 @@ func (h Hop) Traverse(c creds.Creds, opts TraverseOptions) (creds.Creds, error) logger.InfoMsgf("Executing hop: %+v", h) a := creds.AssumeRoleOptions{ RoleName: h.Role, - AccountID: h.Account, + AccountID: h.Account.Account, SessionName: opts.SessionName, Lifetime: opts.Lifetime, } @@ -117,7 +117,7 @@ func (h Hop) Traverse(c creds.Creds, opts TraverseOptions) (creds.Creds, error) a.MfaPrompt = opts.MfaPrompt } - c.Region = h.Region + c.Region = h.Account.Region newCreds, err := c.AssumeRole(a) if err != nil { return creds.Creds{}, err @@ -130,5 +130,5 @@ func (h *Hop) toKey() string { if h.Profile != "" { return fmt.Sprintf("profile--%s", h.Profile) } - return fmt.Sprintf("%s-%s-%t", h.Account, h.Role, h.Mfa) + return fmt.Sprintf("%s-%s-%t", h.Account.Account, h.Role, h.Mfa) }