From 639b3fbe305b347d57bd467a9a58aa386c1f7404 Mon Sep 17 00:00:00 2001 From: Berger Eugene Date: Sat, 9 Jul 2022 21:36:59 +0300 Subject: [PATCH] Health checks --- README.md | 112 ++++++++++++++++++++++++----------- go.mod | 5 +- go.sum | 87 ++++++++++++++------------- process-compose.yaml | 42 +++++++++++++- src/app/config.go | 10 ++++ src/app/process.go | 103 ++++++++++++++++++++++++++------ src/app/project.go | 2 + src/cmd/command.go | 36 ++++++++++++ src/health/exec_checker.go | 26 +++++++++ src/health/health_checks.go | 113 ++++++++++++++++++++++++++++++++++++ src/health/probe.go | 81 ++++++++++++++++++++++++++ src/tui/view.go | 9 +-- 12 files changed, 529 insertions(+), 97 deletions(-) create mode 100644 src/cmd/command.go create mode 100644 src/health/exec_checker.go create mode 100644 src/health/health_checks.go create mode 100644 src/health/probe.go diff --git a/README.md b/README.md index 6e01d9b..d27f4b5 100755 --- a/README.md +++ b/README.md @@ -158,43 +158,23 @@ process2: ##### ✅ Termination Parameters ```yaml -nginx: - command: "docker run --rm --name nginx_test nginx" +process1: + command: "pg_ctl start" shutdown: - command: "docker stop nginx_test" - timeout_seconds: 10 # default 10 - signal: 15 # default 15, but only if the 'command' is not defined or empty -``` - -`shutdown` is optional and can be omitted. The default behavior in this case: `SIGTERM` is issued to the running process. - -In case only `shutdown.signal` is defined `[1..31] ` the running process will be terminated with its value. - -In case the `shutdown.command` is defined: - -1. The `shutdown.command` is executed with all the Environment Variables of the primary process -2. Wait for `shutdown.timeout_seconds` for its completion (if not defined wait for 10 seconds) -3. In case of timeout, the process will receive the `SIGKILL` signal - -##### ✅ Background (detached) Processes - -```yaml -nginx: - command: "docker run -d --rm --name nginx_test nginx" # note the '-d' for detached mode - is_daemon: true # this flag is required for background processes (default false) - shutdown: - command: "docker stop nginx_test" + command: "pg_ctl stop" timeout_seconds: 10 # default 10 signal: 15 # default 15, but only if command is not defined or empty ``` -1. For processes that start services / daemons in the background, please use the `is_daemon` flag set to `true`. +`shutdown` is optional and can be omitted. The default behaviour in this case: `SIGTERM` is issued to the running process. -2. In case a process is daemon it will be considered running until stopped. +In case only `shutdown.signal` is defined `[1..31] ` the running process will be terminated with its value. -3. Daemon processes can only be stopped with the `$PROCESSNAME.shutdown.command` as in the example above. +In case the the `shutdown.command` is defined: - +1. The `shutdown.command` is executed with all the Environment Variables of the main process +2. Wait `shutdown.timeout_seconds` for its completion (if not defined wait for 10 seconds) +3. In case of timeout the process will receive the `SIGKILL` signal #### ✅ Output Handling @@ -273,15 +253,79 @@ processes: This setting controls the `process-compose` log level. The processes log level should be defined inside the process. It is recommended to support its definition with an environment variable that can be defined in `process-compose.yaml` -#### ❌ Health Checks +#### ✅ Health Checks -##### ❌ Is Alive +Many applications running for long periods of time eventually transition to broken states, and cannot recover except by being restarted. Process Compose provides liveness and readiness probes to detect and remedy such situations. -##### ❌ Is Ready +Probes configuration and functionality are designed to work similarly to [Kubernetes liveness and readiness probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/). -##### ❌ Auto Restart if not healthy +##### ✅ Liveness Probe -##### ✅ Auto Restart on exit +```yaml + nginx: + command: "docker run -d --rm -p80:80 --name nginx_test nginx" + is_daemon: true + shutdown: + command: "docker stop nginx_test" + signal: 15 + timeout_seconds: 5 + liveness_probe: + exec: + command: "[ $(docker inspect -f '{{.State.Running}}' nginx_test) = 'true' ]" + initial_delay_seconds: 5 + period_seconds: 2 + timeout_seconds: 5 + success_threshold: 1 + failure_threshold: 3 +``` + + + +##### ✅ Readiness Probe + +```yaml + nginx: + command: "docker run -d --rm -p80:80 --name nginx_test nginx" + is_daemon: true + shutdown: + command: "docker stop nginx_test" + readiness_probe: + http_get: + host: 127.0.0.1 + scheme: http + path: "/" + port: 80 + initial_delay_seconds: 5 + period_seconds: 10 + timeout_seconds: 5 + success_threshold: 1 + failure_threshold: 3 +``` + +Each probe type (`liveness_probe` or `readiness_probe`) can be configured in to use one of the 2 mutually exclusive modes: + +1. `exec`: Will run a configured `command` and based on the `exit code` decide if the process is in a correct state. 0 indicates success. Any other value indicates failure. +2. `http_get`: For an HTTP probe, the Process Compose sends an HTTP request to the specified path and port to perform the check. Response code 200 indicates success. Any other value indicates failure. + - `host`: Host name to connect to. + - `scheme`: Scheme to use for connecting to the host (HTTP or HTTPS). Defaults to HTTP. + - `path`: Path to access on the HTTP server. Defaults to /. + - `port`: Number of the port to access on the process. Number must be in the range 1 to 65535. + +##### Configure Probes + +Probes have a number of fields that you can use to more precisely control the behavior of liveness and readiness checks: + +- `initial_delay_seconds`: Number of seconds after the container has started before liveness or readiness probes are initiated. Defaults to 0 seconds. Minimum value is 0. +- `period_seconds`: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. +- `timeout_seconds`: Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. +- `success_threshold`: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup Probes. Minimum value is 1. **Note**: this value is not respected and was added as a placeholder for future implementation. +- `failure_threshold`: When a probe fails, Process Compose will try `failure_threshold` times before giving up. Giving up in case of liveness probe means restarting the process. In case of readiness probe the Pod will be marked Unready. Defaults to 3. Minimum value is 1. + +##### ✅ Auto Restart if not Healthy + +In order to insure that the process is restarted (and not transitioned to completed state) in case of readiness check fail, please make sure to define the `availability` configuration. For background (`is_daemon=true`) processes, the `restart` policy should be `always`. + +##### ✅ Auto Restart on Exit ```yaml process2: diff --git a/go.mod b/go.mod index 4bbfb9a..45f6f3f 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/f1bonacc1/process-compose go 1.18 require ( + github.com/InVisionApp/go-health/v2 v2.1.2 github.com/fatih/color v1.13.0 github.com/gdamore/tcell/v2 v2.5.1 github.com/gin-gonic/gin v1.8.0 @@ -12,7 +13,10 @@ require ( gopkg.in/yaml.v2 v2.4.0 ) +replace github.com/InVisionApp/go-health/v2 => github.com/f1bonacc1/go-health/v2 v2.1.3 + require ( + github.com/InVisionApp/go-logger v1.0.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/gdamore/encoding v1.0.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect @@ -24,7 +28,6 @@ require ( github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/validator/v10 v10.11.0 // indirect github.com/goccy/go-json v0.9.7 // indirect - github.com/golang/protobuf v1.5.2 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/leodido/go-urn v1.2.1 // indirect diff --git a/go.sum b/go.sum index 85ed159..4243b76 100644 --- a/go.sum +++ b/go.sum @@ -1,37 +1,48 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08= +github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/InVisionApp/go-logger v1.0.1 h1:WFL19PViM1mHUmUWfsv5zMo379KSWj2MRmBlzMFDRiE= +github.com/InVisionApp/go-logger v1.0.1/go.mod h1:+cGTDSn+P8105aZkeOfIhdd7vFO5X1afUHcjvanY0L8= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= +github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/f1bonacc1/go-health/v2 v2.1.3 h1:UbiB5hSNpnqAAs7tsCZ8aA/ScdIpuGbDo2r7lhfIGkQ= +github.com/f1bonacc1/go-health/v2 v2.1.3/go.mod h1:Iz2FZRfK3sJecRvGCIgyBsKOjILdKTdLGiGFaO+JDYc= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= -github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1 h1:QqwPZCwh/k1uYqq6uXSb9TRDhTkfQbO80v8zhnIe5zM= github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1/go.mod h1:Az6Jt+M5idSED2YPGtwnfJV0kXohgdCBPmHGSYc1r04= github.com/gdamore/tcell/v2 v2.5.1 h1:zc3LPdpK184lBW7syF2a5C6MV827KmErk9jGVnmsl/I= github.com/gdamore/tcell/v2 v2.5.1/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/gzip v0.0.3 h1:etUaeesHhEORpZMp18zoOhepboiWnFtXrBZxszWUn4k= -github.com/gin-contrib/gzip v0.0.3/go.mod h1:YxxswVZIqOvcHEQpsSn+QF5guQtO1dCfy0shBPy4jFc= github.com/gin-contrib/gzip v0.0.5 h1:mhnVU32YnnBh2LPH2iqRqsA/eR7SAqRaD388jL2s/j0= github.com/gin-contrib/gzip v0.0.5/go.mod h1:OPIK6HR0Um2vNmBUTlayD7qle4yVVRZT0PyhdUigrKk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= -github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= github.com/gin-gonic/gin v1.8.0 h1:4WFH5yycBMA3za5Hnl425yd9ymdw1XPm4666oab+hv4= github.com/gin-gonic/gin v1.8.0/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -39,8 +50,6 @@ github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/a github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= -github.com/go-openapi/spec v0.20.5 h1:skHa8av4VnAtJU5zyAUXrrdK/NDiVX8lchbG+BfcdrE= -github.com/go-openapi/spec v0.20.5/go.mod h1:QbfOSIVt3/sac+a1wzmKbbcLXm5NdZnyBZYtCijp43o= github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ= github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= @@ -49,47 +58,47 @@ github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrK github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 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.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= @@ -100,7 +109,6 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= @@ -109,17 +117,17 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE= +github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= @@ -136,51 +144,52 @@ github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8/go.mod h1:WIfMkQNY+oq/m github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= -github.com/swaggo/gin-swagger v1.4.2 h1:qDs1YrBOTnurDG/JVMc8678KhoS1B1okQGPtIqVz4YU= -github.com/swaggo/gin-swagger v1.4.2/go.mod h1:hmJ1vPn+XjUvnbzjCdUAxVqgraxELxk8x5zAsjCE5mg= github.com/swaggo/gin-swagger v1.4.3 h1:mHJz+yzJne0udgYnC5qlDf4e7KuxUbVNX2dhD/cw2rU= github.com/swaggo/gin-swagger v1.4.3/go.mod h1:hBg6tGeKJsUu/P79BH+WGUR8nq2LuGE0O160+s4iefo= -github.com/swaggo/swag v1.7.9/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU= -github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI= github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= github.com/swaggo/swag v1.8.2 h1:D4aBiVS2a65zhyk3WFqOUz7Rz0sOaUcgeErcid5uGL4= github.com/swaggo/swag v1.8.2/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= -github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= +github.com/zaffka/mongodb-boltdb-mock v0.0.0-20180816124423-49954d88fa3e/go.mod h1:GsDD1qsG+86MeeCG7ndi6Ei3iGthKL3wQ7PTFigDfNY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e h1:1SzTfNOXwIS2oWiMF+6qu0OUDKb0dauo6MoDUQyu+yU= golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/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= @@ -188,16 +197,17 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2 h1:6mzvA99KwZxbOrxww4EvWVQUnN1+xEu9tafK5ZxkYeA= -golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220526153639-5463443f8c37 h1:lUkvobShwKsOesNfWWlCS5q7fnbG1MEliIzwu886fn8= golang.org/x/net v0.0.0-20220526153639-5463443f8c37/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -211,14 +221,11 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -240,16 +247,19 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -257,7 +267,6 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= diff --git a/process-compose.yaml b/process-compose.yaml index 15cef48..455b0f4 100644 --- a/process-compose.yaml +++ b/process-compose.yaml @@ -40,6 +40,15 @@ processes: command: "sleep 2 && pkill -f process2" signal: 15 timeout_seconds: 4 + readiness_probe: + http_get: + host: "google.com" + scheme: "https" + initial_delay_seconds: 5 + period_seconds: 2 + timeout_seconds: 1 + success_threshold: 1 + failure_threshold: 3 process3: command: "./test_loop.bash process3" @@ -57,9 +66,17 @@ processes: environment: - 'ABC=2221' - 'EXIT_CODE=4' + readiness_probe: + exec: + command: "ps -ef | grep -v grep | grep process4" + initial_delay_seconds: 5 + period_seconds: 2 + timeout_seconds: 1 + success_threshold: 1 + failure_threshold: 3 - docker: - command: "docker run -d --rm --name nginx_test nginx" + nginx: + command: "docker run -d --rm -p80:80 --name nginx_test nginx" # availability: # restart: on-failure is_daemon: true @@ -67,6 +84,27 @@ processes: command: "docker stop nginx_test" signal: 15 timeout_seconds: 5 + liveness_probe: + exec: + command: "[ $(docker inspect -f '{{.State.Running}}' nginx_test) = 'true' ]" + initial_delay_seconds: 5 + period_seconds: 2 + timeout_seconds: 5 + success_threshold: 1 + failure_threshold: 3 + readiness_probe: + http_get: + host: 127.0.0.1 + path: "/" + port: 80 + initial_delay_seconds: 5 + period_seconds: 10 + timeout_seconds: 5 + success_threshold: 1 + failure_threshold: 3 + availability: + restart: "always" + backoff_seconds: 2 kcalc: command: "kcalc" diff --git a/src/app/config.go b/src/app/config.go index 7f2d423..e8e7ad0 100644 --- a/src/app/config.go +++ b/src/app/config.go @@ -3,6 +3,7 @@ package app import ( "sync" + "github.com/f1bonacc1/process-compose/src/health" "github.com/f1bonacc1/process-compose/src/pclog" ) @@ -32,6 +33,8 @@ type ProcessConfig struct { Environment []string `yaml:"environment,omitempty"` RestartPolicy RestartPolicyConfig `yaml:"availability,omitempty"` DependsOn DependsOnConfig `yaml:"depends_on,omitempty"` + LivenessProbe *health.Probe `yaml:"liveness_probe,omitempty"` + ReadinessProbe *health.Probe `yaml:"readiness_probe,omitempty"` ShutDownParams ShutDownParams `yaml:"shutdown,omitempty"` Extensions map[string]interface{} `yaml:",inline"` } @@ -40,6 +43,7 @@ type ProcessState struct { Name string `json:"name"` Status string `json:"status"` SystemTime string `json:"system_time"` + Health string `json:"is_ready"` Restarts int `json:"restarts"` ExitCode int `json:"exit_code"` Pid int `json:"pid"` @@ -73,6 +77,12 @@ const ( ProcessStateCompleted = "Completed" ) +const ( + ProcessHealthReady = "Ready" + ProcessHealthNotReady = "Not Ready" + ProcessHealthUnknown = "N/A" +) + type RestartPolicyConfig struct { Restart string `yaml:",omitempty"` BackoffSeconds int `yaml:"backoff_seconds,omitempty"` diff --git a/src/app/process.go b/src/app/process.go index 296c070..ab5d011 100644 --- a/src/app/process.go +++ b/src/app/process.go @@ -8,12 +8,13 @@ import ( "math/rand" "os" "os/exec" - "runtime" "strconv" "sync" "syscall" "time" + "github.com/f1bonacc1/process-compose/src/cmd" + "github.com/f1bonacc1/process-compose/src/health" "github.com/f1bonacc1/process-compose/src/pclog" "github.com/fatih/color" @@ -43,6 +44,8 @@ type Process struct { done bool replica int startTime time.Time + liveProber *health.Prober + readyProber *health.Prober } func NewProcess( @@ -68,6 +71,7 @@ func NewProcess( procStateChan: make(chan string, 1), } + proc.setUpProbes() proc.procCond = *sync.NewCond(proc) return proc } @@ -78,7 +82,7 @@ func (p *Process) run() error { } for { starter := func() error { - p.cmd = exec.Command(getRunnerShell(), getRunnerArg(), p.getCommand()) + p.cmd = cmd.BuildCommand(p.getCommand()) p.cmd.Env = p.getProcessEnvironment() p.setProcArgs() stdout, _ := p.cmd.StdoutPipe() @@ -92,6 +96,8 @@ func (p *Process) run() error { p.startTime = time.Now() p.procState.Pid = p.cmd.Process.Pid + p.startProbes() + //Wait should wait for I/O consumption, but if the execution is too fast //e.g. echo 'hello world' the output will not reach the pipe //TODO Fix this @@ -188,6 +194,7 @@ func (p *Process) shutDown() error { return nil } p.setState(ProcessStateTerminating) + p.stopProbes() if isStringDefined(p.procConf.ShutDownParams.ShutDownCommand) { return p.doConfiguredStop(p.procConf.ShutDownParams) } @@ -204,7 +211,7 @@ func (p *Process) doConfiguredStop(params ShutDownParams) error { defer cancel() defer p.notifyDaemonStopped() - cmd := exec.CommandContext(ctx, getRunnerShell(), getRunnerArg(), params.ShutDownCommand) + cmd := cmd.BuildCommandContext(ctx, params.ShutDownCommand) cmd.Env = p.getProcessEnvironment() if err := cmd.Run(); err != nil { @@ -297,31 +304,93 @@ func (p *Process) setState(state string) { p.stateMtx.Lock() defer p.stateMtx.Unlock() p.procState.Status = state + p.onStateChange(state) + } func (p *Process) setStateAndRun(state string, runnable func() error) error { p.stateMtx.Lock() defer p.stateMtx.Unlock() p.procState.Status = state + p.onStateChange(state) return runnable() } -func getRunnerShell() string { - shell, ok := os.LookupEnv("SHELL") - if !ok { - if runtime.GOOS == "windows" { - shell = "cmd" - } else { - shell = "bash" - } +func (p *Process) onStateChange(state string) { + switch state { + case ProcessStateRestarting: + fallthrough + case ProcessStateLaunching: + fallthrough + case ProcessStateTerminating: + p.procState.Health = ProcessHealthUnknown } - return shell } -func getRunnerArg() string { - if runtime.GOOS == "windows" { - return "/C" - } else { - return "-c" +func (p *Process) setUpProbes() { + var err error + if p.procConf.LivenessProbe != nil { + p.liveProber, err = health.New( + p.getName()+"_live_probe", + *p.procConf.LivenessProbe, + p.onLivenessCheckEnd, + ) + if err != nil { + log.Error().Msgf("failed to setup liveness probe for %s - %s", p.getName(), err.Error()) + p.logBuffer.Write("Error: " + err.Error()) + } + } + + if p.procConf.ReadinessProbe != nil { + p.readyProber, err = health.New( + p.getName()+"_ready_probe", + *p.procConf.ReadinessProbe, + p.onReadinessCheckEnd, + ) + if err != nil { + log.Error().Msgf("failed to setup readiness probe for %s - %s", p.getName(), err.Error()) + p.logBuffer.Write("Error: " + err.Error()) + } + } +} + +func (p *Process) startProbes() { + if p.liveProber != nil { + p.liveProber.Start() + } + + if p.readyProber != nil { + p.readyProber.Start() + } +} + +func (p *Process) stopProbes() { + if p.liveProber != nil { + p.liveProber.Stop() + } + + if p.readyProber != nil { + p.readyProber.Stop() + } +} + +func (p *Process) onLivenessCheckEnd(isOk, isFatal bool, err string) { + if isFatal { + log.Info().Msgf("%s is not alive anymore - %s", p.getName(), err) + p.logBuffer.Write("Error: liveness check fail - " + err) + p.notifyDaemonStopped() + } +} + +func (p *Process) onReadinessCheckEnd(isOk, isFatal bool, err string) { + if isFatal { + p.procState.Health = ProcessHealthNotReady + log.Info().Msgf("%s is not ready anymore - %s", p.getName(), err) + p.logBuffer.Write("Error: readiness check fail - " + err) + p.shutDown() + } else if isOk { + p.procState.Health = ProcessHealthReady + } else { + p.procState.Health = ProcessHealthNotReady } } diff --git a/src/app/project.go b/src/app/project.go index 1a09aa3..a6ebbcb 100644 --- a/src/app/project.go +++ b/src/app/project.go @@ -106,6 +106,7 @@ func (p *Project) initProcessStates() { Name: key, Status: ProcessStatePending, SystemTime: "", + Health: ProcessHealthUnknown, Restarts: 0, ExitCode: 0, Pid: 0, @@ -131,6 +132,7 @@ func (p *Project) GetProcessState(name string) *ProcessState { } else { procState.Pid = 0 procState.SystemTime = "" + procState.Health = ProcessHealthUnknown } return procState } diff --git a/src/cmd/command.go b/src/cmd/command.go new file mode 100644 index 0000000..682419d --- /dev/null +++ b/src/cmd/command.go @@ -0,0 +1,36 @@ +package cmd + +import ( + "context" + "os" + "os/exec" + "runtime" +) + +func BuildCommand(shellCmd string) *exec.Cmd { + return exec.Command(getRunnerShell(), getRunnerArg(), shellCmd) +} + +func BuildCommandContext(ctx context.Context, shellCmd string) *exec.Cmd { + return exec.CommandContext(ctx, getRunnerShell(), getRunnerArg(), shellCmd) +} + +func getRunnerShell() string { + shell, ok := os.LookupEnv("SHELL") + if !ok { + if runtime.GOOS == "windows" { + shell = "cmd" + } else { + shell = "bash" + } + } + return shell +} + +func getRunnerArg() string { + if runtime.GOOS == "windows" { + return "/C" + } else { + return "-c" + } +} diff --git a/src/health/exec_checker.go b/src/health/exec_checker.go new file mode 100644 index 0000000..46eed79 --- /dev/null +++ b/src/health/exec_checker.go @@ -0,0 +1,26 @@ +package health + +import ( + "context" + "time" + + "github.com/f1bonacc1/process-compose/src/cmd" +) + +type execChecker struct { + command string + timeout int +} + +func (c *execChecker) Status() (interface{}, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(c.timeout)*time.Second) + defer cancel() + + cmd := cmd.BuildCommandContext(ctx, c.command) + + if err := cmd.Run(); err != nil { + return nil, err + } + + return map[string]int{"exit_code": cmd.ProcessState.ExitCode()}, nil +} diff --git a/src/health/health_checks.go b/src/health/health_checks.go new file mode 100644 index 0000000..6630a3f --- /dev/null +++ b/src/health/health_checks.go @@ -0,0 +1,113 @@ +package health + +import ( + "fmt" + "time" + + "github.com/InVisionApp/go-health/v2" + "github.com/InVisionApp/go-health/v2/checkers" + "github.com/rs/zerolog/log" +) + +const ( + FAIL = "failed" + OK = "ok" +) + +type Prober struct { + probe Probe + name string + onCheckEndFunc func(bool, bool, string) + hc *health.Health +} + +func New(name string, probe Probe, onCheckEnd func(bool, bool, string)) (*Prober, error) { + validateAndSetDefaults(&probe) + p := &Prober{ + probe: probe, + name: name, + onCheckEndFunc: onCheckEnd, + hc: health.New(), + } + p.hc.DisableLogging() + if probe.Exec != nil { + err := p.addProber(p.getExecChecker) + if err != nil { + return nil, err + } + return p, err + } + if probe.HttpGet != nil { + err := p.addProber(p.getHttpChecker) + if err != nil { + return nil, err + } + return p, err + } + return p, fmt.Errorf("probe settings are missing [http_get.host, exec.command] for %s", name) +} + +func (p *Prober) Start() { + go func() { + time.Sleep(time.Duration(p.probe.InitialDelay) * time.Second) + log.Debug().Msgf("%s starting monitoring", p.name) + err := p.hc.Start() + if err != nil { + log.Error().Msgf("%s failed to start monitoring - %s", p.name, err.Error()) + } + }() +} + +func (p *Prober) Stop() { + if p.hc != nil { + p.hc.Stop() + } +} + +func (p *Prober) healthCheckCompleted(state *health.State) { + fatal := false + ok := false + if state.ContiguousFailures == int64(p.probe.FailureThreshold) { + fatal = true + } + if state.Status == OK { + ok = true + } + p.onCheckEndFunc(ok, fatal, state.Err) +} + +func (p *Prober) addProber(factory func() (health.ICheckable, error)) error { + checker, err := factory() + if err != nil { + return err + } + return p.hc.AddCheck(&health.Config{ + Name: p.name, + Checker: checker, + Interval: time.Duration(p.probe.PeriodSeconds) * time.Second, + Fatal: false, + OnComplete: p.healthCheckCompleted, + }) +} + +func (p *Prober) getHttpChecker() (health.ICheckable, error) { + url, err := p.probe.HttpGet.getUrl() + if err != nil { + return nil, err + } + checker, err := checkers.NewHTTP(&checkers.HTTPConfig{ + URL: url, + Timeout: time.Duration(p.probe.TimeoutSeconds) * time.Second, + }) + if err != nil { + return nil, err + } + return checker, nil +} + +func (p *Prober) getExecChecker() (health.ICheckable, error) { + return &execChecker{ + command: p.probe.Exec.Command, + timeout: p.probe.TimeoutSeconds, + }, nil +} diff --git a/src/health/probe.go b/src/health/probe.go new file mode 100644 index 0000000..95bcc82 --- /dev/null +++ b/src/health/probe.go @@ -0,0 +1,81 @@ +package health + +import ( + "fmt" + "net/url" + "strings" +) + +type Probe struct { + Exec *ExecProbe `yaml:"exec,omitempty"` + HttpGet *HttpProbe `yaml:"http_get,omitempty"` + InitialDelay int `yaml:"initial_delay_seconds,omitempty"` + PeriodSeconds int `yaml:"period_seconds,omitempty"` + TimeoutSeconds int `yaml:"timeout_seconds,omitempty"` + SuccessThreshold int `yaml:"success_threshold,omitempty"` + FailureThreshold int `yaml:"failure_threshold,omitempty"` +} + +type ExecProbe struct { + Command string `yaml:"command,omitempty"` +} + +type HttpProbe struct { + Host string `yaml:"host,omitempty"` + Path string `yaml:"path,omitempty"` + Scheme string `yaml:"scheme,omitempty"` + Port int `yaml:"port,omitempty"` +} + +func (h HttpProbe) getUrl() (*url.URL, error) { + urlStr := "" + if h.Port != 0 { + urlStr = fmt.Sprintf("%s://%s:%d%s", h.Scheme, h.Host, h.Port, h.Path) + } + if h.Port == 0 { + urlStr = fmt.Sprintf("%s://%s%s", h.Scheme, h.Host, h.Path) + } + return url.Parse(urlStr) +} + +func validateAndSetDefaults(p *Probe) { + if p == nil { + return + } + if p.InitialDelay < 0 { + p.InitialDelay = 0 + } + if p.PeriodSeconds < 1 { + p.PeriodSeconds = 10 + } + if p.TimeoutSeconds < 1 { + p.TimeoutSeconds = 1 + } + if p.SuccessThreshold < 1 { + p.SuccessThreshold = 1 + } + if p.FailureThreshold < 1 { + p.FailureThreshold = 3 + } + + validateAndSetHttpDefaults(p.HttpGet) +} + +func validateAndSetHttpDefaults(h *HttpProbe) { + if h == nil { + return + } + if len(strings.TrimSpace(h.Host)) == 0 { + h.Host = "127.0.0.1" + } + if len(strings.TrimSpace(h.Scheme)) == 0 { + h.Scheme = "http" + } + if len(strings.TrimSpace(h.Path)) == 0 { + h.Path = "/" + } + if h.Port < 1 || h.Port > 65535 { + // if undefined or wrong value - will be treated as undefined + h.Port = 0 + } +} diff --git a/src/tui/view.go b/src/tui/view.go index 6b938f1..98538b0 100644 --- a/src/tui/view.go +++ b/src/tui/view.go @@ -114,8 +114,9 @@ func (pv *pcView) fillTableData() { pv.procTable.SetCell(r+1, 1, tview.NewTableCell(state.Name).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue)) pv.procTable.SetCell(r+1, 2, tview.NewTableCell(state.Status).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue)) pv.procTable.SetCell(r+1, 3, tview.NewTableCell(state.SystemTime).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue)) - pv.procTable.SetCell(r+1, 4, tview.NewTableCell(strconv.Itoa(state.Restarts)).SetAlign(tview.AlignRight).SetExpansion(0).SetTextColor(tcell.ColorLightSkyBlue)) - pv.procTable.SetCell(r+1, 5, tview.NewTableCell(strconv.Itoa(state.ExitCode)).SetAlign(tview.AlignRight).SetExpansion(0).SetTextColor(tcell.ColorLightSkyBlue)) + pv.procTable.SetCell(r+1, 4, tview.NewTableCell(state.Health).SetAlign(tview.AlignLeft).SetExpansion(1).SetTextColor(tcell.ColorLightSkyBlue)) + pv.procTable.SetCell(r+1, 5, tview.NewTableCell(strconv.Itoa(state.Restarts)).SetAlign(tview.AlignRight).SetExpansion(0).SetTextColor(tcell.ColorLightSkyBlue)) + pv.procTable.SetCell(r+1, 6, tview.NewTableCell(strconv.Itoa(state.ExitCode)).SetAlign(tview.AlignRight).SetExpansion(0).SetTextColor(tcell.ColorLightSkyBlue)) } } @@ -171,7 +172,7 @@ func (pv *pcView) createProcTable() *tview.Table { return event }) columns := []string{ - "PID", "NAME", "STATUS", "AGE", "RESTARTS", "EXIT CODE", + "PID", "NAME", "STATUS", "AGE", "READINESS", "RESTARTS", "EXIT CODE", } for i := 0; i < len(columns); i++ { expan := 1 @@ -209,7 +210,7 @@ func (pv *pcView) createStatTable() *tview.Table { table.SetCell(2, 1, tview.NewTableCell(strconv.Itoa(len(pv.procNames))).SetSelectable(false).SetExpansion(1)) table.SetCell(0, 2, tview.NewTableCell("").SetSelectable(false)) - table.SetCell(0, 3, tview.NewTableCell("🔥 Process Compose"). + table.SetCell(0, 3, tview.NewTableCell("Process Compose 🔥"). SetSelectable(false). SetAlign(tview.AlignRight). SetExpansion(1).