Skip to content

Commit

Permalink
Merge pull request #129 from synfinatic/complete
Browse files Browse the repository at this point in the history
Add auto-complete support
  • Loading branch information
synfinatic authored Nov 14, 2021
2 parents 6a962a2 + ced3319 commit f2cb9c6
Show file tree
Hide file tree
Showing 20 changed files with 277 additions and 59 deletions.
12 changes: 12 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
linters:
enable:
- asciicheck
- ineffassign
- gocyclo
- dupl
# - funlen
- gofmt
- gosec
- misspell
- whitespace
# - unparam
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
## [Unreleased]

* Add report card and make improvements to code style #124
* Add auto-complete support #12
* Add golangci-lint support & config file

## [v1.2.3] - 2021-11-13

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ endif

BUILDINFOSDET ?=
PROGRAM_ARGS ?=
PROJECT_VERSION := 1.2.3
PROJECT_VERSION := 1.3.0
DOCKER_REPO := synfinatic
PROJECT_NAME := aws-sso
ifeq ($(PROJECT_TAG),)
Expand Down
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# AWS SSO CLI ![Tests](https://github.com/synfinatic/aws-sso-cli/workflows/Tests/badge.svg) [![Report Card](https://goreportcard.com/badge/github.com/synfinatic/aws-sso-cli)](https://goreportcard.com/report/github.com/synfinatic/aws-sso-cli)
# AWS SSO CLI
![Tests](https://github.com/synfinatic/aws-sso-cli/workflows/Tests/badge.svg)
[![Report Card](https://goreportcard.com/badge/github.com/synfinatic/aws-sso-cli)](https://goreportcard.com/report/github.com/synfinatic/aws-sso-cli)
[![GitHub license](https://img.shields.io/badge/license-GPLv3-blue.svg)](https://raw.githubusercontent.com/synfinatic/aws-sso-cli/main/LICENSE)

## About

Expand Down Expand Up @@ -89,12 +92,12 @@ been granted access!
* `tags` -- List manually created tags for each role
* `time` -- Print how much time remains for currently selected role
* `version` -- Print the version of aws-sso
* `install-autocomplete` -- Install auto-complete functionality into your shell

### Common Flags

* `--help`, `-h` -- Builtin and context sensitive help
* `--browser <path>`, `-b` -- Override default browser to open AWS SSO URL (`$AWS_SSO_BROWSER`)
* `--cache <file>` -- Specify alternative cache file (`$AWS_SSO_CACHE`)
* `--config <file>` -- Specify alternative config file (`$AWS_SSO_CONFIG`)
* `--level <level>`, `-L` -- Change default log level: [error|warn|info|debug|trace]
* `--lines` -- Print file number with logs
Expand Down Expand Up @@ -221,6 +224,12 @@ By default the following key/values are available as tags to your roles:
* `History` -- Tag tracking if this role was recently used. See `HistoryLimit`
in config.

### install-autocomplete

Configures your appropriate shell configuration file to add auto-complete
functionality for commands, flags and options. Must restart your shell
for this to take effect.

### Environment Variables

The following environment variables are honored by `exec` and `console`:
Expand Down
135 changes: 135 additions & 0 deletions cmd/complete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package main

/*
* AWS SSO CLI
* Copyright (c) 2021 Aaron Turner <aturner at synfin dot net>
*
* This program is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or with the authors permission any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import (
"io/ioutil"

"github.com/goccy/go-yaml"
"github.com/posener/complete"
"github.com/synfinatic/aws-sso-cli/sso"
"github.com/synfinatic/aws-sso-cli/utils"
)

type Predictor struct {
configFile string
accountids []string
roles []string
arns []string
}

// AvailableAwsRegions lists all the AWS regions that AWS provides
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-available-regions
var AvailableAwsRegions []string = []string{
"us-east-1",
"us-east-2",
"us-west-1",
"us-west-2",
"af-south-1",
"ap-east-1",
"ap-south-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-southeast-1",
"ap-southeast-2",
"ap-northeast-1",
"ca-central-1",
"eu-central-1",
"eu-west-1",
"eu-west-2",
"eu-south-1",
"eu-west-3",
"eu-north-1",
"me-south-1",
"sa-east-1",
}

// NewPredictor loads our cache file (if exists) and loads the values
func NewPredictor(cacheFile, configFile string) *Predictor {
p := Predictor{
configFile: configFile,
}
c, err := sso.OpenCache(cacheFile, &sso.Settings{})
if err != nil {
return &p
}

uniqueRoles := map[string]bool{}

for i, a := range c.Roles.Accounts {
id, _ := utils.AccountIdToString(i)
p.accountids = append(p.accountids, id)
for role, r := range a.Roles {
p.arns = append(p.arns, r.Arn)
uniqueRoles[role] = true
}
}

for k := range uniqueRoles {
p.roles = append(p.roles, k)
}

return &p
}

// FieldListComplete returns a completor for the `list` fields
func (p *Predictor) FieldListComplete() complete.Predictor {
set := []string{}
for f := range allListFields {
set = append(set, f)
}

return complete.PredictSet(set...)
}

// AccountComplete returns a list of all the valid AWS Accounts we have in the cache
func (p *Predictor) AccountComplete() complete.Predictor {
return complete.PredictSet(p.accountids...)
}

// RoleComplete returns a list of all the valid AWS Roles we have in the cache
func (p *Predictor) RoleComplete() complete.Predictor {
return complete.PredictSet(p.roles...)
}

// ArnComplete returns a list of all the valid AWS Role ARNs we have in the cache
func (p *Predictor) ArnComplete() complete.Predictor {
return complete.PredictSet(p.arns...)
}

// RegionsComplete returns a list of all the valid AWS Regions
func (p *Predictor) RegionComplete() complete.Predictor {
return complete.PredictSet(AvailableAwsRegions...)
}

// SsoComplete returns a list of the valid AWS SSO Instances
func (p *Predictor) SsoComplete() complete.Predictor {
ssos := []string{}
s := sso.Settings{}

if config, err := ioutil.ReadFile(p.configFile); err == nil {
if err = yaml.Unmarshal(config, &s); err == nil {
for sso := range s.SSO {
ssos = append(ssos, sso)
}
}
}
return complete.PredictSet(ssos...)
}
8 changes: 4 additions & 4 deletions cmd/console_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ import (
const AWS_FEDERATED_URL = "https://signin.aws.amazon.com/federation"

type ConsoleCmd struct {
Region string `kong:"optional,name='region',help='AWS Region',env='AWS_DEFAULT_REGION'"`
Region string `kong:"optional,name='region',help='AWS Region',env='AWS_DEFAULT_REGION',predictor='region'"`
AccessKeyId string `kong:"optional,env='AWS_ACCESS_KEY_ID',hidden"`
AccountId int64 `kong:"optional,name='account',short='A',help='AWS AccountID of role to assume',env='AWS_SSO_ACCOUNTID'"`
Arn string `kong:"optional,short='a',help='ARN of role to assume',env='AWS_SSO_ROLE_ARN'"`
AccountId int64 `kong:"optional,name='account',short='A',help='AWS AccountID of role to assume',env='AWS_SSO_ACCOUNTID',predictor='accountId'"`
Arn string `kong:"optional,short='a',help='ARN of role to assume',env='AWS_SSO_ROLE_ARN',predictor='arn'"`
Duration int64 `kong:"optional,short='d',help='AWS Session duration in minutes (default 60)',default=60,env='AWS_SSO_DURATION'"`
Role string `kong:"optional,short='R',help='Name of AWS Role to assume',env='AWS_SSO_ROLE'"`
Role string `kong:"optional,short='R',help='Name of AWS Role to assume',env='AWS_SSO_ROLE',predictor='role'"`
SecretAccessKey string `kong:"optional,env='AWS_SECRET_ACCESS_KEY',hidden"`
SessionToken string `kong:"optional,env='AWS_SESSION_TOKEN',hidden"`
UseEnv bool `kong:"optional,short='e',help='Use existing ENV vars to generate URL'"`
Expand Down
10 changes: 5 additions & 5 deletions cmd/exec_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ import (

type ExecCmd struct {
// AWS Params
Region string `kong:"optional,name='region',help='AWS Region',env='AWS_DEFAULT_REGION'"`
Region string `kong:"optional,name='region',help='AWS Region',env='AWS_DEFAULT_REGION',predictor='region'"`
Duration int64 `kong:"optional,name='duration',short='d',help='AWS Session duration in minutes (default 60)',default=60,env='AWS_SSO_DURATION'"`
Arn string `kong:"optional,name='arn',short='a',help='ARN of role to assume',env='AWS_SSO_ROLE_ARN'"`
AccountId int64 `kong:"optional,name='account',short='A',help='AWS AccountID of role to assume',env='AWS_SSO_ACCOUNTID'"`
Role string `kong:"optional,name='role',short='R',help='Name of AWS Role to assume',env='AWS_SSO_ROLE'"`
Arn string `kong:"optional,name='arn',short='a',help='ARN of role to assume',env='AWS_SSO_ROLE_ARN',predictor='arn'"`
AccountId int64 `kong:"optional,name='account',short='A',help='AWS AccountID of role to assume',env='AWS_SSO_ACCOUNTID',predictor='accountId'"`
Role string `kong:"optional,name='role',short='R',help='Name of AWS Role to assume',env='AWS_SSO_ROLE',predictor='role'"`

// Exec Params
Cmd string `kong:"arg,optional,name='command',help='Command to execute',env='SHELL'"`
Expand Down Expand Up @@ -132,7 +132,7 @@ func execCmd(ctx *RunContext, awssso *sso.AWSSSO, accountid int64, role, region
}

// ready our command and connect everything up
cmd := exec.Command(ctx.Cli.Exec.Cmd, ctx.Cli.Exec.Args...)
cmd := exec.Command(ctx.Cli.Exec.Cmd, ctx.Cli.Exec.Args...) // #nosec
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
cmd.Stdin = os.Stdin
Expand Down
2 changes: 1 addition & 1 deletion cmd/list_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ var allListFields = map[string]string{
}

type ListCmd struct {
Fields []string `kong:"optional,arg,help='Fields to display',env='AWS_SSO_FIELDS'"`
ListFields bool `kong:"optional,name='list-fields',short='f',help='List available fields'"`
Fields []string `kong:"optional,arg,help='Fields to display',env='AWS_SSO_FIELDS',predictor='fieldList'"`
}

// what should this actually do?
Expand Down
88 changes: 52 additions & 36 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@ package main

import (
"fmt"
"os"

"github.com/alecthomas/kong"
// "github.com/davecgh/go-spew/spew"
// "github.com/davecgh/go-spew/spew"
"github.com/posener/complete"
log "github.com/sirupsen/logrus"
"github.com/synfinatic/aws-sso-cli/sso"
"github.com/synfinatic/aws-sso-cli/storage"
"github.com/synfinatic/aws-sso-cli/utils"
"github.com/willabides/kongplete"
)

// These variables are defined in the Makefile
Expand Down Expand Up @@ -81,24 +84,24 @@ var DEFAULT_CONFIG map[string]interface{} = map[string]interface{}{
type CLI struct {
// Common Arguments
Browser string `kong:"optional,short='b',help='Path to browser to open URLs with',env='AWS_SSO_BROWSER'"`
CacheFile string `kong:"optional,name='cache',default='${INSECURE_CACHE_FILE}',help='Insecure cache file',env='AWS_SSO_CACHE'"`
ConfigFile string `kong:"optional,name='config',default='${CONFIG_FILE}',help='Config file',env='AWS_SSO_CONFIG'"`
Lines bool `kong:"optional,help='Print line number in logs'"`
LogLevel string `kong:"optional,short='L',name='level',enum='error,warn,info,debug,trace,',help='Logging level [error|warn|info|debug|trace]'"`
UrlAction string `kong:"optional,short='u',enum='open,print,clip,',help='How to handle URLs [open|print|clip]'"`
SSO string `kong:"optional,short='S',help='AWS SSO Instance',env='AWS_SSO'"`
SSO string `kong:"optional,short='S',help='AWS SSO Instance',env='AWS_SSO',predictor='sso'"`
STSRefresh bool `kong:"optional,help='Force refresh of STS Token Credentials'"`

// Commands
Cache CacheCmd `kong:"cmd,help='Force reload of cached AWS SSO role info and config.yaml'"`
Console ConsoleCmd `kong:"cmd,help='Open AWS Console using specificed AWS Role/profile'"`
Exec ExecCmd `kong:"cmd,help='Execute command using specified AWS Role/Profile'"`
Flush FlushCmd `kong:"cmd,help='Flush AWS SSO/STS credentials from cache'"`
List ListCmd `kong:"cmd,help='List all accounts / role (default command)',default='1'"`
Renew RenewCmd `kong:"cmd,help='Print renewed AWS credentials for your shell'"`
Tags TagsCmd `kong:"cmd,help='List tags'"`
Time TimeCmd `kong:"cmd,help='Print out much time before STS Token expires'"`
Version VersionCmd `kong:"cmd,help='Print version and exit'"`
Cache CacheCmd `kong:"cmd,help='Force reload of cached AWS SSO role info and config.yaml'"`
Console ConsoleCmd `kong:"cmd,help='Open AWS Console using specificed AWS Role/profile'"`
Exec ExecCmd `kong:"cmd,help='Execute command using specified AWS Role/Profile'"`
Flush FlushCmd `kong:"cmd,help='Flush AWS SSO/STS credentials from cache'"`
List ListCmd `kong:"cmd,help='List all accounts / role (default command)',default='1'"`
Renew RenewCmd `kong:"cmd,help='Print renewed AWS credentials for your shell'"`
Tags TagsCmd `kong:"cmd,help='List tags'"`
Time TimeCmd `kong:"cmd,help='Print out much time before STS Token expires'"`
Version VersionCmd `kong:"cmd,help='Print version and exit'"`
InstallCompletions kongplete.InstallCompletions `kong:"cmd,help='Install shell completions'"`
}

func main() {
Expand All @@ -113,9 +116,9 @@ func main() {

// Load the config file
cli.ConfigFile = utils.GetHomePath(cli.ConfigFile)
cli.CacheFile = utils.GetHomePath(cli.CacheFile)
cacheFile := utils.GetHomePath(INSECURE_CACHE_FILE)

if run_ctx.Settings, err = sso.LoadSettings(cli.ConfigFile, cli.CacheFile, DEFAULT_CONFIG, override); err != nil {
if run_ctx.Settings, err = sso.LoadSettings(cli.ConfigFile, cacheFile, DEFAULT_CONFIG, override); err != nil {
log.Fatalf("%s", err.Error())
}

Expand Down Expand Up @@ -147,33 +150,46 @@ func main() {

// parseArgs parses our CLI arguments
func parseArgs(cli *CLI) (*kong.Context, sso.OverrideSettings) {
op := kong.Description("Securely manage temporary AWS API Credentials issued via AWS SSO")
// need to pass in the variables for defaults
vars := kong.Vars{
"CONFIG_DIR": CONFIG_DIR,
"CONFIG_FILE": CONFIG_FILE,
"DEFAULT_STORE": DEFAULT_STORE,
"INSECURE_CACHE_FILE": INSECURE_CACHE_FILE,
"JSON_STORE_FILE": JSON_STORE_FILE,
"CONFIG_DIR": CONFIG_DIR,
"CONFIG_FILE": CONFIG_FILE,
"DEFAULT_STORE": DEFAULT_STORE,
"JSON_STORE_FILE": JSON_STORE_FILE,
}
ctx := kong.Parse(cli, op, vars)

override := sso.OverrideSettings{}
parser := kong.Must(
cli,
kong.Name("aws-sso"),
kong.Description("Securely manage temporary AWS API Credentials issued via AWS SSO"),
kong.UsageOnError(),
vars,
)

if cli.UrlAction != "" {
override.UrlAction = cli.UrlAction
}
if cli.Browser != "" {
override.Browser = cli.Browser
}
if cli.SSO != "" {
override.DefaultSSO = cli.SSO
}
if cli.LogLevel != "" {
override.LogLevel = cli.LogLevel
}
if cli.Lines {
override.LogLines = true
p := NewPredictor(utils.GetHomePath(INSECURE_CACHE_FILE), utils.GetHomePath(CONFIG_FILE))

kongplete.Complete(parser,
kongplete.WithPredictors(
map[string]complete.Predictor{
"accountId": p.AccountComplete(),
"arn": p.ArnComplete(),
"fieldList": p.FieldListComplete(),
"region": p.RegionComplete(),
"role": p.RoleComplete(),
"sso": p.SsoComplete(),
},
),
)

ctx, err := parser.Parse(os.Args[1:])
parser.FatalIfErrorf(err)

override := sso.OverrideSettings{
UrlAction: cli.UrlAction,
Browser: cli.Browser,
DefaultSSO: cli.SSO,
LogLevel: cli.LogLevel,
LogLines: cli.Lines,
}

log.SetFormatter(&log.TextFormatter{
Expand Down
1 change: 0 additions & 1 deletion cmd/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,6 @@ func completeTags(roleTags *sso.RoleTags, allTags *sso.TagsList, accountPrimaryT

default:
desc = fmt.Sprintf("%d roles", roleCnt)

}
suggestions = append(suggestions, prompt.Suggest{
Text: value,
Expand Down
Loading

0 comments on commit f2cb9c6

Please sign in to comment.