Skip to content

Commit 802864b

Browse files
LaurenDebruynclaudemivds
authored
fix: add scheme support, centralize BaseURL, fix response body leak (#8)
* fix: add scheme support, centralize BaseURL, fix response body leak Add --scheme flag to auth login so users can connect over HTTP for local dev. Centralize host+scheme defaulting in Profile.BaseURL() and use it everywhere (API client, auth status, login confirmation). Fix response body leak in UpdateMetricMonitoring by decoding the POST response. Remove redundant UpdateMetricMonitoringRequest struct in favor of MetricMonitoringSettings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: replace --scheme flag with scheme-in-host convention Instead of a separate --scheme parameter, users can now specify http:// or https:// as part of the --host value. Defaults updated to https://cloud.soda.io to make the convention visible. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Michael Van de Steene <michael.vandesteene@hotmail.com>
1 parent f4430e7 commit 802864b

6 files changed

Lines changed: 37 additions & 31 deletions

File tree

go/cmd/auth.go

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ var authLoginCmd = &cobra.Command{
3030
if anyFlagSet {
3131
// Non-interactive: flags were explicitly provided
3232
if host == "" {
33-
host = "cloud.soda.io"
33+
host = "https://cloud.soda.io"
3434
}
3535

3636
if apiKeyID == "" || apiKeySecret == "" {
@@ -94,13 +94,13 @@ var authLoginCmd = &cobra.Command{
9494
}
9595

9696
if host == "" {
97-
host = "cloud.soda.io"
97+
host = "https://cloud.soda.io"
9898
}
9999

100100
form := huh.NewForm(huh.NewGroup(
101101
huh.NewInput().
102102
Title("Soda Cloud host").
103-
Description("EU: cloud.soda.io · US: cloud.us.soda.io").
103+
Description("EU: https://cloud.soda.io · US: https://cloud.us.soda.io").
104104
Value(&host),
105105
huh.NewInput().
106106
Title("API key ID").
@@ -116,13 +116,12 @@ var authLoginCmd = &cobra.Command{
116116
}
117117

118118
if host == "" {
119-
host = "cloud.soda.io"
119+
host = "https://cloud.soda.io"
120120
}
121121

122-
fmt.Println(output.Dim.Render(" Testing connection to " + host + "..."))
123-
124122
// Test connection before saving
125123
testProfile := config.Profile{Host: host, APIKeyID: apiKeyID, APIKeySecret: apiKeySecret}
124+
fmt.Println(output.Dim.Render(" Testing connection to " + testProfile.BaseURL() + "..."))
126125
if err := api.New(testProfile).Ping(); err != nil {
127126
return err
128127
}
@@ -183,13 +182,10 @@ var authStatusCmd = &cobra.Command{
183182
return output.Errorf(2, "could not read credentials: %v", err)
184183
}
185184
p, ok := creds[profileName]
186-
host := p.Host
187-
if host == "" {
188-
host = "cloud.soda.io"
189-
}
185+
baseURL := p.BaseURL()
190186

191187
fmt.Printf(" %-20s %s\n", output.Bold.Render("Profile"), profileName)
192-
fmt.Printf(" %-20s %s\n", output.Bold.Render("Host"), host)
188+
fmt.Printf(" %-20s %s\n", output.Bold.Render("Host"), baseURL)
193189

194190
if !ok || p.APIKeyID == "" {
195191
fmt.Printf(" %-20s %s\n", output.Bold.Render("Connection"), output.Dim.Render("not configured — run `sodacli auth login`"))
@@ -218,7 +214,7 @@ var authSwitchCmd = &cobra.Command{
218214
}
219215

220216
func init() {
221-
authLoginCmd.Flags().String("host", "", "Soda Cloud host (default: cloud.soda.io)")
217+
authLoginCmd.Flags().String("host", "", "Soda Cloud host (default: https://cloud.soda.io)")
222218
authLoginCmd.Flags().String("api-key-id", "", "Soda Cloud API key ID")
223219
authLoginCmd.Flags().String("api-key-secret", "", "Soda Cloud API key secret")
224220

go/cmd/monitor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ var monitorConfigCmd = &cobra.Command{
239239

240240
timezone, _ := cmd.Flags().GetString("timezone")
241241

242-
req := api.UpdateMetricMonitoringRequest{}
242+
req := api.MetricMonitoringSettings{}
243243
if enable {
244244
t := true
245245
req.Enabled = &t

go/internal/api/client.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,8 @@ type Client struct {
2121
}
2222

2323
func New(p config.Profile) *Client {
24-
host := p.Host
25-
if host == "" {
26-
host = "cloud.soda.io"
27-
}
2824
return &Client{
29-
baseURL: "https://" + host,
25+
baseURL: p.BaseURL(),
3026
apiKeyID: p.APIKeyID,
3127
apiKeySecret: p.APIKeySecret,
3228
http: &http.Client{Timeout: 30 * time.Second},

go/internal/api/datasets.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,7 @@ type TimePartitionRequest struct {
190190
// ── Metric monitoring (via dataset update) ────────────────────────────────────
191191

192192
// MetricMonitoringSettings is the shape of the `metricMonitoring` field inside
193-
// POST /api/v1/datasets/{id}. It is separate from UpdateMetricMonitoringRequest
194-
// which targets the (unavailable) /metricMonitoring sub-resource.
193+
// POST /api/v1/datasets/{id}.
195194
type MetricMonitoringSettings struct {
196195
Enabled *bool `json:"enabled,omitempty"`
197196
ScanSchedule *ScanSchedule `json:"scanSchedule,omitempty"`

go/internal/api/monitors.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,6 @@ type MetricMonitoringConfig struct {
6161
CustomSqlMetricMonitors []CustomSqlMonitor `json:"customSqlMetricMonitors"`
6262
}
6363

64-
type UpdateMetricMonitoringRequest struct {
65-
Enabled *bool `json:"enabled,omitempty"`
66-
ScanSchedule *ScanSchedule `json:"scanSchedule,omitempty"`
67-
DatasetMetricMonitorsConfiguration []DatasetMetricMonitorCfg `json:"datasetMetricMonitorsConfiguration,omitempty"`
68-
}
69-
7064
func (c *Client) GetMetricMonitoring(datasetID string) (*MetricMonitoringConfig, error) {
7165
resp, err := c.get("/api/v1/datasets/"+datasetID+"/metricMonitoring", nil)
7266
if err != nil {
@@ -79,16 +73,22 @@ func (c *Client) GetMetricMonitoring(datasetID string) (*MetricMonitoringConfig,
7973
return &result, nil
8074
}
8175

82-
func (c *Client) UpdateMetricMonitoring(datasetID string, req UpdateMetricMonitoringRequest) (*MetricMonitoringConfig, error) {
83-
resp, err := c.post("/api/v1/datasets/"+datasetID+"/metricMonitoring", req)
76+
func (c *Client) UpdateMetricMonitoring(datasetID string, req MetricMonitoringSettings) (*MetricMonitoringConfig, error) {
77+
// Use the dataset update endpoint (POST /api/v1/datasets/{id}) with the
78+
// metricMonitoring field — the dedicated /metricMonitoring sub-resource
79+
// is not available on all deployments.
80+
updateReq := UpdateDatasetRequest{MetricMonitoring: &req}
81+
resp, err := c.post("/api/v1/datasets/"+datasetID, updateReq)
8482
if err != nil {
8583
return nil, err
8684
}
87-
var result MetricMonitoringConfig
88-
if err := decode(resp, &result); err != nil {
85+
// Drain and close body — the POST returns a Dataset, not MetricMonitoringConfig.
86+
var discard Dataset
87+
if err := decode(resp, &discard); err != nil {
8988
return nil, err
9089
}
91-
return &result, nil
90+
// Re-fetch the monitoring config in the expected shape.
91+
return c.GetMetricMonitoring(datasetID)
9292
}
9393

9494
// EnableDefaultMonitoring enables all dataset-level metric monitors for a dataset.

go/internal/config/credentials.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"os"
66
"path/filepath"
7+
"strings"
78

89
"gopkg.in/yaml.v3"
910
)
@@ -14,6 +15,20 @@ type Profile struct {
1415
APIKeySecret string `yaml:"api_key_secret"`
1516
}
1617

18+
// BaseURL returns the full base URL for the profile (e.g. "https://cloud.soda.io").
19+
// If Host already contains a scheme (http:// or https://), it is used as-is.
20+
// Otherwise, https:// is prepended.
21+
func (p Profile) BaseURL() string {
22+
host := p.Host
23+
if host == "" {
24+
host = "https://cloud.soda.io"
25+
}
26+
if strings.HasPrefix(host, "http://") || strings.HasPrefix(host, "https://") {
27+
return strings.TrimRight(host, "/")
28+
}
29+
return "https://" + strings.TrimRight(host, "/")
30+
}
31+
1732
type Credentials map[string]Profile
1833

1934
func CredentialsPath() (string, error) {

0 commit comments

Comments
 (0)