Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
fe9bf23
fix: config set circular dependency - allow config commands without d…
natalie-o-perret Feb 20, 2026
0cc1dcf
refactor(tests): split integration tests by API requirement
natalie-o-perret Feb 20, 2026
69c92eb
ci: add local integration tests to CI/CD pipeline
natalie-o-perret Feb 20, 2026
713716c
ci: separate integration tests into dedicated workflow
natalie-o-perret Feb 20, 2026
8b006d6
test: update integration test to expect improved error message
natalie-o-perret Feb 20, 2026
89afba8
ci: clear go cache before building to ensure fresh binary
natalie-o-perret Feb 20, 2026
23636a1
debug: add detailed logging to integration test
natalie-o-perret Feb 20, 2026
9c82bbe
ci: more aggressive cache clearing to ensure fresh build
natalie-o-perret Feb 20, 2026
7a67f54
ci: add binary verification step to check for new code
natalie-o-perret Feb 20, 2026
6b81150
fix: set GAllAccount in early return for config commands with empty a…
natalie-o-perret Feb 20, 2026
e4d7790
debug: add logging for ReadInConfig failure path
natalie-o-perret Feb 20, 2026
2e02c08
improve: simplify error messages and update all tests
natalie-o-perret Feb 20, 2026
223e57a
improve: add 'exo config add' as alternative in error message
natalie-o-perret Feb 20, 2026
c025f99
ci: rename workflow files for clarity and consistency
natalie-o-perret Feb 20, 2026
10ecb3c
ci: standardize workflow display names with 'Tests -' prefix
natalie-o-perret Feb 20, 2026
1209b1d
ci: add name to build job for consistency
natalie-o-perret Feb 20, 2026
5adc152
ci: align E2E testscript triggers with integration tests
natalie-o-perret Feb 20, 2026
f54280b
ci: remove redundant pull_request trigger from test workflows
natalie-o-perret Feb 20, 2026
63a76ff
ci: consolidate all tests into single CI workflow
natalie-o-perret Feb 20, 2026
0af7d32
ci: target local E2E scenarios explicitly in CI workflow
natalie-o-perret Feb 20, 2026
93e4e29
ci: align golangci-lint and govulncheck workflow triggers with main CI
natalie-o-perret Feb 20, 2026
f0f4575
ci: add 'Local' to test job names to distinguish from future API tests
natalie-o-perret Feb 20, 2026
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
4 changes: 2 additions & 2 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
name: golangci-lint
name: Lint

on:
pull_request:

jobs:
golangci-lint:
name: lint
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/govulncheck.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: govulncheck
name: Check for Vulnerabilities

on:
pull_request:
Expand Down
57 changes: 55 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,63 @@ on:

jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
fetch-depth: 0
go-version-file: 'go.mod'

- name: Build binary
run: make build

test-unit:
name: Tests - Unit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: ./.github/actions/build

test-integration:
name: Tests - Integration - Local
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'

- name: Build binary
run: make build

- name: Run local integration tests
run: |
cd tests/integ/local
go test -v

test-e2e:
name: Tests - E2E (Testscript) - Local
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'

- name: Build binary
run: make build

- name: Run local E2E tests (Testscript)
run: |
cd tests/e2e
go test -v -run TestScripts/local
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: release
name: Release

on:
push:
Expand Down
82 changes: 0 additions & 82 deletions .github/workflows/testscript.yml

This file was deleted.

4 changes: 2 additions & 2 deletions cmd/config/config_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ var configSetCmd = &cobra.Command{
if len(args) < 1 {
return cmd.Usage()
}
if account.GAllAccount == nil {
return fmt.Errorf("no accounts configured")
if account.GAllAccount == nil || len(account.GAllAccount.Accounts) == 0 {
return fmt.Errorf("no accounts configured. Run: exo config (or exo config add)")
}

if a := getAccountByName(args[0]); a == nil {
Expand Down
10 changes: 7 additions & 3 deletions cmd/config/config_show.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,17 @@ Supported output template annotations: %s`,
strings.Join(output.TemplateAnnotations(&configShowOutput{}), ", ")),
Aliases: exocmd.GShowAlias,
RunE: func(cmd *cobra.Command, args []string) error {
if account.GAllAccount == nil {
return fmt.Errorf("no accounts configured")
if account.GAllAccount == nil || len(account.GAllAccount.Accounts) == 0 {
return fmt.Errorf("no accounts configured. Run: exo config (or exo config add)")
}

name := account.CurrentAccount.Name
var name string
if len(args) > 0 {
name = args[0]
} else if account.CurrentAccount != nil && account.CurrentAccount.Name != "" {
name = account.CurrentAccount.Name
} else {
return fmt.Errorf("default account not defined. Please specify an account name or set a default with: exo config set <account-name>")
}

return utils.PrintOutput(showConfig(name))
Expand Down
52 changes: 51 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ func initConfig() { //nolint:gocyclo
if err := GConfig.ReadInConfig(); err != nil {
if isNonCredentialCmd(nonCredentialCmds...) {
ignoreClientBuild = true
// Set GAllAccount with empty config so config commands can handle gracefully
account.GAllAccount = &account.Config{}
return
}

Expand All @@ -257,15 +259,53 @@ func initConfig() { //nolint:gocyclo
if len(config.Accounts) == 0 {
if isNonCredentialCmd(nonCredentialCmds...) {
ignoreClientBuild = true
// Set GAllAccount so config commands can handle the empty state gracefully
account.GAllAccount = config
return
}

log.Fatalf("no accounts were found into %q", GConfig.ConfigFileUsed())
return
}

// Allow config management commands to run without a default account
// This fixes the circular dependency where 'exo config set' couldn't run
// to set a default account because it required a default account to exist
configManagementCmds := []string{"list", "set", "show"}
isConfigManagementCmd := getCmdPosition("config") == 1
if isConfigManagementCmd && len(os.Args) > 2 {
// Check if the subcommand is a config management command
// Need to find the actual subcommand by skipping flags
for i := 2; i < len(os.Args); i++ {
if !strings.HasPrefix(os.Args[i], "-") {
isConfigManagementCmd = contains(configManagementCmds, os.Args[i])
break
}
}
} else {
isConfigManagementCmd = false
}

if config.DefaultAccount == "" && gAccountName == "" {
log.Fatalf("default account not defined")
// Allow config management commands to proceed without default account
if isConfigManagementCmd {
ignoreClientBuild = true
// Set GAllAccount so config commands can access the account list
account.GAllAccount = config
return
}

// Provide helpful error message with available accounts
var availableAccounts []string
for _, acc := range config.Accounts {
availableAccounts = append(availableAccounts, acc.Name)
}
if len(availableAccounts) > 0 {
log.Fatalf("default account not defined\n\nSet a default account with: exo config set <account-name>\nAvailable accounts: %s\n\nOr specify an account for this command with: --use-account <account-name>",
strings.Join(availableAccounts, ", "))
} else {
log.Fatalf("default account not defined")
}
}

if gAccountName == "" {
Expand Down Expand Up @@ -364,6 +404,16 @@ func getCmdPosition(cmd string) int {
return count
}

// contains checks if a string slice contains a specific string
func contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}

// readFromEnv is a os.Getenv on steroids
func readFromEnv(keys ...string) string {
for _, key := range keys {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Test: Commands require default account or --use-account flag
# When config exists but no default account is set, most commands should fail
# with a helpful error message, unless --use-account flag is provided.

# Create config with account but NO defaultAccount field
mkdir -p .config/exoscale
cp test-config.toml .config/exoscale/exoscale.toml

# Config management commands work (they don't require default)
exec exo config list
stdout 'Exoscale-Test'

# Config show without account name or default should fail with helpful message
! exec exo config show
stderr 'default account not defined'

# Non-config commands require default account
! exec exo compute instance list
stderr 'default account not defined'
stderr 'Set a default account with: exo config set <account-name>'
stderr 'Available accounts: Exoscale-Test'

# Workaround 1: --use-account flag bypasses the default account requirement
exec exo --use-account Exoscale-Test config show
stdout 'Exoscale-Test'
stdout 'EXOtest123'

# Workaround 2: Set a default account
exec exo config set Exoscale-Test
stdout 'Default profile set to \[Exoscale-Test\]'

# Now commands work without flag
exec exo config show
stdout 'Exoscale-Test'

# Config file without defaultAccount field
-- test-config.toml --
[[accounts]]
name = "Exoscale-Test"
key = "EXOtest123"
secret = "testsecret123"
defaultZone = "ch-gva-2"

This file was deleted.

Loading