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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ Automation should move to `TI_*` and `TIDB_CLOUD_*` environment variables. The v

### Configure

- Authentication: a TiDB Cloud Public Key and a Private Key from the [TiDB Cloud API Keys](https://tidbcloud.com/org-settings/api-keys) console.
- Authentication: a TiDB Cloud Public Key and a Private Key from the [TiDB Cloud API Keys](https://tidbcloud.com/org-settings/api-keys) console. When either credential is missing, `ti` links to this page in its authentication error before suggesting `ti configure` or the corresponding environment variables.
- Default region: one of aws-us-east-1, aws-us-west-2, aws-eu-central-1, aws-ap-northeast-1, aws-ap-southeast-1, or alicloud-ap-southeast-1.
- Regions supporting TiDB Cloud Filesystem: aws-us-east-1, aws-ap-southeast-1, aws-us-west-2, or alicloud-ap-southeast-1. These endpoints are built into `ti`; endpoint resolution does not download a Drive9 region manifest.
- Regions supporting TiDB Cloud Starter: aws-us-east-1, aws-us-west-2, aws-eu-central-1, aws-ap-northeast-1, aws-ap-southeast-1, or alicloud-ap-southeast-1.
Expand Down
28 changes: 28 additions & 0 deletions e2e/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,34 @@ func TestErrorsAreRenderedAtCLIBoundary(t *testing.T) {
invalidQuery.wantStderrContains("ti [ERROR]: invalid --query expression")
}

func TestMissingTiDBCloudCredentialsLinkToAPIKeysPage(t *testing.T) {
bin := tiBinary(t)
home := t.TempDir()
writeE2EFile(t, filepath.Join(home, ".ti", "config"), "[default]\nregion_code = 'aws-us-east-1'\n", 0o600)
env := []string{
"HOME=" + home,
"TIDB_CLOUD_PUBLIC_KEY=",
"TIDB_CLOUD_PRIVATE_KEY=",
"TDC_PUBLIC_KEY=",
"TDC_PRIVATE_KEY=",
}

commands := [][]string{
{"fs", "create-file-system", "--display-name", "agent-workspace", "--wait"},
{"db", "create-db-cluster", "--db-cluster-type", "starter", "--db-cluster-name", "agent-database", "--wait"},
}
for _, args := range commands {
result := runTIWithInput(t, bin, "", env, args...)
result.wantExitCode(3)
result.wantStderrContains("authentication required: missing tidb_cloud_public_key and tidb_cloud_private_key")
result.wantStderrContains("Run `ti configure` or set TIDB_CLOUD_PUBLIC_KEY and TIDB_CLOUD_PRIVATE_KEY")
result.wantStderrContains("https://tidbcloud.com/org-settings/api-keys")
if result.stdout != "" {
result.fail("stdout should be empty")
}
}
}

func TestHomeMigrationThroughRealBinary(t *testing.T) {
bin := tiBinary(t)

Expand Down
5 changes: 4 additions & 1 deletion internal/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ type Credentials struct {
PrivateKey string
}

const tiDBCloudAPIKeysURL = "https://tidbcloud.com/org-settings/api-keys"

func LoadProfile(ctx context.Context, opts config.LoadOptions) (*config.Profile, error) {
profile, err := config.Load(ctx, opts)
if err == nil {
Expand Down Expand Up @@ -98,9 +100,10 @@ func MissingCredentials(profileName string, keys ...string) error {
"authentication",
3,
fmt.Sprintf(
"authentication required: missing %s for profile %q. Run `ti configure` or set TIDB_CLOUD_PUBLIC_KEY and TIDB_CLOUD_PRIVATE_KEY.",
"authentication required: missing %s for profile %q. Run `ti configure` or set TIDB_CLOUD_PUBLIC_KEY and TIDB_CLOUD_PRIVATE_KEY. If you do not have a TiDB Cloud API key pair, generate one at %s.",
joinKeys(keys),
profileName,
tiDBCloudAPIKeysURL,
),
)
}
Expand Down
4 changes: 2 additions & 2 deletions internal/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func TestValidateProfileMissingCredentials(t *testing.T) {
if got := apperr.ExitCodeFor(err); got != 3 {
t.Fatalf("expected auth exit code 3, got %d", got)
}
if got := apperr.MessageFor(err); !strings.Contains(got, "authentication required") || !strings.Contains(got, "tidb_cloud_public_key and tidb_cloud_private_key") {
if got := apperr.MessageFor(err); !strings.Contains(got, "authentication required") || !strings.Contains(got, "tidb_cloud_public_key and tidb_cloud_private_key") || !strings.Contains(got, tiDBCloudAPIKeysURL) {
t.Fatalf("unexpected message %q", got)
}
}
Expand Down Expand Up @@ -56,7 +56,7 @@ func TestLoadProfileMapsMissingFileCredentialsToAuthError(t *testing.T) {
if got := apperr.ExitCodeFor(err); got != 3 {
t.Fatalf("expected auth exit code 3, got %d", got)
}
if got := apperr.MessageFor(err); !strings.Contains(got, "authentication required") {
if got := apperr.MessageFor(err); !strings.Contains(got, "authentication required") || !strings.Contains(got, tiDBCloudAPIKeysURL) {
t.Fatalf("unexpected message %q", got)
}
}
Expand Down