-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
## Changes This command provide details on auth configuration user is using as well as authenticated user and auth mechanism used. Relies on databricks/databricks-sdk-go#838 (tests will fail until merged) Examples of output ``` Workspace: https://test.com User: andrew.nester@databricks.com Authenticated with: pat ----- Configuration: ✓ auth_type: pat ✓ host: https://test.com (from bundle) ✓ profile: DEFAULT (from --profile flag) ✓ token: ******** (from /Users/andrew.nester/.databrickscfg config file) ``` ``` DATABRICKS_AUTH_TYPE=azure-msi databricks auth describe -p "Azure 2" Unable to authenticate: inner token: Post "https://foobar.com/oauth2/token": AADSTS900023: Specified tenant identifier foobar_aaaaaaa' is neither a valid DNS name, nor a valid external domain. See https://login.microsoftonline.com/error?code=900023 ----- Configuration: ✓ auth_type: azure-msi (from DATABRICKS_AUTH_TYPE environment variable) ✓ azure_client_id: 8470f3ba-aaaa-bbbb-cccc-xxxxyyyyzzzz (from /Users/andrew.nester/.databrickscfg config file) ~ azure_client_secret: ******** (from /Users/andrew.nester/.databrickscfg config file, not used for auth type azure-msi) ~ azure_tenant_id: foobar_aaaaaaa (from /Users/andrew.nester/.databrickscfg config file, not used for auth type azure-msi) ✓ azure_use_msi: true (from /Users/andrew.nester/.databrickscfg config file) ✓ host: https://foobar.com (from /Users/andrew.nester/.databrickscfg config file) ✓ profile: Azure 2 (from --profile flag) ``` For account ``` Unable to authenticate: default auth: databricks-cli: cannot get access token: Error: token refresh: Post "https://xxxxxxx.com/v1/token": http 400: {"error":"invalid_request","error_description":"Refresh token is invalid"} . Config: host=https://xxxxxxx.com, account_id=ed0ca3c5-fae5-4619-bb38-eebe04a4af4b, profile=ACCOUNT-ed0ca3c5-fae5-4619-bb38-eebe04a4af4b ----- Configuration: ✓ account_id: ed0ca3c5-fae5-4619-bb38-eebe04a4af4b (from /Users/andrew.nester/.databrickscfg config file) ✓ auth_type: databricks-cli (from /Users/andrew.nester/.databrickscfg config file) ✓ host: https://xxxxxxxxx.com (from /Users/andrew.nester/.databrickscfg config file) ✓ profile: ACCOUNT-ed0ca3c5-fae5-4619-bb38-eebe04a4af4b ``` ## Tests Added unit tests --------- Co-authored-by: Julia Crawford (Databricks) <julia.crawford@databricks.com>
- Loading branch information
1 parent
079c416
commit 8c144a2
Showing
8 changed files
with
583 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
package auth | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
|
||
"github.com/databricks/cli/cmd/root" | ||
"github.com/databricks/cli/libs/cmdio" | ||
"github.com/databricks/cli/libs/flags" | ||
"github.com/databricks/databricks-sdk-go/config" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var authTemplate = `{{"Host:" | bold}} {{.Details.Host}} | ||
{{- if .AccountID}} | ||
{{"Account ID:" | bold}} {{.AccountID}} | ||
{{- end}} | ||
{{- if .Username}} | ||
{{"User:" | bold}} {{.Username}} | ||
{{- end}} | ||
{{"Authenticated with:" | bold}} {{.Details.AuthType}} | ||
----- | ||
` + configurationTemplate | ||
|
||
var errorTemplate = `Unable to authenticate: {{.Error}} | ||
----- | ||
` + configurationTemplate | ||
|
||
const configurationTemplate = `Current configuration: | ||
{{- $details := .Status.Details}} | ||
{{- range $a := .ConfigAttributes}} | ||
{{- $k := $a.Name}} | ||
{{- if index $details.Configuration $k}} | ||
{{- $v := index $details.Configuration $k}} | ||
{{if $v.AuthTypeMismatch}}~{{else}}✓{{end}} {{$k | bold}}: {{$v.Value}} | ||
{{- if not (eq $v.Source.String "dynamic configuration")}} | ||
{{- " (from" | italic}} {{$v.Source.String | italic}} | ||
{{- if $v.AuthTypeMismatch}}, {{ "not used for auth type " | red | italic }}{{$details.AuthType | red | italic}}{{end}}) | ||
{{- end}} | ||
{{- end}} | ||
{{- end}} | ||
` | ||
|
||
func newDescribeCommand() *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "describe", | ||
Short: "Describes the credentials and the source of those credentials, being used by the CLI to authenticate", | ||
} | ||
|
||
var showSensitive bool | ||
cmd.Flags().BoolVar(&showSensitive, "sensitive", false, "Include sensitive fields like passwords and tokens in the output") | ||
|
||
cmd.RunE = func(cmd *cobra.Command, args []string) error { | ||
ctx := cmd.Context() | ||
var status *authStatus | ||
var err error | ||
status, err = getAuthStatus(cmd, args, showSensitive, func(cmd *cobra.Command, args []string) (*config.Config, bool, error) { | ||
isAccount, err := root.MustAnyClient(cmd, args) | ||
return root.ConfigUsed(cmd.Context()), isAccount, err | ||
}) | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
if status.Error != nil { | ||
return render(ctx, cmd, status, errorTemplate) | ||
} | ||
|
||
return render(ctx, cmd, status, authTemplate) | ||
} | ||
|
||
return cmd | ||
} | ||
|
||
type tryAuth func(cmd *cobra.Command, args []string) (*config.Config, bool, error) | ||
|
||
func getAuthStatus(cmd *cobra.Command, args []string, showSensitive bool, fn tryAuth) (*authStatus, error) { | ||
cfg, isAccount, err := fn(cmd, args) | ||
ctx := cmd.Context() | ||
if err != nil { | ||
return &authStatus{ | ||
Status: "error", | ||
Error: err, | ||
Details: getAuthDetails(cmd, cfg, showSensitive), | ||
}, nil | ||
} | ||
|
||
if isAccount { | ||
a := root.AccountClient(ctx) | ||
|
||
// Doing a simple API call to check if the auth is valid | ||
_, err := a.Workspaces.List(ctx) | ||
if err != nil { | ||
return &authStatus{ | ||
Status: "error", | ||
Error: err, | ||
Details: getAuthDetails(cmd, cfg, showSensitive), | ||
}, nil | ||
} | ||
|
||
status := authStatus{ | ||
Status: "success", | ||
Details: getAuthDetails(cmd, a.Config, showSensitive), | ||
AccountID: a.Config.AccountID, | ||
Username: a.Config.Username, | ||
} | ||
|
||
return &status, nil | ||
} | ||
|
||
w := root.WorkspaceClient(ctx) | ||
me, err := w.CurrentUser.Me(ctx) | ||
if err != nil { | ||
return &authStatus{ | ||
Status: "error", | ||
Error: err, | ||
Details: getAuthDetails(cmd, cfg, showSensitive), | ||
}, nil | ||
} | ||
|
||
status := authStatus{ | ||
Status: "success", | ||
Details: getAuthDetails(cmd, w.Config, showSensitive), | ||
Username: me.UserName, | ||
} | ||
|
||
return &status, nil | ||
} | ||
|
||
func render(ctx context.Context, cmd *cobra.Command, status *authStatus, template string) error { | ||
switch root.OutputType(cmd) { | ||
case flags.OutputText: | ||
return cmdio.RenderWithTemplate(ctx, map[string]any{ | ||
"Status": status, | ||
"ConfigAttributes": config.ConfigAttributes, | ||
}, "", template) | ||
case flags.OutputJSON: | ||
buf, err := json.MarshalIndent(status, "", " ") | ||
if err != nil { | ||
return err | ||
} | ||
cmd.OutOrStdout().Write(buf) | ||
default: | ||
return fmt.Errorf("unknown output type %s", root.OutputType(cmd)) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
type authStatus struct { | ||
Status string `json:"status"` | ||
Error error `json:"error,omitempty"` | ||
Username string `json:"username,omitempty"` | ||
AccountID string `json:"account_id,omitempty"` | ||
Details config.AuthDetails `json:"details"` | ||
} | ||
|
||
func getAuthDetails(cmd *cobra.Command, cfg *config.Config, showSensitive bool) config.AuthDetails { | ||
var opts []config.AuthDetailsOptions | ||
if showSensitive { | ||
opts = append(opts, config.ShowSensitive) | ||
} | ||
details := cfg.GetAuthDetails(opts...) | ||
|
||
for k, v := range details.Configuration { | ||
if k == "profile" && cmd.Flag("profile").Changed { | ||
v.Source = config.Source{Type: config.SourceType("flag"), Name: "--profile"} | ||
} | ||
|
||
if k == "host" && cmd.Flag("host").Changed { | ||
v.Source = config.Source{Type: config.SourceType("flag"), Name: "--host"} | ||
} | ||
} | ||
|
||
// If profile is not set explicitly, default to "default" | ||
if _, ok := details.Configuration["profile"]; !ok { | ||
profile := cfg.Profile | ||
if profile == "" { | ||
profile = "default" | ||
} | ||
details.Configuration["profile"] = &config.AttrConfig{Value: profile, Source: config.Source{Type: config.SourceDynamicConfig}} | ||
} | ||
|
||
// Unset source for databricks_cli_path because it can't be overridden anyway | ||
if v, ok := details.Configuration["databricks_cli_path"]; ok { | ||
v.Source = config.Source{Type: config.SourceDynamicConfig} | ||
} | ||
|
||
return details | ||
} |
Oops, something went wrong.