diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..9b123da --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,23 @@ +name: linting + +on: + push: + branches: + - '*' + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: '1.22' + cache: false + - name: Install dependencies + run: go mod download + - name: Linting code + uses: golangci/golangci-lint-action@v4 + - name: Running tests + run: go test ./... diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..0f7b2bf --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,42 @@ +name: Release +on: + push: + branches: + - main + +permissions: + contents: read + +jobs: + release: + name: Release + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-go@v5 + with: + go-version: '1.22' + cache: false + - uses: go-semantic-release/action@v1 + id: rel + with: + hooks: goreleaser + changelog-file: CHANGELOG.md + changelog-generator-opt: "emojis=true" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} + - name: Build and push + if: steps.rel.outputs.version != '' + uses: docker/build-push-action@v5 + with: + push: true + tags: gportal/redfish_exporter:latest,gportal/redfish_exporter:${{ steps.rel.outputs.version }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f4cccd --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Ignore example config +config.example.yml +config.internal.yml +.idea + +# Distribution directory +/dist \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..79b83bb --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,16 @@ +--- + +run: + tests: true + +linters: + enable-all: true + disable: + - gochecknoglobals + - ireturn + - depguard + - exhaustivestruct + - exhaustruct + - exhaustive + - wsl + - gochecknoinits diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..057d196 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,2 @@ +builds: + - main: ./cmd/ diff --git a/.release.yml b/.release.yml new file mode 100644 index 0000000..e62bc0d --- /dev/null +++ b/.release.yml @@ -0,0 +1,12 @@ +commitFormat: angular +branch: + main: release +release: 'github' +github: + repo: "go-semantic-release" + user: "g-portal" +hooks: + preRelease: + - name: echo $RELEASE_VERSION + postRelease: + - name: echo $RELEASE_VERSION diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8f9bb43 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM gportal/golang:latest + +# Import application source +COPY ./ /opt/app-root/src + +# Change working directory +WORKDIR /opt/app-root/src + +# Build binary for Latency Service +RUN go build -v -o "${APP_ROOT}/redfish_exporter" cmd/main.go && \ + setcap cap_net_bind_service+ep "${APP_ROOT}/redfish_exporter" + +# Finally delete application source +RUN rm -rf /opt/app-root/src/* + +EXPOSE 9096 + +RUN /usr/bin/fix-permissions ${APP_ROOT} && \ + /usr/bin/fix-permissions /data/ + +CMD ["/opt/app-root/redfish_exporter"] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b6c6e64 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Ociris GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3178443 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Redfish Exporter + +Redfish Exporter for Prometheus + +### This is WIP. + +Redfish Exporter is providing generic metrics fetched from Redfish endpoints. + +Tested with: +* HPE ILO4 +* HPE ILO5 +* Dell iDRAC 8 +* Dell iDRAC 9 +* AsRockRack +* SuperMicro SuperServer diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..f2f2bd1 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "flag" + "log" + + "github.com/g-portal/redfish_exporter/pkg/config" + "github.com/g-portal/redfish_exporter/pkg/metric" + "github.com/gin-gonic/gin" +) + +var ( + listenAddr string + configPath string +) + +func init() { + flag.StringVar(&configPath, + "config.file", + "/etc/redfish_exporter/config.yml", + "Defines the path to the platform management config", + ) + flag.StringVar(&listenAddr, + "web.listen-address", + "0.0.0.0:9096", + "Address the exporter listens on", + ) + flag.Parse() + + config.SetPath(configPath) +} + +func main() { + r := gin.Default() + + r.GET("/metrics", metric.Handle) + log.Println("Starting listening on: " + listenAddr) + if err := r.Run(listenAddr); err != nil { + log.Printf("Error starting http server: %v", err) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..861c548 --- /dev/null +++ b/go.mod @@ -0,0 +1,44 @@ +module github.com/g-portal/redfish_exporter + +go 1.22 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/prometheus/client_golang v1.19.0 + github.com/stmcginnis/gofish v0.15.0 + gopkg.in/yaml.v2 v2.4.0 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/bytedance/sonic v1.11.3 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect + github.com/chenzhuoyu/iasm v0.9.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.19.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.0 // indirect + github.com/prometheus/client_model v0.6.0 // indirect + github.com/prometheus/common v0.51.1 // indirect + github.com/prometheus/procfs v0.13.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.7.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/net v0.22.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c8066cc --- /dev/null +++ b/go.sum @@ -0,0 +1,114 @@ +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/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= +github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= +github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +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/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= +github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= +github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +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/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= +github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +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/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= +github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +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.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= +github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= +github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= +github.com/prometheus/common v0.51.1 h1:eIjN50Bwglz6a/c3hAgSMcofL3nD+nFQkV6Dd4DsQCw= +github.com/prometheus/common v0.51.1/go.mod h1:lrWtQx+iDfn2mbH5GUzlH9TSHyfZpHkSiG1W7y3sF2Q= +github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o= +github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g= +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/stmcginnis/gofish v0.15.0 h1:8TG41+lvJk/0Nf8CIIYErxbMlQUy80W0JFRZP3Ld82A= +github.com/stmcginnis/gofish v0.15.0/go.mod h1:BLDSFTp8pDlf/xDbLZa+F7f7eW0E/CHCboggsu8CznI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +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/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= +golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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/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= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/pkg/api/api.go b/pkg/api/api.go new file mode 100644 index 0000000..8a84564 --- /dev/null +++ b/pkg/api/api.go @@ -0,0 +1,24 @@ +package api + +import ( + "fmt" + + "github.com/g-portal/redfish_exporter/pkg/drivers/redfish" + "github.com/prometheus/client_golang/prometheus" +) + +type Client interface { + Connect(host, username, password string, tlsVerify bool) error + GetMetrics() (*prometheus.Registry, error) + Disconnect() error +} + +func NewClient(host, username, password string, tlsVerify bool) (Client, error) { + client := &redfish.Redfish{} + err := client.Connect(host, username, password, tlsVerify) + if err != nil { + return nil, fmt.Errorf("error connecting to client: %w", err) + } + + return client, nil +} diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..2ada13f --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,41 @@ +package config + +import ( + "log" + "os" + + "gopkg.in/yaml.v2" +) + +var ( + configPath string + exporterConfig *Config +) + +func SetPath(opt string) { + configPath = opt + if _, err := os.Stat(configPath); os.IsNotExist(err) { + log.Fatalf("Error loading config file: %v", err) + } + exporterConfig = &Config{} + exporterConfig.reload() +} + +// reload Utility function for reloading the config file. +func (c *Config) reload() { + configContent, err := os.ReadFile(configPath) + if err != nil { + log.Fatalf("Unable to read file: %v", err) + } + + err = yaml.Unmarshal(configContent, c) + if err != nil { + log.Fatalf("Unable to parse config file: %v", err) + } + log.Println("Config file reloaded") +} + +// GetConfig Returns the current config instance. +func GetConfig() *Config { + return exporterConfig +} diff --git a/pkg/config/format.go b/pkg/config/format.go new file mode 100644 index 0000000..27b50a2 --- /dev/null +++ b/pkg/config/format.go @@ -0,0 +1,11 @@ +package config + +type Config struct { + Redfish struct { + // Username Default username for collecting metrics + Username string `yaml:"username"` + + // Password Default password for collecting metrics + Password string `yaml:"password"` + } `yaml:"redfish"` +} diff --git a/pkg/drivers/redfish/client.go b/pkg/drivers/redfish/client.go new file mode 100644 index 0000000..5600dd2 --- /dev/null +++ b/pkg/drivers/redfish/client.go @@ -0,0 +1,50 @@ +package redfish + +import ( + "fmt" + + "github.com/g-portal/redfish_exporter/pkg/drivers/redfish/metrics" + "github.com/prometheus/client_golang/prometheus" + "github.com/stmcginnis/gofish" +) + +type Redfish struct { + client *gofish.APIClient +} + +const TLSHandshakeTimeout = 30 + +func (rf *Redfish) Connect(host, username, password string, verifyTLS bool) error { + var err error + + cfg := gofish.ClientConfig{ + Endpoint: host, + Username: username, + Password: password, + Insecure: !verifyTLS, + TLSHandshakeTimeout: TLSHandshakeTimeout, + } + // Debug + + rf.client, err = gofish.Connect(cfg) + if err != nil { + return fmt.Errorf("error connecting to redfish: %w", err) + } + + return nil +} + +func (rf *Redfish) GetMetrics() (*prometheus.Registry, error) { + allMetrics := metrics.NewMetrics(rf.client) + if err := allMetrics.Collect(); err != nil { + return nil, fmt.Errorf("error collecting metrics: %w", err) + } + + return allMetrics.Registry(), nil +} + +func (rf *Redfish) Disconnect() error { + rf.client.Logout() + + return nil +} diff --git a/pkg/drivers/redfish/metrics/metrics.go b/pkg/drivers/redfish/metrics/metrics.go new file mode 100644 index 0000000..a573680 --- /dev/null +++ b/pkg/drivers/redfish/metrics/metrics.go @@ -0,0 +1,65 @@ +package metrics + +import ( + "fmt" + + "github.com/g-portal/redfish_exporter/pkg/metric/base" + "github.com/stmcginnis/gofish" + "github.com/stmcginnis/gofish/common" + "github.com/stmcginnis/gofish/redfish" +) + +type Metrics struct { + base.Metrics + + api *gofish.APIClient +} + +func NewMetrics(gofish *gofish.APIClient) *Metrics { + return &Metrics{ + api: gofish, + } +} + +func (m *Metrics) Collect() error { + service := m.api.GetService() + + systems, err := service.Systems() + if err != nil { + return fmt.Errorf("error getting systems: %w", err) + } + + for _, system := range systems { + m.With(base.WithRedfishHealthMetric(convertHealthStatus(system.Status.Health), map[string]string{ + "system_id": system.ODataID, + }), base.WithRedfishPowerStateMetric(convertPowerState(system.PowerState), map[string]string{ + "system_id": system.ODataID, + })) + } + + return nil +} + +func convertHealthStatus(status common.Health) base.RedfishHealthStatus { + switch status { + case common.OKHealth: + return base.RedfishHealthOK + case common.WarningHealth: + return base.RedfishHealthWarning + case common.CriticalHealth: + return base.RedfishHealthCritical + default: + return base.RedfishHealthWarning + } +} + +func convertPowerState(state redfish.PowerState) base.RedfishPowerStateType { + switch state { + case redfish.OnPowerState, redfish.PoweringOnPowerState: + return base.RedfishPowerStateON + case redfish.OffPowerState, redfish.PoweringOffPowerState: + return base.RedfishPowerStateOFF + default: + return base.RedfishPowerStateON + } +} diff --git a/pkg/metric/base/metric.go b/pkg/metric/base/metric.go new file mode 100644 index 0000000..ac6bc4e --- /dev/null +++ b/pkg/metric/base/metric.go @@ -0,0 +1,13 @@ +package base + +import ( + "github.com/prometheus/client_golang/prometheus" +) + +type MetricCollection map[ID]Metric + +type Metric interface { + GetID() ID + prometheus.Collector + prometheus.Metric +} diff --git a/pkg/metric/base/metric_gauges.go b/pkg/metric/base/metric_gauges.go new file mode 100644 index 0000000..baa7e53 --- /dev/null +++ b/pkg/metric/base/metric_gauges.go @@ -0,0 +1,13 @@ +package base + +import "github.com/prometheus/client_golang/prometheus" + +type GaugeMetric struct { + ID ID + + prometheus.Gauge +} + +func (g *GaugeMetric) GetID() ID { + return g.ID +} diff --git a/pkg/metric/base/metric_power_state.go b/pkg/metric/base/metric_power_state.go new file mode 100644 index 0000000..916fd7f --- /dev/null +++ b/pkg/metric/base/metric_power_state.go @@ -0,0 +1,32 @@ +package base + +import ( + "github.com/prometheus/client_golang/prometheus" +) + +const ( + RedfishPowerState ID = "redfish_power_state" +) + +var RedfishPowerStateLabels = []string{"system_id"} + +type RedfishPowerStateType float64 + +const ( + RedfishPowerStateON RedfishPowerStateType = 1 + RedfishPowerStateOFF RedfishPowerStateType = 2 +) + +func WithRedfishPowerStateMetric(state RedfishPowerStateType, labels prometheus.Labels) Metric { + metric := &GaugeMetric{ + ID: RedfishPowerState, + Gauge: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: string(RedfishPowerState), + Help: "Indicates the power state of the system. 1 = ON, 2 = OFF.", + }, RedfishPowerStateLabels).With(labels), + } + + metric.Set(float64(state)) + + return metric +} diff --git a/pkg/metric/base/metric_up.go b/pkg/metric/base/metric_up.go new file mode 100644 index 0000000..a5aaa3f --- /dev/null +++ b/pkg/metric/base/metric_up.go @@ -0,0 +1,33 @@ +package base + +import ( + "github.com/prometheus/client_golang/prometheus" +) + +const ( + RedfishHealth ID = "redfish_health" +) + +var RedfishHealthLabels = []string{"system_id"} + +type RedfishHealthStatus float64 + +const ( + RedfishHealthOK RedfishHealthStatus = 1 + RedfishHealthWarning RedfishHealthStatus = 2 + RedfishHealthCritical RedfishHealthStatus = 3 +) + +func WithRedfishHealthMetric(health RedfishHealthStatus, labels prometheus.Labels) Metric { + metric := &GaugeMetric{ + ID: RedfishHealth, + Gauge: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: string(RedfishHealth), + Help: "Indicates the health status of the system. 1 = OK, 2 = Warning, 3 = Critical.", + }, RedfishHealthLabels).With(labels), + } + + metric.Set(float64(health)) + + return metric +} diff --git a/pkg/metric/base/metrics.go b/pkg/metric/base/metrics.go new file mode 100644 index 0000000..771a621 --- /dev/null +++ b/pkg/metric/base/metrics.go @@ -0,0 +1,35 @@ +package base + +import ( + "log" + + "github.com/prometheus/client_golang/prometheus" +) + +type ID string + +type Metrics struct { + metrics MetricCollection +} + +func (b *Metrics) With(metrics ...Metric) { + if b.metrics == nil { + b.metrics = make(MetricCollection) + } + + for _, metric := range metrics { + b.metrics[metric.GetID()] = metric + } +} + +func (b *Metrics) Registry() *prometheus.Registry { + registry := prometheus.NewRegistry() + for id, collector := range b.metrics { + log.Printf("Registering metric %s\n", id) + + // debug log? + registry.MustRegister(collector) + } + + return registry +} diff --git a/pkg/metric/handler.go b/pkg/metric/handler.go new file mode 100644 index 0000000..eef270f --- /dev/null +++ b/pkg/metric/handler.go @@ -0,0 +1,67 @@ +package metric + +import ( + "log" + "net/http" + + "github.com/g-portal/redfish_exporter/pkg/api" + "github.com/g-portal/redfish_exporter/pkg/config" + "github.com/gin-gonic/gin" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +func Handle(ctx *gin.Context) { + params := extractCollectorParams(ctx.Request) + + client, err := api.NewClient(params.Host, params.Username, params.Password, false) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + + return + } + + log.Println("finished with handling") + defer func() { + err := client.Disconnect() + if err != nil { + log.Printf("error disconnecting: %v", err) + } + }() + + // Get metrics from the client. + registry, err := client.GetMetrics() + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + + return + } + + // Delegate http serving to Prometheus client library, which will call collector.Collect. + promhttp.HandlerFor(registry, promhttp.HandlerOpts{}).ServeHTTP(ctx.Writer, ctx.Request) +} + +type collectorParams struct { + Username string + Password string + Host string +} + +func extractCollectorParams(req *http.Request) collectorParams { + cfg := config.GetConfig() + params := collectorParams{ + Host: "", + Username: cfg.Redfish.Username, + Password: cfg.Redfish.Password, + } + if host := req.URL.Query().Get("host"); host != "" { + params.Host = host + } + if username := req.URL.Query().Get("username"); username != "" { + params.Username = username + } + if password := req.URL.Query().Get("password"); password != "" { + params.Password = password + } + + return params +}