Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 8 additions & 12 deletions go/cmd/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ var authLoginCmd = &cobra.Command{
if anyFlagSet {
// Non-interactive: flags were explicitly provided
if host == "" {
host = "cloud.soda.io"
host = "https://cloud.soda.io"
}

if apiKeyID == "" || apiKeySecret == "" {
Expand Down Expand Up @@ -94,13 +94,13 @@ var authLoginCmd = &cobra.Command{
}

if host == "" {
host = "cloud.soda.io"
host = "https://cloud.soda.io"
}

form := huh.NewForm(huh.NewGroup(
huh.NewInput().
Title("Soda Cloud host").
Description("EU: cloud.soda.io · US: cloud.us.soda.io").
Description("EU: https://cloud.soda.io · US: https://cloud.us.soda.io").
Value(&host),
huh.NewInput().
Title("API key ID").
Expand All @@ -116,13 +116,12 @@ var authLoginCmd = &cobra.Command{
}

if host == "" {
host = "cloud.soda.io"
host = "https://cloud.soda.io"
}

fmt.Println(output.Dim.Render(" Testing connection to " + host + "..."))

// Test connection before saving
testProfile := config.Profile{Host: host, APIKeyID: apiKeyID, APIKeySecret: apiKeySecret}
fmt.Println(output.Dim.Render(" Testing connection to " + testProfile.BaseURL() + "..."))
if err := api.New(testProfile).Ping(); err != nil {
return err
}
Expand Down Expand Up @@ -183,13 +182,10 @@ var authStatusCmd = &cobra.Command{
return output.Errorf(2, "could not read credentials: %v", err)
}
p, ok := creds[profileName]
host := p.Host
if host == "" {
host = "cloud.soda.io"
}
baseURL := p.BaseURL()

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

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

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

Expand Down
10 changes: 9 additions & 1 deletion go/cmd/monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,18 @@ var monitorConfigCmd = &cobra.Command{

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

req := api.UpdateMetricMonitoringRequest{}
req := api.MetricMonitoringSettings{}
if enable {
t := true
req.Enabled = &t
monitors := make([]api.DatasetMetricMonitorCfg, len(api.DefaultDatasetMonitorTypes))
for i, mt := range api.DefaultDatasetMonitorTypes {
monitors[i] = api.DatasetMetricMonitorCfg{
MetricType: mt,
Configuration: api.DatasetMonitorConfig{IsEnabled: true},
}
}
req.DatasetMetricMonitorsConfiguration = monitors
} else if disable {
f := false
req.Enabled = &f
Expand Down
6 changes: 1 addition & 5 deletions go/internal/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,8 @@ type Client struct {
}

func New(p config.Profile) *Client {
host := p.Host
if host == "" {
host = "cloud.soda.io"
}
return &Client{
baseURL: "https://" + host,
baseURL: p.BaseURL(),
apiKeyID: p.APIKeyID,
apiKeySecret: p.APIKeySecret,
http: &http.Client{Timeout: 30 * time.Second},
Expand Down
3 changes: 1 addition & 2 deletions go/internal/api/datasets.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,7 @@ type TimePartitionRequest struct {
// ── Metric monitoring (via dataset update) ────────────────────────────────────

// MetricMonitoringSettings is the shape of the `metricMonitoring` field inside
// POST /api/v1/datasets/{id}. It is separate from UpdateMetricMonitoringRequest
// which targets the (unavailable) /metricMonitoring sub-resource.
// POST /api/v1/datasets/{id}.
type MetricMonitoringSettings struct {
Enabled *bool `json:"enabled,omitempty"`
ScanSchedule *ScanSchedule `json:"scanSchedule,omitempty"`
Expand Down
30 changes: 15 additions & 15 deletions go/internal/api/monitors.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,6 @@ type MetricMonitoringConfig struct {
CustomSqlMetricMonitors []CustomSqlMonitor `json:"customSqlMetricMonitors"`
}

type UpdateMetricMonitoringRequest struct {
Enabled *bool `json:"enabled,omitempty"`
ScanSchedule *ScanSchedule `json:"scanSchedule,omitempty"`
DatasetMetricMonitorsConfiguration []DatasetMetricMonitorCfg `json:"datasetMetricMonitorsConfiguration,omitempty"`
}

func (c *Client) GetMetricMonitoring(datasetID string) (*MetricMonitoringConfig, error) {
resp, err := c.get("/api/v1/datasets/"+datasetID+"/metricMonitoring", nil)
if err != nil {
Expand All @@ -79,22 +73,28 @@ func (c *Client) GetMetricMonitoring(datasetID string) (*MetricMonitoringConfig,
return &result, nil
}

func (c *Client) UpdateMetricMonitoring(datasetID string, req UpdateMetricMonitoringRequest) (*MetricMonitoringConfig, error) {
resp, err := c.post("/api/v1/datasets/"+datasetID+"/metricMonitoring", req)
func (c *Client) UpdateMetricMonitoring(datasetID string, req MetricMonitoringSettings) (*MetricMonitoringConfig, error) {
// Use the dataset update endpoint (POST /api/v1/datasets/{id}) with the
// metricMonitoring field — the dedicated /metricMonitoring sub-resource
// is not available on all deployments.
updateReq := UpdateDatasetRequest{MetricMonitoring: &req}
resp, err := c.post("/api/v1/datasets/"+datasetID, updateReq)
if err != nil {
return nil, err
}
var result MetricMonitoringConfig
if err := decode(resp, &result); err != nil {
// Drain and close body — the POST returns a Dataset, not MetricMonitoringConfig.
var discard Dataset
if err := decode(resp, &discard); err != nil {
return nil, err
}
return &result, nil
// Re-fetch the monitoring config in the expected shape.
return c.GetMetricMonitoring(datasetID)
}

// EnableDefaultMonitoring enables all dataset-level metric monitors for a dataset.
// It uses POST /api/v1/datasets/{id} (the generic dataset update endpoint) because
// defaultDatasetMonitorTypes are the known API metricType values for dataset-level monitors.
var defaultDatasetMonitorTypes = []string{
// DefaultDatasetMonitorTypes are the known API metricType values for dataset-level monitors.
var DefaultDatasetMonitorTypes = []string{
"rowCount", "freshness", "schema", "rowsInserted", "totalRowCountChange", "timeliness",
}

Expand All @@ -119,8 +119,8 @@ func (c *Client) EnableDatasetDefaults(datasetID string, monitoring, profiling b

if monitoring {
t := true
monitors := make([]DatasetMetricMonitorCfg, len(defaultDatasetMonitorTypes))
for i, mt := range defaultDatasetMonitorTypes {
monitors := make([]DatasetMetricMonitorCfg, len(DefaultDatasetMonitorTypes))
for i, mt := range DefaultDatasetMonitorTypes {
monitors[i] = DatasetMetricMonitorCfg{
MetricType: mt,
Configuration: DatasetMonitorConfig{IsEnabled: true},
Expand Down
15 changes: 15 additions & 0 deletions go/internal/config/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"

"gopkg.in/yaml.v3"
)
Expand All @@ -14,6 +15,20 @@ type Profile struct {
APIKeySecret string `yaml:"api_key_secret"`
}

// BaseURL returns the full base URL for the profile (e.g. "https://cloud.soda.io").
// If Host already contains a scheme (http:// or https://), it is used as-is.
// Otherwise, https:// is prepended.
func (p Profile) BaseURL() string {
host := p.Host
if host == "" {
host = "https://cloud.soda.io"
}
if strings.HasPrefix(host, "http://") || strings.HasPrefix(host, "https://") {
return strings.TrimRight(host, "/")
}
return "https://" + strings.TrimRight(host, "/")
}

type Credentials map[string]Profile

func CredentialsPath() (string, error) {
Expand Down