Skip to content

Commit

Permalink
feat(scheme/plugin): support -scheme suffix
Browse files Browse the repository at this point in the history
  • Loading branch information
macrat committed Dec 20, 2022
1 parent ab5d072 commit ee8a832
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 32 deletions.
42 changes: 25 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -401,24 +401,30 @@ Even if use it as an alert URL, the behavior is almost the same, but send alert

#### Plugin

A plugin is a executable file named like `ayd-xxx-probe` or `ayd-xxx-alert`, installed to the PATH directory.
A plugin is an executable file installed in the PATH directory.

Ayd looks for `ayd-xxx-probe` for a target or `ayd-xxx-alert` for an alert, if URL scheme is `xxx:`, `xxx-yyy:`, or `xxx+yyy:`.
You can change scheme via changing `xxx`, but you can't use `ayd`, `alert`, and the scheme that is supported by Ayd itself.
And you can use `yyy` part to change plugin behavior, the same as [http:](#http--https) or [dns:](#dns).
The name of plugin depends on the URL scheme its supports and its purpose, like `ayd-xxx-probe`, `ayd-xxx-alert`, or `ayd-xxx-scheme`.
A plugin named `-probe` is for probing target, named `-alert` is for sending alerts, and named `-scheme` supports both purposes.

The longest plugin name has priority if you installed multiple plugins.
For example, `ayd-xxx-yyy-probe` has high priority than `ayd-xxx-probe`.
For example, if the target URL has the scheme `xxx-yyy:`, Ayd will search these executable files in order of priority:

1. `ayd-xxx-yyy-probe`
2. `ayd-xxx-yyy-scheme`
3. `ayd-xxx-probe`
4. `ayd-xxx-scheme`

The scheme names that supported by Ayd, `ayd`, and `alert`, are reserved and cannot be used by plugins.

The plugin prints result to stdout, in the same format as [log file](#log-file).
Plugins should not report results that are more than 1 hour old.

Ayd expects UTF-8 text as outputs of plugins.
But in Windows, you can use system's default character encoding.
Ayd expects the output of the plugin to be in UTF-8.
However, in Windows, the system's default character encoding can be used.
Please see also [text encoding chapter](#text-encoding).

Execution of a plugin will timeout in maximum 1 hour and report as failure.
If a plugin takes longer than 1 hour to execute, it will be timed out and reported as a failure.

The differences from plugin to [`exec:`](#exec) are below.
The differences from plugin to [`exec:`](#exec) are:

| | `exec: ` | plugin |
|---------------------------------------------------------|--------------|----------------------------|
Expand All @@ -428,33 +434,35 @@ The differences from plugin to [`exec:`](#exec) are below.
| receive raw target URL | can not | can |
| record about multiple targets like as [source](#source) | can not | can |

There is [a library for create plugin](https://pkg.go.dev/github.com/macrat/ayd/lib-ayd).
There is [a library for creating plugin](https://pkg.go.dev/github.com/macrat/ayd/lib-ayd).

##### Probe plugin

Probe plugin, which to check the target, receives the target URL as the only one argument of the command.
The probe plugin is for checking the target.
It receives the target URL as the only one argument.

For example, target URL `foobar:your-target` has the same mean as below command.
For example, the target URL `foobar:your-target` will be called like:

``` shell
$ ayd-foobar-probe "foobar:your-target"
```

##### Alert plugin

Alert plugin, which to send alerts, receives two arguments.
The first argument is a URL for alert itself.
The alert plugin is for sending an alert.
It receives two arguments.
The first argument is an alert URL.
The second one is the latest record that fired the alert in JSON format.

For example, the alert URL `foobar:your-alert` for plugin `ayd-foobar-alert` will be called like a below command.
For example, the alert URL `foobar:your-alert` for plugin `ayd-foobar-alert` will be called like:

``` shell
$ ayd-foobar-alert \
"foobar:your-alert" \
'{"time":"2001-02-30T16:05:06+09:00", "status":"FAILURE", "latency":"1.234", "target":"ping:your-target", "message":"this is message of the record"}'
```

The output of the probe plugin will parsed the same way to [log file](#log-file), but all target URL will add `alert:` prefix and won't not show in status page.
The `target` URLs in the alert plugin's output, will be added `alert:` prefix before store in Ayd, and hide in the status pages.

##### plugin list

Expand Down
19 changes: 14 additions & 5 deletions internal/scheme/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,35 @@ type PluginScheme struct {
}

// pluginCandidates makes scheme name candidates of plugin by URL scheme.
func pluginCandidates(scheme string) []string {
// The output is priority ascending order, which means the first candidate has the lowest priority.
func pluginCandidates(scheme, scope string) []string {
var xs []string

for i, x := range scheme {
if x == '-' || x == '+' {
xs = append(xs, scheme[:i])
xs = append(
xs,
"ayd-"+scheme[:i]+"-scheme",
"ayd-"+scheme[:i]+"-"+scope,
)
}
}

xs = append(xs, scheme)
xs = append(
xs,
"ayd-"+scheme+"-scheme",
"ayd-"+scheme+"-"+scope,
)

return xs
}

// findPlugin finds a plugin for URL scheme.
// It choice the longest name plugin.
func findPlugin(scheme, scope string) (commandName string, err error) {
candidates := pluginCandidates(scheme)
candidates := pluginCandidates(scheme, scope)
for i := range candidates {
commandName = "ayd-" + candidates[len(candidates)-i-1] + "-" + scope
commandName = candidates[len(candidates)-i-1]
_, err = exec.LookPath(commandName)
if err == nil || !errors.Is(err, exec.ErrNotFound) {
return
Expand Down
25 changes: 15 additions & 10 deletions internal/scheme/plugin_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ func TestPluginCandidates(t *testing.T) {
Input string
Output []string
}{
{"http", []string{"http"}},
{"source-view", []string{"source", "source-view"}},
{"hello-world+abc-def", []string{"hello", "hello-world", "hello-world+abc", "hello-world+abc-def"}},
{"http", []string{"ayd-http-scheme", "ayd-http-probe"}},
{"source-view", []string{"ayd-source-scheme", "ayd-source-probe", "ayd-source-view-scheme", "ayd-source-view-probe"}},
{"hello-world+abc-def", []string{"ayd-hello-scheme", "ayd-hello-probe", "ayd-hello-world-scheme", "ayd-hello-world-probe", "ayd-hello-world+abc-scheme", "ayd-hello-world+abc-probe", "ayd-hello-world+abc-def-scheme", "ayd-hello-world+abc-def-probe"}},
}

for _, tt := range tests {
tt := tt
t.Run(tt.Input, func(t *testing.T) {
output := pluginCandidates(tt.Input)
output := pluginCandidates(tt.Input, "probe")
if diff := cmp.Diff(output, tt.Output); diff != "" {
t.Errorf(diff)
}
Expand All @@ -50,20 +51,24 @@ func TestFindPlugin(t *testing.T) {
PreparePluginPath(t)

tests := []struct {
Scope string
Input string
Output string
Error error
}{
{"plug-plus", "ayd-plug-plus-probe", nil},
{"plug-minus", "ayd-plug-probe", nil},
{"plug", "ayd-plug-probe", nil},
{"plag-what", "", ErrUnsupportedScheme},
{"plag", "", ErrUnsupportedScheme},
{"probe", "plug-plus", "ayd-plug-plus-probe", nil},
{"probe", "plug-minus", "ayd-plug-probe", nil},
{"probe", "plug", "ayd-plug-probe", nil},
{"probe", "plag-what", "", ErrUnsupportedScheme},
{"probe", "plag", "", ErrUnsupportedScheme},
{"alert", "plug", "ayd-plug-scheme", nil},
{"alert", "foo", "ayd-foo-alert", nil},
}

for _, tt := range tests {
tt := tt
t.Run(tt.Input, func(t *testing.T) {
output, err := findPlugin(tt.Input, "probe")
output, err := findPlugin(tt.Input, tt.Scope)
if err != tt.Error {
t.Errorf("unexpected error: %s", err)
}
Expand Down
Empty file.
Empty file.

0 comments on commit ee8a832

Please sign in to comment.