-
Notifications
You must be signed in to change notification settings - Fork 161
Expand file tree
/
Copy pathprofiles.go
More file actions
181 lines (165 loc) · 5.21 KB
/
profiles.go
File metadata and controls
181 lines (165 loc) · 5.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package auth
import (
"context"
"errors"
"fmt"
"io/fs"
"strings"
"sync"
"time"
"github.com/databricks/cli/libs/auth"
"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/databrickscfg"
"github.com/databricks/cli/libs/databrickscfg/profile"
"github.com/databricks/cli/libs/env"
"github.com/databricks/cli/libs/log"
"github.com/databricks/databricks-sdk-go"
"github.com/databricks/databricks-sdk-go/config"
"github.com/spf13/cobra"
"gopkg.in/ini.v1"
)
type profileMetadata struct {
Name string `json:"name"`
Host string `json:"host,omitempty"`
AccountID string `json:"account_id,omitempty"`
WorkspaceID string `json:"workspace_id,omitempty"`
Cloud string `json:"cloud"`
AuthType string `json:"auth_type"`
Valid bool `json:"valid"`
Default bool `json:"default,omitempty"`
}
func (c *profileMetadata) IsEmpty() bool {
return c.Host == "" && c.AccountID == ""
}
func (c *profileMetadata) Load(ctx context.Context, configFilePath string, skipValidate bool) {
cfg := &config.Config{
Loaders: []config.Loader{config.ConfigFile},
ConfigFile: configFilePath,
Profile: c.Name,
DatabricksCliPath: env.Get(ctx, "DATABRICKS_CLI_PATH"),
}
_ = cfg.EnsureResolved()
if cfg.IsAws() {
c.Cloud = "aws"
} else if cfg.IsAzure() {
c.Cloud = "azure"
} else if cfg.IsGcp() {
c.Cloud = "gcp"
}
if skipValidate {
c.Host = cfg.CanonicalHostName()
c.AuthType = cfg.AuthType
return
}
// ConfigType() classifies based on the host URL prefix (accounts.* →
// AccountConfig, everything else → WorkspaceConfig). SPOG hosts don't
// match the accounts.* prefix so they're misclassified as WorkspaceConfig.
// Use the resolved DiscoveryURL (from .well-known/databricks-config) to
// detect SPOG hosts with account-scoped OIDC, matching the routing logic
// in auth.AuthArguments.ToOAuthArgument().
configType := cfg.ConfigType()
hasWorkspace := cfg.WorkspaceID != "" && cfg.WorkspaceID != auth.WorkspaceIDNone
isAccountScopedOIDC := cfg.DiscoveryURL != "" && strings.Contains(cfg.DiscoveryURL, "/oidc/accounts/")
if configType != config.AccountConfig && cfg.AccountID != "" && isAccountScopedOIDC {
if hasWorkspace {
configType = config.WorkspaceConfig
} else {
configType = config.AccountConfig
}
}
// Legacy backward compat: SDK v0.126.0 removed the UnifiedHost case from
// ConfigType(), so profiles with Experimental_IsUnifiedHost now get
// InvalidConfig instead of being routed to account/workspace validation.
// When .well-known is also unreachable (DiscoveryURL empty), the override
// above can't help. Fall back to workspace_id to choose the validation
// strategy, matching the IsUnifiedHost fallback in ToOAuthArgument().
if configType == config.InvalidConfig && cfg.Experimental_IsUnifiedHost && cfg.AccountID != "" {
if hasWorkspace {
configType = config.WorkspaceConfig
} else {
configType = config.AccountConfig
}
}
switch configType {
case config.AccountConfig:
a, err := databricks.NewAccountClient((*databricks.Config)(cfg))
if err != nil {
return
}
_, err = a.Workspaces.List(ctx)
c.Host = cfg.Host
c.AuthType = cfg.AuthType
if err != nil {
return
}
c.Valid = true
case config.WorkspaceConfig:
w, err := databricks.NewWorkspaceClient((*databricks.Config)(cfg))
if err != nil {
return
}
_, err = w.CurrentUser.Me(ctx)
c.Host = cfg.Host
c.AuthType = cfg.AuthType
if err != nil {
return
}
c.Valid = true
case config.InvalidConfig:
// Invalid configuration, skip validation
return
}
}
func newProfilesCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "profiles",
Short: "Lists profiles from ~/.databrickscfg",
Annotations: map[string]string{
"template": cmdio.Heredoc(`
{{header "Name"}} {{header "Host"}} {{header "Valid"}}
{{range .Profiles}}{{.Name | green}}{{if .Default}} (Default){{end}} {{.Host|cyan}} {{bool .Valid}}
{{end}}`),
},
}
var skipValidate bool
cmd.Flags().BoolVar(&skipValidate, "skip-validate", false, "Whether to skip validating the profiles")
cmd.RunE = func(cmd *cobra.Command, args []string) error {
var profiles []*profileMetadata
iniFile, err := profile.DefaultProfiler.Get(cmd.Context())
if errors.Is(err, fs.ErrNotExist) {
// return empty list for non-configured machines
iniFile = &config.File{
File: &ini.File{},
}
} else if err != nil {
return fmt.Errorf("cannot parse config file: %w", err)
}
defaultProfile := databrickscfg.GetConfiguredDefaultProfileFrom(iniFile)
var wg sync.WaitGroup
for _, v := range iniFile.Sections() {
hash := v.KeysHash()
profile := &profileMetadata{
Name: v.Name(),
Host: hash["host"],
AccountID: hash["account_id"],
WorkspaceID: hash["workspace_id"],
Default: v.Name() == defaultProfile,
}
if profile.IsEmpty() {
continue
}
wg.Go(func() {
ctx := cmd.Context()
t := time.Now()
profile.Load(ctx, iniFile.Path(), skipValidate)
log.Debugf(ctx, "Profile %q took %s to load", profile.Name, time.Since(t))
})
profiles = append(profiles, profile)
}
wg.Wait()
return cmdio.Render(cmd.Context(), struct {
Profiles []*profileMetadata `json:"profiles"`
}{profiles})
}
return cmd
}