Skip to content

Commit 61d16a2

Browse files
committed
Initial commit
0 parents  commit 61d16a2

13 files changed

Lines changed: 1447 additions & 0 deletions

File tree

.github/workflows/build-push.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: build-push
2+
on:
3+
push:
4+
workflow_dispatch:
5+
6+
jobs:
7+
lint-test:
8+
runs-on: ubuntu-24.04
9+
steps:
10+
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
11+
12+
- name: Set up Go
13+
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
14+
with:
15+
go-version-file: "go.mod"
16+
17+
- name: go mod tidy
18+
run: |
19+
go mod tidy
20+
if ! git diff --exit-code go.mod go.sum; then
21+
echo "Error: go.mod or go.sum is not tidy. Run 'go mod tidy' locally and commit the changes."
22+
exit 1
23+
fi
24+
25+
- name: golangci-lint
26+
uses: golangci/golangci-lint-action@e7fa5ac41e1cf5b7d48e45e42232ce7ada589601 # v9
27+
with:
28+
version: latest
29+
30+
- name: Install dependencies
31+
run: go get .
32+
33+
- run: go build
34+
35+
- name: unit test
36+
run: go test -race -v ./...
37+
38+
- name: coverage
39+
run: go test -v -coverprofile=coverage.out -covermode=atomic ./...
40+
41+
- name: upload coverage to codecov
42+
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5
43+
with:
44+
files: ./coverage.out
45+
fail_ci_if_error: false
46+
env:
47+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
48+
49+
run:
50+
needs: [lint-test]
51+
uses: libops/.github/.github/workflows/build-push-ghcr.yaml@main
52+
permissions:
53+
contents: read
54+
packages: write
55+
secrets: inherit

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
cap

.golangci.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
version: "2"
2+
linters:
3+
exclusions:
4+
generated: lax
5+
presets:
6+
- comments
7+
- common-false-positives
8+
- legacy
9+
- std-error-handling
10+
paths:
11+
- ../../go
12+
- ../../../../opt
13+
- third_party$
14+
- builtin$
15+
- examples$
16+
formatters:
17+
enable:
18+
- gofmt
19+
exclusions:
20+
generated: lax
21+
paths:
22+
- ../../go
23+
- ../../../../opt
24+
- third_party$
25+
- builtin$
26+
- examples$

Dockerfile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
FROM ghcr.io/libops/go1.25:main@sha256:f43c9b34f888d2ac53e87c8e061554f826b8eb580863d7b21fd787b6f0378f8f AS builder
2+
3+
SHELL ["/bin/ash", "-o", "pipefail", "-ex", "-c"]
4+
5+
WORKDIR /app
6+
7+
COPY go.mod go.sum ./
8+
RUN --mount=type=cache,target=/go/pkg/mod \
9+
go mod download
10+
11+
COPY *.go ./
12+
COPY config ./config
13+
COPY scraper ./scraper
14+
15+
RUN --mount=type=cache,target=/root/.cache/go-build \
16+
CGO_ENABLED=0 go build -ldflags="-s -w" -o /app/binary .
17+
18+
FROM ghcr.io/libops/go1.25:main@sha256:f43c9b34f888d2ac53e87c8e061554f826b8eb580863d7b21fd787b6f0378f8f
19+
20+
COPY --from=builder /app/binary /app/binary

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# cAdvisor Plus (cap)
2+
3+
[ops-agent](https://docs.cloud.google.com/stackdriver/docs/solutions/agents/ops-agent) metrics for Google Compute Engine instances running Google Container Optimized OS.
4+
5+
This service scrapes cadvisor every 30s, filters out metrics, and sends the filtered metrics to Google Cloud Monitoring
6+
7+
## Attribution
8+
9+
Code essentially forked from https://github.com/GoogleCloudPlatform/prometheus-engine/blob/7ca3c5c4e2a43cad2e79edbf724bc58cf064e423/pkg/export/gcm/promtest/local_export.go

config/config.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package config
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"regexp"
7+
"time"
8+
)
9+
10+
// Config holds all necessary configuration loaded from environment variables.
11+
type Config struct {
12+
ProjectID string
13+
Location string
14+
Cluster string
15+
CADVISORHost string
16+
ScrapeInterval time.Duration
17+
FilterPattern string
18+
FilterRegex *regexp.Regexp
19+
}
20+
21+
const (
22+
defaultCAdvisorHost = "localhost:8080"
23+
defaultScrapeInterval = 30 * time.Second
24+
envVarName = "SERVICE_PATTERN"
25+
)
26+
27+
// LoadFromEnv reads configuration from environment variables.
28+
func LoadFromEnv() (Config, error) {
29+
cfg := Config{
30+
ProjectID: os.Getenv("GCP_PROJECT"),
31+
Location: os.Getenv("GCP_ZONE"),
32+
Cluster: os.Getenv("GCP_INSTANCE_NAME"),
33+
CADVISORHost: os.Getenv("CADVISOR_HOST"),
34+
ScrapeInterval: defaultScrapeInterval,
35+
}
36+
37+
if cfg.CADVISORHost == "" {
38+
cfg.CADVISORHost = defaultCAdvisorHost
39+
}
40+
41+
cfg.FilterPattern = os.Getenv(envVarName)
42+
if cfg.FilterPattern == "" {
43+
cfg.FilterPattern = `.*`
44+
}
45+
46+
var err error
47+
cfg.FilterRegex, err = regexp.Compile(cfg.FilterPattern)
48+
if err != nil {
49+
return cfg, fmt.Errorf("failed to compile regex pattern %q: %w", cfg.FilterPattern, err)
50+
}
51+
52+
if cfg.ProjectID == "" || cfg.Location == "" || cfg.Cluster == "" {
53+
return cfg, fmt.Errorf("GCP environment variables (GCP_PROJECT, GCP_ZONE, GCP_INSTANCE_NAME) must be set")
54+
}
55+
56+
return cfg, nil
57+
}

config/config_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package config_test
2+
3+
import (
4+
"os"
5+
"strings"
6+
"testing"
7+
8+
"github.com/libops/cap/config"
9+
)
10+
11+
// Helper function to reset environment variables after each test
12+
func resetEnv() {
13+
_ = os.Unsetenv("GCP_PROJECT")
14+
_ = os.Unsetenv("GCP_ZONE")
15+
_ = os.Unsetenv("GCP_INSTANCE_NAME")
16+
_ = os.Unsetenv("CADVISOR_HOST")
17+
_ = os.Unsetenv("SERVICE_PATTERN")
18+
}
19+
20+
func TestLoadFromEnv_Success(t *testing.T) {
21+
resetEnv()
22+
_ = os.Setenv("GCP_PROJECT", "test-project")
23+
_ = os.Setenv("GCP_ZONE", "us-central1-a")
24+
_ = os.Setenv("GCP_INSTANCE_NAME", "test-cluster")
25+
_ = os.Setenv("SERVICE_PATTERN", `(test-service|other-service)`)
26+
27+
cfg, err := config.LoadFromEnv()
28+
29+
if err != nil {
30+
t.Fatalf("LoadFromEnv failed unexpectedly: %v", err)
31+
}
32+
33+
if cfg.ProjectID != "test-project" {
34+
t.Errorf("Expected ProjectID 'test-project', got %s", cfg.ProjectID)
35+
}
36+
if cfg.CADVISORHost != "localhost:8080" {
37+
t.Errorf("Expected default CADVISORHost 'localhost:8080', got %s", cfg.CADVISORHost)
38+
}
39+
if cfg.FilterRegex.String() != `(test-service|other-service)` {
40+
t.Errorf("Expected regex to match pattern, got %s", cfg.FilterRegex.String())
41+
}
42+
}
43+
44+
func TestLoadFromEnv_MissingGCPVars(t *testing.T) {
45+
resetEnv()
46+
// Intentionally omit GCP_PROJECT
47+
48+
_, err := config.LoadFromEnv()
49+
50+
if err == nil {
51+
t.Fatal("LoadFromEnv unexpectedly succeeded when required GCP vars were missing")
52+
}
53+
expectedError := "GCP environment variables (GCP_PROJECT, GCP_ZONE, GCP_INSTANCE_NAME) must be set"
54+
if !strings.Contains(err.Error(), expectedError) {
55+
t.Errorf("Expected error containing '%s', got: %v", expectedError, err)
56+
}
57+
}
58+
59+
func TestLoadFromEnv_InvalidRegex(t *testing.T) {
60+
resetEnv()
61+
_ = os.Setenv("GCP_PROJECT", "p")
62+
_ = os.Setenv("GCP_ZONE", "z")
63+
_ = os.Setenv("GCP_INSTANCE_NAME", "c")
64+
// Invalid regex: trailing backslash
65+
_ = os.Setenv("SERVICE_PATTERN", `\`)
66+
67+
_, err := config.LoadFromEnv()
68+
69+
if err == nil {
70+
t.Fatal("LoadFromEnv unexpectedly succeeded with invalid regex")
71+
}
72+
expectedError := "failed to compile regex pattern"
73+
if !strings.Contains(err.Error(), expectedError) {
74+
t.Errorf("Expected error containing '%s', got: %v", expectedError, err)
75+
}
76+
}

go.mod

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
module github.com/libops/cap
2+
3+
go 1.25.3
4+
5+
require (
6+
github.com/GoogleCloudPlatform/prometheus-engine v0.8.0
7+
github.com/go-kit/log v0.2.1
8+
github.com/prometheus/client_golang v1.23.2
9+
github.com/prometheus/prometheus v1.99.0
10+
)
11+
12+
require (
13+
cloud.google.com/go/auth v0.17.0 // indirect
14+
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
15+
cloud.google.com/go/compute/metadata v0.9.0 // indirect
16+
cloud.google.com/go/monitoring v1.24.3 // indirect
17+
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
18+
github.com/aws/aws-sdk-go v1.55.8 // indirect
19+
github.com/beorn7/perks v1.0.1 // indirect
20+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
21+
github.com/davecgh/go-spew v1.1.1 // indirect
22+
github.com/dennwc/varint v1.0.0 // indirect
23+
github.com/go-logfmt/logfmt v0.6.1 // indirect
24+
github.com/go-logr/logr v1.4.3 // indirect
25+
github.com/go-logr/stdr v1.2.2 // indirect
26+
github.com/gogo/protobuf v1.3.2 // indirect
27+
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
28+
github.com/golang/protobuf v1.5.4 // indirect
29+
github.com/google/s2a-go v0.1.9 // indirect
30+
github.com/google/uuid v1.6.0 // indirect
31+
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
32+
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
33+
github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853 // indirect
34+
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
35+
github.com/jmespath/go-jmespath v0.4.0 // indirect
36+
github.com/jpillora/backoff v1.0.0 // indirect
37+
github.com/kylelemons/godebug v1.1.0 // indirect
38+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
39+
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
40+
github.com/pkg/errors v0.9.1 // indirect
41+
github.com/pmezard/go-difflib v1.0.0 // indirect
42+
github.com/prometheus/client_model v0.6.2 // indirect
43+
github.com/prometheus/common v0.67.4 // indirect
44+
github.com/prometheus/common/sigv4 v0.1.0 // indirect
45+
github.com/prometheus/procfs v0.19.2 // indirect
46+
github.com/stretchr/testify v1.11.1 // indirect
47+
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
48+
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
49+
go.opentelemetry.io/otel v1.38.0 // indirect
50+
go.opentelemetry.io/otel/metric v1.38.0 // indirect
51+
go.opentelemetry.io/otel/trace v1.38.0 // indirect
52+
go.uber.org/atomic v1.11.0 // indirect
53+
go.uber.org/goleak v1.3.0 // indirect
54+
go.yaml.in/yaml/v2 v2.4.3 // indirect
55+
golang.org/x/crypto v0.45.0 // indirect
56+
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect
57+
golang.org/x/net v0.47.0 // indirect
58+
golang.org/x/oauth2 v0.33.0 // indirect
59+
golang.org/x/sync v0.18.0 // indirect
60+
golang.org/x/sys v0.38.0 // indirect
61+
golang.org/x/text v0.31.0 // indirect
62+
golang.org/x/time v0.14.0 // indirect
63+
google.golang.org/api v0.256.0 // indirect
64+
google.golang.org/genproto v0.0.0-20251111163417-95abcf5c77ba // indirect
65+
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect
66+
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
67+
google.golang.org/grpc v1.77.0 // indirect
68+
google.golang.org/protobuf v1.36.10 // indirect
69+
gopkg.in/yaml.v2 v2.4.0 // indirect
70+
gopkg.in/yaml.v3 v3.0.1 // indirect
71+
)
72+
73+
replace github.com/prometheus/prometheus => github.com/prometheus/prometheus v0.41.0

0 commit comments

Comments
 (0)