Skip to content

Commit

Permalink
Support Google Cloud and other authentication strategies (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
hgiasac authored Sep 25, 2024
1 parent 1706e0e commit bd5b53a
Show file tree
Hide file tree
Showing 18 changed files with 608 additions and 212 deletions.
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,7 @@ generate-api-types:

.PHONY: generate-test-config
generate-test-config:
CONNECTION_URL=http://localhost:9090 go run ./configuration update -d ./tests/configuration --log-level debug
CONNECTION_URL=http://localhost:9090 \
PROMETHEUS_USERNAME=admin \
PROMETHEUS_PASSWORD=test \
go run ./configuration update -d ./tests/configuration --log-level debug
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ The connector can detect if you want to request an instant query or range query

The range query mode is default If none of the timestamp operators is set.

The `timestamp` and `value` fields are the result of the instant query. If the request is a range query, `timestamp` and `value` are picked the last item of the `values` series.

#### Common arguments

- `step`: the query resolution step width in duration format or float number of seconds. The step should be explicitly set for range queries. Even though the connector can estimate the approximate step width the result may be empty due to too far interval.
Expand Down Expand Up @@ -188,6 +190,62 @@ Execute a raw PromQL query directly. This API should be used by the admin only.
| prometheus_targets | [/api/v1/targets](https://prometheus.io/docs/prometheus/latest/querying/api/#targets) |
| prometheus_targets_metadata | [/api/v1/targets/metadata](https://prometheus.io/docs/prometheus/latest/querying/api/#querying-target-metadata) |

## Configuration

### Authentication

#### Basic Authentication

```yaml
connection_settings:
authentication:
basic:
username:
env: PROMETHEUS_USERNAME
password:
env: PROMETHEUS_PASSWORD
```

#### HTTP Authorization

```yaml
connection_settings:
authentication:
authorization:
type:
value: Bearer
credentials:
env: PROMETHEUS_AUTH_TOKEN
```

#### OAuth2

```yaml
connection_settings:
authentication:
oauth2:
token_url:
value: http://example.com/oauth2/token
client_id:
env: PROMETHEUS_OAUTH2_CLIENT_ID
client_secret:
env: PROMETHEUS_OAUTH2_CLIENT_SECRET
```

#### Google Cloud

The configuration accepts either the Google application credentials JSON string or file path. If the object is empty the client automatically loads the credential file from the `GOOGLE_APPLICATION_CREDENTIALS` environment variable.

```yaml
connection_settings:
authentication:
google:
# credentials:
# env: GOOGLE_APPLICATION_CREDENTIALS_JSON
# credentials_file:
# env: GOOGLE_APPLICATION_CREDENTIALS
```

## Development

### Get started
Expand Down
3 changes: 3 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ services:
- local.hasura.dev=host-gateway
environment:
CONNECTION_URL: http://prometheus:9090
PROMETHEUS_USERNAME: admin
PROMETHEUS_PASSWORD: test
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: http://jaeger:4317
OTEL_METRICS_EXPORTER: prometheus
HASURA_LOG_LEVEL: debug
Expand All @@ -27,6 +29,7 @@ services:
- "--web.console.libraries=/usr/share/prometheus/console_libraries"
- "--web.console.templates=/usr/share/prometheus/consoles"
- "--web.enable-lifecycle"
- "--web.config.file=/etc/prometheus/web.yml"
- "--enable-feature=promql-experimental-functions"
ports:
- 9090:9090
Expand Down
12 changes: 7 additions & 5 deletions configuration/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package main
import (
"context"
"fmt"
"log"
"log/slog"
"os"
"os/signal"
Expand Down Expand Up @@ -31,19 +30,22 @@ func main() {
defer stop()

cmd := kong.Parse(&cli, kong.UsageOnError())
_, err := initLogger(cli.LogLevel)
logger, err := initLogger(cli.LogLevel)
if err != nil {
log.Fatalf("failed to initialize: %s", err)
logger.Error(fmt.Sprintf("failed to initialize: %s", err))
os.Exit(1)
}
switch cmd.Command() {
case "update":
if err := introspectSchema(ctx, &cli.Update); err != nil {
log.Fatalf("failed to update configuration: %s", err)
logger.Error(fmt.Sprintf("failed to update configuration: %s", err))
os.Exit(1)
}
case "version":
_, _ = fmt.Print(version.BuildVersion)
default:
log.Fatalf("unknown command <%s>", cmd.Command())
logger.Error(fmt.Sprintf("unknown command <%s>", cmd.Command()))
os.Exit(1)
}
}

Expand Down
19 changes: 9 additions & 10 deletions configuration/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/hasura/ndc-prometheus/connector/client"
"github.com/hasura/ndc-prometheus/connector/metadata"
"github.com/hasura/ndc-prometheus/connector/types"
"github.com/prometheus/common/model"
"go.opentelemetry.io/otel"
"gopkg.in/yaml.v3"
Expand Down Expand Up @@ -41,12 +42,7 @@ func introspectSchema(ctx context.Context, args *UpdateArguments) error {
originalConfig = &defaultConfiguration
}

endpoint, err := originalConfig.ConnectionSettings.URL.Get()
if err != nil {
return err
}

apiClient, err := client.NewClient(endpoint, originalConfig.ConnectionSettings.ToHTTPClientConfig(), clientTracer, nil)
apiClient, err := client.NewClient(ctx, originalConfig.ConnectionSettings, clientTracer, nil)
if err != nil {
return err
}
Expand Down Expand Up @@ -76,19 +72,22 @@ func introspectSchema(ctx context.Context, args *UpdateArguments) error {
}

func (uc *updateCommand) updateMetricsMetadata(ctx context.Context) error {
metricsInfo, err := uc.Client.API.Metadata(ctx, "", "")
metricsInfo, err := uc.Client.Metadata(ctx, "", "10000000")
if err != nil {
return err
}

newMetrics := map[string]metadata.MetricInfo{}
for key, info := range metricsInfo {
if len(info) == 0 {
continue
}
if (len(uc.Include) > 0 && !validateRegularExpressions(uc.Include, key)) ||
validateRegularExpressions(uc.Exclude, key) ||
len(info) == 0 {
continue
}
slog.Debug(key, slog.String("type", "metrics"))
slog.Info(key, slog.String("type", string(info[0].Type)))
labels, err := uc.getAllLabelsOfMetric(ctx, key)
if err != nil {
return fmt.Errorf("error when fetching labels for metric `%s`: %s", key, err)
Expand Down Expand Up @@ -170,8 +169,8 @@ func (uc *updateCommand) writeConfigFile() error {
}

var defaultConfiguration = metadata.Configuration{
ConnectionSettings: metadata.ClientSettings{
URL: metadata.NewEnvironmentVariable("CONNECTION_URL"),
ConnectionSettings: client.ClientSettings{
URL: types.NewEnvironmentVariable("CONNECTION_URL"),
},
Generator: metadata.GeneratorSettings{
Metrics: metadata.MetricsGeneratorSettings{
Expand Down
2 changes: 1 addition & 1 deletion connector-definition/configuration.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/hasura/ndc-prometheus/main/jsonschema/configuration.json
connection_settings:
url:
variable: CONNECTION_URL
env: CONNECTION_URL
generator:
metrics:
enabled: true
Expand Down
34 changes: 30 additions & 4 deletions connector/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ package client

import (
"context"
"errors"
"fmt"
"log/slog"
"net/http"
"strings"
"time"

"github.com/hasura/ndc-sdk-go/utils"
"github.com/prometheus/client_golang/api"
v1 "github.com/prometheus/client_golang/api/prometheus/v1"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/codes"
Expand All @@ -26,8 +29,17 @@ type Client struct {
}

// NewClient creates a new Prometheus client instance
func NewClient(endpoint string, clientConfig config.HTTPClientConfig, tracer trace.Tracer, timeout *model.Duration) (*Client, error) {
httpClient, err := config.NewClientFromConfig(clientConfig, "ndc-prometheus")
func NewClient(ctx context.Context, cfg ClientSettings, tracer trace.Tracer, timeout *model.Duration) (*Client, error) {

endpoint, err := cfg.URL.Get()
if err != nil {
return nil, fmt.Errorf("url: %s", err)
}
if endpoint == "" {
return nil, errors.New("the endpoint setting is empty")
}

httpClient, err := cfg.createHttpClient(ctx)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -87,5 +99,19 @@ func createHTTPClient(c api.Client) *httpClient {
// Do wraps the api.Client with trace context headers injection
func (ac *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
ac.propagator.Inject(ctx, propagation.HeaderCarrier(req.Header))
return ac.Client.Do(ctx, req)
r, bs, err := ac.Client.Do(ctx, req)
if utils.IsDebug(slog.Default()) {
attrs := []any{}
if r != nil {
attrs = append(attrs, slog.Int("status_code", r.StatusCode))
}
if len(bs) > 0 {
attrs = append(attrs, slog.String("response", string(bs)))
}
if err != nil {
attrs = append(attrs, slog.String("error", err.Error()))
}
slog.Debug(fmt.Sprintf("%s %s", strings.ToUpper(req.Method), req.RequestURI), attrs...)
}
return r, bs, err
}
Loading

0 comments on commit bd5b53a

Please sign in to comment.