Skip to content

Commit 6d55fcc

Browse files
authored
Merge pull request #48 from DevopsArtFactory/feat/verbose-logging
feat: add debug logging and --verbose flag (M4.2)
2 parents 188c664 + 159bdd3 commit 6d55fcc

File tree

16 files changed

+640
-6
lines changed

16 files changed

+640
-6
lines changed

README.md

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ unic
3131
unic --profile my-profile
3232
unic --region ap-northeast-2
3333

34+
# Enable verbose debug logging (writes to ~/.config/unic/logs/unic.log)
35+
unic --verbose
36+
unic -v
37+
3438
# Initialize config file
3539
unic init # Create default config
3640
unic init --force # Overwrite existing config
@@ -40,32 +44,100 @@ unic init --force # Overwrite existing config
4044

4145
`~/.config/unic/config.yaml` (created via `unic init` or auto-generated on first run)
4246

47+
### Legacy Format (Flat)
48+
4349
```yaml
44-
# Simple format
4550
default_profile: my-profile
4651
default_region: ap-northeast-2
4752
```
4853
54+
### Context-Based Format
55+
4956
```yaml
50-
# Context-based format
5157
current: dev-sso
5258

59+
defaults:
60+
region: us-east-1
61+
5362
contexts:
63+
# SSO authentication
5464
- name: dev-sso
55-
profile: dev-sso
5665
region: ap-northeast-2
66+
auth_type: sso
67+
sso_start_url: https://my-sso-portal.awsapps.com/start
68+
sso_account_id: "123456789012"
69+
sso_role_name: DeveloperRole
5770

58-
- name: prod-admin
59-
profile: prod-admin
71+
# Assume Role (cross-account)
72+
- name: prod-assume
6073
region: us-east-1
74+
auth_type: assume_role
75+
profile: base-profile
76+
role_arn: arn:aws:iam::987654321098:role/CrossAccountRole
77+
external_id: optional-external-id
78+
79+
# Credential profile
80+
- name: staging-creds
81+
region: eu-west-1
82+
auth_type: credential
83+
profile: staging
6184
```
6285
86+
### Auth Types
87+
88+
| Auth Type | Required Fields | Description |
89+
|-----------|----------------|-------------|
90+
| `sso` | `sso_start_url`, `sso_account_id`, `sso_role_name` | AWS SSO portal login with token caching |
91+
| `credential` | `profile` | Uses `~/.aws/credentials` profile directly |
92+
| `assume_role` | `profile`, `role_arn` | Assumes a cross-account role from a base profile |
93+
6394
**Priority**: CLI flags (`--profile`, `--region`) > context settings > config defaults > hardcoded defaults (`us-east-1`)
6495

6596
## Currently Implemented Features
6697

6798
| Service | Feature | Status |
6899
|---------|---------|--------|
100+
| EC2 | SSM Session Manager (connect to running, SSM-managed instances) | ✅ Implemented |
101+
| VPC | VPC Browser (VPCs → Subnets → Available IPs with reserved-IP exclusion) | ✅ Implemented |
102+
| RDS | RDS Browser (list, start/stop, failover, Aurora cluster support, auto-polling) | ✅ Implemented |
103+
| Route53 | DNS Browser (Hosted Zones → Records → Record Detail, public/private zones) | ✅ Implemented |
104+
| Secrets Manager | Secrets Browser (list secrets, view key-value pairs or raw values) | ✅ Implemented |
105+
106+
## TUI Key Bindings
107+
108+
### Global Navigation
109+
110+
| Key | Action |
111+
|-----|--------|
112+
| `j`/`k` or `↑`/`↓` | Navigate list |
113+
| `Enter` | Select item |
114+
| `Esc` | Go back one screen |
115+
| `q` | Quit (on service list) |
116+
| `H` | Jump to home (service list) |
117+
| `C` | Open context switcher |
118+
| `/` | Toggle filter mode |
119+
| `Ctrl+C` | Force quit |
120+
121+
### RDS Detail Actions
122+
123+
| Key | Action | Condition |
124+
|-----|--------|-----------|
125+
| `s` | Start database | Instance/cluster is stopped |
126+
| `x` | Stop database | Instance/cluster is available |
127+
| `f` | Failover database | Multi-AZ standalone or Aurora cluster |
128+
| `r` | Refresh status | Always |
129+
130+
### Context Switcher
131+
132+
| Key | Action |
133+
|-----|--------|
134+
| `Enter` | Switch to selected context |
135+
| `a` | Add new context (wizard) |
136+
| `Esc` | Back |
137+
138+
### Filtering
139+
140+
Available on: EC2 instances, VPC/Subnets, RDS instances, Route53 zones/records, Secrets Manager. Press `/` to enter filter mode, type to search, `Esc` or `Enter` to exit filter mode.
69141
| EC2 | SSM Session Manager (connect to EC2 instances) | ✅ Implemented |
70142
| EC2 | Security Group Browser (list/filter SGs, view inbound/outbound rules) | ✅ Implemented |
71143
| VPC | VPC Browser (VPCs → subnets → available IPs) | ✅ Implemented |

cmd/unic/main.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,18 @@ import (
1010
"unic/internal/app"
1111
"unic/internal/cli"
1212
"unic/internal/config"
13+
uniclog "unic/internal/log"
1314
)
1415

1516
func main() {
1617
rootCmd := cli.NewRootCmd()
1718

1819
rootCmd.RunE = func(cmd *cobra.Command, args []string) error {
20+
if err := uniclog.Init(cli.Verbose()); err != nil {
21+
return fmt.Errorf("logger init error: %w", err)
22+
}
23+
defer uniclog.Close()
24+
1925
configPath, err := config.DefaultPath()
2026
if err != nil {
2127
return err
@@ -30,6 +36,13 @@ func main() {
3036
return fmt.Errorf("config load error: %w", err)
3137
}
3238

39+
uniclog.Info("config", "config loaded",
40+
"profile", cfg.Profile,
41+
"region", cfg.Region,
42+
"context", cfg.ContextName,
43+
"auth_type", string(cfg.AuthType),
44+
)
45+
3346
p := tea.NewProgram(app.New(cfg, configPath), tea.WithAltScreen())
3447
if _, err := p.Run(); err != nil {
3548
return fmt.Errorf("TUI error: %w", err)

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/aws/aws-sdk-go-v2/service/ec2 v1.296.0
1010
github.com/aws/aws-sdk-go-v2/service/rds v1.116.3
1111
github.com/aws/aws-sdk-go-v2/service/route53 v1.62.4
12+
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.4
1213
github.com/aws/aws-sdk-go-v2/service/ssm v1.68.3
1314
github.com/aws/aws-sdk-go-v2/service/sso v1.30.13
1415
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9
@@ -25,7 +26,6 @@ require (
2526
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect
2627
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
2728
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 // indirect
28-
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.4 // indirect
2929
github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect
3030
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect
3131
github.com/aws/smithy-go v1.24.2 // indirect

internal/auth/auth.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@ import (
1313
"github.com/aws/aws-sdk-go-v2/service/sts"
1414

1515
"unic/internal/config"
16+
uniclog "unic/internal/log"
1617
awsservice "unic/internal/services/aws"
1718
)
1819

1920
// PostSwitch performs the auth action after switching to a context.
2021
// Returns a human-readable status message.
2122
func PostSwitch(cfg *config.Config) (string, error) {
23+
uniclog.Info("auth", "post-switch started", "context", cfg.ContextName, "auth_type", string(cfg.AuthType))
24+
2225
var msg string
2326
var err error
2427

@@ -33,6 +36,7 @@ func PostSwitch(cfg *config.Config) (string, error) {
3336
msg = fmt.Sprintf("Context %q activated (profile: %s, region: %s)", cfg.ContextName, cfg.Profile, cfg.Region)
3437
}
3538
if err != nil {
39+
uniclog.Error("auth", "post-switch failed", "error", err.Error())
3640
return "", err
3741
}
3842

@@ -46,6 +50,7 @@ func PostSwitch(cfg *config.Config) (string, error) {
4650
}
4751

4852
func postSwitchSSO(cfg *config.Config) (string, error) {
53+
uniclog.Debug("auth", "starting SSO login", "sso_start_url", cfg.SSOStartURL)
4954
if err := awsservice.RunSSOLogin(cfg); err != nil {
5055
return "", err
5156
}
@@ -73,6 +78,7 @@ func postSwitchCredential(cfg *config.Config) (string, error) {
7378
}
7479

7580
func postSwitchAssumeRole(cfg *config.Config) (string, error) {
81+
uniclog.Debug("auth", "assuming role", "role_arn", cfg.RoleArn)
7682
ctx := context.Background()
7783

7884
opts := []func(*awsconfig.LoadOptions) error{

internal/cli/root.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ var (
1010

1111
profile string
1212
region string
13+
verbose bool
1314
)
1415

1516
func NewRootCmd() *cobra.Command {
@@ -22,6 +23,7 @@ func NewRootCmd() *cobra.Command {
2223

2324
cmd.PersistentFlags().StringVarP(&profile, "profile", "p", "", "AWS profile to use")
2425
cmd.PersistentFlags().StringVar(&region, "region", "", "AWS region to use")
26+
cmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose debug logging")
2527

2628
cmd.AddCommand(newInitCmd())
2729

@@ -43,3 +45,8 @@ func Region() *string {
4345
}
4446
return &region
4547
}
48+
49+
// Verbose returns true if --verbose was passed.
50+
func Verbose() bool {
51+
return verbose
52+
}

internal/cli/root_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,11 @@ func TestRootCmdHasFlags(t *testing.T) {
2424
if rf == nil {
2525
t.Error("expected --region flag")
2626
}
27+
vf := cmd.PersistentFlags().Lookup("verbose")
28+
if vf == nil {
29+
t.Error("expected --verbose flag")
30+
}
31+
if vf != nil && vf.Shorthand != "v" {
32+
t.Errorf("expected --verbose shorthand 'v', got '%s'", vf.Shorthand)
33+
}
2734
}

internal/config/config.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"path/filepath"
77

88
"gopkg.in/yaml.v3"
9+
10+
uniclog "unic/internal/log"
911
)
1012

1113
const (
@@ -139,6 +141,14 @@ func Load(cliProfile, cliRegion *string, configPath string) (*Config, error) {
139141
region = *cliRegion
140142
}
141143

144+
uniclog.Debug("config", "config resolved",
145+
"path", configPath,
146+
"profile", profile,
147+
"region", region,
148+
"context", contextName,
149+
"auth_type", string(authType),
150+
)
151+
142152
return &Config{
143153
Profile: profile,
144154
Region: region,

0 commit comments

Comments
 (0)