2024-04-03 08:14:04 +00:00
|
|
|
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"
|
|
|
|
)
|
|
|
|
|
2024-04-03 15:47:45 +00:00
|
|
|
var authTemplate = `{{"Host:" | bold}} {{.Status.Details.Host}}
|
|
|
|
{{- if .Status.AccountID}}
|
|
|
|
{{"Account ID:" | bold}} {{.Status.AccountID}}
|
2024-04-03 08:14:04 +00:00
|
|
|
{{- end}}
|
2024-04-03 15:47:45 +00:00
|
|
|
{{- if .Status.Username}}
|
|
|
|
{{"User:" | bold}} {{.Status.Username}}
|
2024-04-03 08:14:04 +00:00
|
|
|
{{- end}}
|
2024-04-03 15:47:45 +00:00
|
|
|
{{"Authenticated with:" | bold}} {{.Status.Details.AuthType}}
|
2024-04-03 08:14:04 +00:00
|
|
|
-----
|
|
|
|
` + 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
|
|
|
|
}
|