Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/releaser-internal.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:

VERSION=$(echo "$CURRENT_TAG" | sed 's/^v//' | sed 's/-beta-internal//')
NEW_VERSION=$(semver "$VERSION" -i patch)
NEW_TAG="v${NEW_VERSION}"
NEW_TAG="v${NEW_VERSION}-beta-internal"
echo "new_tag=$NEW_TAG" >> $GITHUB_OUTPUT
echo "New tag: $NEW_TAG"

Expand Down
1 change: 0 additions & 1 deletion .goreleaser-internal.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ release:
github:
owner: berrybytes
name: awsctl
name_template: "{{.Version}}-beta-internal"
extra_files:
- glob: ./LICENSE
- glob: ./README.md
Expand Down
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,14 @@ ssoSessions:

The following table summarizes the available `awsctl` commands:

| Command | Description |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `awsctl sso setup` | Creates or updates an AWS SSO profile. If a config file is available at `~/.config/awsctl/`, it will be used; otherwise, you will be prompted to enter the SSO Start URL and Region. The selected profile is then set as the default and authenticated. |
| `awsctl sso init` | Starts SSO authentication by allowing you to select from existing AWS SSO profiles (created via `awsctl sso setup`). Useful for switching between multiple configured SSO profiles. |
| `awsctl bastion` | Manages SSH/SSM connections, SOCKS proxy, or port forwarding to bastion hosts or EC2 instances. |
| `awsctl rds` | Connects to RDS databases directly or via SSH/SSM tunnels. |
| `awsctl eks` | Updates kubeconfig for accessing Amazon EKS clusters. |
| `awsctl ecr` | Authenticates to Amazon ECR for container image operations. |
| Command | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `awsctl sso setup` | Creates/updates AWS SSO profiles. Supports flags: `--name`, `--start-url`, `--region` for non-interactive setup. Uses `~/.config/awsctl/config.yml` if available; otherwise, you will be prompted to enter the SSO Start URL, Region and SSO Name. The selected profile is then set as the default and authenticated. |
| `awsctl sso init` | Starts SSO authentication by allowing you to select from existing AWS SSO profiles (created via `awsctl sso setup`). Useful for switching between multiple configured SSO profiles. |
| `awsctl bastion` | Manages SSH/SSM connections, SOCKS proxy, or port forwarding to bastion hosts or EC2 instances. |
| `awsctl rds` | Connects to RDS databases directly or via SSH/SSM tunnels. |
| `awsctl eks` | Updates kubeconfig for accessing Amazon EKS clusters. |
| `awsctl ecr` | Authenticates to Amazon ECR for container image operations. |

#### For detailed CLI command usage, see [Command Usage Documentation](docs/usage/commands.md).

Expand Down
36 changes: 34 additions & 2 deletions cmd/sso/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,45 @@ package sso
import (
"errors"
"fmt"
"strings"

"github.com/BerryBytes/awsctl/internal/sso"
generalutils "github.com/BerryBytes/awsctl/utils/general"
promptUtils "github.com/BerryBytes/awsctl/utils/prompt"

"github.com/spf13/cobra"
)

func SetupCmd(ssoClient sso.SSOClient) *cobra.Command {
return &cobra.Command{
var startURL string
var region string
var name string

cmd := &cobra.Command{
Use: "setup",
Short: "Setup AWS SSO configuration",
RunE: func(cmd *cobra.Command, args []string) error {
err := ssoClient.SetupSSO()
if startURL != "" && !strings.HasPrefix(startURL, "https://") {
return fmt.Errorf("invalid start URL: must begin with https://")
}

if region != "" {
if !generalutils.IsRegionValid(region) {
return fmt.Errorf("invalid AWS region: %s", region)
}
}

if name != "" && !generalutils.IsValidSessionName(name) {
return fmt.Errorf("invalid session name: must only contain letters, numbers, dashes, or underscores, and cannot start or end with a dash/underscore")
}

opts := sso.SSOFlagOptions{
StartURL: startURL,
Region: region,
Name: name,
}

err := ssoClient.SetupSSO(opts)
if err != nil {
if errors.Is(err, promptUtils.ErrInterrupted) {
return nil
Expand All @@ -25,4 +51,10 @@ func SetupCmd(ssoClient sso.SSOClient) *cobra.Command {
return nil
},
}

cmd.Flags().StringVar(&name, "name", "", "SSO session name")
cmd.Flags().StringVar(&startURL, "start-url", "", "AWS SSO Start URL")
cmd.Flags().StringVar(&region, "region", "", "AWS SSO Region")

return cmd
}
160 changes: 146 additions & 14 deletions cmd/sso/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import (
"errors"
"testing"

"github.com/BerryBytes/awsctl/internal/sso"
mock_sso "github.com/BerryBytes/awsctl/tests/mock/sso"
promptUtils "github.com/BerryBytes/awsctl/utils/prompt"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
)
Expand All @@ -18,30 +18,86 @@ func TestSetupCmd(t *testing.T) {
mockSSOClient := mock_sso.NewMockSSOClient(ctrl)

tests := []struct {
name string
mockSetup func()
expectedError string
name string
args []string
mockSetup func()
expectedError string
expectedOutput string
}{
{
name: "successful setup",
name: "successful setup with no flags",
args: []string{},
mockSetup: func() {
mockSSOClient.EXPECT().SetupSSO(sso.SSOFlagOptions{}).Return(nil)
},
},
{
name: "successful setup with all flags",
args: []string{"--name=test-session", "--start-url=https://test.awsapps.com/start", "--region=us-east-1"},
mockSetup: func() {
mockSSOClient.EXPECT().SetupSSO().Return(nil)
mockSSOClient.EXPECT().SetupSSO(sso.SSOFlagOptions{
Name: "test-session",
StartURL: "https://test.awsapps.com/start",
Region: "us-east-1",
}).Return(nil)
},
expectedError: "",
},
{
name: "error during setup",
args: []string{},
mockSetup: func() {
mockSSOClient.EXPECT().SetupSSO().Return(errors.New("setup error"))
mockSSOClient.EXPECT().SetupSSO(sso.SSOFlagOptions{}).Return(errors.New("setup error"))
},
expectedError: "setup error",
expectedError: "SSO initialization failed: setup error",
},
{
name: "interrupted by user",
args: []string{},
mockSetup: func() {
mockSSOClient.EXPECT().SetupSSO().Return(promptUtils.ErrInterrupted)
mockSSOClient.EXPECT().SetupSSO(sso.SSOFlagOptions{}).Return(promptUtils.ErrInterrupted)
},
},
{
name: "invalid start URL format",
args: []string{"--start-url=invalid-url"},
mockSetup: func() {

},
expectedError: "invalid start URL: must begin with https://",
},
{
name: "invalid region",
args: []string{"--region=invalid-region"},
mockSetup: func() {

},
expectedError: "invalid AWS region: invalid-region",
},
{
name: "invalid session name",
args: []string{"--name=invalid-name-"},
mockSetup: func() {

},
expectedError: "invalid session name: must only contain letters, numbers, dashes, or underscores, and cannot start or end with a dash/underscore",
},
{
name: "partial flags - only name provided",
args: []string{"--name=valid-name"},
mockSetup: func() {
mockSSOClient.EXPECT().SetupSSO(sso.SSOFlagOptions{
Name: "valid-name",
}).Return(nil)
},
},
{
name: "partial flags - only region provided",
args: []string{"--region=us-west-2"},
mockSetup: func() {
mockSSOClient.EXPECT().SetupSSO(sso.SSOFlagOptions{
Region: "us-west-2",
}).Return(nil)
},
expectedError: "",
},
}

Expand All @@ -50,15 +106,91 @@ func TestSetupCmd(t *testing.T) {
tt.mockSetup()

cmd := SetupCmd(mockSSOClient)
cmd.SetArgs([]string{})
cmd.SetArgs(tt.args)

err := cmd.Execute()

if tt.expectedError == "" {
assert.NoError(t, err)
if tt.expectedError != "" {
assert.Error(t, err)
assert.Contains(t, err.Error(), tt.expectedError)
} else {
assert.NoError(t, err)
}
})
}
}

func TestSetupCmd_FlagValidation(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

mockSSOClient := mock_sso.NewMockSSOClient(ctrl)

tests := []struct {
name string
args []string
expectCall bool
expectedError string
}{
{
name: "valid start URL",
args: []string{"--start-url=https://valid.awsapps.com/start"},
expectCall: true,
},
{
name: "invalid start URL missing https",
args: []string{"--start-url=http://invalid.awsapps.com/start"},
expectCall: false,
expectedError: "invalid start URL: must begin with https://",
},
{
name: "invalid start URL format",
args: []string{"--start-url=invalid-format"},
expectCall: false,
expectedError: "invalid start URL: must begin with https://",
},
{
name: "valid region",
args: []string{"--region=eu-west-1"},
expectCall: true,
},
{
name: "invalid region",
args: []string{"--region=invalid-region"},
expectCall: false,
expectedError: "invalid AWS region: invalid-region",
},
{
name: "valid session name",
args: []string{"--name=valid_name-123"},
expectCall: true,
},
{
name: "invalid session name starts with dash",
args: []string{"--name=-invalid"},
expectCall: false,
expectedError: "invalid session name: must only contain letters, numbers, dashes, or underscores, and cannot start or end with a dash/underscore",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.expectCall {
mockSSOClient.EXPECT().SetupSSO(gomock.Any()).Return(nil)
}

cmd := SetupCmd(mockSSOClient)
cmd.SetArgs(tt.args)
cmd.SilenceErrors = true
cmd.SilenceUsage = true

err := cmd.Execute()

if tt.expectedError != "" {
assert.Error(t, err)
assert.Contains(t, err.Error(), tt.expectedError)
} else {
assert.NoError(t, err)
}
})
}
Expand Down
60 changes: 55 additions & 5 deletions docs/usage/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,61 @@

Creates or updates AWS SSO profiles.

- Prompts for:
- SSO Start URL
- AWS Region
- Uses defaults from `~/.config/awsctl/config.yml` if available.
- Automatically sets the profile as default and authenticates it after setup.
#### Basic Usage

```bash
awsctl sso setup [flags]
```

#### Flags

| Flag | Description | Required | Example |
| ------------- | ---------------------------------------------- | -------- | ---------------------------------------------- |
| `--name` | SSO session name | No | `--name my-sso-session` |
| `--start-url` | AWS SSO start URL (must begin with `https://`) | No | `--start-url https://my-sso.awsapps.com/start` |
| `--region` | AWS region for the SSO session | No | `--region us-east-1` |

#### Behavior

- **Interactive Mode** (default when no flags):

- Prompts for:
- SSO Start URL
- AWS Region
- Session name (default: "default-sso")
- Uses defaults from `~/.config/awsctl/config.yml` if available
- Validates all inputs before creating session

- **Non-interactive Mode** (when all flags provided):

- Creates session immediately without prompts
- Validates:
- Start URL format (`https://`)
- Valid AWS region
- Proper session name format

#### Examples

1. Fully interactive:

```bash
awsctl sso setup
```

2. Fully non-interactive:

```bash
awsctl sso setup --name dev-session --start-url https://dev.awsapps.com/start --region us-east-1
```

#### Validation Rules

- `--start-url`: Must begin with `https://`
- `--region`: Must be valid AWS region code
- `--name`:
- Alphanumeric with dashes/underscores
- Cannot start/end with special chars
- 3-64 characters

---

Expand Down
Loading
Loading