diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b3ad83d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: Go CI + +on: + pull_request: + push: + branches: + - main + + workflow_dispatch: + inputs: + skip_tests: + description: 'Skip running tests' + required: false + default: false + type: boolean + triggered_user: + description: 'Who triggered the workflow' + required: false + default: "" + type: string + +jobs: + go-ci: + permissions: + id-token: write + contents: write + packages: read + uses: punchh/GHA-central-workflow/.github/workflows/go-ci.yml@main + + with: + repository: ${{ github.repository }} + branch: ${{ github.event_name == 'workflow_dispatch' && github.ref_name || (github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref_name) }} + go_version: 1.24.1 + triggered_user: ${{ github.event_name == 'workflow_dispatch' && inputs.triggered_user || '' }} + skip_tests: ${{ github.event_name == 'workflow_dispatch' && inputs.skip_tests || false }} + test_cases: false + snyk_severity_threshold: "critical" + go_build: true + secrets: + SVC_ACCOUNT_GITHUB_ORG_TOKEN: ${{ secrets.SVC_ACCOUNT_GITHUB_ORG_TOKEN }} + SVC_ACCOUNT_SNYK_ORG_TOKEN: ${{ secrets.SVC_ACCOUNT_SNYK_ORG_TOKEN }} + CAPTAIN_DEVOPS_SLACK_BOT_TOKEN: ${{ secrets.CAPTAIN_DEVOPS_SLACK_BOT_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml deleted file mode 100644 index 07331c2..0000000 --- a/.github/workflows/go.yml +++ /dev/null @@ -1,30 +0,0 @@ -# This workflow will build a golang project -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go - -name: Go - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - pull_request_review: - types: [submitted, dismissed] - -jobs: - - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '1.21.5' - - name: Install dependencies - run: go get . - - name: Build - run: go build -v ./... - - name: Test with the Go CLI - run: go test diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6abb60e..3e5cd7d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -19,9 +19,9 @@ jobs: with: fetch-depth: 0 - run: git fetch --force --tags - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v5 with: - go-version: '1.21.5' + go-version: '1.24' cache: true # More assembly might be required: Docker logins, GPG, etc. It all depends # on your needs. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3e25a1b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM alpine:3.21.3 +RUN apk update \ + && apk --no-cache add ca-certificates \ + && apk --no-cache add -U tzdata \ + && rm -rf /var/cache/apk/* + +WORKDIR /server +COPY server . + +EXPOSE 8080 +CMD ["./server"] diff --git a/README.md b/README.md index ab74d14..c7fa41a 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Codepush server go is compatible with [react-native-code-push](https://github.co ## Support version - [mysql](https://dev.mysql.com/downloads/mysql/) >= 8.0 -- [golang](https://go.dev/dl/) >= 1.21.5 +- [golang](https://go.dev/dl/) >= 1.24 - [redis](https://redis.io/downloads/) >= 5.0 ## Support client version diff --git a/config/app.json b/config/app.json deleted file mode 100644 index f7131c7..0000000 --- a/config/app.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "mode":"prod" -} \ No newline at end of file diff --git a/config/app.prod.json b/config/app.prod.json deleted file mode 100644 index 8aa921b..0000000 --- a/config/app.prod.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "DBUser": { - "Write": { - "UserName": "root", - "Password": "", - "Host": "127.0.0.1", - "Port": 3306, - "DBname": "code-push" - }, - "MaxIdleConns": 10, - "MaxOpenConns": 100, - "ConnMaxLifetime": 1 - }, - "Redis": { - "Host": "127.0.0.1", - "Port": 6379, - "DBIndex": 0, - "UserName": "", - "Password": "" - }, - "CodePush": { - "FileLocal":"local", - "Local":{ - "SavePath":"./bundles" - }, - "Aws":{ - "Endpoint":"", - "Region":"", - "S3ForcePathStyle":true, - "KeyId":"", - "Secret":"", - "Bucket":"" - }, - "Ftp":{ - "ServerUrl":"", - "UserName":"", - "Password":"" - } - }, - "UrlPrefix": "/", - "ResourceUrl": "", - "Port": ":8080", - "TokenExpireTime": 30 -} \ No newline at end of file diff --git a/config/config.go b/config/config.go index f5fb461..2b3d111 100644 --- a/config/config.go +++ b/config/config.go @@ -2,52 +2,14 @@ package config import ( "encoding/json" + "fmt" "os" - "os/exec" - "path/filepath" + "strconv" "strings" -) - -var configFile *appConfig - -func getExcPath() string { - file, _ := exec.LookPath(os.Args[0]) - // 获取包含可执行文件名称的路径 - path, _ := filepath.Abs(file) - // 获取可执行文件所在目录 - index := strings.LastIndex(path, string(os.PathSeparator)) - ret := path[:index] - return strings.Replace(ret, "\\", "/", -1) -} - -func GetConfig() appConfig { - if configFile != nil { - return *configFile - } - path := getExcPath() - var mode modeConfig = readJson[modeConfig](path + "/config/app.json") - - appConfig := readJson[appConfig](path + "/config/app." + mode.Mode + ".json") - configFile = &appConfig - return *configFile -} - -func readJson[T any](path string) T { - file, err := os.Open(path) - if err != nil { - panic(path + " config not found") - } - defer file.Close() - decoder := json.NewDecoder(file) + "sync" - var jsonF T - decoder.Decode(&jsonF) - return jsonF -} - -type modeConfig struct { - Mode string -} + "github.com/go-playground/validator/v10" +) type appConfig struct { DBUser dbConfig @@ -55,8 +17,14 @@ type appConfig struct { CodePush codePush UrlPrefix string Port string - ResourceUrl string + ResourceUrl string `json:"resource_url" validate:"required"` TokenExpireTime int64 + Environment string `json:"environment" validate:"required"` + TenantName string `json:"tenant_name" validate:"required"` + // Observability (same JSON keys as go-email-templates service secrets) + AirbrakeProjectID int64 + AirbrakeProjectKey string + NewRelicLicenseKey string } type dbConfig struct { Write dbConfigObj @@ -65,38 +33,229 @@ type dbConfig struct { ConnMaxLifetime uint } type dbConfigObj struct { - UserName string - Password string - Host string - Port uint - DBname string + UserName string `json:"db_username" validate:"required"` + Password string `json:"db_password" validate:"required"` + Host string `json:"db_host" validate:"required"` + Port uint `json:"db_port" validate:"required"` + DBname string `json:"db_name" validate:"required"` } type redisConfig struct { - Host string - Port uint - DBIndex uint - UserName string - Password string + Host string `json:"redis_host" validate:"required"` + Port uint `json:"redis_port" validate:"required"` + DBIndex uint `json:"redis_db_index"` + UserName string `json:"redis_username"` + Password string `json:"redis_password"` } type codePush struct { - FileLocal string + FileLocal string `json:"build_save_location" validate:"required"` Local localConfig Aws awsConfig Ftp ftpConfig } type awsConfig struct { - Endpoint string - Region string - S3ForcePathStyle bool - KeyId string - Secret string - Bucket string + Endpoint string `json:"aws_s3_endpoint" validate:"required"` + Region string `json:"aws_region" validate:"required"` + S3ForcePathStyle bool `json:"aws_s3_force_path_style" validate:"required"` + KeyId string `json:"aws_access_key_id" validate:"required"` + Secret string `json:"aws_secret_access_key" validate:"required"` + Bucket string `json:"aws_s3_bucket_name" validate:"required"` } type ftpConfig struct { - ServerUrl string - UserName string - Password string + ServerUrl string `json:"ftp_server_url"` + UserName string `json:"ftp_username"` + Password string `ftp_password` } type localConfig struct { - SavePath string + SavePath string `json:"local_build_save_path"` +} + +var config *appConfig +var once sync.Once + +func GetConfig() *appConfig { + once.Do(func() { + config = LoadConfig() + }) + return config +} + +func LoadConfig() *appConfig { + intFormatter := func(v interface{}) (val int64, ok bool) { + switch ta := v.(type) { + case int: + return int64(ta), true + case int64: + return ta, true + case float64: + return int64(ta), true + case string: + parsed, err := strconv.ParseInt(ta, 10, 64) + if err != nil { + return 0, false + } + return parsed, true + default: + return 0, false + } + } + + fmt.Println("Fetching config from AWS secret manager...") + keys := []string{ + "global", // Global secrets + "tenant", // Tendancy punchh-server secrets + "service", // Email template secrets + "db", // DB secrets + } + + var config appConfig + + var dbObj dbConfigObj + var redis redisConfig + var buildSaveLocation codePush + var aws awsConfig + var ftp ftpConfig + + // default values + config.DBUser.MaxIdleConns = 5 + config.DBUser.MaxOpenConns = 20 + config.DBUser.ConnMaxLifetime = 300 + + config.Port = ":8080" + config.UrlPrefix = "/" + config.ResourceUrl = "" + config.TokenExpireTime = 1 //in days + + for _, key := range keys { + key = key + "_secrets" + + data, ok := os.LookupEnv(key) + if !ok { + fmt.Println("config: no secrets found for - ", key) + continue + } + + secrets := make(map[string]interface{}) + if err := json.Unmarshal([]byte(data), &secrets); err != nil { + fmt.Println("config: error unmarshalling secrets for - ", key) + panic(err) + } + + for k, v := range secrets { + k = strings.ToLower(k) + fmt.Println(k) + // DB + if k == "db_username" { + dbObj.UserName = v.(string) + } + if k == "db_password" { + dbObj.Password = v.(string) + } + if k == "db_host" { + dbObj.Host = v.(string) + } + if k == "db_port" { + u64, _ := strconv.ParseUint(v.(string), 10, 32) + dbObj.Port = uint(u64) + } + if k == "db_name" { + dbObj.DBname = v.(string) + } + + // Redis + if k == "redis_host" { + redis.Host = v.(string) + } + if k == "redis_port" { + u64, _ := strconv.ParseUint(v.(string), 10, 32) + redis.Port = uint(u64) + } + if k == "redis_db_index" { + u64, _ := strconv.ParseUint(v.(string), 10, 32) + redis.DBIndex = uint(u64) + } + if k == "redis_username" { + redis.UserName = v.(string) + } + if k == "redis_password" { + redis.Password = v.(string) + } + + // local bundle save location + if k == "build_save_location" { + buildSaveLocation.FileLocal = v.(string) + } + + // AWS + if k == "aws_s3_endpoint" { + aws.Endpoint = v.(string) + } + if k == "aws_region" { + aws.Region = v.(string) + } + if k == "aws_s3_force_path_style" { + aws.S3ForcePathStyle = true + } + if k == "aws_access_key_id" { + aws.KeyId = v.(string) + } + if k == "aws_secret_access_key" { + aws.Secret = v.(string) + } + if k == "aws_s3_bucket_name" { + aws.Bucket = v.(string) + } + + // ftp + if k == "ftp_server_url" { + ftp.ServerUrl = v.(string) + } + if k == "ftp_username" { + ftp.UserName = v.(string) + } + if k == "ftp_password" { + ftp.Password = v.(string) + } + // common + + // if build_save_location is set to `local` then resource URL should the self server URL + // if build_save_location is set to `aws` then resource URL should the AWS S3 bucket URL + if k == "resource_url" { + config.ResourceUrl = v.(string) + } + if k == "tenant_name" { + config.TenantName = v.(string) + } + + if k == "environment" { + config.Environment = v.(string) + } + + if k == "airbrake_project_id" { + if parsed, ok := intFormatter(v); ok { + config.AirbrakeProjectID = parsed + } else { + fmt.Printf("config: invalid airbrake_project_id value (%T): %v\n", v, v) + } + } + if k == "airbrake_project_key" { + config.AirbrakeProjectKey = v.(string) + } + if k == "newrelic_license_key" { + config.NewRelicLicenseKey = v.(string) + } + } + } + config.DBUser.Write = dbObj + config.Redis = redis + config.CodePush = buildSaveLocation + config.CodePush.Aws = aws + config.CodePush.Ftp = ftp + + // validate the config + validate := validator.New() + if err := validate.Struct(config); err != nil { + fmt.Println("config: invalid/missing configuration", err) + panic(err) + } + return &config } diff --git a/db/db.go b/db/db.go index 63b6daa..2671a48 100644 --- a/db/db.go +++ b/db/db.go @@ -5,6 +5,8 @@ import ( "time" "com.lc.go.codepush/server/config" + + _ "github.com/newrelic/go-agent/v3/integrations/nrmysql" "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/logger" @@ -20,7 +22,14 @@ func GetUserDB() (odb *gorm.DB, err error) { dbConfig := config.GetConfig().DBUser dsnSource := dbConfig.Write.UserName + ":" + dbConfig.Write.Password + "@tcp(" + dbConfig.Write.Host + ":" + strconv.Itoa(int(dbConfig.Write.Port)) + ")/" + dbConfig.Write.DBname + "?charset=utf8mb4&parseTime=True&loc=Local" - db, err := gorm.Open(mysql.Open(dsnSource), &gorm.Config{ + // New Relic datastore instrumentation: + // Using the "nrmysql" driver enables MySQL metrics. To associate DB segments with the + // current HTTP transaction, queries must run with the request context + // (e.g. via `db.WithContext(c.Request.Context())` in handlers). + db, err := gorm.Open(mysql.New(mysql.Config{ + DriverName: "nrmysql", + DSN: dsnSource, + }), &gorm.Config{ Logger: logger.Default.LogMode(logger.Error), }) if err != nil { diff --git a/go.mod b/go.mod index 8c3414d..359f3e4 100644 --- a/go.mod +++ b/go.mod @@ -1,40 +1,55 @@ module com.lc.go.codepush/server -go 1.21.5 +go 1.24 -require gorm.io/driver/mysql v1.5.6 +require ( + github.com/go-playground/validator/v10 v10.19.0 + github.com/jlaffaye/ftp v0.2.0 + github.com/newrelic/go-agent/v3/integrations/nrgin v1.2.1 + github.com/newrelic/go-agent/v3/integrations/nrmysql v1.2.2 + github.com/punchh/go-packages v1.2.1 + github.com/sirupsen/logrus v1.9.4 + gorm.io/driver/mysql v1.5.6 +) require ( + github.com/airbrake/gobrake/v5 v5.6.2 // indirect github.com/bytedance/sonic v1.11.3 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/caio/go-tdigest/v4 v4.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.19.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/jlaffaye/ftp v0.2.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/jonboulle/clockwork v0.5.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/newrelic/go-agent/v3 v3.24.0 // indirect github.com/pelletier/go-toml/v2 v2.2.0 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect golang.org/x/arch v0.7.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/net v0.22.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect + google.golang.org/grpc v1.54.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) @@ -47,5 +62,5 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/redis/go-redis/v9 v9.5.1 - gorm.io/gorm v1.25.9 // indirect + gorm.io/gorm v1.25.9 ) diff --git a/go.sum b/go.sum index 96b83ba..5ba206f 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,20 @@ +github.com/airbrake/gobrake/v5 v5.6.2 h1:/LLjm0B3Jy3gqg17VpeGEnSTRqiIElGDy3RZy9s7+MU= +github.com/airbrake/gobrake/v5 v5.6.2/go.mod h1:7lOWiGlpBnOnmWdz7BJzY/shByCkuAIL4LvHbGBKeZs= github.com/aws/aws-sdk-go v1.51.24 h1:nwL5MaommPkwb7Ixk24eWkdx5HY4of1gD10kFFVAl6A= github.com/aws/aws-sdk-go v1.51.24/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= -github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/caio/go-tdigest/v4 v4.0.1 h1:sx4ZxjmIEcLROUPs2j1BGe2WhOtHD6VSe6NNbBdKYh4= +github.com/caio/go-tdigest/v4 v4.0.1/go.mod h1:Wsa+f0EZnV2gShdj1adgl0tQSoXRxtM0QioTgukFw8U= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= @@ -17,33 +22,38 @@ github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLI github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/gin-contrib/gzip v1.0.0 h1:UKN586Po/92IDX6ie5CWLgMI81obiIp5nSP85T3wlTk= github.com/gin-contrib/gzip v1.0.0/go.mod h1:CtG7tQrPB3vIBo6Gat9FVUsis+1emjvQqd66ME5TdnE= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= -github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -60,21 +70,20 @@ github.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg= github.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= +github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353 h1:X/79QL0b4YJVO5+OsPH9rF2u428CIrGL/jLmPsoOQQ4= +github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353/go.mod h1:N0SVk0uhy+E1PZ3C9ctsPRlvOPAFPkCNlcPBDkt0N3U= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -82,13 +91,28 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/newrelic/go-agent/v3 v3.24.0 h1:DPfbd+p0akRjv6UpWzWJl+pfOMSs+QkAeNRUp0fPLZI= +github.com/newrelic/go-agent/v3 v3.24.0/go.mod h1:7GnP0o5ZwEsnC001iDSoZRJ63jS6AtoAOggpg5XVJh8= +github.com/newrelic/go-agent/v3/integrations/nrgin v1.2.1 h1:re7DEe0rP5oek23/0N1aFfdtH5h2yBk8JhmLZvYAUqo= +github.com/newrelic/go-agent/v3/integrations/nrgin v1.2.1/go.mod h1:nXd6QMW8iuY9U/bQSXpjRLbMdCnDaydncooVLqzxygA= +github.com/newrelic/go-agent/v3/integrations/nrmysql v1.2.2 h1:JtaJdL4y1hj5mH0JA2XIIIZtOsivsCmG0wsp3cGtoNo= +github.com/newrelic/go-agent/v3/integrations/nrmysql v1.2.2/go.mod h1:0JZ1gqlaBi9FUrQsg9LLZR357oDH4fGYYTbQQPhOd8o= +github.com/onsi/ginkgo/v2 v2.7.0 h1:/XxtEV3I3Eif/HobnVx9YmJgk8ENdRsuUmM+fLCFNow= +github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= +github.com/onsi/gomega v1.24.2 h1:J/tulyYK6JwBldPViHJReihxxZ+22FHs0piGjQAvoUE= +github.com/onsi/gomega v1.24.2/go.mod h1:gs3J10IS7Z7r7eXRoNJIrNqU4ToQukCJhFtKrWgHWnk= github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/punchh/go-packages v1.2.1 h1:y7HWLFNVXh23wGNk0I6AXiitNy/UOd01EQqVq6H4sJI= +github.com/punchh/go-packages v1.2.1/go.mod h1:DZ5pM4qI81hZOHx54PPw4k5DM0Y8a52kn9D8dajfMCA= github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8= github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -98,54 +122,45 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= -golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +gonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E= +gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8= gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= -gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8= gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= diff --git a/main.go b/main.go index c2da58d..593136a 100644 --- a/main.go +++ b/main.go @@ -2,26 +2,75 @@ package main import ( "fmt" + "os" + "strconv" "com.lc.go.codepush/server/config" - "com.lc.go.codepush/server/middleware" + localmw "com.lc.go.codepush/server/middleware" "com.lc.go.codepush/server/request" "github.com/gin-contrib/gzip" - "github.com/gin-gonic/gin" + "github.com/newrelic/go-agent/v3/integrations/nrgin" + punchhmw "github.com/punchh/go-packages/middleware" + "github.com/sirupsen/logrus" ) func main() { fmt.Println("code-push-server-go V1.0.5") // gin.SetMode(gin.ReleaseMode) - g := gin.Default() - g.Use(gzip.Gzip(gzip.DefaultCompression)) - g.Use(middleware.Recover) + + logger := punchhmw.NewLogger(logrus.StandardLogger()) configs := config.GetConfig() + // Airbrake on logrus errors (same hook as go-email-templates / go-packages) + airPID, airKey := configs.AirbrakeProjectID, configs.AirbrakeProjectKey + if airPID == 0 { + if s := os.Getenv("AIRBRAKE_PROJECT_ID"); s != "" { + airPID, _ = strconv.ParseInt(s, 10, 64) + } + } + if airKey == "" { + airKey = os.Getenv("AIRBRAKE_PROJECT_KEY") + } + if airPID > 0 && airKey != "" { + abHook := punchhmw.NewAirbrake(configs.Environment, airPID, airKey) + logger.AddHook(abHook) + localmw.SetAirbrakeHook(abHook) + } + + nrKey := configs.NewRelicLicenseKey + if nrKey == "" { + nrKey = os.Getenv("NEW_RELIC_LICENSE_KEY") + } + + var nr *punchhmw.NewRelic + if nrKey != "" { + var err error + nr, err = punchhmw.Newnewrelic(configs.Environment, "code-push-server-go", nrKey) + if err != nil { + logger.Println("newrelic init:", err) + } + } + + logger.Println("starting HTTP server") + + g := gin.New() + g.Use(gin.Logger()) + if nr != nil && nr.Application != nil { + g.Use(nrgin.Middleware(nr.Application)) + } + g.Use(gzip.Gzip(gzip.DefaultCompression)) + g.Use(localmw.Recover) + // g.Static("/bundels", "bundels") + g.GET("/ping", func(c *gin.Context) { + c.JSON(200, gin.H{ + "message": "pong", + }) + }) + g.GET("/v0.1/public/codepush/update_check", request.Client{}.CheckUpdate) g.POST("/v0.1/public/codepush/report_status/deploy", request.Client{}.ReportStatus) g.POST("/v0.1/public/codepush/report_status/download", request.Client{}.Download) @@ -30,7 +79,7 @@ func main() { { apiGroup.POST("/login", request.User{}.Login) } - authApi := apiGroup.Use(middleware.CheckToken) + authApi := apiGroup.Use(localmw.CheckToken) { authApi.POST("/createApp", request.App{}.CreateApp) authApi.POST("/createDeployment", request.App{}.CreateDeployment) diff --git a/middleware/middleware.go b/middleware/middleware.go index c04e315..4f0ab28 100644 --- a/middleware/middleware.go +++ b/middleware/middleware.go @@ -10,8 +10,15 @@ import ( "com.lc.go.codepush/server/model/constants" "com.lc.go.codepush/server/utils" "github.com/gin-gonic/gin" + punchhmw "github.com/punchh/go-packages/middleware" ) +var airbrakeHook *punchhmw.Airbrake + +func SetAirbrakeHook(hook *punchhmw.Airbrake) { + airbrakeHook = hook +} + // 檢查token func CheckToken(ctx *gin.Context) { var token, _ = ctx.Cookie("token") @@ -54,6 +61,9 @@ func Recover(c *gin.Context) { // 加载defer异常处理 defer func() { if err := recover(); err != nil { + if airbrakeHook != nil { + airbrakeHook.Notify(err, c.Request) + } c.Writer.WriteHeader(http.StatusInternalServerError) log.Printf("Error:%s", err) // 返回统一的Json风格 @@ -79,4 +89,14 @@ func Recover(c *gin.Context) { } //继续操作 c.Next() + + // Report 5xx errors after handler execution (non-panic paths). + if c.Writer.Status() >= http.StatusInternalServerError && len(c.Errors) > 0 { + for _, e := range c.Errors { + if airbrakeHook != nil { + airbrakeHook.Notify(e.Err, c.Request) + } + log.Printf("server error: %v", e.Err) + } + } } diff --git a/request/app.go b/request/app.go index c16623e..55f9533 100644 --- a/request/app.go +++ b/request/app.go @@ -5,6 +5,7 @@ import ( "log" "net/http" "os" + "path" "com.lc.go.codepush/server/config" "com.lc.go.codepush/server/db" @@ -197,7 +198,7 @@ func (App) UploadBundle(ctx *gin.Context) { if err != nil { log.Panic(err.Error()) } - os.WriteFile(config.CodePush.Local.SavePath+"/"+key, buf.Bytes(), 0777) + os.WriteFile(path.Clean(config.CodePush.Local.SavePath+"/"+key), buf.Bytes(), 0777) case "aws": s3Config := &aws.Config{ Credentials: credentials.NewStaticCredentials(config.CodePush.Aws.KeyId, config.CodePush.Aws.Secret, ""), diff --git a/request/client.go b/request/client.go index 1b7c866..e93902b 100644 --- a/request/client.go +++ b/request/client.go @@ -4,12 +4,17 @@ import ( "log" "net/http" "strconv" + "time" "com.lc.go.codepush/server/config" "com.lc.go.codepush/server/db/redis" "com.lc.go.codepush/server/model" "com.lc.go.codepush/server/model/constants" "com.lc.go.codepush/server/utils" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" "github.com/gin-gonic/gin" ) @@ -59,10 +64,29 @@ func (Client) CheckUpdate(ctx *gin.Context) { updateInfoRedis.PackageHash = *packag.Hash updateInfoRedis.PackageSize = *packag.Size updateInfoRedis.IsAvailable = true - updateInfoRedis.IsMandatory = true + updateInfoRedis.IsMandatory = false label := strconv.Itoa(*packag.Id) updateInfoRedis.Label = label - updateInfoRedis.DownloadUrl = config.ResourceUrl + *packag.Download + + s3Config := &aws.Config{ + Credentials: credentials.NewStaticCredentials(config.CodePush.Aws.KeyId, config.CodePush.Aws.Secret, ""), + Endpoint: aws.String(config.CodePush.Aws.Endpoint), + Region: aws.String(config.CodePush.Aws.Region), + S3ForcePathStyle: aws.Bool(config.CodePush.Aws.S3ForcePathStyle), + } + newSession, _ := session.NewSession(s3Config) + + s3Client := s3.New(newSession) + request, _ := s3Client.GetObjectRequest(&s3.GetObjectInput{ + Bucket: aws.String(config.CodePush.Aws.Bucket), + Key: aws.String(*packag.Download), + }) + + resourceURL, err := request.Presign(24 * time.Hour) // 24 hours + if err != nil { + log.Panic("Failed to sign request", err) + } + updateInfoRedis.DownloadUrl = resourceURL if packag.Description != nil { updateInfoRedis.Description = *packag.Description } @@ -73,7 +97,7 @@ func (Client) CheckUpdate(ctx *gin.Context) { if deploymentVersionNew != nil { updateInfoRedis.NewVersion = *deploymentVersionNew.AppVersion } - redis.SetRedisObj(redisKey, updateInfoRedis, -1) + redis.SetRedisObj(redisKey, updateInfoRedis, time.Duration(24*time.Hour-(10*time.Second))) } if updateInfoRedis.PackageHash != "" { if updateInfoRedis.PackageHash != packageHash && appVersion == updateInfoRedis.TargetBinaryRange { @@ -81,7 +105,7 @@ func (Client) CheckUpdate(ctx *gin.Context) { updateInfo.PackageHash = updateInfoRedis.PackageHash updateInfo.PackageSize = updateInfoRedis.PackageSize updateInfo.IsAvailable = true - updateInfo.IsMandatory = true + updateInfo.IsMandatory = false updateInfo.Label = updateInfoRedis.Label updateInfo.DownloadUrl = updateInfoRedis.DownloadUrl updateInfo.Description = updateInfoRedis.Description