Skip to content

Commit 2613de8

Browse files
authored
Merge pull request #2 from github/poc
Poc
2 parents f171519 + 491e4e0 commit 2613de8

38 files changed

+2858
-49
lines changed

.github/copilot-instructions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ The linter is powered by `rubocop` with its config file located at `.rubocop.yml
7373
- The use of whitespace (newlines) over compactness of files.
7474
- Naming of variables and methods that lead to expressions and blocks reading more like English sentences.
7575
- Less lines of code over more. Keep changes minimal and focused.
76+
- The `docs/design.md` file is the main design document for the project. It might be out-of-date but it should still contain a general high-level overview of the project.
7677

7778
## Pull Request Requirements
7879

.github/workflows/acceptance.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: acceptance
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
acceptance:
14+
name: acceptance
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- name: checkout
19+
uses: actions/checkout@v4
20+
21+
- uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # [email protected]
22+
with:
23+
bundler-cache: true
24+
25+
- name: bootstrap
26+
run: script/bootstrap
27+
28+
- name: acceptance
29+
run: script/acceptance

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
bin/
1+
# Ignore binstubs but do commit the one specific for this code.
2+
bin/*
3+
!bin/hooks
4+
25
coverage/
36
logs/
47
tmp/
8+
spec/integration/tmp/
59
tarballs/
610
vendor/gems/
711
.idea

Dockerfile

Lines changed: 0 additions & 43 deletions
This file was deleted.

bin/hooks

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
# Development CLI script for the hooks framework
5+
6+
# Add lib directory to load path so we can require our code
7+
lib_dir = File.expand_path("../lib", __dir__)
8+
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
9+
10+
# Set bundle gemfile
11+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12+
13+
require "bundler/setup"
14+
require "optparse"
15+
require "hooks"
16+
require "yaml"
17+
18+
# CLI implementation
19+
class HooksCLI
20+
def initialize
21+
@options = {
22+
config_file: "hooks.yaml",
23+
port: 4567,
24+
host: "0.0.0.0",
25+
environment: "development",
26+
threads: "5:5"
27+
}
28+
end
29+
30+
def run(args = ARGV)
31+
# Handle version and help flags before parsing other options
32+
if args.include?("--version") || args.include?("-v")
33+
puts Hooks::VERSION
34+
exit
35+
end
36+
37+
if args.include?("--help") || args.include?("-h") || args.include?("help")
38+
show_help
39+
exit
40+
end
41+
42+
parse_options(args)
43+
44+
case args.first
45+
when "start", nil
46+
start_server
47+
when "version"
48+
puts Hooks::VERSION
49+
else
50+
puts "Unknown command: #{args.first}"
51+
show_help
52+
exit 1
53+
end
54+
end
55+
56+
private
57+
58+
def parse_options(args)
59+
OptionParser.new do |opts|
60+
opts.banner = "Usage: hooks [command] [options]"
61+
62+
opts.on("-c", "--config FILE", "Configuration file (default: hooks.yaml)") do |file|
63+
@options[:config_file] = file
64+
end
65+
66+
opts.on("-p", "--port PORT", Integer, "Port to listen on (default: 4567)") do |port|
67+
@options[:port] = port
68+
end
69+
70+
opts.on("-H", "--host HOST", "Host to bind to (default: 0.0.0.0)") do |host|
71+
@options[:host] = host
72+
end
73+
74+
opts.on("-e", "--environment ENV", "Environment (default: development)") do |env|
75+
@options[:environment] = env
76+
end
77+
78+
opts.on("-t", "--threads THREADS", "Puma thread pool size (default: 5:5)") do |threads|
79+
@options[:threads] = threads
80+
end
81+
82+
opts.on("-h", "--help", "Show this help message") do
83+
show_help
84+
exit
85+
end
86+
87+
opts.on("-v", "--version", "Show version") do
88+
puts Hooks::VERSION
89+
exit
90+
end
91+
end.parse!(args)
92+
end
93+
94+
def start_server
95+
puts "Starting Hooks webhook server..."
96+
puts "Config file: #{@options[:config_file]}"
97+
puts "Host: #{@options[:host]}"
98+
puts "Port: #{@options[:port]}"
99+
puts "Environment: #{@options[:environment]}"
100+
puts "Threads: #{@options[:threads]}"
101+
puts
102+
103+
# parse the configuration file
104+
if File.exist?(@options[:config_file])
105+
begin
106+
config = YAML.load_file(@options[:config_file])
107+
rescue Psych::SyntaxError => e
108+
puts "Error parsing configuration file: #{e.message}"
109+
exit 1
110+
end
111+
else
112+
puts "Configuration file #{@options[:config_file]} not found. Using defaults."
113+
config = {}
114+
end
115+
116+
# Merge CLI options into config
117+
config.merge!({
118+
"host" => @options[:host],
119+
"port" => @options[:port],
120+
"environment" => @options[:environment],
121+
"threads" => @options[:threads]
122+
})
123+
124+
# Build the application with framework-level config
125+
app = Hooks.build(config:)
126+
127+
# Start the server with CLI options
128+
require "rack"
129+
require "rack/handler/puma"
130+
require "puma"
131+
132+
Rack::Handler::Puma.run(
133+
app,
134+
Host: @options[:host],
135+
Port: @options[:port],
136+
Threads: @options[:threads],
137+
environment: @options[:environment]
138+
)
139+
rescue Interrupt
140+
puts "\nShutting down gracefully..."
141+
exit 0
142+
rescue => e
143+
puts "Error starting server: #{e.message}"
144+
puts e.backtrace if @options[:environment] == "development"
145+
exit 1
146+
end
147+
148+
def show_help
149+
puts <<~HELP
150+
Hooks - A Pluggable Webhook Server Framework
151+
152+
Usage:
153+
hooks [start] Start the webhook server (default)
154+
hooks version Show version information
155+
hooks help Show this help message
156+
157+
Options:
158+
-c, --config FILE Configuration file (default: hooks.yaml)
159+
-p, --port PORT Port to listen on (default: 4567)
160+
-H, --host HOST Host to bind to (default: 0.0.0.0)
161+
-e, --environment ENV Environment (default: development)
162+
-t, --threads THREADS Puma thread pool size (default: 5:5)
163+
-h, --help Show this help message
164+
-v, --version Show version
165+
166+
Examples:
167+
hooks Start server with default settings
168+
hooks start -p 8080 Start server on port 8080
169+
hooks -c custom.yaml -e production Start with custom config in production mode
170+
hooks -t 10:10 Start with 10 threads
171+
hooks version Show version information
172+
173+
For more information, see the README.md file.
174+
HELP
175+
end
176+
end
177+
178+
# Run the CLI if this file is executed directly
179+
if __FILE__ == $0
180+
HooksCLI.new.run
181+
end

config.ru

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "lib/hooks"
4+
5+
app = Hooks.build(config: "./spec/acceptance/config/hooks.yaml")
6+
run app

docs/design.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ path: /team1 # Mounted at <root_path>/team1
167167
handler: Team1Handler # Class in handler_dir
168168

169169
# Signature validation
170-
verify_signature:
170+
request_validator:
171171
type: default # 'default' uses HMACSHA256, or a custom class name
172172
secret_env_key: TEAM1_SECRET
173173
header: X-Hub-Signature
@@ -613,7 +613,7 @@ path: string # Endpoint path (mounted under root_path)
613613
handler: string # Handler class name
614614
615615
# Optional signature validation
616-
verify_signature:
616+
request_validator:
617617
type: string # 'default' or custom validator class name
618618
secret_env_key: string # ENV key containing secret
619619
header: string # Header containing signature (default: X-Hub-Signature)

hooks.gemspec

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ Gem::Specification.new do |spec|
2727

2828
spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
2929

30-
spec.files = %w[LICENSE README.md hooks.gemspec]
30+
spec.files = %w[LICENSE README.md hooks.gemspec config.ru]
3131
spec.files += Dir.glob("lib/**/*.rb")
32+
spec.files += Dir.glob("bin/*")
33+
spec.bindir = "bin"
34+
spec.executables = ["hooks"]
3235
spec.require_paths = ["lib"]
3336
end

lib/hooks.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "hooks/version"
4+
require_relative "hooks/core/builder"
5+
require_relative "hooks/handlers/base"
6+
7+
# Load all plugins (request validators, lifecycle hooks, etc.)
8+
Dir[File.join(__dir__, "hooks/plugins/**/*.rb")].sort.each do |file|
9+
require file
10+
end
11+
12+
# Load all utils
13+
Dir[File.join(__dir__, "hooks/utils/**/*.rb")].sort.each do |file|
14+
require file
15+
end
16+
17+
# Main module for the Hooks webhook server framework
18+
module Hooks
19+
# Build a Rack-compatible webhook server application
20+
#
21+
# @param config [String, Hash] Path to config file or config hash
22+
# @param log [Logger] Custom logger instance (optional)
23+
# @return [Object] Rack-compatible application
24+
def self.build(config: nil, log: nil)
25+
Core::Builder.new(
26+
config:,
27+
log:,
28+
).build
29+
end
30+
end

0 commit comments

Comments
 (0)