mirror of
https://github.com/dreamstarsky/runbin.git
synced 2026-05-15 14:23:07 +00:00
first demo
This commit is contained in:
56
cmd/api/main.go
Normal file
56
cmd/api/main.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"runbin/internal/config"
|
||||
"runbin/internal/controller"
|
||||
"runbin/internal/repository"
|
||||
"runbin/internal/router"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Load configuration
|
||||
cfg := config.LoadApi("config/server.yaml")
|
||||
|
||||
// Initialize storage
|
||||
var store repository.PasteRepository
|
||||
switch cfg.Storage.Type {
|
||||
case "memory":
|
||||
store = repository.NewMemoryPasteStore()
|
||||
case "database":
|
||||
dbStore, err := repository.NewPostgresStore(cfg.Storage.Database.DSN)
|
||||
defer dbStore.Close()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to connect to database: %v", err)
|
||||
}
|
||||
store = dbStore
|
||||
default:
|
||||
log.Fatalf("Unsupported storage type: %s", cfg.Storage.Type)
|
||||
}
|
||||
|
||||
pasteHandler := controller.NewPasteHandler(store)
|
||||
|
||||
// Create router engine
|
||||
engine := gin.Default()
|
||||
engine.SetTrustedProxies(nil)
|
||||
|
||||
// Setup routes
|
||||
router.SetupRoutes(engine, pasteHandler)
|
||||
|
||||
// Configure Gin mode based on environment
|
||||
if cfg.App.Env == "release" {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
} else {
|
||||
gin.SetMode(gin.DebugMode)
|
||||
}
|
||||
|
||||
// Start server with configured port
|
||||
log.Printf("Starting API server in %s mode on port %d", cfg.App.Env, cfg.App.Port)
|
||||
if err := engine.Run(":" + strconv.Itoa(cfg.App.Port)); err != nil {
|
||||
log.Fatalf("Failed to start server: %v", err)
|
||||
}
|
||||
}
|
||||
31
cmd/worker/main.go
Normal file
31
cmd/worker/main.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"runbin/internal/config"
|
||||
"runbin/internal/repository"
|
||||
"runbin/internal/worker"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg := config.LoadWorker("/home/xkm/project/runbin/config/worker.yaml")
|
||||
|
||||
// Initialize storage
|
||||
var store repository.PasteRepository
|
||||
switch cfg.Storage.Type {
|
||||
case "memory":
|
||||
log.Fatal("Worker can't use memory repository at now!")
|
||||
case "database":
|
||||
dbStore, err := repository.NewPostgresStore(cfg.Storage.Database.DSN)
|
||||
defer dbStore.Close()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to connect to database: %v", err)
|
||||
}
|
||||
store = dbStore
|
||||
default:
|
||||
log.Fatalf("Unsupported storage type: %s", cfg.Storage.Type)
|
||||
}
|
||||
|
||||
work := worker.NewWorker(store, cfg)
|
||||
work.Run()
|
||||
}
|
||||
8
config/server.yaml
Normal file
8
config/server.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
app:
|
||||
env: "debug" # release or debug
|
||||
port: 8080
|
||||
|
||||
storage:
|
||||
type: "database" # memory or database
|
||||
database:
|
||||
dsn: "host=localhost port=54320 user=postgres password=password dbname=postgres sslmode=disable"
|
||||
14
config/worker.yaml
Normal file
14
config/worker.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
storage:
|
||||
type: "database"
|
||||
database:
|
||||
dsn: "host=localhost port=54320 user=postgres password=password dbname=postgres sslmode=disable"
|
||||
|
||||
limit:
|
||||
time: 10
|
||||
cpu: 1
|
||||
memory: 512
|
||||
|
||||
process: 16
|
||||
|
||||
name: "猫猫"
|
||||
|
||||
48
go.mod
48
go.mod
@@ -3,35 +3,71 @@ module runbin
|
||||
go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/docker/docker v28.0.4+incompatible
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/spf13/viper v1.20.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.4.14 // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // 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.20.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // 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/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.12.0 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // 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/protobuf v1.36.5 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gotest.tools/v3 v3.5.2 // indirect
|
||||
)
|
||||
|
||||
175
go.sum
175
go.sum
@@ -1,20 +1,47 @@
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
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/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v28.0.4+incompatible h1:JNNkBctYKurkw6FrHfKqY0nKIDf5nrbxjVBtS+cdcok=
|
||||
github.com/docker/docker v28.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
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=
|
||||
@@ -23,69 +50,177 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
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=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
|
||||
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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
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/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
|
||||
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
|
||||
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
|
||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
|
||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
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/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
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.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
|
||||
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
||||
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||
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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.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=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
48
internal/config/LoadApi.go
Normal file
48
internal/config/LoadApi.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type AppConfig struct {
|
||||
Env string
|
||||
Port int
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
DSN string
|
||||
}
|
||||
|
||||
type StorageConfig struct {
|
||||
Type string
|
||||
Database DatabaseConfig
|
||||
}
|
||||
|
||||
type ApiConfig struct {
|
||||
App AppConfig
|
||||
Storage StorageConfig
|
||||
}
|
||||
|
||||
func LoadApi(configFile string) *ApiConfig {
|
||||
v := viper.New()
|
||||
v.SetConfigFile(configFile)
|
||||
v.SetConfigType("yaml")
|
||||
|
||||
// Set default values
|
||||
v.SetDefault("app.env", "development")
|
||||
v.SetDefault("app.port", 8080)
|
||||
v.SetDefault("storage.type", "memory")
|
||||
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
log.Fatalf("Failed to read config file: %v", err)
|
||||
}
|
||||
|
||||
var cfg ApiConfig
|
||||
if err := v.Unmarshal(&cfg); err != nil {
|
||||
log.Fatalf("Failed to unmarshal config: %v", err)
|
||||
}
|
||||
|
||||
return &cfg
|
||||
}
|
||||
43
internal/config/LoadWorker.go
Normal file
43
internal/config/LoadWorker.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type LimitConfig struct {
|
||||
Cpu float32
|
||||
Memory int
|
||||
Time float32
|
||||
}
|
||||
|
||||
type WorkerConfig struct {
|
||||
Storage StorageConfig
|
||||
Limit LimitConfig
|
||||
Process int
|
||||
Name string
|
||||
}
|
||||
|
||||
func LoadWorker(configFile string) *WorkerConfig {
|
||||
v := viper.New()
|
||||
v.SetConfigFile(configFile)
|
||||
v.SetConfigType("yaml")
|
||||
|
||||
v.SetDefault("limit.cpu", 1.0)
|
||||
v.SetDefault("limit.time", 10.0)
|
||||
v.SetDefault("limit.memory", 512*1024)
|
||||
v.SetDefault("process", 1)
|
||||
v.SetDefault("name", "default name")
|
||||
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
log.Fatalf("Failed to read config file: %v", err)
|
||||
}
|
||||
|
||||
var cfg WorkerConfig
|
||||
if err := v.Unmarshal(&cfg); err != nil {
|
||||
log.Fatalf("Failed to unmarshal config: %v", err)
|
||||
}
|
||||
|
||||
return &cfg
|
||||
}
|
||||
76
internal/controller/paste_controller.go
Normal file
76
internal/controller/paste_controller.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"runbin/internal/model"
|
||||
"runbin/internal/repository"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type PasteHandler struct {
|
||||
repo repository.PasteRepository
|
||||
}
|
||||
|
||||
func NewPasteHandler(repo repository.PasteRepository) *PasteHandler {
|
||||
return &PasteHandler{
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *PasteHandler) SubmitPaste(c *gin.Context) {
|
||||
var req model.SubmitRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
paste := &model.Paste{
|
||||
ID: uuid.NewString(),
|
||||
Code: req.Code,
|
||||
Language: req.Language,
|
||||
Stdin: req.Stdin,
|
||||
Status: model.StatusPending,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if err := h.repo.Save(paste); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": "Internal Server Error",
|
||||
})
|
||||
log.Printf("Paste save error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusAccepted, gin.H{
|
||||
"message": "Created",
|
||||
"paste_id": paste.ID,
|
||||
"url": fmt.Sprintf("/api/pastes/%s", paste.ID),
|
||||
})
|
||||
|
||||
if req.Run {
|
||||
go h.repo.DispatchExecutionTask(paste.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *PasteHandler) GetPaste(c *gin.Context) {
|
||||
pasteID := c.Param("id")
|
||||
paste, exists := h.repo.GetByID(pasteID)
|
||||
if !exists {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Paste not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, paste)
|
||||
}
|
||||
|
||||
func (h *PasteHandler) GetLanguages(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"languages": []string{"c++20"},
|
||||
})
|
||||
}
|
||||
21
internal/model/Paste.go
Normal file
21
internal/model/Paste.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Paste struct {
|
||||
ID string
|
||||
Code string `json:"code"`
|
||||
Language string `json:"language"`
|
||||
Stdin string `json:"stdin"`
|
||||
Stdout string `json:"stdout"`
|
||||
Stderr string `json:"stderr"`
|
||||
Status PasteStatus `json:"status"`
|
||||
CompileLog string `json:"compile_log"`
|
||||
ExecutionTimeMs int `json:"execution_time_ms"`
|
||||
MemoryUsageKb int `json:"memory_usage_kb"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
BackEnd string `json:"backend"`
|
||||
}
|
||||
14
internal/model/PasteStatus.go
Normal file
14
internal/model/PasteStatus.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package model
|
||||
|
||||
type PasteStatus string
|
||||
|
||||
const (
|
||||
StatusPending PasteStatus = "pending"
|
||||
StatusRunning PasteStatus = "running"
|
||||
StatusCompileError PasteStatus = "compile error"
|
||||
StatusRuntimeError PasteStatus = "runtime error"
|
||||
StatusTimeLimitExceed PasteStatus = "time limit exceeded"
|
||||
StatusMemoryLimitExceed PasteStatus = "memory limit exceeded"
|
||||
StatusUnknownError PasteStatus = "unknown error"
|
||||
StatusCompleted PasteStatus = "completed"
|
||||
)
|
||||
9
internal/model/SubmitRequest.go
Normal file
9
internal/model/SubmitRequest.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package model
|
||||
|
||||
type SubmitRequest struct {
|
||||
Code string `json:"code" binding:"required"`
|
||||
Language string `json:"language" binding:"required"`
|
||||
Run bool `json:"run"`
|
||||
Stdin string `json:"stdin"`
|
||||
BackEnd string `json:"backend"`
|
||||
}
|
||||
174
internal/repository/database_store.go
Normal file
174
internal/repository/database_store.go
Normal file
@@ -0,0 +1,174 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"runbin/internal/model"
|
||||
"time"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
type PostgresStore struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewPostgresStore(connStr string) (*PostgresStore, error) {
|
||||
db, err := sql.Open("postgres", connStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to database: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := db.PingContext(ctx); err != nil {
|
||||
return nil, fmt.Errorf("database ping failed: %w", err)
|
||||
}
|
||||
|
||||
return &PostgresStore{db: db}, nil
|
||||
}
|
||||
|
||||
func (s *PostgresStore) Save(p *model.Paste) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err := s.db.ExecContext(ctx,
|
||||
`INSERT INTO pastes (
|
||||
id, code, created_at, status,
|
||||
language, stdin, stdout, stderr,
|
||||
execution_time_ms, memory_usage_kb, updated_at, backend,
|
||||
compile_log
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`,
|
||||
p.ID, p.Code, p.CreatedAt, p.Status,
|
||||
p.Language, p.Stdin, p.Stdout, p.Stderr,
|
||||
p.ExecutionTimeMs, p.MemoryUsageKb, p.UpdatedAt, p.BackEnd, p.CompileLog)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *PostgresStore) GetByID(id string) (*model.Paste, bool) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var p model.Paste
|
||||
err := s.db.QueryRowContext(ctx,
|
||||
`SELECT
|
||||
id, code, created_at, status,
|
||||
language, stdin, stdout, stderr,
|
||||
execution_time_ms, memory_usage_kb, updated_at, backend,
|
||||
compile_log
|
||||
FROM pastes WHERE id = $1`, id).Scan(
|
||||
&p.ID,
|
||||
&p.Code,
|
||||
&p.CreatedAt,
|
||||
&p.Status,
|
||||
&p.Language,
|
||||
&p.Stdin,
|
||||
&p.Stdout,
|
||||
&p.Stderr,
|
||||
&p.ExecutionTimeMs,
|
||||
&p.MemoryUsageKb,
|
||||
&p.UpdatedAt,
|
||||
&p.BackEnd,
|
||||
&p.CompileLog)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, false
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
return &p, true
|
||||
}
|
||||
|
||||
func (s *PostgresStore) Close() error {
|
||||
return s.db.Close()
|
||||
}
|
||||
|
||||
func (s *PostgresStore) DispatchExecutionTask(id string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err := s.db.ExecContext(ctx,
|
||||
`INSERT INTO queue (id) VALUES ($1)`,
|
||||
id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *PostgresStore) GetTask(ctx context.Context) (*model.Paste, error) {
|
||||
tx, err := s.db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to begin transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// 原子性地删除并获取队列中最旧的任务ID
|
||||
var taskID string
|
||||
err = tx.QueryRowContext(ctx,
|
||||
`DELETE FROM queue
|
||||
WHERE ctid = (
|
||||
SELECT ctid FROM queue
|
||||
ORDER BY created_at
|
||||
FOR UPDATE SKIP LOCKED
|
||||
LIMIT 1
|
||||
)
|
||||
RETURNING id`).Scan(&taskID)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil // 没有任务时返回nil
|
||||
}
|
||||
return nil, fmt.Errorf("failed to get task: %w", err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, fmt.Errorf("failed to commit transaction: %w", err)
|
||||
}
|
||||
|
||||
// 获取完整的任务数据
|
||||
p, ok := s.GetByID(taskID)
|
||||
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to get task details: %w", err)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (s *PostgresStore) Update(p *model.Paste) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Always update the UpdatedAt timestamp on an update operation
|
||||
p.UpdatedAt = time.Now()
|
||||
|
||||
_, err := s.db.ExecContext(ctx,
|
||||
`UPDATE pastes SET
|
||||
status = $1,
|
||||
stdout = $2,
|
||||
stderr = $3,
|
||||
execution_time_ms = $4,
|
||||
memory_usage_kb = $5,
|
||||
updated_at = $6,
|
||||
backend = $7,
|
||||
compile_log = $8
|
||||
WHERE id = $9; `,
|
||||
p.Status,
|
||||
p.Stdout,
|
||||
p.Stderr,
|
||||
p.ExecutionTimeMs,
|
||||
p.MemoryUsageKb,
|
||||
p.UpdatedAt,
|
||||
p.BackEnd,
|
||||
p.CompileLog,
|
||||
p.ID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute update for paste with id %s: %w", p.ID, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
15
internal/repository/interface.go
Normal file
15
internal/repository/interface.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runbin/internal/model"
|
||||
)
|
||||
|
||||
type PasteRepository interface {
|
||||
Save(p *model.Paste) error
|
||||
Update(p *model.Paste) error
|
||||
GetByID(id string) (*model.Paste, bool)
|
||||
DispatchExecutionTask(id string) error
|
||||
GetTask(ctx context.Context) (*model.Paste, error)
|
||||
}
|
||||
|
||||
50
internal/repository/memory_store.go
Normal file
50
internal/repository/memory_store.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runbin/internal/model"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MemoryPasteStore struct {
|
||||
pastes map[string]*model.Paste
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func NewMemoryPasteStore() *MemoryPasteStore {
|
||||
return &MemoryPasteStore{
|
||||
pastes: make(map[string]*model.Paste),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MemoryPasteStore) Save(p *model.Paste) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
p.UpdatedAt = time.Now()
|
||||
s.pastes[p.ID] = p
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MemoryPasteStore) GetByID(id string) (*model.Paste, bool) {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
p, found := s.pastes[id]
|
||||
return p, found
|
||||
}
|
||||
|
||||
func (s *MemoryPasteStore) DispatchExecutionTask(id string) error {
|
||||
return nil // 内存存储暂不实现队列功能
|
||||
}
|
||||
|
||||
func (s *MemoryPasteStore) GetTask(ctx context.Context) (*model.Paste, error) {
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *MemoryPasteStore) Update(p *model.Paste) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
s.pastes[p.ID] = p
|
||||
return nil
|
||||
}
|
||||
16
internal/router/router.go
Normal file
16
internal/router/router.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"runbin/internal/controller"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SetupRoutes(engine *gin.Engine, handler *controller.PasteHandler) {
|
||||
api := engine.Group("/api")
|
||||
{
|
||||
api.POST("/pastes", handler.SubmitPaste)
|
||||
api.GET("/pastes/:id", handler.GetPaste)
|
||||
api.GET("/languages", handler.GetLanguages)
|
||||
}
|
||||
}
|
||||
152
internal/worker/RunCppTask.go
Normal file
152
internal/worker/RunCppTask.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"runbin/internal/model"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
func (w *Worker) RunCppTask(ctx context.Context, task *model.Paste) error {
|
||||
// Create temporary workspace
|
||||
tmpDir, err := os.MkdirTemp("", "cpp_compile_")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create temp dir error: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Write user code to main.cpp
|
||||
codePath := filepath.Join(tmpDir, "main.cpp")
|
||||
if err := os.WriteFile(codePath, []byte(task.Code), 0644); err != nil {
|
||||
return fmt.Errorf("write code file error: %v", err)
|
||||
}
|
||||
|
||||
// Get execution limits from config
|
||||
timeout := time.Duration(w.cfg.Limit.Time) * time.Second
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
|
||||
// Initialize Docker client
|
||||
cli, err := client.NewClientWithOpts(client.FromEnv)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create docker client: %v", err)
|
||||
}
|
||||
|
||||
hostConfig := &container.HostConfig{
|
||||
Binds: []string{tmpDir + ":/app"},
|
||||
Resources: container.Resources{
|
||||
Memory: int64(w.cfg.Limit.Memory * 1024 * 1024),
|
||||
CPUQuota: int64(w.cfg.Limit.Cpu * 100000),
|
||||
},
|
||||
AutoRemove: true,
|
||||
NetworkMode: "none",
|
||||
}
|
||||
|
||||
// Create compile container configuration
|
||||
resp, err := cli.ContainerCreate(ctx, &container.Config{
|
||||
Image: "gcc:14",
|
||||
Cmd: []string{"sh", "-c", "g++ -std=c++20 /app/main.cpp -o /app/output > /app/compile.txt 2>&1"},
|
||||
}, hostConfig, nil, nil, filepath.Base(tmpDir)+"_builder")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create compile container error: %v", err)
|
||||
}
|
||||
|
||||
// Start compile container execution
|
||||
if err := cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil {
|
||||
return fmt.Errorf("failed to start compile container: %v", err)
|
||||
}
|
||||
|
||||
// Wait for compile container completion
|
||||
statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
|
||||
|
||||
// Handle compile container execution results
|
||||
select {
|
||||
case <-statusCh:
|
||||
// Get compile container detailed state
|
||||
containerState, err := cli.ContainerInspect(ctx, resp.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("container state inspection error: %v", err)
|
||||
}
|
||||
|
||||
// Read compilation log
|
||||
if logData, err := os.ReadFile(filepath.Join(tmpDir, "compile.txt")); err == nil {
|
||||
task.CompileLog = string(logData)
|
||||
}
|
||||
|
||||
// Non-zero exit code indicates compilation failure
|
||||
if containerState.State.ExitCode != 0 {
|
||||
task.Status = model.StatusCompileError
|
||||
return nil
|
||||
}
|
||||
case err := <-errCh:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
task.Status = model.StatusCompileError
|
||||
task.CompileLog = "Compile process exceeded time limit"
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write input to input.txt
|
||||
inputPath := filepath.Join(tmpDir, "input.txt")
|
||||
if err := os.WriteFile(inputPath, []byte(task.Stdin), 0644); err != nil {
|
||||
return fmt.Errorf("write input file error: %v", err)
|
||||
}
|
||||
|
||||
// Create runner container configuration
|
||||
resp, err = cli.ContainerCreate(ctx, &container.Config{
|
||||
Image: "gcc:14",
|
||||
Cmd: []string{"sh", "-c", "sh -c \"/app/output < /app/input.txt > /app/stdout.txt\" > /app/stderr.txt 2>&1"},
|
||||
}, hostConfig, nil, nil, filepath.Base(tmpDir)+"_runner")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create runner container error: %v", err)
|
||||
}
|
||||
|
||||
// Start runner container execution
|
||||
if err := cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil {
|
||||
return fmt.Errorf("failed to start runner container: %v", err)
|
||||
}
|
||||
|
||||
// Wait for container completion
|
||||
statusCh, errCh = cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
|
||||
|
||||
// Handle container execution results
|
||||
select {
|
||||
case <-statusCh:
|
||||
// Get container detailed state
|
||||
containerState, err := cli.ContainerInspect(ctx, resp.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("container state inspection error: %v", err)
|
||||
}
|
||||
|
||||
// Non-zero exit code indicates compilation failure
|
||||
if containerState.State.ExitCode != 0 {
|
||||
task.Status = model.StatusRuntimeError
|
||||
} else {
|
||||
task.Status = model.StatusCompleted
|
||||
}
|
||||
case err := <-errCh:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
task.Status = model.StatusTimeLimitExceed
|
||||
}
|
||||
|
||||
// Process execution results by reading output files
|
||||
stdoutPath := filepath.Join(tmpDir, "stdout.txt")
|
||||
stderrPath := filepath.Join(tmpDir, "stderr.txt")
|
||||
|
||||
// Read program output
|
||||
if outData, err := os.ReadFile(stdoutPath); err == nil {
|
||||
task.Stdout = string(outData)
|
||||
}
|
||||
if outData, err := os.ReadFile(stderrPath); err == nil {
|
||||
task.Stderr = string(outData)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
89
internal/worker/worker.go
Normal file
89
internal/worker/worker.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"runbin/internal/config"
|
||||
"runbin/internal/model"
|
||||
"runbin/internal/repository"
|
||||
)
|
||||
|
||||
type Worker struct {
|
||||
repo repository.PasteRepository
|
||||
cfg *config.WorkerConfig
|
||||
}
|
||||
|
||||
func NewWorker(repo repository.PasteRepository, cfg *config.WorkerConfig) *Worker {
|
||||
return &Worker{
|
||||
repo: repo,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Worker) Run() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// run n process
|
||||
for range w.cfg.Process {
|
||||
go w.processTasks(ctx)
|
||||
}
|
||||
|
||||
<-ctx.Done()
|
||||
}
|
||||
|
||||
func (w *Worker) processTasks(ctx context.Context) {
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
log.Println("Thread start!")
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if task, err := w.repo.GetTask(ctx); err == nil {
|
||||
if task == nil {
|
||||
continue
|
||||
}
|
||||
if err := w.handleTask(ctx, task); err != nil {
|
||||
log.Printf("Worker error at PasteID: %s, error: %v\n", task.ID, err)
|
||||
}
|
||||
if err := w.repo.Update(task); err != nil {
|
||||
log.Printf("Update error at PasteID: %s, error: %v\n", task.ID, err)
|
||||
}
|
||||
log.Printf("Judged task %s", task.ID)
|
||||
} else {
|
||||
log.Printf("Worker get task error: %v\n", err)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Worker) handleTask(ctx context.Context, task *model.Paste) error {
|
||||
log.Printf("Hangling task %s for language %s", task.ID, task.Language)
|
||||
|
||||
task.Status = model.StatusRunning
|
||||
task.BackEnd = w.cfg.Name
|
||||
w.repo.Update(task)
|
||||
|
||||
|
||||
var err error
|
||||
|
||||
switch task.Language {
|
||||
case "c++20":
|
||||
err = w.RunCppTask(ctx, task)
|
||||
default:
|
||||
err = fmt.Errorf("Unsupported language '%s'", task.Language)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
task.Status = model.StatusUnknownError
|
||||
task.CompileLog = err.Error()
|
||||
}
|
||||
return err
|
||||
}
|
||||
192
main.go
192
main.go
@@ -1,192 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type PasteStatus string
|
||||
|
||||
const (
|
||||
StatusPending PasteStatus = "pending"
|
||||
StatusRunning PasteStatus = "running"
|
||||
StatusCompileError PasteStatus = "compile error"
|
||||
StatusRuntimeError PasteStatus = "runtime error"
|
||||
StatusTimeLimitExceed PasteStatus = "time limit exceeded"
|
||||
StatusMemoryLimitExceed PasteStatus = "memory limit exceeded"
|
||||
StatusUnknownError PasteStatus = "unknown error"
|
||||
StatusCompleted PasteStatus = "completed"
|
||||
)
|
||||
|
||||
type Paste struct {
|
||||
ID string
|
||||
Code string
|
||||
Language string
|
||||
Stdin string
|
||||
Stdout string
|
||||
Stderr string
|
||||
Status PasteStatus
|
||||
ExecutionTimeMs int
|
||||
MemoryUsageKb int
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
BackEnd string
|
||||
}
|
||||
|
||||
type SubmitRequest struct {
|
||||
Code string `json:"code" binding:"required"`
|
||||
Language string `json:"language" binding:"required"`
|
||||
Run bool `json:"run"`
|
||||
Stdin string `json:"stdin"`
|
||||
BackEnd string `json:"backend"`
|
||||
}
|
||||
|
||||
type ExecutionTask struct {
|
||||
PasteID string `json:"paste_id"`
|
||||
Code string `json:"code"`
|
||||
Language string `json:"language"`
|
||||
Stdin string `json:"stdin"`
|
||||
}
|
||||
|
||||
// begin
|
||||
|
||||
var (
|
||||
// 使用 map 模拟数据库存储 Paste
|
||||
pastes = make(map[string]*Paste)
|
||||
// 使用读写锁保证并发访问 map 的安全
|
||||
pastesMutex = sync.RWMutex{}
|
||||
)
|
||||
|
||||
// savePaste 将 Paste 保存到内存存储
|
||||
func savePaste(p *Paste) {
|
||||
pastesMutex.Lock() // 获取写锁
|
||||
defer pastesMutex.Unlock() // 函数结束时释放写锁
|
||||
p.UpdatedAt = time.Now()
|
||||
pastes[p.ID] = p
|
||||
}
|
||||
|
||||
// getPasteByID 从内存存储中获取 Paste
|
||||
func getPasteByID(id string) (*Paste, bool) {
|
||||
pastesMutex.RLock() // 获取读锁
|
||||
defer pastesMutex.RUnlock() // 函数结束时释放读锁
|
||||
p, found := pastes[id]
|
||||
// 返回副本以避免外部修改影响存储 (对于指针类型,这仍然是浅拷贝,但对 Paste 结构体本身是安全的)
|
||||
if found {
|
||||
// 创建一个副本返回,避免并发问题
|
||||
// copy := *p
|
||||
// return ©, true
|
||||
// 或者直接返回指针,但调用者不应修改它 (在这个简单例子中暂时直接返回)
|
||||
return p, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// end
|
||||
|
||||
func dispatchExecutionTask(task ExecutionTask) error {
|
||||
// 在实际应用中,这里会连接到 RabbitMQ/Redis/Kafka 等,并将 task 序列化后发布
|
||||
fmt.Printf(" MOCK QUEUE: Dispatching task for Paste ID %s\n", task.PasteID)
|
||||
fmt.Printf(" Language: %s\n", task.Language)
|
||||
fmt.Printf(" Stdin: %s\n", task.Stdin[:min(50, len(task.Code))]+"...")
|
||||
fmt.Printf(" Code: %s\n", task.Code[:min(50, len(task.Code))]+"...") // 打印部分代码
|
||||
// 模拟成功
|
||||
return nil
|
||||
// 实际例子:
|
||||
// taskJSON, err := json.Marshal(task)
|
||||
// if err != nil { return err }
|
||||
// return messageQueueClient.Publish("execution_tasks", taskJSON)
|
||||
}
|
||||
|
||||
func handleSubmitPaste(c *gin.Context) {
|
||||
var req SubmitRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Invalid request body: " + err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
paste := &Paste{
|
||||
ID: uuid.NewString(),
|
||||
Code: req.Code,
|
||||
Language: req.Language,
|
||||
Stdin: req.Stdin,
|
||||
Status: StatusPending,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if !req.Run {
|
||||
paste.Status = StatusCompleted
|
||||
}
|
||||
|
||||
savePaste(paste)
|
||||
log.Printf("Created Paste with ID: %s", paste.ID)
|
||||
|
||||
c.JSON(http.StatusAccepted, gin.H{
|
||||
"message": "Created",
|
||||
"paste_id": paste.ID,
|
||||
"url": fmt.Sprintf("/api/pastes/%s", paste.ID),
|
||||
})
|
||||
|
||||
if !req.Run {
|
||||
return
|
||||
}
|
||||
|
||||
task := ExecutionTask{
|
||||
PasteID: paste.ID,
|
||||
Code: paste.Code,
|
||||
Language: paste.Language,
|
||||
Stdin: paste.Stdin,
|
||||
}
|
||||
|
||||
if err := dispatchExecutionTask(task); err != nil {
|
||||
// 如果分发失败,这通常是一个内部错误
|
||||
log.Printf("Error dispatching task for Paste ID %s: %v", paste.ID, err)
|
||||
paste.Status = StatusUnknownError
|
||||
savePaste(paste) // 更新状态
|
||||
}
|
||||
}
|
||||
|
||||
func handleGetPaste(c *gin.Context) {
|
||||
pasteID := c.Param("id")
|
||||
|
||||
paste, found := getPasteByID(pasteID)
|
||||
if !found {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"error": "Paste not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, paste)
|
||||
}
|
||||
|
||||
func handleGetLanguages(c *gin.Context) {
|
||||
supportedLanguages := []string{
|
||||
"c++20",
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"languages": supportedLanguages,
|
||||
})
|
||||
}
|
||||
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
api := router.Group("/api")
|
||||
{
|
||||
api.POST("/pastes", handleSubmitPaste)
|
||||
api.GET("/pastes/:id", handleGetPaste)
|
||||
api.GET("/languages", handleGetLanguages)
|
||||
}
|
||||
|
||||
port := "8080"
|
||||
log.Printf("Statr API server on post %s", port)
|
||||
if err := router.Run(":" + port); err != nil {
|
||||
log.Fatalf("Failed to run server: %v", err)
|
||||
}
|
||||
}
|
||||
18
migrations/0001_init_pastes.sql
Normal file
18
migrations/0001_init_pastes.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
-- +goose Up
|
||||
CREATE TABLE IF NOT EXISTS pastes (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
code TEXT NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
language VARCHAR(20),
|
||||
stdin TEXT,
|
||||
stdout TEXT,
|
||||
stderr TEXT,
|
||||
status VARCHAR(20) NOT NULL,
|
||||
execution_time_ms INTEGER,
|
||||
memory_usage_kb INTEGER,
|
||||
updated_at TIMESTAMP WITH TIME ZONE,
|
||||
backend VARCHAR(50)
|
||||
);
|
||||
|
||||
-- +goose Down
|
||||
DROP TABLE pastes;
|
||||
11
migrations/0002_create_queue_table.sql
Normal file
11
migrations/0002_create_queue_table.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
-- +goose Up
|
||||
CREATE TABLE IF NOT EXISTS queue (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
locked_at TIMESTAMP,
|
||||
attempts INT NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
-- +goose Down
|
||||
DROP TABLE queue;
|
||||
|
||||
5
migrations/0003_add_compilelog_column.sql
Normal file
5
migrations/0003_add_compilelog_column.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
-- +goose Up
|
||||
ALTER TABLE pastes ADD COLUMN IF NOT EXISTS compile_log TEXT NOT NULL DEFAULT '';
|
||||
|
||||
-- +goose Down
|
||||
ALTER TABLE pastes DROP COLUMN compile_log;
|
||||
Reference in New Issue
Block a user