From 8af70d9eb81289c21bb25806c5664f287a8a2c47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Tue, 19 Nov 2024 12:12:08 +0100 Subject: [PATCH 01/26] Accept requests from http --- bootstrapper.go | 15 +++- config/config.go | 11 +++ internal/http/check.go | 168 +++++++++++++++++++++++++++++++++++++++++ internal/push/state.go | 2 +- 4 files changed, 193 insertions(+), 3 deletions(-) create mode 100644 internal/http/check.go diff --git a/bootstrapper.go b/bootstrapper.go index f826c39..7005d84 100644 --- a/bootstrapper.go +++ b/bootstrapper.go @@ -14,6 +14,7 @@ import ( "github.com/adevinta/vulcan-check-sdk/agent" "github.com/adevinta/vulcan-check-sdk/config" + "github.com/adevinta/vulcan-check-sdk/internal/http" "github.com/adevinta/vulcan-check-sdk/internal/local" "github.com/adevinta/vulcan-check-sdk/internal/logging" "github.com/adevinta/vulcan-check-sdk/internal/push" @@ -131,8 +132,18 @@ func NewCheck(name string, checker Checker) Check { conf.Check.Opts = options c = newLocalCheck(name, checker, logger, conf, json) } else { - logger.Debug("Push mode") - c = push.NewCheckWithConfig(name, checker, logger, conf) + if conf.Port > 0 { + logger.Debug("Http mode") + l := logging.BuildLoggerWithConfigAndFields(conf.Log, log.Fields{ + // "checkTypeName": "TODO", + // "checkTypeVersion": "TODO", + // "component": "checks", + }) + c = http.NewCheck(name, checker, l, conf) + } else { + logger.Debug("Push mode") + c = push.NewCheckWithConfig(name, checker, logger, conf) + } } cachedConfig = conf return c diff --git a/config/config.go b/config/config.go index 128aa9c..3035253 100644 --- a/config/config.go +++ b/config/config.go @@ -30,6 +30,8 @@ const ( pushAgentAddr = "VULCAN_AGENT_ADDRESS" pushMsgBufferLen = "VULCAN_CHECK_MSG_BUFF_LEN" + httpPort = "VULCAN_HTTP_PORT" + // Allows scanning private / reserved IP addresses. allowPrivateIPs = "VULCAN_ALLOW_PRIVATE_IPS" @@ -63,6 +65,7 @@ type Config struct { Log LogConfig `toml:"Log"` CommMode string `toml:"CommMode"` Push rest.PusherConfig `toml:"Push"` + Port int AllowPrivateIPs *bool `toml:"AllowPrivateIps"` RequiredVars map[string]string `toml:"RequiredVars"` } @@ -121,6 +124,14 @@ func overrideCommConfigEnvVars(c *Config) { c.Push.AgentAddr = pushEndPoint } + port := os.Getenv(httpPort) + if port != "" { + p, err := strconv.Atoi(port) + if err == nil { + c.Port = p + } + } + msgBuffLen := os.Getenv(pushMsgBufferLen) len, err := strconv.ParseInt(msgBuffLen, 0, 32) if err != nil { diff --git a/internal/http/check.go b/internal/http/check.go new file mode 100644 index 0000000..a2d69ea --- /dev/null +++ b/internal/http/check.go @@ -0,0 +1,168 @@ +/* +Copyright 2019 Adevinta +*/ + +package http + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/adevinta/vulcan-check-sdk/agent" + "github.com/adevinta/vulcan-check-sdk/config" + "github.com/adevinta/vulcan-check-sdk/state" + report "github.com/adevinta/vulcan-report" + log "github.com/sirupsen/logrus" +) + +// Check stores all the information needed to run a check locally. +type Check struct { + Logger *log.Entry + Name string + checker Checker + config *config.Config + port int + ctx context.Context + cancel context.CancelFunc + exitSignal chan os.Signal +} + +// RunAndServe implements the behavior needed by the sdk for a check runner to +// execute a check. +func (c *Check) RunAndServe() { + http.HandleFunc("/run", func(w http.ResponseWriter, r *http.Request) { + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "error reading request body", http.StatusBadRequest) + return + } + var job Job + err = json.Unmarshal(body, &job) + if err != nil { + w.WriteHeader(500) + return + } + + logger := c.Logger.WithFields(log.Fields{ + "target": job.Target, + "checkID": job.CheckID, + }) + ctx := context.WithValue(c.ctx, "logger", logger) + checkState := &State{ + state: agent.State{ + Report: report.Report{ + CheckData: report.CheckData{ + CheckID: job.CheckID, + StartTime: time.Now(), + ChecktypeName: c.config.Check.CheckTypeName, + ChecktypeVersion: c.config.Check.CheckTypeVersion, + Options: job.Options, + Target: job.Target, + }, + ResultData: report.ResultData{}, + }, + }, + } + + runtimeState := state.State{ + ResultData: &checkState.state.Report.ResultData, + ProgressReporter: state.ProgressReporterHandler(checkState.SetProgress), + } + logger.WithField("opts", job.Options).Info("Starting check") + err = c.checker.Run(ctx, job.Target, job.AssetType, job.Options, runtimeState) + c.checker.CleanUp(context.Background(), job.Target, job.AssetType, job.Options) + checkState.state.Report.CheckData.EndTime = time.Now() + elapsedTime := time.Since(checkState.state.Report.CheckData.StartTime) + // If an error has been returned, we set the correct status. + if err != nil { + if errors.Is(err, context.Canceled) { + checkState.state.Status = agent.StatusAborted + } else if errors.Is(err, state.ErrAssetUnreachable) { + checkState.state.Status = agent.StatusInconclusive + } else if errors.Is(err, state.ErrNonPublicAsset) { + checkState.state.Status = agent.StatusInconclusive + } else { + c.Logger.WithError(err).Error("Error running check") + checkState.state.Status = agent.StatusFailed + checkState.state.Report.Error = err.Error() + } + } else { + checkState.state.Status = agent.StatusFinished + } + checkState.state.Report.Status = checkState.state.Status + + logger.WithField("seconds", elapsedTime.Seconds()).WithField("state", checkState.state.Status).Info("Check finished") + + // Initialize sync point for the checker and the push state to be finished. + out, err := json.Marshal(checkState.state) + if err != nil { + logger.WithError(err).Error("error marshalling the check state") + http.Error(w, "error marshalling the check state", http.StatusInternalServerError) + return + } + w.Write(out) + }) + + addr := fmt.Sprintf(":%d", c.port) + c.Logger.Info(fmt.Sprintf("Listening at %s", addr)) + log.Fatal(http.ListenAndServe(addr, nil)) +} + +type Job struct { + CheckID string `json:"check_id"` // Required + StartTime time.Time `json:"start_time"` // Required + Image string `json:"image"` // Required + Target string `json:"target"` // Required + Timeout int `json:"timeout"` // Required + AssetType string `json:"assettype"` // Optional + Options string `json:"options"` // Optional + RequiredVars []string `json:"required_vars"` // Optional + Metadata map[string]string `json:"metadata"` // Optional + RunTime int64 +} + +// Shutdown is needed to fullfil the check interface but we don't need to do +// anything in this case. +func (c *Check) Shutdown() error { + return nil +} + +// NewCheck creates new check to be run from the command line without having an agent. +func NewCheck(name string, checker Checker, logger *log.Entry, conf *config.Config) *Check { + c := &Check{ + Name: name, + Logger: logger, + config: conf, + exitSignal: make(chan os.Signal, 1), + port: conf.Port, + } + signal.Notify(c.exitSignal, syscall.SIGINT, syscall.SIGTERM) + c.ctx, c.cancel = context.WithCancel(context.Background()) + c.checker = checker + return c +} + +// State holds the state for a local check. +type State struct { + state agent.State +} + +// Checker defines the shape a checker must have in order to be executed as vulcan-check. +type Checker interface { + Run(ctx context.Context, target, assetType, opts string, state state.State) error + CleanUp(ctx context.Context, target, assetType, opts string) +} + +func (p *State) SetProgress(progress float32) { + if p.state.Status == agent.StatusRunning && progress > p.state.Progress { + p.state.Progress = progress + } +} diff --git a/internal/push/state.go b/internal/push/state.go index 2646a75..a34fa05 100644 --- a/internal/push/state.go +++ b/internal/push/state.go @@ -60,7 +60,7 @@ func (p *State) SetProgress(progress float32) { func (p *State) SetStatusRunning() { p.state.Status = agent.StatusRunning p.state.Progress = 0.0 - p.state.Report.Status = string(agent.StatusRunning) + p.state.Report.Status = agent.StatusRunning p.pusher.UpdateState(p.state) } From 4783156c664bb93b4d23a2b7ce48ccddc34e61ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Tue, 19 Nov 2024 16:17:25 +0100 Subject: [PATCH 02/26] Add NewCheckLogFromContext func --- bootstrapper.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bootstrapper.go b/bootstrapper.go index 7005d84..2e3e6c4 100644 --- a/bootstrapper.go +++ b/bootstrapper.go @@ -160,6 +160,14 @@ func NewCheckLog(name string) *log.Entry { return (l) } +// NewCheckLogFromContext retrieves the logger from the context or creates a new one. +func NewCheckLogFromContext(ctx context.Context, name string) *log.Entry { + if l, ok := ctx.Value("logger").(*log.Entry); ok { + return l + } + return NewCheckLog(name) +} + // NewCheckFromHandlerWithConfig creates a new check from run and abort handlers using provided config. func NewCheckFromHandlerWithConfig(name string, conf *config.Config, run CheckerHandleRun) Check { checkerAdapter := struct { From b0714507513c3ce990ba1fced30339ad65558b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Tue, 19 Nov 2024 19:23:55 +0100 Subject: [PATCH 03/26] Upgrade dependencies --- go.mod | 24 +++++++------- go.sum | 100 ++++++++++++++++++++------------------------------------- 2 files changed, 46 insertions(+), 78 deletions(-) diff --git a/go.mod b/go.mod index 94f9b09..1730554 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,8 @@ require ( github.com/BurntSushi/toml v1.2.1 github.com/adevinta/vulcan-report v1.0.0 github.com/adevinta/vulcan-types v1.2.11 - github.com/aws/aws-sdk-go v1.51.0 - github.com/go-git/go-git/v5 v5.6.1 + github.com/aws/aws-sdk-go v1.55.5 + github.com/go-git/go-git/v5 v5.12.0 github.com/google/go-cmp v0.6.0 github.com/kr/pretty v0.3.1 github.com/lair-framework/go-nmap v0.0.0-20191202052157-3507e0b03523 @@ -19,16 +19,17 @@ require ( require ( cloud.google.com/go/compute v1.23.4 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - github.com/Microsoft/go-winio v0.6.0 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230411080316-8b3893ee7fca // indirect - github.com/acomagu/bufpipe v1.0.4 // indirect - github.com/cloudflare/circl v1.3.3 // indirect + dario.cat/mergo v1.0.0 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/ProtonMail/go-crypto v1.0.0 // indirect + github.com/cloudflare/circl v1.3.7 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/distribution/reference v0.5.0 // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-git/gcfg v1.5.0 // indirect - github.com/go-git/go-billy/v5 v5.4.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -37,16 +38,15 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.3 // indirect - github.com/imdario/mergo v0.3.15 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/miekg/dns v1.1.58 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect - github.com/rogpeppe/go-internal v1.10.0 // indirect - github.com/sergi/go-diff v1.3.1 // indirect - github.com/skeema/knownhosts v1.1.0 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.2.2 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect diff --git a/go.sum b/go.sum index 2dd4e93..905118f 100644 --- a/go.sum +++ b/go.sum @@ -3,35 +3,34 @@ cloud.google.com/go/compute v1.23.4 h1:EBT9Nw4q3zyE7G45Wvv3MzolIrCJEuHys5muLY0wv cloud.google.com/go/compute v1.23.4/go.mod h1:/EJMj55asU6kAFnuZET8zqgwgJ9FvXWXOkkfQZa4ioI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= -github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= -github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= -github.com/ProtonMail/go-crypto v0.0.0-20230411080316-8b3893ee7fca h1:3N4LNZ++dKh8SXcBRsT6P6mxhDm5swmkgmahlIS9yb0= -github.com/ProtonMail/go-crypto v0.0.0-20230411080316-8b3893ee7fca/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= -github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= -github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= +github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/adevinta/vulcan-report v1.0.0 h1:44aICPZ+4svucgCSA5KmjlT3ZGzrvZXiSnkbnj6AC2k= github.com/adevinta/vulcan-report v1.0.0/go.mod h1:k34KaeoXc3H77WNMwI9F4F1G28hBjB95PeMUp9oHbEE= github.com/adevinta/vulcan-types v1.2.11 h1:Jqd/L6gL+X1lLS2NivrnQOs4wlvbYE/Xw9ZxJxRoJ8U= github.com/adevinta/vulcan-types v1.2.11/go.mod h1:CFNWxeKdr7mVq0hbzstxBUPQIJ4iKvmQ4gqL/O1qDuI= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/aws/aws-sdk-go v1.51.0 h1:EA6GlEYMT3ouCO+v+oTWzKB/vcoHD2T9H9qulRx3lPg= -github.com/aws/aws-sdk-go v1.51.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= -github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= +github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= -github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= 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= @@ -39,6 +38,7 @@ github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -47,17 +47,14 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= -github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= -github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= -github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= -github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= -github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ= -github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= -github.com/go-git/go-git/v5 v5.6.1 h1:q4ZRqQl4pR/ZJHc1L5CFjGA1a10u76aV1iC+nh+bHsk= -github.com/go-git/go-git/v5 v5.6.1/go.mod h1:mvyoL6Unz0PiTQrGQfSfiLFhBH1c1e84ylC2MDs4ee8= +github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -88,7 +85,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= @@ -100,16 +96,11 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfF github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= -github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= -github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -118,12 +109,9 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lair-framework/go-nmap v0.0.0-20191202052157-3507e0b03523 h1:N4NQR4on0n3Kc3xlBXUYzCZorFdordwkR2kcZMk9te0= github.com/lair-framework/go-nmap v0.0.0-20191202052157-3507e0b03523/go.mod h1:7Em1Lxm3DFdLvXWUZ6bQ/xIbGlxFy7jl07bziQMZ/kU= -github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= -github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= -github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= 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/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= @@ -135,16 +123,15 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0= -github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag= +github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= +github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -154,7 +141,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -168,15 +155,11 @@ go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGX go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -golang.org/x/arch v0.1.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= @@ -185,7 +168,6 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= @@ -200,10 +182,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= @@ -220,20 +200,15 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h 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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20211007075335-d3039528d8ac/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.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -241,8 +216,7 @@ golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/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/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= @@ -263,7 +237,6 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 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/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= @@ -304,20 +277,15 @@ google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGm google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/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= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= From 8d765e1bda368c51c53194506d4174f26ec905f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Tue, 19 Nov 2024 19:24:25 +0100 Subject: [PATCH 04/26] Fix lints --- config/config_test.go | 3 --- helpers/target.go | 5 +---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/config/config_test.go b/config/config_test.go index 2685630..7b65bb7 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -167,9 +167,6 @@ func setEnvVars(envVars map[string]string) error { } func TestLoadConfigFromFile(t *testing.T) { - type args struct { - filePath string - } tests := []struct { name string filepath string diff --git a/helpers/target.go b/helpers/target.go index 73a9cda..1fc1a62 100644 --- a/helpers/target.go +++ b/helpers/target.go @@ -371,10 +371,7 @@ func IsWebAddrsReachable(target string) bool { } _, err := http.Get(target) - if err != nil { - return false - } - return true + return err == nil } // IsDomainReachable returns whether the input target is a reachable From cd68fbfa0c76e5a09d0489bf75e3ac54b572b8c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Tue, 3 Dec 2024 16:49:24 +0100 Subject: [PATCH 05/26] Allow env variables in command exec helpers --- helpers/command/command.go | 47 ++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/helpers/command/command.go b/helpers/command/command.go index 7b03df6..38b3f44 100644 --- a/helpers/command/command.go +++ b/helpers/command/command.go @@ -38,13 +38,18 @@ func (e *ParseError) Error() string { return fmt.Sprintf("ProcessOutput:\n%sParser error:%s\n", string(e.ProcessOutput), e.ParserError) } -// ExecuteWithStdErr executes a 'command' in a new process +// ExecuteWithStdErr executes ExecuteWithEnvStdErr without env variables. +func ExecuteWithStdErr(ctx context.Context, logger *log.Entry, exe string, params ...string) ([]byte, []byte, int, error) { + return ExecuteWithEnvStdErr(ctx, logger, nil, exe, params...) +} + +// ExecuteWithEnvStdErr executes a 'command' in a new process // Parameter command must contain a path to the command, or simply the command name if lookup in path is wanted. // A nil value can be passed in parameters ctx and logger. // Returns the outputs of the process written to the standard output and error, also returns the status code returned by the command. // Note that, contrary to the standard library, the function doesn't return an error if the command execution returned a value different from 0. // The new process where the command is executed inherits all the env vars of the current process. -func ExecuteWithStdErr(ctx context.Context, logger *log.Entry, exe string, params ...string) ([]byte, []byte, int, error) { +func ExecuteWithEnvStdErr(ctx context.Context, logger *log.Entry, appendEnv []string, exe string, params ...string) ([]byte, []byte, int, error) { if ctx == nil { ctx = context.Background() } @@ -55,6 +60,9 @@ func ExecuteWithStdErr(ctx context.Context, logger *log.Entry, exe string, param var returnCode int cmd := exec.CommandContext(ctx, exe, params...) //nolint cmd.Env = os.Environ() + if appendEnv != nil { + cmd.Env = append(cmd.Env, appendEnv...) + } logger.Info("Executing command") stdErr := &bytes.Buffer{} stdOut := &bytes.Buffer{} @@ -80,6 +88,12 @@ func ExecuteWithStdErr(ctx context.Context, logger *log.Entry, exe string, param return output, errOutput, returnCode, nil } +// Execute executes ExecuteEnv without appendEnv variables. +func Execute(ctx context.Context, logger *log.Entry, exe string, params ...string) (output []byte, exitCode int, err error) { + output, _, exitCode, err = ExecuteWithEnvStdErr(ctx, logger, nil, exe, params...) + return +} + // Execute executes a 'command' in a new process // Parameter command must contain a path to the command, or simply the command name if lookup in path is wanted. // A nil value can be passed in parameters ctx and logger. @@ -87,11 +101,16 @@ func ExecuteWithStdErr(ctx context.Context, logger *log.Entry, exe string, param // Where there is an error, // Note that, contrary to the standard library, the function doesn't return an error if the command execution returned a value different from 0. // The new process where the command is executed inherits all the env vars of the current process. -func Execute(ctx context.Context, logger *log.Entry, exe string, params ...string) (output []byte, exitCode int, err error) { - output, _, exitCode, err = ExecuteWithStdErr(ctx, logger, exe, params...) +func ExecuteEnv(ctx context.Context, logger *log.Entry, appendEnv []string, exe string, params ...string) (output []byte, exitCode int, err error) { + output, _, exitCode, err = ExecuteWithEnvStdErr(ctx, logger, appendEnv, exe, params...) return } +// ExecuteAndParseJSON executes ExecuteEnvAndParseJSON without appendEnv. +func ExecuteAndParseJSON(ctx context.Context, logger *log.Entry, result interface{}, exe string, params ...string) (int, error) { + return ExecuteEnvAndParseJSON(ctx, logger, result, nil, exe, params...) +} + // ExecuteAndParseJSON executes a command, using the func Execute. // After execution: // returned error is nil and the param result contains the output parsed as json. @@ -99,13 +118,18 @@ func Execute(ctx context.Context, logger *log.Entry, exe string, params ...strin // error is not nil and the result doesn't contain the process output parsed as json. // If an error is raised when trying to parse the process output, the function returns an error of type ParseError that contains the // raw output of the process and the error returned by the json parser. -func ExecuteAndParseJSON(ctx context.Context, logger *log.Entry, result interface{}, exe string, params ...string) (int, error) { +func ExecuteEnvAndParseJSON(ctx context.Context, logger *log.Entry, result interface{}, appendEnv []string, exe string, params ...string) (int, error) { jsonParser := func(output []byte, result interface{}) error { return json.Unmarshal(output, result) } return ExecuteAndParse(ctx, logger, jsonParser, result, exe, params...) } +// ExecuteAndParseXML executes ExecuteEnvAndParseXML without appendEnv. +func ExecuteAndParseXML(ctx context.Context, logger *log.Entry, result interface{}, exe string, params ...string) (int, error) { + return ExecuteEnvAndParseXML(ctx, logger, result, nil, exe, params...) +} + // ExecuteAndParseXML executes a command, using the func Execute. // After execution: // returned error is nil and the param result contains the output parsed as XML. @@ -113,16 +137,21 @@ func ExecuteAndParseJSON(ctx context.Context, logger *log.Entry, result interfac // error is not nil and the result doesn't contain the process output parsed as XML. // If an error is raised when trying to parse the process output, the function returns an error of type ParseError that contains the // the raw output of the process and the error returned by the json parser. -func ExecuteAndParseXML(ctx context.Context, logger *log.Entry, result interface{}, exe string, params ...string) (int, error) { +func ExecuteEnvAndParseXML(ctx context.Context, logger *log.Entry, result interface{}, appendEnv []string, exe string, params ...string) (int, error) { xmlParser := func(output []byte, result interface{}) error { return xml.Unmarshal(output, result) } - return ExecuteAndParse(ctx, logger, xmlParser, result, exe, params...) + return ExecuteEnvAndParse(ctx, logger, xmlParser, result, appendEnv, exe, params...) } // OutputParser represent a function that parses an output from process type OutputParser func(output []byte, result interface{}) error +// ExecuteAndParse executes ExecuteEnvAndParse without appendEnv. +func ExecuteAndParse(ctx context.Context, logger *log.Entry, parser OutputParser, result interface{}, exe string, params ...string) (int, error) { + return ExecuteEnvAndParse(ctx, logger, parser, result, nil, exe, params...) +} + // ExecuteAndParse executes a command, using the func Execute and parsing the output using the provided parser function. // After execution: // returned error is nil and the param result contains the output parsed as json. @@ -130,8 +159,8 @@ type OutputParser func(output []byte, result interface{}) error // error is not nil and the result doesn't contain the result parsed as json. // If an error is raised when trying to parse the output, the function returns an error of type ParseError that contains the // the raw output of the process and the error returned by the json parser. -func ExecuteAndParse(ctx context.Context, logger *log.Entry, parser OutputParser, result interface{}, exe string, params ...string) (int, error) { - output, errOutput, status, err := ExecuteWithStdErr(ctx, logger, exe, params...) +func ExecuteEnvAndParse(ctx context.Context, logger *log.Entry, parser OutputParser, result interface{}, appendEnv []string, exe string, params ...string) (int, error) { + output, errOutput, status, err := ExecuteWithEnvStdErr(ctx, logger, appendEnv, exe, params...) if err != nil { return status, err } From 59365ddb60b2ff98a39a99bf7a11bade3b943c5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Fri, 6 Dec 2024 19:05:48 +0100 Subject: [PATCH 06/26] Add CloneGitRepositoryContext func --- helpers/git.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/helpers/git.go b/helpers/git.go index 3ad4d9e..0f20007 100644 --- a/helpers/git.go +++ b/helpers/git.go @@ -1,6 +1,7 @@ package helpers import ( + "context" "fmt" "net/url" "os" @@ -19,9 +20,14 @@ const ( gheTokenVar = "GITHUB_ENTERPRISE_TOKEN" ) -// CloneGitRepository clones a Git repository into a temporary directory and returns the path and branch name. -// If a branch is not specified, the default branch will be used and its name will be returned. +// CloneGitRepository executes CloneGitRepositoryContext with background context. func CloneGitRepository(target string, branch string, depth int) (string, string, error) { + return CloneGitRepositoryContext(context.Background(), target, branch, depth) +} + +// CloneGitRepositoryContext clones a Git repository into a temporary directory and returns the path and branch name. +// If a branch is not specified, the default branch will be used and its name will be returned. +func CloneGitRepositoryContext(ctx context.Context, target string, branch string, depth int) (string, string, error) { // Check if the target repository is on Github Enterprise and return populated credentials if necessary. auth, err := gheAuth(target) if err != nil { @@ -53,7 +59,7 @@ func CloneGitRepository(target string, branch string, depth int) (string, string if branch != "" { cloneOptions.ReferenceName = plumbing.ReferenceName(path.Join("refs/heads", branch)) } - repo, err := git.PlainClone(repoPath, false, &cloneOptions) + repo, err := git.PlainCloneContext(ctx, repoPath, false, &cloneOptions) if err != nil { return "", "", fmt.Errorf("error cloning the repository: %w", err) } From cc15265dd1c8c1080968ac4008ca2df9701b7538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Mon, 9 Dec 2024 18:43:35 +0100 Subject: [PATCH 07/26] Improve context management and graceful shutdown --- bootstrapper.go | 23 ++- config/config.go | 4 +- go.mod | 1 + go.sum | 2 + internal/http/check.go | 168 ++++++++++-------- internal/http/check_test.go | 333 ++++++++++++++++++++++++++++++++++++ 6 files changed, 441 insertions(+), 90 deletions(-) create mode 100644 internal/http/check_test.go diff --git a/bootstrapper.go b/bootstrapper.go index 2e3e6c4..6dc765a 100644 --- a/bootstrapper.go +++ b/bootstrapper.go @@ -131,20 +131,19 @@ func NewCheck(name string, checker Checker) Check { conf.Check.Target = runTarget conf.Check.Opts = options c = newLocalCheck(name, checker, logger, conf, json) + } else if conf.Port != nil { + logger.Debug("Http mode") + l := logging.BuildLoggerWithConfigAndFields(conf.Log, log.Fields{ + // "checkTypeName": "TODO", + // "checkTypeVersion": "TODO", + // "component": "checks", + }) + c = http.NewCheck(name, checker, l, conf) } else { - if conf.Port > 0 { - logger.Debug("Http mode") - l := logging.BuildLoggerWithConfigAndFields(conf.Log, log.Fields{ - // "checkTypeName": "TODO", - // "checkTypeVersion": "TODO", - // "component": "checks", - }) - c = http.NewCheck(name, checker, l, conf) - } else { - logger.Debug("Push mode") - c = push.NewCheckWithConfig(name, checker, logger, conf) - } + logger.Debug("Push mode") + c = push.NewCheckWithConfig(name, checker, logger, conf) } + cachedConfig = conf return c } diff --git a/config/config.go b/config/config.go index 3035253..ea91387 100644 --- a/config/config.go +++ b/config/config.go @@ -65,7 +65,7 @@ type Config struct { Log LogConfig `toml:"Log"` CommMode string `toml:"CommMode"` Push rest.PusherConfig `toml:"Push"` - Port int + Port *int `toml:"Port"` AllowPrivateIPs *bool `toml:"AllowPrivateIps"` RequiredVars map[string]string `toml:"RequiredVars"` } @@ -128,7 +128,7 @@ func overrideCommConfigEnvVars(c *Config) { if port != "" { p, err := strconv.Atoi(port) if err == nil { - c.Port = p + c.Port = &p } } diff --git a/go.mod b/go.mod index 1730554..4a73a52 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/kr/pretty v0.3.1 github.com/lair-framework/go-nmap v0.0.0-20191202052157-3507e0b03523 github.com/sirupsen/logrus v1.9.0 + go.uber.org/goleak v1.3.0 google.golang.org/api v0.171.0 gopkg.in/resty.v1 v1.12.0 ) diff --git a/go.sum b/go.sum index 905118f..3a14532 100644 --- a/go.sum +++ b/go.sum @@ -155,6 +155,8 @@ go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGX go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= diff --git a/internal/http/check.go b/internal/http/check.go index a2d69ea..2b5ce09 100644 --- a/internal/http/check.go +++ b/internal/http/check.go @@ -1,5 +1,5 @@ /* -Copyright 2019 Adevinta +Copyright 2024 Adevinta */ package http @@ -30,90 +30,104 @@ type Check struct { checker Checker config *config.Config port int - ctx context.Context - cancel context.CancelFunc + server *http.Server exitSignal chan os.Signal } -// RunAndServe implements the behavior needed by the sdk for a check runner to -// execute a check. -func (c *Check) RunAndServe() { - http.HandleFunc("/run", func(w http.ResponseWriter, r *http.Request) { - body, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, "error reading request body", http.StatusBadRequest) - return - } - var job Job - err = json.Unmarshal(body, &job) - if err != nil { - w.WriteHeader(500) - return - } +// ServeHTTP implements an http POST handler that receives a JSON enconde Job, and returns an +// agent.State JSON enconded response. +func (c *Check) ServeHTTP(w http.ResponseWriter, r *http.Request) { + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "error reading request body", http.StatusBadRequest) + return + } + var job Job + err = json.Unmarshal(body, &job) + if err != nil { + w.WriteHeader(500) + return + } - logger := c.Logger.WithFields(log.Fields{ - "target": job.Target, - "checkID": job.CheckID, - }) - ctx := context.WithValue(c.ctx, "logger", logger) - checkState := &State{ - state: agent.State{ - Report: report.Report{ - CheckData: report.CheckData{ - CheckID: job.CheckID, - StartTime: time.Now(), - ChecktypeName: c.config.Check.CheckTypeName, - ChecktypeVersion: c.config.Check.CheckTypeVersion, - Options: job.Options, - Target: job.Target, - }, - ResultData: report.ResultData{}, + logger := c.Logger.WithFields(log.Fields{ + "target": job.Target, + "checkID": job.CheckID, + }) + ctx := context.WithValue(r.Context(), "logger", logger) + checkState := &State{ + state: agent.State{ + Report: report.Report{ + CheckData: report.CheckData{ + CheckID: job.CheckID, + StartTime: job.StartTime, // TODO: Is this correct or should be time.Now() + ChecktypeName: c.config.Check.CheckTypeName, + ChecktypeVersion: c.config.Check.CheckTypeVersion, + Options: job.Options, + Target: job.Target, }, + ResultData: report.ResultData{}, }, - } + }, + } - runtimeState := state.State{ - ResultData: &checkState.state.Report.ResultData, - ProgressReporter: state.ProgressReporterHandler(checkState.SetProgress), - } - logger.WithField("opts", job.Options).Info("Starting check") - err = c.checker.Run(ctx, job.Target, job.AssetType, job.Options, runtimeState) - c.checker.CleanUp(context.Background(), job.Target, job.AssetType, job.Options) - checkState.state.Report.CheckData.EndTime = time.Now() - elapsedTime := time.Since(checkState.state.Report.CheckData.StartTime) - // If an error has been returned, we set the correct status. - if err != nil { - if errors.Is(err, context.Canceled) { - checkState.state.Status = agent.StatusAborted - } else if errors.Is(err, state.ErrAssetUnreachable) { - checkState.state.Status = agent.StatusInconclusive - } else if errors.Is(err, state.ErrNonPublicAsset) { - checkState.state.Status = agent.StatusInconclusive - } else { - c.Logger.WithError(err).Error("Error running check") - checkState.state.Status = agent.StatusFailed - checkState.state.Report.Error = err.Error() - } + runtimeState := state.State{ + ResultData: &checkState.state.Report.ResultData, + ProgressReporter: state.ProgressReporterHandler(checkState.SetProgress), + } + logger.WithField("opts", job.Options).Info("Starting check") + err = c.checker.Run(ctx, job.Target, job.AssetType, job.Options, runtimeState) + c.checker.CleanUp(ctx, job.Target, job.AssetType, job.Options) + checkState.state.Report.CheckData.EndTime = time.Now() + elapsedTime := time.Since(checkState.state.Report.CheckData.StartTime) + // If an error has been returned, we set the correct status. + if err != nil { + if errors.Is(err, context.Canceled) { + checkState.state.Status = agent.StatusAborted + } else if errors.Is(err, state.ErrAssetUnreachable) { + checkState.state.Status = agent.StatusInconclusive + } else if errors.Is(err, state.ErrNonPublicAsset) { + checkState.state.Status = agent.StatusInconclusive } else { - checkState.state.Status = agent.StatusFinished + c.Logger.WithError(err).Error("Error running check") + checkState.state.Status = agent.StatusFailed + checkState.state.Report.Error = err.Error() } - checkState.state.Report.Status = checkState.state.Status + } else { + checkState.state.Status = agent.StatusFinished + } + checkState.state.Report.Status = checkState.state.Status + + logger.WithField("seconds", elapsedTime.Seconds()).WithField("state", checkState.state.Status).Info("Check finished") - logger.WithField("seconds", elapsedTime.Seconds()).WithField("state", checkState.state.Status).Info("Check finished") + // Initialize sync point for the checker and the push state to be finished. + out, err := json.Marshal(checkState.state) + if err != nil { + logger.WithError(err).Error("error marshalling the check state") + http.Error(w, "error marshalling the check state", http.StatusInternalServerError) + return + } + w.Write(out) +} - // Initialize sync point for the checker and the push state to be finished. - out, err := json.Marshal(checkState.state) - if err != nil { - logger.WithError(err).Error("error marshalling the check state") - http.Error(w, "error marshalling the check state", http.StatusInternalServerError) - return +// RunAndServe implements the behavior needed by the sdk for a check runner to +// execute a check. +func (c *Check) RunAndServe() { + http.HandleFunc("/run", c.ServeHTTP) + c.Logger.Info(fmt.Sprintf("Listening at %s", c.server.Addr)) + go func() { + if err := c.server.ListenAndServe(); err != nil { + // handle err } - w.Write(out) - }) + }() - addr := fmt.Sprintf(":%d", c.port) - c.Logger.Info(fmt.Sprintf("Listening at %s", addr)) - log.Fatal(http.ListenAndServe(addr, nil)) + s := <-c.exitSignal + + c.Logger.WithField("signal", s.String()).Info("Stopping server") + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) // TODO: Allow configure value. + defer cancel() + if err := c.server.Shutdown(ctx); err != nil { + c.Logger.WithError(err).Error("Shutting down server") + } } type Job struct { @@ -129,23 +143,24 @@ type Job struct { RunTime int64 } -// Shutdown is needed to fullfil the check interface but we don't need to do +// Shutdown is needed to fulfil the check interface but we don't need to do // anything in this case. func (c *Check) Shutdown() error { + c.exitSignal <- syscall.SIGTERM return nil } -// NewCheck creates new check to be run from the command line without having an agent. +// NewCheck creates new check to be run from the command line without having an agent. func NewCheck(name string, checker Checker, logger *log.Entry, conf *config.Config) *Check { c := &Check{ Name: name, Logger: logger, config: conf, exitSignal: make(chan os.Signal, 1), - port: conf.Port, + port: *conf.Port, } + c.server = &http.Server{Addr: fmt.Sprintf(":%d", c.port)} signal.Notify(c.exitSignal, syscall.SIGINT, syscall.SIGTERM) - c.ctx, c.cancel = context.WithCancel(context.Background()) c.checker = checker return c } @@ -161,6 +176,7 @@ type Checker interface { CleanUp(ctx context.Context, target, assetType, opts string) } +// SetProgress updates the progress of the state. func (p *State) SetProgress(progress float32) { if p.state.Status == agent.StatusRunning && progress > p.state.Progress { p.state.Progress = progress diff --git a/internal/http/check_test.go b/internal/http/check_test.go new file mode 100644 index 0000000..8e4b230 --- /dev/null +++ b/internal/http/check_test.go @@ -0,0 +1,333 @@ +/* +Copyright 2019 Adevinta +*/ + +package http + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strconv" + "sync" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "go.uber.org/goleak" + + log "github.com/sirupsen/logrus" + + "github.com/adevinta/vulcan-check-sdk/agent" + "github.com/adevinta/vulcan-check-sdk/config" + "github.com/adevinta/vulcan-check-sdk/internal/logging" + "github.com/adevinta/vulcan-check-sdk/state" + report "github.com/adevinta/vulcan-report" +) + +type CheckerHandleRun func(ctx context.Context, target, assetType, opts string, s state.State) error + +// Run is used as adapter to satisfy the method with same name in interface Checker. +func (handler CheckerHandleRun) Run(ctx context.Context, target, assetType string, opts string, s state.State) error { + return (handler(ctx, target, assetType, opts, s)) +} + +// CheckerHandleCleanUp func type to specify a CleanUp handler function for a checker. +type CheckerHandleCleanUp func(ctx context.Context, target, assetType, opts string) + +// CleanUp is used as adapter to satisfy the method with same name in interface Checker. +func (handler CheckerHandleCleanUp) CleanUp(ctx context.Context, target, assetType, opts string) { + (handler(ctx, target, assetType, opts)) +} + +// NewCheckFromHandler creates a new check given a checker run handler. +func NewCheckFromHandlerWithConfig(name string, run CheckerHandleRun, clean CheckerHandleCleanUp, conf *config.Config, l *log.Entry) *Check { + if clean == nil { + clean = func(ctx context.Context, target, assetType, opts string) {} + } + checkerAdapter := struct { + CheckerHandleRun + CheckerHandleCleanUp + }{ + run, + clean, + } + return NewCheck(name, checkerAdapter, l, conf) +} + +type httpTest struct { + name string + args httpIntParams + want map[string]agent.State + wantResourceState interface{} +} + +type httpIntParams struct { + checkRunner CheckerHandleRun + checkCleaner func(resourceToClean interface{}, ctx context.Context, target, assetType, optJSON string) + resourceToClean interface{} + checkName string + config *config.Config + jobs map[string]Job +} + +// sleepCheckRunner implements a check that sleeps based on the options and generates inconclusive in case of a target with that name. +func sleepCheckRunner(ctx context.Context, target, assetType, optJSON string, st state.State) (err error) { + log := logging.BuildRootLog("TestChecker") + log.Debug("Check running") + st.SetProgress(0.1) + type t struct { + SleepTime int + } + opt := t{} + if optJSON == "" { + return errors.New("error: missing sleep time") + } + if err := json.Unmarshal([]byte(optJSON), &opt); err != nil { + return err + } + if target == "inconclusive" { + return state.ErrAssetUnreachable + } + if opt.SleepTime <= 0 { + return errors.New("error: missing or 0 sleep time") + } + log.Debugf("going sleep %v seconds.", strconv.Itoa(opt.SleepTime)) + + select { + case <-time.After(time.Duration(opt.SleepTime) * time.Second): + log.Debugf("slept successfully %s seconds", strconv.Itoa(opt.SleepTime)) + case <-ctx.Done(): + log.Info("Check aborted") + } + st.AddVulnerabilities(report.Vulnerability{ + Summary: "Summary", + Description: "Test Vulnerability", + }) + return nil +} + +func TestIntegrationHttpMode(t *testing.T) { + port := 8888 + startTime := time.Now() + intTests := []httpTest{ + { + name: "HappyPath", + args: httpIntParams{ + config: &config.Config{ + Check: config.CheckConfig{ + CheckTypeName: "checkTypeName", + }, + Log: config.LogConfig{ + LogFmt: "text", + LogLevel: "debug", + }, + Port: &port, + }, + checkRunner: sleepCheckRunner, + jobs: map[string]Job{ + "checkHappy": { + CheckID: "checkHappy", + Options: `{"SleepTime": 1}`, + Target: "www.example.com", + AssetType: "Hostname", + StartTime: startTime, + }, + "checkDeadline": { + CheckID: "checkDeadline", + Options: `{"SleepTime": 10}`, + Target: "www.example.com", + AssetType: "Hostname", + StartTime: startTime, + }, + "checkInconclusive": { + CheckID: "checkInconclusive", + Options: `{"SleepTime": 1}`, + Target: "inconclusive", + AssetType: "Hostname", + StartTime: startTime, + }, + "checkFailed": { + CheckID: "checkFailed", + Options: `{}`, + Target: "www.example.com", + AssetType: "Hostname", + StartTime: startTime, + }, + }, + resourceToClean: map[string]string{"key": "initial"}, + checkCleaner: func(resource interface{}, ctx context.Context, target, assetType, opt string) { + r := resource.(map[string]string) + r["key"] = "cleaned" + }, + }, + wantResourceState: map[string]string{"key": "cleaned"}, + want: map[string]agent.State{ + "checkHappy": { + Status: agent.StatusFinished, + Report: report.Report{ + CheckData: report.CheckData{ + CheckID: "checkHappy", + ChecktypeName: "checkTypeName", + ChecktypeVersion: "", + Target: "www.example.com", + Options: `{"SleepTime": 1}`, + Status: agent.StatusFinished, + StartTime: startTime, + EndTime: time.Time{}, + }, + ResultData: report.ResultData{ + Vulnerabilities: []report.Vulnerability{ + { + Description: "Test Vulnerability", + Summary: "Summary", + }, + }, + Error: "", + Data: nil, + Notes: "", + }, + }}, + "checkDeadline": { + Status: agent.StatusAborted, + }, + "checkInconclusive": { + Status: agent.StatusInconclusive, + Report: report.Report{ + CheckData: report.CheckData{ + CheckID: "checkInconclusive", + ChecktypeName: "checkTypeName", + ChecktypeVersion: "", + Target: "inconclusive", + Options: `{"SleepTime": 1}`, + Status: agent.StatusInconclusive, + StartTime: startTime, + EndTime: time.Time{}, + }, + }, + }, + "checkFailed": { + Status: agent.StatusFailed, + Report: report.Report{ + CheckData: report.CheckData{ + CheckID: "checkFailed", + ChecktypeName: "checkTypeName", + ChecktypeVersion: "", + Target: "www.example.com", + Options: `{}`, + Status: agent.StatusFailed, + StartTime: startTime, + EndTime: time.Time{}, + }, + ResultData: report.ResultData{ + Error: "error: missing or 0 sleep time", + }, + }, + }, + }, + }, + } + + defer goleak.VerifyNone(t) + + for _, tt := range intTests { + tt := tt + t.Run(tt.name, func(t2 *testing.T) { + conf := tt.args.config + var cleaner func(ctx context.Context, target, assetType, opts string) + if tt.args.checkCleaner != nil { + cleaner = func(ctx context.Context, target, assetType, opts string) { + tt.args.checkCleaner(tt.args.resourceToClean, ctx, target, assetType, opts) + } + } + l := logging.BuildRootLog("httpCheck") + c := NewCheckFromHandlerWithConfig(tt.args.checkName, tt.args.checkRunner, cleaner, conf, l) + go c.RunAndServe() + client := &http.Client{} + url := fmt.Sprintf("http://localhost:%d/run", *tt.args.config.Port) + + type not struct { + check string + resp agent.State + } + + // ch will receibe the results of the concurrent job executions + ch := make(chan not, len(tt.args.jobs)) + wg := sync.WaitGroup{} + + // Runs each job in a go routine with a 3 seconds deadline. + for key, job := range tt.args.jobs { + wg.Add(1) + go func(key string, job Job) { + defer wg.Done() + var err error + n := not{ + check: key, + } + defer func() { + ch <- n + }() + cc, err := json.Marshal(job) + if err != nil { + l.Error("Marshal error", "error", err) + return + } + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(cc)) + if err != nil { + l.Error("NewRequestWithContext error", "error", err) + return + } + req.Header.Add("Content-Type", "application/json") + resp, err := client.Do(req) + if err != nil { + if errors.Is(err, context.DeadlineExceeded) { + n.resp = agent.State{Status: agent.StatusAborted} + return + } + l.Error("request error", "error", err) + return + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + l.Error("failed to read response body", "error", err) + return + } + r := agent.State{} + err = json.Unmarshal(body, &r) + if err != nil { + l.Error("Unable to unmarshal response", "error", err) + return + } + + // Compare resource to clean up state with wanted state. + diff := cmp.Diff(tt.wantResourceState, tt.args.resourceToClean) + if diff != "" { + t.Errorf("Error want resource to clean state != got. Diff %s", diff) + } + n.resp = r + }(key, job) + } + wg.Wait() + close(ch) + + results := map[string]agent.State{} + for x := range ch { + results[x.check] = x.resp + } + + diff := cmp.Diff(results, tt.want, cmpopts.IgnoreFields(report.CheckData{}, "EndTime")) + if diff != "" { + t.Errorf("Error in test %s. diffs %+v", tt.name, diff) + } + c.Shutdown() + }) + } +} From 6c83fe401d3134f15b64cfd536033ad5e94a6345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Fri, 13 Dec 2024 13:07:30 +0100 Subject: [PATCH 08/26] Add healthcheck --- internal/http/check.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/http/check.go b/internal/http/check.go index 2b5ce09..d1094b0 100644 --- a/internal/http/check.go +++ b/internal/http/check.go @@ -112,6 +112,10 @@ func (c *Check) ServeHTTP(w http.ResponseWriter, r *http.Request) { // RunAndServe implements the behavior needed by the sdk for a check runner to // execute a check. func (c *Check) RunAndServe() { + http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`"OK"`)) + }) http.HandleFunc("/run", c.ServeHTTP) c.Logger.Info(fmt.Sprintf("Listening at %s", c.server.Addr)) go func() { From 5940c0b0f932b1edf09af7e38d72eebcafe00749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Mon, 16 Dec 2024 12:00:24 +0100 Subject: [PATCH 09/26] Fix elapsed time --- internal/http/check.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/http/check.go b/internal/http/check.go index d1094b0..be188be 100644 --- a/internal/http/check.go +++ b/internal/http/check.go @@ -37,6 +37,7 @@ type Check struct { // ServeHTTP implements an http POST handler that receives a JSON enconde Job, and returns an // agent.State JSON enconded response. func (c *Check) ServeHTTP(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "error reading request body", http.StatusBadRequest) @@ -78,7 +79,7 @@ func (c *Check) ServeHTTP(w http.ResponseWriter, r *http.Request) { err = c.checker.Run(ctx, job.Target, job.AssetType, job.Options, runtimeState) c.checker.CleanUp(ctx, job.Target, job.AssetType, job.Options) checkState.state.Report.CheckData.EndTime = time.Now() - elapsedTime := time.Since(checkState.state.Report.CheckData.StartTime) + elapsedTime := time.Since(startTime) // If an error has been returned, we set the correct status. if err != nil { if errors.Is(err, context.Canceled) { @@ -97,7 +98,7 @@ func (c *Check) ServeHTTP(w http.ResponseWriter, r *http.Request) { } checkState.state.Report.Status = checkState.state.Status - logger.WithField("seconds", elapsedTime.Seconds()).WithField("state", checkState.state.Status).Info("Check finished") + logger.WithFields(log.Fields{"seconds": elapsedTime.Round(time.Millisecond * 100).Seconds(), "state": checkState.state.Status}).Info("Check finished") // Initialize sync point for the checker and the push state to be finished. out, err := json.Marshal(checkState.state) From 4eeb5e9bdc2f0212202582e19ed8761b71d5bf34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Mon, 16 Dec 2024 12:01:23 +0100 Subject: [PATCH 10/26] Improve http server logging --- internal/http/check.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/http/check.go b/internal/http/check.go index be188be..201912d 100644 --- a/internal/http/check.go +++ b/internal/http/check.go @@ -120,9 +120,10 @@ func (c *Check) RunAndServe() { http.HandleFunc("/run", c.ServeHTTP) c.Logger.Info(fmt.Sprintf("Listening at %s", c.server.Addr)) go func() { - if err := c.server.ListenAndServe(); err != nil { - // handle err + if err := c.server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { + c.Logger.WithError(err).Error("Starting http server") } + c.Logger.Info("Stopped serving new connections.") }() s := <-c.exitSignal From 69e7421784d8ee31c55ed2f8430ef7bf913d0ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Mon, 16 Dec 2024 13:44:07 +0100 Subject: [PATCH 11/26] Make shutdown to wait for the server shutdown --- internal/http/check.go | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/internal/http/check.go b/internal/http/check.go index 201912d..40f916d 100644 --- a/internal/http/check.go +++ b/internal/http/check.go @@ -25,13 +25,14 @@ import ( // Check stores all the information needed to run a check locally. type Check struct { - Logger *log.Entry - Name string - checker Checker - config *config.Config - port int - server *http.Server - exitSignal chan os.Signal + Logger *log.Entry + Name string + checker Checker + config *config.Config + port int + server *http.Server + exitSignal chan os.Signal // used to stopt the server either by an os Signal or by calling Shutdown() + shuttedDown chan int // used to wait for the server to shut down. } // ServeHTTP implements an http POST handler that receives a JSON enconde Job, and returns an @@ -119,11 +120,15 @@ func (c *Check) RunAndServe() { }) http.HandleFunc("/run", c.ServeHTTP) c.Logger.Info(fmt.Sprintf("Listening at %s", c.server.Addr)) + c.shuttedDown = make(chan int) go func() { if err := c.server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { c.Logger.WithError(err).Error("Starting http server") } c.Logger.Info("Stopped serving new connections.") + if c.shuttedDown != nil { + c.shuttedDown <- 1 + } }() s := <-c.exitSignal @@ -149,21 +154,28 @@ type Job struct { RunTime int64 } -// Shutdown is needed to fulfil the check interface but we don't need to do -// anything in this case. +// Shutdown is needed to fulfil the check interface and in this case we are +// shutting down the http server and waiting func (c *Check) Shutdown() error { + // Send the exit signal to shutdown the server. c.exitSignal <- syscall.SIGTERM + + if c.shuttedDown != nil { + // Wait for the server to shutdown. + <-c.shuttedDown + } return nil } // NewCheck creates new check to be run from the command line without having an agent. func NewCheck(name string, checker Checker, logger *log.Entry, conf *config.Config) *Check { c := &Check{ - Name: name, - Logger: logger, - config: conf, - exitSignal: make(chan os.Signal, 1), - port: *conf.Port, + Name: name, + Logger: logger, + config: conf, + exitSignal: make(chan os.Signal, 1), + port: *conf.Port, + shuttedDown: nil, } c.server = &http.Server{Addr: fmt.Sprintf(":%d", c.port)} signal.Notify(c.exitSignal, syscall.SIGINT, syscall.SIGTERM) From fd5edef88d814a77b368d66f4c72b2b84db43fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Mon, 16 Dec 2024 14:25:57 +0100 Subject: [PATCH 12/26] Improve test --- internal/http/check_test.go | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/internal/http/check_test.go b/internal/http/check_test.go index 8e4b230..b448137 100644 --- a/internal/http/check_test.go +++ b/internal/http/check_test.go @@ -114,7 +114,7 @@ func sleepCheckRunner(ctx context.Context, target, assetType, optJSON string, st func TestIntegrationHttpMode(t *testing.T) { port := 8888 - startTime := time.Now() + requestTimeout := 3 * time.Second intTests := []httpTest{ { name: "HappyPath", @@ -136,28 +136,24 @@ func TestIntegrationHttpMode(t *testing.T) { Options: `{"SleepTime": 1}`, Target: "www.example.com", AssetType: "Hostname", - StartTime: startTime, }, "checkDeadline": { CheckID: "checkDeadline", Options: `{"SleepTime": 10}`, Target: "www.example.com", AssetType: "Hostname", - StartTime: startTime, }, "checkInconclusive": { CheckID: "checkInconclusive", Options: `{"SleepTime": 1}`, Target: "inconclusive", AssetType: "Hostname", - StartTime: startTime, }, "checkFailed": { CheckID: "checkFailed", Options: `{}`, Target: "www.example.com", AssetType: "Hostname", - StartTime: startTime, }, }, resourceToClean: map[string]string{"key": "initial"}, @@ -178,8 +174,6 @@ func TestIntegrationHttpMode(t *testing.T) { Target: "www.example.com", Options: `{"SleepTime": 1}`, Status: agent.StatusFinished, - StartTime: startTime, - EndTime: time.Time{}, }, ResultData: report.ResultData{ Vulnerabilities: []report.Vulnerability{ @@ -206,8 +200,6 @@ func TestIntegrationHttpMode(t *testing.T) { Target: "inconclusive", Options: `{"SleepTime": 1}`, Status: agent.StatusInconclusive, - StartTime: startTime, - EndTime: time.Time{}, }, }, }, @@ -221,8 +213,6 @@ func TestIntegrationHttpMode(t *testing.T) { Target: "www.example.com", Options: `{}`, Status: agent.StatusFailed, - StartTime: startTime, - EndTime: time.Time{}, }, ResultData: report.ResultData{ Error: "error: missing or 0 sleep time", @@ -277,7 +267,7 @@ func TestIntegrationHttpMode(t *testing.T) { l.Error("Marshal error", "error", err) return } - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) defer cancel() req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(cc)) if err != nil { @@ -323,7 +313,7 @@ func TestIntegrationHttpMode(t *testing.T) { results[x.check] = x.resp } - diff := cmp.Diff(results, tt.want, cmpopts.IgnoreFields(report.CheckData{}, "EndTime")) + diff := cmp.Diff(results, tt.want, cmpopts.IgnoreFields(report.CheckData{}, "StartTime", "EndTime")) if diff != "" { t.Errorf("Error in test %s. diffs %+v", tt.name, diff) } From d0e77bf94ea3ec61bbb3f46bd97e626b9ac710fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Wed, 18 Dec 2024 14:24:05 +0100 Subject: [PATCH 13/26] Review fixes --- helpers/command/command.go | 6 +++--- internal/http/check.go | 12 ++++++------ internal/http/check_test.go | 15 +++++++-------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/helpers/command/command.go b/helpers/command/command.go index 38b3f44..86842ef 100644 --- a/helpers/command/command.go +++ b/helpers/command/command.go @@ -94,7 +94,7 @@ func Execute(ctx context.Context, logger *log.Entry, exe string, params ...strin return } -// Execute executes a 'command' in a new process +// ExecuteEnv executes a 'command' in a new process // Parameter command must contain a path to the command, or simply the command name if lookup in path is wanted. // A nil value can be passed in parameters ctx and logger. // Returns the outputs of the process written to the standard output and the status code returned by the command. @@ -111,7 +111,7 @@ func ExecuteAndParseJSON(ctx context.Context, logger *log.Entry, result interfac return ExecuteEnvAndParseJSON(ctx, logger, result, nil, exe, params...) } -// ExecuteAndParseJSON executes a command, using the func Execute. +// ExecuteEnvAndParseJSON executes a command, using the func Execute. // After execution: // returned error is nil and the param result contains the output parsed as json. // (x)or @@ -130,7 +130,7 @@ func ExecuteAndParseXML(ctx context.Context, logger *log.Entry, result interface return ExecuteEnvAndParseXML(ctx, logger, result, nil, exe, params...) } -// ExecuteAndParseXML executes a command, using the func Execute. +// ExecuteEnvAndParseXML executes a command, using the func Execute. // After execution: // returned error is nil and the param result contains the output parsed as XML. // (x)or diff --git a/internal/http/check.go b/internal/http/check.go index 40f916d..1d4f923 100644 --- a/internal/http/check.go +++ b/internal/http/check.go @@ -16,11 +16,12 @@ import ( "syscall" "time" + report "github.com/adevinta/vulcan-report" + log "github.com/sirupsen/logrus" + "github.com/adevinta/vulcan-check-sdk/agent" "github.com/adevinta/vulcan-check-sdk/config" "github.com/adevinta/vulcan-check-sdk/state" - report "github.com/adevinta/vulcan-report" - log "github.com/sirupsen/logrus" ) // Check stores all the information needed to run a check locally. @@ -35,8 +36,8 @@ type Check struct { shuttedDown chan int // used to wait for the server to shut down. } -// ServeHTTP implements an http POST handler that receives a JSON enconde Job, and returns an -// agent.State JSON enconded response. +// ServeHTTP implements an HTTP POST handler that receives a JSON encoded job, and returns an +// agent.State JSON encoded response. func (c *Check) ServeHTTP(w http.ResponseWriter, r *http.Request) { startTime := time.Now() body, err := io.ReadAll(r.Body) @@ -45,8 +46,7 @@ func (c *Check) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } var job Job - err = json.Unmarshal(body, &job) - if err != nil { + if err = json.Unmarshal(body, &job); err != nil { w.WriteHeader(500) return } diff --git a/internal/http/check_test.go b/internal/http/check_test.go index b448137..53ff5b2 100644 --- a/internal/http/check_test.go +++ b/internal/http/check_test.go @@ -1,5 +1,5 @@ /* -Copyright 2019 Adevinta +Copyright 2024 Adevinta */ package http @@ -17,17 +17,16 @@ import ( "testing" "time" + report "github.com/adevinta/vulcan-report" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "go.uber.org/goleak" - log "github.com/sirupsen/logrus" + "go.uber.org/goleak" "github.com/adevinta/vulcan-check-sdk/agent" "github.com/adevinta/vulcan-check-sdk/config" "github.com/adevinta/vulcan-check-sdk/internal/logging" "github.com/adevinta/vulcan-check-sdk/state" - report "github.com/adevinta/vulcan-report" ) type CheckerHandleRun func(ctx context.Context, target, assetType, opts string, s state.State) error @@ -42,7 +41,7 @@ type CheckerHandleCleanUp func(ctx context.Context, target, assetType, opts stri // CleanUp is used as adapter to satisfy the method with same name in interface Checker. func (handler CheckerHandleCleanUp) CleanUp(ctx context.Context, target, assetType, opts string) { - (handler(ctx, target, assetType, opts)) + handler(ctx, target, assetType, opts) } // NewCheckFromHandler creates a new check given a checker run handler. @@ -77,7 +76,7 @@ type httpIntParams struct { } // sleepCheckRunner implements a check that sleeps based on the options and generates inconclusive in case of a target with that name. -func sleepCheckRunner(ctx context.Context, target, assetType, optJSON string, st state.State) (err error) { +func sleepCheckRunner(ctx context.Context, target, _, optJSON string, st state.State) (err error) { log := logging.BuildRootLog("TestChecker") log.Debug("Check running") st.SetProgress(0.1) @@ -246,7 +245,7 @@ func TestIntegrationHttpMode(t *testing.T) { resp agent.State } - // ch will receibe the results of the concurrent job executions + // ch will receive the results of the concurrent job executions ch := make(chan not, len(tt.args.jobs)) wg := sync.WaitGroup{} @@ -264,7 +263,7 @@ func TestIntegrationHttpMode(t *testing.T) { }() cc, err := json.Marshal(job) if err != nil { - l.Error("Marshal error", "error", err) + l.Fatal("Marshal error", "error", err) return } ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) From 308fae3e68719e2c9b3a69663cd792016d3b1541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Wed, 18 Dec 2024 14:50:00 +0100 Subject: [PATCH 14/26] Migrate from travis to github actions --- .github/workflows/build.yaml | 24 ++++++++++++++++++++++++ .travis.yml | 11 ----------- 2 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/build.yaml delete mode 100644 .travis.yml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..cdae6bc --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,24 @@ +name: Build + +on: + push: + pull_request: + +jobs: + + build: + + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: '>=1.19' + + - name: Install tests deps + run: sudo apt install nmap -y + + - name: Go test + run: go test ./... -count=1 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 03ba112..0000000 --- a/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -dist: bionic -language: go -go: - - 1.19.x -env: - global: - - CGO_ENABLED=0 -go_import_path: github.com/adevinta/vulcan-check-sdk -script: - - go install -v $(go list ./... | grep -v /vendor/) - - go test -short -v $(go list ./... | grep -v /vendor/) From 7270d8d0b984a3733139f19f94876ce03f5b69d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Wed, 18 Dec 2024 19:45:35 +0100 Subject: [PATCH 15/26] Fix test wait for healthz and tidy-up --- .github/workflows/build.yaml | 4 +- internal/http/check.go | 24 +++-- internal/http/check_test.go | 165 +++++++++++++++++++++-------------- 3 files changed, 115 insertions(+), 78 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index cdae6bc..0112bd6 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -5,9 +5,7 @@ on: pull_request: jobs: - build: - runs-on: ubuntu-24.04 steps: @@ -21,4 +19,4 @@ jobs: run: sudo apt install nmap -y - name: Go test - run: go test ./... -count=1 + run: go test -v ./... -count 1 diff --git a/internal/http/check.go b/internal/http/check.go index 1d4f923..4e8d02b 100644 --- a/internal/http/check.go +++ b/internal/http/check.go @@ -115,30 +115,37 @@ func (c *Check) ServeHTTP(w http.ResponseWriter, r *http.Request) { // execute a check. func (c *Check) RunAndServe() { http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.Write([]byte(`"OK"`)) + if c.shuttedDown == nil { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`"OK"`)) + } else { + // the server is shutting down + http.Error(w, "shuttingDown", http.StatusServiceUnavailable) + } }) http.HandleFunc("/run", c.ServeHTTP) c.Logger.Info(fmt.Sprintf("Listening at %s", c.server.Addr)) - c.shuttedDown = make(chan int) go func() { if err := c.server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { c.Logger.WithError(err).Error("Starting http server") } c.Logger.Info("Stopped serving new connections.") if c.shuttedDown != nil { + c.Logger.Info("Notifying shuttedDown") c.shuttedDown <- 1 + c.Logger.Info("Notified shuttedDown") } }() s := <-c.exitSignal c.Logger.WithField("signal", s.String()).Info("Stopping server") - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) // TODO: Allow configure value. + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) // TODO: Allow configure value. defer cancel() if err := c.server.Shutdown(ctx); err != nil { c.Logger.WithError(err).Error("Shutting down server") } + c.Logger.Info("Finished RunAndServe.") } type Job struct { @@ -160,10 +167,11 @@ func (c *Check) Shutdown() error { // Send the exit signal to shutdown the server. c.exitSignal <- syscall.SIGTERM - if c.shuttedDown != nil { - // Wait for the server to shutdown. - <-c.shuttedDown - } + c.shuttedDown = make(chan int) + // Wait for the server to shutdown. + c.Logger.Info("Shutdown: waiting for shuttedDown") + <-c.shuttedDown + c.Logger.Info("Shutdown:shutted down") return nil } diff --git a/internal/http/check_test.go b/internal/http/check_test.go index 53ff5b2..86074a0 100644 --- a/internal/http/check_test.go +++ b/internal/http/check_test.go @@ -14,6 +14,7 @@ import ( "net/http" "strconv" "sync" + "syscall" "testing" "time" @@ -68,7 +69,6 @@ type httpTest struct { type httpIntParams struct { checkRunner CheckerHandleRun - checkCleaner func(resourceToClean interface{}, ctx context.Context, target, assetType, optJSON string) resourceToClean interface{} checkName string config *config.Config @@ -111,9 +111,12 @@ func sleepCheckRunner(ctx context.Context, target, _, optJSON string, st state.S return nil } +// TestIntegrationHttpMode executes one test with numIters * len(jobs) concurrent http checks. func TestIntegrationHttpMode(t *testing.T) { port := 8888 requestTimeout := 3 * time.Second + wantHealthz := `"OK"` + numIters := 10 intTests := []httpTest{ { name: "HappyPath", @@ -156,10 +159,6 @@ func TestIntegrationHttpMode(t *testing.T) { }, }, resourceToClean: map[string]string{"key": "initial"}, - checkCleaner: func(resource interface{}, ctx context.Context, target, assetType, opt string) { - r := resource.(map[string]string) - r["key"] = "cleaned" - }, }, wantResourceState: map[string]string{"key": "cleaned"}, want: map[string]agent.State{ @@ -222,20 +221,12 @@ func TestIntegrationHttpMode(t *testing.T) { }, } - defer goleak.VerifyNone(t) - for _, tt := range intTests { tt := tt t.Run(tt.name, func(t2 *testing.T) { conf := tt.args.config - var cleaner func(ctx context.Context, target, assetType, opts string) - if tt.args.checkCleaner != nil { - cleaner = func(ctx context.Context, target, assetType, opts string) { - tt.args.checkCleaner(tt.args.resourceToClean, ctx, target, assetType, opts) - } - } l := logging.BuildRootLog("httpCheck") - c := NewCheckFromHandlerWithConfig(tt.args.checkName, tt.args.checkRunner, cleaner, conf, l) + c := NewCheckFromHandlerWithConfig(tt.args.checkName, tt.args.checkRunner, nil, conf, l) go c.RunAndServe() client := &http.Client{} url := fmt.Sprintf("http://localhost:%d/run", *tt.args.config.Port) @@ -245,78 +236,118 @@ func TestIntegrationHttpMode(t *testing.T) { resp agent.State } + // Wait for the server to be healthy and test the healthz response. + i := 5 + for i > 0 { + i-- + res, err := http.Get(fmt.Sprintf("http://localhost:%d/healthz", *tt.args.config.Port)) + if err != nil { + if errors.Is(err, syscall.ECONNREFUSED) { + l.Infof("Connection refused - let's wait ... %d", i) + time.Sleep(1 * time.Second) + continue + } + + // No other err should happen + t.Errorf("staring server: %v", err) + break + } + + // Is not an error, let's check the status code and the reponse. + if res.StatusCode != 200 { + t.Errorf("unexpected status for healthz: %v", res.StatusCode) + } + defer res.Body.Close() + body, _ := io.ReadAll(res.Body) + if string(body) != wantHealthz { + t.Errorf("unexpected body for healthz: %v", string(body)) + } + break + } + // Do not wait more + if i == 0 { + t.Error("Unable to start the server") + } + // ch will receive the results of the concurrent job executions - ch := make(chan not, len(tt.args.jobs)) + ch := make(chan not, len(tt.args.jobs)*numIters) wg := sync.WaitGroup{} // Runs each job in a go routine with a 3 seconds deadline. - for key, job := range tt.args.jobs { - wg.Add(1) - go func(key string, job Job) { - defer wg.Done() - var err error - n := not{ - check: key, - } - defer func() { - ch <- n - }() - cc, err := json.Marshal(job) - if err != nil { - l.Fatal("Marshal error", "error", err) - return - } - ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - defer cancel() - req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(cc)) - if err != nil { - l.Error("NewRequestWithContext error", "error", err) - return - } - req.Header.Add("Content-Type", "application/json") - resp, err := client.Do(req) - if err != nil { - if errors.Is(err, context.DeadlineExceeded) { - n.resp = agent.State{Status: agent.StatusAborted} + for i := 0; i < numIters; i++ { + for key, job := range tt.args.jobs { + wg.Add(1) + go func(key string, iter int, job Job) { + defer wg.Done() + var err error + n := not{ + check: key, + } + defer func() { + ch <- n + }() + cc, err := json.Marshal(job) + if err != nil { + t.Errorf("Marshal error: %v", err) + } + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) + defer cancel() + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(cc)) + if err != nil { + t.Errorf("NewRequestWithContext error: %v", err) return } - l.Error("request error", "error", err) - return - } - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - if err != nil { - l.Error("failed to read response body", "error", err) - return - } - r := agent.State{} - err = json.Unmarshal(body, &r) - if err != nil { - l.Error("Unable to unmarshal response", "error", err) - return - } - - // Compare resource to clean up state with wanted state. - diff := cmp.Diff(tt.wantResourceState, tt.args.resourceToClean) - if diff != "" { - t.Errorf("Error want resource to clean state != got. Diff %s", diff) - } - n.resp = r - }(key, job) + req.Header.Add("Content-Type", "application/json") + resp, err := client.Do(req) + if err != nil { + if errors.Is(err, context.DeadlineExceeded) { + n.resp = agent.State{Status: agent.StatusAborted} + return + } + t.Errorf("request error: %v", err) + return + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Errorf("failed to read response body: %v", err) + return + } + r := agent.State{} + err = json.Unmarshal(body, &r) + if err != nil { + t.Errorf("Unable to unmarshal response: %v", err) + return + } + n.resp = r + }(key, i, job) + } } wg.Wait() close(ch) + c.Shutdown() results := map[string]agent.State{} for x := range ch { + // We are executing numIters times the same job. The results for each one must be exactly the same. + if r, ok := results[x.check]; ok { + diff := cmp.Diff(r, x.resp, cmpopts.IgnoreFields(report.CheckData{}, "StartTime", "EndTime")) + if diff != "" { + t.Errorf("Result mismatch from previous result %s. diffs %+v", tt.name, diff) + } + } results[x.check] = x.resp } + // Test that the final result matches. diff := cmp.Diff(results, tt.want, cmpopts.IgnoreFields(report.CheckData{}, "StartTime", "EndTime")) if diff != "" { t.Errorf("Error in test %s. diffs %+v", tt.name, diff) } - c.Shutdown() + + l.Info("waiting for go routines to tidy-up before goleak test ....") + time.Sleep(5 * time.Second) + goleak.VerifyNone(t) }) } } From ae9bb2d246e70f4c7da9b816704460d906c49c17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Wed, 15 Jan 2025 14:14:06 +0100 Subject: [PATCH 16/26] Fix logger --- internal/http/check.go | 2 +- process.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/http/check.go b/internal/http/check.go index 4e8d02b..269f538 100644 --- a/internal/http/check.go +++ b/internal/http/check.go @@ -90,7 +90,7 @@ func (c *Check) ServeHTTP(w http.ResponseWriter, r *http.Request) { } else if errors.Is(err, state.ErrNonPublicAsset) { checkState.state.Status = agent.StatusInconclusive } else { - c.Logger.WithError(err).Error("Error running check") + logger.WithError(err).Error("Error running check") checkState.state.Status = agent.StatusFailed checkState.state.Report.Error = err.Error() } diff --git a/process.go b/process.go index 9a754ef..d62ec25 100644 --- a/process.go +++ b/process.go @@ -58,6 +58,11 @@ type ProcessCheck struct { // Run starts the execution of the process. func (p *ProcessCheck) Run(ctx context.Context) (pState *os.ProcessState, err error) { + // In case a logger is in the context we override the default logger. + if l, ok := ctx.Value("logger").(*log.Entry); ok { + p.logger = l + } + childCtx, cancel := context.WithCancel(ctx) p.cancel = cancel p.logger.WithFields(log.Fields{"process_exec": p.executable, "process_params": p.args}).Info("Running process") From 6ce9e2cd4db9ae8936152d42e46f2ff4ff1288a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Thu, 23 Jan 2025 13:15:43 +0100 Subject: [PATCH 17/26] Suggestions in actions --- .github/workflows/build.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 0112bd6..397b9d5 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -6,7 +6,7 @@ on: jobs: build: - runs-on: ubuntu-24.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -19,4 +19,4 @@ jobs: run: sudo apt install nmap -y - name: Go test - run: go test -v ./... -count 1 + run: go test -v -count 1 -race ./... From 741780dce95ee6f9fb021ad7f580f24a80737eae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Thu, 23 Jan 2025 13:16:02 +0100 Subject: [PATCH 18/26] Error when invalid port --- config/config.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/config/config.go b/config/config.go index ea91387..9d46b77 100644 --- a/config/config.go +++ b/config/config.go @@ -92,7 +92,9 @@ func OverrideConfigFromOptions(c *Config) { func OverrideConfigFromEnvVars(c *Config) error { overrideConfigLogEnvVars(c) overrideConfigCheckEnvVars(c) - overrideCommConfigEnvVars(c) + if err := overrideCommConfigEnvVars(c); err != nil { + return err + } return overrideValidationConfigEnvVars(c) } @@ -110,7 +112,7 @@ func overrideValidationConfigEnvVars(c *Config) error { return nil } -func overrideCommConfigEnvVars(c *Config) { +func overrideCommConfigEnvVars(c *Config) error { comMode := os.Getenv(commModeEnv) if comMode != "" { c.CommMode = comMode @@ -126,8 +128,9 @@ func overrideCommConfigEnvVars(c *Config) { port := os.Getenv(httpPort) if port != "" { - p, err := strconv.Atoi(port) - if err == nil { + if p, err := strconv.Atoi(port); err != nil { + return fmt.Errorf("invalid port number: %v", port) + } else { c.Port = &p } } @@ -142,6 +145,7 @@ func overrideCommConfigEnvVars(c *Config) { if msgBuffLen != "" { c.Push.BufferLen = int(len) } + return nil } func overrideConfigLogEnvVars(c *Config) { From 7cacc936f22da6f217bf31370034186e72209bab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Thu, 23 Jan 2025 17:24:13 +0100 Subject: [PATCH 19/26] Improve loggin fields and add vcs info --- bootstrapper.go | 19 ++++++++++++------- config/config.go | 27 +++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/bootstrapper.go b/bootstrapper.go index 6dc765a..05e71a0 100644 --- a/bootstrapper.go +++ b/bootstrapper.go @@ -132,13 +132,18 @@ func NewCheck(name string, checker Checker) Check { conf.Check.Opts = options c = newLocalCheck(name, checker, logger, conf, json) } else if conf.Port != nil { - logger.Debug("Http mode") - l := logging.BuildLoggerWithConfigAndFields(conf.Log, log.Fields{ - // "checkTypeName": "TODO", - // "checkTypeVersion": "TODO", - // "component": "checks", - }) - c = http.NewCheck(name, checker, l, conf) + lf := log.Fields{} + if conf.Check.CheckTypeName != "" { + lf["CheckTypeName"] = conf.Check.CheckTypeName + } + if conf.Check.CheckTypeVersion != "" { + lf["CheckTypeVersion"] = conf.Check.CheckTypeVersion + } + if conf.VcsRevision != "" { + lf["VcsRev"] = conf.VcsRevision + } + logger = logging.BuildLoggerWithConfigAndFields(conf.Log, lf) + c = http.NewCheck(name, checker, logger, conf) } else { logger.Debug("Push mode") c = push.NewCheckWithConfig(name, checker, logger, conf) diff --git a/config/config.go b/config/config.go index 9d46b77..a8f6ace 100644 --- a/config/config.go +++ b/config/config.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "os" + "runtime/debug" "strconv" "github.com/BurntSushi/toml" @@ -68,6 +69,7 @@ type Config struct { Port *int `toml:"Port"` AllowPrivateIPs *bool `toml:"AllowPrivateIps"` RequiredVars map[string]string `toml:"RequiredVars"` + VcsRevision string `toml:"VcsRevision"` } type optionsLogConfig struct { @@ -95,6 +97,7 @@ func OverrideConfigFromEnvVars(c *Config) error { if err := overrideCommConfigEnvVars(c); err != nil { return err } + c.VcsRevision = getVcsRevision() return overrideValidationConfigEnvVars(c) } @@ -226,3 +229,27 @@ func BuildConfig() (*Config, error) { return c, nil // NOTE: what happens if there no config file and also no env vars setted? } + +// getVcsRevision gets the buildInfo vcs revision if available. +func getVcsRevision() string { + if x, ok := debug.ReadBuildInfo(); ok { + rev := "" + mod := "" + for _, s := range x.Settings { + switch s.Key { + case "vcs.revision": + if len(s.Value) == 40 { // is the commin hash length + rev = s.Value[:8] + } else { + rev = s.Value + } + case "vcs.modified": + if s.Value == "true" { + mod = "*" + } + } + } + return rev + mod + } + return "" +} From 6cc32267778812d6e5e3512aca7f99ef293474b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Fri, 24 Jan 2025 10:46:47 +0100 Subject: [PATCH 20/26] Rename CloneGitRepository --- helpers/git.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helpers/git.go b/helpers/git.go index 0f20007..e251305 100644 --- a/helpers/git.go +++ b/helpers/git.go @@ -22,12 +22,12 @@ const ( // CloneGitRepository executes CloneGitRepositoryContext with background context. func CloneGitRepository(target string, branch string, depth int) (string, string, error) { - return CloneGitRepositoryContext(context.Background(), target, branch, depth) + return CloneGitRepositoryWithContext(context.Background(), target, branch, depth) } // CloneGitRepositoryContext clones a Git repository into a temporary directory and returns the path and branch name. // If a branch is not specified, the default branch will be used and its name will be returned. -func CloneGitRepositoryContext(ctx context.Context, target string, branch string, depth int) (string, string, error) { +func CloneGitRepositoryWithContext(ctx context.Context, target string, branch string, depth int) (string, string, error) { // Check if the target repository is on Github Enterprise and return populated credentials if necessary. auth, err := gheAuth(target) if err != nil { From 877c330718594bef1468b09846b8dbdfe4e3f388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Fri, 24 Jan 2025 11:33:10 +0100 Subject: [PATCH 21/26] Refactor http server signals --- internal/http/check.go | 90 +++++++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 37 deletions(-) diff --git a/internal/http/check.go b/internal/http/check.go index 269f538..dec66c3 100644 --- a/internal/http/check.go +++ b/internal/http/check.go @@ -13,6 +13,7 @@ import ( "net/http" "os" "os/signal" + "sync/atomic" "syscall" "time" @@ -26,14 +27,19 @@ import ( // Check stores all the information needed to run a check locally. type Check struct { - Logger *log.Entry - Name string - checker Checker - config *config.Config - port int - server *http.Server - exitSignal chan os.Signal // used to stopt the server either by an os Signal or by calling Shutdown() - shuttedDown chan int // used to wait for the server to shut down. + Logger *log.Entry + Name string + checker Checker + config *config.Config + port int + exitSignal chan os.Signal // used to stopt the server either by an os Signal or by calling Shutdown() + shutdownSignal chan bool // used to signal the server to shutdown. + done chan bool // used to wait for the server to shut down. + inShutdown atomic.Bool // used to know if the server is shutting down. +} + +func (c *Check) shutingDown() bool { + return c.inShutdown.Load() } // ServeHTTP implements an HTTP POST handler that receives a JSON encoded job, and returns an @@ -114,38 +120,45 @@ func (c *Check) ServeHTTP(w http.ResponseWriter, r *http.Request) { // RunAndServe implements the behavior needed by the sdk for a check runner to // execute a check. func (c *Check) RunAndServe() { - http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { - if c.shuttedDown == nil { - w.Header().Set("Content-Type", "application/json") - w.Write([]byte(`"OK"`)) - } else { - // the server is shutting down + mux := http.ServeMux{} + mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { + if c.shutingDown() { http.Error(w, "shuttingDown", http.StatusServiceUnavailable) + return } + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`"OK"`)) }) - http.HandleFunc("/run", c.ServeHTTP) - c.Logger.Info(fmt.Sprintf("Listening at %s", c.server.Addr)) + mux.HandleFunc("/run", c.ServeHTTP) + + server := &http.Server{ + Addr: fmt.Sprintf(":%d", c.port), + Handler: &mux, + } + + c.Logger.Info(fmt.Sprintf("Listening at %s", server.Addr)) go func() { - if err := c.server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { + if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { c.Logger.WithError(err).Error("Starting http server") } c.Logger.Info("Stopped serving new connections.") - if c.shuttedDown != nil { - c.Logger.Info("Notifying shuttedDown") - c.shuttedDown <- 1 - c.Logger.Info("Notified shuttedDown") - } + c.done <- true }() - s := <-c.exitSignal + select { + case s := <-c.exitSignal: + c.Logger.WithField("signal", s.String()).Info("Signal received") + case <-c.shutdownSignal: + c.Logger.Info("Shutdown request received") + } - c.Logger.WithField("signal", s.String()).Info("Stopping server") + c.Logger.Info("Stopping server") ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) // TODO: Allow configure value. defer cancel() - if err := c.server.Shutdown(ctx); err != nil { + if err := server.Shutdown(ctx); err != nil { c.Logger.WithError(err).Error("Shutting down server") } - c.Logger.Info("Finished RunAndServe.") + c.Logger.Info("Finished RunAndServe") } type Job struct { @@ -164,13 +177,16 @@ type Job struct { // Shutdown is needed to fulfil the check interface and in this case we are // shutting down the http server and waiting func (c *Check) Shutdown() error { - // Send the exit signal to shutdown the server. - c.exitSignal <- syscall.SIGTERM + if c.shutingDown() { + return nil + } + c.inShutdown.Store(true) - c.shuttedDown = make(chan int) + // Send the exit signal to shutdown the server. + c.shutdownSignal <- true // Wait for the server to shutdown. c.Logger.Info("Shutdown: waiting for shuttedDown") - <-c.shuttedDown + <-c.done c.Logger.Info("Shutdown:shutted down") return nil } @@ -178,14 +194,14 @@ func (c *Check) Shutdown() error { // NewCheck creates new check to be run from the command line without having an agent. func NewCheck(name string, checker Checker, logger *log.Entry, conf *config.Config) *Check { c := &Check{ - Name: name, - Logger: logger, - config: conf, - exitSignal: make(chan os.Signal, 1), - port: *conf.Port, - shuttedDown: nil, + Name: name, + Logger: logger, + config: conf, + exitSignal: make(chan os.Signal, 1), + shutdownSignal: make(chan bool), + port: *conf.Port, + done: make(chan bool), } - c.server = &http.Server{Addr: fmt.Sprintf(":%d", c.port)} signal.Notify(c.exitSignal, syscall.SIGINT, syscall.SIGTERM) c.checker = checker return c From b0dbd23ad4f9956b0057946b78a65c8ad45ea1e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Fri, 24 Jan 2025 13:27:10 +0100 Subject: [PATCH 22/26] Refactor http server signals: fixup --- internal/http/check.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/http/check.go b/internal/http/check.go index dec66c3..ac97099 100644 --- a/internal/http/check.go +++ b/internal/http/check.go @@ -149,6 +149,7 @@ func (c *Check) RunAndServe() { case s := <-c.exitSignal: c.Logger.WithField("signal", s.String()).Info("Signal received") case <-c.shutdownSignal: + c.inShutdown.Store(true) c.Logger.Info("Shutdown request received") } From 24c1494b126395343977aedefe633339f71f71f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Fri, 24 Jan 2025 15:15:50 +0100 Subject: [PATCH 23/26] Fix race condition in test --- .github/workflows/build.yaml | 4 +++- internal/http/check_test.go | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 397b9d5..6597274 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -19,4 +19,6 @@ jobs: run: sudo apt install nmap -y - name: Go test - run: go test -v -count 1 -race ./... + run: | + go test ./internal/http/... -count=1 -race + go test -v -count 1 ./... diff --git a/internal/http/check_test.go b/internal/http/check_test.go index 86074a0..9d126a9 100644 --- a/internal/http/check_test.go +++ b/internal/http/check_test.go @@ -77,7 +77,11 @@ type httpIntParams struct { // sleepCheckRunner implements a check that sleeps based on the options and generates inconclusive in case of a target with that name. func sleepCheckRunner(ctx context.Context, target, _, optJSON string, st state.State) (err error) { - log := logging.BuildRootLog("TestChecker") + // Use the implementation of the logger from the check-sdk instead of NewCheckLogFromContext to prevent cycles. + log, ok := ctx.Value("logger").(*log.Entry) + if !ok { + return errors.New("logger not found in context") + } log.Debug("Check running") st.SetProgress(0.1) type t struct { From fa9cfab419ec569e1f34164d777579dbcb0afcc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Fri, 24 Jan 2025 15:56:11 +0100 Subject: [PATCH 24/26] Upgrade direct dependencies --- go.mod | 76 +++++++-------- go.sum | 298 ++++++++++++++++++--------------------------------------- 2 files changed, 132 insertions(+), 242 deletions(-) diff --git a/go.mod b/go.mod index 4a73a52..9c5fa0e 100644 --- a/go.mod +++ b/go.mod @@ -1,69 +1,69 @@ module github.com/adevinta/vulcan-check-sdk -go 1.19 +go 1.22 + +toolchain go1.23.5 require ( - github.com/BurntSushi/toml v1.2.1 + github.com/BurntSushi/toml v1.4.0 github.com/adevinta/vulcan-report v1.0.0 - github.com/adevinta/vulcan-types v1.2.11 + github.com/adevinta/vulcan-types v1.2.21 github.com/aws/aws-sdk-go v1.55.5 - github.com/go-git/go-git/v5 v5.12.0 + github.com/go-git/go-git/v5 v5.13.2 github.com/google/go-cmp v0.6.0 github.com/kr/pretty v0.3.1 github.com/lair-framework/go-nmap v0.0.0-20191202052157-3507e0b03523 - github.com/sirupsen/logrus v1.9.0 + github.com/sirupsen/logrus v1.9.3 go.uber.org/goleak v1.3.0 - google.golang.org/api v0.171.0 + google.golang.org/api v0.218.0 gopkg.in/resty.v1 v1.12.0 ) require ( - cloud.google.com/go/compute v1.23.4 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/auth v0.14.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect + cloud.google.com/go/compute/metadata v0.6.0 // indirect dario.cat/mergo v1.0.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v1.0.0 // indirect + github.com/ProtonMail/go-crypto v1.1.5 // indirect github.com/cloudflare/circl v1.3.7 // indirect - github.com/cyphar/filepath-securejoin v0.2.4 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/distribution v2.8.3+incompatible // indirect + github.com/cyphar/filepath-securejoin v0.3.6 // indirect + github.com/distribution/reference v0.6.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.5.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/s2a-go v0.1.7 // indirect + github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.3 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kr/text v0.2.0 // indirect - github.com/miekg/dns v1.1.58 // indirect + github.com/miekg/dns v1.1.62 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/pjbgf/sha1cd v0.3.0 // indirect - github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect - github.com/skeema/knownhosts v1.2.2 // indirect + github.com/skeema/knownhosts v1.3.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/otel v1.24.0 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect - go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.22.0 // indirect - golang.org/x/oauth2 v0.18.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.17.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect - google.golang.org/grpc v1.62.1 // indirect - google.golang.org/protobuf v1.33.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel v1.31.0 // indirect + go.opentelemetry.io/otel/metric v1.31.0 // indirect + go.opentelemetry.io/otel/trace v1.31.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/mod v0.18.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/oauth2 v0.25.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/tools v0.22.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/go.sum b/go.sum index 3a14532..f488d24 100644 --- a/go.sum +++ b/go.sum @@ -1,101 +1,73 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go/compute v1.23.4 h1:EBT9Nw4q3zyE7G45Wvv3MzolIrCJEuHys5muLY0wvAw= -cloud.google.com/go/compute v1.23.4/go.mod h1:/EJMj55asU6kAFnuZET8zqgwgJ9FvXWXOkkfQZa4ioI= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/auth v0.14.0 h1:A5C4dKV/Spdvxcl0ggWwWEzzP7AZMJSEIgrkngwhGYM= +cloud.google.com/go/auth v0.14.0/go.mod h1:CYsoRL1PdiDuqeQpZE0bP2pnPrGqFcOkI0nldEQis+A= +cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= +cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= -github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= +github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/adevinta/vulcan-report v1.0.0 h1:44aICPZ+4svucgCSA5KmjlT3ZGzrvZXiSnkbnj6AC2k= github.com/adevinta/vulcan-report v1.0.0/go.mod h1:k34KaeoXc3H77WNMwI9F4F1G28hBjB95PeMUp9oHbEE= -github.com/adevinta/vulcan-types v1.2.11 h1:Jqd/L6gL+X1lLS2NivrnQOs4wlvbYE/Xw9ZxJxRoJ8U= -github.com/adevinta/vulcan-types v1.2.11/go.mod h1:CFNWxeKdr7mVq0hbzstxBUPQIJ4iKvmQ4gqL/O1qDuI= +github.com/adevinta/vulcan-types v1.2.21 h1:mgRUHo0kVQ8ZJ0oFaPOxrns1YqdWSIaJ/2dKnBT0x4c= +github.com/adevinta/vulcan-types v1.2.21/go.mod h1:YOtF3BmOQFRrUiFl5DOgjJhzZRh2acdbC80jMDQaZIo= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= -github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= +github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= 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.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= -github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= +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/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM= +github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= -github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= -github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= -github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= +github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +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/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= -github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= -github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= @@ -109,177 +81,97 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lair-framework/go-nmap v0.0.0-20191202052157-3507e0b03523 h1:N4NQR4on0n3Kc3xlBXUYzCZorFdordwkR2kcZMk9te0= github.com/lair-framework/go-nmap v0.0.0-20191202052157-3507e0b03523/go.mod h1:7Em1Lxm3DFdLvXWUZ6bQ/xIbGlxFy7jl07bziQMZ/kU= -github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= -github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= +github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= 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/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= -github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= -github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= +github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +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/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +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.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= +go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -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-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= +golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -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.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -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/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.6/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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -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/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= -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= -google.golang.org/api v0.171.0 h1:w174hnBPqut76FzW5Qaupt7zY8Kql6fiVjgys4f58sU= -google.golang.org/api v0.171.0/go.mod h1:Hnq5AHm4OTMt2BUVjael2CWZFD6vksJdWCWiUAmjC9o= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1:lfpJ/2rWPa/kJgxyyXM8PrNnfCzcmxJ265mADgwmvLI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= -google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +google.golang.org/api v0.218.0 h1:x6JCjEWeZ9PFCRe9z0FBrNwj7pB7DOAqT35N+IPnAUA= +google.golang.org/api v0.218.0/go.mod h1:5VGHBAkxrA/8EFjLVEYmMUJ8/8+gWWQ3s4cFH0FxG2M= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/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-20190902080502-41f04d3bba15/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/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= @@ -289,5 +181,3 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From abdba9a5fd6711076807cb5b871aac7337e11f78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Fri, 24 Jan 2025 18:14:02 +0100 Subject: [PATCH 25/26] Fix when the server is unable to start --- internal/http/check.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/internal/http/check.go b/internal/http/check.go index ac97099..5ea87a8 100644 --- a/internal/http/check.go +++ b/internal/http/check.go @@ -34,7 +34,7 @@ type Check struct { port int exitSignal chan os.Signal // used to stopt the server either by an os Signal or by calling Shutdown() shutdownSignal chan bool // used to signal the server to shutdown. - done chan bool // used to wait for the server to shut down. + finished chan error // used to wait for the server to shutted down. inShutdown atomic.Bool // used to know if the server is shutting down. } @@ -139,13 +139,16 @@ func (c *Check) RunAndServe() { c.Logger.Info(fmt.Sprintf("Listening at %s", server.Addr)) go func() { if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { - c.Logger.WithError(err).Error("Starting http server") + c.finished <- err + } else { + c.finished <- nil } - c.Logger.Info("Stopped serving new connections.") - c.done <- true }() select { + case err := <-c.finished: + c.Logger.WithError(err).Error("ListenAndServe: Unable to start server") + return // No need to shutdow the server because it was not started. case s := <-c.exitSignal: c.Logger.WithField("signal", s.String()).Info("Signal received") case <-c.shutdownSignal: @@ -157,7 +160,8 @@ func (c *Check) RunAndServe() { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) // TODO: Allow configure value. defer cancel() if err := server.Shutdown(ctx); err != nil { - c.Logger.WithError(err).Error("Shutting down server") + // Some requests were canceled, but the server was shutdown. + c.Logger.WithError(err).Warn("Shutting down server") } c.Logger.Info("Finished RunAndServe") } @@ -185,11 +189,9 @@ func (c *Check) Shutdown() error { // Send the exit signal to shutdown the server. c.shutdownSignal <- true - // Wait for the server to shutdown. + c.Logger.Info("Shutdown: waiting for shuttedDown") - <-c.done - c.Logger.Info("Shutdown:shutted down") - return nil + return <-c.finished } // NewCheck creates new check to be run from the command line without having an agent. @@ -201,7 +203,7 @@ func NewCheck(name string, checker Checker, logger *log.Entry, conf *config.Conf exitSignal: make(chan os.Signal, 1), shutdownSignal: make(chan bool), port: *conf.Port, - done: make(chan bool), + finished: make(chan error), } signal.Notify(c.exitSignal, syscall.SIGINT, syscall.SIGTERM) c.checker = checker From bbd5df34bd0bd44d05a72c92d3ba87bafb79fe07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Carpintero?= Date: Fri, 24 Jan 2025 18:14:20 +0100 Subject: [PATCH 26/26] Allow to configure ShutdownTimeout --- config/config.go | 24 ++++++++++++++++++------ internal/http/check.go | 6 +++++- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/config/config.go b/config/config.go index a8f6ace..b8f48fb 100644 --- a/config/config.go +++ b/config/config.go @@ -31,7 +31,8 @@ const ( pushAgentAddr = "VULCAN_AGENT_ADDRESS" pushMsgBufferLen = "VULCAN_CHECK_MSG_BUFF_LEN" - httpPort = "VULCAN_HTTP_PORT" + httpPort = "VULCAN_HTTP_PORT" + httpShutdownTimeout = "VULCAN_HTTP_SHUTDOWN_TIMEOUT" // Allows scanning private / reserved IP addresses. allowPrivateIPs = "VULCAN_ALLOW_PRIVATE_IPS" @@ -67,6 +68,8 @@ type Config struct { CommMode string `toml:"CommMode"` Push rest.PusherConfig `toml:"Push"` Port *int `toml:"Port"` + ShutdownTimeout *int `toml:"ShutdownTimeout"` + AllowPrivateIPs *bool `toml:"AllowPrivateIps"` RequiredVars map[string]string `toml:"RequiredVars"` VcsRevision string `toml:"VcsRevision"` @@ -129,12 +132,21 @@ func overrideCommConfigEnvVars(c *Config) error { c.Push.AgentAddr = pushEndPoint } - port := os.Getenv(httpPort) - if port != "" { - if p, err := strconv.Atoi(port); err != nil { - return fmt.Errorf("invalid port number: %v", port) + text := os.Getenv(httpPort) + if text != "" { + if i, err := strconv.Atoi(text); err != nil { + return fmt.Errorf("invalid port number: %v", text) + } else { + c.Port = &i + } + } + + text = os.Getenv(httpShutdownTimeout) + if text != "" { + if i, err := strconv.Atoi(text); err != nil { + return fmt.Errorf("invalid shutdownTimeout number: %v", text) } else { - c.Port = &p + c.ShutdownTimeout = &i } } diff --git a/internal/http/check.go b/internal/http/check.go index 5ea87a8..b4fdb56 100644 --- a/internal/http/check.go +++ b/internal/http/check.go @@ -157,7 +157,11 @@ func (c *Check) RunAndServe() { } c.Logger.Info("Stopping server") - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) // TODO: Allow configure value. + secs := 30 + if c.config.ShutdownTimeout != nil { + secs = *c.config.ShutdownTimeout + } + ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(secs)) defer cancel() if err := server.Shutdown(ctx); err != nil { // Some requests were canceled, but the server was shutdown.