├── .allow-ai-service ├── .github ├── dependabot.yml └── workflows │ ├── go-ossf-slsa3-publish.yml │ ├── go.yml │ └── release.yml ├── .gitignore ├── .goreleaser.yaml ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── green-orb.go ├── green-orb.yaml ├── images └── green-orb.png ├── signals_unix.go └── signals_windows.go /.allow-ai-service: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atgreen/green-orb/4425799ebbcf4245602c056687cc56405a3e439e/.allow-ai-service -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" # Location of package manifests 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/go-ossf-slsa3-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | # This workflow lets you compile your Go project using a SLSA3 compliant builder. 7 | # This workflow will generate a so-called "provenance" file describing the steps 8 | # that were performed to generate the final binary. 9 | # The project is an initiative of the OpenSSF (openssf.org) and is developed at 10 | # https://github.com/slsa-framework/slsa-github-generator. 11 | # The provenance file can be verified using https://github.com/slsa-framework/slsa-verifier. 12 | # For more information about SLSA and how it improves the supply-chain, visit slsa.dev. 13 | 14 | name: SLSA Go releaser 15 | on: 16 | workflow_dispatch: 17 | release: 18 | types: [created] 19 | 20 | permissions: read-all 21 | 22 | jobs: 23 | # ======================================================================================================================================== 24 | # Prerequesite: Create a .slsa-goreleaser.yml in the root directory of your project. 25 | # See format in https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/go/README.md#configuration-file 26 | #========================================================================================================================================= 27 | build: 28 | permissions: 29 | id-token: write # To sign. 30 | contents: write # To upload release assets. 31 | actions: read # To read workflow path. 32 | uses: slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml@v1.4.0 33 | with: 34 | go-version: 1.17 35 | # ============================================================================================================= 36 | # Optional: For more options, see https://github.com/slsa-framework/slsa-github-generator#golang-projects 37 | # ============================================================================================================= 38 | 39 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Build 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: '1.21' 23 | 24 | - name: Build 25 | run: go build 26 | 27 | - name: Test 28 | run: go test 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # .github/workflows/release.yml 2 | name: goreleaser 3 | 4 | on: 5 | workflow_dispatch: 6 | release: 7 | types: [created] 8 | push: 9 | # run only against tags 10 | tags: 11 | - "*" 12 | 13 | permissions: 14 | contents: write 15 | # packages: write 16 | # issues: write 17 | 18 | jobs: 19 | goreleaser: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: anchore/sbom-action@v0 23 | - uses: actions/checkout@v3 24 | with: 25 | fetch-depth: 0 26 | - run: git fetch --force --tags 27 | - uses: actions/setup-go@v4 28 | with: 29 | go-version: stable 30 | # More assembly might be required: Docker logins, GPG, etc. 31 | # It all depends on your needs. 32 | - uses: goreleaser/goreleaser-action@v5 33 | with: 34 | # either 'goreleaser' (default) or 'goreleaser-pro': 35 | distribution: goreleaser 36 | version: latest 37 | args: release --clean 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' 41 | # distribution: 42 | # GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | /oarb 23 | config.yaml 24 | *~ 25 | dist/ 26 | /secret.yaml 27 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # This is an example .goreleaser.yml file with some sensible defaults. 2 | # Make sure to check the documentation at https://goreleaser.com 3 | 4 | # The lines below are called `modelines`. See `:help modeline` 5 | # Feel free to remove those if you don't want/need to use them. 6 | # yaml-language-server: $schema=https://goreleaser.com/static/schema.json 7 | # vim: set ts=2 sw=2 tw=0 fo=cnqoj 8 | 9 | version: 1 10 | 11 | project_name: green-orb 12 | 13 | before: 14 | hooks: 15 | - go mod tidy 16 | - go generate ./... 17 | 18 | sboms: 19 | - artifacts: binary 20 | 21 | builds: 22 | - binary: orb 23 | env: 24 | - CGO_ENABLED=0 25 | ldflags: 26 | - -s -w -X main.version={{.Version}} 27 | goos: 28 | - linux 29 | - windows 30 | - darwin 31 | goarch: 32 | - amd64 33 | - arm64 34 | - arm 35 | - s390x 36 | - ppc64le 37 | goarm: 38 | - 7 39 | ignore: 40 | - goos: windows 41 | goarch: arm64 42 | - goos: windows 43 | goarch: arm 44 | - goos: windows 45 | goarch: s390x 46 | - goos: windows 47 | goarch: ppc64le 48 | 49 | archives: 50 | - format: tar.gz 51 | name_template: "green-orb-{{ .Version }}-{{ .Os }}-{{ .Arch }}" 52 | allow_different_binary_count: true 53 | format_overrides: 54 | - goos: windows 55 | format: zip 56 | 57 | changelog: 58 | sort: asc 59 | filters: 60 | exclude: 61 | - "^docs:" 62 | - "^test:" 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Anthony Green 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Green Orb 4 | > An 'Observe and Report Buddy' for your SRE toolbox 5 | 6 | ## Introduction 7 | 8 | Green Orb is a lightweight monitoring tool that enhances your 9 | application's reliability by observing its console output for specific 10 | patterns and executing predefined actions in response. Designed to 11 | integrate seamlessly, it's deployed as a single executable binary that 12 | runs your application as a subprocess, where it can monitor all 13 | console output, making it particularly useful in containerized 14 | environments. Green Orb acts as a proactive assistant, handling 15 | essential monitoring tasks and enabling SREs to automate responses to 16 | critical system events effectively. 17 | 18 | ## Features 19 | 20 | With Green Orb, you can: 21 | 22 | - **Send Notifications**: Utilize popular messaging services like Slack, Email, Discord, and more to keep your team updated. 23 | - **Trigger Webhooks**: Integrate with services like Ansible Automation Platform through webhooks for seamless automation. 24 | - **Publish to Kafka**: Send important alerts or logs directly to a Kafka topic for real-time processing. 25 | - **Execute Commands**: Run shell commands automatically, allowing actions like capturing thread dumps of the observed process. 26 | - **Manage Processes**: Restart or kill the observed process to maintain desired state or recover from issues. 27 | 28 | ## Quick Start 29 | 30 | Green Orb is easy to configure and use. It's distributed as a single binary, `orb`, and requires just one YAML configuration file. 31 | 32 | Simply preface your application with `orb` and customize your 33 | `green-orb.yaml` file to enable its special powers. For example, 34 | instead of: 35 | ``` 36 | $ java -jar mywebapp.jar 37 | ``` 38 | ...use... 39 | ``` 40 | $ orb java -jar mywebapp.jar 41 | ``` 42 | 43 | Or, if you are using containers, change... 44 | ``` 45 | ENTRYPOINT [ "java", "-jar", "jar-file-name.jar" ] 46 | ``` 47 | ...in your Dockerfile, to... 48 | ``` 49 | ENTRYPOINT [ "orb", "java", "-jar", "jar-file-name.jar" ] 50 | ``` 51 | 52 | This assumes `green-orb.yaml` is in the current directory. Use the 53 | `-c` flag to point it elsewhere. 54 | 55 | The config file tells `orb` what to watch for, and what to do. For 56 | instance, if it contains the following, you'll get an email every time 57 | your application starts up, and a thread dump every time you get a 58 | thread pool exhausted warning. 59 | 60 | ``` 61 | channels: 62 | - name: "startup-email" 63 | type: "notify" 64 | url: "smtp://MYEMAIL@gmail.com:MYPASSWORD@smtp.gmail.com:587/?from=MYEMAIL@gmail.com&to=MYEMAIL@gmail.com&subject=Application%20Starting!" 65 | 66 | - name: "thread-dump" 67 | type: "exec" 68 | shell: | 69 | FILENAME=thread-dump-$(date).txt 70 | jstack $ORB_PID > /tmp/${FILENAME} 71 | aws s3 mv /tmp/${FILENAME} s3:/my-bucket/${FILENAME} 72 | 73 | signals: 74 | - regex: "Starting Application" 75 | channel: "startup-email" 76 | 77 | - regex: "Warning: thread pool exhausted" 78 | channel: "thread-dump" 79 | ``` 80 | 81 | `orb` does not interfere with the execution of your application. All 82 | console logs still go to the console, Linux and macOS signals 83 | are passed through to the observed process, and the exit code for your 84 | application is returned through `orb`. 85 | 86 | ## Channels and Signals 87 | 88 | In Green Orb, "channels" and "signals" are foundational concepts: 89 | 90 | - Signals: Mappings of regular expressions to channels. When a 91 | signal's regex matches a log entry, the corresponding channel is 92 | invoked. 93 | 94 | - Channels: Define the action to take and how. For example, the above 95 | `startup-email` channel defines how to send a message to a specific 96 | SMTP server. 97 | 98 | Channels and signals are defined in your `orb` config file. A config 99 | file can define any number of channels and signals. Each signal maps 100 | to a single channel. However, multiple signals can map to the same 101 | channel. 102 | 103 | ## Channel Details 104 | 105 | All channel definitions must have a `name` and a `type`. Signals 106 | reference channels by `name`. The channel's `type` must be one of 107 | `notify`, `kafka`, `exec`, `suppress`, `restart` or `kill`. These 108 | types are described below. 109 | 110 | ### Sending notifications to messaging platforms 111 | 112 | The channel type `notify` is for sending messages to popular messaging 113 | platforms. 114 | 115 | You must specify a URL and, optionally, a message template. 116 | 117 | `orb` uses `shoutrrr` for sending notifications. Use the following 118 | URL formats for these different services. Additional details are 119 | available from the [`shoutrrr` 120 | documentation](https://containrrr.dev/shoutrrr/v0.8/services/overview/). 121 | 122 | | Service | URL Format | 123 | |-------------|-------------------------------------------------------------------------------------------- | 124 | | Bark | `bark://devicekey@host` | 125 | | Discord | `discord://token@id` | 126 | | Email | `smtp://username:password@host:port/?from=fromAddress&to=recipient1[,recipient2,...]` | 127 | | Gotify | `gotify://gotify-host/token` | 128 | | Google Chat | `googlechat://chat.googleapis.com/v1/spaces/FOO/messages?key=bar&token=baz` | 129 | | IFTTT | `ifttt://key/?events=event1[,event2,...]&value1=value1&value2=value2&value3=value3` | 130 | | Join | `join://shoutrrr:api-key@join/?devices=device1[,device2, ...][&icon=icon][&title=title]` | 131 | | Mattermost | `mattermost://[username@]mattermost-host/token[/channel]` | 132 | | Matrix | `matrix://username:password@host:port/[?rooms=!roomID1[,roomAlias2]]` | 133 | | Ntfy | `ntfy://username:password@ntfy.sh/topic` | 134 | | OpsGenie | `opsgenie://host/token?responders=responder1[,responder2]` | 135 | | Pushbullet | `pushbullet://api-token[/device/#channel/email]` | 136 | | Pushover | `pushover://shoutrrr:apiToken@userKey/?devices=device1[,device2, ...]` | 137 | | Rocketchat | `rocketchat://[username@]rocketchat-host/token[/channel|@recipient]` | 138 | | Slack | `slack://[botname@]token-a/token-b/token-c` | 139 | | Teams | `teams://group@tenant/altId/groupOwner?host=organization.webhook.office.com` | 140 | | Telegram | `telegram://token@telegram?chats=@channel-1[,chat-id-1,...]` | 141 | | Zulip Chat | `zulip://bot-mail:bot-key@zulip-domain/?stream=name-or-id&topic=name` | 142 | 143 | URLs are actually [go templates](https://pkg.go.dev/text/template) 144 | that are processed before use. Data that you can access from the 145 | template engine includes: 146 | - `.Timestamp` : the RFC3339 formatted timestamp for the matching log entry 147 | - `.PID`: the process ID for the observed process 148 | - `.Matches`: An array where the first element (`{{index .Matches 0}}`) is the entire log line matched by the regular expression, and subsequent elements (`{{index .Matches 1}}`, `{{index .Matches 2}}`, ...) contain the data captured by each of the regular expression's capturing groups. 149 | - `.Logline`: the matching log line. Equivalent to `{{index .Matches 0}}`. 150 | - `.Env`: a map to access environment variables (e.g. `{{.Env.USER_PASSWORD}}`) 151 | 152 | Similarly, the message sent may also be a template. If no `template` 153 | is specified in the channel definition, then the logline is used as 154 | the message. If a `template` is specified, then the template is 155 | processed with the same data as above before sending. 156 | 157 | As an example, here's a channel that sends an email containing json 158 | data with the observed PID in the email subject line: 159 | 160 | ``` 161 | - name: "email-on-startup" 162 | type: "notify" 163 | url: "smtp://EMAIL@gmail.com:PASSWORD@smtp.gmail.com:587/?from=EMAIL@gmail.com&to=EMAIL@gmail.com&subject=Starting%20process%20{{.PID}}!" 164 | template: "{ \"timestamp\": \"{{.Timestamp}}\", \"message\": \"{{.Logline}}\" }" 165 | ``` 166 | 167 | Generic webhooks and handled specially by `shoutrrr`. Their URL 168 | format is described at 169 | [https://containrrr.dev/shoutrrr/v0.8/services/generic/](https://containrrr.dev/shoutrrr/v0.8/services/generic/). 170 | 171 | ### Sending Kafka messages 172 | 173 | The channel type `kafka` is for sending messages to a kafka broker. 174 | 175 | You must specify a broker and topic in your definition, like so: 176 | 177 | ``` 178 | - name: "kafka-alerts" 179 | type: "kafka" 180 | broker: "mybroker.example.com:9092" 181 | topic: "orb-alerts" 182 | ``` 183 | 184 | Producer timeouts are currently fixed at 5 seconds. 185 | 186 | ### Running shell scripts 187 | 188 | The channel type `exec` is for running arbitrary shell commands. 189 | 190 | The process ID of the observed process is presented to the shell code 191 | through the environment variable `$ORB_PID`. In this example, the 192 | channel `thread-dump` invokes the `jstack` tool to dump java thread 193 | stacks to a file that is copied into an s3 bucket for later 194 | examination. 195 | 196 | ``` 197 | - name: "thread-dump" 198 | type: "exec" 199 | shell: | 200 | FILENAME=thread-dump-$(date).txt 201 | jstack $ORB_PID > /tmp/${FILENAME} 202 | aws s3 mv /tmp/${FILENAME} s3:/my-bucket/${FILENAME} 203 | ``` 204 | 205 | ### Suppressing output 206 | 207 | The channel type `suppress` is for suppressing output from your 208 | observed process. Anything sent to a `suppress` channel will not flow 209 | through to standard output or standard error. 210 | 211 | ### Restarting your process 212 | 213 | The channel type `restart` is for restarting your observed process. 214 | 215 | The `orb` process will run continuously, but it will force the 216 | observed process to terminate and then restart. 217 | 218 | ### Killing your process 219 | 220 | The channel type `kill` is for killing your observed process. 221 | 222 | The `orb` process will exit. 223 | 224 | ## Contributing 225 | 226 | Green Orb is Free Software, and any and all contributions are welcome! 227 | Please use GitHub's Issue tracker and Pull Request systems for 228 | feedback and improvements. 229 | 230 | ## Author and License 231 | 232 | Green Orb was written by [Anthony 233 | Green](https://github.com/atgreen), and is distributed under the terms 234 | of the MIT License. See 235 | [LICENSE](https://raw.githubusercontent.com/atgreen/green-orb/main/LICENSE) 236 | for details. 237 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/atgreen/green-orb/orb 2 | 3 | go 1.21.5 4 | toolchain go1.23.7 5 | 6 | require ( 7 | github.com/containrrr/shoutrrr v0.8.0 8 | github.com/twmb/franz-go v1.18.1 9 | github.com/urfave/cli/v3 v3.0.0-beta1 10 | gopkg.in/yaml.v3 v3.0.1 11 | ) 12 | 13 | require ( 14 | github.com/fatih/color v1.15.0 // indirect 15 | github.com/google/go-cmp v0.6.0 // indirect 16 | github.com/klauspost/compress v1.17.11 // indirect 17 | github.com/kr/pretty v0.3.0 // indirect 18 | github.com/mattn/go-colorable v0.1.13 // indirect 19 | github.com/mattn/go-isatty v0.0.17 // indirect 20 | github.com/pierrec/lz4/v4 v4.1.22 // indirect 21 | github.com/twmb/franz-go/pkg/kmsg v1.9.0 // indirect 22 | golang.org/x/crypto v0.35.0 // indirect 23 | golang.org/x/net v0.36.0 // indirect 24 | golang.org/x/sys v0.30.0 // indirect 25 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect 26 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/containrrr/shoutrrr v0.8.0 h1:mfG2ATzIS7NR2Ec6XL+xyoHzN97H8WPjir8aYzJUSec= 2 | github.com/containrrr/shoutrrr v0.8.0/go.mod h1:ioyQAyu1LJY6sILuNyKaQaw+9Ttik5QePU8atnAdO2o= 3 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= 7 | github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= 8 | github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= 9 | github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 10 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 11 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 12 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 13 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 14 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 15 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 16 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= 17 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 18 | github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= 19 | github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= 20 | github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= 21 | github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 22 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 23 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 24 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 25 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 26 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 27 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 28 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 29 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 30 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 31 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 32 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 33 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 34 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 35 | github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= 36 | github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= 37 | github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= 38 | github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= 39 | github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= 40 | github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 41 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 42 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 43 | github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= 44 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 45 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 46 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 47 | github.com/twmb/franz-go v1.18.1 h1:D75xxCDyvTqBSiImFx2lkPduE39jz1vaD7+FNc+vMkc= 48 | github.com/twmb/franz-go v1.18.1/go.mod h1:Uzo77TarcLTUZeLuGq+9lNpSkfZI+JErv7YJhlDjs9M= 49 | github.com/twmb/franz-go/pkg/kmsg v1.9.0 h1:JojYUph2TKAau6SBtErXpXGC7E3gg4vGZMv9xFU/B6M= 50 | github.com/twmb/franz-go/pkg/kmsg v1.9.0/go.mod h1:CMbfazviCyY6HM0SXuG5t9vOwYDHRCSrJJyBAe5paqg= 51 | github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg= 52 | github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= 53 | golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= 54 | golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= 55 | golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= 56 | golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= 57 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 58 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 59 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 60 | golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= 61 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 62 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= 63 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 64 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 65 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 66 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 67 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 68 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 69 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 70 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 71 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 72 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 73 | -------------------------------------------------------------------------------- /green-orb.go: -------------------------------------------------------------------------------- 1 | // green-orb.go - an Observe and Report Buddy 2 | // 3 | // SPDX-License-Identifier: MIT 4 | // 5 | // Copyright (C) 2023, 2024 Anthony Green - green@moxielogic.com 6 | // 7 | // Permission is hereby granted, free of charge, to any person 8 | // obtaining a copy of this software and associated documentation 9 | // files (the “Software”), to deal in the Software without 10 | // restriction, including without limitation the rights to use, copy, 11 | // modify, merge, publish, distribute, sublicense, and/or sell copies 12 | // of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be 16 | // included in all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 22 | // BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 23 | // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 24 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | 27 | package main 28 | 29 | import ( 30 | "bufio" 31 | "bytes" 32 | "context" 33 | "encoding/base64" 34 | "fmt" 35 | "github.com/containrrr/shoutrrr" 36 | "github.com/twmb/franz-go/pkg/kgo" 37 | "github.com/urfave/cli/v3" 38 | "gopkg.in/yaml.v3" 39 | "io/ioutil" 40 | "log" 41 | "math" 42 | "os" 43 | "os/exec" 44 | "os/signal" 45 | "regexp" 46 | "strings" 47 | "sync" 48 | "syscall" 49 | "text/template" 50 | "time" 51 | ) 52 | 53 | var version = "dev" 54 | var restart bool = true 55 | var observed_cmd *exec.Cmd 56 | 57 | type Channel struct { 58 | Name string `yaml:"name"` 59 | Type string `yaml:"type"` 60 | URL string `yaml:"url"` 61 | Template string `yaml:"template"` 62 | Topic string `yaml:"topic"` 63 | Broker string `yaml:"broker"` 64 | Shell string `yaml:"shell"` 65 | } 66 | 67 | type Signal struct { 68 | Regex string `yaml:"regex"` 69 | Channel string `yaml:"channel"` 70 | } 71 | 72 | type Config struct { 73 | Channel []Channel `yaml:"channels"` 74 | Signal []Signal `yaml:"signals"` 75 | } 76 | 77 | var kafkaClients map[string]*kgo.Client = make(map[string]*kgo.Client) 78 | 79 | func kafkaConnect(channels []Channel) (map[string]*kgo.Client, error) { 80 | 81 | for _, channel := range channels { 82 | if channel.Type == "kafka" { 83 | seeds := []string{channel.Broker} 84 | opts := []kgo.Opt{ 85 | kgo.RequiredAcks(kgo.AllISRAcks()), 86 | kgo.DisableIdempotentWrite(), 87 | kgo.ProducerLinger(50 * time.Millisecond), 88 | kgo.RecordRetries(math.MaxInt32), 89 | kgo.RecordDeliveryTimeout(5 * time.Second), 90 | kgo.ProduceRequestTimeout(5 * time.Second), 91 | kgo.SeedBrokers(seeds...), 92 | } 93 | 94 | cl, err := kgo.NewClient(opts...) 95 | if err != nil { 96 | log.Fatal("green-orb error: failed to create kafka client connection: ", err) 97 | } 98 | kafkaClients[channel.Name] = cl 99 | } 100 | } 101 | return kafkaClients, nil 102 | } 103 | 104 | func compileSignals(signals []Signal) ([]CompiledSignal, error) { 105 | var compiledSignals []CompiledSignal 106 | for _, signal := range signals { 107 | re, err := regexp.Compile(signal.Regex) 108 | if err != nil { 109 | log.Fatal("green-orb error: failed to compile regex \"", signal.Regex, "\": ", err) 110 | } 111 | compiledSignals = append(compiledSignals, CompiledSignal{ 112 | Regex: re, 113 | Channel: signal.Channel, 114 | }) 115 | } 116 | return compiledSignals, nil 117 | } 118 | 119 | type CompiledSignal struct { 120 | Regex *regexp.Regexp 121 | Channel string 122 | } 123 | 124 | type Notification struct { 125 | PID int 126 | Channel Channel 127 | Match []string 128 | Message string 129 | } 130 | 131 | type TemplateData struct { 132 | PID int 133 | Logline string 134 | Matches []string 135 | Timestamp string 136 | Env map[string]string 137 | } 138 | 139 | func loadYAMLConfig(filename string, config *Config) error { 140 | bytes, err := ioutil.ReadFile(filename) 141 | if err != nil { 142 | log.Fatal("green-orb error: ", err) 143 | } 144 | 145 | err = yaml.Unmarshal(bytes, config) 146 | if err != nil { 147 | log.Fatal("green-orb error: Failed parsing config file: ", err) 148 | } 149 | 150 | return nil 151 | } 152 | 153 | func envToMap() (map[string]string, error) { 154 | envMap := make(map[string]string) 155 | var err error 156 | 157 | for _, v := range os.Environ() { 158 | split_v := strings.Split(v, "=") 159 | envMap[split_v[0]] = strings.Join(split_v[1:], "=") 160 | } 161 | return envMap, err 162 | } 163 | 164 | func startWorkers(notificationQueue <-chan Notification, numWorkers int64, wg *sync.WaitGroup) { 165 | var messageString string 166 | 167 | for i := 0; i < int(numWorkers); i++ { 168 | wg.Add(1) 169 | go func(workerID int) { 170 | defer wg.Done() 171 | for notification := range notificationQueue { 172 | env, _ := envToMap() 173 | td := TemplateData{PID: notification.PID, 174 | Logline: notification.Message, 175 | Env: env, 176 | Matches: notification.Match, 177 | Timestamp: time.Now().Format(time.RFC3339)} 178 | switch notification.Channel.Type { 179 | case "notify": 180 | tmpl, err := template.New("url").Parse(notification.Channel.URL) 181 | if err != nil { 182 | log.Fatal("green-orb error: can't parse URL template: ", err) 183 | } 184 | var buffer bytes.Buffer 185 | err = tmpl.Execute(&buffer, td) 186 | if err != nil { 187 | log.Fatal("green-orb error: can't execute URL template: ", err) 188 | } 189 | urlString := buffer.String() 190 | if notification.Channel.Template != "" { 191 | tmpl, err := template.New("msg").Parse(notification.Channel.Template) 192 | if err != nil { 193 | log.Fatal("green-orb error: can't parse template: ", err) 194 | } 195 | var buffer bytes.Buffer 196 | err = tmpl.Execute(&buffer, td) 197 | if err != nil { 198 | log.Fatal("green-orb error: can't execute URL template: ", err) 199 | } 200 | messageString = buffer.String() 201 | } else { 202 | messageString = notification.Message 203 | } 204 | err = shoutrrr.Send(urlString, messageString) 205 | if err != nil { 206 | log.Println("green-orb warning: failed sending notification: ", err) 207 | } 208 | case "exec": 209 | var stdout bytes.Buffer 210 | var stderr bytes.Buffer 211 | 212 | serializedMatches := "(" 213 | for _, m := range notification.Match[0:] { 214 | encoded := base64.StdEncoding.EncodeToString([]byte(m)) 215 | serializedMatches += fmt.Sprintf("$(echo %s | base64 -d) ", encoded) 216 | } 217 | serializedMatches += ")" 218 | 219 | cmd := exec.Command("bash", "-c", "export ORB_MATCHES=" + serializedMatches + "; " + notification.Channel.Shell) 220 | cmd.Env = os.Environ() 221 | cmd.Env = append(cmd.Env, fmt.Sprintf("ORB_PID=%d", notification.PID)) 222 | cmd.Stdout = &stdout 223 | cmd.Stderr = &stderr 224 | cmd.Run() 225 | case "kafka": 226 | ctx := context.Background() 227 | record := &kgo.Record{Topic: notification.Channel.Topic, Value: []byte(notification.Message)} 228 | if err := kafkaClients[notification.Channel.Name].ProduceSync(ctx, record).FirstErr(); err != nil { 229 | log.Println("green-orb warning: kafka record had a produce error:", err) 230 | } 231 | case "restart": 232 | restart = true 233 | observed_cmd.Process.Signal(syscall.SIGTERM) 234 | case "kill": 235 | restart = false 236 | observed_cmd.Process.Signal(syscall.SIGTERM) 237 | } 238 | } 239 | }(i) 240 | } 241 | } 242 | 243 | func main() { 244 | 245 | var configFilePath string 246 | var numWorkers int64 247 | 248 | cmd := &cli.Command{ 249 | Name: "orb", 250 | HideHelpCommand: true, 251 | Version: version, 252 | Usage: "Your observe-and-report buddy", 253 | Copyright: "Copyright (C) 2023-2024 Anthony Green .\nDistributed under the terms of the MIT license.\nSee https://github.com/atgreen/green-orb for details.", 254 | Flags: []cli.Flag{ 255 | &cli.StringFlag{ 256 | Name: "config", 257 | Value: "green-orb.yaml", 258 | Aliases: []string{"c"}, 259 | Usage: "path to the green-orb configuration file", 260 | Destination: &configFilePath, 261 | }, 262 | &cli.IntFlag{ 263 | Name: "workers", 264 | Value: 5, 265 | Aliases: []string{"w"}, 266 | Usage: "number of reporting workers", 267 | Action: func(ctx context.Context, cmd *cli.Command, v int64) error { 268 | if (v > 100) || (v < 1) { 269 | return fmt.Errorf("Flag workers value %v out of range [1-100]", v) 270 | } 271 | return nil 272 | }, 273 | Destination: &numWorkers, 274 | }, 275 | }, 276 | Action: func(ctx context.Context, cmd *cli.Command) error { 277 | 278 | if cmd.NArg() == 0 { 279 | // No arguments provided, show help text 280 | cli.ShowAppHelp(cmd) 281 | return nil 282 | } 283 | 284 | config := Config{} 285 | err := loadYAMLConfig(configFilePath, &config) 286 | if err != nil { 287 | log.Fatal("green-orb error: Failed to load config: ", err) 288 | } 289 | 290 | kafkaConnect(config.Channel) 291 | compiledSignals, _ := compileSignals(config.Signal) 292 | 293 | // The remaining arguments after flags are parsed 294 | subprocessArgs := cmd.Args().Slice() 295 | if len(subprocessArgs) == 0 { 296 | log.Fatal("green-orb error: No command provided to run") 297 | } 298 | 299 | notificationQueue := make(chan Notification, 100) 300 | 301 | // Use a WaitGroup to wait for the reading goroutines to finish 302 | var wg sync.WaitGroup 303 | var nwg sync.WaitGroup 304 | 305 | startWorkers(notificationQueue, numWorkers, &nwg) 306 | 307 | channelMap := make(map[string]Channel) 308 | for _, ch := range config.Channel { 309 | channelMap[ch.Name] = ch 310 | } 311 | 312 | for restart { 313 | 314 | restart = false 315 | 316 | // Prepare to run the subprocess 317 | observed_cmd = exec.Command(subprocessArgs[0], subprocessArgs[1:]...) 318 | // Rest of your code to handle the subprocess... 319 | stdout, _ := observed_cmd.StdoutPipe() 320 | stderr, _ := observed_cmd.StderrPipe() 321 | 322 | sigChan := make(chan os.Signal, 1) 323 | signal.Notify(sigChan) 324 | 325 | if err := observed_cmd.Start(); err != nil { 326 | log.Fatal("green-orb error: Failed to start subprocess: ", err) 327 | } 328 | 329 | // Goroutine for passing signals 330 | go func() { 331 | for sig := range sigChan { 332 | process_signal(observed_cmd, sig) 333 | } 334 | }() 335 | 336 | pid := observed_cmd.Process.Pid 337 | 338 | // Increment WaitGroup and start reading stdout 339 | wg.Add(2) 340 | go func() { 341 | defer wg.Done() 342 | monitorOutput(pid, bufio.NewScanner(stdout), compiledSignals, notificationQueue, channelMap, false) 343 | }() 344 | go func() { 345 | defer wg.Done() 346 | monitorOutput(pid, bufio.NewScanner(stderr), compiledSignals, notificationQueue, channelMap, true) 347 | }() 348 | 349 | // Wait for all reading to be complete 350 | wg.Wait() 351 | 352 | // Wait for the command to finish 353 | err = observed_cmd.Wait() 354 | 355 | // After cmd.Wait(), stop listening for signals 356 | signal.Stop(sigChan) 357 | close(sigChan) 358 | } 359 | 360 | close(notificationQueue) 361 | 362 | // Wait for all reading to be complete 363 | nwg.Wait() 364 | 365 | // Handle exit status 366 | if err != nil { 367 | // observed_cmd.Wait() returns an error if the command exits non-zero 368 | if exitError, ok := err.(*exec.ExitError); ok { 369 | // Get the command's exit code 370 | os.Exit(exitError.ExitCode()) 371 | } else { 372 | // Other error types (not non-zero exit) 373 | log.Fatal("green-orb error: Error waiting for Command:", err) 374 | } 375 | } else { 376 | // Success (exit code 0) 377 | os.Exit(0) 378 | } 379 | return nil 380 | }, 381 | } 382 | if err := cmd.Run(context.Background(), os.Args); err != nil { 383 | log.Fatal(err) 384 | } 385 | } 386 | 387 | func monitorOutput(pid int, scanner *bufio.Scanner, compiledSignals []CompiledSignal, notificationQueue chan Notification, channelMap map[string]Channel, is_stderr bool) { 388 | for scanner.Scan() { 389 | line := scanner.Text() 390 | suppress := false; 391 | 392 | for _, signal := range compiledSignals { 393 | match := signal.Regex.FindStringSubmatch(line) 394 | if (match != nil) { 395 | channel, _ := channelMap[signal.Channel] 396 | if channel.Type == "suppress" { 397 | suppress = true 398 | } else { 399 | notificationQueue <- Notification{PID: pid, Match: match, Channel: channel, Message: line} 400 | } 401 | } 402 | } 403 | 404 | if (! suppress) { 405 | if is_stderr { 406 | fmt.Fprintln(os.Stderr, line) 407 | } else { 408 | fmt.Println(line) 409 | } 410 | } 411 | } 412 | if err := scanner.Err(); err != nil { 413 | log.Fatal("green-orb error: Problem reading from pipe: ", err) 414 | } 415 | } 416 | -------------------------------------------------------------------------------- /green-orb.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | channels: 3 | 4 | # Launch an Ansible job. Important data is in environment variables. 5 | 6 | - name: "launch-ansible-job-template" 7 | type: "notify" 8 | url: "generic+https://{{.Env.AAP_USER}}:{{.Env.AAP_PASSWORD}}@{{.Env.AAP_HOST}}/api/v2/job_templates/14/launch/" 9 | 10 | # Send an email. 11 | 12 | - name: "email_alerts" 13 | type: "notify" 14 | url: "smtp://{{.Env.SENDER_EMAIL}:{{.Env.SENDER_PASSWORD}}@smtp.gmail.com:587/?from={{.Env.SENDER_EMAIL}}&to={{.Env.RECIPIENT_EMAIL}}&subject=Go%20Alert!" 15 | template: "Just started process {{.PID}} on {{.Env.HOSTNAME}}" 16 | 17 | # Produce a kafka message. 18 | 19 | - name: "kafka-test" 20 | type: "kafka" 21 | broker: "localhost:9632" # notsecret 22 | topic: "orb-messages" 23 | 24 | # Restart the process 25 | 26 | - name: "restart-test" 27 | type: "restart" 28 | 29 | - name: "jdump" 30 | type: "exec" 31 | shell: | 32 | jstack $ORB_PID > /tmp/jdump-$(date).txt 2>&1 33 | 34 | # Don't show this 35 | 36 | - name: "hide" 37 | type: "suppress" 38 | 39 | signals: 40 | 41 | - regex: "some log output" 42 | channel: "launch-ansible-job-template" 43 | 44 | - regex: "^Error:" 45 | channel: "jdump" 46 | 47 | - regex: "README.md" 48 | channel: "hide" 49 | -------------------------------------------------------------------------------- /images/green-orb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atgreen/green-orb/4425799ebbcf4245602c056687cc56405a3e439e/images/green-orb.png -------------------------------------------------------------------------------- /signals_unix.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin 2 | 3 | // SPDX-License-Identifier: MIT 4 | // 5 | // signals_unix.go - pass signals through to observed command 6 | // 7 | // Copyright (C) 2023, 2024 Anthony Green - green@moxielogic.com 8 | // 9 | // Permission is hereby granted, free of charge, to any person 10 | // obtaining a copy of this software and associated documentation 11 | // files (the “Software”), to deal in the Software without 12 | // restriction, including without limitation the rights to use, copy, 13 | // modify, merge, publish, distribute, sublicense, and/or sell copies 14 | // of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 24 | // BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 25 | // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 26 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | 29 | package main 30 | 31 | import ( 32 | "log" 33 | "os" 34 | "os/exec" 35 | "syscall" 36 | ) 37 | 38 | func process_signal(observed_cmd *exec.Cmd, sig os.Signal) error { 39 | if sig == syscall.SIGCHLD { 40 | return nil 41 | } 42 | if err := observed_cmd.Process.Signal(sig); err != nil { 43 | log.Println("green-orb error: Failed to send signal to subprocess:", err) 44 | } 45 | 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /signals_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | // SPDX-License-Identifier: MIT 4 | // 5 | // signals_windows.go - dummy placeholder 6 | // 7 | // Copyright (C) 2023, 2024 Anthony Green - green@moxielogic.com 8 | // 9 | // Permission is hereby granted, free of charge, to any person 10 | // obtaining a copy of this software and associated documentation 11 | // files (the “Software”), to deal in the Software without 12 | // restriction, including without limitation the rights to use, copy, 13 | // modify, merge, publish, distribute, sublicense, and/or sell copies 14 | // of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 24 | // BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 25 | // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 26 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | 29 | package main 30 | 31 | import ( 32 | "os" 33 | "os/exec" 34 | ) 35 | 36 | func process_signal(observed_cmd *exec.Cmd, sig os.Signal) error { 37 | return nil 38 | } 39 | --------------------------------------------------------------------------------