-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathclient.go
More file actions
196 lines (172 loc) · 6.19 KB
/
Copy pathclient.go
File metadata and controls
196 lines (172 loc) · 6.19 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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
// Copyright 2026 Columnar Technologies Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dbc
import (
"fmt"
"net/http"
"net/url"
"os"
"path/filepath"
"runtime"
"sync"
"github.com/columnar-tech/dbc/auth"
"github.com/columnar-tech/dbc/internal"
"github.com/google/uuid"
)
type clientConfig struct {
httpClient *http.Client
// registries holds the current registry list as options are applied.
// WithRegistries overwrites this; WithGlobalConfig/WithProjectRegistries
// consult it as the "defaults" slice in the merge, which means callers
// who pass WithRegistries retain full control over that baseline.
registries []Registry
userAgent string
baseURL string
credentialResolver func(*url.URL) (*auth.Credential, error)
globalConfig *GlobalConfig
projectRegistries []RegistryEntry
projectReplaceDefaults *bool
// explicitRegistries tracks whether the caller passed WithRegistries.
// When true, registry-config options (global/project) are ignored so
// the caller's explicit registry set is used as-is.
explicitRegistries bool
}
type Option func(*clientConfig)
// Client is a dbc client for searching registries and managing ADBC drivers.
type Client struct {
httpClient *http.Client
registries []Registry
userAgent string
mid string
uid uuid.UUID
setupOnce sync.Once
credentialResolver func(*url.URL) (*auth.Credential, error)
}
// NewClient creates a new driver registry client with the given options.
func NewClient(opts ...Option) (*Client, error) {
cfg := &clientConfig{
registries: []Registry{
{BaseURL: mustParseURL("https://dbc-cdn.columnar.tech")},
{BaseURL: mustParseURL("https://" + auth.DefaultOauthURI())},
},
userAgent: fmt.Sprintf("dbc-cli/%s (%s; %s)", Version, runtime.GOOS, runtime.GOARCH),
}
for _, opt := range opts {
opt(cfg)
}
httpClient := cfg.httpClient
if httpClient == nil {
httpClient = &http.Client{
Transport: &uaRoundTripper{
RoundTripper: http.DefaultTransport,
userAgent: cfg.userAgent,
},
}
}
if cfg.baseURL != "" {
cfg.registries = []Registry{{BaseURL: mustParseURL(cfg.baseURL)}}
} else if cfg.explicitRegistries {
// WithRegistries was passed — use the caller's list verbatim and
// do not merge with global/project configuration.
} else if cfg.globalConfig != nil || cfg.projectRegistries != nil || cfg.projectReplaceDefaults != nil {
for _, e := range cfg.projectRegistries {
if err := validateRegistryEntry(e); err != nil {
return nil, err
}
}
var globalRegs []RegistryEntry
var globalReplace bool
if cfg.globalConfig != nil {
for _, e := range cfg.globalConfig.Registries {
if err := validateRegistryEntry(e); err != nil {
return nil, err
}
}
globalRegs = cfg.globalConfig.Registries
globalReplace = cfg.globalConfig.ReplaceDefaults
}
merged := mergeRegistries(cfg.projectRegistries, cfg.projectReplaceDefaults, globalRegs, globalReplace, cfg.registries)
if len(merged) == 0 {
return nil, fmt.Errorf("registry configuration produced an empty registry list; replace_defaults requires at least one [[registries]] entry")
}
cfg.registries = merged
}
credResolver := cfg.credentialResolver
if credResolver == nil {
credResolver = auth.GetCredentials
}
return &Client{
httpClient: httpClient,
registries: cfg.registries,
userAgent: cfg.userAgent,
credentialResolver: credResolver,
}, nil
}
func (c *Client) setup() {
c.setupOnce.Do(func() {
c.mid, _ = telemetryMachineID()
userdir, err := internal.GetUserConfigPath()
if err != nil {
c.uid = uuid.New()
return
}
fp := filepath.Join(userdir, "uid.uuid")
data, err := os.ReadFile(fp)
if err == nil {
if err = c.uid.UnmarshalBinary(data); err == nil {
return
}
}
c.uid = uuid.New()
if err = os.MkdirAll(filepath.Dir(fp), 0o700); err == nil {
if data, err = c.uid.MarshalBinary(); err == nil {
os.WriteFile(fp, data, 0o600)
}
}
})
}
func (c *Client) HTTPClient() *http.Client { return c.httpClient }
// Registries returns the list of driver registries configured for this client.
func (c *Client) Registries() []Registry { return c.registries }
// UserAgent returns the user agent string used by this client.
func (c *Client) UserAgent() string { return c.userAgent }
// WithHTTPClient sets the HTTP client to use for requests.
func WithHTTPClient(hc *http.Client) Option {
return func(cfg *clientConfig) { cfg.httpClient = hc }
}
// WithCredentialResolver sets the resolver used to look up credentials for a
// registry URL, overriding the default on-disk resolver. Useful where there is
// no credential file (e.g. WASM hosts injecting a token).
func WithCredentialResolver(resolver func(*url.URL) (*auth.Credential, error)) Option {
return func(cfg *clientConfig) { cfg.credentialResolver = resolver }
}
// WithRegistries sets the driver registries to use. When passed, this takes
// precedence over any WithGlobalConfig / WithProjectRegistries options: the
// caller's explicit list is used as-is, not merged with configuration files.
func WithRegistries(r []Registry) Option {
return func(cfg *clientConfig) {
cfg.registries = append([]Registry(nil), r...)
cfg.explicitRegistries = true
}
}
// WithBaseURL sets the base URL for the driver registry.
func WithBaseURL(u string) Option {
return func(cfg *clientConfig) { cfg.baseURL = u }
}
// WithUserAgent sets the user agent string for requests. This only takes
// effect when no custom HTTP client is provided via WithHTTPClient; if a
// custom client is supplied its transport is used as-is.
func WithUserAgent(ua string) Option {
return func(cfg *clientConfig) { cfg.userAgent = ua }
}