diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..f5994254 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,43 @@ +# Taken from https://docs.github.com/en/actions/publishing-packages/publishing-docker-images +name: Create and publish a Docker image + +on: + push: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + + # Sets the permissions granted to the GITHUB_TOKEN for the actions in this job. + permissions: + contents: read + packages: write + + steps: + - name: "Checkout repository" + uses: actions/checkout@v4 + + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build and push Docker image + uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 58c9c4b8..85946aa2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,40 @@ -ARG ARCH="amd64" -ARG OS="linux" -FROM quay.io/prometheus/busybox-${OS}-${ARCH}:glibc -LABEL maintainer="The Prometheus Authors " +# ARG ARCH="arm64" +# ARG OS="linux" +# FROM quay.io/prometheus/busybox-${OS}-${ARCH}:glibc +# LABEL maintainer="The Prometheus Authors " -ARG ARCH="amd64" -ARG OS="linux" -COPY .build/${OS}-${ARCH}/json_exporter /bin/json_exporter +# ARG ARCH="arm64" +# ARG OS="linux" +# COPY .build/${OS}-${ARCH}/json_exporter /bin/json_exporter + +FROM golang:1.19 as builder + +# Create and change to the app directory. +WORKDIR /app + +# Retrieve application dependencies. +# This allows the container build to reuse cached dependencies. +# Expecting to copy go.mod and if present go.sum. +COPY go.* ./ +RUN go mod download + +# Copy local code to the container image. +COPY . ./ + +# Build the binary. +RUN go build -v -o json_exporter + +# Use the official Debian slim image for a lean production container. +# https://hub.docker.com/_/debian +# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds +FROM debian:buster-slim +RUN set -x && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \ + ca-certificates && \ + rm -rf /var/lib/apt/lists/* + +# Copy the binary to the production image from the builder stage. +COPY --from=builder /app/json_exporter /app/json_exporter EXPOSE 7979 USER nobody -ENTRYPOINT [ "/bin/json_exporter" ] +ENTRYPOINT [ "/app/json_exporter" ] diff --git a/exporter/collector.go b/exporter/collector.go index 4effc10f..080f5dda 100644 --- a/exporter/collector.go +++ b/exporter/collector.go @@ -57,17 +57,27 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) { continue } - if floatValue, err := SanitizeValue(value); err == nil { - metric := prometheus.MustNewConstMetric( + floatValue, err := SanitizeValue(value) + if err == nil { + ch <- prometheus.MustNewConstMetric( m.Desc, m.ValueType, floatValue, extractLabels(mc.Logger, mc.Data, m.LabelsJSONPaths)..., ) - ch <- timestampMetric(mc.Logger, m, mc.Data, metric) } else { - level.Error(mc.Logger).Log("msg", "Failed to convert extracted value to float64", "path", m.KeyJSONPath, "value", value, "err", err, "metric", m.Desc) - continue + intValue, err := SanitizeHexIntValue(value) + if err == nil { + ch <- prometheus.MustNewConstMetric( + m.Desc, + m.ValueType, + float64(intValue), + extractLabels(mc.Logger, mc.Data, m.LabelsJSONPaths)..., + ) + } else { + level.Error(mc.Logger).Log("msg", "Failed to convert extracted value to float64", "path", m.KeyJSONPath, "value", value, "err", err, "metric", m.Desc) + continue + } } case config.ObjectScrape: @@ -91,7 +101,8 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) { continue } - if floatValue, err := SanitizeValue(value); err == nil { + floatValue, err := SanitizeValue(value) + if err == nil { metric := prometheus.MustNewConstMetric( m.Desc, m.ValueType, @@ -100,8 +111,18 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) { ) ch <- timestampMetric(mc.Logger, m, jdata, metric) } else { - level.Error(mc.Logger).Log("msg", "Failed to convert extracted value to float64", "path", m.ValueJSONPath, "value", value, "err", err, "metric", m.Desc) - continue + intValue, err := SanitizeHexIntValue(value) + if err == nil { + ch <- prometheus.MustNewConstMetric( + m.Desc, + m.ValueType, + float64(intValue), + extractLabels(mc.Logger, mc.Data, m.LabelsJSONPaths)..., + ) + } else { + level.Error(mc.Logger).Log("msg", "Failed to convert extracted value to float64", "path", m.ValueJSONPath, "value", value, "err", err, "metric", m.Desc) + continue + } } } } else { diff --git a/exporter/util.go b/exporter/util.go index 8374ddce..32571a56 100644 --- a/exporter/util.go +++ b/exporter/util.go @@ -74,6 +74,23 @@ func SanitizeIntValue(s string) (int64, error) { return value, fmt.Errorf(resultErr) } +func SanitizeHexIntValue(s string) (int64, error) { + var err error + var value int64 + var resultErr string + + // remove 0x suffix if found in the input string + cleaned := strings.Replace(s, "0x", "", -1) + cleaned = strings.Replace(cleaned, "\"", "", -1) + + if value, err = strconv.ParseInt(cleaned, 16, 64); err == nil { + return value, nil + } + resultErr = fmt.Sprintf("%s", err) + + return value, fmt.Errorf(resultErr) +} + func CreateMetricsList(c config.Module) ([]JSONMetric, error) { var ( metrics []JSONMetric diff --git a/exporter/util_test.go b/exporter/util_test.go index 90392849..3688d193 100644 --- a/exporter/util_test.go +++ b/exporter/util_test.go @@ -48,6 +48,27 @@ func TestSanitizeValue(t *testing.T) { } } +func TestSanitizeValueHex(t *testing.T) { + tests := []struct { + Input string + ExpectedOutput int64 + ShouldSucceed bool + }{ + {"0x1d55195", 30757269, true}, + {"\"0x1d55195\"", 30757269, true}, + } + + for i, test := range tests { + actualOutput, err := SanitizeHexIntValue(test.Input) + if err != nil && test.ShouldSucceed { + t.Fatalf("Value snitization test %d failed with an unexpected error.\nINPUT:\n%q\nERR:\n%s", i, test.Input, err) + } + if test.ShouldSucceed && actualOutput != test.ExpectedOutput { + t.Fatalf("Value sanitization test %d fails unexpectedly.\nGOT:\n%d\nEXPECTED:\n%d", i, actualOutput, test.ExpectedOutput) + } + } +} + func TestSanitizeValueNaN(t *testing.T) { actualOutput, err := SanitizeValue("") if err != nil {