diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 59b226a..71596fb 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -12,7 +12,7 @@ jobs: contents: write steps: - name: Checkout - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -20,23 +20,23 @@ jobs: run: git fetch --force --tags - name: Set up QEMU - uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 + uses: docker/setup-qemu-action@v3 - name: Login to ghcr.io - uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Go - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 + uses: actions/setup-go@v5 with: - go-version: "1.20.x" + go-version-file: 'go.mod' - name: Run GoReleaser - uses: goreleaser/goreleaser-action@336e29918d653399e599bfca99fadc1d7ffbc9f7 + uses: goreleaser/goreleaser-action@v6 with: - args: release --rm-dist + args: release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-and-build.yaml b/.github/workflows/test-and-build.yaml index 1ef372f..67ea2ed 100644 --- a/.github/workflows/test-and-build.yaml +++ b/.github/workflows/test-and-build.yaml @@ -10,15 +10,15 @@ jobs: contents: read steps: - name: Checkout - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + uses: actions/checkout@v4 - name: Unshallow run: git fetch --prune --unshallow - name: Set up Go - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 + uses: actions/setup-go@v5 with: - go-version: "1.20.x" + go-version-file: 'go.mod' - name: Test run: go test -short -v ./... @@ -30,19 +30,19 @@ jobs: packages: write steps: - name: Checkout - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + uses: actions/checkout@v4 - name: Unshallow run: git fetch --prune --unshallow - name: Set up Go - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 + uses: actions/setup-go@v5 with: - go-version: "1.20.x" + go-version-file: 'go.mod' - name: Run GoReleaser - uses: goreleaser/goreleaser-action@336e29918d653399e599bfca99fadc1d7ffbc9f7 + uses: goreleaser/goreleaser-action@v6 with: - args: --snapshot --skip-sign --skip-validate --skip-publish --rm-dist + args: --snapshot --skip validate env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 0284f0e..4913425 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -21,11 +21,11 @@ builds: -X github.com/prometheus/common/version.BuildDate={{.Date}} release: github: - owner: jetstack + owner: dwalker-sabiogroup name: dependency-track-exporter dockers: - image_templates: - - "ghcr.io/jetstack/dependency-track-exporter:{{.Version}}-amd64" + - "ghcr.io/dwalker-sabiogroup/dependency-track-exporter:{{.Version}}-amd64" dockerfile: Dockerfile.goreleaser use: buildx build_flag_templates: @@ -37,7 +37,7 @@ dockers: - "--label=org.opencontainers.image.source={{.GitURL}}" - "--platform=linux/amd64" - image_templates: - - "ghcr.io/jetstack/dependency-track-exporter:{{.Version}}-arm64" + - "ghcr.io/dwalker-sabiogroup/dependency-track-exporter:{{.Version}}-arm64" dockerfile: Dockerfile.goreleaser use: buildx build_flag_templates: @@ -50,11 +50,11 @@ dockers: - "--platform=linux/arm64" goarch: arm64 docker_manifests: - - name_template: "ghcr.io/jetstack/dependency-track-exporter:{{.Version}}" + - name_template: "ghcr.io/dwalker-sabiogroup/dependency-track-exporter:{{.Version}}" image_templates: - - "ghcr.io/jetstack/dependency-track-exporter:{{.Version}}-amd64" - - "ghcr.io/jetstack/dependency-track-exporter:{{.Version}}-arm64" - - name_template: "ghcr.io/jetstack/dependency-track-exporter:latest" + - "ghcr.io/dwalker-sabiogroup/dependency-track-exporter:{{.Version}}-amd64" + - "ghcr.io/dwalker-sabiogroup/dependency-track-exporter:{{.Version}}-arm64" + - name_template: "ghcr.io/dwalker-sabiogroup/dependency-track-exporter:latest" image_templates: - - "ghcr.io/jetstack/dependency-track-exporter:{{.Version}}-amd64" - - "ghcr.io/jetstack/dependency-track-exporter:{{.Version}}-arm64" + - "ghcr.io/dwalker-sabiogroup/dependency-track-exporter:{{.Version}}-amd64" + - "ghcr.io/dwalker-sabiogroup/dependency-track-exporter:{{.Version}}-arm64" diff --git a/README.md b/README.md index 04945d0..5d2f780 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ Flags: Dependency-Track server address (default: http://localhost:8080 or $DEPENDENCY_TRACK_ADDR) --dtrack.api-key=DTRACK.API-KEY Dependency-Track API key (default: $DEPENDENCY_TRACK_API_KEY) + --[no-]exporter.reduce-policy-cardinality + Initialize all policy_violations metric label values (can also be set with $EXPORTER_REDUCE_POLICY_CARDINALITY) --log.level=info Only log messages with the given severity or above. One of: [debug, info, warn, error] --log.format=logfmt Output format of log messages. One of: [logfmt, json] --version Show application version. diff --git a/go.mod b/go.mod index cd9f84a..fa916c1 100644 --- a/go.mod +++ b/go.mod @@ -1,39 +1,45 @@ -module github.com/jetstack/dependency-track-exporter +module github.com/dwalker-sabiogroup/dependency-track-exporter -go 1.20 +go 1.22 require ( - github.com/DependencyTrack/client-go v0.11.0 - github.com/alecthomas/kingpin/v2 v2.3.2 + github.com/DependencyTrack/client-go v0.13.0 + github.com/alecthomas/kingpin/v2 v2.4.0 github.com/go-kit/log v0.2.1 - github.com/google/go-cmp v0.5.9 - github.com/google/uuid v1.3.0 - github.com/prometheus/client_golang v1.16.0 - github.com/prometheus/common v0.44.0 - github.com/prometheus/exporter-toolkit v0.10.0 + github.com/google/go-cmp v0.6.0 + github.com/google/uuid v1.6.0 + github.com/prometheus/client_golang v1.19.1 + github.com/prometheus/common v0.55.0 + github.com/prometheus/exporter-toolkit v0.11.0 ) require ( - github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect + github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/go-logfmt/logfmt v0.5.1 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/jpillora/backoff v1.0.0 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect - github.com/prometheus/client_model v0.4.0 // indirect - github.com/prometheus/procfs v0.10.1 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/samber/slog-http v1.4.1 // indirect + github.com/samber/slog-otel v0.0.0-20240701120852-8150bb781d6a // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect - golang.org/x/crypto v0.8.0 // indirect - golang.org/x/net v0.10.0 // indirect - golang.org/x/oauth2 v0.8.0 // indirect - golang.org/x/sync v0.2.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.30.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/net v0.27.0 // indirect + golang.org/x/oauth2 v0.21.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index debdaae..f435016 100644 --- a/go.sum +++ b/go.sum @@ -1,90 +1,102 @@ -github.com/DependencyTrack/client-go v0.11.0 h1:1g+eHC8nJyIzi68zcs+dr3OHRvS1aC+4Uy3YKA0JJhc= -github.com/DependencyTrack/client-go v0.11.0/go.mod h1:XLZnOksOs56Svq+K4xmBkN8U97gpP7r1BkhCc/xA8Iw= -github.com/alecthomas/kingpin/v2 v2.3.2 h1:H0aULhgmSzN8xQ3nX1uxtdlTHYoPLu5AhHxWrKI6ocU= -github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/DependencyTrack/client-go v0.13.0 h1:w2TvudrBbgBtS2oMbisfo9Av4rJnS1g9VsdCpZ6PU/8= +github.com/DependencyTrack/client-go v0.13.0/go.mod h1:XLZnOksOs56Svq+K4xmBkN8U97gpP7r1BkhCc/xA8Iw= +github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= +github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= +github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 h1:t3eaIm0rUkzbrIewtiFmMK5RXHej2XnoXNhxVsAYUfg= +github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= +github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= -github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= -github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= -github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= -github.com/prometheus/exporter-toolkit v0.10.0 h1:yOAzZTi4M22ZzVxD+fhy1URTuNRj/36uQJJ5S8IPza8= -github.com/prometheus/exporter-toolkit v0.10.0/go.mod h1:+sVFzuvV5JDyw+Ih6p3zFxZNVnKQa3x5qPmDSiPu4ZY= -github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= -github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/exporter-toolkit v0.11.0 h1:yNTsuZ0aNCNFQ3aFTD2uhPOvr4iD7fdBvKPAEGkNf+g= +github.com/prometheus/exporter-toolkit v0.11.0/go.mod h1:BVnENhnNecpwoTLiABx7mrPB/OLRIgN74qlQbV+FK1Q= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/samber/slog-http v1.4.1 h1:+QdPr43wLrzDwRlo5jHPBkkZwP3Sg0FkNdPvnZ4exUo= +github.com/samber/slog-http v1.4.1/go.mod h1:n6h4x2ZBeTgLqMKf95EuNlU6mcJF1b/RVLxo1od5+V0= +github.com/samber/slog-otel v0.0.0-20240701120852-8150bb781d6a h1:jTxxtTkTUft08O2kNOYz21iG0DZbJuaa0r5wEFMJR6Y= +github.com/samber/slog-otel v0.0.0-20240701120852-8150bb781d6a/go.mod h1:w/QbgLQKaKHzS1corVXOimL+/oVE5r1DTBsRr7wfI24= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/exporter/exporter.go b/internal/exporter/exporter.go index a84775a..23198cf 100644 --- a/internal/exporter/exporter.go +++ b/internal/exporter/exporter.go @@ -2,303 +2,105 @@ package exporter import ( "context" - "fmt" - "net/http" - "strconv" + "log/slog" dtrack "github.com/DependencyTrack/client-go" - "github.com/go-kit/log" - "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" ) -const ( - // Namespace is the metrics namespace of the exporter - Namespace string = "dependency_track" -) - -// Exporter exports metrics from a Dependency-Track server type Exporter struct { - Client *dtrack.Client - Logger log.Logger + client *dtrack.Client + logger *slog.Logger + vulnerabilities *prometheus.Desc + policyViolations *prometheus.Desc + lastBOMImport *prometheus.Desc + inheritedRiskScore *prometheus.Desc } -// HandlerFunc handles requests to /metrics -func (e *Exporter) HandlerFunc() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - registry := prometheus.NewRegistry() - - if err := e.collectPortfolioMetrics(r.Context(), registry); err != nil { - level.Error(e.Logger).Log("err", err) - http.Error(w, fmt.Sprintf("error: %s", err), http.StatusInternalServerError) - return - } - - if err := e.collectProjectMetrics(r.Context(), registry); err != nil { - level.Error(e.Logger).Log("err", err) - http.Error(w, fmt.Sprintf("error: %s", err), http.StatusInternalServerError) - return - } - - // Serve - h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{}) - h.ServeHTTP(w, r) +func New(cl *dtrack.Client, l *slog.Logger) *Exporter { + return &Exporter{ + client: cl, + logger: l, + vulnerabilities: prometheus.NewDesc( + "dependency_track_project_vulnerability", + "Number of vulnerabilities for a project by severity", + []string{"name", "uuid", "version", "severity"}, + nil, + ), + policyViolations: prometheus.NewDesc( + "dependency_track_project_policy_violation", + "Policy violations for a project", + []string{"name", "uuid", "version", "state"}, + nil, + ), + lastBOMImport: prometheus.NewDesc( + "dependency_track_project_last_bom_import", + "Last BOM import date, represented as a Unix timestamp", + []string{"name", "uuid", "version"}, + nil, + ), + inheritedRiskScore: prometheus.NewDesc( + "dependency_track_project_inherited_risk_score", + "Inherited risk score for a project", + []string{"name", "uuid", "version"}, + nil, + ), } } -func (e *Exporter) collectPortfolioMetrics(ctx context.Context, registry *prometheus.Registry) error { - var ( - inheritedRiskScore = prometheus.NewGauge( - prometheus.GaugeOpts{ - Name: prometheus.BuildFQName(Namespace, "portfolio", "inherited_risk_score"), - Help: "The inherited risk score of the whole portfolio.", - }, - ) - vulnerabilities = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Name: prometheus.BuildFQName(Namespace, "portfolio", "vulnerabilities"), - Help: "Number of vulnerabilities across the whole portfolio, by severity.", - }, - []string{ - "severity", - }, - ) - findings = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Name: prometheus.BuildFQName(Namespace, "portfolio", "findings"), - Help: "Number of findings across the whole portfolio, audited and unaudited.", - }, - []string{ - "audited", - }, - ) - ) - registry.MustRegister( - inheritedRiskScore, - vulnerabilities, - findings, - ) - - portfolioMetrics, err := e.Client.Metrics.LatestPortfolioMetrics(ctx) +func (e *Exporter) Collect(ch chan<- prometheus.Metric) { + ctx := context.Background() + projects, err := e.fetchProjects(ctx) if err != nil { - return err + e.logger.WarnContext(ctx, err.Error()) } - inheritedRiskScore.Set(portfolioMetrics.InheritedRiskScore) + for _, p := range e.filterProjects(projects) { + ch <- prometheus.MustNewConstMetric(e.vulnerabilities, prometheus.GaugeValue, float64(p.Metrics.Critical), p.Name, p.UUID.String(), p.Version, "critical") + ch <- prometheus.MustNewConstMetric(e.vulnerabilities, prometheus.GaugeValue, float64(p.Metrics.High), p.Name, p.UUID.String(), p.Version, "high") + ch <- prometheus.MustNewConstMetric(e.vulnerabilities, prometheus.GaugeValue, float64(p.Metrics.Medium), p.Name, p.UUID.String(), p.Version, "medium") + ch <- prometheus.MustNewConstMetric(e.vulnerabilities, prometheus.GaugeValue, float64(p.Metrics.Critical), p.Name, p.UUID.String(), p.Version, "low") + ch <- prometheus.MustNewConstMetric(e.vulnerabilities, prometheus.GaugeValue, float64(p.Metrics.Unassigned), p.Name, p.UUID.String(), p.Version, "unassigned") - severities := map[string]int{ - "CRITICAL": portfolioMetrics.Critical, - "HIGH": portfolioMetrics.High, - "MEDIUM": portfolioMetrics.Medium, - "LOW": portfolioMetrics.Low, - "UNASSIGNED": portfolioMetrics.Unassigned, - } - for severity, v := range severities { - vulnerabilities.With(prometheus.Labels{ - "severity": severity, - }).Set(float64(v)) - } + ch <- prometheus.MustNewConstMetric(e.policyViolations, prometheus.GaugeValue, float64(p.Metrics.PolicyViolationsFail), p.Name, p.UUID.String(), p.Version, "fail") + ch <- prometheus.MustNewConstMetric(e.policyViolations, prometheus.GaugeValue, float64(p.Metrics.PolicyViolationsWarn), p.Name, p.UUID.String(), p.Version, "warn") + ch <- prometheus.MustNewConstMetric(e.policyViolations, prometheus.GaugeValue, float64(p.Metrics.PolicyViolationsInfo), p.Name, p.UUID.String(), p.Version, "info") + ch <- prometheus.MustNewConstMetric(e.policyViolations, prometheus.GaugeValue, float64(p.Metrics.PolicyViolationsUnaudited), p.Name, p.UUID.String(), p.Version, "unaudited") - findingsAudited := map[string]int{ - "true": portfolioMetrics.FindingsAudited, - "false": portfolioMetrics.FindingsUnaudited, - } - for audited, v := range findingsAudited { - findings.With(prometheus.Labels{ - "audited": audited, - }).Set(float64(v)) + ch <- prometheus.MustNewConstMetric(e.lastBOMImport, prometheus.GaugeValue, float64(p.LastBOMImport), p.Name, p.UUID.String(), p.Version) + ch <- prometheus.MustNewConstMetric(e.inheritedRiskScore, prometheus.GaugeValue, p.Metrics.InheritedRiskScore, p.Name, p.UUID.String(), p.Version) } - - return nil } -func (e *Exporter) collectProjectMetrics(ctx context.Context, registry *prometheus.Registry) error { - var ( - info = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Name: prometheus.BuildFQName(Namespace, "project", "info"), - Help: "Project information.", - }, - []string{ - "uuid", - "name", - "version", - "classifier", - "active", - "tags", - }, - ) - vulnerabilities = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Name: prometheus.BuildFQName(Namespace, "project", "vulnerabilities"), - Help: "Number of vulnerabilities for a project by severity.", - }, - []string{ - "uuid", - "name", - "version", - "severity", - }, - ) - policyViolations = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Name: prometheus.BuildFQName(Namespace, "project", "policy_violations"), - Help: "Policy violations for a project.", - }, - []string{ - "uuid", - "name", - "version", - "type", - "state", - "analysis", - "suppressed", - }, - ) - lastBOMImport = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Name: prometheus.BuildFQName(Namespace, "project", "last_bom_import"), - Help: "Last BOM import date, represented as a Unix timestamp.", - }, - []string{ - "uuid", - "name", - "version", - }, - ) - inheritedRiskScore = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Name: prometheus.BuildFQName(Namespace, "project", "inherited_risk_score"), - Help: "Inherited risk score for a project.", - }, - []string{ - "uuid", - "name", - "version", - }, - ) - ) - registry.MustRegister( - info, - vulnerabilities, - policyViolations, - lastBOMImport, - inheritedRiskScore, - ) +func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { + prometheus.DescribeByCollect(e, ch) +} - projects, err := e.fetchProjects(ctx) - if err != nil { - return err - } +func (e *Exporter) fetchProjects(ctx context.Context) ([]dtrack.Project, error) { + return dtrack.FetchAll(func(po dtrack.PageOptions) (dtrack.Page[dtrack.Project], error) { + return e.client.Project.GetAll(ctx, po) + }) +} - for _, project := range projects { - projTags := "," - for _, t := range project.Tags { - projTags = projTags + t.Name + "," - } - info.With(prometheus.Labels{ - "uuid": project.UUID.String(), - "name": project.Name, - "version": project.Version, - "classifier": project.Classifier, - "active": strconv.FormatBool(project.Active), - "tags": projTags, - }).Set(1) +func (e *Exporter) filterProjects(projects []dtrack.Project) []dtrack.Project { + m := make(map[string]dtrack.Project, len(projects)) - severities := map[string]int{ - "CRITICAL": project.Metrics.Critical, - "HIGH": project.Metrics.High, - "MEDIUM": project.Metrics.Medium, - "LOW": project.Metrics.Low, - "UNASSIGNED": project.Metrics.Unassigned, - } - for severity, v := range severities { - vulnerabilities.With(prometheus.Labels{ - "uuid": project.UUID.String(), - "name": project.Name, - "version": project.Version, - "severity": severity, - }).Set(float64(v)) + for _, p := range projects { + val, ok := m[p.Name] + if !ok { + m[p.Name] = p } - lastBOMImport.With(prometheus.Labels{ - "uuid": project.UUID.String(), - "name": project.Name, - "version": project.Version, - }).Set(float64(project.LastBOMImport)) - inheritedRiskScore.With(prometheus.Labels{ - "uuid": project.UUID.String(), - "name": project.Name, - "version": project.Version, - }).Set(project.Metrics.InheritedRiskScore) - - // Initialize all the possible violation series with a 0 value so that it - // properly records increments from 0 -> 1 - for _, possibleType := range []string{"LICENSE", "OPERATIONAL", "SECURITY"} { - for _, possibleState := range []string{"INFO", "WARN", "FAIL"} { - for _, possibleAnalysis := range []dtrack.ViolationAnalysisState{ - dtrack.ViolationAnalysisStateApproved, - dtrack.ViolationAnalysisStateRejected, - dtrack.ViolationAnalysisStateNotSet, - // If there isn't any analysis for a policy - // violation then the value in the UI is - // actually empty. So let's represent that in - // these metrics as a possible analysis state. - "", - } { - for _, possibleSuppressed := range []string{"true", "false"} { - policyViolations.With(prometheus.Labels{ - "uuid": project.UUID.String(), - "name": project.Name, - "version": project.Version, - "type": possibleType, - "state": possibleState, - "analysis": string(possibleAnalysis), - "suppressed": possibleSuppressed, - }) - } - } - } + if val.Metrics.FirstOccurrence < p.Metrics.FirstOccurrence { + m[p.Name] = p } } - violations, err := e.fetchPolicyViolations(ctx) - if err != nil { - return err - } + filtered := make([]dtrack.Project, 0, len(projects)) - for _, violation := range violations { - var ( - analysisState string - suppressed string = "false" - ) - if analysis := violation.Analysis; analysis != nil { - analysisState = string(analysis.State) - suppressed = strconv.FormatBool(analysis.Suppressed) - } - policyViolations.With(prometheus.Labels{ - "uuid": violation.Project.UUID.String(), - "name": violation.Project.Name, - "version": violation.Project.Version, - "type": violation.Type, - "state": violation.PolicyCondition.Policy.ViolationState, - "analysis": analysisState, - "suppressed": suppressed, - }).Inc() + for _, p := range m { + filtered = append(filtered, p) } - return nil -} - -func (e *Exporter) fetchProjects(ctx context.Context) ([]dtrack.Project, error) { - return dtrack.FetchAll(func(po dtrack.PageOptions) (dtrack.Page[dtrack.Project], error) { - return e.Client.Project.GetAll(ctx, po) - }) -} - -func (e *Exporter) fetchPolicyViolations(ctx context.Context) ([]dtrack.PolicyViolation, error) { - return dtrack.FetchAll(func(po dtrack.PageOptions) (dtrack.Page[dtrack.PolicyViolation], error) { - return e.Client.PolicyViolation.GetAll(ctx, true, po) - }) + return filtered } diff --git a/internal/exporter/exporter_test.go b/internal/exporter/exporter_test.go deleted file mode 100644 index 6d1e672..0000000 --- a/internal/exporter/exporter_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package exporter - -import ( - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "strconv" - "testing" - - dtrack "github.com/DependencyTrack/client-go" - "github.com/google/go-cmp/cmp" - "github.com/google/uuid" -) - -func TestFetchProjects_Pagination(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - - var wantProjects []dtrack.Project - for i := 0; i < 468; i++ { - wantProjects = append(wantProjects, dtrack.Project{ - UUID: uuid.New(), - }) - } - - mux.HandleFunc("/api/v1/project", func(w http.ResponseWriter, r *http.Request) { - pageSize, err := strconv.Atoi(r.URL.Query().Get("pageSize")) - if err != nil { - t.Fatalf("unexpected error converting pageSize to int: %s", err) - } - pageNumber, err := strconv.Atoi(r.URL.Query().Get("pageNumber")) - if err != nil { - t.Fatalf("unexpected error converting pageNumber to int: %s", err) - } - w.Header().Set("X-Total-Count", strconv.Itoa(len(wantProjects))) - w.Header().Set("Content-type", "application/json") - var projects []dtrack.Project - for i := 0; i < pageSize; i++ { - idx := (pageSize * (pageNumber - 1)) + i - if idx >= len(wantProjects) { - break - } - projects = append(projects, wantProjects[idx]) - } - json.NewEncoder(w).Encode(projects) - }) - - client, err := dtrack.NewClient(server.URL) - if err != nil { - t.Fatalf("unexpected error setting up client: %s", err) - } - - e := &Exporter{ - Client: client, - } - - gotProjects, err := e.fetchProjects(context.Background()) - if err != nil { - t.Fatalf("unexpected error fetching projects: %s", err) - } - - if diff := cmp.Diff(wantProjects, gotProjects); diff != "" { - t.Errorf("unexpected projects:\n%s", diff) - } -} - -func TestFetchPolicyViolations_Pagination(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - - var wantPolicyViolations []dtrack.PolicyViolation - for i := 0; i < 468; i++ { - wantPolicyViolations = append(wantPolicyViolations, dtrack.PolicyViolation{ - UUID: uuid.New(), - }) - } - - mux.HandleFunc("/api/v1/violation", func(w http.ResponseWriter, r *http.Request) { - pageSize, err := strconv.Atoi(r.URL.Query().Get("pageSize")) - if err != nil { - t.Fatalf("unexpected error converting pageSize to int: %s", err) - } - pageNumber, err := strconv.Atoi(r.URL.Query().Get("pageNumber")) - if err != nil { - t.Fatalf("unexpected error converting pageNumber to int: %s", err) - } - w.Header().Set("X-Total-Count", strconv.Itoa(len(wantPolicyViolations))) - w.Header().Set("Content-type", "application/json") - var policyViolations []dtrack.PolicyViolation - for i := 0; i < pageSize; i++ { - idx := (pageSize * (pageNumber - 1)) + i - if idx >= len(wantPolicyViolations) { - break - } - policyViolations = append(policyViolations, wantPolicyViolations[idx]) - } - json.NewEncoder(w).Encode(policyViolations) - }) - - client, err := dtrack.NewClient(server.URL) - if err != nil { - t.Fatalf("unexpected error setting up client: %s", err) - } - - e := &Exporter{ - Client: client, - } - - gotPolicyViolations, err := e.fetchPolicyViolations(context.Background()) - if err != nil { - t.Fatalf("unexpected error fetching projects: %s", err) - } - - if diff := cmp.Diff(wantPolicyViolations, gotPolicyViolations); diff != "" { - t.Errorf("unexpected policy violations:\n%s", diff) - } -} diff --git a/main.go b/main.go index e36cf54..8726011 100644 --- a/main.go +++ b/main.go @@ -2,21 +2,26 @@ package main import ( "fmt" + "log" + "log/slog" "net/http" "os" "os/signal" "syscall" + _ "net/http/pprof" + dtrack "github.com/DependencyTrack/client-go" "github.com/alecthomas/kingpin/v2" - "github.com/go-kit/log/level" - "github.com/jetstack/dependency-track-exporter/internal/exporter" + "github.com/dwalker-sabiogroup/dependency-track-exporter/internal/exporter" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/collectors" + "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/common/promlog" "github.com/prometheus/common/promlog/flag" "github.com/prometheus/common/version" - "github.com/prometheus/exporter-toolkit/web" - webflag "github.com/prometheus/exporter-toolkit/web/kingpinflag" + sloghttp "github.com/samber/slog-http" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) const ( @@ -25,66 +30,86 @@ const ( ) func init() { - prometheus.MustRegister(version.NewCollector(exporter.Namespace + "_exporter")) + prometheus.MustRegister(collectors.NewBuildInfoCollector()) } func main() { var ( - webConfig = webflag.AddFlags(kingpin.CommandLine, ":9916") - metricsPath = kingpin.Flag("web.metrics-path", "Path under which to expose metrics").Default("/metrics").String() - dtAddress = kingpin.Flag("dtrack.address", fmt.Sprintf("Dependency-Track server address (can also be set with $%s)", envAddress)).Default("http://localhost:8080").Envar(envAddress).String() - dtAPIKey = kingpin.Flag("dtrack.api-key", fmt.Sprintf("Dependency-Track API key (can also be set with $%s)", envAPIKey)).Envar(envAPIKey).Required().String() - promlogConfig = promlog.Config{} + profilingConfig = kingpin.Flag("web.pprof-listen-address", "Address to listen on for pprof").Default(":9917").String() + webConfig = kingpin.Flag("web.listen-address", "Address to listen on for web interface and telemetry").Default(":9916").String() + metricsPath = kingpin.Flag("web.metrics-path", "Path under which to expose metrics").Default("/metrics").String() + dtAddress = kingpin.Flag("dtrack.address", fmt.Sprintf("Dependency-Track server address (can also be set with $%s)", envAddress)).Default("http://localhost:8080").Envar(envAddress).String() + dtAPIKey = kingpin.Flag("dtrack.api-key", fmt.Sprintf("Dependency-Track API key (can also be set with $%s)", envAPIKey)).Envar(envAPIKey).Required().String() + promlogConfig = promlog.Config{} ) flag.AddFlags(kingpin.CommandLine, &promlogConfig) - kingpin.Version(version.Print(exporter.Namespace + "_exporter")) + kingpin.Version(version.Print("dependency_track_exporter")) kingpin.HelpFlag.Short('h') kingpin.Parse() - logger := promlog.New(&promlogConfig) + logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) + + config := sloghttp.Config{ + WithSpanID: true, + WithTraceID: true, + } - level.Info(logger).Log("msg", fmt.Sprintf("Starting %s_exporter %s", exporter.Namespace, version.Info())) - level.Info(logger).Log("msg", fmt.Sprintf("Build context %s", version.BuildContext())) + logger.Info(fmt.Sprintf("Starting depnendency_track_exporter %s", version.Info())) + logger.Info("Build context " + version.BuildContext()) c, err := dtrack.NewClient(*dtAddress, dtrack.WithAPIKey(*dtAPIKey)) if err != nil { - level.Error(logger).Log("msg", "Error creating client", "err", err) + logger.Error("Error creating client", slog.String("error", err.Error())) os.Exit(1) } - e := exporter.Exporter{ - Client: c, - Logger: logger, - } + e := exporter.New(c, logger) + + prometheus.MustRegister(e) + + mux := http.NewServeMux() + + handler := sloghttp.Recovery(mux) + handler = sloghttp.NewWithConfig(logger, config)(handler) - http.HandleFunc(*metricsPath, e.HandlerFunc()) - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - _, _ = w.Write([]byte(` - Dependency-Track Exporter - -

Dependency-Track Exporter

-

Metrics

- - `)) - }) + mux.Handle(*metricsPath, otelhttp.WithRouteTag(*metricsPath, promhttp.Handler())) + + mux.Handle("/", otelhttp.WithRouteTag("/", http.HandlerFunc( + func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + }))) srvc := make(chan struct{}) term := make(chan os.Signal, 1) signal.Notify(term, os.Interrupt, syscall.SIGTERM) go func() { - srv := &http.Server{} - if err := web.ListenAndServe(srv, webConfig, logger); err != http.ErrServerClosed { - level.Error(logger).Log("msg", "Error starting HTTP server", "err", err) - close(srvc) + log.Fatal(http.ListenAndServe(*profilingConfig, http.DefaultServeMux)) + }() + + go func() { + err := http.ListenAndServe(*webConfig, + otelhttp.NewHandler( + handler, + "server", + otelhttp.WithMessageEvents( + otelhttp.ReadEvents, + otelhttp.WriteEvents, + ), + ), + ) + + if err != nil { + logger.Error(err.Error()) } + }() for { select { case <-term: - level.Info(logger).Log("msg", "Received SIGTERM, exiting gracefully...") + logger.Info("Received SIGTERM, exiting gracefully...") os.Exit(0) case <-srvc: os.Exit(1)