Skip to content

feat: add warning message if multiple variable sources #4880

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
7 changes: 6 additions & 1 deletion core/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,12 @@ func Bootstrap(config *BootstrapConfig) (exitCode int, result any, err error) {
// Run checks after command has been executed
defer func() { // if we plan to remove defer, do not forget logger is not set until cobra pre init func
// Check CLI new version and api key expiration date
runAfterCommandChecks(ctx, config.BuildInfo.checkVersion, checkAPIKey)
runAfterCommandChecks(
ctx,
config.BuildInfo.checkVersion,
checkAPIKey,
checkIfMultipleVariableSources,
)
}()

if !config.DisableAliases {
Expand Down
47 changes: 47 additions & 0 deletions core/checks.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
package core

import (
"bytes"
"context"
"fmt"
"os"
"path/filepath"
"reflect"
"time"

iam "github.com/scaleway/scaleway-sdk-go/api/iam/v1alpha1"
"github.com/scaleway/scaleway-sdk-go/scw"
)

var (
apiKeyExpireTime = 24 * time.Hour
lastChecksFileLocalName = "last-cli-checks"
)

const (
defaultCredentialSource = "environment variable"
)

type AfterCommandCheckFunc func(ctx context.Context)

// wasFileModifiedLast24h checks whether the file has been updated during last 24 hours.
Expand Down Expand Up @@ -105,3 +112,43 @@ func checkAPIKey(ctx context.Context) {
ExtractLogger(ctx).Warningf("Current api key expires in %s\n", expiresIn)
}
}

// checkIfMultipleVariableSources return an informative message during the CLI initialization
// if there are multiple sources of configuration that could confuse the user
func checkIfMultipleVariableSources(ctx context.Context) {
config, err := scw.LoadConfigFromPath(ExtractConfigPath(ctx))
if err != nil {
return
}

activeProfile, err := config.GetActiveProfile()
if err != nil {
return
}

profileEnv := scw.LoadEnvProfile()

vFile := reflect.ValueOf(activeProfile).Elem()
vEnv := reflect.ValueOf(profileEnv).Elem()
t := vFile.Type()

var buffer bytes.Buffer
buffer.WriteString("Checking multiple variable sources: \n")

for i := range t.NumField() {
valFile := vFile.Field(i)
valEnv := vEnv.Field(i)

if !valFile.IsNil() && !valEnv.IsNil() {
if valFile.Elem().String() != valEnv.Elem().String() {
buffer.WriteString(fmt.Sprintf(
"- Variable '%s' is defined in both config.yaml and environment with different values. Using: %s.\n",
t.Field(i).Name,
defaultCredentialSource,
))
}
}
}

ExtractLogger(ctx).Warning(buffer.String())
}
51 changes: 51 additions & 0 deletions core/checks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,54 @@ func TestCheckAPIKey(t *testing.T) {
},
}))
}

func TestCheckIfMultipleVariableSources(t *testing.T) {
testCommands := core.NewCommands(
&core.Command{
Namespace: "test",
ArgSpecs: core.ArgSpecs{},
ArgsType: reflect.TypeOf(testType{}),
Run: func(ctx context.Context, _ any) (any, error) { return "", nil },
},
)

t.Run("conflicting sources should trigger warning", core.Test(&core.TestConfig{
Commands: testCommands,
TmpHomeDir: true,
BeforeFunc: func(ctx *core.BeforeFuncCtx) error {
cfg := &scw.Config{
Profile: scw.Profile{
AccessKey: scw.StringPtr("SCW11111111111111111"),
SecretKey: scw.StringPtr("config-secret"),
DefaultProjectID: scw.StringPtr("config-project-id"),
},
}

configPath := filepath.Join(ctx.OverrideEnv["HOME"], ".config", "scw", "config.yaml")
if err := cfg.SaveTo(configPath); err != nil {
return err
}

t.Setenv("SCW_ACCESS_KEY", "SCW99999999999999999")
t.Setenv("SCW_SECRET_KEY", "env-secret")
t.Setenv("SCW_DEFAULT_PROJECT_ID", "config-project-id")

return nil
},
Cmd: "scw test",
Check: core.TestCheckCombine(
core.TestCheckExitCode(0),
func(t *testing.T, ctx *core.CheckFuncCtx) {
t.Helper()
expected := "" +
"Checking multiple variable sources: \n" +
"- Variable 'AccessKey' is defined in both config.yaml and environment with different values. " +
"Using: environment variable.\n" +
"- Variable 'SecretKey' is defined in both config.yaml and environment with different values. " +
"Using: environment variable.\n\n"

assert.Equal(t, expected, ctx.LogBuffer)
},
),
}))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
version: 1
interactions:
- request:
body: '{"message":"authentication is denied","method":"api_key","reason":"not_found","type":"denied_authentication"}'
form: {}
headers:
User-Agent:
- scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.4; darwin; arm64) cli-e2e-test
url: https://api.scaleway.com/iam/v1alpha1/api-keys/SCWXXXXXXXXXXXXXXXXX
method: GET
response:
body: '{"message":"authentication is denied","method":"api_key","reason":"not_found","type":"denied_authentication"}'
headers:
Content-Length:
- "109"
Content-Security-Policy:
- default-src 'none'; frame-ancestors 'none'
Content-Type:
- application/json
Date:
- Tue, 22 Jul 2025 12:16:22 GMT
Server:
- Scaleway API Gateway (fr-par-1;edge01)
Strict-Transport-Security:
- max-age=63072000
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Request-Id:
- d55dda1c-064c-4670-95ff-50d7c32fe521
status: 401 Unauthorized
code: 401
duration: ""
Loading