switch-profile 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +140 -26
- package/docs/architecture.md +185 -0
- package/docs/aws-profile-management.md +308 -0
- package/docs/cli-interface.md +267 -0
- package/docs/configuration-files.md +240 -0
- package/docs/development-guide.md +168 -0
- package/index.js +36 -2
- package/package.json +2 -2
- package/src/aws/index.js +12 -8
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# Configuration Files
|
|
2
|
+
|
|
3
|
+
This document describes the exact format of all AWS configuration files that `switch-profile` reads and writes.
|
|
4
|
+
|
|
5
|
+
## `~/.aws/config`
|
|
6
|
+
|
|
7
|
+
The AWS CLI configuration file. Stores profile settings including region, output format, and SSO metadata.
|
|
8
|
+
|
|
9
|
+
### Format
|
|
10
|
+
|
|
11
|
+
Standard INI-style format. The default profile uses `[default]`, all other profiles use `[profile name]`.
|
|
12
|
+
|
|
13
|
+
### Example
|
|
14
|
+
|
|
15
|
+
```ini
|
|
16
|
+
[default]
|
|
17
|
+
region = us-east-1
|
|
18
|
+
output = json
|
|
19
|
+
|
|
20
|
+
[profile my-standard]
|
|
21
|
+
region = ap-southeast-2
|
|
22
|
+
output = json
|
|
23
|
+
|
|
24
|
+
[profile sso-dev]
|
|
25
|
+
sso_start_url = https://my-company.awsapps.com/start
|
|
26
|
+
sso_region = us-east-1
|
|
27
|
+
sso_account_id = 123456789012
|
|
28
|
+
sso_role_name = CloudlessAdmin
|
|
29
|
+
region = us-east-1
|
|
30
|
+
output = json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Fields
|
|
34
|
+
|
|
35
|
+
**Standard profile fields:**
|
|
36
|
+
|
|
37
|
+
| Field | Description | Example |
|
|
38
|
+
|-------|-------------|---------|
|
|
39
|
+
| `region` | AWS region for API calls | `us-east-1` |
|
|
40
|
+
| `output` | CLI output format | `json` |
|
|
41
|
+
|
|
42
|
+
**SSO profile additional fields:**
|
|
43
|
+
|
|
44
|
+
| Field | Description | Example |
|
|
45
|
+
|-------|-------------|---------|
|
|
46
|
+
| `sso_start_url` | SSO portal URL | `https://my-company.awsapps.com/start` |
|
|
47
|
+
| `sso_region` | Region where SSO is configured | `us-east-1` |
|
|
48
|
+
| `sso_account_id` | AWS account ID (12 digits) | `123456789012` |
|
|
49
|
+
| `sso_role_name` | IAM role name in the account | `CloudlessAdmin` |
|
|
50
|
+
|
|
51
|
+
### How `switch-profile` Modifies This File
|
|
52
|
+
|
|
53
|
+
When setting a profile as default, only the `[default]` section is updated (region and output). Other profile sections are preserved unchanged.
|
|
54
|
+
|
|
55
|
+
When creating a standard profile, a new `[profile name]` section is appended. When creating an SSO profile, the `aws configure sso` command writes the section.
|
|
56
|
+
|
|
57
|
+
When deleting a profile, the `[profile name]` section and its contents (up to the next `[` bracket) are removed via regex.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## `~/.aws/credentials`
|
|
62
|
+
|
|
63
|
+
Stores AWS access credentials. All profiles use `[name]` syntax (no `profile` prefix).
|
|
64
|
+
|
|
65
|
+
### Example
|
|
66
|
+
|
|
67
|
+
```ini
|
|
68
|
+
[default]
|
|
69
|
+
aws_access_key_id = ASIAZOCWXABCD123456
|
|
70
|
+
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
|
|
71
|
+
aws_session_token = AQoDYXdzEJr...
|
|
72
|
+
expiry_date = 2021-07-17T11:33:12.000Z
|
|
73
|
+
profile = sso-dev
|
|
74
|
+
|
|
75
|
+
[my-standard]
|
|
76
|
+
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
|
|
77
|
+
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Standard AWS Fields
|
|
81
|
+
|
|
82
|
+
| Field | Description | Present For |
|
|
83
|
+
|-------|-------------|-------------|
|
|
84
|
+
| `aws_access_key_id` | AWS access key | All profiles |
|
|
85
|
+
| `aws_secret_access_key` | AWS secret key | All profiles |
|
|
86
|
+
| `aws_session_token` | Temporary session token | SSO profiles only |
|
|
87
|
+
|
|
88
|
+
### Custom Fields (added by `switch-profile`)
|
|
89
|
+
|
|
90
|
+
These fields are written into the `[default]` section only:
|
|
91
|
+
|
|
92
|
+
| Field | Description | Purpose |
|
|
93
|
+
|-------|-------------|---------|
|
|
94
|
+
| `profile` | Name of the profile these credentials belong to | Track which profile is currently active |
|
|
95
|
+
| `expiry_date` | ISO 8601 timestamp of when credentials expire | Display expiry status, detect expired profiles |
|
|
96
|
+
|
|
97
|
+
These are non-standard fields. The AWS CLI ignores them, but `switch-profile` reads them back to show profile status.
|
|
98
|
+
|
|
99
|
+
### How `switch-profile` Modifies This File
|
|
100
|
+
|
|
101
|
+
When setting a profile as default, the `[default]` section is replaced with the selected profile's credentials plus the custom `profile` and `expiry_date` fields.
|
|
102
|
+
|
|
103
|
+
When creating a standard profile, a new `[name]` section with access key and secret key is appended.
|
|
104
|
+
|
|
105
|
+
When deleting a profile, the `[name]` section and its contents are removed via regex.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## `~/.aws/sso/cache/` (Read-only)
|
|
110
|
+
|
|
111
|
+
`switch-profile` reads but does not write to this directory. It is managed entirely by the AWS CLI.
|
|
112
|
+
|
|
113
|
+
### Purpose
|
|
114
|
+
|
|
115
|
+
Stores SSO session tokens obtained through the `aws sso login` flow.
|
|
116
|
+
|
|
117
|
+
### File Format
|
|
118
|
+
|
|
119
|
+
Files are named with hash-based identifiers (e.g., `bdc1be3a4f0c3b5e8c0e4d6a1b2c3d4e.json`).
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"startUrl": "https://my-company.awsapps.com/start",
|
|
124
|
+
"region": "us-east-1",
|
|
125
|
+
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
126
|
+
"expiresAt": "2021-07-17T11:33:12Z"
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Fields
|
|
131
|
+
|
|
132
|
+
| Field | Description |
|
|
133
|
+
|-------|-------------|
|
|
134
|
+
| `startUrl` | The SSO portal URL this session belongs to |
|
|
135
|
+
| `region` | The SSO region |
|
|
136
|
+
| `accessToken` | JWT token for the SSO session |
|
|
137
|
+
| `expiresAt` | ISO 8601 expiry timestamp |
|
|
138
|
+
|
|
139
|
+
### How `switch-profile` Uses It
|
|
140
|
+
|
|
141
|
+
The tool searches all `*.json` files in this directory when it needs to verify or find an SSO session. It matches by comparing the `startUrl` host with the profile's `sso_start_url` host. A session is considered valid if it expires more than 2 minutes from now.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## `~/.aws/cli/cache/` (Read-only)
|
|
146
|
+
|
|
147
|
+
`switch-profile` reads but does not write to this directory. It is managed entirely by the AWS CLI.
|
|
148
|
+
|
|
149
|
+
### Purpose
|
|
150
|
+
|
|
151
|
+
Stores temporary AWS credentials (access key, secret key, session token) obtained through SSO authentication.
|
|
152
|
+
|
|
153
|
+
### File Format
|
|
154
|
+
|
|
155
|
+
Files are named with hash-based identifiers.
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"ProviderType": "sso",
|
|
160
|
+
"Credentials": {
|
|
161
|
+
"AccessKeyId": "ASIAZOCWXABCD123456",
|
|
162
|
+
"SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
163
|
+
"SessionToken": "AQoDYXdzEJr...",
|
|
164
|
+
"Expiration": "2021-07-17T11:33:12Z"
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Fields
|
|
170
|
+
|
|
171
|
+
| Field | Description |
|
|
172
|
+
|-------|-------------|
|
|
173
|
+
| `ProviderType` | Credential source type (`"sso"` for SSO) |
|
|
174
|
+
| `Credentials.AccessKeyId` | AWS access key ID |
|
|
175
|
+
| `Credentials.SecretAccessKey` | AWS secret access key |
|
|
176
|
+
| `Credentials.SessionToken` | Temporary session token |
|
|
177
|
+
| `Credentials.Expiration` | ISO 8601 expiry timestamp |
|
|
178
|
+
|
|
179
|
+
### How `switch-profile` Uses It
|
|
180
|
+
|
|
181
|
+
After running `aws configure list --profile <name>` (which populates this cache), the tool searches for a file where:
|
|
182
|
+
|
|
183
|
+
1. `ProviderType` is `"sso"`.
|
|
184
|
+
2. The last 4 characters of `AccessKeyId` match what `aws configure list` reported.
|
|
185
|
+
3. The last 4 characters of `SecretAccessKey` match what `aws configure list` reported.
|
|
186
|
+
4. `Expiration` is at least 2 minutes in the future.
|
|
187
|
+
|
|
188
|
+
This matching-by-suffix approach is necessary because `aws configure list` only shows masked keys (e.g., `****3456`), not the full values.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Default File Contents
|
|
193
|
+
|
|
194
|
+
When `switch-profile` creates a standard profile and the AWS config files don't exist yet, it initializes them with these defaults:
|
|
195
|
+
|
|
196
|
+
**`~/.aws/config`:**
|
|
197
|
+
```ini
|
|
198
|
+
[default]
|
|
199
|
+
region = ap-southeast-1
|
|
200
|
+
output = json
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**`~/.aws/credentials`:**
|
|
205
|
+
```ini
|
|
206
|
+
[default]
|
|
207
|
+
aws_access_key_id = 1234_DUMMY
|
|
208
|
+
aws_secret_access_key = 4567_DUMMY
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
These are dummy values that serve as placeholders until a real profile is set as default.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## INI Parsing Approach
|
|
217
|
+
|
|
218
|
+
`switch-profile` does not use a dedicated INI parser. Instead, it uses regex-based string operations:
|
|
219
|
+
|
|
220
|
+
**Reading a section:**
|
|
221
|
+
```javascript
|
|
222
|
+
// Extract [default] section content (everything until the next [ or end of file)
|
|
223
|
+
const section = fileContent.match(/\[default\]((.|\n|\r)*?)(\[|$)/)[0]
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Extracting a parameter:**
|
|
227
|
+
```javascript
|
|
228
|
+
// Match " paramName = value" with flexible whitespace
|
|
229
|
+
const regexp = new RegExp(`\\s*${paramName}\\s*=\\s*`)
|
|
230
|
+
const value = params.filter(p => regexp.test(p)).map(p => p.replace(regexp,'').trim())[0]
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Replacing a section:**
|
|
234
|
+
```javascript
|
|
235
|
+
// Replace the [default] section while preserving the opening bracket of the next section
|
|
236
|
+
const lastChar = defaultSection.slice(-1) // Either '[' or end-of-string
|
|
237
|
+
updatedContent = fileContent.replace(defaultSection, newSection + lastChar)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
This approach works for the tool's use case but depends on well-formed config files. It preserves all content outside the targeted section unchanged.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Development Guide
|
|
2
|
+
|
|
3
|
+
This document covers how to set up, develop, test, lint, and release `switch-profile`.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```shell
|
|
8
|
+
git clone https://github.com/nicolasdao/switch-profile.git
|
|
9
|
+
cd switch-profile
|
|
10
|
+
npm install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Running Locally
|
|
14
|
+
|
|
15
|
+
**Development mode:**
|
|
16
|
+
```shell
|
|
17
|
+
npm run dev
|
|
18
|
+
# Runs: TZ=UTC NODE_ENV=dev node index.js
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Production mode:**
|
|
22
|
+
```shell
|
|
23
|
+
npm start
|
|
24
|
+
# Runs: TZ=UTC NODE_ENV=production node index.js
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Direct execution:**
|
|
28
|
+
```shell
|
|
29
|
+
node index.js
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Both modes set `TZ=UTC` to ensure consistent timestamp handling.
|
|
33
|
+
|
|
34
|
+
## Linting
|
|
35
|
+
|
|
36
|
+
```shell
|
|
37
|
+
npm run lint
|
|
38
|
+
# Runs: eslint index.js src/ test/ --fix
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The ESLint configuration (`.eslintrc.json`) enforces:
|
|
42
|
+
|
|
43
|
+
| Rule | Setting |
|
|
44
|
+
|------|---------|
|
|
45
|
+
| Indentation | Tabs |
|
|
46
|
+
| Quotes | Single |
|
|
47
|
+
| Semicolons | None (never) |
|
|
48
|
+
| Line endings | Unix (`\n`) |
|
|
49
|
+
| Environment | ES6, Node.js (CommonJS) |
|
|
50
|
+
| Console | Allowed |
|
|
51
|
+
|
|
52
|
+
The `--fix` flag auto-fixes style issues when possible.
|
|
53
|
+
|
|
54
|
+
## Testing
|
|
55
|
+
|
|
56
|
+
```shell
|
|
57
|
+
npm test
|
|
58
|
+
# Runs: mocha --exit
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Tests are located in `test/index.js` and use [Mocha](https://mochajs.org/) as the test runner and [Chai](https://www.chaijs.com/) for assertions.
|
|
62
|
+
|
|
63
|
+
**Current state:** The test suite contains a single placeholder test. There are no functional tests for the AWS operations, profile management, or file I/O.
|
|
64
|
+
|
|
65
|
+
**Test utilities (from comments in test file):**
|
|
66
|
+
- Skip a test: Use `xit` instead of `it`, or `describe.skip` instead of `describe`.
|
|
67
|
+
- Run only one test: Use `it.only` instead of `it`.
|
|
68
|
+
|
|
69
|
+
## Dependencies
|
|
70
|
+
|
|
71
|
+
### Production Dependencies
|
|
72
|
+
|
|
73
|
+
| Package | Purpose |
|
|
74
|
+
|---------|---------|
|
|
75
|
+
| `commander` | CLI argument parsing and command registration |
|
|
76
|
+
| `inquirer` | Interactive prompts (list, input, confirm, checkbox) |
|
|
77
|
+
| `inquirer-autocomplete-prompt` | Autocomplete support for region selection |
|
|
78
|
+
| `colors` | Colored terminal output |
|
|
79
|
+
| `puffy` | Error handling (`catchErrors`, `wrapErrors`, `delay`) |
|
|
80
|
+
| `core-async` | Generator-based async flow (co-routines) |
|
|
81
|
+
| `fast-glob` | File pattern matching for cache directory scanning |
|
|
82
|
+
| `rimraf` | Cross-platform recursive directory deletion |
|
|
83
|
+
| `mime-types` | MIME type detection (used by fileHelper, not core functionality) |
|
|
84
|
+
| `archiver` | ZIP creation (imported by fileHelper, unused in current flows) |
|
|
85
|
+
| `tar-stream` | TAR streaming (imported by fileHelper, unused in current flows) |
|
|
86
|
+
| `convert-stream` | Stream conversion (imported by fileHelper, unused in current flows) |
|
|
87
|
+
|
|
88
|
+
### Dev Dependencies
|
|
89
|
+
|
|
90
|
+
| Package | Purpose |
|
|
91
|
+
|---------|---------|
|
|
92
|
+
| `mocha` | Test runner |
|
|
93
|
+
| `chai` | Assertion library |
|
|
94
|
+
| `eslint` | Code linting |
|
|
95
|
+
| `standard-version` | Automated versioning and changelog generation |
|
|
96
|
+
|
|
97
|
+
### Unused Dependencies
|
|
98
|
+
|
|
99
|
+
The `fileHelper.js` module is a shared utility library that imports several packages not used by `switch-profile`'s core functionality: `archiver`, `tar-stream`, `convert-stream`, and partially `mime-types`. These are present because `fileHelper.js` provides general-purpose file operations that may be used in other projects sharing this codebase.
|
|
100
|
+
|
|
101
|
+
## Release Process
|
|
102
|
+
|
|
103
|
+
### Step 1: Version Bump and Changelog
|
|
104
|
+
|
|
105
|
+
```shell
|
|
106
|
+
npm run rls -- minor # For new features (0.1.x → 0.2.0)
|
|
107
|
+
npm run rls -- patch # For bug fixes (0.1.2 → 0.1.3)
|
|
108
|
+
npm run rls -- major # For breaking changes (0.x.x → 1.0.0)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
This runs `standard-version --release-as <type>`, which:
|
|
112
|
+
1. Bumps the version in `package.json`.
|
|
113
|
+
2. Updates `CHANGELOG.md` based on conventional commit messages.
|
|
114
|
+
3. Creates a git commit with the version bump.
|
|
115
|
+
4. Creates an annotated git tag (e.g., `v0.1.3`).
|
|
116
|
+
|
|
117
|
+
### Step 2: Publish
|
|
118
|
+
|
|
119
|
+
```shell
|
|
120
|
+
npm run push
|
|
121
|
+
# Runs: git push --follow-tags origin master && npm publish --access=public
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
This:
|
|
125
|
+
1. Pushes the commit and tag to the `master` branch on GitHub.
|
|
126
|
+
2. Publishes the package to the npm registry with public access.
|
|
127
|
+
|
|
128
|
+
### Version Check
|
|
129
|
+
|
|
130
|
+
```shell
|
|
131
|
+
npm run v
|
|
132
|
+
# Prints the current version from package.json
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## npm Package Configuration
|
|
136
|
+
|
|
137
|
+
| Field | Value |
|
|
138
|
+
|-------|-------|
|
|
139
|
+
| Package name | `switch-profile` |
|
|
140
|
+
| Entry point | `index.js` |
|
|
141
|
+
| Binary | `index.js` (registered via `"bin"` field) |
|
|
142
|
+
| Access | Public |
|
|
143
|
+
| License | BSD-3-Clause |
|
|
144
|
+
| Files excluded from npm | `test/` (via `.npmignore`) |
|
|
145
|
+
|
|
146
|
+
## Conventional Commits
|
|
147
|
+
|
|
148
|
+
The project uses [standard-version](https://github.com/conventional-commits/standard-version) for automated changelog generation. Commit messages should follow the [Conventional Commits](https://www.conventionalcommits.org/) format:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
feat: Add support for profile refresh
|
|
152
|
+
fix: Multiple SSO profiles are not supported
|
|
153
|
+
chore(release): 0.1.2
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Prefixes:
|
|
157
|
+
- `feat:` - New features (bumps minor version)
|
|
158
|
+
- `fix:` - Bug fixes (bumps patch version)
|
|
159
|
+
- `chore:` - Maintenance tasks (no version bump unless specified)
|
|
160
|
+
|
|
161
|
+
## Project Layout Conventions
|
|
162
|
+
|
|
163
|
+
- **No semicolons** in JavaScript files.
|
|
164
|
+
- **Tab indentation** throughout.
|
|
165
|
+
- **Single quotes** for strings.
|
|
166
|
+
- **CommonJS** module system (`require`/`module.exports`), not ES modules.
|
|
167
|
+
- **Error tuple pattern**: Async functions return `[errors, result]` via the `catchErrors` wrapper from `puffy`.
|
|
168
|
+
- **Shebang**: `index.js` starts with `#!/usr/bin/env node` for direct CLI execution.
|
package/index.js
CHANGED
|
@@ -109,8 +109,38 @@ const createNewProfile = async (profiles, makeItDefault) => {
|
|
|
109
109
|
const [profileErrors] = await createProfile({ name, aws_access_key_id, aws_secret_access_key, region })
|
|
110
110
|
if (profileErrors)
|
|
111
111
|
return printAWSerrors([new Error('Fail to create profile'), ...profileErrors])
|
|
112
|
-
} else
|
|
113
|
-
|
|
112
|
+
} else {
|
|
113
|
+
console.log('')
|
|
114
|
+
console.log('┌─────────────────────────────────────────────────────────────────────┐'.cyan)
|
|
115
|
+
console.log('│ SSO Profile Setup Guide │'.cyan)
|
|
116
|
+
console.log('├─────────────────────────────────────────────────────────────────────┤'.cyan)
|
|
117
|
+
console.log('│ │'.cyan)
|
|
118
|
+
console.log('│ You\'re about to run \'aws configure sso\'. Here\'s what to expect: │'.cyan)
|
|
119
|
+
console.log('│ │'.cyan)
|
|
120
|
+
console.log('│ 1. SSO session name'.bold + ' │'.cyan)
|
|
121
|
+
console.log('│ Provide a name (e.g., "my-company-sso"). │'.cyan)
|
|
122
|
+
console.log('│ This enables token reuse across profiles and automatic refresh. │'.cyan)
|
|
123
|
+
console.log('│ Skipping this uses the legacy format (no auto-refresh). │'.cyan)
|
|
124
|
+
console.log('│ │'.cyan)
|
|
125
|
+
console.log('│ 2. SSO start URL'.bold + ' │'.cyan)
|
|
126
|
+
console.log('│ Your AWS SSO portal URL (e.g., https://my-co.awsapps.com/start) │'.cyan)
|
|
127
|
+
console.log('│ Ask your AWS administrator if you don\'t have it. │'.cyan)
|
|
128
|
+
console.log('│ │'.cyan)
|
|
129
|
+
console.log('│ 3. SSO region'.bold + ' │'.cyan)
|
|
130
|
+
console.log('│ The region where your SSO instance is configured. │'.cyan)
|
|
131
|
+
console.log('│ WARNING: This is NOT your deployment region. │'.yellow)
|
|
132
|
+
console.log('│ Using the wrong region causes "invalid_grant" errors. │'.yellow)
|
|
133
|
+
console.log('│ │'.cyan)
|
|
134
|
+
console.log('│ Docs: https://docs.aws.amazon.com/cli/latest/userguide/ │'.cyan)
|
|
135
|
+
console.log('│ cli-configure-sso.html │'.cyan)
|
|
136
|
+
console.log('│ │'.cyan)
|
|
137
|
+
console.log('└─────────────────────────────────────────────────────────────────────┘'.cyan)
|
|
138
|
+
console.log('')
|
|
139
|
+
|
|
140
|
+
const [ssoErrors] = await createSsoProfile(name)
|
|
141
|
+
if (ssoErrors)
|
|
142
|
+
return printAWSerrors([new Error(`Fail to create SSO profile ${name}`), ...ssoErrors])
|
|
143
|
+
}
|
|
114
144
|
|
|
115
145
|
console.log(`New profile ${name.bold} successfully created 🚀`.green)
|
|
116
146
|
|
|
@@ -302,6 +332,9 @@ const switchCmd = async () => {
|
|
|
302
332
|
const setProfileToDefault = async (profileName, profileList, successMsg) => {
|
|
303
333
|
const profile = profileList.find(p => p.friendlyName == profileName || p.name == profileName)
|
|
304
334
|
|
|
335
|
+
if (!profile)
|
|
336
|
+
return printAWSerrors([new Error(`Profile ${profileName.bold} was not found in ~/.aws/config. It may not have been created correctly. Try creating it again.`)], { noStack:true })
|
|
337
|
+
|
|
305
338
|
// Gets the AWS credentials for a specific profile. If that profile is an SSO profile, this function has a series of
|
|
306
339
|
// side-effects:
|
|
307
340
|
// - If the local SSO session stored under ~/.aws/sso/cache has expired, then it will redirect the user to the SSO portal and eventually refresh that ~/.aws/sso/cache.
|
|
@@ -310,6 +343,7 @@ const setProfileToDefault = async (profileName, profileList, successMsg) => {
|
|
|
310
343
|
if (credsErrors)
|
|
311
344
|
return printAWSerrors([new Error(`Fail to get credentials for profile ${profile.name}`), ...credsErrors])
|
|
312
345
|
|
|
346
|
+
// console.log(profile)
|
|
313
347
|
// console.log(creds)
|
|
314
348
|
|
|
315
349
|
const [errors] = await updateDefaultProfile({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switch-profile",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"bin": "index.js",
|
|
6
6
|
"description": "NPX script that exposes terminal shortcut to easily switch between cloud configuration (aws, gcp).",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"license": "BSD-3-Clause",
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"chai": "^4.1.2",
|
|
35
|
-
"eslint": "^
|
|
35
|
+
"eslint": "^10.0.0",
|
|
36
36
|
"mocha": "^8.2.1",
|
|
37
37
|
"standard-version": "^9.3.0"
|
|
38
38
|
},
|
package/src/aws/index.js
CHANGED
|
@@ -7,10 +7,10 @@ const fileHelper = require('../fileHelper')
|
|
|
7
7
|
const regions = require('./regions')
|
|
8
8
|
|
|
9
9
|
const SSO_GET_CREDS_TIMEOUT = 5*60*1000 // 5 minutes to complete the SSO login
|
|
10
|
-
const AWS_CONFIG_FILE = join(homedir(), '.aws
|
|
11
|
-
const AWS_CREDS_FILE = join(homedir(), '.aws
|
|
12
|
-
const AWS_SSO_FOLDER = join(homedir(), '.aws
|
|
13
|
-
const AWS_CLI_FOLDER = join(homedir(), '.aws
|
|
10
|
+
const AWS_CONFIG_FILE = join(homedir(), '.aws', 'config')
|
|
11
|
+
const AWS_CREDS_FILE = join(homedir(), '.aws', 'credentials')
|
|
12
|
+
const AWS_SSO_FOLDER = join(homedir(), '.aws', 'sso', 'cache')
|
|
13
|
+
const AWS_CLI_FOLDER = join(homedir(), '.aws', 'cli', 'cache')
|
|
14
14
|
const DEFAULT_CONFIG =
|
|
15
15
|
`[default]
|
|
16
16
|
region = ap-southeast-1
|
|
@@ -189,14 +189,15 @@ const getSsoCredsFromCacheFile = (access_key_end, secret_key_end) => catchErrors
|
|
|
189
189
|
|
|
190
190
|
for (let i=0;i<files.length;i++) {
|
|
191
191
|
const _creds = await fileHelper.json.get(files[i])
|
|
192
|
+
|
|
192
193
|
// Creds must be SSO
|
|
193
194
|
if (!_creds || _creds.ProviderType != 'sso' || !_creds.Credentials)
|
|
194
195
|
continue
|
|
195
196
|
|
|
196
197
|
// Creds must still be valid for the next 2 minutes
|
|
197
198
|
if (_creds.Credentials.Expiration && (Date.now() - 2*60*1000) < new Date(_creds.Credentials.Expiration).getTime()) {
|
|
198
|
-
const credsMatch = _creds.Credentials.AccessKeyId||''.slice(-4) == access_key_end &&
|
|
199
|
-
_creds.Credentials.SecretAccessKey||''.slice(-4) == secret_key_end
|
|
199
|
+
const credsMatch = (_creds.Credentials.AccessKeyId||'').slice(-4) == access_key_end &&
|
|
200
|
+
(_creds.Credentials.SecretAccessKey||'').slice(-4) == secret_key_end
|
|
200
201
|
|
|
201
202
|
if (credsMatch)
|
|
202
203
|
return {
|
|
@@ -238,6 +239,7 @@ const getSsoCredentials = profile => catchErrors((async () => {
|
|
|
238
239
|
return null
|
|
239
240
|
|
|
240
241
|
const [ssoCredsErrors, creds] = await getSsoCredsFromCacheFile(access_key_end, secret_key_end)
|
|
242
|
+
|
|
241
243
|
if (ssoCredsErrors)
|
|
242
244
|
throw wrapErrors(errMsg, ssoCredsErrors)
|
|
243
245
|
return creds
|
|
@@ -508,10 +510,12 @@ const createProfile = ({ name, aws_access_key_id, aws_secret_access_key, region
|
|
|
508
510
|
|
|
509
511
|
const createSsoProfile = name => catchErrors((async () => {
|
|
510
512
|
await awsCliV2Exists()
|
|
511
|
-
await new Promise(next => {
|
|
513
|
+
const exitCode = await new Promise(next => {
|
|
512
514
|
const child = spawn('aws', ['configure', 'sso', '--profile', name],{ stdio: 'inherit' })
|
|
513
|
-
child.on('exit', next)
|
|
515
|
+
child.on('exit', code => next(code))
|
|
514
516
|
})
|
|
517
|
+
if (exitCode !== 0)
|
|
518
|
+
throw new Error(`'aws configure sso' exited with code ${exitCode}. The SSO profile may not have been created correctly.`)
|
|
515
519
|
})())
|
|
516
520
|
|
|
517
521
|
module.exports = {
|