Commit f0bda77d authored by Dmytro Zaporozhets's avatar Dmytro Zaporozhets

Rename plugins to file hooks

Signed-off-by: default avatarDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
parent 3a3935d9
......@@ -1325,7 +1325,7 @@ class Project < ApplicationRecord
end
def has_active_hooks?(hooks_scope = :push_hooks)
hooks.hooks_for(hooks_scope).any? || SystemHook.hooks_for(hooks_scope).any? || Gitlab::Plugin.any?
hooks.hooks_for(hooks_scope).any? || SystemHook.hooks_for(hooks_scope).any? || Gitlab::FileHook.any?
end
def has_active_services?(hooks_scope = :push_hooks)
......
......@@ -14,7 +14,7 @@ class SystemHooksService
hook.async_execute(data, 'system_hooks')
end
Gitlab::Plugin.execute_all_async(data)
Gitlab::FileHook.execute_all_async(data)
end
private
......
......@@ -11,4 +11,4 @@
= render 'shared/web_hooks/index', hooks: @hooks, hook_class: @hook.class
= render 'shared/plugins/index'
= render 'shared/file_hooks/index'
- plugins = Gitlab::Plugin.files
- file_hooks = Gitlab::FileHook.files
.row.prepend-top-default
.col-lg-4
%h4.prepend-top-0
Plugins
= _('File Hooks')
%p
#{link_to 'Plugins', help_page_path('administration/plugins')} are similar to
system hooks but are executed as files instead of sending data to a URL.
= _('File hooks are similar to system hooks but are executed as files instead of sending data to a URL.')
= link_to _('For more information, see the File Hooks documentation.'), help_page_path('administration/file_hooks')
.col-lg-8.append-bottom-default
- if plugins.any?
- if file_hooks.any?
.card
.card-header
Plugins (#{plugins.count})
= _('File Hooks (%{count})') % { count: file_hooks.count }
%ul.content-list
- plugins.each do |file|
- file_hooks.each do |file|
%li
.monospace
= File.basename(file)
- else
.card.bg-light.text-center
.nothing-here-block No plugins found.
.nothing-here-block= _('No file hooks found.')
......@@ -158,7 +158,7 @@
- pages
- pages_domain_verification
- pages_domain_ssl_renewal
- plugin
- file_hook
- post_receive
- process_commit
- project_cache
......
# frozen_string_literal: true
class PluginWorker
class FileHookWorker
include ApplicationWorker
sidekiq_options retry: false
feature_category :integrations
def perform(file_name, data)
success, message = Gitlab::Plugin.execute(file_name, data)
success, message = Gitlab::FileHook.execute(file_name, data)
unless success
Gitlab::PluginLogger.error("Plugin Error => #{file_name}: #{message}")
Gitlab::FileHookLogger.error("File Hook Error => #{file_name}: #{message}")
end
true
......
---
title: Rename GitLab Plugins feature to GitLab File Hooks
merge_request: 22979
author:
type: changed
......@@ -76,7 +76,7 @@
- [pages_domain_ssl_renewal, 1]
- [object_storage_upload, 1]
- [object_storage, 1]
- [plugin, 1]
- [file_hook, 1]
- [pipeline_background, 1]
- [repository_update_remote_mirror, 1]
- [repository_remove_remote, 1]
......
# File hooks
> Introduced in GitLab 10.6.
> Until 12.8 the feature name was Plugins.
With custom file hooks, GitLab administrators can introduce custom integrations
without modifying GitLab's source code.
NOTE: **Note:**
Instead of writing and supporting your own file hook you can make changes
directly to the GitLab source code and contribute back upstream. This way we can
ensure functionality is preserved across versions and covered by tests.
NOTE: **Note:**
File hooks must be configured on the filesystem of the GitLab server. Only GitLab
server administrators will be able to complete these tasks. Explore
[system hooks] or [webhooks] as an option if you do not have filesystem access.
A file hook will run on each event so it's up to you to filter events or projects
within a file hook code. You can have as many file hooks as you want. Each file hook will
be triggered by GitLab asynchronously in case of an event. For a list of events
see the [system hooks] documentation.
## Setup
The file hooks must be placed directly into the `plugins` directory, subdirectories
will be ignored. There is an
[`example` directory inside `plugins`](https://gitlab.com/gitlab-org/gitlab/tree/master/plugins/examples)
where you can find some basic examples.
Follow the steps below to set up a custom hook:
1. On the GitLab server, navigate to the plugin directory.
For an installation from source the path is usually
`/home/git/gitlab/plugins/`. For Omnibus installs the path is
usually `/opt/gitlab/embedded/service/gitlab-rails/plugins`.
For [highly available] configurations, your hook file should exist on each
application server.
1. Inside the `plugins` directory, create a file with a name of your choice,
without spaces or special characters.
1. Make the hook file executable and make sure it's owned by the Git user.
1. Write the code to make the file hook function as expected. That can be
in any language, and ensure the 'shebang' at the top properly reflects the
language type. For example, if the script is in Ruby the shebang will
probably be `#!/usr/bin/env ruby`.
1. The data to the file hook will be provided as JSON on STDIN. It will be exactly
same as for [system hooks]
That's it! Assuming the file hook code is properly implemented, the hook will fire
as appropriate. The file hooks file list is updated for each event, there is no
need to restart GitLab to apply a new file hook.
If a file hook executes with non-zero exit code or GitLab fails to execute it, a
message will be logged to:
- `gitlab-rails/plugin.log` in an Omnibus installation.
- `log/plugin.log` in a source installation.
## Creating file hooks
Below is an example that will only response on the event `project_create` and
will inform the admins from the GitLab instance that a new project has been created.
```ruby
# By using the embedded ruby version we eliminate the possibility that our chosen language
# would be unavailable from
#!/opt/gitlab/embedded/bin/ruby
require 'json'
require 'mail'
# The incoming variables are in JSON format so we need to parse it first.
ARGS = JSON.parse(STDIN.read)
# We only want to trigger this file hook on the event project_create
return unless ARGS['event_name'] == 'project_create'
# We will inform our admins of our gitlab instance that a new project is created
Mail.deliver do
from 'info@gitlab_instance.com'
to 'admin@gitlab_instance.com'
subject "new project " + ARGS['name']
body ARGS['owner_name'] + 'created project ' + ARGS['name']
end
```
## Validation
Writing your own file hook can be tricky and it's easier if you can check it
without altering the system. A rake task is provided so that you can use it
in a staging environment to test your file hook before using it in production.
The rake task will use a sample data and execute each of file hook. The output
should be enough to determine if the system sees your file hook and if it was
executed without errors.
```bash
# Omnibus installations
sudo gitlab-rake file_hooks:validate
# Installations from source
cd /home/git/gitlab
bundle exec rake file_hooks:validate RAILS_ENV=production
```
Example of output:
```
Validating file hooks from /plugins directory
* /home/git/gitlab/plugins/save_to_file.clj succeed (zero exit code)
* /home/git/gitlab/plugins/save_to_file.rb failure (non-zero exit code)
```
[system hooks]: ../system_hooks/system_hooks.md
[webhooks]: ../user/project/integrations/webhooks.md
[highly available]: ./high_availability/README.md
# GitLab Plugin system
---
redirect_to: 'file_hooks.md'
---
> Introduced in GitLab 10.6.
With custom plugins, GitLab administrators can introduce custom integrations
without modifying GitLab's source code.
NOTE: **Note:**
Instead of writing and supporting your own plugin you can make changes
directly to the GitLab source code and contribute back upstream. This way we can
ensure functionality is preserved across versions and covered by tests.
NOTE: **Note:**
Plugins must be configured on the filesystem of the GitLab server. Only GitLab
server administrators will be able to complete these tasks. Explore
[system hooks] or [webhooks] as an option if you do not have filesystem access.
A plugin will run on each event so it's up to you to filter events or projects
within a plugin code. You can have as many plugins as you want. Each plugin will
be triggered by GitLab asynchronously in case of an event. For a list of events
see the [system hooks] documentation.
## Setup
The plugins must be placed directly into the `plugins` directory, subdirectories
will be ignored. There is an
[`example` directory inside `plugins`](https://gitlab.com/gitlab-org/gitlab/tree/master/plugins/examples)
where you can find some basic examples.
Follow the steps below to set up a custom hook:
1. On the GitLab server, navigate to the plugin directory.
For an installation from source the path is usually
`/home/git/gitlab/plugins/`. For Omnibus installs the path is
usually `/opt/gitlab/embedded/service/gitlab-rails/plugins`.
For [highly available] configurations, your hook file should exist on each
application server.
1. Inside the `plugins` directory, create a file with a name of your choice,
without spaces or special characters.
1. Make the hook file executable and make sure it's owned by the Git user.
1. Write the code to make the plugin function as expected. That can be
in any language, and ensure the 'shebang' at the top properly reflects the
language type. For example, if the script is in Ruby the shebang will
probably be `#!/usr/bin/env ruby`.
1. The data to the plugin will be provided as JSON on STDIN. It will be exactly
same as for [system hooks]
That's it! Assuming the plugin code is properly implemented, the hook will fire
as appropriate. The plugins file list is updated for each event, there is no
need to restart GitLab to apply a new plugin.
If a plugin executes with non-zero exit code or GitLab fails to execute it, a
message will be logged to:
- `gitlab-rails/plugin.log` in an Omnibus installation.
- `log/plugin.log` in a source installation.
## Creating plugins
Below is an example that will only response on the event `project_create` and
will inform the admins from the GitLab instance that a new project has been created.
```ruby
# By using the embedded ruby version we eliminate the possibility that our chosen language
# would be unavailable from
#!/opt/gitlab/embedded/bin/ruby
require 'json'
require 'mail'
# The incoming variables are in JSON format so we need to parse it first.
ARGS = JSON.parse(STDIN.read)
# We only want to trigger this plugin on the event project_create
return unless ARGS['event_name'] == 'project_create'
# We will inform our admins of our gitlab instance that a new project is created
Mail.deliver do
from 'info@gitlab_instance.com'
to 'admin@gitlab_instance.com'
subject "new project " + ARGS['name']
body ARGS['owner_name'] + 'created project ' + ARGS['name']
end
```
## Validation
Writing your own plugin can be tricky and it's easier if you can check it
without altering the system. A rake task is provided so that you can use it
in a staging environment to test your plugin before using it in production.
The rake task will use a sample data and execute each of plugin. The output
should be enough to determine if the system sees your plugin and if it was
executed without errors.
```bash
# Omnibus installations
sudo gitlab-rake plugins:validate
# Installations from source
cd /home/git/gitlab
bundle exec rake plugins:validate RAILS_ENV=production
```
Example of output:
```
Validating plugins from /plugins directory
* /home/git/gitlab/plugins/save_to_file.clj succeed (zero exit code)
* /home/git/gitlab/plugins/save_to_file.rb failure (non-zero exit code)
```
[system hooks]: ../system_hooks/system_hooks.md
[webhooks]: ../user/project/integrations/webhooks.md
[highly available]: ./high_availability/README.md
This document was moved to [File Hooks](file_hooks.md), after the Plugins feature was renamed to File Hooks.
# frozen_string_literal: true
module Gitlab
module Plugin
module FileHook
def self.any?
plugin_glob.any? { |entry| File.file?(entry) }
end
......@@ -17,7 +17,7 @@ module Gitlab
def self.execute_all_async(data)
args = files.map { |file| [file, data] }
PluginWorker.bulk_perform_async(args)
FileHookWorker.bulk_perform_async(args)
end
def self.execute(file, data)
......
# frozen_string_literal: true
module Gitlab
class PluginLogger < Gitlab::Logger
class FileHookLogger < Gitlab::Logger
def self.file_name_noext
'plugin'
end
......
namespace :plugins do
namespace :file_hooks do
desc 'Validate existing plugins'
task validate: :environment do
puts 'Validating plugins from /plugins directory'
puts 'Validating file hooks from /plugins directory'
Gitlab::Plugin.files.each do |file|
success, message = Gitlab::Plugin.execute(file, Gitlab::DataBuilder::Push::SAMPLE_DATA)
Gitlab::FileHook.files.each do |file|
success, message = Gitlab::FileHook.execute(file, Gitlab::DataBuilder::Push::SAMPLE_DATA)
if success
puts "* #{file} succeed (zero exit code)."
......
......@@ -7992,6 +7992,12 @@ msgstr ""
msgid "Fetching licenses failed. You are not permitted to perform this action."
msgstr ""
msgid "File Hooks"
msgstr ""
msgid "File Hooks (%{count})"
msgstr ""
msgid "File added"
msgstr ""
......@@ -8001,6 +8007,9 @@ msgstr ""
msgid "File deleted"
msgstr ""
msgid "File hooks are similar to system hooks but are executed as files instead of sending data to a URL."
msgstr ""
msgid "File mode changed from %{a_mode} to %{b_mode}"
msgstr ""
......@@ -8166,6 +8175,9 @@ msgstr ""
msgid "For more information, please review %{link_start_tag}Jaeger's configuration doc%{link_end_tag}"
msgstr ""
msgid "For more information, see the File Hooks documentation."
msgstr ""
msgid "For more information, see the documentation on %{deactivating_usage_ping_link_start}deactivating the usage ping%{deactivating_usage_ping_link_end}."
msgstr ""
......@@ -12203,6 +12215,9 @@ msgstr ""
msgid "No file chosen"
msgstr ""
msgid "No file hooks found."
msgstr ""
msgid "No file selected"
msgstr ""
......
......@@ -28,11 +28,11 @@ describe 'Admin::Hooks' do
end
it 'renders plugins list as well' do
allow(Gitlab::Plugin).to receive(:files).and_return(['foo.rb', 'bar.clj'])
allow(Gitlab::FileHook).to receive(:files).and_return(['foo.rb', 'bar.clj'])
visit admin_hooks_path
expect(page).to have_content('Plugins')
expect(page).to have_content('File Hooks')
expect(page).to have_content('foo.rb')
expect(page).to have_content('bar.clj')
end
......
......@@ -2,11 +2,11 @@
require 'spec_helper'
describe Gitlab::Plugin do
let(:plugin) { Rails.root.join('plugins', 'test.rb') }
let(:tmp_file) { Tempfile.new('plugin-dump') }
describe Gitlab::FileHook do
let(:file_hook) { Rails.root.join('plugins', 'test.rb') }
let(:tmp_file) { Tempfile.new('file_hook-dump') }
let(:plugin_source) do
let(:file_hook_source) do
<<~EOS
#!/usr/bin/env ruby
x = STDIN.read
......@@ -14,13 +14,13 @@ describe Gitlab::Plugin do
EOS
end
context 'with plugins present' do
context 'with file_hooks present' do
before do
File.write(plugin, plugin_source)
File.write(file_hook, file_hook_source)
end
after do
FileUtils.rm(plugin)
FileUtils.rm(file_hook)
end
describe '.any?' do
......@@ -30,13 +30,13 @@ describe Gitlab::Plugin do
end
describe '.files?' do
it 'returns a list of plugins' do
expect(described_class.files).to match_array([plugin.to_s])
it 'returns a list of file_hooks' do
expect(described_class.files).to match_array([file_hook.to_s])
end
end
end
context 'without any plugins' do
context 'without any file_hooks' do
describe '.any?' do
it 'returns false' do
expect(described_class.any?).to be false
......@@ -52,21 +52,21 @@ describe Gitlab::Plugin do
describe '.execute' do
let(:data) { Gitlab::DataBuilder::Push::SAMPLE_DATA }
let(:result) { described_class.execute(plugin.to_s, data) }
let(:result) { described_class.execute(file_hook.to_s, data) }
let(:success) { result.first }
let(:message) { result.last }
before do
File.write(plugin, plugin_source)
File.write(file_hook, file_hook_source)
end
after do
FileUtils.rm(plugin)
FileUtils.rm(file_hook)
end
context 'successful execution' do
before do
File.chmod(0o777, plugin)
File.chmod(0o777, file_hook)
end
after do
......@@ -76,7 +76,7 @@ describe Gitlab::Plugin do
it { expect(success).to be true }
it { expect(message).to be_empty }
it 'ensures plugin received data via stdin' do
it 'ensures file_hook received data via stdin' do
result
expect(File.read(tmp_file.path)).to eq(data.to_json)
......@@ -89,7 +89,7 @@ describe Gitlab::Plugin do
end
context 'non-zero exit' do
let(:plugin_source) do
let(:file_hook_source) do
<<~EOS
#!/usr/bin/env ruby
exit 1
......@@ -97,7 +97,7 @@ describe Gitlab::Plugin do
end
before do
File.chmod(0o777, plugin)
File.chmod(0o777, file_hook)
end
it { expect(success).to be false }
......
......@@ -4745,7 +4745,7 @@ describe Project do
end
it 'returns true when a plugin exists' do
expect(Gitlab::Plugin).to receive(:any?).twice.and_return(true)
expect(Gitlab::FileHook).to receive(:any?).twice.and_return(true)
expect(project.has_active_hooks?(:merge_request_events)).to be_truthy
expect(project.has_active_hooks?).to be_truthy
......
......@@ -2,25 +2,25 @@
require 'spec_helper'
describe PluginWorker do
describe FileHookWorker do
include RepoHelpers
let(:filename) { 'my_plugin.rb' }
let(:filename) { 'my_file_hook.rb' }
let(:data) { { 'event_name' => 'project_create' } }
subject { described_class.new }
describe '#perform' do
it 'executes Gitlab::Plugin with expected values' do
allow(Gitlab::Plugin).to receive(:execute).with(filename, data).and_return([true, ''])
it 'executes Gitlab::FileHook with expected values' do
allow(Gitlab::FileHook).to receive(:execute).with(filename, data).and_return([true, ''])
expect(subject.perform(filename, data)).to be_truthy
end
it 'logs message in case of plugin execution failure' do
allow(Gitlab::Plugin).to receive(:execute).with(filename, data).and_return([false, 'permission denied'])
it 'logs message in case of file_hook execution failure' do
allow(Gitlab::FileHook).to receive(:execute).with(filename, data).and_return([false, 'permission denied'])
expect(Gitlab::PluginLogger).to receive(:error)
expect(Gitlab::FileHookLogger).to receive(:error)
expect(subject.perform(filename, data)).to be_truthy
end
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment