diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 35ff17e..9fbdcf2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,6 +22,8 @@ jobs: tests: runs-on: parity-ubuntu + env: + DOCKER_API_VERSION: '1.44' steps: - name: Checkout code uses: actions/checkout@v4 @@ -78,7 +80,7 @@ jobs: environment: ${{ github.event_name == 'workflow_dispatch' && 'main' || null }} env: REGISTRY_PATH: docker.io/${{ github.event_name == 'workflow_dispatch' && 'paritytech' || 'paritypr' }}/cattery - VERSION: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name || github.sha }} + VERSION: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name || github.event.pull_request.head.sha }} EXTRA_TAG: ${{ github.event_name == 'workflow_dispatch' && 'latest' || 'main' }} steps: - uses: actions/checkout@v4 diff --git a/Dockerfile b/Dockerfile index bb5b64a..84ee267 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.24-alpine AS builder +FROM golang:1.25.5-alpine AS builder ARG CATTERY_VERSION="0.0.0" diff --git a/src/agent/Watchers/fileWatcher.go b/src/agent/Watchers/fileWatcher.go new file mode 100644 index 0000000..b5b2a5d --- /dev/null +++ b/src/agent/Watchers/fileWatcher.go @@ -0,0 +1,66 @@ +package Watchers + +import ( + "cattery/agent/shutdownEvents" + "cattery/lib/messages" + "context" + "os" + + "github.com/fsnotify/fsnotify" + log "github.com/sirupsen/logrus" +) + +var filename = "./shutdown_file" + +func WatchFile(ctx context.Context) { + go func() { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Fatal(err) + } + defer watcher.Close() + + createFile(filename) + + err = watcher.Add(filename) + if err != nil { + log.Fatal(err) + } + + var message string + + if ctx == nil { + ctx = context.Background() + } + + select { + case <-ctx.Done(): + return + case event := <-watcher.Events: + if event.Op.Has(fsnotify.Write) { + message = "Modified file: " + event.Name + } + if event.Op.Has(fsnotify.Remove) { + message = "Removed file: " + event.Name + } + if event.Op.Has(fsnotify.Rename) { + message = "Renamed file: " + event.Name + } + case err := <-watcher.Errors: + message = "File error: " + err.Error() + log.Error(message) + } + + log.Info(message) + + shutdownEvents.Emit(messages.UnregisterReasonPreempted, message) + }() +} + +func createFile(filename string) { + f, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND, 0644) + if err != nil { + log.Fatal(err) + } + f.Close() +} diff --git a/src/agent/Watchers/pingWatcher.go b/src/agent/Watchers/pingWatcher.go new file mode 100644 index 0000000..1979614 --- /dev/null +++ b/src/agent/Watchers/pingWatcher.go @@ -0,0 +1,48 @@ +package Watchers + +import ( + "cattery/agent/catteryClient" + "cattery/agent/shutdownEvents" + "cattery/lib/messages" + "context" + "time" + + log "github.com/sirupsen/logrus" +) + +func WatchPing(ctx context.Context, client *catteryClient.CatteryClient) { + go func() { + var msg string + var finished = false + + if ctx == nil { + ctx = context.Background() + } + + for !finished { + select { + case <-ctx.Done(): // selected when context is canceled or times out + msg = "cattery client shutdown" + finished = true + break + default: + pingResponse, err := client.Ping() + if err != nil { + msg = "error pinging controller: " + err.Error() + log.Error(msg) + continue + } + + if pingResponse.Terminate { + msg = "controller ping receive 'terminate': " + pingResponse.Message + finished = true + break + } + + time.Sleep(60 * time.Second) + } + } + + shutdownEvents.Emit(messages.UnregisterReasonControllerKill, msg) + }() +} diff --git a/src/agent/Watchers/signalWather.go b/src/agent/Watchers/signalWather.go new file mode 100644 index 0000000..ba80a7d --- /dev/null +++ b/src/agent/Watchers/signalWather.go @@ -0,0 +1,25 @@ +package Watchers + +import ( + "cattery/agent/shutdownEvents" + "cattery/lib/messages" + "os" + "os/signal" + "syscall" + + log "github.com/sirupsen/logrus" +) + +func WatchSignal() { + go func() { + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT) + signal.Notify(sigs, syscall.SIGTERM) + signal.Notify(sigs, syscall.SIGKILL) + + sig := <-sigs + log.Info("Got signal ", sig) + + shutdownEvents.Emit(messages.UnregisterReasonSigTerm, "Got signal "+sig.String()) + }() +} diff --git a/src/agent/agent.go b/src/agent/agent.go index d50bbda..fe571ac 100644 --- a/src/agent/agent.go +++ b/src/agent/agent.go @@ -1,17 +1,17 @@ package agent import ( + "cattery/agent/Watchers" + "cattery/agent/catteryClient" + "cattery/agent/githubListener" + "cattery/agent/shutdownEvents" "cattery/agent/tools" "cattery/lib/agents" - "cattery/lib/messages" - "os" - "os/exec" - "os/signal" + "context" "path" "sync" - "syscall" - "github.com/sirupsen/logrus" + log "github.com/sirupsen/logrus" ) var RunnerFolder string @@ -26,8 +26,8 @@ func Start() { type CatteryAgent struct { mutex sync.Mutex - logger *logrus.Entry - catteryClient *CatteryClient + logger *log.Entry + catteryClient *catteryClient.CatteryClient agent *agents.Agent agentId string @@ -38,8 +38,8 @@ type CatteryAgent struct { func NewCatteryAgent(runnerFolder string, catteryServerUrl string, agentId string) *CatteryAgent { return &CatteryAgent{ mutex: sync.Mutex{}, - logger: logrus.WithFields(logrus.Fields{"name": "agent", "agentId": agentId}), - catteryClient: createClient(catteryServerUrl), + logger: log.WithFields(log.Fields{"name": "agent", "agentId": agentId}), + catteryClient: createClient(catteryServerUrl, agentId), listenerExecPath: path.Join(runnerFolder, "bin", "Runner.Listener"), agentId: agentId, interrupted: false, @@ -60,76 +60,28 @@ func (a *CatteryAgent) Start() { a.logger.Info("Agent registered, starting Listener") - var commandRun = exec.Command(a.listenerExecPath, "run", "--jitconfig", *jitConfig) - commandRun.Stdout = os.Stdout - commandRun.Stderr = os.Stderr + Watchers.WatchSignal() + Watchers.WatchFile(context.Background()) + Watchers.WatchPing(context.Background(), a.catteryClient) - go func() { - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGINT) - signal.Notify(sigs, syscall.SIGTERM) - signal.Notify(sigs, syscall.SIGKILL) + var ghListener = githubListener.NewGithubListener(a.listenerExecPath) + ghListener.Start(jitConfig) - sig := <-sigs - a.logger.Info("Got signal ", sig) + // blocking call + var event = shutdownEvents.WaitEvent() - a.interrupt(commandRun.Process) - }() + a.logger.Infof("Received shutdown event: %s, reason: %d", event.Message, event.Reason) - err = commandRun.Run() - if err != nil { - var errMsg = "Runner failed: " + err.Error() - a.logger.Error(errMsg) - } - - a.stop() -} - -func (a *CatteryAgent) interrupt(runnerProcess *os.Process) { - a.mutex.Lock() - defer a.mutex.Unlock() - - if a.interrupted { - return - } - - a.logger.Info("Interrupting runner") - err := a.catteryClient.InterruptAgent(a.agent) - if err != nil { - var errMsg = "Failed to interrupt agent: " + err.Error() - a.logger.Error(errMsg) - } - // SIGINT from go kills listener immediately, so we use pkill - var commandInterruptRun = exec.Command("pkill", "--signal", "SIGINT", "Runner.Listener") - err = commandInterruptRun.Run() - if err != nil { - var errMsg = "Failed to interrupt runner: " + err.Error() - a.logger.Error(errMsg) - } - // TODO: debug why SIGINT does not work correctly - // err := runnerProcess.Signal(syscall.SIGINT) - // if err != nil { - // var errMsg = "Failed to stop runner: " + err.Error() - // a.logger.Error(errMsg) - // } - - a.interrupted = true + ghListener.Stop() + a.stop(event) } // stop stops the runner process -func (a *CatteryAgent) stop() { +func (a *CatteryAgent) stop(event shutdownEvents.ShutdownEvent) { - var reason messages.UnregisterReason - - if a.interrupted { - reason = messages.UnregisterReasonPreempted - a.logger.Infof("Runner has been interrupted") - } else { - reason = messages.UnregisterReasonDone - a.logger.Infof("Runner has finished its job") - } + log.Infof("Stopping Cattery Agent with reason: %d, message: `%s`", event.Reason, event.Message) - err := a.catteryClient.UnregisterAgent(a.agent, reason) + err := a.catteryClient.UnregisterAgent(a.agent, event.Reason, event.Message) if err != nil { var errMsg = "Failed to unregister agent: " + err.Error() a.logger.Error(errMsg) @@ -142,6 +94,6 @@ func (a *CatteryAgent) stop() { } // createClient creates a new http client -func createClient(baseUrl string) *CatteryClient { - return NewCatteryClient(baseUrl) +func createClient(baseUrl string, agentId string) *catteryClient.CatteryClient { + return catteryClient.NewCatteryClient(baseUrl, agentId) } diff --git a/src/agent/client.go b/src/agent/catteryClient/client.go similarity index 66% rename from src/agent/client.go rename to src/agent/catteryClient/client.go index ead0be0..c245743 100644 --- a/src/agent/client.go +++ b/src/agent/catteryClient/client.go @@ -1,4 +1,4 @@ -package agent +package catteryClient import ( "bytes" @@ -6,9 +6,11 @@ import ( "cattery/lib/messages" "encoding/json" "errors" + "fmt" "io" "net/http" "net/url" + "strings" "github.com/sirupsen/logrus" ) @@ -17,13 +19,15 @@ type CatteryClient struct { httpClient *http.Client baseURL string logger *logrus.Entry + agentId string } -func NewCatteryClient(baseURL string) *CatteryClient { +func NewCatteryClient(baseURL string, agentId string) *CatteryClient { return &CatteryClient{ httpClient: &http.Client{}, baseURL: baseURL, logger: logrus.WithField("name", "catteryClient"), + agentId: agentId, } } @@ -46,6 +50,8 @@ func (c *CatteryClient) RegisterAgent(id string) (*agents.Agent, *string, error) return nil, nil, err } + defer response.Body.Close() + if response.StatusCode != http.StatusOK { bodyBytes, _ := io.ReadAll(response.Body) return nil, nil, errors.New("response status code: " + response.Status + " body: " + string(bodyBytes)) @@ -61,13 +67,14 @@ func (c *CatteryClient) RegisterAgent(id string) (*agents.Agent, *string, error) } // UnregisterAgent sends a POST request to the Cattery server to unregister the agent -func (c *CatteryClient) UnregisterAgent(agent *agents.Agent, reason messages.UnregisterReason) error { +func (c *CatteryClient) UnregisterAgent(agent *agents.Agent, reason messages.UnregisterReason, message string) error { var client = c.httpClient requestJson, err := json.Marshal(messages.UnregisterRequest{ - Agent: *agent, - Reason: reason, + Agent: *agent, + Reason: reason, + Message: message, }) if err != nil { return err @@ -84,6 +91,8 @@ func (c *CatteryClient) UnregisterAgent(agent *agents.Agent, reason messages.Unr return err } + defer response.Body.Close() + if response.StatusCode != http.StatusOK { bodyBytes, _ := io.ReadAll(response.Body) return errors.New("response status code: " + response.Status + " body: " + string(bodyBytes)) @@ -92,6 +101,29 @@ func (c *CatteryClient) UnregisterAgent(agent *agents.Agent, reason messages.Unr return nil } +func (c *CatteryClient) Ping() (*messages.PingResponse, error) { + + var response, err = c.get("/agent", "ping", c.agentId) + if err != nil { + return nil, errors.New("get error: " + err.Error()) + } + + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(response.Body) + return nil, errors.New("response status code: " + response.Status + " body: " + string(bodyBytes)) + } + + var pingResponse = &messages.PingResponse{} + err = json.NewDecoder(response.Body).Decode(pingResponse) + if err != nil { + return nil, errors.New("error decoding ping response: " + err.Error()) + } + + return pingResponse, nil +} + func (c *CatteryClient) InterruptAgent(agent *agents.Agent) error { var client = c.httpClient @@ -114,6 +146,8 @@ func (c *CatteryClient) InterruptAgent(agent *agents.Agent) error { return err } + defer response.Body.Close() + if response.StatusCode != http.StatusOK { bodyBytes, _ := io.ReadAll(response.Body) return errors.New("response status code: " + response.Status + " body: " + string(bodyBytes)) @@ -121,3 +155,19 @@ func (c *CatteryClient) InterruptAgent(agent *agents.Agent) error { return nil } + +// get +func (c *CatteryClient) get(path ...string) (*http.Response, error) { + client := c.httpClient + requestUrl, err := url.JoinPath(c.baseURL, path...) + if err != nil { + return nil, errors.New(fmt.Sprintf("failed to join path %s, %s", strings.Join(path, " "), err.Error())) + } + + response, err := client.Get(requestUrl) + if err != nil { + return nil, errors.New(fmt.Sprintf("failed to do request %s, %s", requestUrl, err.Error())) + } + + return response, nil +} diff --git a/src/agent/githubListener/githubListener.go b/src/agent/githubListener/githubListener.go new file mode 100644 index 0000000..def22a0 --- /dev/null +++ b/src/agent/githubListener/githubListener.go @@ -0,0 +1,73 @@ +package githubListener + +import ( + "cattery/agent/shutdownEvents" + "cattery/lib/messages" + "os" + "os/exec" + "sync" + + log "github.com/sirupsen/logrus" +) + +type GithubListener struct { + listenerPath string + process *os.Process + + mut sync.Mutex +} + +func NewGithubListener(listenerPath string) *GithubListener { + return &GithubListener{ + listenerPath: listenerPath, + } +} + +func (l *GithubListener) Start(jitConfig *string) { + var commandRun = exec.Command(l.listenerPath, "run", "--jitconfig", *jitConfig) + commandRun.Stdout = os.Stdout + commandRun.Stderr = os.Stderr + + go func() { + var msg = "Listener finished" + + err := commandRun.Start() + if err != nil { + msg = "Listener failed to start: " + err.Error() + log.Error(msg) + shutdownEvents.Emit(messages.UnregisterReasonUnknown, msg) + return + } + + l.process = commandRun.Process + err = commandRun.Wait() + if err != nil { + msg = "Runner failed: " + err.Error() + log.Error(msg) + } + + //TODO: check startup errors, like deprecated runner + + shutdownEvents.Emit(messages.UnregisterReasonDone, msg) + }() +} + +func (l *GithubListener) Stop() { + l.mut.Lock() + defer l.mut.Unlock() + + if l.process == nil { + return + } + + err := l.kill() + if err != nil { + log.Error("Failed to kill process: ", err) + } + + l.process = nil +} + +func (l *GithubListener) kill() error { + return kill(l) +} diff --git a/src/agent/githubListener/kill.go b/src/agent/githubListener/kill.go new file mode 100644 index 0000000..ae7301a --- /dev/null +++ b/src/agent/githubListener/kill.go @@ -0,0 +1,17 @@ +//go:build !linux + +package githubListener + +import ( + "errors" + "os" +) + +func kill(l *GithubListener) error { + err := l.process.Signal(os.Kill) + if err != nil { + return errors.New("Failed to kill process: " + err.Error()) + } + + return nil +} diff --git a/src/agent/githubListener/kill_linux.go b/src/agent/githubListener/kill_linux.go new file mode 100644 index 0000000..9e98987 --- /dev/null +++ b/src/agent/githubListener/kill_linux.go @@ -0,0 +1,24 @@ +package githubListener + +import ( + "errors" + "os/exec" +) + +func kill(l *GithubListener) error { + var commandInterruptRun = exec.Command("pkill", "--signal", "SIGINT", "Runner.Listener") + err := commandInterruptRun.Run() + if err != nil { + var errMsg = "Failed to interrupt runner: " + err.Error() + return errors.New(errMsg) + } + + return nil + + // TODO: debug why SIGINT does not work correctly + // err := runnerProcess.Signal(syscall.SIGINT) + // if err != nil { + // var errMsg = "Failed to interrupt runner: " + err.Error() + // a.logger.Error(errMsg) + // } +} diff --git a/src/agent/shutdownEvents/channel.go b/src/agent/shutdownEvents/channel.go new file mode 100644 index 0000000..5616e50 --- /dev/null +++ b/src/agent/shutdownEvents/channel.go @@ -0,0 +1,35 @@ +package shutdownEvents + +import ( + "cattery/lib/messages" + "sync" +) + +type ShutdownEvent struct { + Reason messages.UnregisterReason + Message string +} + +var mut = new(sync.Mutex) + +var channel = make(chan ShutdownEvent, 1) +var emitted = false + +func Emit(unregisterReason messages.UnregisterReason, message string) { + mut.Lock() + defer mut.Unlock() + + var event = ShutdownEvent{ + Reason: unregisterReason, + Message: message, + } + + if !emitted { + channel <- event + emitted = true + } +} + +func WaitEvent() ShutdownEvent { + return <-channel +} diff --git a/src/go.mod b/src/go.mod index 15b7c50..021a078 100644 --- a/src/go.mod +++ b/src/go.mod @@ -1,78 +1,78 @@ module cattery -go 1.24.1 +go 1.25.5 require ( - cloud.google.com/go/compute v1.45.0 - github.com/bradleyfalzon/ghinstallation/v2 v2.16.0 + cloud.google.com/go/compute v1.53.0 + github.com/bradleyfalzon/ghinstallation/v2 v2.17.0 + github.com/fsnotify/fsnotify v1.9.0 github.com/go-playground/validator v9.31.0+incompatible github.com/go-viper/mapstructure/v2 v2.4.0 github.com/google/go-github/v70 v70.0.0 github.com/prometheus/client_golang v1.23.2 github.com/sirupsen/logrus v1.9.3 - github.com/spf13/cobra v1.10.1 - github.com/spf13/viper v1.20.1 + github.com/spf13/cobra v1.10.2 + github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 - go.mongodb.org/mongo-driver/v2 v2.3.0 - google.golang.org/api v0.248.0 - google.golang.org/protobuf v1.36.8 + go.mongodb.org/mongo-driver/v2 v2.4.1 + google.golang.org/api v0.259.0 + google.golang.org/protobuf v1.36.11 ) require ( - cloud.google.com/go/auth v0.16.5 // indirect + cloud.google.com/go/auth v0.18.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect - cloud.google.com/go/compute/metadata v0.8.0 // indirect + cloud.google.com/go/compute/metadata v0.9.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-logr/logr v1.4.3 // 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/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/snappy v1.0.0 // indirect - github.com/google/go-github/v72 v72.0.0 // indirect - github.com/google/go-querystring v1.1.0 // indirect + github.com/google/go-github/v75 v75.0.0 // indirect + github.com/google/go-querystring v1.2.0 // indirect github.com/google/s2a-go v0.1.9 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect - github.com/googleapis/gax-go/v2 v2.15.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.9 // indirect + github.com/googleapis/gax-go/v2 v2.16.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/compress v1.18.2 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect - github.com/sagikazarmark/locafero v0.10.0 // indirect - github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect - github.com/spf13/afero v1.14.0 // indirect - github.com/spf13/cast v1.9.2 // indirect + github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/procfs v0.19.2 // indirect + github.com/sagikazarmark/locafero v0.12.0 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect - github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/scram v1.2.0 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/crypto v0.41.0 // indirect - golang.org/x/net v0.43.0 // indirect - golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.28.0 // indirect - google.golang.org/genproto v0.0.0-20250826171959-ef028d996bc1 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect - google.golang.org/grpc v1.75.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + google.golang.org/genproto v0.0.0-20251222181119-0a764e51fe1b // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect + google.golang.org/grpc v1.78.0 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/src/go.sum b/src/go.sum index fa1b6c6..48643d3 100644 --- a/src/go.sum +++ b/src/go.sum @@ -1,17 +1,17 @@ -cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= -cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= -cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI= -cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ= +cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= +cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= +cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0= +cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= -cloud.google.com/go/compute v1.45.0 h1:bcq5kVYiC6O62afoM/rh40jnLpLUw6GP1O+8a8NiI+Y= -cloud.google.com/go/compute v1.45.0/go.mod h1:wQjjP1m9aYkZAPbYxilUyJ0RSAAb+/PFNGHBVLzDiRM= -cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA= -cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw= +cloud.google.com/go/compute v1.53.0 h1:dILGanjePNsYfZVYYv6K0d4+IPnKX1gn84Fk8jDPNvs= +cloud.google.com/go/compute v1.53.0/go.mod h1:zdogTa7daHhEtEX92+S5IARtQmi/RNVPUfoI8Jhl8Do= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bradleyfalzon/ghinstallation/v2 v2.16.0 h1:B91r9bHtXp/+XRgS5aZm6ZzTdz3ahgJYmkt4xZkgDz8= -github.com/bradleyfalzon/ghinstallation/v2 v2.16.0/go.mod h1:OeVe5ggFzoBnmgitZe/A+BqGOnv1DvU/0uiLQi1wutM= +github.com/bradleyfalzon/ghinstallation/v2 v2.17.0 h1:SmbUK/GxpAspRjSQbB6ARvH+ArzlNzTtHydNyXUQ6zg= +github.com/bradleyfalzon/ghinstallation/v2 v2.17.0/go.mod h1:vuD/xvJT9Y+ZVZRv4HQ42cMyPFIYqpc7AbB4Gvt/DlY= 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/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -43,27 +43,27 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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/go-github/v70 v70.0.0 h1:/tqCp5KPrcvqCc7vIvYyFYTiCGrYvaWoYMGHSQbo55o= github.com/google/go-github/v70 v70.0.0/go.mod h1:xBUZgo8MI3lUL/hwxl3hlceJW1U8MVnXP3zUyI+rhQY= -github.com/google/go-github/v72 v72.0.0 h1:FcIO37BLoVPBO9igQQ6tStsv2asG4IPcYFi655PPvBM= -github.com/google/go-github/v72 v72.0.0/go.mod h1:WWtw8GMRiL62mvIquf1kO3onRHeWWKmK01qdCY8c5fg= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/go-github/v75 v75.0.0 h1:k7q8Bvg+W5KxRl9Tjq16a9XEgVY1pwuiG5sIL7435Ic= +github.com/google/go-github/v75 v75.0.0/go.mod h1:H3LUJEA1TCrzuUqtdAQniBNwuKiQIqdGKgBo1/M/uqI= +github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0= +github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= 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/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= -github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= -github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= -github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= +github.com/googleapis/enterprise-certificate-proxy v0.3.9 h1:TOpi/QG8iDcZlkQlGlFUti/ZtyLkliXvHDcyUIMuFrU= +github.com/googleapis/enterprise-certificate-proxy v0.3.9/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= +github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= +github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= 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= @@ -82,30 +82,28 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= -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/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.10.0 h1:FM8Cv6j2KqIhM2ZK7HZjm4mpj9NBktLgowT1aN9q5Cc= -github.com/sagikazarmark/locafero v0.10.0/go.mod h1:Ieo3EUsjifvQu4NZwV5sPd4dwvu0OCgEQV7vjc9yDjw= +github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= +github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= 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.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= -github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= -github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= -github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= -github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= -github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/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/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= @@ -114,84 +112,85 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= -github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs= +github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8= github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.mongodb.org/mongo-driver/v2 v2.3.0 h1:sh55yOXA2vUjW1QYw/2tRlHSQViwDyPnW61AwpZ4rtU= -go.mongodb.org/mongo-driver/v2 v2.3.0/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI= -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.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.mongodb.org/mongo-driver/v2 v2.4.1 h1:hGDMngUao03OVQ6sgV5csk+RWOIkF+CuLsTPobNMGNI= +go.mongodb.org/mongo-driver/v2 v2.4.1/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= 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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/api v0.248.0 h1:hUotakSkcwGdYUqzCRc5yGYsg4wXxpkKlW5ryVqvC1Y= -google.golang.org/api v0.248.0/go.mod h1:yAFUAF56Li7IuIQbTFoLwXTCI6XCFKueOlS7S9e4F9k= -google.golang.org/genproto v0.0.0-20250826171959-ef028d996bc1 h1:Nm5SEGIguOIBDXs5rhfz2aKwEVWlgwC58UcmEnLDc8Y= -google.golang.org/genproto v0.0.0-20250826171959-ef028d996bc1/go.mod h1:Jz9LrroM7Mcm+a0QrLh4UpZ1B/WhjIbqwEcUf4y08nQ= -google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1 h1:APHvLLYBhtZvsbnpkfknDZ7NyH4z5+ub/I0u8L3Oz6g= -google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1/go.mod h1:xUjFWUnWDpZ/C0Gu0qloASKFb6f8/QXiiXhSPFsD668= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og= -google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= -google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/api v0.259.0 h1:90TaGVIxScrh1Vn/XI2426kRpBqHwWIzVBzJsVZ5XrQ= +google.golang.org/api v0.259.0/go.mod h1:LC2ISWGWbRoyQVpxGntWwLWN/vLNxxKBK9KuJRI8Te4= +google.golang.org/genproto v0.0.0-20251222181119-0a764e51fe1b h1:kqShdsddZrS6q+DGBCA73CzHsKDu5vW4qw78tFnbVvY= +google.golang.org/genproto v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:gw1DtiPCt5uh/HV9STVEeaO00S5ATsJiJ2LsZV8lcDI= +google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b h1:uA40e2M6fYRBf0+8uN5mLlqUtV192iiksiICIBkYJ1E= +google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:Xa7le7qx2vmqB/SzWUBa7KdMjpdpAHlh5QCSnjessQk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= +google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 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= diff --git a/src/lib/messages/register.go b/src/lib/messages/register.go index 461115f..2d004e4 100644 --- a/src/lib/messages/register.go +++ b/src/lib/messages/register.go @@ -10,8 +10,9 @@ type RegisterResponse struct { } type UnregisterRequest struct { - Agent agents.Agent `json:"agent"` - Reason UnregisterReason `json:"reason"` + Agent agents.Agent `json:"agent"` + Reason UnregisterReason `json:"reason"` + Message string `json:"message"` } type UnregisterReason int @@ -20,4 +21,11 @@ const ( UnregisterReasonUnknown UnregisterReason = iota UnregisterReasonDone UnregisterReasonPreempted + UnregisterReasonSigTerm + UnregisterReasonControllerKill ) + +type PingResponse struct { + Terminate bool `json:"terminate"` + Message string `json:"message"` +} diff --git a/src/lib/trays/repositories/mongodbTrayRepository.go b/src/lib/trays/repositories/mongodbTrayRepository.go index 787f936..9164820 100644 --- a/src/lib/trays/repositories/mongodbTrayRepository.go +++ b/src/lib/trays/repositories/mongodbTrayRepository.go @@ -29,6 +29,10 @@ func (m *MongodbTrayRepository) GetById(trayId string) (*trays.Tray, error) { var result trays.Tray err := dbResult.Decode(&result) if err != nil { + if errors.Is(err, mongo.ErrNoDocuments) { + // Handle the "not found" case implicitly + return nil, nil + } return nil, err } diff --git a/src/lib/trays/repositories/mongodbTrayRepository_test.go b/src/lib/trays/repositories/mongodbTrayRepository_test.go index f010905..91f671d 100644 --- a/src/lib/trays/repositories/mongodbTrayRepository_test.go +++ b/src/lib/trays/repositories/mongodbTrayRepository_test.go @@ -115,8 +115,11 @@ func TestGetById(t *testing.T) { // Test GetById with non-existent ID tray, err = repo.GetById("non-existent") - if err == nil { - t.Error("Expected error for non-existent tray, got nil") + if err != nil { + t.Error("Expected no error for non-existent tray, got: ", err) + } + if tray != nil { + t.Error("Expected tray to be nil for non-existent tray") } } @@ -269,8 +272,8 @@ func TestDelete(t *testing.T) { // Verify the tray was deleted deletedTray, err := repo.GetById("test-tray-1") - if err == nil { - t.Error("Expected error for deleted tray, got nil") + if err != nil { + t.Error("Expected no error for deleted tray, got: ", err) } if deletedTray != nil { diff --git a/src/server/handlers/agentHandler.go b/src/server/handlers/agentHandler.go index 6a1a94b..589b5cf 100644 --- a/src/server/handlers/agentHandler.go +++ b/src/server/handlers/agentHandler.go @@ -6,11 +6,13 @@ import ( "cattery/lib/githubClient" "cattery/lib/messages" "cattery/lib/metrics" + "cattery/lib/trays" "encoding/json" "fmt" "net/http" "os" "path/filepath" + "time" log "github.com/sirupsen/logrus" ) @@ -138,6 +140,10 @@ func AgentUnregister(responseWriter http.ResponseWriter, r *http.Request) { http.Error(responseWriter, errMsg, http.StatusBadRequest) return } + if tray == nil { + http.Error(responseWriter, "Tray does not exist", http.StatusNotFound) + return + } var unregisterRequest messages.UnregisterRequest err = json.NewDecoder(r.Body).Decode(&unregisterRequest) @@ -217,6 +223,73 @@ func AgentDownloadBinary(responseWriter http.ResponseWriter, r *http.Request) { logger.Infof("Binary file served: %s (%d bytes)", execPath, fileInfo.Size()) } +func AgentPing(responseWriter http.ResponseWriter, r *http.Request) { + var logger = log.WithFields(log.Fields{ + "handler": "agent", + "call": "AgentPing", + }) + + logger.Tracef("AgentPing: %v", r) + + var id = r.PathValue("id") + var agentId = validateAgentId(id) + + var pingResponse = &messages.PingResponse{ + Terminate: false, + Message: "", + } + + tray, err := TrayManager.GetTrayById(agentId) + if err != nil { + var errMsg = fmt.Sprintf("Failed to get tray by id '%s': %v", agentId, err) + logger.Error(errMsg) + http.Error(responseWriter, errMsg, http.StatusInternalServerError) + + pingResponse.Message = "Failed to get tray by id: " + errMsg + pingResponse.Terminate = true + writeResponse(responseWriter, pingResponse, logger) + + return + } + if tray == nil { + var errMsg = fmt.Sprintf("Tray with id '%s' not found", agentId) + logger.Error(errMsg) + http.Error(responseWriter, errMsg, http.StatusGone) + + pingResponse.Message = "Failed to get tray by id: " + errMsg + pingResponse.Terminate = true + writeResponse(responseWriter, pingResponse, logger) + + return + } + + if tray.Status == trays.TrayStatusRunning { + writeResponse(responseWriter, pingResponse, logger) + return + } + + if time.Now().UTC().Sub(tray.StatusChanged) > time.Minute*2 { + var errMsg = fmt.Sprintf("Tray '%s' status not changed in 2 minutes", tray.Id) + logger.Error(errMsg) + + pingResponse.Terminate = true + pingResponse.Message = errMsg + writeResponse(responseWriter, pingResponse, logger) + return + } + + writeResponse(responseWriter, pingResponse, logger) +} + +func writeResponse(responseWriter http.ResponseWriter, pingResponse any, logger *log.Entry) { + responseWriter.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(responseWriter).Encode(pingResponse); err != nil { + logger.Errorf("Failed to encode ping response: %v", err) + http.Error(responseWriter, "Failed to encode response", http.StatusInternalServerError) + return + } +} + func AgentInterrupt(responseWriter http.ResponseWriter, r *http.Request) { var logger = log.WithFields(log.Fields{ "handler": "agent", diff --git a/src/server/server.go b/src/server/server.go index c15492a..6583f98 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -35,6 +35,7 @@ func Start() { webhookMux.HandleFunc("POST /agent/unregister/{id}", handlers.AgentUnregister) webhookMux.HandleFunc("GET /agent/download", handlers.AgentDownloadBinary) webhookMux.HandleFunc("POST /agent/interrupt/{id}", handlers.AgentInterrupt) + webhookMux.HandleFunc("POST /agent/ping/{id}", handlers.AgentPing) webhookMux.HandleFunc("POST /github/{org}", handlers.Webhook)