Skip to content

Commit

Permalink
feat: Refactored fuzzyfinder previewer (#3)
Browse files Browse the repository at this point in the history
Co-authored-by: Reus <Reus>
  • Loading branch information
JorgeReus committed Feb 5, 2022
1 parent 98df447 commit 0890e29
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 62 deletions.
Binary file added .md/previewer.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

<img src="./.md/aws-sso-creds.gif" />
<br>
<img src="./.md/previewer.gif" />
<br>
<img src="./.md/gopher.png" alt="Logo" width="80" height="80">

<h3 align="center">AWS SSO Creds</h3>
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ require (
gopkg.in/ini.v1 v1.62.0
)

require github.com/deckarep/golang-set v1.8.0

require (
github.com/gdamore/encoding v1.0.0 // indirect
github.com/gdamore/tcell/v2 v2.4.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ github.com/containerd/console v1.0.2 h1:Pi6D+aZXM+oUw1czuKgH5IJ+y0jhYcwBJfx5/Ghn
github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4=
github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.4.0 h1:W6dxJEmaxYvhICFoTY3WrLLEXsQ11SaFnKGVEXW57KM=
Expand Down
2 changes: 1 addition & 1 deletion internal/app/sso.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ func (s *SSOFlow) GetCredentials() ([]CredentialsResult, error) {
})
continue
}
profName := tempCredsPrefix + strings.Split(item.roleName, " ")[1]
profName := tempCredsPrefix + strings.TrimPrefix(item.roleName, "profile ")
credsSection, err := creds.File.NewSection(profName)
if err != nil {
return nil, item.err
Expand Down
204 changes: 146 additions & 58 deletions internal/pkg/ui/fuzzyfinder.go
Original file line number Diff line number Diff line change
@@ -1,85 +1,173 @@
package ui

import (
"errors"
"fmt"
"sort"
"strconv"
"strings"
"time"

"github.com/bigkevmcd/go-configparser"
mapset "github.com/deckarep/golang-set"
"github.com/ktr0731/go-fuzzyfinder"
)

func fuzzyPreviewer(credentialsPath string, rolesPath string) string {
var selected string
creds, err := configparser.NewConfigParserFromFile(credentialsPath)
const (
VALID_TEXT = "Valid"
EXPIRED_TEXT = "Expired"
)

type FuzzyPreviewer struct {
entries *configparser.ConfigParser
outputSections []string
rolesMapping *map[string]string
}

func NewFuzzyPreviewer(credentialsPath string, rolesPath string) (*FuzzyPreviewer, error) {
creds, err := configparser.NewConfigParserFromFile(credentialsPath)
if err != nil {
return nil, errors.New(fmt.Sprintf("Cannot parse file %s: %v", credentialsPath, err))
}
roles, err := configparser.NewConfigParserFromFile(rolesPath)
if err != nil {
return nil, errors.New(fmt.Sprintf("Cannot parse file %s: %v", rolesPath, err))
}

sections := creds.Sections()
sections = append(sections, roles.Sections()...)
sort.Strings(sections)
_, err = fuzzyfinder.FindMulti(
sections,
func(i int) string {
return sections[i]
},
fuzzyfinder.WithPreviewWindow(func(i, w, h int) string {
if i == -1 {
return ""
}
selected = sections[i]
s := fmt.Sprintf("[%s]\n", selected)

// if is a profile (~/.aws/confg)
var aux configparser.Dict
var showedKeys configparser.Dict = make(configparser.Dict)
if strings.HasPrefix(selected, "profile") {
aux, err = roles.Items(selected)
rolesMapping := map[string]string{}

outputSections := []string{}
extraSections := mapset.NewSet()
entries := configparser.New()

// Go though the sections of the credentials file (~/.aws/credentials)
for _, sec := range creds.Sections() {
err := entries.AddSection(sec)
if err != nil {
return nil, errors.New(fmt.Sprintf("Cannot add section %s: %v", sec, err))
}
extraSections.Add(sec)
items, err := creds.Items(sec)
if err != nil {
return nil, errors.New(fmt.Sprintf("Cannot get the %s entries from the credentials file (~/.aws/credentials): %v", sec, err))
}

rolesMapping[sec] = sec
for k, v := range items {
entries.Set(sec, k, v)
}
}

var outputName string
var extraItems configparser.Dict

// Go though the sections of the config file (~/.aws/config)
for _, sec := range roles.Sections() {
profileName := strings.TrimPrefix(sec, "profile ")
_, err := roles.GetBool(sec, "sso_auto_populated")
// If is not autopopulated
if err != nil {
if entries.HasSection(profileName) {
extraItems, _ = entries.Items(profileName)
err = entries.RemoveSection(profileName)
if err != nil {
return ""
return nil, errors.New(fmt.Sprintf("Cannot erase section %s from configFile: %v", profileName, err))
}
showedKeys["Account name"] = aux["sso_account_name"]
showedKeys["Account ID"] = aux["sso_account_id"]
showedKeys["Region"] = aux["region"]
extraSections.Remove(profileName)
}
outputName = fmt.Sprintf("(profile) %s", profileName)
} else {
outputName = fmt.Sprintf("(SSO profile) %s", profileName)
}

rolesMapping[outputName] = sec

outputSections = append(outputSections, outputName)
entries.AddSection(sec)

items, _ := roles.Items(sec)
for k, v := range items {
entries.Set(sec, k, v)
}

for k, v := range extraItems {
entries.Set(sec, k, v)
}
}

for sec := range extraSections.Iter() {
outputSections = append(outputSections, sec.(string))
}
return &FuzzyPreviewer{
rolesMapping: &rolesMapping,
entries: entries,
outputSections: outputSections,
}, nil
}

func (fp *FuzzyPreviewer) generatePreviewAttrs(selected string) (*string, error) {
s := fmt.Sprintf("[%s]\n", (*fp.rolesMapping)[selected])
items, _ := fp.entries.Items((*fp.rolesMapping)[selected])
keys := sort.StringSlice(items.Keys())
for _, k := range keys {
v := items[k]
switch k {
case "aws_secret_access_key", "aws_session_token":
continue
case "expires_time":
exp, err := strconv.Atoi(v)
if err != nil {
s += fmt.Sprintln(fmt.Sprintf("Cannot parse: %s", k))
}
expiresAt := time.Unix(int64(exp), 0)
s += fmt.Sprintln(fmt.Sprintf("Expires Time: %s", expiresAt.String()))
var expiredTxt string
if expiresAt.Before(time.Now()) {
expiredTxt = EXPIRED_TEXT
} else {
aux, err = creds.Items(selected)
if err != nil {
return ""
}
showedKeys["AWS access key id"] = aux["aws_access_key_id"]
iss, err := strconv.Atoi(aux["issued_time"])
if err != nil {
return ""
}
exp, err := strconv.Atoi(aux["expires_time"])
if err != nil {
return ""
}
expiredAt := time.Unix(int64(exp), 0)
showedKeys["Issued at"] = time.Unix(int64(iss), 0).String()
showedKeys["Expires at"] = expiredAt.String()
if expiredAt.Before(time.Now()) {
showedKeys["Status"] = "Expiradas"
} else {
showedKeys["Status"] = "Válidas"
}
expiredTxt = VALID_TEXT
}
s += fmt.Sprintln(fmt.Sprintf("Status: %s", expiredTxt))
break
case "issued_time":
iss, err := strconv.Atoi(v)
if err != nil {
s += fmt.Sprintln(fmt.Sprintf("Cannot parse %s", k))
}
issuedAt := time.Unix(int64(iss), 0)
s += fmt.Sprintln(fmt.Sprintf("Issued Time: %s", issuedAt.String()))
break
default:
k = strings.Title(strings.ReplaceAll(k, "_", " "))
s += fmt.Sprintln(fmt.Sprintf("%s: %s", k, v))
}
}
return &s, nil
}

for _, key := range showedKeys.Keys() {
s += fmt.Sprintf("%s: %s\n", key, showedKeys[key])
func (fp *FuzzyPreviewer) Preview() (*string, error) {
var selected string
_, err := fuzzyfinder.FindMulti(
fp.outputSections,
func(i int) string {
return fp.outputSections[i]
},
fuzzyfinder.WithPreviewWindow(func(i, w, h int) string {
if i == -1 {
return ""
}
selected = fp.outputSections[i]
s, err := fp.generatePreviewAttrs(selected)
if err != nil {
return fmt.Sprintf("Cannot parse attributes from %s", selected)
}
return s
return *s
}))

parts := strings.Split(selected, " ")
var role string
if len(parts) == 1 {
role = parts[0]
} else {
role = parts[1]
if err != nil {
return nil, err
}
return role

result := strings.TrimPrefix((*fp.rolesMapping)[selected], "profile ")
return &result, nil
}
15 changes: 12 additions & 3 deletions internal/pkg/ui/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,23 @@ func (u *UI) Start() error {
}

if u.UsePreviewer {
selectedEntry := fuzzyPreviewer(credentialsPath, configFilePath)
fmt.Println(selectedEntry)
fp, err := NewFuzzyPreviewer(credentialsPath, configFilePath)
if err != nil {
fmt.Println(fmt.Sprintf("Error starting program: %s", err))
os.Exit(1)
}
selectedEntry, err := fp.Preview()
if err != nil {
fmt.Println(fmt.Sprintf("Error Selecting entry: %s", err))
os.Exit(1)
}
fmt.Println(*selectedEntry)
} else {
m := initialModel()
p := tea.NewProgram(m)
go u.handleFlow()
if err := p.Start(); err != nil {
fmt.Printf("Error starting program: %s\n", err)
fmt.Println(fmt.Sprintf("Error starting program: %s", err))
os.Exit(1)
}
}
Expand Down
74 changes: 74 additions & 0 deletions testdata/config.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
[profile tmp:Account 5:Role 2]
region = us-east-1

[profile Account 1:Role 1]
sso_start_url = https://myApp.awsapps.com/start
sso_region = us-east-1
sso_account_name = Account 5
sso_account_id = XXXXXXXXXXXX
sso_role_name = Role 1
region = us-east-1
sso_auto_populated = true

[profile Account 2:Role 1]
sso_start_url = https://myApp.awsapps.com/start
sso_region = us-east-1
sso_account_name = Account 2
sso_account_id = XXXXXXXXXXXX
sso_role_name = Role 1
region = us-east-1
sso_auto_populated = true

[profile Account 3:Role 1]
sso_start_url = https://myApp.awsapps.com/start
sso_region = us-east-1
sso_account_name = Account 3
sso_account_id = XXXXXXXXXXXX
sso_role_name = Role 1
region = us-east-1
sso_auto_populated = true

[profile Account 4:Role 1]
sso_start_url = https://myApp.awsapps.com/start
sso_region = us-east-1
sso_account_name = Account 4
sso_account_id = XXXXXXXXXXXX
sso_role_name = Role 1
region = us-east-1
sso_auto_populated = true

[profile Account 5:Role 1]
sso_start_url = https://myApp.awsapps.com/start
sso_region = us-east-1
sso_account_name = Account 5
sso_account_id = XXXXXXXXXXXX
sso_role_name = Role 1
region = us-east-1
sso_auto_populated = true

[profile Account 5:Role 2]
sso_start_url = https://myApp.awsapps.com/start
sso_region = us-east-1
sso_account_name = Account 5
sso_account_id = XXXXXXXXXXXX
sso_role_name = Role 2
region = us-east-1
sso_auto_populated = true

[profile Account 5:Role 3]
sso_start_url = https://myApp.awsapps.com/start
sso_region = us-east-1
sso_account_name = Account 5
sso_account_id = XXXXXXXXXXXX
sso_role_name = Role 3
region = us-east-1
sso_auto_populated = true

[profile Account 6:Role 1]
sso_start_url = https://myApp.awsapps.com/start
sso_region = us-east-1
sso_account_name = Account 6
sso_account_id = XXXXXXXXXXXX
sso_role_name = Role 1
region = us-east-1
sso_auto_populated = true

0 comments on commit 0890e29

Please sign in to comment.