From 8a7ebfefa1f49228c2651a4b381834abdbaa1dae Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Wed, 21 Jun 2023 23:29:41 +0200 Subject: [PATCH] feat: up version Signed-off-by: martabal <74269598+martabal@users.noreply.github.com> --- .dockerignore | 5 +- .env.example | 3 +- .github/workflows/build.yml | 17 ++ .github/workflows/codeql-analysis.yml | 34 ++++ .../workflows/{push_docker.yml => docker.yml} | 25 +-- .github/workflows/test.yml | 17 ++ Dockerfile | 11 +- README.md | 30 +++- go.mod | 10 +- go.sum | 7 + package.json | 2 +- src/immich/auth.go | 60 ------- src/immich/data.go | 54 ++---- src/immich/sendbackmessage.go | 8 +- src/init.go | 158 ++++++++++-------- src/models/api.go | 64 +++++++ src/models/app.go | 27 +++ src/models/error.go | 11 -- src/models/immich.go | 68 ++------ src/models/models.go | 47 ------ 20 files changed, 336 insertions(+), 322 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/codeql-analysis.yml rename .github/workflows/{push_docker.yml => docker.yml} (59%) create mode 100644 .github/workflows/test.yml delete mode 100644 src/immich/auth.go create mode 100644 src/models/api.go create mode 100644 src/models/app.go delete mode 100644 src/models/error.go delete mode 100644 src/models/models.go diff --git a/.dockerignore b/.dockerignore index d22e30d..7828809 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,7 @@ img/ grafana/ .github/ -Dockerfile \ No newline at end of file +Dockerfile +README.md +.env* +.gitignore \ No newline at end of file diff --git a/.env.example b/.env.example index 755743f..c153499 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,2 @@ -IMMICH_USERNAME= -IMMICH_PASSWORD= +IMMICH_API_KEY= IMMICH_BASE_URL= \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..5539eb9 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,17 @@ +name: Build +on: + workflow_dispatch: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] +jobs: + + build: + name: Run build + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Build + run: go build -o ./immich.out ./src \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..d052439 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,34 @@ +name: CodeQL + +on: + workflow_dispatch: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + + steps: + - name: Checkout repo + uses: actions/checkout@v2 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + + - name: CodeQL Analysis + uses: github/codeql-action/analyze@v2 \ No newline at end of file diff --git a/.github/workflows/push_docker.yml b/.github/workflows/docker.yml similarity index 59% rename from .github/workflows/push_docker.yml rename to .github/workflows/docker.yml index d535850..2c506cf 100644 --- a/.github/workflows/push_docker.yml +++ b/.github/workflows/docker.yml @@ -8,9 +8,7 @@ on: type: string jobs: - - - build_push_monolith: + build_docker_release: runs-on: ubuntu-latest steps: - name: Checkout @@ -19,29 +17,34 @@ jobs: ref: 'main' fetch-depth: 0 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v2.1.0 + uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v2.2.1 + uses: docker/setup-buildx-action@v2 - name: Login to Docker Hub uses: docker/login-action@v2 with: username: martabal password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GH_TOKEN }} - - name: Build and push immich - uses: docker/build-push-action@v3.2.0 + - name: Build and push + uses: docker/build-push-action@v4 with: context: ./ - file: ./Dockerfile platforms: linux/arm/v7,linux/amd64,linux/arm64 push: ${{ github.event_name != 'pull_request' }} tags: | martabal/immich-exporter:${{ inputs.tags }} martabal/immich-exporter:latest - + ghcr.io/${{ github.repository_owner }}/immich-exporter:${{ inputs.tags }} + ghcr.io/${{ github.repository_owner }}/immich-exporter:latest \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..7f73fbd --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,17 @@ +name: Test +on: + workflow_dispatch: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] +jobs: + + test: + name: Run tests + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Run formatter + run: test -z $(gofmt -l ./src) \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 96dd709..d611b63 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,17 @@ -FROM golang:1.19-alpine3.17 AS builder +FROM golang:1.20-alpine3.18 AS builder WORKDIR /app COPY . . -RUN go get -d -v ./src/ && \ - go build -o /go/bin/immich-exporter ./src +RUN go build -o /go/bin/immich-exporter ./src -FROM alpine:3.17 + +FROM alpine:3.18 COPY --from=builder /go/bin/immich-exporter /go/bin/immich-exporter COPY package.json /go/bin/ WORKDIR /go/bin -CMD ["/go/bin/immich-exporter"] + +CMD ["/go/bin/immich-exporter"] \ No newline at end of file diff --git a/README.md b/README.md index 9eea173..eeaee4d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # immich-exporter -[![Publish Release](https://github.com/martabal/immich-exporter/actions/workflows/push_docker.yml/badge.svg)](https://github.com/martabal/immich-exporter/actions/workflows/push_docker.yml) +[![Publish Release](https://github.com/martabal/immich-exporter/actions/workflows/docker.yml/badge.svg)](https://github.com/martabal/immich-exporter/actions/workflows/docker.yml) +[![Build](https://github.com/martabal/immich-exporter/actions/workflows/build.yml/badge.svg)](https://github.com/martabal/immich-exporter/actions/workflows/build.yml) +[![Test](https://github.com/martabal/immich-exporter/actions/workflows/test.yml/badge.svg)](https://github.com/martabal/immich-exporter/actions/workflows/test.yml)

  @@ -11,13 +13,14 @@ This app is made to be integrated with the [immich-grafana-dashboard](https://gi ## Run it +Create an API key in your Immich settings and set `IMMICH_API_KEY` to is value. + ### Docker-cli ([click here for more info](https://docs.docker.com/engine/reference/commandline/cli/)) ```sh docker run --name=immich-exporter \ -e IMMICH_URL=http://192.168.1.10:8080 \ - -e IMMICH_PASSWORD='' \ - -e IMMICH_USERNAME=admin \ + -e IMMICH_API_KEY= \ -p 8090:8090 \ martabal/immich-exporter ``` @@ -32,8 +35,7 @@ services: container_name: immich-exporter environment: - IMMICH_URL=http://192.168.1.10:8080 - - IMMICH_PASSWORD='' - - IMMICH_USERNAME=admin + - IMMICH_API_KEY= ports: - 8090:8090 restart: unless-stopped @@ -63,12 +65,24 @@ If you want to use an .env file, edit `.env.example` to match your setup, rename | Parameters | Function | | :-----: | ----- | | `-p 8090` | Webservice port | -| `-e IMMICH_USERNAME` | Immich username | -| `-e IMMICH_PASSWORD` | Immich password | | `-e IMMICH_BASE_URL` | Immich base URL | +| `-e IMMICH_API_KEY` | Immich API key | +| `-e EXPORTER_PORT` | qbittorrent export port (optional) | `8090` | +| `-e LOG_LEVEL` | App log level (`TRACE`, `DEBUG`, `INFO`, `WARN` and `ERROR`) | `INFO` | ### Arguments | Arguments | Function | | :-----: | ----- | -| -e | Use a .env file containing environment variables (.env file must be placed in the same directory) | +| -e | If qbittorrent-exporter detects a .env file in the same directory, the values in the .env will be used, `-e` forces the usage of environment variables | + +### Setup + +Add the target to your `scrape_configs` in your `prometheus.yml` file of your Prometheus instance. + +```yaml +scrape_configs: + - job_name: 'immich' + static_configs: + - targets: [ ':8090' ] +``` diff --git a/go.mod b/go.mod index c50fe88..3e94810 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,19 @@ -module immich-exporter +module immich-exp go 1.20 -require github.com/joho/godotenv v1.5.1 +require ( + github.com/joho/godotenv v1.5.1 + github.com/prometheus/client_golang v1.14.0 + github.com/sirupsen/logrus v1.6.0 +) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect diff --git a/go.sum b/go.sum index 906d70e..94823d4 100644 --- a/go.sum +++ b/go.sum @@ -52,6 +52,7 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -109,6 +110,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -138,6 +140,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -155,6 +158,7 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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 v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -185,11 +189,13 @@ github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0ua github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -465,6 +471,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/package.json b/package.json index 229dbf9..c9585c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "immich-exporter", - "version": "0.1.2", + "version": "1.0.0", "description": "exporter for immich", "main": "src/main.go", "scripts": { diff --git a/src/immich/auth.go b/src/immich/auth.go deleted file mode 100644 index ef9e221..0000000 --- a/src/immich/auth.go +++ /dev/null @@ -1,60 +0,0 @@ -package immich - -import ( - "encoding/json" - "fmt" - "immich-exporter/src/models" - "io/ioutil" - "log" - "net/http" - "strings" -) - -func Auth() { - - url := models.GetURL() + "/api/auth/login" - method := "POST" - - payload := strings.NewReader(`{ - "email": "` + models.GetUsername() + `", - "password": "` + models.Getpassword() + `"}`) - - client := &http.Client{} - req, err := http.NewRequest(method, url, payload) - - if err != nil { - fmt.Println(err) - return - } else { - req.Header.Add("Content-Type", "application/json") - req.Header.Add("Accept", "application/json") - - res, err := client.Do(req) - if err != nil { - log.Println(err) - return - } else { - defer res.Body.Close() - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - log.Println(err) - return - } - - if res.StatusCode == 400 { - log.Fatalln("Incorrect login") - } else { - var result models.StructLogin - if err := json.Unmarshal(body, &result); err != nil { - log.Println("Can not unmarshal JSON") - } - - models.SetAccessToken(result.AccessToken) - } - - } - - } - -} diff --git a/src/immich/data.go b/src/immich/data.go index dc4640f..35420e4 100644 --- a/src/immich/data.go +++ b/src/immich/data.go @@ -3,7 +3,7 @@ package immich import ( "encoding/json" "fmt" - "immich-exporter/src/models" + "immich-exp/src/models" "io/ioutil" "log" "net/http" @@ -25,9 +25,10 @@ func Allrequests(r *prometheus.Registry) { func Analyze(r *prometheus.Registry) { defer wg.Done() - allusers := make(chan func() (*models.StructAllUsers, error)) + allusers := make(chan func() (*models.StructAllUsers, error)) serverinfo := make(chan func() (*models.StructServerInfo, error)) + wg.Add(1) go GetAllUsers(allusers) res1, err := (<-allusers)() @@ -47,16 +48,7 @@ func Analyze(r *prometheus.Registry) { func GetAllUsers(c chan func() (*models.StructAllUsers, error)) { defer wg.Done() resp, err := Apirequest("/api/user?isAll=true", "GET") - if err != nil { - if err.Error() == "403" { - log.Println("Cookie changed, try to reconnect ...") - Auth() - } else { - if models.GetPromptError() == false { - log.Println("Error : ", err) - } - } - } else { + if err == nil { if models.GetPromptError() == true { models.SetPromptError(false) } @@ -71,25 +63,14 @@ func GetAllUsers(c chan func() (*models.StructAllUsers, error)) { } c <- (func() (*models.StructAllUsers, error) { return result, nil }) - } } - } func ServerVersion(r *prometheus.Registry) { defer wg.Done() resp, err := Apirequest("/api/server-info/version", "GET") - if err != nil { - if err.Error() == "403" { - log.Println("Cookie changed, try to reconnect ...") - Auth() - } else { - if models.GetPromptError() == false { - log.Println("Error : ", err) - } - } - } else { + if err == nil { if models.GetPromptError() == true { models.SetPromptError(false) } @@ -100,14 +81,12 @@ func ServerVersion(r *prometheus.Registry) { var result models.StructServerVersion if err := json.Unmarshal(body, &result); err != nil { // Parse []byte to go struct pointer - log.Println("Can not unmarshal JSON") + log.Println("Can not unmarshal JSON for version") } SendBackMessageserverVersion(&result, r) - } } - } func ServerInfo(c chan func() (*models.StructServerInfo, error)) { @@ -116,7 +95,6 @@ func ServerInfo(c chan func() (*models.StructServerInfo, error)) { if err != nil { if err.Error() == "403" { log.Println("Cookie changed, try to reconnect ...") - Auth() } else { if models.GetPromptError() == false { log.Println("Error : ", err) @@ -134,23 +112,22 @@ func ServerInfo(c chan func() (*models.StructServerInfo, error)) { result := new(models.StructServerInfo) if err := json.Unmarshal(body, &result); err != nil { // Parse []byte to go struct pointer - log.Println("Can not unmarshal JSON") + log.Println("Can not unmarshal JSON for server infos") } c <- (func() (*models.StructServerInfo, error) { return result, nil }) } } - } func Apirequest(uri string, method string) (*http.Response, error) { - req, err := http.NewRequest(method, models.GetURL()+uri, nil) + req, err := http.NewRequest(method, models.Getbaseurl()+uri, nil) if err != nil { log.Fatalln("Error with url") } - - req.AddCookie(&http.Cookie{Name: "immich_access_token", Value: models.GetAccessToken()}) + req.Header.Add("Accept", "application/json") + req.Header.Add("x-api-key", models.GetApiKey()) client := &http.Client{} resp, err := client.Do(req) if err != nil { @@ -164,23 +141,16 @@ func Apirequest(uri string, method string) (*http.Response, error) { return resp, err } else { - models.SetPromptError(false) if resp.StatusCode == 200 { - + models.SetPromptError(false) return resp, nil - } else { err := fmt.Errorf("%d", resp.StatusCode) if models.GetPromptError() == false { models.SetPromptError(true) - - log.Println("Error code", err.Error()) - + log.Println("Error code", err.Error(), " for ", models.Getbaseurl()+uri) } return resp, err - } - } - } diff --git a/src/immich/sendbackmessage.go b/src/immich/sendbackmessage.go index a90d81c..65c869e 100644 --- a/src/immich/sendbackmessage.go +++ b/src/immich/sendbackmessage.go @@ -1,7 +1,7 @@ package immich import ( - "immich-exporter/src/models" + "immich-exp/src/models" "strconv" "github.com/prometheus/client_golang/prometheus" @@ -52,14 +52,14 @@ func SendBackMessagePreference(result *models.StructServerInfo, result2 *models. r.MustRegister(user_photos) total_photos.Add(float64((*result).Photos)) total_videos.Add(float64((*result).Videos)) - total_usage.Add(float64((*result).UsageRaw)) + total_usage.Add(float64((*result).Usage)) total_users.Add(float64(len((*result).UsageByUser))) for i := 0; i < len((*result).UsageByUser); i++ { var myuser = GetName((*result).UsageByUser[i].UserID, result2) - user_info.With(prometheus.Labels{"videos": strconv.Itoa((*result).UsageByUser[i].Videos), "photos": strconv.Itoa((*result).UsageByUser[i].Photos), "uid": (*result).UsageByUser[i].UserID, "usage": strconv.Itoa(int((*result).UsageByUser[i].UsageRaw)), "firstname": myuser.FirstName, "lastname": myuser.LastName}).Inc() + user_info.With(prometheus.Labels{"videos": strconv.Itoa((*result).UsageByUser[i].Videos), "photos": strconv.Itoa((*result).UsageByUser[i].Photos), "uid": (*result).UsageByUser[i].UserID, "usage": strconv.Itoa(int((*result).UsageByUser[i].Usage)), "firstname": myuser.FirstName, "lastname": myuser.LastName}).Inc() user_photos.With(prometheus.Labels{"uid": (*result).UsageByUser[i].UserID, "firstname": myuser.FirstName, "lastname": myuser.LastName}).Set(float64((*result).UsageByUser[i].Photos)) - user_usage.With(prometheus.Labels{"uid": (*result).UsageByUser[i].UserID, "firstname": myuser.FirstName, "lastname": myuser.LastName}).Set(float64((*result).UsageByUser[i].UsageRaw)) + user_usage.With(prometheus.Labels{"uid": (*result).UsageByUser[i].UserID, "firstname": myuser.FirstName, "lastname": myuser.LastName}).Set(float64((*result).UsageByUser[i].Usage)) user_videos.With(prometheus.Labels{"uid": (*result).UsageByUser[i].UserID, "firstname": myuser.FirstName, "lastname": myuser.LastName}).Set(float64((*result).UsageByUser[i].Videos)) } diff --git a/src/init.go b/src/init.go index c8a4e72..bca5e4f 100644 --- a/src/init.go +++ b/src/init.go @@ -3,31 +3,39 @@ package main import ( "encoding/json" "flag" - "immich-exporter/src/immich" - "immich-exporter/src/models" - "io/ioutil" - "net/http" + "fmt" + "immich-exp/src/immich" + "immich-exp/src/models" + + "net/http" + "strconv" + "strings" - "log" "os" "github.com/joho/godotenv" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" + log "github.com/sirupsen/logrus" ) -func main() { - startup() - log.Println("Immich URL :", models.GetURL()) - log.Println("username :", models.GetUsername()) - log.Println("password :", models.Getpasswordmasked()) - log.Println("Started") +const DEFAULTPORT = 8090 +func main() { + loadenv() + projectinfo() + log.Info("Immich URL: ", models.Getbaseurl()) + log.Info("Started") http.HandleFunc("/metrics", metrics) - http.ListenAndServe(":8090", nil) + addr := ":" + strconv.Itoa(models.GetPort()) + if models.GetPort() != DEFAULTPORT { + log.Info("Listening on port", models.GetPort()) + } + http.ListenAndServe(addr, nil) } func metrics(w http.ResponseWriter, r *http.Request) { + log.Trace("New request") registry := prometheus.NewRegistry() immich.Allrequests(registry) h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{}) @@ -35,80 +43,86 @@ func metrics(w http.ResponseWriter, r *http.Request) { } -func startup() { - projectinfo() - var envfile bool - - flag.BoolVar(&envfile, "e", false, "Use .env file") - flag.Parse() - if envfile { - useenvfile() - } else { - initenv() - } - - immich.Auth() - -} - func projectinfo() { - fileContent, err := os.Open("./package.json") - + fileContent, err := os.ReadFile("./package.json") if err != nil { log.Fatal(err) return } - defer fileContent.Close() - - byteResult, _ := ioutil.ReadAll(fileContent) - var res map[string]interface{} - json.Unmarshal([]byte(byteResult), &res) - log.Println("Author :", res["author"]) - log.Println(res["name"], "version", res["version"]) + err = json.Unmarshal(fileContent, &res) + if err != nil { + log.Fatal(err) + return + } + + fmt.Print(res["name"], " (version ", res["version"], ")\n") + fmt.Print("Author: ", res["author"], "\n") + fmt.Print("Using log level: ", log.GetLevel(), "\n") } -func useenvfile() { - myEnv, err := godotenv.Read() +func loadenv() { + var envfile bool + flag.BoolVar(&envfile, "e", false, "Use .env file") + flag.Parse() + _, err := os.Stat(".env") + if !os.IsNotExist(err) && !envfile { + err := godotenv.Load(".env") + if err != nil { + log.Panic("Error loading .env file:", err) + } + // fmt.Println("Using .env file") + } - username := myEnv["IMMICH_USERNAME"] - password := myEnv["IMMICH_PASSWORD"] - immich_url := myEnv["IMMICH_BASE_URL"] - if myEnv["IMMICH_USERNAME"] == "" { - log.Panic("Immich username is not set.") - } - if myEnv["IMMICH_PASSWORD"] == "" { - log.Panic("Immich password is not set.") - } - if myEnv["IMMICH_BASE_URL"] == "" { - log.Panic("IMMICH base_url is not set.") - } - models.Setuser(username, password, immich_url) + immichapikey := getEnv("IMMICH_API_KEY", "", false, "Immich API Key is not set", true) + immichURL := getEnv("IMMICH_BASE_URL", "http://localhost:8080", true, "Qbittorrent base_url is not set. Using default base_url", false) + exporterPort := getEnv("EXPORTER_PORT", strconv.Itoa(DEFAULTPORT), false, "", false) + + num, err := strconv.Atoi(exporterPort) if err != nil { - log.Fatal("Error loading .env file") + log.Panic("EXPORTER_PORT must be an integer") } - log.Println("Using .env file") + if num < 0 || num > 65353 { + log.Panic("EXPORTER_PORT must be > 0 and < 65353") + } + + setLogLevel(getEnv("LOG_LEVEL", "INFO", false, "", false)) + models.SetApp(num, false) + models.Setuser(immichURL, immichapikey) +} +func setLogLevel(logLevel string) { + logLevels := map[string]log.Level{ + "TRACE": log.TraceLevel, + "DEBUG": log.DebugLevel, + "INFO": log.InfoLevel, + "WARN": log.WarnLevel, + "ERROR": log.ErrorLevel, + } + + level, found := logLevels[strings.ToUpper(logLevel)] + if !found { + level = log.InfoLevel + } + + log.SetLevel(level) + log.SetFormatter(&log.TextFormatter{ + DisableColors: false, + FullTimestamp: true, + }) } -func initenv() { - - username := os.Getenv("IMMICH_USERNAME") - password := os.Getenv("IMMICH_PASSWORD") - immich_url := os.Getenv("IMMICH_BASE_URL") - if os.Getenv("IMMICH_USERNAME") == "" { - log.Panic("Immich username is not set.") - +func getEnv(key string, fallback string, printLog bool, logPrinted string, needed bool) string { + value, ok := os.LookupEnv(key) + if !ok || value == "" { + if needed { + log.Panicln("Please set a value for", key) + } + if printLog { + log.Warn(logPrinted) + } + return fallback } - if os.Getenv("IMMICH_PASSWORD") == "" { - log.Panic("Immich password is not set.") - - } - if os.Getenv("IMMICH_BASE_URL") == "" { - log.Panic("Immich base_url is not set.") - - } - models.Setuser(username, password, immich_url) - + return value } diff --git a/src/models/api.go b/src/models/api.go new file mode 100644 index 0000000..1dc0047 --- /dev/null +++ b/src/models/api.go @@ -0,0 +1,64 @@ +package models + +import "time" + +type StructLogin struct { + AccessToken string `json:"accessToken"` + UserID string `json:"userId"` + UserEmail string `json:"userEmail"` + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + IsAdmin bool `json:"isAdmin"` + ShouldChangePassword bool `json:"shouldChangePassword"` +} + +type StructServerInfo struct { + Photos int `json:"photos"` + Videos int `json:"videos"` + Usage int64 `json:"usage"` + UsageByUser []struct { + UserID string `json:"userId"` + UserFirstName string `json:"userFirstName"` + UserLastName string `json:"userLastName"` + Photos int `json:"photos"` + Videos int `json:"videos"` + Usage int `json:"usage"` + } `json:"usageByUser"` +} + +type StructDiskInfo struct { + DiskAvailable string `json:"diskAvailable"` + DiskSize string `json:"diskSize"` + DiskUse string `json:"diskUse"` + DiskAvailableRaw int64 `json:"diskAvailableRaw"` + DiskSizeRaw int64 `json:"diskSizeRaw"` + DiskUseRaw int64 `json:"diskUseRaw"` + DiskUsagePercentage float64 `json:"diskUsagePercentage"` +} + +type StructServerVersion struct { + Major int `json:"major"` + Minor int `json:"minor"` + Patch int `json:"patch"` +} + +type StructAllUsers []struct { + ID string `json:"id"` + Email string `json:"email"` + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + CreatedAt time.Time `json:"createdAt"` + ProfileImagePath string `json:"profileImagePath"` + ShouldChangePassword bool `json:"shouldChangePassword"` + IsAdmin bool `json:"isAdmin"` + DeletedAt time.Time `json:"deletedAt"` + OauthID string `json:"oauthId"` +} + +type StructCustomUser struct { + Email string + ID string + FirstName string + LastName string + IsAdmin bool +} diff --git a/src/models/app.go b/src/models/app.go new file mode 100644 index 0000000..bc9139a --- /dev/null +++ b/src/models/app.go @@ -0,0 +1,27 @@ +package models + +type TypeAppConfig struct { + Port int + Error bool +} + +var AppConfig TypeAppConfig + +func SetApp(setport int, seterror bool) { + AppConfig = TypeAppConfig{ + Port: setport, + Error: seterror, + } +} + +func GetPort() int { + return AppConfig.Port +} + +func SetPromptError(prompt bool) { + AppConfig.Error = prompt +} + +func GetPromptError() bool { + return AppConfig.Error +} diff --git a/src/models/error.go b/src/models/error.go deleted file mode 100644 index f60f316..0000000 --- a/src/models/error.go +++ /dev/null @@ -1,11 +0,0 @@ -package models - -var myerr bool - -func SetPromptError(prompt bool) { - myerr = prompt -} - -func GetPromptError() bool { - return myerr -} diff --git a/src/models/immich.go b/src/models/immich.go index 2b1a329..802d064 100644 --- a/src/models/immich.go +++ b/src/models/immich.go @@ -1,64 +1,22 @@ package models -import "time" - -type StructLogin struct { - AccessToken string `json:"accessToken"` - UserID string `json:"userId"` - UserEmail string `json:"userEmail"` - FirstName string `json:"firstName"` - LastName string `json:"lastName"` - IsAdmin bool `json:"isAdmin"` - ShouldChangePassword bool `json:"shouldChangePassword"` +type StructImmich struct { + APIKey string + URL string } -type StructServerInfo struct { - Photos int `json:"photos"` - Videos int `json:"videos"` - UsageByUser []struct { - UserID string `json:"userId"` - Videos int `json:"videos"` - Photos int `json:"photos"` - UsageRaw int64 `json:"usageRaw"` - Usage string `json:"usage"` - } `json:"usageByUser"` - UsageRaw int64 `json:"usageRaw"` - Usage string `json:"usage"` +var myuser StructImmich + +func Setuser(url string, apikey string) { + myuser.URL = url + myuser.APIKey = apikey + } -type StructDiskInfo struct { - DiskAvailable string `json:"diskAvailable"` - DiskSize string `json:"diskSize"` - DiskUse string `json:"diskUse"` - DiskAvailableRaw int64 `json:"diskAvailableRaw"` - DiskSizeRaw int64 `json:"diskSizeRaw"` - DiskUseRaw int64 `json:"diskUseRaw"` - DiskUsagePercentage float64 `json:"diskUsagePercentage"` +func Getbaseurl() string { + return myuser.URL } -type StructServerVersion struct { - Major int `json:"major"` - Minor int `json:"minor"` - Patch int `json:"patch"` -} - -type StructAllUsers []struct { - ID string `json:"id"` - Email string `json:"email"` - FirstName string `json:"firstName"` - LastName string `json:"lastName"` - CreatedAt time.Time `json:"createdAt"` - ProfileImagePath string `json:"profileImagePath"` - ShouldChangePassword bool `json:"shouldChangePassword"` - IsAdmin bool `json:"isAdmin"` - DeletedAt time.Time `json:"deletedAt"` - OauthID string `json:"oauthId"` -} - -type StructCustomUser struct { - Email string - ID string - FirstName string - LastName string - IsAdmin bool +func GetApiKey() string { + return myuser.APIKey } diff --git a/src/models/models.go b/src/models/models.go deleted file mode 100644 index 3a5d47b..0000000 --- a/src/models/models.go +++ /dev/null @@ -1,47 +0,0 @@ -package models - -type StructImmich struct { - Username string - Password string - URL string - AccessToken string -} - -var myuser StructImmich - -func Getuser() (string, string) { - return myuser.Username, myuser.Password -} - -func Setuser(username string, password string, url string) { - myuser.Username = username - myuser.Password = password - myuser.URL = url -} - -func GetUsername() string { - return myuser.Username -} - -func Getpassword() string { - return myuser.Password -} -func Getpasswordmasked() string { - hide := "" - for i := 0; i < len(myuser.Password); i++ { - hide += "*" - } - return hide -} - -func SetAccessToken(accessToken string) { - myuser.AccessToken = accessToken -} - -func GetAccessToken() string { - return myuser.AccessToken -} - -func GetURL() string { - return myuser.URL -}