rodbot 0.1.0

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.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +1 -0
  3. data/CHANGELOG.md +14 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +653 -0
  6. data/exe/rodbot +7 -0
  7. data/lib/roda/plugins/rodbot.rb +36 -0
  8. data/lib/rodbot/async.rb +45 -0
  9. data/lib/rodbot/cli/command.rb +25 -0
  10. data/lib/rodbot/cli/commands/console.rb +23 -0
  11. data/lib/rodbot/cli/commands/credentials.rb +19 -0
  12. data/lib/rodbot/cli/commands/deploy.rb +20 -0
  13. data/lib/rodbot/cli/commands/new.rb +21 -0
  14. data/lib/rodbot/cli/commands/simulator.rb +17 -0
  15. data/lib/rodbot/cli/commands/start.rb +26 -0
  16. data/lib/rodbot/cli/commands/stop.rb +15 -0
  17. data/lib/rodbot/cli/commands/version.rb +15 -0
  18. data/lib/rodbot/cli/commands.rb +18 -0
  19. data/lib/rodbot/cli.rb +9 -0
  20. data/lib/rodbot/config.rb +157 -0
  21. data/lib/rodbot/constants.rb +13 -0
  22. data/lib/rodbot/db/hash.rb +71 -0
  23. data/lib/rodbot/db/redis.rb +61 -0
  24. data/lib/rodbot/db.rb +91 -0
  25. data/lib/rodbot/dispatcher.rb +125 -0
  26. data/lib/rodbot/env.rb +48 -0
  27. data/lib/rodbot/error.rb +19 -0
  28. data/lib/rodbot/generator.rb +108 -0
  29. data/lib/rodbot/log.rb +67 -0
  30. data/lib/rodbot/memoize.rb +86 -0
  31. data/lib/rodbot/plugins/github_webhook/README.github_webhook.md +42 -0
  32. data/lib/rodbot/plugins/github_webhook/app.rb +46 -0
  33. data/lib/rodbot/plugins/gitlab_webhook/README.gitlab_webhook.md +40 -0
  34. data/lib/rodbot/plugins/gitlab_webhook/app.rb +40 -0
  35. data/lib/rodbot/plugins/hal/README.hal.md +15 -0
  36. data/lib/rodbot/plugins/hal/app.rb +22 -0
  37. data/lib/rodbot/plugins/matrix/README.matrix.md +42 -0
  38. data/lib/rodbot/plugins/matrix/relay.rb +113 -0
  39. data/lib/rodbot/plugins/otp/README.otp.md +82 -0
  40. data/lib/rodbot/plugins/otp/app.rb +47 -0
  41. data/lib/rodbot/plugins/word_of_the_day/README.word_of_the_day.md +13 -0
  42. data/lib/rodbot/plugins/word_of_the_day/schedule.rb +51 -0
  43. data/lib/rodbot/plugins.rb +81 -0
  44. data/lib/rodbot/rack.rb +50 -0
  45. data/lib/rodbot/refinements.rb +118 -0
  46. data/lib/rodbot/relay.rb +104 -0
  47. data/lib/rodbot/services/app.rb +72 -0
  48. data/lib/rodbot/services/relay.rb +37 -0
  49. data/lib/rodbot/services/schedule.rb +29 -0
  50. data/lib/rodbot/services.rb +32 -0
  51. data/lib/rodbot/simulator.rb +60 -0
  52. data/lib/rodbot/version.rb +5 -0
  53. data/lib/rodbot.rb +60 -0
  54. data/lib/templates/deploy/docker/compose.yaml.gerb +37 -0
  55. data/lib/templates/deploy/docker-split/compose.yaml.gerb +52 -0
  56. data/lib/templates/deploy/procfile/Procfile.gerb +1 -0
  57. data/lib/templates/deploy/procfile-split/Procfile.gerb +5 -0
  58. data/lib/templates/deploy/render/render.yaml.gerb +0 -0
  59. data/lib/templates/deploy/render-split/render.yaml.gerb +0 -0
  60. data/lib/templates/new/LICENSE.txt +22 -0
  61. data/lib/templates/new/README.md +4 -0
  62. data/lib/templates/new/app/app.rb +11 -0
  63. data/lib/templates/new/app/routes/help.rb +19 -0
  64. data/lib/templates/new/app/views/layout.erb +12 -0
  65. data/lib/templates/new/app/views/root.erb +5 -0
  66. data/lib/templates/new/config/rodbot.rb +8 -0
  67. data/lib/templates/new/config/schedule.rb +5 -0
  68. data/lib/templates/new/config.ru +3 -0
  69. data/lib/templates/new/gems.locked +104 -0
  70. data/lib/templates/new/gems.rb +15 -0
  71. data/lib/templates/new/guardfile.rb +9 -0
  72. data/lib/templates/new/public/assets/images/rodbot.avif +0 -0
  73. data/lib/templates/new/public/assets/stylesheets/base.css +18 -0
  74. data/lib/templates/new/rakefile.rb +28 -0
  75. data.tar.gz.sig +0 -0
  76. metadata +510 -0
  77. metadata.gz.sig +3 -0
@@ -0,0 +1,125 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodbot
4
+
5
+ # Dispatcher infrastructure to run and supervise tasks
6
+ class Dispatcher
7
+
8
+ # Which signals detached processes trap in order to exit
9
+ TRAPS = %w(INT TERM).freeze
10
+
11
+ # @return [String] name of the group of tasks
12
+ attr_reader :group
13
+
14
+ # @return [String] registered tasks
15
+ attr_reader :tasks
16
+
17
+ # @param group [String] name of the group of tasks
18
+ # @param refork_delay [Integer] seconds to wait before re-forking dead tasks
19
+ def initialize(group, refork_delay: 5)
20
+ @group, @refork_delay = group, refork_delay
21
+ @tasks = {}
22
+ end
23
+
24
+ # Register a task
25
+ #
26
+ # @param task [String] task name
27
+ # @yield block for the task to run
28
+ # @return self
29
+ def register(task)
30
+ tasks[task] = Proc.new do
31
+ detach task
32
+ unless Rodbot::Log.std?
33
+ logger = Rodbot::Log.logger("dispatcher #{group}.#{task}]")
34
+ $stdout = Rodbot::Log::LoggerIO.new(logger, Logger::INFO)
35
+ $stderr = Rodbot::Log::LoggerIO.new(logger, Logger::WARN)
36
+ $stdin.reopen(File::NULL)
37
+ end
38
+ yield
39
+ end
40
+ self
41
+ end
42
+
43
+ # Run the registered tasks
44
+ #
45
+ # @param daemonize [Boolean] whether to run and supervise the tasks in
46
+ # the background
47
+ def run(daemonize: false)
48
+ if daemonize
49
+ Process.daemon(false, true)
50
+ detach 'supervisor'
51
+ dispatch
52
+ supervise
53
+ else
54
+ Process.setproctitle("#{group}.supervisor")
55
+ dispatch
56
+ sleep
57
+ end
58
+ ensure
59
+ cleanup
60
+ end
61
+
62
+ # Interrupt the registered tasks
63
+ def interrupt
64
+ Process.kill('INT', pid_file('supervisor').read.to_i)
65
+ rescue Errno::ESRCH
66
+ end
67
+
68
+ private
69
+
70
+ # Dispatch all registered tasks
71
+ def dispatch
72
+ tasks.each_value { fork &_1 }
73
+ end
74
+
75
+ # Supervise all dispatched tasks
76
+ def supervise
77
+ loop do
78
+ pid = Process.wait
79
+ sleep @refork_delay
80
+ fork &tasks[task(pid)]
81
+ end
82
+ end
83
+
84
+ # Remove all artefacts
85
+ def cleanup
86
+ Rodbot.env.tmp.glob("#{group}.*.pid").each do |pid_file|
87
+ pid = pid_file.read.to_i
88
+ Process.kill('INT', pid) unless pid == Process.pid
89
+ rescue Errno::ESRCH
90
+ ensure
91
+ pid_file.delete
92
+ end
93
+ end
94
+
95
+ # Perform operations to properly detach the task
96
+ #
97
+ # @param task [String] task name
98
+ def detach(task)
99
+ pid_file(task).write Process.pid
100
+ Process.setproctitle("#{group}.#{task}")
101
+ TRAPS.each { trap(_1) { exit } }
102
+ end
103
+
104
+ # PID file of the given task should be
105
+ #
106
+ # @param task [String] task name
107
+ # @return [Pathname] PID file
108
+ def pid_file(task)
109
+ Rodbot.env.tmp.join("#{group}.#{task}.pid")
110
+ end
111
+
112
+ # Fetch a task name for a process ID from the PID files
113
+ #
114
+ # @param pid [Integer] process ID
115
+ # @return [String] task name
116
+ def task(pid)
117
+ Rodbot.env.tmp.glob("#{group}.*.pid").find do |pid_file|
118
+ pid_file.read.to_i == pid
119
+ end.then do |pid_file|
120
+ pid_file.basename.to_s.split('.')[1] if pid_file
121
+ end
122
+ end
123
+
124
+ end
125
+ end
data/lib/rodbot/env.rb ADDED
@@ -0,0 +1,48 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodbot
4
+
5
+ # Environment the bot is currently living in
6
+ #
7
+ # @note Use the +Rodbot.env+ shortcut to access these methods!
8
+ class Env
9
+
10
+ # Supported environments
11
+ ENVS = %w(production development test).freeze
12
+
13
+ # @return [Pathname] root directory
14
+ attr_reader :root
15
+
16
+ # @return [Pathname] root directory
17
+ attr_reader :tmp
18
+
19
+ # @return [Pathname] gem root directory
20
+ attr_reader :gem
21
+
22
+ # @return [String] current environment - any of {ENVS}
23
+ attr_reader :current
24
+
25
+ # @param root [Pathname, String] root path (default: current directory)
26
+ def initialize(root: nil)
27
+ @root = root ? Pathname(root).realpath : Pathname.pwd
28
+ @tmp = @root.join('tmp')
29
+ @gem = Pathname(__dir__).join('..', '..').realpath
30
+ @current = ENV['RODBOT_ENV']
31
+ @current = 'development' unless ENVS.include? @current
32
+ end
33
+
34
+ # @!method production?
35
+ # @!method development?
36
+ # @!method test?
37
+ #
38
+ # Inquire the env based on RODBOT_ENV
39
+ #
40
+ # @return [Boolean]
41
+ ENVS.each do |env|
42
+ define_method "#{env}?" do
43
+ env == current
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,19 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodbot
4
+ class Error < StandardError
5
+ def initialize(message, details=nil)
6
+ @details = details
7
+ super(message)
8
+ end
9
+
10
+ def detailed_message
11
+ [message, @details].compact.join(': ')
12
+ end
13
+ end
14
+
15
+ GeneratorError = Class.new(Error)
16
+ PluginError = Class.new(Error)
17
+ ServiceError = Class.new(Error)
18
+ RelayError = Class.new(Error)
19
+ end
@@ -0,0 +1,108 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'fileutils'
4
+ require 'erb'
5
+ require 'pastel'
6
+
7
+ module Rodbot
8
+
9
+ # Generators for new bots, deployments and such
10
+ #
11
+ # All files inside the +templates_path+ are honoured provided they match the
12
+ # {GLOB}. Files with the extension ".gerb" are parsed like ERB files, however,
13
+ # GERB tags must use square brackets.
14
+ #
15
+ # ERB: <%= 'foobar' %>
16
+ # GERB: [%= 'foobar' %]
17
+ #
18
+ # It's therefore possible to generate ERB files such as +index.erb.gerb+.
19
+ #
20
+ # Helpers available in GERB templates have to be defined in
21
+ # {Rodbot::Generator::Helpers}.
22
+ class Generator
23
+
24
+ # Glob to filter relevant template files
25
+ GLOB = "**/{*,.ruby-version,.gitignore,.keep}"
26
+
27
+ # Colors used by +info+ to color part of the output
28
+ TAG_COLORS = {
29
+ create: :green
30
+ }.freeze
31
+
32
+ # @param templates_path [Pathname] root path of the templates to generate from
33
+ def initialize(templates_path)
34
+ @templates_path = templates_path
35
+ @helpers_binding = Helpers.new.instance_eval('binding')
36
+ @pastel = Pastel.new
37
+ end
38
+
39
+ # Print the interpolated template to STDOUT
40
+ def display
41
+ each_template_path do |template_path, target_path, content|
42
+ puts "# #{target_path}", (content || template_path.read)
43
+ end
44
+ end
45
+
46
+ # Write the interpolated template to directory
47
+ #
48
+ # @param directory [Pathname] where to write the files to
49
+ def write(directory)
50
+ fail(Rodbot::GeneratorError, "cowardly refusing to write to existing #{directory}") if directory.exist?
51
+ each_template_path do |template_path, target_path, content|
52
+ absolute_target_path = directory.join(target_path)
53
+ absolute_target_path.dirname.mkpath
54
+ puts tag(:create, target_path)
55
+ if content
56
+ absolute_target_path.write(content)
57
+ else
58
+ FileUtils.copy(template_path, absolute_target_path)
59
+ end
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def each_template_path
66
+ @templates_path.glob(GLOB).each do |template_path|
67
+ next unless template_path.file?
68
+ target_path = template_path.relative_path_from(@templates_path)
69
+ content = if template_path.extname == '.gerb'
70
+ target_path = target_path.dirname.join(target_path.basename('.gerb'))
71
+ eval_gerb(template_path.read)
72
+ end
73
+ yield(template_path, target_path, content)
74
+ end
75
+ end
76
+
77
+ def eval_gerb(string)
78
+ ungerbify(ERB.new(gerbify(string), trim_mode: '-').result(@helpers_binding))
79
+ end
80
+
81
+ def gerbify(string)
82
+ string.gsub('<%', '{%').gsub('%>', '%}').gsub('[%', '<%').gsub('%]', '%>')
83
+ end
84
+
85
+ def ungerbify(string)
86
+ string.gsub('{%', '<%').gsub('%}', '%>')
87
+ end
88
+
89
+ def tag(tag, string)
90
+ padded_tag = '[' + tag.to_s.ljust(6, ' ') + '] '
91
+ @pastel.decorate(padded_tag, TAG_COLORS[tag]) + string.to_s.strip
92
+ end
93
+
94
+ class Helpers
95
+ def timezone
96
+ Rodbot.config(:timezone)
97
+ end
98
+
99
+ def relay_extensions
100
+ Rodbot.plugins.extend_relay
101
+ Rodbot.plugins.extensions[:relay].to_h do |name, _|
102
+ [name, URI(Rodbot::Services::Relay.url(name)).port]
103
+ end
104
+ end
105
+ end
106
+
107
+ end
108
+ end
data/lib/rodbot/log.rb ADDED
@@ -0,0 +1,67 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodbot
4
+
5
+ # Log facilities
6
+ class Log
7
+
8
+ # Default logger
9
+ attr_reader :default_logger
10
+
11
+ # Black hole logger
12
+ attr_reader :null_logger
13
+
14
+ def initialize
15
+ @default_logger = self.class.logger('rodbot')
16
+ @null_logger = Logger.new(File::NULL)
17
+ end
18
+
19
+ # Add a log entry to the default log
20
+ #
21
+ # @note Use the +Rodbot.log+ shortcut to access this method!
22
+ #
23
+ # @param message [String] log message
24
+ # @param level [Integer] any log level from {Logger}
25
+ def log(message, level: Logger::INFO)
26
+ @default_logger.log(level, message)
27
+ end
28
+
29
+ # Create a logger instance for the given scope
30
+ #
31
+ # @param progname [String] progname used as default scope
32
+ def self.logger(progname)
33
+ Logger.new(Rodbot.config(:log, :to), progname: progname).tap do |logger|
34
+ logger.level = Rodbot.config(:log, :level)
35
+ end
36
+ end
37
+
38
+ # Whether currently configured to log to a std device (+STDOUT+ or +STDERR+)
39
+ #
40
+ # @return [Boolean]
41
+ def self.std?
42
+ [STDOUT, STDERR].include? Rodbot.config(:log, :to)
43
+ end
44
+
45
+ # Simple wrapper to decorate a logger for use with $stdout and $stderr
46
+ class LoggerIO
47
+
48
+ # @ param logger [Logger] logger instance
49
+ # @ param level [Integer] any log level from +Logger+
50
+ def initialize(logger, level)
51
+ @logger, @level = logger, level
52
+ end
53
+
54
+ # Write to the log
55
+ #
56
+ # @param message [String] log entry to add
57
+ def write(message)
58
+ @logger.log(@level, message.strip)
59
+ end
60
+
61
+ # Swallow any other method such as +sync+ or +flush+
62
+ def method_missing(*)
63
+ end
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,86 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodbot
4
+
5
+ # Memoize the return value of a specific method
6
+ #
7
+ # The method signature is taken into account, therefore calls of the same
8
+ # method with different positional and/or keyword arguments are cached
9
+ # independently. On the other hand, when calling the method with a block,
10
+ # no memoization is performed at all.
11
+ #
12
+ # @example Explicit declaration
13
+ # class Either
14
+ # include Rodbot::Memoize
15
+ #
16
+ # def either(argument=nil, keyword: nil, &block)
17
+ # $entropy || argument || keyword || (block.call if block)
18
+ # end
19
+ # memoize :either
20
+ # end
21
+ #
22
+ # @example Prefixed declaration
23
+ # class Either
24
+ # include Rodbot::Memoize
25
+ #
26
+ # memoize def either(argument=nil, keyword: nil, &block)
27
+ # $entropy || argument || keyword || (block.call if block)
28
+ # end
29
+ # end
30
+ #
31
+ # @example Behaviour of either of the above
32
+ # e = Either.new
33
+ # $entropy = nil
34
+ # e.either(1) # => 1
35
+ # e.either(keyword: 2) # => 2
36
+ # e.either { 3 } # => 3
37
+ # $entropy = :not_nil
38
+ # e.either(1) # => 1 (memoized)
39
+ # e.either(keyword: 2) # => 2 (memoized)
40
+ # e.either { 3 } # => :not_nil (cannot be memoized)
41
+ # Rodbot::Memoize.suspend do
42
+ # e.either(1) # => 1 (calculated, not memoized)
43
+ # end
44
+ module Memoize
45
+ module ClassMethods
46
+ def memoize(method)
47
+ unmemoized_method = :"_unmemoized_#{method}"
48
+ alias_method unmemoized_method, method
49
+ define_method method do |*args, **kargs, &block|
50
+ if Rodbot::Memoize.suspended? || block
51
+ send(unmemoized_method, *args, **kargs, &block)
52
+ else
53
+ id = method.hash ^ args.hash ^ kargs.hash
54
+ @_memoize_cache ||= {}
55
+ if @_memoize_cache.has_key? id
56
+ @_memoize_cache[id]
57
+ else
58
+ @_memoize_cache[id] = send(unmemoized_method, *args, **kargs)
59
+ end
60
+ end
61
+ end
62
+ method
63
+ end
64
+ end
65
+
66
+ class << self
67
+ def suspend
68
+ @suspended = true
69
+ yield
70
+ ensure
71
+ @suspended = false
72
+ end
73
+
74
+ def suspended?
75
+ @suspended = false if @suspended.nil?
76
+ @suspended
77
+ end
78
+
79
+ def included(base)
80
+ base.extend(ClassMethods)
81
+ end
82
+ end
83
+ end
84
+
85
+ end
86
+
@@ -0,0 +1,42 @@
1
+ # Rodbot Plugin – GitHub Webhook
2
+
3
+ Pipeline event announcements from GitHub
4
+
5
+ ## Preparation
6
+
7
+ The Rodbot app binds to `localhost` by default which cannot be reached from GitHub. Make sure this connection is possible by setting a different IP in `config/rodbot.rb`:
8
+
9
+ ```ruby
10
+ app do
11
+ host '0.0.0.0'
12
+ end
13
+ ```
14
+
15
+ To authenticate the webhook calls from GitHub, create a new random secret token:
16
+
17
+ ```
18
+ ruby -r securerandom -e "puts SecureRandom.alphanumeric(20)"
19
+ ```
20
+
21
+ ## Activation
22
+
23
+ Activate and configure this plugin in `config/rodbot.rb`:
24
+
25
+ ```ruby
26
+ plugin :github_webhook do
27
+ secret_tokens '<TOKEN>'
28
+ end
29
+ ```
30
+
31
+ You can set any number of secure tokens here separated with colons.
32
+
33
+ ## Add Repositories
34
+
35
+ Add a webhook to every GitHub repository you'd like to see pipeline event announcements for. Go to `https://github.com/<USER>/<REPO>/settings/hooks` and create a new webhook with the following properties:
36
+
37
+ * Payload URL: `https://<RODBOT-APP>/github_webhook`
38
+ * Content type: `application/json`
39
+ * Secret: `<TOKEN>`
40
+ * SSL verification: (o) Enable SSL verification
41
+ * Which events? (o) Let me select individual events: [x] Workflow runs
42
+ * And... [x] Active
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rodbot
4
+ class Plugins
5
+ class GithubWebhook
6
+ module App
7
+
8
+ class Routes < ::App
9
+ route do |r|
10
+ r.post '' do
11
+ r.halt 200 if request.env['HTTP_X_GITHUB_EVENT'] == 'ping'
12
+ r.halt 400 unless request.env['HTTP_X_GITHUB_EVENT'] == 'workflow_run'
13
+ r.halt 401 unless authorized? request
14
+ json = JSON.parse(request.body.read)
15
+ project = json.dig('repository', 'full_name')
16
+ status = json.dig('workflow_run', 'status')
17
+ status = json.dig('workflow_run', 'conclusion') if status == 'completed'
18
+ Rodbot.say [emoji_for(status), project, status.gsub('_', ' ')].join(' ')
19
+ r.halt 200
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def authorized?(request)
26
+ Rodbot.config(:plugin, :github_webhook, :secret_tokens).to_s.split(':').any? do |secret|
27
+ signature = 'sha256=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), secret, request.body.read)
28
+ request.body.rewind
29
+ ::Rack::Utils.secure_compare(signature, request.env['HTTP_X_HUB_SIGNATURE_256'])
30
+ end
31
+ end
32
+
33
+ def emoji_for(status)
34
+ case status
35
+ when 'requested' then '🟡'
36
+ when 'success' then '🟢'
37
+ when 'failure' then '🔴'
38
+ else '⚪️'
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,40 @@
1
+ # Rodbot Plugin – GitLab Webhook
2
+
3
+ Pipeline event announcements from GitLab
4
+
5
+ ## Preparation
6
+
7
+ The Rodbot app binds to `localhost` by default which cannot be reached from GitLab. Make sure this connection is possible by setting a different IP in `config/rodbot.rb`:
8
+
9
+ ```ruby
10
+ app do
11
+ host '0.0.0.0'
12
+ end
13
+ ```
14
+
15
+ To authenticate the webhook calls from GitLab, create a new random secret token:
16
+
17
+ ```
18
+ ruby -r securerandom -e "puts SecureRandom.alphanumeric(20)"
19
+ ```
20
+
21
+ ## Activation
22
+
23
+ Activate and configure this plugin in `config/rodbot.rb`:
24
+
25
+ ```ruby
26
+ plugin :gitlab_webhook do
27
+ secret_tokens '<TOKEN>'
28
+ end
29
+ ```
30
+
31
+ You can set any number of secure tokens here separated with colons.
32
+
33
+ ## Add Repositories
34
+
35
+ Add a webhook to every GitLab repository you'd like to see pipeline event announcements for. Go to `https://gitlab.com/<USER>/<REPO>/-/hooks` and create a new webhook with the following properties:
36
+
37
+ * URL: `https://<RODBOT-APP>/gitlab_webhook`
38
+ * Secret token: `<TOKEN>`
39
+ * Trigger: [x] Pipeline events
40
+ * SSL verification: [x] Enable SSL verification
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rodbot
4
+ class Plugins
5
+ class GitlabWebhook
6
+ module App
7
+
8
+ class Routes < ::App
9
+ route do |r|
10
+ r.post '' do
11
+ r.halt 401 unless authorized? request
12
+ json = JSON.parse(request.body.read)
13
+ r.halt 400 unless json['object_kind'] == 'pipeline'
14
+ project = json.dig('project', 'path_with_namespace')
15
+ status = json.dig('object_attributes', 'detailed_status')
16
+ Rodbot.say [emoji_for(status), project, status.gsub('_', ' ')].join(' ')
17
+ r.halt 200
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def authorized?(request)
24
+ Rodbot.config(:plugin, :gitlab_webhook, :secret_tokens).to_s.split(':').include?(request.env['HTTP_X_GITLAB_TOKEN'])
25
+ end
26
+
27
+ def emoji_for(status)
28
+ case status
29
+ when 'running' then '🟡'
30
+ when 'passed' then '🟢'
31
+ when 'failed' then '🔴'
32
+ else '⚪️'
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,15 @@
1
+ # Rodbot Plugin – HAL 9000
2
+
3
+ Feel like Dave
4
+
5
+ ## Setup
6
+
7
+ Activate this plugin in `config/rodbot.rb`:
8
+
9
+ ```ruby
10
+ plugin :hal
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Send the "hal" command, on most communication networks with `!hal`.
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rodbot
4
+ class Plugins
5
+ class Hal
6
+ module App
7
+
8
+ class Routes < ::App
9
+ route do |r|
10
+ r.root do
11
+ response['Content-Type'] = 'text/markdown; charset=utf-8'
12
+ <<~END
13
+ [🔴](https://www.youtube.com/watch?v=ARJ8cAGm6JE) I'm sorry [[SENDER]], I'm afraid I can't do that.
14
+ END
15
+ end
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+ end