forked from Silverfish/proton-bridge
chore: merge branch release/stone to devel
This commit is contained in:
348
.gitlab-ci.yml
348
.gitlab-ci.yml
@ -30,13 +30,6 @@ stages:
|
|||||||
- test
|
- test
|
||||||
- build
|
- build
|
||||||
|
|
||||||
.rules-branch-and-MR-always:
|
|
||||||
rules:
|
|
||||||
- if: $CI_COMMIT_BRANCH || $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
||||||
when: always
|
|
||||||
allow_failure: false
|
|
||||||
- when: never
|
|
||||||
|
|
||||||
.rules-branch-and-MR-manual:
|
.rules-branch-and-MR-manual:
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_COMMIT_BRANCH || $CI_PIPELINE_SOURCE == "merge_request_event"
|
- if: $CI_COMMIT_BRANCH || $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
@ -44,16 +37,6 @@ stages:
|
|||||||
allow_failure: true
|
allow_failure: true
|
||||||
- when: never
|
- when: never
|
||||||
|
|
||||||
.rules-branch-manual-MR-always:
|
|
||||||
rules:
|
|
||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
||||||
when: always
|
|
||||||
allow_failure: false
|
|
||||||
- if: $CI_COMMIT_BRANCH
|
|
||||||
when: manual
|
|
||||||
allow_failure: true
|
|
||||||
- when: never
|
|
||||||
|
|
||||||
.rules-branch-manual-MR-and-devel-always:
|
.rules-branch-manual-MR-and-devel-always:
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_COMMIT_BRANCH == "devel" || $CI_PIPELINE_SOURCE == "merge_request_event"
|
- if: $CI_COMMIT_BRANCH == "devel" || $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
@ -64,172 +47,8 @@ stages:
|
|||||||
allow_failure: true
|
allow_failure: true
|
||||||
- when: never
|
- when: never
|
||||||
|
|
||||||
.after-script-code-coverage:
|
# ENV
|
||||||
after_script:
|
.env-windows:
|
||||||
- go get github.com/boumenot/gocover-cobertura
|
|
||||||
- go run github.com/boumenot/gocover-cobertura < /tmp/coverage.out > coverage.xml
|
|
||||||
- "go tool cover -func=/tmp/coverage.out | grep total:"
|
|
||||||
coverage: '/total:.*\(statements\).*\d+\.\d+%/'
|
|
||||||
artifacts:
|
|
||||||
reports:
|
|
||||||
coverage_report:
|
|
||||||
coverage_format: cobertura
|
|
||||||
path: coverage.xml
|
|
||||||
|
|
||||||
# Stage: TEST
|
|
||||||
|
|
||||||
lint:
|
|
||||||
stage: test
|
|
||||||
extends:
|
|
||||||
- .rules-branch-and-MR-always
|
|
||||||
script:
|
|
||||||
- make lint
|
|
||||||
tags:
|
|
||||||
- medium
|
|
||||||
|
|
||||||
|
|
||||||
.test-base:
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- make test
|
|
||||||
|
|
||||||
test-linux:
|
|
||||||
extends:
|
|
||||||
- .test-base
|
|
||||||
- .rules-branch-manual-MR-and-devel-always
|
|
||||||
- .after-script-code-coverage
|
|
||||||
tags:
|
|
||||||
- large
|
|
||||||
|
|
||||||
test-linux-race:
|
|
||||||
extends:
|
|
||||||
- test-linux
|
|
||||||
- .rules-branch-and-MR-manual
|
|
||||||
script:
|
|
||||||
- make test-race
|
|
||||||
|
|
||||||
test-integration:
|
|
||||||
extends:
|
|
||||||
- test-linux
|
|
||||||
script:
|
|
||||||
- make test-integration
|
|
||||||
tags:
|
|
||||||
- large
|
|
||||||
|
|
||||||
test-integration-race:
|
|
||||||
extends:
|
|
||||||
- test-integration
|
|
||||||
- .rules-branch-and-MR-manual
|
|
||||||
script:
|
|
||||||
- make test-integration-race
|
|
||||||
|
|
||||||
|
|
||||||
.windows-base:
|
|
||||||
before_script:
|
|
||||||
- export GOROOT=/c/Go1.20
|
|
||||||
- export PATH=$GOROOT/bin:$PATH
|
|
||||||
- export GOARCH=amd64
|
|
||||||
- export GOPATH=~/go1.20
|
|
||||||
- export GO111MODULE=on
|
|
||||||
- export PATH=$GOPATH/bin:$PATH
|
|
||||||
- export MSYSTEM=
|
|
||||||
tags:
|
|
||||||
- windows-bridge
|
|
||||||
|
|
||||||
test-windows:
|
|
||||||
extends:
|
|
||||||
- .rules-branch-manual-MR-always
|
|
||||||
- .windows-base
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- make test
|
|
||||||
|
|
||||||
# Stage: BUILD
|
|
||||||
|
|
||||||
.build-base:
|
|
||||||
stage: build
|
|
||||||
needs: ["lint"]
|
|
||||||
rules:
|
|
||||||
# GODT-1833: use `=~ /qa/` after mac and windows runners are fixed
|
|
||||||
- if: $CI_JOB_NAME =~ /build-linux-qa/ && $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
||||||
when: always
|
|
||||||
allow_failure: false
|
|
||||||
- if: $CI_COMMIT_BRANCH || $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
||||||
when: manual
|
|
||||||
allow_failure: true
|
|
||||||
- when: never
|
|
||||||
script:
|
|
||||||
- make build
|
|
||||||
- git diff && git diff-index --quiet HEAD
|
|
||||||
- make vault-editor
|
|
||||||
artifacts:
|
|
||||||
expire_in: 1 day
|
|
||||||
when: always
|
|
||||||
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
|
||||||
paths:
|
|
||||||
- bridge_*.tgz
|
|
||||||
- vault-editor
|
|
||||||
|
|
||||||
|
|
||||||
.linux-build-setup:
|
|
||||||
image: gitlab.protontech.ch:4567/go/bridge-internal:build-go1.20-qt6.3.2
|
|
||||||
variables:
|
|
||||||
VCPKG_DEFAULT_BINARY_CACHE: ${CI_PROJECT_DIR}/.cache
|
|
||||||
cache:
|
|
||||||
key: linux-vcpkg
|
|
||||||
paths:
|
|
||||||
- .cache
|
|
||||||
when: 'always'
|
|
||||||
before_script:
|
|
||||||
- mkdir -p .cache/bin
|
|
||||||
- export PATH=$(pwd)/.cache/bin:$PATH
|
|
||||||
- export GOPATH="$CI_PROJECT_DIR/.cache"
|
|
||||||
- export PATH=$PATH:$QT6DIR/bin
|
|
||||||
- $(git config --global -l | grep -o 'url.*gitlab.protontech.ch.*insteadof' | xargs -L 1 git config --global --unset &> /dev/null) || echo "nothing to remove"
|
|
||||||
- git config --global url.https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}.insteadOf https://${CI_SERVER_HOST}
|
|
||||||
tags:
|
|
||||||
- large
|
|
||||||
|
|
||||||
build-linux:
|
|
||||||
extends:
|
|
||||||
- .build-base
|
|
||||||
- .linux-build-setup
|
|
||||||
|
|
||||||
build-linux-qa:
|
|
||||||
extends:
|
|
||||||
- build-linux
|
|
||||||
variables:
|
|
||||||
BUILD_TAGS: "build_qa"
|
|
||||||
|
|
||||||
|
|
||||||
.darwin-build-setup:
|
|
||||||
before_script:
|
|
||||||
- export PATH=/usr/local/bin:$PATH
|
|
||||||
- export PATH=/usr/local/opt/git/bin:$PATH
|
|
||||||
- export PATH=/usr/local/opt/make/libexec/gnubin:$PATH
|
|
||||||
- export PATH=/usr/local/opt/go@1.13/bin:$PATH
|
|
||||||
- export PATH=/usr/local/opt/gnu-sed/libexec/gnubin:$PATH
|
|
||||||
- export GOPATH=~/go1.20
|
|
||||||
- export PATH=$GOPATH/bin:$PATH
|
|
||||||
- export CGO_CPPFLAGS='-Wno-error -Wno-nullability-completeness -Wno-expansion-to-defined -Wno-builtin-requires-header'
|
|
||||||
- $(git config --global -l | grep -o 'url.*gitlab.protontech.ch.*insteadof' | xargs -L 1 git config --global --unset &> /dev/null) || echo "nothing to remove"
|
|
||||||
- git config --global url.https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}.insteadOf https://${CI_SERVER_HOST}
|
|
||||||
cache: {}
|
|
||||||
tags:
|
|
||||||
- macOS
|
|
||||||
|
|
||||||
build-darwin:
|
|
||||||
extends:
|
|
||||||
- .build-base
|
|
||||||
- .darwin-build-setup
|
|
||||||
|
|
||||||
build-darwin-qa:
|
|
||||||
extends:
|
|
||||||
- build-darwin
|
|
||||||
variables:
|
|
||||||
BUILD_TAGS: "build_qa"
|
|
||||||
|
|
||||||
.windows-build-setup:
|
|
||||||
before_script:
|
before_script:
|
||||||
- export GOROOT=/c/Go1.20/
|
- export GOROOT=/c/Go1.20/
|
||||||
- export PATH=$GOROOT/bin:$PATH
|
- export PATH=$GOROOT/bin:$PATH
|
||||||
@ -249,10 +68,169 @@ build-darwin-qa:
|
|||||||
tags:
|
tags:
|
||||||
- windows-bridge
|
- windows-bridge
|
||||||
|
|
||||||
|
.env-darwin:
|
||||||
|
before_script:
|
||||||
|
- export PATH=/usr/local/bin:$PATH
|
||||||
|
- export PATH=/usr/local/opt/git/bin:$PATH
|
||||||
|
- export PATH=/usr/local/opt/make/libexec/gnubin:$PATH
|
||||||
|
- export PATH=/usr/local/opt/gnu-sed/libexec/gnubin:$PATH
|
||||||
|
- export GOROOT=~/local/opt/go@1.20
|
||||||
|
- export PATH="${GOROOT}/bin:$PATH"
|
||||||
|
- export GOPATH=~/go1.20
|
||||||
|
- export PATH="${GOPATH}/bin:$PATH"
|
||||||
|
- export QT6DIR=/opt/Qt/6.3.2/macos
|
||||||
|
- export PATH="${QT6DIR}/bin:$PATH"
|
||||||
|
- uname -a
|
||||||
|
cache: {}
|
||||||
|
tags:
|
||||||
|
- macos-m1-bridge
|
||||||
|
|
||||||
|
.env-linux-build:
|
||||||
|
image: gitlab.protontech.ch:4567/go/bridge-internal:build-go1.20-qt6.3.2
|
||||||
|
variables:
|
||||||
|
VCPKG_DEFAULT_BINARY_CACHE: ${CI_PROJECT_DIR}/.cache
|
||||||
|
cache:
|
||||||
|
key: linux-vcpkg
|
||||||
|
paths:
|
||||||
|
- .cache
|
||||||
|
when: 'always'
|
||||||
|
before_script:
|
||||||
|
- mkdir -p .cache/bin
|
||||||
|
- export PATH=$(pwd)/.cache/bin:$PATH
|
||||||
|
- export GOPATH="$CI_PROJECT_DIR/.cache"
|
||||||
|
- export PATH=$PATH:$QT6DIR/bin
|
||||||
|
- $(git config --global -l | grep -o 'url.*gitlab.protontech.ch.*insteadof' | xargs -L 1 git config --global --unset &> /dev/null) || echo "nothing to remove"
|
||||||
|
- git config --global url.https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}.insteadOf https://${CI_SERVER_HOST}
|
||||||
|
tags:
|
||||||
|
- large
|
||||||
|
|
||||||
|
# Stage: TEST
|
||||||
|
|
||||||
|
lint:
|
||||||
|
stage: test
|
||||||
|
extends:
|
||||||
|
- .rules-branch-manual-MR-and-devel-always
|
||||||
|
script:
|
||||||
|
- make lint
|
||||||
|
tags:
|
||||||
|
- medium
|
||||||
|
|
||||||
|
.script-test:
|
||||||
|
stage: test
|
||||||
|
extends:
|
||||||
|
- .rules-branch-manual-MR-and-devel-always
|
||||||
|
script:
|
||||||
|
- make test
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- coverage/**
|
||||||
|
|
||||||
|
test-linux:
|
||||||
|
extends:
|
||||||
|
- .script-test
|
||||||
|
tags:
|
||||||
|
- large
|
||||||
|
|
||||||
|
test-linux-race:
|
||||||
|
extends:
|
||||||
|
- test-linux
|
||||||
|
- .rules-branch-and-MR-manual
|
||||||
|
script:
|
||||||
|
- make test-race
|
||||||
|
|
||||||
|
test-integration:
|
||||||
|
extends:
|
||||||
|
- test-linux
|
||||||
|
script:
|
||||||
|
- make test-integration
|
||||||
|
|
||||||
|
test-integration-race:
|
||||||
|
extends:
|
||||||
|
- test-integration
|
||||||
|
- .rules-branch-and-MR-manual
|
||||||
|
script:
|
||||||
|
- make test-integration-race
|
||||||
|
|
||||||
|
test-windows:
|
||||||
|
extends:
|
||||||
|
- .env-windows
|
||||||
|
- .script-test
|
||||||
|
|
||||||
|
test-darwin:
|
||||||
|
extends:
|
||||||
|
- .env-darwin
|
||||||
|
- .script-test
|
||||||
|
|
||||||
|
test-coverage:
|
||||||
|
stage: test
|
||||||
|
extends:
|
||||||
|
- .rules-branch-manual-MR-and-devel-always
|
||||||
|
script:
|
||||||
|
- ./utils/coverage.sh
|
||||||
|
coverage: '/total:.*\(statements\).*\d+\.\d+%/'
|
||||||
|
needs:
|
||||||
|
- test-linux
|
||||||
|
- test-windows
|
||||||
|
- test-darwin
|
||||||
|
- test-integration
|
||||||
|
tags:
|
||||||
|
- small
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- coverage*
|
||||||
|
- coverage/**
|
||||||
|
when: 'always'
|
||||||
|
reports:
|
||||||
|
coverage_report:
|
||||||
|
coverage_format: cobertura
|
||||||
|
path: coverage.xml
|
||||||
|
|
||||||
|
# Stage: BUILD
|
||||||
|
|
||||||
|
.script-build:
|
||||||
|
stage: build
|
||||||
|
needs: ["lint"]
|
||||||
|
extends:
|
||||||
|
- .rules-branch-and-MR-manual
|
||||||
|
script:
|
||||||
|
- make build
|
||||||
|
- git diff && git diff-index --quiet HEAD
|
||||||
|
- make vault-editor
|
||||||
|
artifacts:
|
||||||
|
expire_in: 1 day
|
||||||
|
when: always
|
||||||
|
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||||
|
paths:
|
||||||
|
- bridge_*.tgz
|
||||||
|
- vault-editor
|
||||||
|
|
||||||
|
build-linux:
|
||||||
|
extends:
|
||||||
|
- .script-build
|
||||||
|
- .env-linux-build
|
||||||
|
|
||||||
|
build-linux-qa:
|
||||||
|
extends:
|
||||||
|
- build-linux
|
||||||
|
- .rules-branch-manual-MR-and-devel-always
|
||||||
|
variables:
|
||||||
|
BUILD_TAGS: "build_qa"
|
||||||
|
|
||||||
|
build-darwin:
|
||||||
|
extends:
|
||||||
|
- .script-build
|
||||||
|
- .env-darwin
|
||||||
|
|
||||||
|
build-darwin-qa:
|
||||||
|
extends:
|
||||||
|
- build-darwin
|
||||||
|
variables:
|
||||||
|
BUILD_TAGS: "build_qa"
|
||||||
|
|
||||||
build-windows:
|
build-windows:
|
||||||
extends:
|
extends:
|
||||||
- .build-base
|
- .script-build
|
||||||
- .windows-build-setup
|
- .env-windows
|
||||||
|
|
||||||
build-windows-qa:
|
build-windows-qa:
|
||||||
extends:
|
extends:
|
||||||
|
|||||||
@ -58,7 +58,7 @@ Proton Mail Bridge includes the following 3rd party software:
|
|||||||
* [testify](https://github.com/stretchr/testify) available under [license](https://github.com/stretchr/testify/blob/master/LICENSE)
|
* [testify](https://github.com/stretchr/testify) available under [license](https://github.com/stretchr/testify/blob/master/LICENSE)
|
||||||
* [cli](https://github.com/urfave/cli/v2) available under [license](https://github.com/urfave/cli/v2/blob/master/LICENSE)
|
* [cli](https://github.com/urfave/cli/v2) available under [license](https://github.com/urfave/cli/v2/blob/master/LICENSE)
|
||||||
* [msgpack](https://github.com/vmihailenco/msgpack/v5) available under [license](https://github.com/vmihailenco/msgpack/v5/blob/master/LICENSE)
|
* [msgpack](https://github.com/vmihailenco/msgpack/v5) available under [license](https://github.com/vmihailenco/msgpack/v5/blob/master/LICENSE)
|
||||||
* [goleak](https://go.uber.org/goleak)
|
* [goleak](https://go.uber.org/goleak) available under [license](https://pkg.go.dev/go.uber.org/goleak?tab=licenses)
|
||||||
* [exp](https://golang.org/x/exp) available under [license](https://cs.opensource.google/go/x/exp/+/master:LICENSE)
|
* [exp](https://golang.org/x/exp) available under [license](https://cs.opensource.google/go/x/exp/+/master:LICENSE)
|
||||||
* [net](https://golang.org/x/net) available under [license](https://cs.opensource.google/go/x/net/+/master:LICENSE)
|
* [net](https://golang.org/x/net) available under [license](https://cs.opensource.google/go/x/net/+/master:LICENSE)
|
||||||
* [sys](https://golang.org/x/sys) available under [license](https://cs.opensource.google/go/x/sys/+/master:LICENSE)
|
* [sys](https://golang.org/x/sys) available under [license](https://cs.opensource.google/go/x/sys/+/master:LICENSE)
|
||||||
@ -66,8 +66,8 @@ Proton Mail Bridge includes the following 3rd party software:
|
|||||||
* [grpc](https://google.golang.org/grpc) available under [license](https://github.com/grpc/grpc-go/blob/master/LICENSE)
|
* [grpc](https://google.golang.org/grpc) available under [license](https://github.com/grpc/grpc-go/blob/master/LICENSE)
|
||||||
* [protobuf](https://google.golang.org/protobuf) available under [license](https://github.com/protocolbuffers/protobuf/blob/main/LICENSE)
|
* [protobuf](https://google.golang.org/protobuf) available under [license](https://github.com/protocolbuffers/protobuf/blob/main/LICENSE)
|
||||||
* [plist](https://howett.net/plist) available under [license](https://github.com/DHowett/go-plist/blob/main/LICENSE)
|
* [plist](https://howett.net/plist) available under [license](https://github.com/DHowett/go-plist/blob/main/LICENSE)
|
||||||
* [atlas](https://ariga.io/atlas)
|
* [atlas](https://ariga.io/atlas) available under [license](https://github.com/ariga/atlas/blob/master/LICENSE)
|
||||||
* [ent](https://entgo.io/ent)
|
* [ent](https://entgo.io/ent) available under [license](https://pkg.go.dev/entgo.io/ent?tab=licenses)
|
||||||
* [bcrypt](https://github.com/ProtonMail/bcrypt) available under [license](https://github.com/ProtonMail/bcrypt/blob/master/LICENSE)
|
* [bcrypt](https://github.com/ProtonMail/bcrypt) available under [license](https://github.com/ProtonMail/bcrypt/blob/master/LICENSE)
|
||||||
* [go-crypto](https://github.com/ProtonMail/go-crypto) available under [license](https://github.com/ProtonMail/go-crypto/blob/master/LICENSE)
|
* [go-crypto](https://github.com/ProtonMail/go-crypto) available under [license](https://github.com/ProtonMail/go-crypto/blob/master/LICENSE)
|
||||||
* [go-mime](https://github.com/ProtonMail/go-mime) available under [license](https://github.com/ProtonMail/go-mime/blob/master/LICENSE)
|
* [go-mime](https://github.com/ProtonMail/go-mime) available under [license](https://github.com/ProtonMail/go-mime/blob/master/LICENSE)
|
||||||
@ -136,8 +136,8 @@ Proton Mail Bridge includes the following 3rd party software:
|
|||||||
* [mod](https://golang.org/x/mod) available under [license](https://cs.opensource.google/go/x/mod/+/master:LICENSE)
|
* [mod](https://golang.org/x/mod) available under [license](https://cs.opensource.google/go/x/mod/+/master:LICENSE)
|
||||||
* [sync](https://golang.org/x/sync) available under [license](https://cs.opensource.google/go/x/sync/+/master:LICENSE)
|
* [sync](https://golang.org/x/sync) available under [license](https://cs.opensource.google/go/x/sync/+/master:LICENSE)
|
||||||
* [tools](https://golang.org/x/tools) available under [license](https://cs.opensource.google/go/x/tools/+/master:LICENSE)
|
* [tools](https://golang.org/x/tools) available under [license](https://cs.opensource.google/go/x/tools/+/master:LICENSE)
|
||||||
* [genproto](https://google.golang.org/genproto)
|
* [genproto](https://google.golang.org/genproto) available under [license](https://pkg.go.dev/google.golang.org/genproto?tab=licenses)
|
||||||
gopkg.in/yaml.v3
|
* [yaml](https://gopkg.in/yaml.v3) available under [license](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)
|
||||||
* [docker-credential-helpers](https://github.com/ProtonMail/docker-credential-helpers) available under [license](https://github.com/ProtonMail/docker-credential-helpers/blob/master/LICENSE)
|
* [docker-credential-helpers](https://github.com/ProtonMail/docker-credential-helpers) available under [license](https://github.com/ProtonMail/docker-credential-helpers/blob/master/LICENSE)
|
||||||
* [go-message](https://github.com/ProtonMail/go-message) available under [license](https://github.com/ProtonMail/go-message/blob/master/LICENSE)
|
* [go-message](https://github.com/ProtonMail/go-message) available under [license](https://github.com/ProtonMail/go-message/blob/master/LICENSE)
|
||||||
* [go-keychain](https://github.com/cuthix/go-keychain) available under [license](https://github.com/cuthix/go-keychain/blob/master/LICENSE)
|
* [go-keychain](https://github.com/cuthix/go-keychain) available under [license](https://github.com/cuthix/go-keychain/blob/master/LICENSE)
|
||||||
|
|||||||
18
Makefile
18
Makefile
@ -229,14 +229,28 @@ add-license:
|
|||||||
change-copyright-year:
|
change-copyright-year:
|
||||||
./utils/missing_license.sh change-year
|
./utils/missing_license.sh change-year
|
||||||
|
|
||||||
|
GOCOVERAGE=-covermode=count -coverpkg=github.com/ProtonMail/proton-bridge/v3/internal/...,github.com/ProtonMail/proton-bridge/v3/pkg/...,
|
||||||
|
GOCOVERDIR=-args -test.gocoverdir=$$PWD/coverage
|
||||||
|
|
||||||
test: gofiles
|
test: gofiles
|
||||||
go test -v -timeout=20m -p=1 -count=1 -coverprofile=/tmp/coverage.out -run=${TESTRUN} ./internal/... ./pkg/...
|
mkdir -p coverage/unit-${GOOS}
|
||||||
|
go test \
|
||||||
|
-v -timeout=20m -p=1 -count=1 \
|
||||||
|
${GOCOVERAGE} \
|
||||||
|
-run=${TESTRUN} ./internal/... ./pkg/... \
|
||||||
|
${GOCOVERDIR}/unit-${GOOS}
|
||||||
|
|
||||||
test-race: gofiles
|
test-race: gofiles
|
||||||
go test -v -timeout=40m -p=1 -count=1 -race -failfast -run=${TESTRUN} ./internal/... ./pkg/...
|
go test -v -timeout=40m -p=1 -count=1 -race -failfast -run=${TESTRUN} ./internal/... ./pkg/...
|
||||||
|
|
||||||
test-integration: gofiles
|
test-integration: gofiles
|
||||||
go test -v -timeout=60m -p=1 -count=1 github.com/ProtonMail/proton-bridge/v3/tests
|
mkdir -p coverage/integration
|
||||||
|
go test \
|
||||||
|
-v -timeout=60m -p=1 -count=1 \
|
||||||
|
${GOCOVERAGE} \
|
||||||
|
github.com/ProtonMail/proton-bridge/v3/tests \
|
||||||
|
${GOCOVERDIR}/integration
|
||||||
|
|
||||||
|
|
||||||
test-integration-debug: gofiles
|
test-integration-debug: gofiles
|
||||||
dlv test github.com/ProtonMail/proton-bridge/v3/tests -- -test.v -test.timeout=10m -test.parallel=1 -test.count=1
|
dlv test github.com/ProtonMail/proton-bridge/v3/tests -- -test.v -test.timeout=10m -test.parallel=1 -test.count=1
|
||||||
|
|||||||
@ -43,9 +43,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
appName = "Proton Mail Launcher"
|
appName = "Proton Mail Launcher"
|
||||||
exeName = "bridge"
|
exeName = "bridge"
|
||||||
guiName = "bridge-gui"
|
guiName = "bridge-gui"
|
||||||
|
launcherName = "launcher"
|
||||||
|
|
||||||
FlagCLI = "cli"
|
FlagCLI = "cli"
|
||||||
FlagCLIShort = "c"
|
FlagCLIShort = "c"
|
||||||
@ -53,6 +54,7 @@ const (
|
|||||||
FlagNonInteractiveShort = "n"
|
FlagNonInteractiveShort = "n"
|
||||||
FlagLauncher = "--launcher"
|
FlagLauncher = "--launcher"
|
||||||
FlagWait = "--wait"
|
FlagWait = "--wait"
|
||||||
|
FlagSessionID = "--session-id"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() { //nolint:funlen
|
func main() { //nolint:funlen
|
||||||
@ -75,9 +77,11 @@ func main() { //nolint:funlen
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
l.WithError(err).Fatal("Failed to get logs path")
|
l.WithError(err).Fatal("Failed to get logs path")
|
||||||
}
|
}
|
||||||
crashHandler.AddRecoveryAction(logging.DumpStackTrace(logsPath))
|
|
||||||
|
|
||||||
if err := logging.Init(logsPath, os.Getenv("VERBOSITY")); err != nil {
|
sessionID := logging.NewSessionID()
|
||||||
|
crashHandler.AddRecoveryAction(logging.DumpStackTrace(logsPath, sessionID, launcherName))
|
||||||
|
|
||||||
|
if err := logging.Init(logsPath, sessionID, launcherName, os.Getenv("VERBOSITY")); err != nil {
|
||||||
l.WithError(err).Fatal("Failed to setup logging")
|
l.WithError(err).Fatal("Failed to setup logging")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +138,7 @@ func main() { //nolint:funlen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := execabs.Command(exe, appendLauncherPath(launcher, args)...) //nolint:gosec
|
cmd := execabs.Command(exe, appendLauncherPath(launcher, append(args, FlagSessionID, string(sessionID)))...) //nolint:gosec
|
||||||
|
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
|
|||||||
2
go.sum
2
go.sum
@ -28,8 +28,6 @@ github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs
|
|||||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
|
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
|
||||||
github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk=
|
github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk=
|
||||||
github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g=
|
github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g=
|
||||||
github.com/ProtonMail/gluon v0.16.1-0.20230607083802-83f92429ca8d h1:+1BKm++zxmfGwj81q3jFkiDpgVwg529qznGbI//uXpk=
|
|
||||||
github.com/ProtonMail/gluon v0.16.1-0.20230607083802-83f92429ca8d/go.mod h1:ERZikuN+2i/oTeSwS5fq7J0Fms76uUcBlTAwT4KaEAk=
|
|
||||||
github.com/ProtonMail/gluon v0.16.1-0.20230607122549-dbdb8e1cc0c3 h1:VMbbJD3dcGPPIgbdQTS5Z4nX0QU/SsVZWdmsMVVBBsI=
|
github.com/ProtonMail/gluon v0.16.1-0.20230607122549-dbdb8e1cc0c3 h1:VMbbJD3dcGPPIgbdQTS5Z4nX0QU/SsVZWdmsMVVBBsI=
|
||||||
github.com/ProtonMail/gluon v0.16.1-0.20230607122549-dbdb8e1cc0c3/go.mod h1:ERZikuN+2i/oTeSwS5fq7J0Fms76uUcBlTAwT4KaEAk=
|
github.com/ProtonMail/gluon v0.16.1-0.20230607122549-dbdb8e1cc0c3/go.mod h1:ERZikuN+2i/oTeSwS5fq7J0Fms76uUcBlTAwT4KaEAk=
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
|
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
|
||||||
|
|||||||
@ -76,10 +76,12 @@ const (
|
|||||||
flagNoWindow = "no-window"
|
flagNoWindow = "no-window"
|
||||||
flagParentPID = "parent-pid"
|
flagParentPID = "parent-pid"
|
||||||
flagSoftwareRenderer = "software-renderer"
|
flagSoftwareRenderer = "software-renderer"
|
||||||
|
flagSessionID = "session-id"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
appUsage = "Proton Mail IMAP and SMTP Bridge"
|
appUsage = "Proton Mail IMAP and SMTP Bridge"
|
||||||
|
appShortName = "bridge"
|
||||||
)
|
)
|
||||||
|
|
||||||
func New() *cli.App {
|
func New() *cli.App {
|
||||||
@ -150,6 +152,10 @@ func New() *cli.App {
|
|||||||
Hidden: true,
|
Hidden: true,
|
||||||
Value: false,
|
Value: false,
|
||||||
},
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: flagSessionID,
|
||||||
|
Hidden: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Action = run
|
app.Action = run
|
||||||
@ -311,12 +317,13 @@ func withLogging(c *cli.Context, crashHandler *crash.Handler, locations *locatio
|
|||||||
logrus.WithField("path", logsPath).Debug("Received logs path")
|
logrus.WithField("path", logsPath).Debug("Received logs path")
|
||||||
|
|
||||||
// Initialize logging.
|
// Initialize logging.
|
||||||
if err := logging.Init(logsPath, c.String(flagLogLevel)); err != nil {
|
sessionID := logging.NewSessionIDFromString(c.String(flagSessionID))
|
||||||
|
if err := logging.Init(logsPath, sessionID, appShortName, c.String(flagLogLevel)); err != nil {
|
||||||
return fmt.Errorf("could not initialize logging: %w", err)
|
return fmt.Errorf("could not initialize logging: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we dump a stack trace if we crash.
|
// Ensure we dump a stack trace if we crash.
|
||||||
crashHandler.AddRecoveryAction(logging.DumpStackTrace(logsPath))
|
crashHandler.AddRecoveryAction(logging.DumpStackTrace(logsPath, sessionID, appShortName))
|
||||||
|
|
||||||
logrus.
|
logrus.
|
||||||
WithField("appName", constants.FullAppName).
|
WithField("appName", constants.FullAppName).
|
||||||
|
|||||||
@ -44,7 +44,7 @@ import (
|
|||||||
// deleteOldGoIMAPFiles Set with `-ldflags -X app.deleteOldGoIMAPFiles=true` to enable cleanup of old imap cache data.
|
// deleteOldGoIMAPFiles Set with `-ldflags -X app.deleteOldGoIMAPFiles=true` to enable cleanup of old imap cache data.
|
||||||
var deleteOldGoIMAPFiles bool //nolint:gochecknoglobals
|
var deleteOldGoIMAPFiles bool //nolint:gochecknoglobals
|
||||||
|
|
||||||
// withBridge creates creates and tears down the bridge.
|
// withBridge creates and tears down the bridge.
|
||||||
func withBridge(
|
func withBridge(
|
||||||
c *cli.Context,
|
c *cli.Context,
|
||||||
exe string,
|
exe string,
|
||||||
|
|||||||
@ -54,14 +54,14 @@ func (bridge *Bridge) ReportBug(ctx context.Context, osType, osVersion, descript
|
|||||||
|
|
||||||
if attachLogs {
|
if attachLogs {
|
||||||
logs, err := getMatchingLogs(bridge.locator, func(filename string) bool {
|
logs, err := getMatchingLogs(bridge.locator, func(filename string) bool {
|
||||||
return logging.MatchLogName(filename) && !logging.MatchStackTraceName(filename)
|
return logging.MatchBridgeLogName(filename) && !logging.MatchStackTraceName(filename)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
crashes, err := getMatchingLogs(bridge.locator, func(filename string) bool {
|
crashes, err := getMatchingLogs(bridge.locator, func(filename string) bool {
|
||||||
return logging.MatchLogName(filename) && logging.MatchStackTraceName(filename)
|
return logging.MatchBridgeLogName(filename) && logging.MatchStackTraceName(filename)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
4
internal/frontend/bridge-gui/bridge-gui-tester/.lldbinit
Normal file
4
internal/frontend/bridge-gui/bridge-gui-tester/.lldbinit
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# The following fix an issue happening using LLDB with OpenSSL 3.1 on ARM64 architecture. (GODT-2680)
|
||||||
|
# WARNING: this file is ignored if you do not enable reading lldb config from cwd in ~/.lldbinit (`settings set target.load-cwd-lldbinit true`)
|
||||||
|
settings set platform.plugin.darwin.ignored-exceptions EXC_BAD_INSTRUCTION
|
||||||
|
process handle SIGILL -n false -p true -s false
|
||||||
4
internal/frontend/bridge-gui/bridge-gui/.lldbinit
Normal file
4
internal/frontend/bridge-gui/bridge-gui/.lldbinit
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# The following fix an issue happening using LLDB with OpenSSL 3.1 on ARM64 architecture. (GODT-2680)
|
||||||
|
# WARNING: this file is ignored if you do not enable reading lldb config from cwd in ~/.lldbinit (`settings set target.load-cwd-lldbinit true`)
|
||||||
|
settings set platform.plugin.darwin.ignored-exceptions EXC_BAD_INSTRUCTION
|
||||||
|
process handle SIGILL -n false -p true -s false
|
||||||
@ -19,6 +19,7 @@
|
|||||||
#include "Pch.h"
|
#include "Pch.h"
|
||||||
#include "CommandLine.h"
|
#include "CommandLine.h"
|
||||||
#include "Settings.h"
|
#include "Settings.h"
|
||||||
|
#include <bridgepp/SessionID/SessionID.h>
|
||||||
|
|
||||||
|
|
||||||
using namespace bridgepp;
|
using namespace bridgepp;
|
||||||
@ -142,5 +143,12 @@ CommandLineOptions parseCommandLine(int argc, char *argv[]) {
|
|||||||
|
|
||||||
options.logLevel = parseLogLevel(argc, argv);
|
options.logLevel = parseLogLevel(argc, argv);
|
||||||
|
|
||||||
|
options.sessionID = parseGoCLIStringArgument(argc, argv, { "session-id" });
|
||||||
|
if (options.sessionID.isEmpty()) {
|
||||||
|
options.sessionID = newSessionID();
|
||||||
|
options.bridgeArgs.append("--session-id");
|
||||||
|
options.bridgeArgs.append(options.sessionID);
|
||||||
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,6 +34,7 @@ struct CommandLineOptions {
|
|||||||
bridgepp::Log::Level logLevel { bridgepp::Log::defaultLevel }; ///< The log level
|
bridgepp::Log::Level logLevel { bridgepp::Log::defaultLevel }; ///< The log level
|
||||||
bool noWindow { false }; ///< Should the application start without displaying the main window?
|
bool noWindow { false }; ///< Should the application start without displaying the main window?
|
||||||
bool useSoftwareRenderer { false }; ///< Should QML be renderer in software (i.e. without rendering hardware interface).
|
bool useSoftwareRenderer { false }; ///< Should QML be renderer in software (i.e. without rendering hardware interface).
|
||||||
|
QString sessionID; ///< The sessionID.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,6 @@
|
|||||||
#include "LogUtils.h"
|
#include "LogUtils.h"
|
||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
#include <bridgepp/Log/LogUtils.h>
|
#include <bridgepp/Log/LogUtils.h>
|
||||||
#include <bridgepp/BridgeUtils.h>
|
|
||||||
|
|
||||||
|
|
||||||
using namespace bridgepp;
|
using namespace bridgepp;
|
||||||
@ -28,7 +27,7 @@ using namespace bridgepp;
|
|||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
/// \return A reference to the log.
|
/// \return A reference to the log.
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
Log &initLog() {
|
Log &initLog(QString const &sessionID) {
|
||||||
Log &log = app().log();
|
Log &log = app().log();
|
||||||
log.registerAsQtMessageHandler();
|
log.registerAsQtMessageHandler();
|
||||||
log.setEchoInConsole(true);
|
log.setEchoInConsole(true);
|
||||||
@ -41,7 +40,7 @@ Log &initLog() {
|
|||||||
|
|
||||||
// create new GUI log file
|
// create new GUI log file
|
||||||
QString error;
|
QString error;
|
||||||
if (!log.startWritingToFile(logsDir.absoluteFilePath(QString("gui_v%1_%2.log").arg(PROJECT_VER).arg(QDateTime::currentSecsSinceEpoch())), &error)) {
|
if (!log.startWritingToFile(logsDir.absoluteFilePath(QString("%1_000_gui_v%2_%3.log").arg(sessionID, PROJECT_VER, PROJECT_TAG)), &error)) {
|
||||||
log.error(error);
|
log.error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
#include <bridgepp/Log/Log.h>
|
#include <bridgepp/Log/Log.h>
|
||||||
|
|
||||||
|
|
||||||
bridgepp::Log &initLog(); ///< Initialize the application log.
|
bridgepp::Log &initLog(QString const &sessionID); ///< Initialize the application log.
|
||||||
|
|
||||||
|
|
||||||
#endif //BRIDGE_GUI_LOG_UTILS_H
|
#endif //BRIDGE_GUI_LOG_UTILS_H
|
||||||
|
|||||||
@ -286,7 +286,8 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
initQtApplication();
|
initQtApplication();
|
||||||
|
|
||||||
Log &log = initLog();
|
CommandLineOptions const cliOptions = parseCommandLine(argc, argv);
|
||||||
|
Log &log = initLog(cliOptions.sessionID);
|
||||||
|
|
||||||
QLockFile lock(bridgepp::userCacheDir() + "/" + bridgeGUILock);
|
QLockFile lock(bridgepp::userCacheDir() + "/" + bridgeGUILock);
|
||||||
if (!checkSingleInstance(lock)) {
|
if (!checkSingleInstance(lock)) {
|
||||||
@ -294,8 +295,6 @@ int main(int argc, char *argv[]) {
|
|||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandLineOptions const cliOptions = parseCommandLine(argc, argv);
|
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
registerSecondInstanceHandler();
|
registerSecondInstanceHandler();
|
||||||
setDockIconVisibleState(!cliOptions.noWindow);
|
setDockIconVisibleState(!cliOptions.noWindow);
|
||||||
|
|||||||
@ -32,9 +32,9 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|||||||
|
|
||||||
if (NOT DEFINED BRIDGE_APP_VERSION)
|
if (NOT DEFINED BRIDGE_APP_VERSION)
|
||||||
message(FATAL_ERROR "BRIDGE_APP_VERSION is not defined.")
|
message(FATAL_ERROR "BRIDGE_APP_VERSION is not defined.")
|
||||||
else()
|
else ()
|
||||||
message(STATUS "Bridge version is ${BRIDGE_APP_VERSION}")
|
message(STATUS "Bridge version is ${BRIDGE_APP_VERSION}")
|
||||||
endif()
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
#****************************************************************************************************************************************************
|
#****************************************************************************************************************************************************
|
||||||
@ -148,6 +148,7 @@ add_library(bridgepp
|
|||||||
bridgepp/Log/Log.h bridgepp/Log/Log.cpp
|
bridgepp/Log/Log.h bridgepp/Log/Log.cpp
|
||||||
bridgepp/Log/LogUtils.h bridgepp/Log/LogUtils.cpp
|
bridgepp/Log/LogUtils.h bridgepp/Log/LogUtils.cpp
|
||||||
bridgepp/ProcessMonitor.cpp bridgepp/ProcessMonitor.h
|
bridgepp/ProcessMonitor.cpp bridgepp/ProcessMonitor.h
|
||||||
|
bridgepp/SessionID/SessionID.cpp bridgepp/SessionID/SessionID.h
|
||||||
bridgepp/User/User.cpp bridgepp/User/User.h
|
bridgepp/User/User.cpp bridgepp/User/User.h
|
||||||
bridgepp/Worker/Worker.h bridgepp/Worker/Overseer.h bridgepp/Worker/Overseer.cpp)
|
bridgepp/Worker/Worker.h bridgepp/Worker/Overseer.h bridgepp/Worker/Overseer.cpp)
|
||||||
|
|
||||||
@ -167,7 +168,7 @@ target_precompile_headers(bridgepp PRIVATE Pch.h)
|
|||||||
|
|
||||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
|
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
|
||||||
cmake_policy(SET CMP0135 NEW) # avoid warning DOWNLOAD_EXTRACT_TIMESTAMP
|
cmake_policy(SET CMP0135 NEW) # avoid warning DOWNLOAD_EXTRACT_TIMESTAMP
|
||||||
endif()
|
endif ()
|
||||||
|
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
@ -188,7 +189,9 @@ enable_testing()
|
|||||||
add_executable(bridgepp-test EXCLUDE_FROM_ALL
|
add_executable(bridgepp-test EXCLUDE_FROM_ALL
|
||||||
Test/TestBridgeUtils.cpp
|
Test/TestBridgeUtils.cpp
|
||||||
Test/TestException.cpp
|
Test/TestException.cpp
|
||||||
Test/TestWorker.cpp Test/TestWorker.h)
|
Test/TestSessionID.cpp
|
||||||
|
Test/TestWorker.cpp Test/TestWorker.h
|
||||||
|
)
|
||||||
add_dependencies(bridgepp-test bridgepp)
|
add_dependencies(bridgepp-test bridgepp)
|
||||||
target_precompile_headers(bridgepp-test PRIVATE Pch.h)
|
target_precompile_headers(bridgepp-test PRIVATE Pch.h)
|
||||||
target_link_libraries(bridgepp-test
|
target_link_libraries(bridgepp-test
|
||||||
|
|||||||
36
internal/frontend/bridge-gui/bridgepp/Test/TestSessionID.cpp
Normal file
36
internal/frontend/bridge-gui/bridgepp/Test/TestSessionID.cpp
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
#include "QtCore/qdatetime.h"
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <bridgepp/SessionID/SessionID.h>
|
||||||
|
|
||||||
|
|
||||||
|
using namespace bridgepp;
|
||||||
|
|
||||||
|
|
||||||
|
TEST(SessionID, SessionID) {
|
||||||
|
QString const sessionID = newSessionID();
|
||||||
|
EXPECT_TRUE(sessionID.size() > 0);
|
||||||
|
|
||||||
|
EXPECT_FALSE(sessionIDToDateTime("invalidSessionID").isValid());
|
||||||
|
|
||||||
|
QDateTime const dateTime = sessionIDToDateTime(sessionID);
|
||||||
|
EXPECT_TRUE(dateTime.isValid());
|
||||||
|
EXPECT_TRUE(qAbs(dateTime.secsTo(QDateTime::currentDateTime())) < 5);
|
||||||
|
}
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
#include "SessionID.h"
|
||||||
|
#include "QtCore/qdatetime.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
|
||||||
|
QString const dateTimeFormat = "yyyyMMdd_hhmmsszzz"; ///< The format string for date/time used by the sessionID.
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
namespace bridgepp {
|
||||||
|
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
/// \return a new session ID based on the current local date/time
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
QString newSessionID() {
|
||||||
|
return QDateTime::currentDateTime().toString(dateTimeFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
/// \param[in] sessionID The sessionID.
|
||||||
|
/// \return The date/time corresponding to the sessionID.
|
||||||
|
/// \return An invalid date/time if an error occurs.
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
QDateTime sessionIDToDateTime(QString const &sessionID) {
|
||||||
|
return QDateTime::fromString(sessionID, dateTimeFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef BRIDGE_PP_SESSION_ID_H
|
||||||
|
#define BRIDGE_PP_SESSION_ID_H
|
||||||
|
|
||||||
|
|
||||||
|
namespace bridgepp {
|
||||||
|
|
||||||
|
|
||||||
|
QString newSessionID(); ///< Create a new sessions
|
||||||
|
QDateTime sessionIDToDateTime(QString const &sessionID); ///< Parse the date/time from a sessionID.
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
#endif //BRIDGE_PP_SESSION_ID_H
|
||||||
@ -40,12 +40,12 @@ func clearLogs(logDir string, maxLogs int, maxCrashes int) error {
|
|||||||
|
|
||||||
// Remove old logs.
|
// Remove old logs.
|
||||||
removeOldLogs(logDir, xslices.Filter(names, func(name string) bool {
|
removeOldLogs(logDir, xslices.Filter(names, func(name string) bool {
|
||||||
return MatchLogName(name) && !MatchStackTraceName(name)
|
return MatchBridgeLogName(name) && !MatchStackTraceName(name)
|
||||||
}), maxLogs)
|
}), maxLogs)
|
||||||
|
|
||||||
// Remove old stack traces.
|
// Remove old stack traces.
|
||||||
removeOldLogs(logDir, xslices.Filter(names, func(name string) bool {
|
removeOldLogs(logDir, xslices.Filter(names, func(name string) bool {
|
||||||
return MatchLogName(name) && MatchStackTraceName(name)
|
return MatchBridgeLogName(name) && MatchStackTraceName(name)
|
||||||
}), maxCrashes)
|
}), maxCrashes)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -58,7 +58,7 @@ func removeOldLogs(dir string, names []string, max int) {
|
|||||||
|
|
||||||
// Sort by timestamp, oldest first.
|
// Sort by timestamp, oldest first.
|
||||||
slices.SortFunc(names, func(a, b string) bool {
|
slices.SortFunc(names, func(a, b string) bool {
|
||||||
return getLogTime(a) < getLogTime(b)
|
return getLogTime(a).Before(getLogTime(b))
|
||||||
})
|
})
|
||||||
|
|
||||||
for _, path := range xslices.Map(names[:len(names)-max], func(name string) string { return filepath.Join(dir, name) }) {
|
for _, path := range xslices.Map(names[:len(names)-max], func(name string) string { return filepath.Join(dir, name) }) {
|
||||||
|
|||||||
@ -23,16 +23,15 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/crash"
|
"github.com/ProtonMail/proton-bridge/v3/internal/crash"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DumpStackTrace(logsPath string) crash.RecoveryAction {
|
func DumpStackTrace(logsPath string, sessionID SessionID, appName string) crash.RecoveryAction {
|
||||||
return func(r interface{}) error {
|
return func(r interface{}) error {
|
||||||
file := filepath.Join(logsPath, getStackTraceName(constants.Version, constants.Revision))
|
file := filepath.Join(logsPath, getStackTraceName(sessionID, appName, constants.Version, constants.Tag))
|
||||||
|
|
||||||
f, err := os.OpenFile(filepath.Clean(file), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o600)
|
f, err := os.OpenFile(filepath.Clean(file), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -53,10 +52,10 @@ func DumpStackTrace(logsPath string) crash.RecoveryAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStackTraceName(version, revision string) string {
|
func getStackTraceName(sessionID SessionID, appName, version, tag string) string {
|
||||||
return fmt.Sprintf("v%v_%v_crash_%v.log", version, revision, time.Now().Unix())
|
return fmt.Sprintf("%v_000_%v_v%v_%v_crash.log", sessionID, appName, version, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MatchStackTraceName(name string) bool {
|
func MatchStackTraceName(name string) bool {
|
||||||
return regexp.MustCompile(`^v.*_crash_.*\.log$`).MatchString(name)
|
return regexp.MustCompile(`^\d{8}_\d{9}_000_.*_crash\.log$`).MatchString(name)
|
||||||
}
|
}
|
||||||
|
|||||||
32
internal/logging/crash_test.go
Normal file
32
internal/logging/crash_test.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMatchStackTraceName(t *testing.T) {
|
||||||
|
filename := getStackTraceName(NewSessionID(), constants.AppName, constants.Version, constants.Tag)
|
||||||
|
require.True(t, len(filename) > 0)
|
||||||
|
require.True(t, MatchStackTraceName(filename))
|
||||||
|
require.False(t, MatchStackTraceName("Invalid.log"))
|
||||||
|
}
|
||||||
@ -19,26 +19,21 @@ package logging
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// MaxLogSize defines the maximum log size we should permit: 5 MB
|
// MaxLogSize defines the maximum log size we should permit: 5 MB
|
||||||
//
|
//
|
||||||
// The Zendesk limit for an attachement is 50MB and this is what will
|
// The Zendesk limit for an attachment is 50MB and this is what will
|
||||||
// be allowed via the API. However, if that fails for some reason, the
|
// be allowed via the API. However, if that fails for some reason, the
|
||||||
// fallback is sending the report via email, which has a limit of 10mb
|
// fallback is sending the report via email, which has a limit of 10mb
|
||||||
// total or 7MB per file. Since we can produce up to 6 logs, and we
|
// total or 7MB per file. Since we can produce up to 6 logs, and we
|
||||||
// compress all the files (avarage compression - 80%), we need to have
|
// compress all the files (average compression - 80%), we need to have
|
||||||
// a limit of 30MB total before compression, hence 5MB per log file.
|
// a limit of 30MB total before compression, hence 5MB per log file.
|
||||||
MaxLogSize = 5 * 1024 * 1024
|
MaxLogSize = 5 * 1024 * 1024
|
||||||
|
|
||||||
@ -82,7 +77,7 @@ func (cs *coloredStdOutHook) Fire(entry *logrus.Entry) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init(logsPath, level string) error {
|
func Init(logsPath string, sessionID SessionID, appName, level string) error {
|
||||||
logrus.SetFormatter(&logrus.TextFormatter{
|
logrus.SetFormatter(&logrus.TextFormatter{
|
||||||
DisableColors: true,
|
DisableColors: true,
|
||||||
FullTimestamp: true,
|
FullTimestamp: true,
|
||||||
@ -91,13 +86,7 @@ func Init(logsPath, level string) error {
|
|||||||
|
|
||||||
logrus.AddHook(newColoredStdOutHook())
|
logrus.AddHook(newColoredStdOutHook())
|
||||||
|
|
||||||
rotator, err := NewRotator(MaxLogSize, func() (io.WriteCloser, error) {
|
rotator, err := NewDefaultRotator(logsPath, sessionID, appName, MaxLogSize)
|
||||||
if err := clearLogs(logsPath, MaxLogs, MaxLogs); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return os.Create(filepath.Join(logsPath, getLogName(constants.Version, constants.Revision))) //nolint:gosec // G304
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -137,34 +126,42 @@ func setLevel(level string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLogName(version, revision string) string {
|
func getLogTime(filename string) time.Time {
|
||||||
return fmt.Sprintf("v%v_%v_%v.log", version, revision, time.Now().Unix())
|
re := regexp.MustCompile(`^(?P<sessionID>\d{8}_\d{9})_.*\.log$`)
|
||||||
}
|
|
||||||
|
|
||||||
func getLogTime(name string) int {
|
match := re.FindStringSubmatch(filename)
|
||||||
re := regexp.MustCompile(`^v.*_.*_(?P<timestamp>\d+).log$`)
|
|
||||||
|
|
||||||
match := re.FindStringSubmatch(name)
|
|
||||||
|
|
||||||
if len(match) == 0 {
|
if len(match) == 0 {
|
||||||
logrus.Warn("Could not parse log name: ", name)
|
logrus.WithField("filename", filename).Warn("Could not parse log filename")
|
||||||
return 0
|
return time.Time{}
|
||||||
}
|
}
|
||||||
|
|
||||||
timestamp, err := strconv.Atoi(match[re.SubexpIndex("timestamp")])
|
index := re.SubexpIndex("sessionID")
|
||||||
if err != nil {
|
if index < 0 {
|
||||||
return 0
|
logrus.WithField("filename", filename).Warn("Could not parse log filename")
|
||||||
|
return time.Time{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return timestamp
|
return SessionID(match[index]).toTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
func MatchLogName(name string) bool {
|
// MatchBridgeLogName return true iff filename is a bridge log filename.
|
||||||
return regexp.MustCompile(`^v.*\.log$`).MatchString(name)
|
func MatchBridgeLogName(filename string) bool {
|
||||||
|
return matchLogName(filename, "bridge")
|
||||||
}
|
}
|
||||||
|
|
||||||
func MatchGUILogName(name string) bool {
|
// MatchGUILogName return true iff filename is a bridge-gui log filename.
|
||||||
return regexp.MustCompile(`^gui_v.*\.log$`).MatchString(name)
|
func MatchGUILogName(filename string) bool {
|
||||||
|
return matchLogName(filename, "gui")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchLauncherLogName return true iff filename is a launcher log filename.
|
||||||
|
func MatchLauncherLogName(filename string) bool {
|
||||||
|
return matchLogName(filename, "launcher")
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchLogName(logName, appName string) bool {
|
||||||
|
return regexp.MustCompile(`^\d{8}_\d{9}_\d{3}_` + appName + `.*\.log$`).MatchString(logName)
|
||||||
}
|
}
|
||||||
|
|
||||||
type logKey string
|
type logKey string
|
||||||
@ -180,12 +177,3 @@ func WithLogrusField(ctx context.Context, key string, value interface{}) context
|
|||||||
fields[key] = value
|
fields[key] = value
|
||||||
return context.WithValue(ctx, logrusFields, fields)
|
return context.WithValue(ctx, logrusFields, fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LogFromContext(ctx context.Context) *logrus.Entry {
|
|
||||||
fields, ok := ctx.Value(logrusFields).(logrus.Fields)
|
|
||||||
if !ok || fields == nil {
|
|
||||||
return logrus.WithField("ctx", "empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
return logrus.WithFields(fields)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -25,59 +25,95 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestClearLogs tests that cearLogs removes only bridge old log files keeping last three of them.
|
func TestGetLogTime(t *testing.T) {
|
||||||
func TestClearLogs(t *testing.T) {
|
sessionID := NewSessionID()
|
||||||
dir := t.TempDir()
|
fp := defaultFileProvider(os.TempDir(), sessionID, "bridge-test")
|
||||||
|
wc, err := fp(0)
|
||||||
// Create some old log files.
|
|
||||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "other.log"), []byte("Hello"), 0o755))
|
|
||||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.4.7_debe87f2f5_0000000001.log"), []byte("Hello"), 0o755))
|
|
||||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.4.8_debe87f2f5_0000000002.log"), []byte("Hello"), 0o755))
|
|
||||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.4.9_debe87f2f5_0000000003.log"), []byte("Hello"), 0o755))
|
|
||||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.0_debe87f2f5_0000000004.log"), []byte("Hello"), 0o755))
|
|
||||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.1_debe87f2f5_0000000005.log"), []byte("Hello"), 0o755))
|
|
||||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.2_debe87f2f5_0000000006.log"), []byte("Hello"), 0o755))
|
|
||||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.3_debe87f2f5_0000000007.log"), []byte("Hello"), 0o755))
|
|
||||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.4_debe87f2f5_0000000008.log"), []byte("Hello"), 0o755))
|
|
||||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.5_debe87f2f5_0000000009.log"), []byte("Hello"), 0o755))
|
|
||||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.6_debe87f2f5_0000000010.log"), []byte("Hello"), 0o755))
|
|
||||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.7_debe87f2f5_0000000011.log"), []byte("Hello"), 0o755))
|
|
||||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.8_debe87f2f5_0000000012.log"), []byte("Hello"), 0o755))
|
|
||||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.12_debe87f2f5_0000000013.log"), []byte("Hello"), 0o755))
|
|
||||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.9_debe87f2f5_0000000014.log"), []byte("Hello"), 0o755))
|
|
||||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.10_debe87f2f5_0000000015.log"), []byte("Hello"), 0o755))
|
|
||||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.11_debe87f2f5_0000000016.log"), []byte("Hello"), 0o755))
|
|
||||||
|
|
||||||
// Clear the logs.
|
|
||||||
require.NoError(t, clearLogs(dir, 3, 0))
|
|
||||||
|
|
||||||
// We should only clear matching files, and keep the 3 most recent ones.
|
|
||||||
checkFileNames(t, dir, []string{
|
|
||||||
"other.log",
|
|
||||||
"v2.5.9_debe87f2f5_0000000014.log",
|
|
||||||
"v2.5.10_debe87f2f5_0000000015.log",
|
|
||||||
"v2.5.11_debe87f2f5_0000000016.log",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkFileNames(t *testing.T, dir string, expectedFileNames []string) {
|
|
||||||
require.ElementsMatch(t, expectedFileNames, getFileNames(t, dir))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFileNames(t *testing.T, dir string) []string {
|
|
||||||
files, err := os.ReadDir(dir)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
file, ok := wc.(*os.File)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
fileNames := []string{}
|
sessionIDTime := sessionID.toTime()
|
||||||
for _, file := range files {
|
require.False(t, sessionIDTime.IsZero())
|
||||||
fileNames = append(fileNames, file.Name())
|
logTime := getLogTime(filepath.Base(file.Name()))
|
||||||
if file.IsDir() {
|
require.False(t, logTime.IsZero())
|
||||||
subDir := filepath.Join(dir, file.Name())
|
require.Equal(t, sessionIDTime, logTime)
|
||||||
subFileNames := getFileNames(t, subDir)
|
|
||||||
for _, subFileName := range subFileNames {
|
|
||||||
fileNames = append(fileNames, file.Name()+"/"+subFileName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fileNames
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMatchLogName(t *testing.T) {
|
||||||
|
bridgeLog := "20230602_094633102_000_bridge_v3.0.99+git_5b650b1be3.log"
|
||||||
|
crashLog := "20230602_094633102_000_bridge_v3.0.99+git_5b650b1be3_crash.log"
|
||||||
|
guiLog := "20230602_094633102_000_gui_v3.0.99+git_5b650b1be3.log"
|
||||||
|
launcherLog := "20230602_094633102_000_launcher_v3.0.99+git_5b650b1be3.log"
|
||||||
|
require.True(t, MatchBridgeLogName(bridgeLog))
|
||||||
|
require.False(t, MatchGUILogName(bridgeLog))
|
||||||
|
require.False(t, MatchLauncherLogName(bridgeLog))
|
||||||
|
require.True(t, MatchBridgeLogName(crashLog))
|
||||||
|
require.False(t, MatchGUILogName(crashLog))
|
||||||
|
require.False(t, MatchLauncherLogName(crashLog))
|
||||||
|
require.False(t, MatchBridgeLogName(guiLog))
|
||||||
|
require.True(t, MatchGUILogName(guiLog))
|
||||||
|
require.False(t, MatchLauncherLogName(guiLog))
|
||||||
|
require.False(t, MatchBridgeLogName(launcherLog))
|
||||||
|
require.False(t, MatchGUILogName(launcherLog))
|
||||||
|
require.True(t, MatchLauncherLogName(launcherLog))
|
||||||
|
}
|
||||||
|
|
||||||
|
// The test below is temporarily disabled, and will be restored when implementing new retention policy GODT-2668
|
||||||
|
|
||||||
|
// TestClearLogs tests that clearLogs removes only bridge old log files keeping last three of them.
|
||||||
|
// func TestClearLogs(t *testing.T) {
|
||||||
|
// dir := t.TempDir()
|
||||||
|
//
|
||||||
|
// // Create some old log files.
|
||||||
|
// require.NoError(t, os.WriteFile(filepath.Join(dir, "other.log"), []byte("Hello"), 0o755))
|
||||||
|
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.4.7_debe87f2f5_0000000001.log"), []byte("Hello"), 0o755))
|
||||||
|
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.4.8_debe87f2f5_0000000002.log"), []byte("Hello"), 0o755))
|
||||||
|
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.4.9_debe87f2f5_0000000003.log"), []byte("Hello"), 0o755))
|
||||||
|
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.0_debe87f2f5_0000000004.log"), []byte("Hello"), 0o755))
|
||||||
|
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.1_debe87f2f5_0000000005.log"), []byte("Hello"), 0o755))
|
||||||
|
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.2_debe87f2f5_0000000006.log"), []byte("Hello"), 0o755))
|
||||||
|
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.3_debe87f2f5_0000000007.log"), []byte("Hello"), 0o755))
|
||||||
|
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.4_debe87f2f5_0000000008.log"), []byte("Hello"), 0o755))
|
||||||
|
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.5_debe87f2f5_0000000009.log"), []byte("Hello"), 0o755))
|
||||||
|
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.6_debe87f2f5_0000000010.log"), []byte("Hello"), 0o755))
|
||||||
|
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.7_debe87f2f5_0000000011.log"), []byte("Hello"), 0o755))
|
||||||
|
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.8_debe87f2f5_0000000012.log"), []byte("Hello"), 0o755))
|
||||||
|
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.12_debe87f2f5_0000000013.log"), []byte("Hello"), 0o755))
|
||||||
|
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.9_debe87f2f5_0000000014.log"), []byte("Hello"), 0o755))
|
||||||
|
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.10_debe87f2f5_0000000015.log"), []byte("Hello"), 0o755))
|
||||||
|
// require.NoError(t, os.WriteFile(filepath.Join(dir, "v2.5.11_debe87f2f5_0000000016.log"), []byte("Hello"), 0o755))
|
||||||
|
//
|
||||||
|
// // Clear the logs.
|
||||||
|
// require.NoError(t, clearLogs(dir, 3, 0))
|
||||||
|
//
|
||||||
|
// // We should only clear matching files, and keep the 3 most recent ones.
|
||||||
|
// checkFileNames(t, dir, []string{
|
||||||
|
// "other.log",
|
||||||
|
// "v2.5.9_debe87f2f5_0000000014.log",
|
||||||
|
// "v2.5.10_debe87f2f5_0000000015.log",
|
||||||
|
// "v2.5.11_debe87f2f5_0000000016.log",
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func checkFileNames(t *testing.T, dir string, expectedFileNames []string) {
|
||||||
|
// require.ElementsMatch(t, expectedFileNames, getFileNames(t, dir))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func getFileNames(t *testing.T, dir string) []string {
|
||||||
|
// files, err := os.ReadDir(dir)
|
||||||
|
// require.NoError(t, err)
|
||||||
|
//
|
||||||
|
// fileNames := []string{}
|
||||||
|
// for _, file := range files {
|
||||||
|
// fileNames = append(fileNames, file.Name())
|
||||||
|
// if file.IsDir() {
|
||||||
|
// subDir := filepath.Join(dir, file.Name())
|
||||||
|
// subFileNames := getFileNames(t, subDir)
|
||||||
|
// for _, subFileName := range subFileNames {
|
||||||
|
// fileNames = append(fileNames, file.Name()+"/"+subFileName)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return fileNames
|
||||||
|
// }
|
||||||
|
|||||||
@ -17,16 +17,36 @@
|
|||||||
|
|
||||||
package logging
|
package logging
|
||||||
|
|
||||||
import "io"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
|
)
|
||||||
|
|
||||||
type Rotator struct {
|
type Rotator struct {
|
||||||
getFile FileProvider
|
getFile FileProvider
|
||||||
wc io.WriteCloser
|
wc io.WriteCloser
|
||||||
size int
|
size int
|
||||||
maxSize int
|
maxSize int
|
||||||
|
nextIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileProvider func() (io.WriteCloser, error)
|
type FileProvider func(index int) (io.WriteCloser, error)
|
||||||
|
|
||||||
|
func defaultFileProvider(logsPath string, sessionID SessionID, appName string) FileProvider {
|
||||||
|
return func(index int) (io.WriteCloser, error) {
|
||||||
|
if err := clearLogs(logsPath, MaxLogs, MaxLogs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Create(filepath.Join(logsPath, //nolint:gosec // G304
|
||||||
|
fmt.Sprintf("%v_%03d_%v_v%v_%v.log", sessionID, index, appName, constants.Version, constants.Tag),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func NewRotator(maxSize int, getFile FileProvider) (*Rotator, error) {
|
func NewRotator(maxSize int, getFile FileProvider) (*Rotator, error) {
|
||||||
r := &Rotator{
|
r := &Rotator{
|
||||||
@ -41,6 +61,10 @@ func NewRotator(maxSize int, getFile FileProvider) (*Rotator, error) {
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewDefaultRotator(logsPath string, sessionID SessionID, appName string, maxSize int) (*Rotator, error) {
|
||||||
|
return NewRotator(maxSize, defaultFileProvider(logsPath, sessionID, appName))
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Rotator) Write(p []byte) (int, error) {
|
func (r *Rotator) Write(p []byte) (int, error) {
|
||||||
if r.size+len(p) > r.maxSize {
|
if r.size+len(p) > r.maxSize {
|
||||||
if err := r.rotate(); err != nil {
|
if err := r.rotate(); err != nil {
|
||||||
@ -63,11 +87,12 @@ func (r *Rotator) rotate() error {
|
|||||||
_ = r.wc.Close()
|
_ = r.wc.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
wc, err := r.getFile()
|
wc, err := r.getFile(r.nextIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.nextIndex++
|
||||||
r.wc = wc
|
r.wc = wc
|
||||||
r.size = 0
|
r.size = 0
|
||||||
|
|
||||||
|
|||||||
@ -19,8 +19,10 @@ package logging
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -38,7 +40,7 @@ func (c *WriteCloser) Close() error {
|
|||||||
func TestRotator(t *testing.T) {
|
func TestRotator(t *testing.T) {
|
||||||
n := 0
|
n := 0
|
||||||
|
|
||||||
getFile := func() (io.WriteCloser, error) {
|
getFile := func(_ int) (io.WriteCloser, error) {
|
||||||
n++
|
n++
|
||||||
return &WriteCloser{}, nil
|
return &WriteCloser{}, nil
|
||||||
}
|
}
|
||||||
@ -75,11 +77,70 @@ func TestRotator(t *testing.T) {
|
|||||||
assert.Equal(t, 4, n)
|
assert.Equal(t, 4, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func countFilesMatching(pattern string) int {
|
||||||
|
files, err := filepath.Glob(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(files)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanupLogs(t *testing.T, sessionID SessionID) {
|
||||||
|
paths, err := filepath.Glob(filepath.Join(os.TempDir(), string(sessionID)+"*.log"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
for _, path := range paths {
|
||||||
|
require.NoError(t, os.Remove(path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultRotator(t *testing.T) {
|
||||||
|
fiveBytes := []byte("00000")
|
||||||
|
tmpDir := os.TempDir()
|
||||||
|
|
||||||
|
sessionID := NewSessionID()
|
||||||
|
basePath := filepath.Join(tmpDir, string(sessionID))
|
||||||
|
|
||||||
|
r, err := NewDefaultRotator(tmpDir, sessionID, "bridge", 10)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, countFilesMatching(basePath+"_000_*.log"))
|
||||||
|
require.Equal(t, 1, countFilesMatching(basePath+"*.log"))
|
||||||
|
|
||||||
|
_, err = r.Write(fiveBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, countFilesMatching(basePath+"*.log"))
|
||||||
|
|
||||||
|
_, err = r.Write(fiveBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, countFilesMatching(basePath+"*.log"))
|
||||||
|
|
||||||
|
_, err = r.Write(fiveBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, countFilesMatching(basePath+"*.log"))
|
||||||
|
require.Equal(t, 1, countFilesMatching(basePath+"_001_*.log"))
|
||||||
|
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
_, err = r.Write(fiveBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, r.wc.Close())
|
||||||
|
|
||||||
|
// total written: 35 bytes, i.e. 4 log files
|
||||||
|
logFileCount := countFilesMatching(basePath + "*.log")
|
||||||
|
require.Equal(t, 4, logFileCount)
|
||||||
|
for i := 0; i < logFileCount; i++ {
|
||||||
|
require.Equal(t, 1, countFilesMatching(basePath+fmt.Sprintf("_%03d_*.log", i)))
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupLogs(t, sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkRotate(b *testing.B) {
|
func BenchmarkRotate(b *testing.B) {
|
||||||
benchRotate(b, MaxLogSize, getTestFile(b, b.TempDir(), MaxLogSize-1))
|
benchRotate(b, MaxLogSize, getTestFile(b, b.TempDir(), MaxLogSize-1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func benchRotate(b *testing.B, logSize int, getFile func() (io.WriteCloser, error)) {
|
func benchRotate(b *testing.B, logSize int, getFile func(index int) (io.WriteCloser, error)) {
|
||||||
r, err := NewRotator(logSize, getFile)
|
r, err := NewRotator(logSize, getFile)
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
|
|
||||||
@ -92,8 +153,8 @@ func benchRotate(b *testing.B, logSize int, getFile func() (io.WriteCloser, erro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTestFile(b *testing.B, dir string, length int) func() (io.WriteCloser, error) {
|
func getTestFile(b *testing.B, dir string, length int) func(int) (io.WriteCloser, error) {
|
||||||
return func() (io.WriteCloser, error) {
|
return func(index int) (io.WriteCloser, error) {
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
defer b.StartTimer()
|
defer b.StartTimer()
|
||||||
|
|
||||||
|
|||||||
64
internal/logging/session_id.go
Normal file
64
internal/logging/session_id.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
|
||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SessionID string
|
||||||
|
|
||||||
|
const (
|
||||||
|
timeFormat = "20060102_150405" // time format in Go does not support milliseconds without dot, so we'll process it manually.
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewSessionID creates a sessionID based on the current time.
|
||||||
|
func NewSessionID() SessionID {
|
||||||
|
now := time.Now()
|
||||||
|
return SessionID(now.Format(timeFormat) + fmt.Sprintf("%03d", now.Nanosecond()/1000000))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSessionIDFromString Return a new sessionID from string. If the str is empty a new time based sessionID is returned, otherwise the string
|
||||||
|
// is used as the sessionID.
|
||||||
|
func NewSessionIDFromString(str string) SessionID {
|
||||||
|
if (len(str)) > 0 {
|
||||||
|
return SessionID(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewSessionID()
|
||||||
|
}
|
||||||
|
|
||||||
|
// toTime converts a sessionID to a date/Time, considering the time zone is local.
|
||||||
|
func (s SessionID) toTime() time.Time {
|
||||||
|
if len(s) < 3 {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := time.ParseInLocation(timeFormat, string(s)[:len(s)-3], time.Local)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ms int
|
||||||
|
if ms, err = strconv.Atoi(string(s)[len(s)-3:]); err != nil {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.Add(time.Duration(ms) * time.Millisecond)
|
||||||
|
}
|
||||||
38
internal/logging/session_id_test.go
Normal file
38
internal/logging/session_id_test.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (c) 2023 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail Bridge.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
|
||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSessionID(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
sessionID := NewSessionID()
|
||||||
|
sessionTime := sessionID.toTime()
|
||||||
|
require.False(t, sessionTime.IsZero())
|
||||||
|
require.WithinRange(t, sessionTime, now.Add(-1*time.Millisecond), now.Add(1*time.Millisecond))
|
||||||
|
|
||||||
|
fromString := NewSessionIDFromString("")
|
||||||
|
require.True(t, len(fromString) > 0)
|
||||||
|
fromString = NewSessionIDFromString(string(sessionID))
|
||||||
|
require.True(t, len(fromString) > 0)
|
||||||
|
require.Equal(t, sessionID, fromString)
|
||||||
|
}
|
||||||
@ -28,70 +28,80 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func syncFolders(localPath, updatePath string) (err error) {
|
func syncFolders(localPath, updatePath string) error {
|
||||||
backupDir := filepath.Join(filepath.Dir(updatePath), "backup")
|
backupDir := filepath.Join(filepath.Dir(updatePath), "backup")
|
||||||
if err = createBackup(localPath, backupDir); err != nil {
|
if err := createBackup(localPath, backupDir); err != nil {
|
||||||
return
|
logrus.WithField("dir", backupDir).WithError(err).Error("Cannot create backup")
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = removeMissing(localPath, updatePath); err != nil {
|
if err := removeMissing(localPath, updatePath); err != nil {
|
||||||
|
logrus.WithError(err).Error("Sync folders: failed to remove missing.")
|
||||||
restoreFromBackup(backupDir, localPath)
|
restoreFromBackup(backupDir, localPath)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = copyRecursively(updatePath, localPath); err != nil {
|
if err := copyRecursively(updatePath, localPath); err != nil {
|
||||||
|
logrus.WithError(err).Error("Sync folders: failed to copy.")
|
||||||
restoreFromBackup(backupDir, localPath)
|
restoreFromBackup(backupDir, localPath)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:nakedret
|
func removeMissing(folderToCleanPath, itemsToKeepPath string) error {
|
||||||
func removeMissing(folderToCleanPath, itemsToKeepPath string) (err error) {
|
logrus.WithField("dir", folderToCleanPath).Debug("Remove missing")
|
||||||
logrus.WithField("from", folderToCleanPath).Debug("Remove missing")
|
|
||||||
// Create list of files.
|
// Create list of files.
|
||||||
existingRelPaths := map[string]bool{}
|
existingRelPaths := map[string]bool{}
|
||||||
err = filepath.Walk(itemsToKeepPath, func(keepThis string, _ os.FileInfo, walkErr error) error {
|
if err := filepath.Walk(itemsToKeepPath, func(keepThis string, _ os.FileInfo, walkErr error) error {
|
||||||
if walkErr != nil {
|
if walkErr != nil {
|
||||||
return walkErr
|
return walkErr
|
||||||
}
|
}
|
||||||
|
|
||||||
relPath, walkErr := filepath.Rel(itemsToKeepPath, keepThis)
|
relPath, walkErr := filepath.Rel(itemsToKeepPath, keepThis)
|
||||||
if walkErr != nil {
|
if walkErr != nil {
|
||||||
return walkErr
|
return walkErr
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.WithField("path", relPath).Trace("Keep the path")
|
logrus.WithField("path", relPath).Trace("Keep the path")
|
||||||
existingRelPaths[relPath] = true
|
existingRelPaths[relPath] = true
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delList := []string{}
|
delList := []string{}
|
||||||
err = filepath.Walk(folderToCleanPath, func(removeThis string, _ os.FileInfo, walkErr error) error {
|
if err := filepath.Walk(folderToCleanPath, func(fullPath string, _ os.FileInfo, walkErr error) error {
|
||||||
if walkErr != nil {
|
if walkErr != nil {
|
||||||
return walkErr
|
return walkErr
|
||||||
}
|
}
|
||||||
relPath, walkErr := filepath.Rel(folderToCleanPath, removeThis)
|
|
||||||
|
relPath, walkErr := filepath.Rel(folderToCleanPath, fullPath)
|
||||||
if walkErr != nil {
|
if walkErr != nil {
|
||||||
|
logrus.WithField("full", fullPath).WithError(walkErr).Error("Failed to get relative path")
|
||||||
return walkErr
|
return walkErr
|
||||||
}
|
}
|
||||||
logrus.Debug("check path ", relPath)
|
|
||||||
|
l := logrus.WithField("path", relPath)
|
||||||
|
l.Debug("Check")
|
||||||
|
|
||||||
if !existingRelPaths[relPath] {
|
if !existingRelPaths[relPath] {
|
||||||
logrus.Debug("path not in list, removing ", removeThis)
|
l.WithField("remove", fullPath).Debug("Path not in list, removing")
|
||||||
delList = append(delList, removeThis)
|
delList = append(delList, fullPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, removeThis := range delList {
|
for _, removeThis := range delList {
|
||||||
if err = os.RemoveAll(removeThis); err != nil && !errors.Is(err, fs.ErrNotExist) {
|
if err := os.RemoveAll(removeThis); err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||||
logrus.Error("remove error ", err)
|
logrus.WithField("path", removeThis).WithError(err).Error("Cannot remove")
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,23 +109,30 @@ func removeMissing(folderToCleanPath, itemsToKeepPath string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func restoreFromBackup(backupDir, localPath string) {
|
func restoreFromBackup(backupDir, localPath string) {
|
||||||
logrus.WithField("from", backupDir).
|
l := logrus.WithField("from", backupDir).
|
||||||
WithField("to", localPath).
|
WithField("to", localPath)
|
||||||
Error("recovering")
|
l.Warning("Recovering")
|
||||||
|
|
||||||
if err := copyRecursively(backupDir, localPath); err != nil {
|
if err := copyRecursively(backupDir, localPath); err != nil {
|
||||||
logrus.WithField("from", backupDir).
|
l.WithError(err).Error("Not able to recover")
|
||||||
WithField("to", localPath).
|
|
||||||
Error("Not able to recover.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createBackup(srcFile, dstDir string) (err error) {
|
func createBackup(srcFile, dstDir string) error {
|
||||||
logrus.WithField("from", srcFile).WithField("to", dstDir).Debug("Create backup")
|
l := logrus.WithField("from", srcFile).WithField("to", dstDir)
|
||||||
if err = mkdirAllClear(dstDir); err != nil {
|
|
||||||
return
|
l.Debug("Create backup")
|
||||||
|
if err := mkdirAllClear(dstDir); err != nil {
|
||||||
|
l.WithError(err).Error("Cannot create backup folder")
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return copyRecursively(srcFile, dstDir)
|
if err := copyRecursively(srcFile, dstDir); err != nil {
|
||||||
|
l.WithError(err).Error("Cannot copy to backup folder")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mkdirAllClear(path string) error {
|
func mkdirAllClear(path string) error {
|
||||||
@ -129,12 +146,14 @@ func mkdirAllClear(path string) error {
|
|||||||
func checksum(path string) (hash string) {
|
func checksum(path string) (hash string) {
|
||||||
file, err := os.Open(filepath.Clean(path))
|
file, err := os.Open(filepath.Clean(path))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logrus.WithError(err).WithField("path", path).Error("Cannot open file for checksum")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer file.Close() //nolint:errcheck,gosec
|
defer file.Close() //nolint:errcheck,gosec
|
||||||
|
|
||||||
hasher := sha256.New()
|
hasher := sha256.New()
|
||||||
if _, err := io.Copy(hasher, file); err != nil {
|
if _, err := io.Copy(hasher, file); err != nil {
|
||||||
|
logrus.WithError(err).WithField("path", path).Error("Cannot read file for checksum")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,40 +171,50 @@ func copyRecursively(srcDir, dstDir string) error {
|
|||||||
srcIsLink := srcInfo.Mode()&os.ModeSymlink == os.ModeSymlink
|
srcIsLink := srcInfo.Mode()&os.ModeSymlink == os.ModeSymlink
|
||||||
srcIsDir := srcInfo.IsDir()
|
srcIsDir := srcInfo.IsDir()
|
||||||
|
|
||||||
|
l := logrus.WithField("source", srcPath)
|
||||||
|
|
||||||
// Non regular source (e.g. named pipes, sockets, devices...).
|
// Non regular source (e.g. named pipes, sockets, devices...).
|
||||||
if !srcIsLink && !srcIsDir && !srcInfo.Mode().IsRegular() {
|
if !srcIsLink && !srcIsDir && !srcInfo.Mode().IsRegular() {
|
||||||
logrus.Error("File ", srcPath, " with mode ", srcInfo.Mode())
|
err := errors.New("irregular source file: copy not implemented")
|
||||||
return errors.New("irregular source file. Copy not implemented")
|
l.WithField("mode", srcInfo.Mode()).WithError(err).Error("Source with iregular mode")
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destination path.
|
// Destination path.
|
||||||
srcRelPath, err := filepath.Rel(srcDir, srcPath)
|
srcRelPath, err := filepath.Rel(srcDir, srcPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
l.WithField("dir", srcDir).WithError(err).Error("Failed to get relative source path")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dstPath := filepath.Join(dstDir, srcRelPath)
|
dstPath := filepath.Join(dstDir, srcRelPath)
|
||||||
logrus.Debug("src: ", srcPath, " dst: ", dstPath)
|
l = l.WithField("destination", dstPath)
|
||||||
|
|
||||||
// Destination exists.
|
// Destination exists.
|
||||||
dstInfo, err := os.Lstat(dstPath)
|
dstInfo, err := os.Lstat(dstPath)
|
||||||
|
l.WithError(err).Debug("Destination check")
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
dstIsLink := dstInfo.Mode()&os.ModeSymlink == os.ModeSymlink
|
dstIsLink := dstInfo.Mode()&os.ModeSymlink == os.ModeSymlink
|
||||||
dstIsDir := dstInfo.IsDir()
|
dstIsDir := dstInfo.IsDir()
|
||||||
|
|
||||||
// Non regular destination (e.g. named pipes, sockets, devices...).
|
// Non regular destination (e.g. named pipes, sockets, devices...).
|
||||||
if !dstIsLink && !dstIsDir && !dstInfo.Mode().IsRegular() {
|
if !dstIsLink && !dstIsDir && !dstInfo.Mode().IsRegular() {
|
||||||
logrus.Error("File ", dstPath, " with mode ", dstInfo.Mode())
|
err := errors.New("irregular target file: copy not implemented")
|
||||||
return errors.New("irregular target file. Copy not implemented")
|
l.WithError(err).WithField("mode", dstInfo.Mode()).Error("Destination with irregular mode")
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if dstIsLink {
|
if dstIsLink {
|
||||||
if err = os.Remove(dstPath); err != nil {
|
if err = os.Remove(dstPath); err != nil {
|
||||||
|
l.WithError(err).Error("Cannot remove destination link")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !dstIsLink && dstIsDir && !srcIsDir {
|
if !dstIsLink && dstIsDir && !srcIsDir {
|
||||||
if err = os.RemoveAll(dstPath); err != nil {
|
if err = os.RemoveAll(dstPath); err != nil {
|
||||||
|
l.WithError(err).Error("Cannot remove destination folder")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -194,63 +223,89 @@ func copyRecursively(srcDir, dstDir string) error {
|
|||||||
|
|
||||||
if dstInfo.Mode().IsRegular() && !srcInfo.Mode().IsRegular() {
|
if dstInfo.Mode().IsRegular() && !srcInfo.Mode().IsRegular() {
|
||||||
if err = os.Remove(dstPath); err != nil {
|
if err = os.Remove(dstPath); err != nil {
|
||||||
|
l.WithError(err).Error("Cannot remove destination file")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if !errors.Is(err, fs.ErrNotExist) {
|
} else if !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
l.WithError(err).Error("Destination error")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create symbolic link and return.
|
// Create symbolic link and return.
|
||||||
if srcIsLink {
|
if srcIsLink {
|
||||||
logrus.Debug("It is a symlink")
|
l.Debug("Source is a symlink")
|
||||||
linkPath, err := os.Readlink(srcPath)
|
linkPath, err := os.Readlink(srcPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
l.WithError(err).Error("Failed to read link")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logrus.Debug("link to ", linkPath)
|
l.WithField("linkPath", linkPath).Debug("Creating symlink")
|
||||||
return os.Symlink(linkPath, dstPath)
|
return os.Symlink(linkPath, dstPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create dir and return.
|
// Create dir and return.
|
||||||
if srcIsDir {
|
if srcIsDir {
|
||||||
logrus.Debug("It is a dir")
|
l.Debug("Source is a dir")
|
||||||
return os.MkdirAll(dstPath, srcInfo.Mode())
|
err := os.MkdirAll(dstPath, srcInfo.Mode())
|
||||||
|
if err != nil {
|
||||||
|
l.WithError(err).Error("Failed to create dir")
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regular files only.
|
// Regular files only.
|
||||||
// If files are same return.
|
// If files are same return.
|
||||||
if os.SameFile(srcInfo, dstInfo) || checksum(srcPath) == checksum(dstPath) {
|
if os.SameFile(srcInfo, dstInfo) || checksum(srcPath) == checksum(dstPath) {
|
||||||
logrus.Debug("Same files, skip copy")
|
l.Debug("Same files, skip copy")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create/overwrite regular file.
|
// Create/overwrite regular file.
|
||||||
srcReader, err := os.Open(filepath.Clean(srcPath))
|
srcReader, err := os.Open(filepath.Clean(srcPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
l.WithError(err).Error("Failed to open source")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer srcReader.Close() //nolint:errcheck,gosec
|
defer srcReader.Close() //nolint:errcheck,gosec
|
||||||
|
|
||||||
return copyToTmpFileRename(srcReader, dstPath, srcInfo.Mode())
|
return copyToTmpFileRename(srcReader, dstPath, srcInfo.Mode())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyToTmpFileRename(srcReader io.Reader, dstPath string, dstMode os.FileMode) error {
|
func copyToTmpFileRename(srcReader io.Reader, dstPath string, dstMode os.FileMode) error {
|
||||||
logrus.Debug("Tmp and rename ", dstPath)
|
|
||||||
tmpPath := dstPath + ".tmp"
|
tmpPath := dstPath + ".tmp"
|
||||||
|
l := logrus.WithField("dstPath", dstPath)
|
||||||
|
l.Debug("Create tmp and rename")
|
||||||
|
|
||||||
if err := copyToFileTruncate(srcReader, tmpPath, dstMode); err != nil {
|
if err := copyToFileTruncate(srcReader, tmpPath, dstMode); err != nil {
|
||||||
|
l.WithError(err).Error("Failed to copy and truncate")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return os.Rename(tmpPath, dstPath)
|
|
||||||
|
if err := os.Rename(tmpPath, dstPath); err != nil {
|
||||||
|
l.WithError(err).Error("Failed to rename")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyToFileTruncate(srcReader io.Reader, dstPath string, dstMode os.FileMode) error {
|
func copyToFileTruncate(srcReader io.Reader, dstPath string, dstMode os.FileMode) error {
|
||||||
logrus.Debug("Copy and truncate ", dstPath)
|
l := logrus.WithField("dstPath", dstPath)
|
||||||
|
l.Debug("Copy and truncate")
|
||||||
|
|
||||||
dstWriter, err := os.OpenFile(filepath.Clean(dstPath), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, dstMode) //nolint:gosec // Cannot guess the safe part of path
|
dstWriter, err := os.OpenFile(filepath.Clean(dstPath), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, dstMode) //nolint:gosec // Cannot guess the safe part of path
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
l.WithError(err).Error("Failed to open destination")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer dstWriter.Close() //nolint:errcheck,gosec
|
defer dstWriter.Close() //nolint:errcheck,gosec
|
||||||
_, err = io.Copy(dstWriter, srcReader)
|
|
||||||
return err
|
if _, err := io.Copy(dstWriter, srcReader); err != nil {
|
||||||
|
l.WithError(err).Error("Failed to open destination")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import (
|
|||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -101,6 +102,7 @@ func (u *Updater) InstallUpdate(ctx context.Context, downloader Downloader, upda
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := u.installer.InstallUpdate(update.Version, bytes.NewReader(b)); err != nil {
|
if err := u.installer.InstallUpdate(update.Version, bytes.NewReader(b)); err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to install update")
|
||||||
return ErrInstall
|
return ErrInstall
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -77,7 +77,7 @@ func (restarter *Restarter) Restart() {
|
|||||||
//nolint:gosec
|
//nolint:gosec
|
||||||
cmd := execabs.Command(
|
cmd := execabs.Command(
|
||||||
restarter.exe,
|
restarter.exe,
|
||||||
xslices.Join(removeFlagWithValue(removeFlag(os.Args[1:], "no-window"), "parent-pid"), restarter.flags)...,
|
xslices.Join(removeFlagsWithValue(removeFlag(os.Args[1:], "no-window"), "parent-pid", "session-id"), restarter.flags)...,
|
||||||
)
|
)
|
||||||
|
|
||||||
l := logrus.WithFields(logrus.Fields{
|
l := logrus.WithFields(logrus.Fields{
|
||||||
@ -157,6 +157,16 @@ func removeFlagWithValue(argList []string, flag string) []string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// removeFlagWithValue removes flags that require a value from a list of command line parameters.
|
||||||
|
// The flags can be of the following form `-flag value`, `--flag value`, `-flag=value` or `--flags=value`.
|
||||||
|
func removeFlagsWithValue(argList []string, flags ...string) []string {
|
||||||
|
for _, flag := range flags {
|
||||||
|
argList = removeFlagWithValue(argList, flag)
|
||||||
|
}
|
||||||
|
|
||||||
|
return argList
|
||||||
|
}
|
||||||
|
|
||||||
func removeFlag(argList []string, flag string) []string {
|
func removeFlag(argList []string, flag string) []string {
|
||||||
return xslices.Filter(argList, func(arg string) bool { return (arg != "-"+flag) && (arg != "--"+flag) })
|
return xslices.Filter(argList, func(arg string) bool { return (arg != "-"+flag) && (arg != "--"+flag) })
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,6 +42,23 @@ func TestRemoveFlagWithValue(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRemoveFlagsWithValue(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
argList []string
|
||||||
|
flags []string
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{[]string{}, []string{"a", "b"}, nil},
|
||||||
|
{[]string{"-a", "-b=value", "-c"}, []string{"b"}, []string{"-a", "-c"}},
|
||||||
|
{[]string{"-a", "--b=value", "-c"}, []string{"b", "c"}, []string{"-a"}},
|
||||||
|
{[]string{"-a", "-b", "value", "-c"}, []string{"c", "b"}, []string{"-a"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
require.Equal(t, removeFlagsWithValue(tt.argList, tt.flags...), tt.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRemoveFlag(t *testing.T) {
|
func TestRemoveFlag(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
argList []string
|
argList []string
|
||||||
|
|||||||
52
utils/coverage.sh
Executable file
52
utils/coverage.sh
Executable file
@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Copyright (c) 2023 Proton AG
|
||||||
|
#
|
||||||
|
# This file is part of Proton Mail Bridge.
|
||||||
|
#
|
||||||
|
# Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
# This script calculates coverage for bridge project
|
||||||
|
#
|
||||||
|
# Output:
|
||||||
|
# stdout : total coverage (to be parsed by Gitlab pipeline)
|
||||||
|
# coverage.xml : Cobertura format of covered lines for coverage visualization in Gitlab
|
||||||
|
|
||||||
|
# Assuming that test coverages from all jobs were put into `./coverage` folder
|
||||||
|
# and passed as artifacts. The flags are:
|
||||||
|
# -covermode=count
|
||||||
|
# -coverpkg=github.com/ProtonMail/proton-bridge/v3/internal/...,github.com/ProtonMail/proton-bridge/v3/pkg/...,
|
||||||
|
# -args -test.gocoverdir=$$PWD/coverage/${TOPIC}
|
||||||
|
|
||||||
|
|
||||||
|
ALLINPUTS="coverage$(printf ",%s" coverage/*)"
|
||||||
|
|
||||||
|
go tool covdata textfmt \
|
||||||
|
-i "$ALLINPUTS" \
|
||||||
|
-o coverage_withGen.out
|
||||||
|
|
||||||
|
# Filter out auto-generated code
|
||||||
|
grep -v '\.pb\.go' coverage_withGen.out > coverage.out
|
||||||
|
|
||||||
|
# Print out coverage
|
||||||
|
go tool cover -func=./coverage.out | grep total:
|
||||||
|
|
||||||
|
# Convert to Cobertura
|
||||||
|
#
|
||||||
|
# NOTE: We are not using the latest `github.com/boumenot/gocover-cobertura`
|
||||||
|
# because it does not support multiplatform coverage in one profile. See
|
||||||
|
# https://github.com/boumenot/gocover-cobertura/pull/3#issuecomment-1571586099
|
||||||
|
go get github.com/t-yuki/gocover-cobertura
|
||||||
|
go run github.com/t-yuki/gocover-cobertura < ./coverage.out > coverage.xml
|
||||||
@ -49,6 +49,8 @@ generate_dep_licenses(){
|
|||||||
sed -i -r '/^github.com\/therecipe\/qt\/internal\/binding\/files\/docs\//d;' "$tmpDepLicenses"
|
sed -i -r '/^github.com\/therecipe\/qt\/internal\/binding\/files\/docs\//d;' "$tmpDepLicenses"
|
||||||
sed -i -r 's|^(.*)/([[:alnum:]-]+)/(v[[:digit:]]+)$|* [\2](https://\1/\2/\3)|g' "$tmpDepLicenses"
|
sed -i -r 's|^(.*)/([[:alnum:]-]+)/(v[[:digit:]]+)$|* [\2](https://\1/\2/\3)|g' "$tmpDepLicenses"
|
||||||
sed -i -r 's|^(.*)/([[:alnum:]-]+)$|* [\2](https://\1/\2)|g' "$tmpDepLicenses"
|
sed -i -r 's|^(.*)/([[:alnum:]-]+)$|* [\2](https://\1/\2)|g' "$tmpDepLicenses"
|
||||||
|
sed -i -r 's|^(.*)/([[:alnum:]-]+).(v[[:digit:]]+)$|* [\2](https://\1/\2.\3)|g' "$tmpDepLicenses"
|
||||||
|
|
||||||
|
|
||||||
## add license file to github links, and others
|
## add license file to github links, and others
|
||||||
sed -i -r '/github.com/s|^(.*(https://[^)]+).*)$|\1 available under [license](\2/blob/master/LICENSE) |g' "$tmpDepLicenses"
|
sed -i -r '/github.com/s|^(.*(https://[^)]+).*)$|\1 available under [license](\2/blob/master/LICENSE) |g' "$tmpDepLicenses"
|
||||||
@ -57,8 +59,14 @@ generate_dep_licenses(){
|
|||||||
sed -i -r '/golang.org\/x/s|^(.*golang.org/x/([^)]+).*)$|\1 available under [license](https://cs.opensource.google/go/x/\2/+/master:LICENSE) |g' "$tmpDepLicenses"
|
sed -i -r '/golang.org\/x/s|^(.*golang.org/x/([^)]+).*)$|\1 available under [license](https://cs.opensource.google/go/x/\2/+/master:LICENSE) |g' "$tmpDepLicenses"
|
||||||
sed -i -r '/google.golang.org\/grpc/s|^(.*)$|\1 available under [license](https://github.com/grpc/grpc-go/blob/master/LICENSE) |g' "$tmpDepLicenses"
|
sed -i -r '/google.golang.org\/grpc/s|^(.*)$|\1 available under [license](https://github.com/grpc/grpc-go/blob/master/LICENSE) |g' "$tmpDepLicenses"
|
||||||
sed -i -r '/google.golang.org\/protobuf/s|^(.*)$|\1 available under [license](https://github.com/protocolbuffers/protobuf/blob/main/LICENSE) |g' "$tmpDepLicenses"
|
sed -i -r '/google.golang.org\/protobuf/s|^(.*)$|\1 available under [license](https://github.com/protocolbuffers/protobuf/blob/main/LICENSE) |g' "$tmpDepLicenses"
|
||||||
|
sed -i -r '/go.uber.org\/goleak/s|^(.*)$|\1 available under [license](https://pkg.go.dev/go.uber.org/goleak?tab=licenses) |g' "$tmpDepLicenses"
|
||||||
|
sed -i -r '/ariga.io\/atlas/s|^(.*)$|\1 available under [license](https://github.com/ariga/atlas/blob/master/LICENSE) |g' "$tmpDepLicenses"
|
||||||
|
sed -i -r '/entgo.io\/ent/s|^(.*)$|\1 available under [license](https://pkg.go.dev/entgo.io/ent?tab=licenses) |g' "$tmpDepLicenses"
|
||||||
|
sed -i -r '/google.golang.org\/genproto/s|^(.*)$|\1 available under [license](https://pkg.go.dev/google.golang.org/genproto?tab=licenses) |g' "$tmpDepLicenses"
|
||||||
|
sed -i -r '/gopkg.in\/yaml\.v3/s|^(.*)$|\1 available under [license](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE) |g' "$tmpDepLicenses"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
check_dependecies(){
|
check_dependecies(){
|
||||||
generate_dep_licenses
|
generate_dep_licenses
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user