awswap 0.4.2

Fast AWS profile switching with credential validation and ECR authentication
# awswap

[![CI](https://img.shields.io/github/actions/workflow/status/drew-simmons/awswap/ci.yml?branch=main&label=CI)](https://github.com/drew-simmons/awswap/actions/workflows/ci.yml)
[![Latest release](https://img.shields.io/github/v/release/drew-simmons/awswap?label=release)](https://github.com/drew-simmons/awswap/releases/latest)
[![License](https://img.shields.io/github/license/drew-simmons/awswap)](LICENSE)

`awswap` is a fast AWS profile switcher with a searchable picker. It shows each
profile's region, sign-in method, and account metadata; checks credentials when
you switch; opens AWS sign-in when needed; and signs Docker and Helm in to ECR.
Built-in status and diagnostic views help find setup and credential issues.

## Install

### Installer (macOS and Linux)

```sh
curl --proto '=https' --tlsv1.2 -LsSf \
  https://github.com/drew-simmons/awswap/releases/latest/download/awswap-installer.sh \
  | sh
```

> [!NOTE]
> The installer downloads a prebuilt release and checks its SHA-256 checksum.
> You can also download archives and checksums from
> [GitHub Releases]https://github.com/drew-simmons/awswap/releases.

### Build from source

Building from source requires Rust 1.88 or newer:

```sh
git clone https://github.com/drew-simmons/awswap.git
cd awswap
cargo install --path . --locked
```

### Supported release targets

| Platform | Architectures |
| --- | --- |
| macOS | Apple Silicon (`aarch64`), Intel (`x86_64`) |
| Linux with glibc | `aarch64`, `x86_64` |
| Linux with musl/Alpine | `x86_64` |

## Requirements

- AWS CLI v2
- Docker and/or Helm when ECR authentication is needed
- Bash, Zsh, or Fish for updating the active shell

Configure profiles with the AWS CLI first, for example:

```sh
aws configure sso
```

Profiles using an AWS CLI `login_session` are also supported.

## Shell setup

> [!IMPORTANT]
> Install the shell hook so `awswap` can update `AWS_PROFILE` in the current
> shell. Without it, `awswap` can check and sign in a profile, but the profile
> will not stay active after the command exits.

**Zsh** (`~/.zshrc`):

```sh
eval "$(awswap init zsh)"
```

**Bash** (`~/.bashrc`):

```sh
eval "$(awswap init bash)"
```

**Fish** (`~/.config/fish/conf.d/awswap.fish`):

```fish
awswap init fish | source
```

Restart the shell or reload its configuration after you add the hook.

Optional generated completions use configured profile names as candidates:

```sh
# Zsh or Bash
source <(awswap completions zsh) # replace zsh with bash as needed
```

```fish
# Fish
awswap completions fish | source
```

> [!TIP]
> These commands load completions for the current shell only. Add the matching
> command to your shell configuration to load them in each new shell.

## Usage

```text
awswap [options]                  Search and select a profile
awswap [options] <profile>        Switch directly to a profile
awswap [options] -                Switch back to the previous profile
awswap current                    Print the active profile
awswap list                       List configured profiles
awswap status [profile]           Show identity and integration status
awswap doctor [profile]           Diagnose configuration and credentials
awswap login [options] [profile]  Refresh AWS and ECR authentication
awswap completions <shell>        Generate Bash, Zsh, or Fish completions
awswap help                       Show all commands and options
```

Common options:

```text
--no-ecr                  Skip Docker and Helm ECR authentication
-r, --registry <value>    Use a registry hostname or AWS account ID; repeatable
-q, --quiet               Suppress progress and success output
--json                    Emit JSON from list, current, status, doctor, and login
-v, --verbose             Show commands and detailed AWS failures
```

Examples:

```sh
awswap dev
aws sts get-caller-identity
awswap status
awswap doctor

awswap --no-ecr staging
awswap --registry 123456789012 dev
awswap status --json

docker pull 123456789012.dkr.ecr.us-east-1.amazonaws.com/app:latest
helm pull oci://123456789012.dkr.ecr.us-east-1.amazonaws.com/charts/app

awswap -
```

## Authentication and ECR

On each switch, `awswap` shows progress for network tasks and prints an identity
receipt with the profile, account, region, and role or user. The picker shows
local region, sign-in, and account metadata. It sorts the current, previous, and
recent profiles first.

It then:

1. Checks the profile with `aws sts get-caller-identity`.
2. If credentials are invalid, runs `aws sso login` for SSO profiles or
   `aws login` for profiles with `login_session`, opening the AWS sign-in flow.
3. Authenticates registry clients: Helm receives an ECR login token; Docker
   receives one unless its effective credential helper is `ecr-login`, in which
   case `awswap` validates that helper under the selected profile instead.

If Helm's macOS Keychain helper reports duplicate item error `-25299`, `awswap`
logs out from that registry and retries the Helm login once.

The ECR credential helper gets short-lived Docker credentials as needed. It
does not support Docker's credential `store` operation, so `awswap` skips
`docker login` when that helper is set. The shell hook keeps `AWS_PROFILE`
available to the helper for later pulls.

The shell hook exports `AWS_PROFILE` and `AWS_DEFAULT_PROFILE`. It also removes
stale static AWS credential variables that would override the profile.

> [!NOTE]
> `awswap` stores only the current, previous, and eight most recent profile
> names under `$XDG_STATE_HOME/awswap` (or `~/.local/state/awswap`). AWS,
> Docker, Helm, and any credential helpers manage their own credentials.

Flags are preferred for one-off behavior. Equivalent environment controls are
available for persistent configuration:

```sh
AWSWAP_NO_ECR=1 awswap dev
AWSWAP_ECR_REGISTRIES=123456789012,registry.example.com awswap dev
AWSWAP_HOME=/custom/state/directory awswap dev
```

`AWSWAP_ECR_REGISTRIES` accepts comma-separated AWS account IDs or registry
hostnames. Without it, `awswap` uses the selected profile's account ID and
region. Account IDs are expanded with the correct ECR DNS suffix for the `aws`,
`aws-cn`, `aws-us-gov`, `aws-iso`, `aws-iso-b`, `aws-iso-e`, `aws-iso-f`, and
`aws-eusc` partitions.

## Upgrade and uninstall

Rerun the installer command from [Install](#install) to upgrade.

Uninstall the shell-installed binary with:

```sh
rm "${CARGO_HOME:-$HOME/.cargo}/bin/awswap"
```

Also remove the shell hook from your shell configuration if it is no longer
needed. State contains only profile names and can be deleted from
`${XDG_STATE_HOME:-$HOME/.local/state}/awswap`.

## Troubleshooting

Run `awswap doctor` first. It checks the AWS CLI, profile list, active identity,
region, shell hook, state path, Docker and Helm, the Docker credential helper,
and static environment variables that may override profiles.

- If the profile does not persist, install the shell hook and start a new shell.
  Without the hook, `awswap` reports a profile as **selected**, not **active**.
- Authentication errors distinguish expired or unavailable credentials, access
  denial, network failures, missing regions, and missing profiles, with a
  specific recovery command when possible. Add `--verbose` for the original AWS
  CLI detail.
- If ECR is not needed or Docker/Helm is unavailable, use `--no-ecr`.
- Use `NO_COLOR=1` to disable colored output.

## Development

The project uses Rust 1.88.0. Before submitting a change, run:

```sh
cargo fmt --all --check
cargo clippy --locked --all-targets --all-features -- -D warnings
cargo test --locked --all-targets
cargo package --locked --allow-dirty
```

See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidance,
[SECURITY.md](SECURITY.md) for vulnerability reporting, and
[RELEASING.md](RELEASING.md) for maintainer release steps.

## License

MIT