diff --git a/README.md b/README.md index a1350ba..0d04e05 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ If you are still using the legacy [Access scopes][access-scopes], the `https://w | Flag | Required | Default | Description | | ----------------------------------- | -------- |---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `google.project-id` | No | GCloud SDK auto-discovery | Repeatable flag of Google Project IDs | +| `google.project-ids` | No | GCloud SDK auto-discovery | Repeatable flag of Google Project IDs | | `google.projects.filter` | No | | GCloud projects filter expression. See more [here](https://cloud.google.com/sdk/gcloud/reference/projects/list). | | `monitoring.metrics-ingest-delay` | No | | Offsets metric collection by a delay appropriate for each metric type, e.g. because bigquery metrics are slow to appear | | `monitoring.drop-delegated-projects` | No | No | Drop metrics from attached projects and fetch `project_id` only. | @@ -143,7 +143,7 @@ If we want to get all `CPU` (`compute.googleapis.com/instance/cpu`) and `Disk` ( ``` stackdriver_exporter \ - --google.project-id=my-test-project \ + --google.project-ids=my-test-project \ --monitoring.metrics-prefixes "compute.googleapis.com/instance/cpu" --monitoring.metrics-prefixes "compute.googleapis.com/instance/disk" ``` @@ -170,7 +170,7 @@ The final query sent to the metrics API already includes filters for project and Full example ``` stackdriver_exporter \ - --google.project-id=my-test-project \ + --google.project-ids=my-test-project \ --monitoring.metrics-prefixes='pubsub.googleapis.com/subscription' \ --monitoring.metrics-prefixes='compute.googleapis.com/instance/cpu' \ --monitoring.filters='pubsub.googleapis.com/subscription:resource.labels.subscription_id=monitoring.regex.full_match("us-west4.*my-team-subs.*")' \ diff --git a/stackdriver_exporter.go b/stackdriver_exporter.go index e63247d..7060144 100644 --- a/stackdriver_exporter.go +++ b/stackdriver_exporter.go @@ -57,8 +57,12 @@ var ( "web.stackdriver-telemetry-path", "Path under which to expose Stackdriver metrics.", ).Default("/metrics").String() + projectID = kingpin.Flag( + "google.project-id", "DEPRECATED - Comma seperated list of Google Project IDs. Use 'google.project-ids' instead.", + ).String() + projectIDs = kingpin.Flag( - "google.project-id", "Google Project IDs. Repeat this flag for multiple projects.", + "google.project-ids", "Repeatable flag of Google Project IDs", ).Strings() projectsFilter = kingpin.Flag( @@ -86,10 +90,13 @@ var ( ).Default("503").Ints() // Monitoring collector flags + monitoringMetricsTypePrefixes = kingpin.Flag( + "monitoring.metrics-type-prefixes", "DEPRECATED - Comma separated Google Stackdriver Monitoring Metric Type prefixes. Use 'monitoring.metrics-prefixes' instead.", + ).String() monitoringMetricsPrefixes = kingpin.Flag( "monitoring.metrics-prefixes", "Google Stackdriver Monitoring Metric Type prefixes. Repeat this flag to scrape multiple prefixes.", - ).Required().Strings() + ).Strings() monitoringMetricsInterval = kingpin.Flag( "monitoring.metrics-interval", "Interval to request the Google Stackdriver Monitoring Metrics for. Only the most recent data point is used.", @@ -137,7 +144,7 @@ func init() { prometheus.MustRegister(versioncollector.NewCollector("stackdriver_exporter")) } -func getDefaultGCPProject(ctx context.Context) (*[]string, error) { +func getDefaultGCPProject(ctx context.Context) (*string, error) { credentials, err := google.FindDefaultCredentials(ctx, compute.ComputeScope) if err != nil { return nil, err @@ -145,7 +152,7 @@ func getDefaultGCPProject(ctx context.Context) (*[]string, error) { if credentials.ProjectID == "" { return nil, fmt.Errorf("unable to identify the gcloud project. Got empty string") } - return &[]string{credentials.ProjectID}, nil + return &credentials.ProjectID, nil } func createMonitoringService(ctx context.Context) (*monitoring.Service, error) { @@ -269,16 +276,29 @@ func main() { kingpin.Parse() logger := promlog.New(promlogConfig) + if *projectID != "" { + level.Warn(logger).Log("msg", "The google.project-id flag is deprecated and will be replaced by google.project-ids.") + } + if *monitoringMetricsTypePrefixes != "" { + level.Warn(logger).Log("msg", "The monitoring.metrics-type-prefixes flag is deprecated and will be replaced by monitoring.metrics-prefix.") + } + if *monitoringMetricsTypePrefixes == "" && len(*monitoringMetricsPrefixes) == 0 { + level.Error(logger).Log("msg", "At least one GCP monitoring prefix is required.") + os.Exit(1) + } ctx := context.Background() - if len(*projectIDs) == 0 && *projectsFilter == "" { - level.Info(logger).Log("msg", "Neither projectID nor projectsFilter was provided. Trying to discover it") + var discoveredProjectIDs []string + + if len(*projectIDs) == 0 && *projectID == "" && *projectsFilter == "" { + level.Info(logger).Log("msg", "Neither projectIDs nor projectsFilter was provided. Trying to discover it") var err error - projectIDs, err = getDefaultGCPProject(ctx) + defaultProject, err := getDefaultGCPProject(ctx) if err != nil { - level.Error(logger).Log("msg", "no explicit projectID and error trying to discover default GCloud project", "err", err) + level.Error(logger).Log("msg", "no explicit projectIDs and error trying to discover default GCloud project", "err", err) os.Exit(1) } + discoveredProjectIDs = append(discoveredProjectIDs, *defaultProject) } monitoringService, err := createMonitoringService(ctx) @@ -287,42 +307,54 @@ func main() { os.Exit(1) } - var discoveredProjectIDs []string - if *projectsFilter != "" { - discoveredProjectIDs, err = utils.GetProjectIDsFromFilter(ctx, *projectsFilter) + projectIDsFromFilter, err := utils.GetProjectIDsFromFilter(ctx, *projectsFilter) if err != nil { level.Error(logger).Log("msg", "failed to get project IDs from filter", "err", err) os.Exit(1) } + discoveredProjectIDs = append(discoveredProjectIDs, projectIDsFromFilter...) } if len(*projectIDs) > 0 { discoveredProjectIDs = append(discoveredProjectIDs, *projectIDs...) } + if *projectID != "" { + discoveredProjectIDs = append(discoveredProjectIDs, strings.Split(*projectID, ",")...) + } + + var metricsPrefixes []string + if len(*monitoringMetricsPrefixes) > 0 { + metricsPrefixes = append(metricsPrefixes, *monitoringMetricsPrefixes...) + } + if *monitoringMetricsTypePrefixes != "" { + metricsPrefixes = append(metricsPrefixes, strings.Split(*monitoringMetricsTypePrefixes, ",")...) + } level.Info(logger).Log( "msg", "Starting stackdriver_exporter", "version", version.Info(), "build_context", version.BuildContext(), - "projects", *projectIDs, - "metric_prefixes", *monitoringMetricsPrefixes, + "metric_prefixes", fmt.Sprintf("%v", metricsPrefixes), "extra_filters", strings.Join(*monitoringMetricsExtraFilter, ","), "projectIDs", fmt.Sprintf("%v", discoveredProjectIDs), "projectsFilter", *projectsFilter, ) - parsedMetricsPrefixes := parseMetricTypePrefixes(*monitoringMetricsPrefixes) + parsedMetricsPrefixes := parseMetricTypePrefixes(metricsPrefixes) metricExtraFilters := parseMetricExtraFilters() + // drop duplicate projects + slices.Sort(discoveredProjectIDs) + uniqueProjectIds := slices.Compact(discoveredProjectIDs) if *metricsPath == *stackdriverMetricsPath { handler := newHandler( - discoveredProjectIDs, parsedMetricsPrefixes, metricExtraFilters, monitoringService, logger, prometheus.DefaultGatherer) + uniqueProjectIds, parsedMetricsPrefixes, metricExtraFilters, monitoringService, logger, prometheus.DefaultGatherer) http.Handle(*metricsPath, promhttp.InstrumentMetricHandler(prometheus.DefaultRegisterer, handler)) } else { level.Info(logger).Log("msg", "Serving Stackdriver metrics at separate path", "path", *stackdriverMetricsPath) handler := newHandler( - discoveredProjectIDs, parsedMetricsPrefixes, metricExtraFilters, monitoringService, logger, nil) + uniqueProjectIds, parsedMetricsPrefixes, metricExtraFilters, monitoringService, logger, nil) http.Handle(*stackdriverMetricsPath, promhttp.InstrumentMetricHandler(prometheus.DefaultRegisterer, handler)) http.Handle(*metricsPath, promhttp.Handler()) }