rodbot 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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