leopard 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.release-please-config.json +32 -0
- data/.release-please-manifest.json +3 -0
- data/.rubocop.yml +46 -0
- data/.version.txt +1 -0
- data/CHANGELOG.md +32 -0
- data/Rakefile +17 -0
- data/Readme.adoc +109 -7
- data/ci/build_image.sh +257 -0
- data/ci/nats/accounts.txt +27 -0
- data/ci/nats/start.sh +33 -0
- data/ci/publish-gem.sh +90 -0
- data/examples/echo_endpoint.rb +19 -0
- data/lib/leopard/errors.rb +9 -0
- data/lib/leopard/message_wrapper.rb +64 -0
- data/lib/leopard/nats_api_server.rb +226 -0
- data/lib/leopard/settings.rb +13 -0
- data/lib/leopard/version.rb +1 -1
- data/lib/leopard.rb +21 -0
- metadata +73 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a1e36e2390cc27892053da1e1e37648f5b9d525ea45490dbce1388073c0dd909
|
4
|
+
data.tar.gz: 4696ea5b7be68a9f9b7700f457240e398cfb20cd26f62ab6f2a196ab37a92215
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6dc17020cfdae2aadac7620feef13b52f9c362d6fdf2dfdc2ea16f52a9bfbca6538317c647f6ad000a21c6f9ca8279e2c7116143db4907d055239f19a3da369e
|
7
|
+
data.tar.gz: fd5ff2f5041715540adc05b8939e64fb5f5a7efca1d207a0c6144a0f608f2869c58034de4551fe3c55f2db756b17c607dde9afd20155145933662806952489c4
|
@@ -0,0 +1,32 @@
|
|
1
|
+
{
|
2
|
+
"packages": {
|
3
|
+
".": {
|
4
|
+
"changelog-path": "CHANGELOG.md",
|
5
|
+
"release-type": "simple",
|
6
|
+
"bump-minor-pre-major": true,
|
7
|
+
"bump-patch-for-minor-pre-major": true,
|
8
|
+
"draft": false,
|
9
|
+
"prerelease": false,
|
10
|
+
"version-file": ".version.txt",
|
11
|
+
"extra-files": [
|
12
|
+
{
|
13
|
+
"type": "generic",
|
14
|
+
"path": "lib/leopard/version.rb"
|
15
|
+
},
|
16
|
+
{
|
17
|
+
"type": "generic",
|
18
|
+
"path": "oci/Gemfile"
|
19
|
+
}
|
20
|
+
],
|
21
|
+
"exclude-paths": [
|
22
|
+
".release-please-manifest.json",
|
23
|
+
".version.txt",
|
24
|
+
"lib/leopard/version.rb",
|
25
|
+
".rubocop.yml",
|
26
|
+
".overcommit.yml",
|
27
|
+
"coverage/coverage.json"
|
28
|
+
]
|
29
|
+
}
|
30
|
+
},
|
31
|
+
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
|
32
|
+
}
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
plugins:
|
2
|
+
- rubocop-performance
|
3
|
+
- rubocop-rake
|
4
|
+
- rubocop-minitest
|
5
|
+
|
6
|
+
AllCops:
|
7
|
+
NewCops: enable
|
8
|
+
|
9
|
+
Layout/LineLength:
|
10
|
+
Max: 140
|
11
|
+
|
12
|
+
Metrics/BlockLength:
|
13
|
+
Exclude:
|
14
|
+
- 'spec/**/*.rb'
|
15
|
+
|
16
|
+
Layout/ArgumentAlignment:
|
17
|
+
EnforcedStyle: with_fixed_indentation
|
18
|
+
|
19
|
+
Bundler/OrderedGems:
|
20
|
+
TreatCommentsAsGroupSeparators: true
|
21
|
+
|
22
|
+
Layout/MultilineMethodCallIndentation:
|
23
|
+
EnforcedStyle: indented
|
24
|
+
|
25
|
+
Layout/ArrayAlignment:
|
26
|
+
EnforcedStyle: with_fixed_indentation
|
27
|
+
|
28
|
+
Style/Documentation:
|
29
|
+
Enabled: false
|
30
|
+
|
31
|
+
Style/FrozenStringLiteralComment:
|
32
|
+
Enabled: true
|
33
|
+
SafeAutoCorrect: true
|
34
|
+
|
35
|
+
Style/TrailingCommaInArguments:
|
36
|
+
EnforcedStyleForMultiline: comma
|
37
|
+
|
38
|
+
Style/TrailingCommaInArrayLiteral:
|
39
|
+
EnforcedStyleForMultiline: comma
|
40
|
+
|
41
|
+
Style/TrailingCommaInHashLiteral:
|
42
|
+
EnforcedStyleForMultiline: comma
|
43
|
+
|
44
|
+
Style/HashSyntax:
|
45
|
+
Enabled: true
|
46
|
+
EnforcedShorthandSyntax: always
|
data/.version.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.3
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## [0.1.3](https://github.com/rubyists/leopard/compare/v0.1.2...v0.1.3) (2025-07-31)
|
4
|
+
|
5
|
+
|
6
|
+
### Bug Fixes
|
7
|
+
|
8
|
+
* Fixes the gem publisher, and adds missing .version.txt ([#11](https://github.com/rubyists/leopard/issues/11)) ([3dcbd3c](https://github.com/rubyists/leopard/commit/3dcbd3c1d687e04ce5fde85fef5c2d1c10a8a4cc))
|
9
|
+
|
10
|
+
## [0.1.2](https://github.com/rubyists/leopard/compare/v0.1.1...v0.1.2) (2025-07-31)
|
11
|
+
|
12
|
+
|
13
|
+
### Bug Fixes
|
14
|
+
|
15
|
+
* Remove sequel cruft in ci config ([#9](https://github.com/rubyists/leopard/issues/9)) ([09a43e2](https://github.com/rubyists/leopard/commit/09a43e23c309167c56095dd608af9d79ff4f9b19))
|
16
|
+
|
17
|
+
## [0.1.1](https://github.com/rubyists/leopard/compare/v0.1.0...v0.1.1) (2025-07-31)
|
18
|
+
|
19
|
+
|
20
|
+
### Features
|
21
|
+
|
22
|
+
* Adds gemspec and gemfile ([#1](https://github.com/rubyists/leopard/issues/1)) ([972dc72](https://github.com/rubyists/leopard/commit/972dc72de804ca10db5cf869d0ea996a94ac9722))
|
23
|
+
* Adds rakefile back ([#3](https://github.com/rubyists/leopard/issues/3)) ([271592c](https://github.com/rubyists/leopard/commit/271592c357e07d58de085297850533eaae60a285))
|
24
|
+
* Adds settings to module ([9ff0942](https://github.com/rubyists/leopard/commit/9ff0942dddd86bf4f97bc82626cc7bb35e4115ac))
|
25
|
+
* Basic functionality for serving apis ([#4](https://github.com/rubyists/leopard/issues/4)) ([9ff0942](https://github.com/rubyists/leopard/commit/9ff0942dddd86bf4f97bc82626cc7bb35e4115ac))
|
26
|
+
* Initial readme ([4ea9f34](https://github.com/rubyists/leopard/commit/4ea9f341c9df6096b8df3595ff6a075eb9b5c4f6))
|
27
|
+
|
28
|
+
|
29
|
+
### Bug Fixes
|
30
|
+
|
31
|
+
* Corrects gemname in publish-gem.sh ([9ff0942](https://github.com/rubyists/leopard/commit/9ff0942dddd86bf4f97bc82626cc7bb35e4115ac))
|
32
|
+
* Corrects the version ([#7](https://github.com/rubyists/leopard/issues/7)) ([a3de532](https://github.com/rubyists/leopard/commit/a3de5320a8c54e9ca6724b6e90812bb5b1b7d150))
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'minitest/test_task'
|
5
|
+
require 'bundler/gem_tasks'
|
6
|
+
require 'rubocop/rake_task'
|
7
|
+
|
8
|
+
RuboCop::RakeTask.new
|
9
|
+
|
10
|
+
Minitest::TestTask.create(:test) do |task|
|
11
|
+
task.libs << 'lib'
|
12
|
+
task.libs << 'test'
|
13
|
+
task.test_globs = ['test/*/**/*.rb']
|
14
|
+
task.warning = true
|
15
|
+
end
|
16
|
+
|
17
|
+
task default: %i[rubocop test]
|
data/Readme.adoc
CHANGED
@@ -1,9 +1,111 @@
|
|
1
|
-
|
1
|
+
= Leopard NATS ServiceApi Server
|
2
2
|
bougyman <me@bougyman.com>
|
3
|
-
:service-api: https://github.com/rubyists/nats-pure.rb/blob/main/docs/service_api.md[Service API]
|
3
|
+
:service-api: https://github.com/rubyists/nats-pure.rb/blob/main/docs/service_api.md[NATS Service API]
|
4
|
+
:conventional-commits: https://www.conventionalcommits.org/en/v1.0.0/[Conventional Commits]
|
5
|
+
:dry-configurable: https://github.com/dry-rb/dry-configurable[Dry::Configurable]
|
6
|
+
:dry-monads: https://github.com/dry-rb/dry-monads[Dry::Monads]
|
4
7
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
8
|
+
Leopard is a small framework for building concurrent {service-api} workers.
|
9
|
+
It uses `Concurrent::FixedThreadPool` to manage multiple workers in a single process and provides a
|
10
|
+
minimal DSL for defining endpoints and middleware.
|
11
|
+
|
12
|
+
== Features
|
13
|
+
|
14
|
+
* Declarative endpoint definitions with `#endpoint`.
|
15
|
+
* Grouping of endpoints with `#group`
|
16
|
+
* Simple concurrency via `#run` with a configurable number of instances.
|
17
|
+
* JSON aware message wrapper that gracefully handles parse errors.
|
18
|
+
* Middleware support using `#use`.
|
19
|
+
* Railway Oriented Design, using {dry-monads} for success and failure handling.
|
20
|
+
* {dry-configurable} settings container.
|
21
|
+
* `#logger` defaults to SemanticLogger (adjustable as the `#logger=` setting)
|
22
|
+
|
23
|
+
== Requirements
|
24
|
+
|
25
|
+
* Ruby >= 3.4.0
|
26
|
+
* A running NATS server with the Service API enabled.
|
27
|
+
|
28
|
+
== Installation
|
29
|
+
|
30
|
+
Add the gem to your project:
|
31
|
+
|
32
|
+
[source,ruby]
|
33
|
+
----
|
34
|
+
# Gemfile
|
35
|
+
gem 'leopard'
|
36
|
+
----
|
37
|
+
|
38
|
+
Then install it with Bundler.
|
39
|
+
|
40
|
+
[source,bash]
|
41
|
+
----
|
42
|
+
$ bundle install
|
43
|
+
----
|
44
|
+
|
45
|
+
== Usage
|
46
|
+
|
47
|
+
Create a service class and include `Rubyists::Leopard::NatsApiServer`.
|
48
|
+
Define one or more endpoints. Each endpoint receives a
|
49
|
+
`Rubyists::Leopard::MessageWrapper` object for each request to the {service-api} endpoint
|
50
|
+
that service class is is subscribed to (subject:, or name:). The message handler/callback
|
51
|
+
is expected to return a `Dry::Monads[:result]` object, typically a `Success` or `Failure`.
|
52
|
+
|
53
|
+
[source,ruby]
|
54
|
+
----
|
55
|
+
class EchoService
|
56
|
+
include Rubyists::Leopard::NatsApiServer
|
57
|
+
|
58
|
+
endpoint :echo do |msg|
|
59
|
+
Success(msg.data)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
----
|
63
|
+
|
64
|
+
Run the service by providing the NATS connection details and service options:
|
65
|
+
|
66
|
+
[source,ruby]
|
67
|
+
----
|
68
|
+
EchoService.run(
|
69
|
+
nats_url: 'nats://localhost:4222',
|
70
|
+
service_opts: { name: 'echo' },
|
71
|
+
instances: 4
|
72
|
+
)
|
73
|
+
----
|
74
|
+
|
75
|
+
Middleware can be inserted around endpoint dispatch:
|
76
|
+
|
77
|
+
[source,ruby]
|
78
|
+
----
|
79
|
+
class LoggerMiddleware
|
80
|
+
def initialize(app)
|
81
|
+
@app = app
|
82
|
+
end
|
83
|
+
|
84
|
+
def call(wrapper)
|
85
|
+
puts "received: #{wrapper.data.inspect}"
|
86
|
+
@app.call(wrapper)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
EchoService.use LoggerMiddleware
|
91
|
+
----
|
92
|
+
|
93
|
+
== Development
|
94
|
+
|
95
|
+
The project uses Minitest and RuboCop. Run tests with Rake:
|
96
|
+
|
97
|
+
[source,bash]
|
98
|
+
----
|
99
|
+
$ bundle exec rake
|
100
|
+
----
|
101
|
+
|
102
|
+
=== Conventional Commits (semantic commit messages)
|
103
|
+
|
104
|
+
This project follows the {conventional-commits} specification.
|
105
|
+
|
106
|
+
To contribute, please follow that commit message format,
|
107
|
+
or your pull request may be rejected.
|
108
|
+
|
109
|
+
== License
|
110
|
+
|
111
|
+
MIT
|
data/ci/build_image.sh
ADDED
@@ -0,0 +1,257 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
if readlink -f . >/dev/null 2>&1 # {{{ makes readlink work on mac
|
4
|
+
then
|
5
|
+
readlink=readlink
|
6
|
+
else
|
7
|
+
if greadlink -f . >/dev/null 2>&1
|
8
|
+
then
|
9
|
+
readlink=greadlink
|
10
|
+
else
|
11
|
+
printf "You must install greadlink to use this (brew install coreutils)\n" >&2
|
12
|
+
fi
|
13
|
+
fi # }}}
|
14
|
+
|
15
|
+
# Set here to the full path to this script
|
16
|
+
me=${BASH_SOURCE[0]}
|
17
|
+
[ -L "$me" ] && me=$($readlink -f "$me")
|
18
|
+
here=$(cd "$(dirname "$me")" && pwd)
|
19
|
+
just_me=$(basename "$me")
|
20
|
+
|
21
|
+
repo_top=$(git rev-parse --show-toplevel)
|
22
|
+
cd "$repo_top" || {
|
23
|
+
printf "Could not cd to %s\n" "$repo_top" >&2
|
24
|
+
exit 1
|
25
|
+
}
|
26
|
+
|
27
|
+
base_dir=$(basename "$(pwd)")
|
28
|
+
: "${UBUNTU_VERSION:=bookworm}"
|
29
|
+
: "${BUILD_CONTEXT:=$(pwd)}"
|
30
|
+
: "${IMAGE_NAME:=$base_dir}"
|
31
|
+
: "${LICENSE:=MIT}"
|
32
|
+
: "${REGISTRY:=ghcr.io}"
|
33
|
+
: "${RUBY_VERSION:=3.3.6}"
|
34
|
+
: "${REGISTRY_TOKEN:=$GITHUB_TOKEN}"
|
35
|
+
|
36
|
+
base_image_tag="$RUBY_VERSION-$UBUNTU_VERSION"
|
37
|
+
base_exists=$(skopeo list-tags docker://docker.io/ruby |jq -r "any(.Tags[] == \"$base_image_tag\"; .)")
|
38
|
+
if [ "$base_exists" = "false" ]
|
39
|
+
then
|
40
|
+
printf "Base image %s does not exist at docker.io/ruby, cannot build.\n" "$base_image_tag" >&2
|
41
|
+
exit 99
|
42
|
+
fi
|
43
|
+
|
44
|
+
usage() { # {{{
|
45
|
+
cat <<-EOT
|
46
|
+
Build an image, optionally pushing it to the registry
|
47
|
+
|
48
|
+
Usage: $0 <options> <image_tag>
|
49
|
+
Options:
|
50
|
+
-c CONTAINERFILE Path to the containerfile (default: ./oci/Containerfile)
|
51
|
+
-C CONTEXT Build context (default: $BUILD_CONTEXT)
|
52
|
+
-i NAME Name of the image (default: $IMAGE_NAME)
|
53
|
+
-l LICENSE License of the image (default: $LICENSE)
|
54
|
+
-r REGISTRY Registry to push the image to when -p is given (default: $REGISTRY)
|
55
|
+
-p Push the image to the registry
|
56
|
+
-h Show help / usage
|
57
|
+
EOT
|
58
|
+
} # }}}
|
59
|
+
|
60
|
+
die() { # {{{
|
61
|
+
local -i code
|
62
|
+
code=$1
|
63
|
+
shift
|
64
|
+
error "$*"
|
65
|
+
printf "\n" >&2
|
66
|
+
usage >&2
|
67
|
+
# shellcheck disable=SC2086
|
68
|
+
exit $code
|
69
|
+
} # }}}
|
70
|
+
|
71
|
+
## Logging functions # {{{
|
72
|
+
log() { # {{{
|
73
|
+
printf "%s [%s] <%s> %s\n" "$(date '+%Y-%m-%d %H:%M:%S.%6N')" "$$" "${just_me:-$0}" "$*"
|
74
|
+
} # }}}
|
75
|
+
|
76
|
+
debug() { # {{{
|
77
|
+
[ $verbose -lt 2 ] && return 0
|
78
|
+
# shellcheck disable=SC2059
|
79
|
+
log_line=$(printf "$@")
|
80
|
+
log "[DEBUG] $log_line" >&2
|
81
|
+
} # }}}
|
82
|
+
|
83
|
+
warn() { # {{{
|
84
|
+
# shellcheck disable=SC2059
|
85
|
+
log_line=$(printf "$@")
|
86
|
+
log "[WARN] $log_line" >&2
|
87
|
+
} # }}}
|
88
|
+
|
89
|
+
error() { # {{{
|
90
|
+
# shellcheck disable=SC2059
|
91
|
+
log_line=$(printf "$@")
|
92
|
+
log "[ERROR] $log_line" >&2
|
93
|
+
} # }}}
|
94
|
+
|
95
|
+
info() { # {{{
|
96
|
+
[ $verbose -lt 1 ] && return 0
|
97
|
+
# shellcheck disable=SC2059
|
98
|
+
log_line=$(printf "$@")
|
99
|
+
log "[INFO] $log_line" >&2
|
100
|
+
} # }}}
|
101
|
+
# }}}
|
102
|
+
|
103
|
+
push=0
|
104
|
+
verbose=0
|
105
|
+
while getopts :hpvc:C:i:l:r: opt # {{{
|
106
|
+
do
|
107
|
+
case $opt in
|
108
|
+
c)
|
109
|
+
CONTAINERFILE=$OPTARG
|
110
|
+
;;
|
111
|
+
C)
|
112
|
+
BUILD_CONTEXT=$OPTARG
|
113
|
+
;;
|
114
|
+
i)
|
115
|
+
IMAGE_NAME=$OPTARG
|
116
|
+
;;
|
117
|
+
l)
|
118
|
+
LICENSE=$OPTARG
|
119
|
+
;;
|
120
|
+
r)
|
121
|
+
REGISTRY=$OPTARG
|
122
|
+
;;
|
123
|
+
p)
|
124
|
+
push=1
|
125
|
+
;;
|
126
|
+
v)
|
127
|
+
verbose=$((verbose + 1))
|
128
|
+
;;
|
129
|
+
h)
|
130
|
+
usage
|
131
|
+
exit
|
132
|
+
;;
|
133
|
+
:)
|
134
|
+
printf "Option %s requires an argument\n" "$OPTARG" >&2
|
135
|
+
usage >&2
|
136
|
+
exit 28
|
137
|
+
;;
|
138
|
+
?)
|
139
|
+
printf "Invalid option '%s'\n" "$OPTARG" >&2
|
140
|
+
usage >&2
|
141
|
+
exit 27
|
142
|
+
;;
|
143
|
+
esac
|
144
|
+
done # }}}
|
145
|
+
shift $((OPTIND-1))
|
146
|
+
|
147
|
+
tag=$1
|
148
|
+
[ -z "$tag" ] && die 1 "Missing image tag"
|
149
|
+
shift
|
150
|
+
|
151
|
+
# Check for extra argument
|
152
|
+
if [ $# -gt 0 ]; then
|
153
|
+
# If we have the special argument '--' we shift it away, otherwise we die
|
154
|
+
[ "$1" != '--' ] && die 2 "Too many arguments"
|
155
|
+
# Once this is shifted away, the rest of the arguments are passed to the build command, below
|
156
|
+
shift
|
157
|
+
fi
|
158
|
+
|
159
|
+
if [ -z "$CONTAINERFILE" ]; then
|
160
|
+
printf "No containerfile specified, looking for default locations\n"
|
161
|
+
for containerfile in Containerfile Dockerfile
|
162
|
+
do
|
163
|
+
if [ -f ./oci/"$containerfile" ]; then
|
164
|
+
debug "Found ./oci/%s\n" "$containerfile"
|
165
|
+
containerfile=./oci/"$containerfile"
|
166
|
+
break
|
167
|
+
fi
|
168
|
+
if [ -f "$containerfile" ]; then
|
169
|
+
debug "Found %s\n" "$containerfile"
|
170
|
+
break
|
171
|
+
fi
|
172
|
+
done
|
173
|
+
else
|
174
|
+
[ -f "$CONTAINERFILE" ] || die 3 "Containerfile '$CONTAINERFILE' not found"
|
175
|
+
debug "Using containerfile %s\n" "$CONTAINERFILE"
|
176
|
+
containerfile=$CONTAINERFILE
|
177
|
+
fi
|
178
|
+
|
179
|
+
[ -f "$containerfile" ] || die 4 "No containerfile found"
|
180
|
+
|
181
|
+
[ -d "$BUILD_CONTEXT" ] || die 5 "Build context '$BUILD_CONTEXT' not found"
|
182
|
+
|
183
|
+
debug 'Building image from %s in in %s\n' "$containerfile" "$here"
|
184
|
+
# Build the image
|
185
|
+
if command -v podman 2>/dev/null
|
186
|
+
then
|
187
|
+
runtime=podman
|
188
|
+
elif command -v docker 2>/dev/null
|
189
|
+
then
|
190
|
+
runtime=docker
|
191
|
+
else
|
192
|
+
die 6 "No container runtime found"
|
193
|
+
fi
|
194
|
+
|
195
|
+
revision=$(git rev-parse HEAD)
|
196
|
+
shortref=$(git rev-parse --short "$revision")
|
197
|
+
repo_url=$(git remote get-url origin)
|
198
|
+
if [ -z "$repo_url" ]
|
199
|
+
then
|
200
|
+
die 7 "No remote found"
|
201
|
+
fi
|
202
|
+
if [[ $repo_url == *github.com/* ]]
|
203
|
+
then
|
204
|
+
owner_and_repo=${repo_url#*github.com/}
|
205
|
+
else
|
206
|
+
owner_and_repo=${repo_url##*:}
|
207
|
+
fi
|
208
|
+
# Get rid of the trailing .git
|
209
|
+
service=$(basename "$owner_and_repo" .git)
|
210
|
+
owner=$(dirname "$owner_and_repo")
|
211
|
+
|
212
|
+
full_tag=$IMAGE_NAME:$tag
|
213
|
+
# Pass any extra arguments to the build command ("$@" contains the rest of the arguments)
|
214
|
+
$runtime build --tag "$full_tag" "$@" \
|
215
|
+
--label org.opencontainers.image.created="$(date --utc --iso-8601=seconds)" \
|
216
|
+
--label org.opencontainers.image.description="Image for $service" \
|
217
|
+
--label org.opencontainers.image.licenses="$LICENSE" \
|
218
|
+
--label org.opencontainers.image.revision="$revision" \
|
219
|
+
--label org.opencontainers.image.url="$repo_url" \
|
220
|
+
--label org.opencontainers.image.title="$IMAGE_NAME" \
|
221
|
+
--label org.opencontainers.image.source="Generated by ruby-automation's build_image.sh ($USER@$HOSTNAME)" \
|
222
|
+
--label org.opencontainers.image.version="$full_tag" \
|
223
|
+
--label shortref="$shortref" \
|
224
|
+
--build-arg UBUNTU_VERSION="$UBUNTU_VERSION" \
|
225
|
+
--build-arg RUBY_VERSION="$RUBY_VERSION" \
|
226
|
+
-f "$containerfile" "$BUILD_CONTEXT" || die 8 "Failed to build image"
|
227
|
+
|
228
|
+
[ $push -eq 1 ] || exit 0
|
229
|
+
if ! $runtime login --get-login "$REGISTRY" >/dev/null 2>/dev/null
|
230
|
+
then
|
231
|
+
printf "Not logged in to '%s', trying to login\n" "$REGISTRY" >&2
|
232
|
+
[ -z "$REGISTRY_TOKEN" ] && die 9 "No REGISTRY_TOKEN (nor GITHUB_TOKEN) set, cannot login"
|
233
|
+
printf "%s" "$REGISTRY_TOKEN" | $runtime login -u "$REGISTRY_TOKEN" --password-stdin "$REGISTRY" || die 10 "Failed to login to $REGISTRY"
|
234
|
+
fi
|
235
|
+
|
236
|
+
# Split 1.2.3 into 1.2.3, 1.2, 1. We want to tag our image with all 3 of these
|
237
|
+
mapfile -t tags < <(echo "$tag" | awk -F'.' 'NF==3{print; print $1"."$2; print $1; next} NF==2{print; print $1; next} {print}')
|
238
|
+
for t in "${tags[@]}"
|
239
|
+
do
|
240
|
+
new_tag=$IMAGE_NAME:$t
|
241
|
+
registry_image_name="$REGISTRY/$owner/$new_tag"
|
242
|
+
if [ "$runtime" = "podman" ]
|
243
|
+
then
|
244
|
+
if [ "$full_tag" != "$new_tag" ]
|
245
|
+
then
|
246
|
+
debug "Tagging %s as %s\n" "$full_tag" "$new_tag"
|
247
|
+
podman tag "$full_tag" "$new_tag" || die 11 "Failed to tag image $full_tag as $new_tag"
|
248
|
+
fi
|
249
|
+
podman push "$new_tag" "$registry_image_name" || die 12 "Failed to push image $new_tag to $registry_image_name"
|
250
|
+
else
|
251
|
+
debug "Tagging %s as %s\n" "$full_tag" "$registry_image_name"
|
252
|
+
docker tag "$full_tag" "$registry_image_name" || die 13 "Failed to tag image $full_tag as $registry_image_name"
|
253
|
+
docker push "$registry_image_name" || die 14 "Failed to push image $new_tag to $registry_image_name"
|
254
|
+
fi
|
255
|
+
done
|
256
|
+
|
257
|
+
# vim: set foldmethod=marker et ts=4 sts=4 sw=4 ft=bash :
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Client port of 4222 on all interfaces
|
2
|
+
port: 4222
|
3
|
+
|
4
|
+
# HTTP monitoring port
|
5
|
+
monitor_port: 8222
|
6
|
+
|
7
|
+
accounts: {
|
8
|
+
$SYS: {
|
9
|
+
users: [
|
10
|
+
{ user: sys, password: sys }
|
11
|
+
]
|
12
|
+
}
|
13
|
+
ME: {
|
14
|
+
jetstream: enabled
|
15
|
+
users: [
|
16
|
+
{ user: me, password: youandme }
|
17
|
+
]
|
18
|
+
}
|
19
|
+
}
|
20
|
+
no_auth_user: me
|
21
|
+
|
22
|
+
authorization {
|
23
|
+
default_permissions = {
|
24
|
+
publish = ">"
|
25
|
+
subscribe = ">"
|
26
|
+
}
|
27
|
+
}
|
data/ci/nats/start.sh
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
NATS_VERSION=2
|
4
|
+
|
5
|
+
if readlink -f . >/dev/null 2>&1 # {{{ makes readlink work on mac
|
6
|
+
then
|
7
|
+
readlink=readlink
|
8
|
+
else
|
9
|
+
if greadlink -f . >/dev/null 2>&1
|
10
|
+
then
|
11
|
+
readlink=greadlink
|
12
|
+
else
|
13
|
+
printf "You must install greadlink to use this (brew install coreutils)\n" >&2
|
14
|
+
fi
|
15
|
+
fi # }}}
|
16
|
+
|
17
|
+
# Set here to the full path to this script
|
18
|
+
me=${BASH_SOURCE[0]}
|
19
|
+
[ -L "$me" ] && me=$($readlink -f "$me")
|
20
|
+
here=$(cd "$(dirname "$me")" && pwd)
|
21
|
+
just_me=$(basename "$me")
|
22
|
+
export just_me
|
23
|
+
|
24
|
+
cd "$here" || exit 1
|
25
|
+
if command -v podman 2>/dev/null
|
26
|
+
then
|
27
|
+
runtime=podman
|
28
|
+
else
|
29
|
+
runtime=docker
|
30
|
+
fi
|
31
|
+
|
32
|
+
set -x
|
33
|
+
exec "$runtime" run --rm -it -p 4222:4222 -p 6222:6222 -p 8222:8222 -v ./accounts.txt:/accounts.txt nats:"$NATS_VERSION" -js -c /accounts.txt "$@"
|
data/ci/publish-gem.sh
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
if readlink -f . >/dev/null 2>&1 # {{{ makes readlink work on mac
|
4
|
+
then
|
5
|
+
readlink=readlink
|
6
|
+
else
|
7
|
+
if greadlink -f . >/dev/null 2>&1
|
8
|
+
then
|
9
|
+
readlink=greadlink
|
10
|
+
else
|
11
|
+
printf "You must install greadlink to use this (brew install coreutils)\n" >&2
|
12
|
+
fi
|
13
|
+
fi # }}}
|
14
|
+
|
15
|
+
# Set here to the full path to this script
|
16
|
+
me=${BASH_SOURCE[0]}
|
17
|
+
[ -L "$me" ] && me=$($readlink -f "$me")
|
18
|
+
here=$(cd "$(dirname "$me")" && pwd)
|
19
|
+
root=$(cd "$here/.." && pwd)
|
20
|
+
just_me=$(basename "$me")
|
21
|
+
|
22
|
+
: "${GEM_NAME:=leopard}"
|
23
|
+
: "${GIT_ORG:=rubyists}"
|
24
|
+
|
25
|
+
GEM_HOST=$1
|
26
|
+
: "${GEM_HOST:=rubygems}"
|
27
|
+
|
28
|
+
case "$GEM_HOST" in
|
29
|
+
rubygems)
|
30
|
+
gem_key='rubygems'
|
31
|
+
gem_host='https://rubygems.org'
|
32
|
+
;;
|
33
|
+
github)
|
34
|
+
gem_key='github'
|
35
|
+
gem_host="https://rubygems.pkg.github.com/$GIT_ORG"
|
36
|
+
# Replace the gem host in the gemspec, so it allows pushing to the GitHub package registry
|
37
|
+
sed --in-place=.bak -e "s|https://rubygems.org|https://rubygems.pkg.github.com/$GIT_ORG|" "$here/../$GEM_NAME".gemspec
|
38
|
+
# Restore the original gemspec after the script finishes
|
39
|
+
trap 'mv -v "$here/../$GEM_NAME".gemspec.bak "$here/../$GEM_NAME".gemspec' EXIT
|
40
|
+
;;
|
41
|
+
*)
|
42
|
+
printf 'Unknown GEM_HOST: %s\n' "$GEM_HOST" >&2
|
43
|
+
exit 1
|
44
|
+
;;
|
45
|
+
esac
|
46
|
+
|
47
|
+
# We only want this part running in CI, with no ~/.gem dir
|
48
|
+
# For local testing, you should have a ~/.gem/credentials file with
|
49
|
+
# the keys you need to push to rubygems or github
|
50
|
+
if [ ! -d ~/.gem ]
|
51
|
+
then
|
52
|
+
if [ -z "$GEM_TOKEN" ]
|
53
|
+
then
|
54
|
+
printf 'No GEM_TOKEN provided, cannot publish\n' >&2
|
55
|
+
exit 1
|
56
|
+
fi
|
57
|
+
mkdir -p ~/.gem
|
58
|
+
printf '%s\n:%s: %s\n' '---' "$gem_key" "$GEM_TOKEN" > ~/.gem/credentials
|
59
|
+
chmod 600 ~/.gem/credentials
|
60
|
+
fi
|
61
|
+
|
62
|
+
bundle exec gem build
|
63
|
+
if [ -f "$here"/../.version.txt ]
|
64
|
+
then
|
65
|
+
version=$(<"$here"/../.version.txt)
|
66
|
+
else
|
67
|
+
version=$(git describe --tags --abbrev=0 | sed -e 's/^v//')
|
68
|
+
fi
|
69
|
+
|
70
|
+
if [ -z "$version" ]
|
71
|
+
then
|
72
|
+
gem="$(ls "$here"/../"$GEM_NAME"-*.gem | tail -1)"
|
73
|
+
else
|
74
|
+
gem="$(printf '%s/../%s-%s.gem' "$here" "$GEM_NAME" "$version")"
|
75
|
+
fi
|
76
|
+
|
77
|
+
if [ ! -f "$gem" ]
|
78
|
+
then
|
79
|
+
printf 'No gem file found: %s\n' "$gem" >&2
|
80
|
+
exit 1
|
81
|
+
fi
|
82
|
+
|
83
|
+
if [[ "${TRACE:-false}" == true || "${ACTIONS_STEP_DEBUG:-false}" == true ]]
|
84
|
+
then
|
85
|
+
printf "DEBUG: [%s] Building And Publishing %s to %s\n" "$just_me" "$gem" "$gem_host" >&2
|
86
|
+
fi
|
87
|
+
|
88
|
+
bundle exec gem push -k "$gem_key" --host "$gem_host" "$(basename "$gem")"
|
89
|
+
|
90
|
+
# vim: set foldmethod=marker et ts=4 sts=4 sw=4 ft=bash :
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative '../lib/leopard/nats_api_server'
|
5
|
+
|
6
|
+
# Example to echo the given message
|
7
|
+
class EchoService
|
8
|
+
include Rubyists::Leopard::NatsApiServer
|
9
|
+
|
10
|
+
endpoint(:echo) { |msg| Success(msg.data) }
|
11
|
+
end
|
12
|
+
|
13
|
+
if __FILE__ == $PROGRAM_NAME
|
14
|
+
EchoService.run(
|
15
|
+
nats_url: 'nats://localhost:4222',
|
16
|
+
service_opts: { name: 'example.echo', version: '1.0.0' },
|
17
|
+
instances: 4,
|
18
|
+
)
|
19
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Rubyists
|
6
|
+
module Leopard
|
7
|
+
class MessageWrapper
|
8
|
+
# @!attribute [r] raw
|
9
|
+
# @return [NATS::Message] The original NATS message.
|
10
|
+
#
|
11
|
+
# @!attribute [r] data
|
12
|
+
# @return [Object] The parsed data from the NATS message.
|
13
|
+
#
|
14
|
+
# @!attribute [r] headers
|
15
|
+
# @return [Hash] The headers from the NATS message.
|
16
|
+
attr_reader :raw, :data, :headers
|
17
|
+
|
18
|
+
# @param nats_msg [NATS::Message] The NATS message to wrap.
|
19
|
+
def initialize(nats_msg)
|
20
|
+
@raw = nats_msg
|
21
|
+
@data = parse_data(nats_msg.data)
|
22
|
+
@headers = nats_msg.header.to_h
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param payload [Object] The payload to respond with.
|
26
|
+
#
|
27
|
+
# @return [void]
|
28
|
+
def respond(payload)
|
29
|
+
raw.respond(serialize(payload))
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param err [String, Exception] The error message or exception to respond with.
|
33
|
+
# @param code [Integer] The HTTP status code to use for the error response.
|
34
|
+
#
|
35
|
+
# @return [void]
|
36
|
+
def respond_with_error(err, code: 500)
|
37
|
+
raw.respond_with_error(err.to_s, code:)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Parses the raw data from the NATS message.
|
43
|
+
# Assumes the data is in JSON format.
|
44
|
+
# If parsing fails, it returns the raw string.
|
45
|
+
#
|
46
|
+
# @param raw [String] The raw data from the NATS message.
|
47
|
+
#
|
48
|
+
# @return [Object] The parsed data, or the raw string if parsing fails.
|
49
|
+
def parse_data(raw)
|
50
|
+
JSON.parse(raw)
|
51
|
+
rescue JSON::ParserError
|
52
|
+
raw
|
53
|
+
end
|
54
|
+
|
55
|
+
# Serializes the object to a JSON string if it is not already a string.
|
56
|
+
# @param obj [Object] The object to serialize.
|
57
|
+
#
|
58
|
+
# @return [String] The serialized JSON string or the original string.
|
59
|
+
def serialize(obj)
|
60
|
+
obj.is_a?(String) ? obj : JSON.generate(obj)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'nats/client'
|
4
|
+
require 'dry/monads'
|
5
|
+
require 'concurrent'
|
6
|
+
require_relative '../leopard'
|
7
|
+
require_relative 'message_wrapper'
|
8
|
+
|
9
|
+
module Rubyists
|
10
|
+
module Leopard
|
11
|
+
module NatsApiServer
|
12
|
+
include Dry::Monads[:result]
|
13
|
+
extend Dry::Monads[:result]
|
14
|
+
|
15
|
+
def self.included(base)
|
16
|
+
base.extend(ClassMethods)
|
17
|
+
base.extend(Dry::Monads[:result])
|
18
|
+
base.include(SemanticLogger::Loggable)
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
def endpoints = @endpoints ||= []
|
23
|
+
def groups = @groups ||= {}
|
24
|
+
def middleware = @middleware ||= []
|
25
|
+
|
26
|
+
# Define an endpoint for the NATS API server.
|
27
|
+
#
|
28
|
+
# @param name [String] The name of the endpoint.
|
29
|
+
# @param subject [String, nil] The NATS subject to listen on. Defaults to the endpoint name.
|
30
|
+
# @param queue [String, nil] The NATS queue group to use. Defaults to nil.
|
31
|
+
# @param group [String, nil] The group this endpoint belongs to. Defaults to nil.
|
32
|
+
# @param handler [Proc] The block that will handle incoming messages.
|
33
|
+
#
|
34
|
+
# @return [void]
|
35
|
+
def endpoint(name, subject: nil, queue: nil, group: nil, &handler)
|
36
|
+
endpoints << {
|
37
|
+
name:,
|
38
|
+
subject: subject || name,
|
39
|
+
queue:,
|
40
|
+
group:,
|
41
|
+
handler:,
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
# Define a group for organizing endpoints.
|
46
|
+
#
|
47
|
+
# @param name [String] The name of the group.
|
48
|
+
# @param group [String, nil] The parent group this group belongs to. Defaults to nil.
|
49
|
+
# @param queue [String, nil] The NATS queue group to use for this group. Defaults to nil.
|
50
|
+
#
|
51
|
+
# @return [void]
|
52
|
+
def group(name, group: nil, queue: nil)
|
53
|
+
groups[name] = { name:, parent: group, queue: }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Use a middleware class for processing messages.
|
57
|
+
#
|
58
|
+
# @param klass [Class] The middleware class to use.
|
59
|
+
# @param args [Array] Optional arguments to pass to the middleware class.
|
60
|
+
# @param block [Proc] Optional block to pass to the middleware class.
|
61
|
+
#
|
62
|
+
# @return [void]
|
63
|
+
def use(klass, *args, &block)
|
64
|
+
middleware << [klass, args, block]
|
65
|
+
end
|
66
|
+
|
67
|
+
# Start the NATS API server.
|
68
|
+
# This method connects to the NATS server and spawns multiple instances of the API server.
|
69
|
+
#
|
70
|
+
# @param nats_url [String] The URL of the NATS server to connect to.
|
71
|
+
# @param service_opts [Hash] Options for the NATS service.
|
72
|
+
# @param instances [Integer] The number of instances to spawn. Defaults to 1.
|
73
|
+
# @param blocking [Boolean] If false, does not block current thread after starting the server. Defaults to true.
|
74
|
+
#
|
75
|
+
# @return [void]
|
76
|
+
def run(nats_url:, service_opts:, instances: 1, blocking: true)
|
77
|
+
logger.info 'Booting NATS API server...'
|
78
|
+
# Return the thread pool if non-blocking
|
79
|
+
return spawn_instances(nats_url, service_opts, instances) unless blocking
|
80
|
+
|
81
|
+
# Otherwise, just sleep the main thread forever
|
82
|
+
sleep
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# Spawns multiple instances of the NATS API server.
|
88
|
+
#
|
89
|
+
# @param url [String] The URL of the NATS server.
|
90
|
+
# @param opts [Hash] Options for the NATS service.
|
91
|
+
# @param count [Integer] The number of instances to spawn.
|
92
|
+
#
|
93
|
+
# @return [Concurrent::FixedThreadPool] The thread pool managing the worker threads.
|
94
|
+
def spawn_instances(url, opts, count)
|
95
|
+
pool = Concurrent::FixedThreadPool.new(count)
|
96
|
+
count.times do
|
97
|
+
eps = endpoints.dup
|
98
|
+
gps = groups.dup
|
99
|
+
pool.post { setup_worker(url, opts, eps, gps) }
|
100
|
+
end
|
101
|
+
pool
|
102
|
+
end
|
103
|
+
|
104
|
+
# Sets up a worker thread for the NATS API server.
|
105
|
+
# This method connects to the NATS server, adds the service, groups, and endpoints,
|
106
|
+
# and keeps the worker thread alive.
|
107
|
+
#
|
108
|
+
# @param url [String] The URL of the NATS server.
|
109
|
+
# @param opts [Hash] Options for the NATS service.
|
110
|
+
# @param eps [Array<Hash>] The list of endpoints to add.
|
111
|
+
# @param gps [Hash] The groups to add.
|
112
|
+
#
|
113
|
+
# @return [void]
|
114
|
+
def setup_worker(url, opts, eps, gps)
|
115
|
+
client = NATS.connect url
|
116
|
+
service = client.services.add(**opts)
|
117
|
+
group_map = add_groups(service, gps)
|
118
|
+
add_endpoints service, eps, group_map
|
119
|
+
# Keep the worker thread alive
|
120
|
+
sleep
|
121
|
+
end
|
122
|
+
|
123
|
+
# Adds groups to the NATS service.
|
124
|
+
#
|
125
|
+
# @param service [NATS::Service] The NATS service to add groups to.
|
126
|
+
# @param gps [Hash] The groups to add, where keys are group names and values are group definitions.
|
127
|
+
#
|
128
|
+
# @return [Hash] A map of group names to their created group objects.
|
129
|
+
def add_groups(service, gps)
|
130
|
+
created = {}
|
131
|
+
gps.each_key { |name| build_group(service, gps, created, name) }
|
132
|
+
created
|
133
|
+
end
|
134
|
+
|
135
|
+
# Builds a group in the NATS service.
|
136
|
+
#
|
137
|
+
# @param service [NATS::Service] The NATS service to add the group to.
|
138
|
+
# @param defs [Hash] The group definitions, where keys are group names and values are group definitions.
|
139
|
+
# @param cache [Hash] A cache to store already created groups.
|
140
|
+
# @param name [String] The name of the group to build.
|
141
|
+
#
|
142
|
+
# @return [NATS::Group] The created group object.
|
143
|
+
def build_group(service, defs, cache, name)
|
144
|
+
return cache[name] if cache.key?(name)
|
145
|
+
|
146
|
+
gdef = defs[name]
|
147
|
+
raise ArgumentError, "Group #{name} not defined" unless gdef
|
148
|
+
|
149
|
+
parent = gdef[:parent] ? build_group(service, defs, cache, gdef[:parent]) : service
|
150
|
+
cache[name] = parent.groups.add(gdef[:name], queue: gdef[:queue])
|
151
|
+
end
|
152
|
+
|
153
|
+
# Adds endpoints to the NATS service.
|
154
|
+
#
|
155
|
+
# @param service [NATS::Service] The NATS service to add endpoints to.
|
156
|
+
# @param endpoints [Array<Hash>] The list of endpoints to add.
|
157
|
+
# @param group_map [Hash] A map of group names to their created group objects.
|
158
|
+
#
|
159
|
+
# @return [void]
|
160
|
+
def add_endpoints(service, endpoints, group_map)
|
161
|
+
endpoints.each do |ep|
|
162
|
+
parent = ep[:group] ? group_map[ep[:group]] : service
|
163
|
+
raise ArgumentError, "Group #{ep[:group]} not defined" if ep[:group] && parent.nil?
|
164
|
+
|
165
|
+
parent.endpoints.add(
|
166
|
+
ep[:name], subject: ep[:subject], queue: ep[:queue]
|
167
|
+
) do |raw_msg|
|
168
|
+
wrapper = MessageWrapper.new(raw_msg)
|
169
|
+
dispatch_with_middleware(wrapper, ep[:handler])
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Dispatches a message through the middleware stack and handles it with the provided handler.
|
175
|
+
#
|
176
|
+
# @param wrapper [MessageWrapper] The message wrapper containing the raw message.
|
177
|
+
# @param handler [Proc] The handler to process the message.
|
178
|
+
#
|
179
|
+
# @return [void]
|
180
|
+
def dispatch_with_middleware(wrapper, handler)
|
181
|
+
app = ->(w) { handle_message(w.raw, handler) }
|
182
|
+
middleware.reverse_each do |(klass, args, blk)|
|
183
|
+
app = klass.new(app, *args, &blk)
|
184
|
+
end
|
185
|
+
app.call(wrapper)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Handles a raw NATS message using the provided handler.
|
189
|
+
#
|
190
|
+
# @param raw_msg [NATS::Message] The raw NATS message to handle.
|
191
|
+
# @param handler [Proc] The handler to process the message.
|
192
|
+
#
|
193
|
+
# @return [void]
|
194
|
+
def handle_message(raw_msg, handler)
|
195
|
+
wrapper = MessageWrapper.new(raw_msg)
|
196
|
+
result = instance_exec(wrapper, &handler)
|
197
|
+
process_result(wrapper, result)
|
198
|
+
rescue StandardError => e
|
199
|
+
logger.error 'Error processing message: ', e
|
200
|
+
wrapper.respond_with_error(e.message)
|
201
|
+
end
|
202
|
+
|
203
|
+
# Processes the result of the handler execution.
|
204
|
+
#
|
205
|
+
#
|
206
|
+
# @param wrapper [MessageWrapper] The message wrapper containing the raw message.
|
207
|
+
# @param result [Dry::Monads::Result] The result of the handler execution.
|
208
|
+
#
|
209
|
+
# @return [void]
|
210
|
+
# @raise [ResultError] If the result is not a Success or Failure monad.
|
211
|
+
def process_result(wrapper, result)
|
212
|
+
case result
|
213
|
+
in Dry::Monads::Success
|
214
|
+
wrapper.respond(result.value!)
|
215
|
+
in Dry::Monads::Failure
|
216
|
+
logger.error 'Error processing message: ', result.failure
|
217
|
+
wrapper.respond_with_error(result.failure)
|
218
|
+
else
|
219
|
+
logger.error('Unexpected result: ', result:)
|
220
|
+
raise ResultError, "Unexpected Response from Handler, must respond with a Success or Failure monad: #{result}"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'semantic_logger'
|
4
|
+
|
5
|
+
module Rubyists
|
6
|
+
module Leopard
|
7
|
+
extend Dry::Configurable
|
8
|
+
|
9
|
+
setting :libroot, reader: true, default: Pathname(__FILE__).dirname.join('..').expand_path
|
10
|
+
setting :root, reader: true, default: libroot.join('..').expand_path
|
11
|
+
setting :logger, reader: true, default: SemanticLogger[:Leopard]
|
12
|
+
end
|
13
|
+
end
|
data/lib/leopard/version.rb
CHANGED
data/lib/leopard.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/configurable'
|
4
|
+
require 'pathname'
|
5
|
+
require 'semantic_logger'
|
6
|
+
SemanticLogger.add_appender(io: $stdout, formatter: :color)
|
7
|
+
|
8
|
+
class Pathname
|
9
|
+
def /(other)
|
10
|
+
join other.to_s
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Rubyists
|
15
|
+
module Leopard
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
require_relative 'leopard/settings'
|
20
|
+
require_relative 'leopard/version'
|
21
|
+
require_relative 'leopard/errors'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: leopard
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- bougyman
|
@@ -9,6 +9,48 @@ bindir: exe
|
|
9
9
|
cert_chain: []
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: concurrent-ruby
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '1.1'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '1.1'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: dry-configurable
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.3'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.3'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: dry-monads
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.9'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.9'
|
12
54
|
- !ruby/object:Gem::Dependency
|
13
55
|
name: nats-pure
|
14
56
|
requirement: !ruby/object:Gem::Requirement
|
@@ -23,6 +65,20 @@ dependencies:
|
|
23
65
|
- - "~>"
|
24
66
|
- !ruby/object:Gem::Version
|
25
67
|
version: '2.5'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: semantic_logger
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '4'
|
75
|
+
type: :runtime
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '4'
|
26
82
|
description: Leopard is a puma-like server for managing concurrent NATS ServiceApi
|
27
83
|
endpoint workers
|
28
84
|
email:
|
@@ -31,7 +87,23 @@ executables: []
|
|
31
87
|
extensions: []
|
32
88
|
extra_rdoc_files: []
|
33
89
|
files:
|
90
|
+
- ".release-please-config.json"
|
91
|
+
- ".release-please-manifest.json"
|
92
|
+
- ".rubocop.yml"
|
93
|
+
- ".version.txt"
|
94
|
+
- CHANGELOG.md
|
95
|
+
- Rakefile
|
34
96
|
- Readme.adoc
|
97
|
+
- ci/build_image.sh
|
98
|
+
- ci/nats/accounts.txt
|
99
|
+
- ci/nats/start.sh
|
100
|
+
- ci/publish-gem.sh
|
101
|
+
- examples/echo_endpoint.rb
|
102
|
+
- lib/leopard.rb
|
103
|
+
- lib/leopard/errors.rb
|
104
|
+
- lib/leopard/message_wrapper.rb
|
105
|
+
- lib/leopard/nats_api_server.rb
|
106
|
+
- lib/leopard/settings.rb
|
35
107
|
- lib/leopard/version.rb
|
36
108
|
homepage: https://github.com/rubyists/leopard
|
37
109
|
licenses:
|