├── .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 |
--------------------------------------------------------------------------------