├── .gitignore ├── lib ├── reaktor.rb └── reaktor │ ├── version.rb │ ├── git.rb │ ├── r10k.rb │ ├── utils │ ├── payload_base.rb │ ├── github_payload.rb │ ├── gitlab_payload.rb │ └── stash_payload.rb │ ├── event_jobs.rb │ ├── gitaction.rb │ ├── jobs │ ├── event.rb │ ├── delete_event.rb │ ├── modify_event.rb │ ├── create_event.rb │ ├── controller.rb │ ├── gitlab_controller.rb │ ├── stash_controller.rb │ └── github_controller.rb │ ├── gitaction │ ├── action.rb │ ├── delete_action.rb │ ├── modify_action.rb │ ├── create_action.rb │ └── action_controller.rb │ ├── r10k │ ├── deploy.rb │ └── puppetfile.rb │ ├── notification │ ├── notify.rb │ ├── available_notifiers │ │ ├── slack.rb │ │ └── hipchat.rb │ ├── notifier.rb │ └── active_notifiers │ │ └── hipchat.rb │ ├── git │ ├── repo.rb │ └── workdir.rb │ ├── r10k_app.rb │ ├── envconfig.rb │ └── commandrunner.rb ├── config └── resque.yml ├── spec ├── unit │ └── fixtures │ │ ├── notifiers │ │ └── notifiertest.rb │ │ ├── Puppetfile │ │ ├── deleted_stash.json │ │ ├── deleted.json │ │ ├── deleted_path_as_part_of_branch_name.json │ │ ├── created.json │ │ ├── created_path_as_part_of_branch_name.json │ │ └── created_stash.json ├── notifier_spec.rb ├── stash_payload_spec.rb ├── gitlab_payload_spec.rb ├── spec_helper.rb ├── github_payload_spec.rb └── puppetfile_spec.rb ├── CONTRIBUTORS ├── reaktor-cfg.yml ├── .travis.yml ├── .codeclimate.yml ├── config.ru ├── LICENSE ├── Guardfile ├── Gemfile ├── Capfile ├── Rakefile ├── god └── resque_workers.god ├── README.md └── .rubocop.yml /.gitignore: -------------------------------------------------------------------------------- 1 | fission.iws 2 | .idea 3 | Gemfile.lock 4 | coverage/ 5 | -------------------------------------------------------------------------------- /lib/reaktor.rb: -------------------------------------------------------------------------------- 1 | module Reaktor; end 2 | 3 | require 'reaktor/version' 4 | -------------------------------------------------------------------------------- /lib/reaktor/version.rb: -------------------------------------------------------------------------------- 1 | module Reaktor 2 | VERSION = '0.1.0' 3 | end 4 | -------------------------------------------------------------------------------- /config/resque.yml: -------------------------------------------------------------------------------- 1 | development: localhost:6379 2 | test: localhost:6379 3 | production: test-box-01:6379 4 | -------------------------------------------------------------------------------- /lib/reaktor/git.rb: -------------------------------------------------------------------------------- 1 | module Reaktor 2 | module Git 3 | require 'git/repo' 4 | require 'git/workdir' 5 | end 6 | end 7 | 8 | -------------------------------------------------------------------------------- /lib/reaktor/r10k.rb: -------------------------------------------------------------------------------- 1 | module Reaktor 2 | module R10K 3 | require 'r10k/puppetfile' 4 | require 'r10k/deploy' 5 | end 6 | end 7 | 8 | -------------------------------------------------------------------------------- /spec/unit/fixtures/notifiers/notifiertest.rb: -------------------------------------------------------------------------------- 1 | module Notifiers 2 | class Notifiertest 3 | def initialize 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Ben Thomas 2 | Gregory Saramite 3 | Greg Petras 4 | Greg Saramite 5 | Jeff Quaintance 6 | Nick Huanca 7 | Phil Zimmerman 8 | pzimmer 9 | Shawn Sterling 10 | Tim Hughes 11 | -------------------------------------------------------------------------------- /reaktor-cfg.yml: -------------------------------------------------------------------------------- 1 | --- 2 | address: localhost 3 | port: 4570 4 | servers: 1 5 | max_conns: 1024 6 | max_persistent_conns: 512 7 | timeout: 30 8 | environment: production 9 | pid: tmp/pids/reaktor.pid 10 | log: reaktor.log 11 | daemonize: true 12 | -------------------------------------------------------------------------------- /lib/reaktor/utils/payload_base.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | 3 | module Reaktor 4 | module Utils 5 | class PayloadBase 6 | 7 | attr_reader :branch_name 8 | 9 | def initialize(payload) 10 | @payload = payload 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: ruby 3 | script: 4 | - "bundle exec rspec --format d" 5 | 6 | rvm: 7 | - 1.9.3 8 | - 2.0 9 | - 2.1 10 | - 2.2 11 | 12 | addons: 13 | code_climate: 14 | repo_token: 46e34356104fbee7adc66a371d37939391bbee5c126978ed8cbb330eeab8a5d0 15 | 16 | -------------------------------------------------------------------------------- /spec/unit/fixtures/Puppetfile: -------------------------------------------------------------------------------- 1 | mod 'testmod1', 2 | :git => 'git@testing.com:reaktor/testmod1.git', 3 | :ref => 'RELEASE_1.0.0' 4 | 5 | mod 'testmod2', 6 | :git => 'git@testing.com:reaktor/myproject-testmod2.git', 7 | :ref => 'RELEASE_2.0.10' 8 | 9 | mod 'testmod3', 10 | :git => 'git@testing.com:reaktor/testmod3.git', 11 | :tag => 'RELEASE_10.2.3' 12 | 13 | 14 | -------------------------------------------------------------------------------- /lib/reaktor/event_jobs.rb: -------------------------------------------------------------------------------- 1 | module Reaktor 2 | module EventJobs 3 | require 'jobs/event' 4 | require 'jobs/create_event' 5 | require 'jobs/delete_event' 6 | require 'jobs/modify_event' 7 | require 'jobs/controller' 8 | require 'jobs/github_controller' 9 | require 'jobs/gitlab_controller' 10 | require 'jobs/stash_controller' 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/reaktor/gitaction.rb: -------------------------------------------------------------------------------- 1 | module Reaktor 2 | module GitAction 3 | require 'gitaction/action' 4 | require 'gitaction/create_action' 5 | require 'gitaction/delete_action' 6 | require 'gitaction/modify_action' 7 | require 'gitaction/action_controller' 8 | require 'utils/github_payload' 9 | require 'utils/gitlab_payload' 10 | require 'utils/stash_payload' 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | duplication: 4 | enabled: true 5 | config: 6 | languages: 7 | - ruby 8 | - javascript 9 | - python 10 | - php 11 | fixme: 12 | enabled: true 13 | rubocop: 14 | enabled: true 15 | ratings: 16 | paths: 17 | - "**.inc" 18 | - "**.js" 19 | - "**.jsx" 20 | - "**.module" 21 | - "**.php" 22 | - "**.py" 23 | - "**.rb" 24 | exclude_paths: 25 | - config/**/* 26 | - spec/**/* 27 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift ::File.expand_path(::File.dirname(__FILE__) + '/lib/reaktor') 2 | 3 | ENV['RACK_ENV'] ||= 'production' 4 | 5 | rack_root = ENV['RACK_ROOT'] || "/data/apps/sinatra/reaktor" 6 | reaktor_log = ENV['REAKTOR_LOG'] || "#{rack_root}/reaktor.log" 7 | 8 | require 'sinatra' 9 | require 'resque/server' 10 | require 'logger' 11 | require 'r10k_app' 12 | 13 | LOGGER = Logger.new("#{reaktor_log}") 14 | 15 | run Rack::URLMap.new \ 16 | "/" => Reaktor::R10KApp.new, 17 | "/resque" => Resque::Server.new 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014, 2015 Phil Zimmerman 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /lib/reaktor/jobs/event.rb: -------------------------------------------------------------------------------- 1 | require 'resque' 2 | require 'resque-retry' 3 | require 'redis-objects' 4 | require 'logger' 5 | require 'gitaction' 6 | require 'event_jobs' 7 | 8 | module Reaktor 9 | module Jobs 10 | module Event 11 | extend Resque::Plugins::Retry 12 | # directly enqueue job when lock occurred 13 | @retry_delay = 0 14 | # we don't need the limit because sometimes the lock should be cleared 15 | @retry_limit = 10000 16 | # just catch lock timeouts 17 | @retry_exceptions = [Redis::Lock::LockTimeout] 18 | # logger 19 | @logger ||= Logger.new(STDOUT, Logger::INFO) 20 | end 21 | end 22 | end 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /spec/notifier_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'notification/notifier' 3 | 4 | # need to figure out how best to test this, given it's a singleton 5 | # until then, it's basically commented out 6 | describe Reaktor::Notification::Notifier do 7 | let(:logger) { double('logger').as_null_object } 8 | let(:filename) { "spec/unit/fixtures/notifiers/notifiertest.rb" } 9 | 10 | before :each do 11 | @notifier = Reaktor::Notification::Notifier.instance 12 | end 13 | 14 | #subject { Reaktor::Notification::Notifier.new { include Singleton } } 15 | 16 | #it 'should return class name of Notifiertest' do 17 | # classname = @notifier.getClassFromFile(filename) 18 | # expect(classname).to eql ('Notifiertest') 19 | #end 20 | end 21 | -------------------------------------------------------------------------------- /lib/reaktor/jobs/delete_event.rb: -------------------------------------------------------------------------------- 1 | module Reaktor 2 | module Jobs 3 | class DeleteEvent 4 | include Event 5 | 6 | @queue = :resque_delete 7 | @logger ||= Logger.new(STDOUT, Logger::INFO) 8 | 9 | def self.perform(module_name, branch_name) 10 | @options = { :module_name => module_name, 11 | :branch_name => branch_name, 12 | :logger => @logger 13 | } 14 | Redis::Lock.new(branch_name, :expiration => 30).lock do 15 | # do your stuff here ... 16 | action = Reaktor::GitAction::DeleteAction.new(@options) 17 | action.setup 18 | action.deletePuppetfileBranch 19 | action.cleanup 20 | end 21 | end 22 | end 23 | end 24 | end 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/reaktor/jobs/modify_event.rb: -------------------------------------------------------------------------------- 1 | module Reaktor 2 | module Jobs 3 | class ModifyEvent 4 | include Event 5 | 6 | @queue = :resque_modify 7 | @logger ||= Logger.new(STDOUT, Logger::INFO) 8 | 9 | def self.perform(module_name, branch_name) 10 | @options = { :module_name => module_name, 11 | :branch_name => branch_name, 12 | :logger => @logger 13 | } 14 | Redis::Lock.new(branch_name, :expiration => 60).lock do 15 | # do your stuff here ... 16 | action = Reaktor::GitAction::ModifyAction.new(@options) 17 | action.setup 18 | action.updatePuppetfile 19 | action.cleanup 20 | end 21 | end 22 | end 23 | end 24 | end 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /lib/reaktor/gitaction/action.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | require 'r10k' 3 | require 'git' 4 | require 'notification/notifier' 5 | 6 | module Reaktor 7 | module GitAction 8 | class Action 9 | include R10K::Deploy 10 | attr_accessor :module_name, 11 | :branch_name, 12 | :puppetfile, 13 | :logger 14 | 15 | def initialize(options = {}) 16 | @options = options 17 | if module_name = options[:module_name] 18 | @module_name = module_name 19 | end 20 | if branch_name = options[:branch_name] 21 | @branch_name = branch_name 22 | end 23 | if logger = options[:logger] 24 | @logger = logger 25 | else 26 | @logger = Logger.new(STDOUT) 27 | end 28 | end 29 | end 30 | end 31 | end 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | ## Uncomment and set this to only include directories you want to watch 5 | # directories %w(app lib config test spec features) \ 6 | # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")} 7 | 8 | ## Note: if you are using the `directories` clause above and you are not 9 | ## watching the project directory ('.'), then you will want to move 10 | ## the Guardfile to a watched dir and symlink it back, e.g. 11 | # 12 | # $ mkdir config 13 | # $ mv Guardfile config/ 14 | # $ ln -s config/Guardfile . 15 | # 16 | # and, you'll have to watch "config/Guardfile" instead of "Guardfile" 17 | 18 | guard 'rake', :task => 'test' do 19 | watch(%r{^lib/.+$}) 20 | watch(%r{^spec/.+$}) 21 | end 22 | -------------------------------------------------------------------------------- /lib/reaktor/gitaction/delete_action.rb: -------------------------------------------------------------------------------- 1 | module Reaktor 2 | module GitAction 3 | class DeleteAction < Action 4 | def initialize(options = {}) 5 | super(options) 6 | @puppetfile = R10K::Puppetfile.new(self.branch_name, self.module_name, self.logger) 7 | @puppetfile_dir = Git::WorkDir.new(@puppetfile.git_work_dir, @puppetfile.git_url) 8 | logger.info("In #{self}") 9 | end 10 | def setup 11 | logger.info("branch = #{self.branch_name}") 12 | @puppetfile_dir.clone 13 | end 14 | def deletePuppetfileBranch 15 | Notification::Notifier.instance.notification = "Deleting #{branch_name} from puppetfile repo" 16 | @puppetfile_dir.deleteBranch(self.branch_name) 17 | end 18 | def cleanup 19 | @puppetfile_dir.destroy_workdir 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/reaktor/jobs/create_event.rb: -------------------------------------------------------------------------------- 1 | require 'gitaction/create_action' 2 | require 'logger' 3 | 4 | module Reaktor 5 | module Jobs 6 | class CreateEvent 7 | include Event 8 | 9 | @queue = :resque_create 10 | @logger ||= Logger.new(STDOUT, Logger::INFO) 11 | 12 | def self.perform(module_name, branch_name) 13 | @options = { :module_name => module_name, 14 | :branch_name => branch_name, 15 | :logger => @logger 16 | } 17 | Redis::Lock.new(branch_name, :expiration => 300).lock do 18 | # do your stuff here ... 19 | action = Reaktor::GitAction::CreateAction.new(@options) 20 | action.setup 21 | action.updatePuppetFile 22 | action.cleanup 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/reaktor/r10k/deploy.rb: -------------------------------------------------------------------------------- 1 | require 'commandrunner' 2 | 3 | module Reaktor 4 | module R10K 5 | module Deploy 6 | include Reaktor::CommandRunner 7 | 8 | #call capistrano task to deploy env to all masters using r10k 9 | def r10k_deploy_env(branchname) 10 | cap_cmd = ["cap", "update_environment", "-s", "branchname=#{branchname}"] 11 | deploy cap_cmd 12 | end 13 | 14 | #call capistrano task to deploy module to all masters using r10k 15 | def r10k_deploy_module(modulename) 16 | cap_cmd = ["cap", "deploy_module", "-s", "module_name=#{modulename}"] 17 | deploy cap_cmd 18 | end 19 | 20 | def deploy(cap_command) 21 | @cap_command = cap_command 22 | #cmd_runner = Reaktor::CommandRunner.new() 23 | result = execute_cap(@cap_command) 24 | end 25 | 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/reaktor/jobs/controller.rb: -------------------------------------------------------------------------------- 1 | require 'resque' 2 | require 'event_jobs' 3 | require 'gitaction' 4 | require 'logger' 5 | #needed temporarily to test 6 | require 'notification/notifier' 7 | 8 | module Reaktor 9 | module Jobs 10 | class Controller 11 | 12 | attr_reader :created, :deleted, :json 13 | 14 | def initialize(json, logger) 15 | @json = json 16 | @logger ||= Logger.new(STDOUT, Logger::INFO) 17 | end 18 | 19 | ## 20 | # enqueue the event job 21 | # @param event_class - event class [CreateEvent, ModifyEvent, DeleteEvent] 22 | # @param repo_name - name of the repo_name (for the module) 23 | # @param branch_name - name of the branch 24 | def enqueue_event(event_class, repo_name, branch_name) 25 | Resque.enqueue(event_class, repo_name, branch_name) 26 | end 27 | 28 | end 29 | end 30 | end 31 | 32 | 33 | -------------------------------------------------------------------------------- /lib/reaktor/utils/github_payload.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | require 'utils/payload_base' 3 | 4 | module Reaktor 5 | module Utils 6 | class GitHubPayload < Reaktor::Utils::PayloadBase 7 | 8 | attr_reader :branch_name 9 | attr_reader :ref_type 10 | attr_reader :repo_name 11 | attr_reader :created 12 | attr_reader :deleted 13 | 14 | def initialize(payload) 15 | @logger ||= Logger.new(STDOUT, Logger::INFO) 16 | super 17 | parse_json(payload) 18 | end 19 | 20 | def parse_json(payload) 21 | @repo_name = payload['repository']['name'] 22 | repo_ref = payload['ref'] 23 | @created = payload['created'] 24 | @deleted = payload['deleted'] 25 | ref_array = repo_ref.split("/", 3) 26 | @ref_type = ref_array[1] 27 | @branch_name = ref_array[2] 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rake', '10.3.2' 4 | gem 'sinatra', '1.4.5' 5 | gem 'sinatra-contrib', '1.4.2' 6 | gem 'json', '~> 1.8' 7 | gem 'redis', '3.0.7' 8 | gem 'redis-objects', '0.9.1' 9 | gem 'resque', '1.25.2' 10 | gem 'resque-retry', '1.1.4' 11 | gem 'thin', '1.6.2' 12 | gem 'god', '0.13.4' 13 | 14 | # Pin versions for old ruby 15 | if RUBY_VERSION.to_f < 2.0 16 | gem 'net-ssh', '2.9.1' 17 | end 18 | 19 | if RUBY_VERSION.to_f >= 2.2 20 | gem 'guard-rake', '1.0.0' 21 | end 22 | 23 | ## gems for hipchat-api 24 | gem 'hipchat-api', '1.0.6' 25 | gem 'capistrano', '2.15.5' 26 | 27 | group :test do 28 | gem 'rspec', '3.0.0' 29 | gem 'rack-test', '0.6.2' 30 | gem 'jsonlint' 31 | gem 'codeclimate-test-reporter', require: false 32 | gem 'coveralls', require: false 33 | end 34 | 35 | if File.exists? "#{__FILE__}.local" 36 | eval(File.read("#{__FILE__}.local"), binding) 37 | end 38 | # vim:ft=ruby 39 | -------------------------------------------------------------------------------- /Capfile: -------------------------------------------------------------------------------- 1 | 2 | def getPuppetMasters 3 | m_file = ENV['REAKTOR_PUPPET_MASTERS_FILE'] 4 | mastersFile = open(m_file) 5 | mastersFile.readlines 6 | end 7 | 8 | role(:puppet_master) {getPuppetMasters} 9 | desc "for specified branch in puppetfile repo, use r10k to deploy all modules for the specified environment." 10 | task "update_environment", :roles => :puppet_master do 11 | if exists?(:branchname) 12 | #run "r10k -v debug deploy environment #{branchname} -p" 13 | run "r10k deploy environment #{branchname} -p" 14 | else 15 | puts "Please provide a valid git branch name as an argument" 16 | end 17 | end 18 | 19 | desc "Deploy specified module in all environments using r10k" 20 | task "deploy_module", :roles => :puppet_master do 21 | if exists?(:module_name) 22 | #run "r10k -v debug deploy module #{module_name}" 23 | run "r10k deploy module #{module_name}" 24 | else 25 | puts "Please provide a module name to deploy" 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/reaktor/notification/notify.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | require 'notification' 3 | 4 | module Reaktor 5 | module Notification 6 | class Notify 7 | 8 | attr_accessor :message, 9 | :username, 10 | :password, 11 | :token, 12 | :room_id, 13 | :from, 14 | :logger 15 | 16 | def initialize(options = {}) 17 | @options = options 18 | if username = options[:username] 19 | @username = username 20 | end 21 | if password = options[:password] 22 | @password = password 23 | end 24 | if api_token = options[:api_token] 25 | @api_token = api_token 26 | end 27 | if room_id = options[:room_id] 28 | @room_id = room_id 29 | end 30 | if from = options[:from] 31 | @from = from 32 | end 33 | if logger = options[:logger] 34 | @logger = logger 35 | else 36 | @logger ||= Logger.new(STDOUT) 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/unit/fixtures/deleted_stash.json: -------------------------------------------------------------------------------- 1 | { 2 | "changesets": { 3 | "isLastPage": true, 4 | "limit": 500, 5 | "size": 0, 6 | "start": 0, 7 | "values": [] 8 | }, 9 | "refChanges": [ 10 | { 11 | "fromHash": "cdffec80a3c1a6381b547e78dc1e7a0e6540f922", 12 | "refId": "refs/heads/dev_hook_test", 13 | "toHash": "0000000000000000000000000000000000000000", 14 | "type": "DELETE" 15 | } 16 | ], 17 | "repository": { 18 | "forkable": true, 19 | "id": 14, 20 | "name": "hooktest", 21 | "project": { 22 | "description": "Repositories for Puppet", 23 | "id": 21, 24 | "key": "PUP", 25 | "name": "Puppet", 26 | "public": true, 27 | "type": "NORMAL" 28 | }, 29 | "public": false, 30 | "scmId": "git", 31 | "slug": "hooktest", 32 | "state": "AVAILABLE", 33 | "statusMessage": "Available" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /spec/unit/fixtures/deleted.json: -------------------------------------------------------------------------------- 1 | { 2 | "ref": "refs\/heads\/dev_hook_test", 3 | "after": "0000000000000000000000000000000000000000", 4 | "before": "88adbbc742fac0152915555bb062a04ddf63cef7", 5 | "created": false, 6 | "deleted": true, 7 | "forced": true, 8 | "compare": "https:\/\/test-server-01.com\/fylgia\/webhook_test_two\/compare\/88adbbc742fa...000000000000", 9 | "commits": [ 10 | 11 | ], 12 | "head_commit": null, 13 | "repository": { 14 | "id": 1025, 15 | "name": "webhook_test_two", 16 | "url": "https:\/\/test-server-01.com\/fylgia\/webhook_test_two", 17 | "description": "", 18 | "watchers": 0, 19 | "stargazers": 0, 20 | "forks": 0, 21 | "fork": false, 22 | "size": 44, 23 | "owner": { 24 | "name": "fylgia", 25 | "email": null 26 | }, 27 | "private": false, 28 | "open_issues": 0, 29 | "has_issues": true, 30 | "has_downloads": true, 31 | "has_wiki": true, 32 | "language": "Ruby", 33 | "created_at": 1397246026, 34 | "pushed_at": 1400943117, 35 | "master_branch": "master", 36 | "organization": "fylgia" 37 | }, 38 | "pusher": { 39 | "name": "pzimmer", 40 | "email": "pzimmerman.home@gmail.com" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/reaktor/utils/gitlab_payload.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | require 'utils/payload_base' 3 | 4 | module Reaktor 5 | module Utils 6 | class GitLabPayload < Reaktor::Utils::PayloadBase 7 | 8 | attr_reader :branch_name 9 | attr_reader :ref_type 10 | attr_reader :repo_name 11 | attr_reader :created 12 | attr_reader :deleted 13 | 14 | def initialize(payload) 15 | @logger ||= Logger.new(STDOUT, Logger::INFO) 16 | super 17 | parse_json(payload) 18 | end 19 | 20 | def parse_json(payload) 21 | @repo_name = payload['repository']['name'] 22 | repo_ref = payload['ref'] 23 | @created = created?(payload['before']) 24 | @deleted = deleted?(payload['after']) 25 | ref_array = repo_ref.split("/", 3) 26 | @ref_type = ref_array[1] 27 | @branch_name = ref_array[2] 28 | end 29 | 30 | def created?(before_hash) 31 | if before_hash =~ /^0+$/ 32 | true 33 | else 34 | false 35 | end 36 | end 37 | 38 | def deleted?(after_hash) 39 | if after_hash =~ /^0+$/ 40 | true 41 | else 42 | false 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/unit/fixtures/deleted_path_as_part_of_branch_name.json: -------------------------------------------------------------------------------- 1 | { 2 | "ref": "refs\/heads\/myfeature\/dev_hook_test", 3 | "after": "0000000000000000000000000000000000000000", 4 | "before": "88adbbc742fac0152915555bb062a04ddf63cef7", 5 | "created": false, 6 | "deleted": true, 7 | "forced": true, 8 | "compare": "https:\/\/test-server-01.com\/fylgia\/webhook_test_two\/compare\/88adbbc742fa...000000000000", 9 | "commits": [ 10 | 11 | ], 12 | "head_commit": null, 13 | "repository": { 14 | "id": 1025, 15 | "name": "webhook_test_two", 16 | "url": "https:\/\/test-server-01.com\/fylgia\/webhook_test_two", 17 | "description": "", 18 | "watchers": 0, 19 | "stargazers": 0, 20 | "forks": 0, 21 | "fork": false, 22 | "size": 44, 23 | "owner": { 24 | "name": "fylgia", 25 | "email": null 26 | }, 27 | "private": false, 28 | "open_issues": 0, 29 | "has_issues": true, 30 | "has_downloads": true, 31 | "has_wiki": true, 32 | "language": "Ruby", 33 | "created_at": 1397246026, 34 | "pushed_at": 1400943117, 35 | "master_branch": "master", 36 | "organization": "fylgia" 37 | }, 38 | "pusher": { 39 | "name": "pzimmer", 40 | "email": "pzimmerman.home@gmail.com" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/reaktor/gitaction/modify_action.rb: -------------------------------------------------------------------------------- 1 | module Reaktor 2 | module GitAction 3 | class ModifyAction < Action 4 | def initialize(options = {}) 5 | super(options) 6 | @puppetfile = R10K::Puppetfile.new(self.branch_name, self.module_name, self.logger) 7 | @puppetfile_dir = Git::WorkDir.new(@puppetfile.git_work_dir, @puppetfile.git_url) 8 | logger.info("In #{self}") 9 | end 10 | def setup 11 | logger.info("branch = #{self.branch_name}") 12 | @puppetfile_dir.clone 13 | @puppetfile_dir.checkout(self.branch_name) 14 | end 15 | def updatePuppetfile 16 | self.module_name = @puppetfile.get_module_name(self.module_name) 17 | pfile_contents = @puppetfile.update_module_ref(self.module_name, self.branch_name) 18 | @puppetfile.write_new_puppetfile(pfile_contents) 19 | @puppetfile_dir.push(self.branch_name, @puppetfile.git_update_ref_msg) 20 | Notification::Notifier.instance.notification = "r10k deploy module for #{module_name} in progress..." 21 | r10k_deploy_module self.module_name 22 | Notification::Notifier.instance.notification = "r10k deploy module for #{module_name} finished" 23 | end 24 | def cleanup 25 | @puppetfile_dir.destroy_workdir 26 | end 27 | end 28 | end 29 | end 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /lib/reaktor/jobs/gitlab_controller.rb: -------------------------------------------------------------------------------- 1 | require 'event_jobs' 2 | require 'gitaction' 3 | require 'logger' 4 | 5 | module Reaktor 6 | module Jobs 7 | class GitLabController < Controller 8 | 9 | # process the event - enqueue and let the relevant action class 10 | # do the processing 11 | def process_event 12 | logger = @logger 13 | @git_payload = Reaktor::Utils::GitLabPayload.new(@json) 14 | repo_name = @git_payload.repo_name 15 | ref_type = @git_payload.ref_type 16 | branch_name = @git_payload.branch_name 17 | @created = @git_payload.created 18 | @deleted = @git_payload.deleted 19 | 20 | if @created && isBranch(ref_type) 21 | logger.info("Create Event") 22 | enqueue_event(CreateEvent, repo_name, branch_name) 23 | end 24 | 25 | if @deleted && isBranch(ref_type) 26 | logger.info("Delete Event") 27 | enqueue_event(DeleteEvent, repo_name, branch_name) 28 | end 29 | 30 | if !@created && !@deleted 31 | logger.info("Modify Event") 32 | enqueue_event(ModifyEvent, repo_name, branch_name) 33 | end 34 | 35 | end 36 | 37 | def isBranch(refType) 38 | return refType == "heads" 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/reaktor/utils/stash_payload.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | require 'utils/payload_base' 3 | 4 | module Reaktor 5 | module Utils 6 | class StashPayload < Reaktor::Utils::PayloadBase 7 | 8 | attr_reader :branch_name 9 | attr_reader :ref_type 10 | attr_reader :repo_name 11 | attr_reader :created 12 | attr_reader :deleted 13 | 14 | def initialize(payload) 15 | @logger ||= Logger.new(STDOUT, Logger::INFO) 16 | super 17 | parse_json(payload) 18 | end 19 | 20 | def parse_json(payload) 21 | @repo_name = payload['repository']['name'] 22 | repo_ref = payload['refChanges'][0]['refId'] 23 | @created = created?(payload['refChanges'][0]['fromHash']) 24 | @deleted = deleted?(payload['refChanges'][0]['toHash']) 25 | ref_array = repo_ref.split("/", 3) 26 | @ref_type = ref_array[1] 27 | @branch_name = ref_array[2] 28 | end 29 | 30 | def created?(before_hash) 31 | if before_hash =~ /^0+$/ 32 | true 33 | else 34 | false 35 | end 36 | end 37 | 38 | def deleted?(after_hash) 39 | if after_hash =~ /^0+$/ 40 | true 41 | else 42 | false 43 | end 44 | end 45 | 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/reaktor/gitaction/create_action.rb: -------------------------------------------------------------------------------- 1 | module Reaktor 2 | module GitAction 3 | class CreateAction < Action 4 | def initialize(options = {}) 5 | super(options) 6 | @puppetfile = R10K::Puppetfile.new(self.branch_name, self.module_name, self.logger) 7 | @puppetfile_dir = Git::WorkDir.new(@puppetfile.git_work_dir, @puppetfile.git_url) 8 | logger.info("In #{self}") 9 | end 10 | def setup 11 | logger.info("branch = #{self.branch_name}") 12 | @puppetfile_dir.clone 13 | @puppetfile_dir.checkout(self.branch_name) 14 | end 15 | def updatePuppetFile 16 | pfile_contents = @puppetfile.update_module_ref(self.module_name, self.branch_name) 17 | @puppetfile.write_new_puppetfile(pfile_contents) 18 | pushed = @puppetfile_dir.push(self.branch_name, @puppetfile.git_update_ref_msg) 19 | if pushed 20 | Notification::Notifier.instance.notification = "r10k deploy environment for #{branch_name} in progress..." 21 | result = r10k_deploy_env self.branch_name 22 | if result.exited? 23 | Notification::Notifier.instance.notification = "r10k deploy environment for #{branch_name} finished" 24 | end 25 | end 26 | end 27 | def cleanup 28 | @puppetfile_dir.destroy_workdir 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/reaktor/notification/available_notifiers/slack.rb: -------------------------------------------------------------------------------- 1 | #require 'slack-api' 2 | require 'logger' 3 | 4 | ## Dummy slack.rb notifier template. Use as a reference to implement a functional slack notifier 5 | module Notifiers 6 | class Slack 7 | include Singleton 8 | include Observable 9 | 10 | attr_accessor :notification 11 | 12 | def initialize 13 | @notification = nil 14 | @logger ||= Logger.new(STDOUT, Logger::INFO) 15 | env = ENV.to_hash 16 | token = env['REAKTOR_SLACK_TOKEN'] 17 | @room_id = env['REAKTOR_SLACK_ROOM'] 18 | @from = env['REAKTOR_SLACK_FROM'] 19 | @slack = SLACK::API.new(token) 20 | @logger.info("token = #{token}") 21 | # ensure room_id exists 22 | if not slack_room_exist? @room_id 23 | @logger.info("The defined room_id: #{@room_id} does not exist.") 24 | @logger.info("Slack messages cannot be sent until a valid room is defined.") 25 | end 26 | end 27 | 28 | # The callback method for this observer 29 | def update(message) 30 | @logger.info("#{self}: #{message}") 31 | # Implement the slack message handling here 32 | @notification = message 33 | # send the notification to a slack room defined above 34 | end 35 | 36 | def slack_room_exist?(room_name) 37 | #implement slack-specific logic here 38 | end 39 | 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/reaktor/jobs/stash_controller.rb: -------------------------------------------------------------------------------- 1 | require 'event_jobs' 2 | require 'gitaction' 3 | require 'logger' 4 | require 'event_jobs' 5 | 6 | module Reaktor 7 | module Jobs 8 | class StashController < Controller 9 | 10 | ## 11 | # process the event - enqueue and let the relevant action class 12 | # do the processing 13 | # 14 | def process_event 15 | logger = @logger 16 | @git_payload = Reaktor::Utils::StashPayload.new(@json) 17 | repo_name = @git_payload.repo_name 18 | ref_type = @git_payload.ref_type 19 | branch_name = @git_payload.branch_name 20 | @created = @git_payload.created 21 | @deleted = @git_payload.deleted 22 | 23 | #temp for testing 24 | #Notification::Notifier.instance.notification = "branch_name = #{branch_name}" 25 | 26 | if @created && isBranch(ref_type) 27 | logger.info("Create Event") 28 | enqueue_event(CreateEvent, repo_name, branch_name) 29 | end 30 | 31 | if @deleted && isBranch(ref_type) 32 | logger.info("Delete Event") 33 | enqueue_event(DeleteEvent, repo_name, branch_name) 34 | end 35 | 36 | if !@created && !@deleted 37 | logger.info("Modify Event") 38 | enqueue_event(ModifyEvent, repo_name, branch_name) 39 | end 40 | 41 | end 42 | 43 | def isBranch(refType) 44 | return refType == "heads" 45 | end 46 | end 47 | end 48 | end 49 | 50 | 51 | -------------------------------------------------------------------------------- /lib/reaktor/jobs/github_controller.rb: -------------------------------------------------------------------------------- 1 | require 'event_jobs' 2 | require 'gitaction' 3 | require 'logger' 4 | require 'event_jobs' 5 | 6 | module Reaktor 7 | module Jobs 8 | class GitHubController < Controller 9 | 10 | ## 11 | # process the event - enqueue and let the relevant action class 12 | # do the processing 13 | # 14 | def process_event 15 | logger = @logger 16 | @git_payload = Reaktor::Utils::GitHubPayload.new(@json) 17 | repo_name = @git_payload.repo_name 18 | ref_type = @git_payload.ref_type 19 | branch_name = @git_payload.branch_name 20 | @created = @git_payload.created 21 | @deleted = @git_payload.deleted 22 | 23 | #temp for testing 24 | #Notification::Notifier.instance.notification = "branch_name = #{branch_name}" 25 | 26 | if @created && isBranch(ref_type) 27 | logger.info("Create Event") 28 | enqueue_event(CreateEvent, repo_name, branch_name) 29 | end 30 | 31 | if @deleted && isBranch(ref_type) 32 | logger.info("Delete Event") 33 | enqueue_event(DeleteEvent, repo_name, branch_name) 34 | end 35 | 36 | if !@created && !@deleted 37 | logger.info("Modify Event") 38 | enqueue_event(ModifyEvent, repo_name, branch_name) 39 | end 40 | 41 | end 42 | 43 | def isBranch(refType) 44 | return refType == "heads" 45 | end 46 | end 47 | end 48 | end 49 | 50 | 51 | -------------------------------------------------------------------------------- /lib/reaktor/git/repo.rb: -------------------------------------------------------------------------------- 1 | require 'commandrunner' 2 | 3 | # Define a base class for git repositories. 4 | class Reaktor::Git::Repo 5 | include Reaktor::CommandRunner 6 | 7 | # @!attribute [r] git_dir 8 | # @return [String] The path to the git directory (checkoutdir/.git) 9 | attr_reader :git_dir 10 | 11 | private 12 | 13 | # Fetch from the given git remote 14 | # 15 | # @param remote [#to_s] The remote name to fetch from (default is origin) 16 | def fetch(remote = 'origin') 17 | git ['fetch', '--prune', remote], :git_dir => @git_dir 18 | end 19 | 20 | # @param cmd [Array] cmd The arguments for the git prompt 21 | # @param opts [Hash] opts 22 | # 23 | # @option opts [String] :path 24 | # @option opts [String] :git_dir 25 | # @option opts [String] :work_tree 26 | # @option opts [String] :raise_on_fail 27 | # 28 | # @raise [GitFailure] If the executed command exited with a 29 | # nonzero exit code. 30 | # 31 | # @return [String] The git command output 32 | def git(cmd, opts = {}) 33 | #raise_on_fail = opts.fetch(:raise_on_fail, true) 34 | 35 | argv = %w{git} 36 | 37 | if opts[:path] 38 | argv << "--git-dir" << File.join(opts[:path], '.git') 39 | argv << "--work-tree" << opts[:path] 40 | else 41 | if opts[:git_dir] 42 | argv << "--git-dir" << opts[:git_dir] 43 | end 44 | if opts[:work_tree] 45 | argv << "--work-tree" << opts[:work_tree] 46 | end 47 | end 48 | 49 | argv.concat(cmd) 50 | #logger.info("#{self}: git command = #{argv}") 51 | 52 | #result = execute(argv) 53 | execute(argv) 54 | end 55 | end 56 | 57 | 58 | -------------------------------------------------------------------------------- /spec/stash_payload_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'utils/stash_payload' 3 | require 'json' 4 | 5 | describe Reaktor::Utils::StashPayload do 6 | let(:json_created_str) { read_fixture("created_stash.json") } 7 | let(:payload_created) { JSON.load(json_created_str) } 8 | 9 | subject { described_class.new(payload_created) } 10 | 11 | it 'should parse branch_name dev_hook_test' do 12 | branchname = subject.branch_name 13 | expect(branchname).to eql ("dev_hook_test") 14 | end 15 | 16 | it 'created should be true' do 17 | created = subject.created 18 | expect(created).to eql (true) 19 | end 20 | 21 | it 'deleted should be false' do 22 | deleted = subject.deleted 23 | expect(deleted).to eql (false) 24 | end 25 | 26 | it 'repo name should be hooktest' do 27 | repo_name = subject.repo_name 28 | expect(repo_name).to eql ('hooktest') 29 | end 30 | end 31 | 32 | describe Reaktor::Utils::StashPayload do 33 | let(:json_deleted_str) { read_fixture("deleted_stash.json") } 34 | let(:payload_deleted) { JSON.load(json_deleted_str) } 35 | 36 | subject { described_class.new(payload_deleted) } 37 | 38 | it 'should parse branch_name dev_hook_test' do 39 | branchname = subject.branch_name 40 | expect(branchname).to eql ("dev_hook_test") 41 | end 42 | 43 | it 'created should be false' do 44 | created = subject.created 45 | expect(created).to eql (false) 46 | end 47 | 48 | it 'deleted should be true' do 49 | deleted = subject.deleted 50 | expect(deleted).to eql (true) 51 | end 52 | 53 | it 'repo name should be hooktest' do 54 | repo_name = subject.repo_name 55 | expect(repo_name).to eql ('hooktest') 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/gitlab_payload_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'utils/gitlab_payload' 3 | require 'json' 4 | 5 | describe Reaktor::Utils::GitLabPayload do 6 | let(:json_created_str) { read_fixture("created.json") } 7 | let(:payload_created) { JSON.load(json_created_str) } 8 | 9 | subject { described_class.new(payload_created) } 10 | 11 | it 'should parse branch_name dev_hook_test' do 12 | branchname = subject.branch_name 13 | expect(branchname).to eql ("dev_hook_test") 14 | end 15 | 16 | it 'created should be true' do 17 | created = subject.created 18 | expect(created).to eql (true) 19 | end 20 | 21 | it 'deleted should be false' do 22 | deleted = subject.deleted 23 | expect(deleted).to eql (false) 24 | end 25 | 26 | it 'repo name should be webhook_test_two' do 27 | repo_name = subject.repo_name 28 | expect(repo_name).to eql ('webhook_test_two') 29 | end 30 | end 31 | 32 | describe Reaktor::Utils::GitLabPayload do 33 | let(:json_deleted_str) { read_fixture("deleted.json") } 34 | let(:payload_deleted) { JSON.load(json_deleted_str) } 35 | 36 | subject { described_class.new(payload_deleted) } 37 | 38 | it 'should parse branch_name dev_hook_test' do 39 | branchname = subject.branch_name 40 | expect(branchname).to eql ("dev_hook_test") 41 | end 42 | 43 | it 'created should be false' do 44 | created = subject.created 45 | expect(created).to eql (false) 46 | end 47 | 48 | it 'deleted should be true' do 49 | deleted = subject.deleted 50 | expect(deleted).to eql (true) 51 | end 52 | 53 | it 'repo name should be webhook_test_two' do 54 | repo_name = subject.repo_name 55 | expect(repo_name).to eql ('webhook_test_two') 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/unit/fixtures/created.json: -------------------------------------------------------------------------------- 1 | { 2 | "ref": "refs/heads/dev_hook_test", 3 | "after": "88adbbc742fac0152915555bb062a04ddf63cef7", 4 | "before": "0000000000000000000000000000000000000000", 5 | "created": true, 6 | "deleted": false, 7 | "forced": true, 8 | "base_ref": "refs/heads/master", 9 | "compare": "https://test-server-01.com/webhook_test_two/compare/dev_hook_test", 10 | "commits": [ 11 | 12 | ], 13 | "head_commit": { 14 | "id": "88adbbc742fac0152915555bb062a04ddf63cef7", 15 | "distinct": true, 16 | "message": "new files", 17 | "timestamp": "2014-04-11T19:54:04+00:00", 18 | "url": "https://test-server-01.com/webhook_test_two/commit/88adbbc742fac0152915555bb062a04ddf63cef7", 19 | "author": { 20 | "name": "pzimmer", 21 | "email": "pzimmerman.home.com", 22 | "username": "pzimmer" 23 | }, 24 | "committer": { 25 | "name": "pzimmer", 26 | "email": "pzimmerman.home@gmail.com", 27 | "username": "pzimmer" 28 | }, 29 | "added": [ 30 | ".unit.yml", 31 | "Gemfile", 32 | "Modulefile", 33 | "Rakefile", 34 | "spec/unit/hiera.yaml", 35 | "spec/spec_helper.rb" 36 | ], 37 | "removed": [ 38 | 39 | ], 40 | "modified": [ 41 | 42 | ] 43 | }, 44 | "repository": { 45 | "id": 1025, 46 | "name": "webhook_test_two", 47 | "url": "https://test-server-01.com/webhook_test_two", 48 | "description": "", 49 | "watchers": 0, 50 | "stargazers": 0, 51 | "forks": 0, 52 | "fork": false, 53 | "size": 48, 54 | "owner": { 55 | "name": "fylgia", 56 | "email": null 57 | }, 58 | "private": false, 59 | "open_issues": 0, 60 | "has_issues": true, 61 | "has_downloads": true, 62 | "has_wiki": true, 63 | "language": "Ruby", 64 | "created_at": 1397246026, 65 | "pushed_at": 1400944913, 66 | "master_branch": "master", 67 | "organization": "fylgia" 68 | }, 69 | "pusher": { 70 | "name": "pzimmer", 71 | "email": "pzimmerman.home@gmail.com" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /spec/unit/fixtures/created_path_as_part_of_branch_name.json: -------------------------------------------------------------------------------- 1 | { 2 | "ref": "refs/heads/myfeature/dev_hook_test", 3 | "after": "88adbbc742fac0152915555bb062a04ddf63cef7", 4 | "before": "0000000000000000000000000000000000000000", 5 | "created": true, 6 | "deleted": false, 7 | "forced": true, 8 | "base_ref": "refs/heads/master", 9 | "compare": "https://test-server-01.com/webhook_test_two/compare/myfeature/dev_hook_test", 10 | "commits": [ 11 | 12 | ], 13 | "head_commit": { 14 | "id": "88adbbc742fac0152915555bb062a04ddf63cef7", 15 | "distinct": true, 16 | "message": "new files", 17 | "timestamp": "2014-04-11T19:54:04+00:00", 18 | "url": "https://test-server-01.com/webhook_test_two/commit/88adbbc742fac0152915555bb062a04ddf63cef7", 19 | "author": { 20 | "name": "pzimmer", 21 | "email": "pzimmerman.home.com", 22 | "username": "pzimmer" 23 | }, 24 | "committer": { 25 | "name": "pzimmer", 26 | "email": "pzimmerman.home@gmail.com", 27 | "username": "pzimmer" 28 | }, 29 | "added": [ 30 | ".unit.yml", 31 | "Gemfile", 32 | "Modulefile", 33 | "Rakefile", 34 | "spec/unit/hiera.yaml", 35 | "spec/spec_helper.rb" 36 | ], 37 | "removed": [ 38 | 39 | ], 40 | "modified": [ 41 | 42 | ] 43 | }, 44 | "repository": { 45 | "id": 1025, 46 | "name": "webhook_test_two", 47 | "url": "https://test-server-01.com/webhook_test_two", 48 | "description": "", 49 | "watchers": 0, 50 | "stargazers": 0, 51 | "forks": 0, 52 | "fork": false, 53 | "size": 48, 54 | "owner": { 55 | "name": "fylgia", 56 | "email": null 57 | }, 58 | "private": false, 59 | "open_issues": 0, 60 | "has_issues": true, 61 | "has_downloads": true, 62 | "has_wiki": true, 63 | "language": "Ruby", 64 | "created_at": 1397246026, 65 | "pushed_at": 1400944913, 66 | "master_branch": "master", 67 | "organization": "fylgia" 68 | }, 69 | "pusher": { 70 | "name": "pzimmer", 71 | "email": "pzimmerman.home@gmail.com" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/reaktor/gitaction/action_controller.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | 3 | module Reaktor 4 | module GitAction 5 | class ActionController 6 | 7 | attr_reader :created, :deleted, :json 8 | 9 | def initialize(json) 10 | @json = json 11 | @logger ||= Logger.new(STDOUT, Logger::INFO) 12 | end 13 | 14 | ## 15 | # action_type returns an instance of an action class. 16 | # If no action is registered for the GitAction action then `nil` is returned. 17 | # 18 | # @return [Reaktor::Action] subclass instance suitable to perform necessary actions 19 | # , or `nil`. 20 | def action_type 21 | logger = @logger 22 | repo_name = @json['repository']['name'] 23 | repo_ref = @json['ref'] 24 | @created = @json['created'] 25 | @deleted = @json['deleted'] 26 | ref_array = repo_ref.split("/") 27 | ref_type = ref_array[1] 28 | branch_name = ref_array[2] 29 | 30 | if @created && isBranch(ref_type) 31 | logger.info("Create Action") 32 | options = { :module_name => repo_name, 33 | :branch_name => branch_name, 34 | :logger => logger 35 | } 36 | action = Reaktor::GitAction::CreateAction.new(options) 37 | return action 38 | end 39 | 40 | if @deleted && isBranch(ref_type) 41 | logger.info("Delete Action") 42 | options = { :branch_name => branch_name, 43 | :logger => logger 44 | } 45 | action = Reaktor::GitAction::DeleteAction.new(options) 46 | return action 47 | end 48 | 49 | if !@created && !@deleted 50 | logger.info("Modify Action") 51 | options = { :module_name => repo_name, 52 | :branch_name => branch_name, 53 | :logger => logger 54 | } 55 | action = Reaktor::GitAction::ModifyAction.new(options) 56 | return action 57 | end 58 | 59 | end 60 | 61 | def isBranch(refType) 62 | return refType == "heads" 63 | end 64 | end 65 | end 66 | end 67 | 68 | -------------------------------------------------------------------------------- /lib/reaktor/notification/available_notifiers/hipchat.rb: -------------------------------------------------------------------------------- 1 | require 'hipchat-api' 2 | require 'logger' 3 | 4 | module Notifiers 5 | class Hipchat 6 | include Singleton 7 | include Observable 8 | 9 | attr_accessor :notification 10 | 11 | def initialize 12 | @notification = nil 13 | @logger ||= Logger.new(STDOUT, Logger::INFO) 14 | env = ENV.to_hash 15 | token = env['REAKTOR_HIPCHAT_TOKEN'] 16 | @room_id = env['REAKTOR_HIPCHAT_ROOM'] 17 | @from = env['REAKTOR_HIPCHAT_FROM'] 18 | hipchat_url = env['REAKTOR_HIPCHAT_URL'] 19 | if hipchat_url.nil? or hipchat_url.empty? 20 | @hipchat = HipChat::API.new(token) 21 | else 22 | @hipchat = HipChat::API.new(token, hipchat_url) 23 | end 24 | @logger.info("token = #{token}") 25 | # ensure room_id exists 26 | if not room_exist? @room_id 27 | @logger.info("The defined room_id: #{@room_id} does not exist.") 28 | @logger.info("Hipchat messages cannot be sent until a valid room is defined.") 29 | end 30 | end 31 | 32 | # The callback method for this observer 33 | def update(message) 34 | @logger.info("#{self}: #{message}") 35 | # Hipchat has message max length of 10K chars. If message size is 36 | # greater than 10K, need to get out the machete. Using last 5K 37 | # chars of message in this instance 38 | if message.length > 10000 39 | message = message.split(//).last(5000).join("") 40 | end 41 | @notification = message 42 | @hipchat.rooms_message(@room_id, 43 | @from, 44 | @notification, 45 | notify = 0, 46 | color = 'purple', 47 | message_format = 'html') 48 | end 49 | 50 | def room_exist?(room_name) 51 | rooms = @hipchat.rooms_list 52 | room = rooms['rooms'].select { |x| x['name'] == room_name } 53 | @logger.info("room = #{room}") 54 | room.empty? ? false : true 55 | end 56 | 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/reaktor/notification/notifier.rb: -------------------------------------------------------------------------------- 1 | require 'observer' 2 | require 'singleton' 3 | 4 | module Reaktor 5 | module Notification 6 | class Notifier 7 | include Singleton 8 | include Observable 9 | 10 | attr_accessor :notification 11 | 12 | def initialize 13 | # if user wants to override where to look for custom notifiers 14 | @notifier_dir = ENV['REAKTOR_NOTIFIERS_DIR'] 15 | # if no custom notifier dir found, use default 16 | current_dir = Dir.pwd 17 | @notifier_dir ||= "#{current_dir}/lib/reaktor/notification/active_notifiers" 18 | loadNotifiersAsObservers(@notifier_dir) 19 | end 20 | 21 | # load all notifiers and add them as observers 22 | # @param notifier_dir - directory where notifier ruby files live 23 | def loadNotifiersAsObservers(notifier_dir) 24 | # dynamically load each notifier found 25 | Dir.glob("#{notifier_dir}/**/*.rb").each{|f| require f} 26 | @notification = nil 27 | @logger ||= Logger.new(STDOUT, Logger::INFO) 28 | @logger.info("NOTIFIER_DIR = #{notifier_dir}") 29 | Dir.glob("#{notifier_dir}/*.rb").each do |f| 30 | @logger.debug("loading notifier from file: #{f}") 31 | clazz = getClassFromFile(f) 32 | self.add_observer(clazz.instance) 33 | end 34 | end 35 | 36 | # get the class object from the file it's declared in 37 | # @param filename - the ruby class file 38 | # returns the class object name 39 | def getClassFromFile(filename) 40 | notifier_base_name = File.basename(filename, ".rb") 41 | instance_name = notifier_base_name.capitalize 42 | # split on "::" and iterate the results, returning the last element found, 43 | # which is the class name we're after 44 | "Notifiers::#{instance_name}".split('::').inject(Object) {|o,c| o.const_get c} 45 | end 46 | 47 | # notification method which triggers the observers 48 | # @param message - the message string to send to all observers 49 | def notification=(message) 50 | @notification = message 51 | changed 52 | notify_observers(message) 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/reaktor/r10k_app.rb: -------------------------------------------------------------------------------- 1 | # web app libraries 2 | require 'json' 3 | require 'sinatra/base' 4 | require 'sinatra/config_file' 5 | 6 | require 'gitaction' 7 | require 'event_jobs' 8 | require 'r10k' 9 | 10 | require 'logger' 11 | 12 | module Reaktor 13 | class R10KApp < Sinatra::Base 14 | 15 | rack_root = ENV['RACK_ROOT'] || "/data/apps/sinatra/reaktor" 16 | reaktor_log = ENV['REAKTOR_LOG'] || "#{rack_root}/reaktor.log" 17 | logger ||= Logger.new("#{reaktor_log}", Logger::INFO) 18 | 19 | # endpoint for github/github enterprise payloads 20 | post '/github_payload' do 21 | jsonPayload = json 22 | logger.info("github payload = #{jsonPayload}") 23 | github_controller = Reaktor::Jobs::GitHubController.new(jsonPayload, @logger) 24 | github_controller.process_event 25 | end 26 | 27 | post '/gitlab_payload' do 28 | logger.info("gitlab payload = #{json}") 29 | gitlab_controller = Reaktor::Jobs::GitLabController.new(json, @logger) 30 | gitlab_controller.process_event 31 | end 32 | 33 | post '/stash_payload' do 34 | jsonPayload = json 35 | logger.info("stash payload = #{jsonPayload}") 36 | stash_controller = Reaktor::Jobs::StashController.new(jsonPayload, @logger) 37 | stash_controller.process_event 38 | end 39 | 40 | ## 41 | # Log a simple INFO message using the request.path_info method. 42 | def log(msg) 43 | logger.info "[#{request.path_info}] #{msg}" 44 | end 45 | 46 | def response_headers 47 | @response_headers ||= {'Content-Type' => 'application/json'} 48 | end 49 | 50 | ## 51 | # Obtain the payload from the request. If there is Form data, we expect 52 | # this as a string in the parameter named payload. If there is no form 53 | # data, then we expect to the payload to be the body. 54 | def payload 55 | if request.form_data? 56 | request['payload'] 57 | else 58 | request.body.rewind 59 | request.body.read 60 | end 61 | end 62 | 63 | ## 64 | # Read and parse the JSON payload. This assumes the payload method 65 | # returns a JSON string. 66 | def json 67 | @json ||= JSON.load(payload) 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # figure out where we are being loaded from 2 | if $LOADED_FEATURES.grep(/spec\/spec_helper\.rb/).any? 3 | begin 4 | raise "foo" 5 | rescue => e 6 | puts <<-MSG 7 | =================================================== 8 | It looks like spec_helper.rb has been loaded 9 | multiple times. Normalize the require to: 10 | 11 | require 'spec_helper' 12 | 13 | Things like File.join and File.expand_path will 14 | cause it to be loaded multiple times. 15 | 16 | Loaded this time from: 17 | 18 | #{e.backtrace.join("\n ")} 19 | =================================================== 20 | MSG 21 | end 22 | end 23 | 24 | require 'rspec' 25 | #require 'rack/test' 26 | require 'json' 27 | require 'yaml' 28 | 29 | require "codeclimate-test-reporter" 30 | CodeClimate::TestReporter.start 31 | require 'coveralls' 32 | Coveralls.wear! 33 | 34 | PROJECT_ROOT = File.expand_path("../../lib/reaktor", __FILE__) 35 | SPEC_ROOT = File.expand_path("../lib/reaktor", __FILE__) 36 | 37 | $LOAD_PATH.unshift(PROJECT_ROOT).unshift(SPEC_ROOT) 38 | 39 | module Test 40 | module Methods 41 | def read_fixture(name) 42 | #f = File.open("spec/unit/fixtures/created.json", "r") 43 | #f.each_line do |line| 44 | # puts line 45 | #end 46 | #f.close 47 | File.read(File.join(File.expand_path("..", __FILE__), "unit", "fixtures", name)) 48 | end 49 | end 50 | end 51 | 52 | #require 'shared-contexts' 53 | 54 | # FIXME much of this configuration is duplicated in the :environment task in 55 | # the Rakefile 56 | RSpec.configure do |config| 57 | ENV['RACK_ENV'] = 'test' 58 | include Test::Methods 59 | 60 | # config.mock_with :rspec 61 | 62 | #config.before :all do 63 | # Reaktor::Envconfig.init_environment(ENV['RACK_ENV']) 64 | #end 65 | end 66 | 67 | #RSpec::Matchers.define :have_status do |expected_status| 68 | # match do |actual| 69 | # actual.status == expected_status 70 | # end 71 | # description do 72 | # "have a #{expected_status} status" 73 | # end 74 | # failure_message_for_should do |actual| 75 | # <<-EOM 76 | #expected the response to have a #{expected_status} status but got a #{actual.status}. 77 | #Errors: 78 | #{actual.errors} 79 | # EOM 80 | # end 81 | #end 82 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift("#{File.expand_path('..', __FILE__)}/lib/reaktor") 2 | #$LOAD_PATH.unshift(File.expand_path('../lib', __FILE__)) 3 | require 'resque/tasks' 4 | require 'envconfig' 5 | require 'event_jobs' 6 | 7 | begin 8 | require 'jsonlint/rake_task' 9 | JsonLint::RakeTask.new do |t| 10 | t.paths = %w( 11 | spec/**/*.json 12 | ) 13 | end 14 | rescue LoadError 15 | end 16 | 17 | begin 18 | require 'rspec/core/rake_task' 19 | RSpec::Core::RakeTask.new(:spec) do |t| 20 | t.rspec_opts = '--format documentation' 21 | end 22 | rescue LoadError 23 | end 24 | 25 | task :default => [:help] 26 | 27 | ENV['RACK_ENV'] ||= 'production' 28 | desc "Start the rack server`" 29 | task :start do 30 | Rake::Task["init_environment"].invoke 31 | exec "thin -C reaktor-cfg.yml -R config.ru start" 32 | end 33 | 34 | desc "Stop the rack server" 35 | task :stop do 36 | exec "thin stop -C reaktor-cfg.yml" 37 | end 38 | 39 | desc "Start the resque workers and god watchers" 40 | task :start_workers do 41 | exec "god -c god/resque_workers.god" 42 | end 43 | 44 | desc "Stop the resque workers and god watchers" 45 | task :stop_workers do 46 | exec "god terminate god/resque_workers.god" 47 | end 48 | 49 | desc "initialize the environment configuration" 50 | task :init_environment do 51 | Reaktor::Envconfig.init_environment(ENV['RACK_ENV']) 52 | end 53 | 54 | desc "Get the resque config settings for env" 55 | task :load_config do 56 | config = Reaktor::Envconfig.dbconfig(ENV['RACK_ENV']) 57 | server = config[ENV['RACK_ENV']] 58 | puts "server = #{server}" 59 | end 60 | 61 | 62 | desc "Populate CONTRIBUTORS file" 63 | task :contributors do 64 | system("git log --format='%aN' | sort -u > CONTRIBUTORS") 65 | end 66 | 67 | desc "Check syntax of ruby files" 68 | task :syntax do 69 | Dir['spec/**/*.rb','lib/**/*.rb'].each do |ruby_file| 70 | sh "ruby -c #{ruby_file}" unless ruby_file =~ /spec\/fixtures/ 71 | end 72 | #Dir['**/*.json'].each do |json_file| 73 | # JSON.parse(File.open("#{json_file}").read) 74 | #end 75 | end 76 | 77 | 78 | desc "Run full test suite" 79 | task :test => [ 80 | :jsonlint, 81 | :syntax, 82 | :spec, 83 | ] 84 | 85 | desc "Display the list of available rake tasks" 86 | task :help do 87 | system("rake -T") 88 | end 89 | 90 | -------------------------------------------------------------------------------- /lib/reaktor/notification/active_notifiers/hipchat.rb: -------------------------------------------------------------------------------- 1 | require 'hipchat-api' 2 | require 'logger' 3 | 4 | module Notifiers 5 | class Hipchat 6 | include Singleton 7 | include Observable 8 | 9 | attr_accessor :notification 10 | 11 | def initialize 12 | @notification = nil 13 | @logger ||= Logger.new(STDOUT, Logger::INFO) 14 | env = ENV.to_hash 15 | token = env['REAKTOR_HIPCHAT_TOKEN'] 16 | @room_id = env['REAKTOR_HIPCHAT_ROOM'] 17 | @from = env['REAKTOR_HIPCHAT_FROM'] 18 | hipchat_url = env['REAKTOR_HIPCHAT_URL'] 19 | if hipchat_url.nil? or hipchat_url.empty? 20 | @hipchat = HipChat::API.new(token) 21 | else 22 | @hipchat = HipChat::API.new(token, hipchat_url) 23 | end 24 | @logger.info("token = #{token}") 25 | # ensure room_id exists 26 | if not room_exist? @room_id 27 | @logger.info("The defined room_id: #{@room_id} does not exist.") 28 | @logger.info("Hipchat messages cannot be sent until a valid room is defined.") 29 | end 30 | end 31 | 32 | # The callback method for this observer 33 | def update(message) 34 | @logger.info("#{self}: #{message}") 35 | # Hipchat has message max length of 10K chars. If message size is 36 | # greater than 10K, need to get out the machete. Using last 5K 37 | # chars of message in this instance 38 | if message.length > 10000 39 | message = message.split(//).last(5000).join("") 40 | end 41 | @notification = message 42 | @hipchat.rooms_message(@room_id, 43 | @from, 44 | @notification, 45 | notify = 0, 46 | color = 'purple', 47 | message_format = 'html') 48 | end 49 | 50 | def room_exist?(room_name) 51 | rooms = @hipchat.rooms_list 52 | if rooms['error'] and rooms['error']['code'] !~ /^2\d+/ 53 | @logger.error "#{rooms['error']['code']} - #{rooms['error']['message']}" 54 | false 55 | end 56 | room = rooms['rooms'].select { |x| x['name'] == room_name } 57 | @logger.info("room = #{room}") 58 | room.empty? ? false : true 59 | end 60 | 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/reaktor/r10k/puppetfile.rb: -------------------------------------------------------------------------------- 1 | #require 'commandrunner' 2 | 3 | module Reaktor 4 | module R10K 5 | class Puppetfile 6 | GIT_URL = ENV['PUPPETFILE_GIT_URL'] 7 | attr_accessor :branch, :mod, :git_url, :logger, :git_work_dir, :git_update_ref_msg 8 | 9 | def initialize(branch, mod, logger) 10 | logger.info("In #{self}") 11 | @branch = branch 12 | @mod = mod 13 | @git_url = GIT_URL 14 | @logger = logger 15 | @now = Time.now.strftime('%Y%m%d%H%M%S%L') 16 | @git_work_dir = File.expand_path("/var/tmp/puppetfile_repo_#{@now}") 17 | @git_dir = "#{@git_work_dir}/.git" 18 | @git_cmd = "git --git-dir=#{@git_dir} --work-tree=#{@git_work_dir}" 19 | @git_update_ref_msg = "changing :ref for #{mod} to #{branch}" 20 | end 21 | 22 | def loadFile 23 | File.read("#{@git_work_dir}/Puppetfile") 24 | end 25 | 26 | # Retrieve the module's name in Puppetfile. Using Puppetfile as source of truth for the module names 27 | # 28 | # @param repo_name - The repo name assiociated with the module 29 | def get_module_name(repo_name) 30 | pfile = loadFile 31 | regex = /mod ["'](\w*)["'],\s*$\n^(\s*):git\s*=>\s*["'].*#{repo_name}.git["'],+(\s*):ref\s*=>\s*['"](\w+|\w+\.\d+\.\d+)['"]$/ 32 | new_contents = pfile.match(regex) 33 | if new_contents 34 | module_name = new_contents[1] 35 | else 36 | logger.info("ERROR - VERIFY YOU PUPPETFILE SYNTAX - Repository: #{repo_name} - Git url: #{@git_url}") 37 | end 38 | module_name 39 | end 40 | 41 | 42 | # update the module ref in Puppetfile 43 | # 44 | # @param module_name - The module to change the ref for 45 | # @param branchname - The ref to change for the module 46 | def update_module_ref(module_name, branchname) 47 | pfile = loadFile 48 | regex = /(#{module_name}(\.git)+['"],)+(\s*):ref\s*=>\s*['"](\w+|\w+\.\d+\.\d+)['"]/m 49 | pfile.gsub!(regex, """\\1\\3:ref => '#{branchname}'""".strip) 50 | end 51 | 52 | def write_new_puppetfile(contents) 53 | if contents 54 | puppetfile = File.open("#{@git_work_dir}/Puppetfile", "w") 55 | puppetfile.write(contents) 56 | puppetfile.close() 57 | p_file_after_write = `cat #{@git_work_dir}/Puppetfile` 58 | logger.info("modified puppetfile: #{p_file_after_write}") 59 | else 60 | logger.info("Wont create empty Puppetfile") 61 | end 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/github_payload_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'utils/github_payload' 3 | require 'json' 4 | 5 | describe Reaktor::Utils::GitHubPayload do 6 | let(:json_created_str) { read_fixture("created.json") } 7 | let(:payload_created) { JSON.load(json_created_str) } 8 | 9 | subject { described_class.new(payload_created) } 10 | 11 | it 'should parse branch_name dev_hook_test' do 12 | branchname = subject.branch_name 13 | expect(branchname).to eql ("dev_hook_test") 14 | end 15 | 16 | it 'created should be true' do 17 | created = subject.created 18 | expect(created).to eql (true) 19 | end 20 | 21 | it 'deleted should be false' do 22 | deleted = subject.deleted 23 | expect(deleted).to eql (false) 24 | end 25 | 26 | it 'repo name should be webhook_test_two' do 27 | repo_name = subject.repo_name 28 | expect(repo_name).to eql ('webhook_test_two') 29 | end 30 | end 31 | 32 | describe Reaktor::Utils::GitHubPayload do 33 | let(:json_created_str) { read_fixture("created_path_as_part_of_branch_name.json") } 34 | let(:payload_created) { JSON.load(json_created_str) } 35 | 36 | subject { described_class.new(payload_created) } 37 | 38 | it 'should parse branch_name myfeature/dev_hook_test' do 39 | branchname = subject.branch_name 40 | expect(branchname).to eql ("myfeature/dev_hook_test") 41 | end 42 | end 43 | 44 | describe Reaktor::Utils::GitHubPayload do 45 | let(:json_deleted_str) { read_fixture("deleted.json") } 46 | let(:payload_deleted) { JSON.load(json_deleted_str) } 47 | 48 | subject { described_class.new(payload_deleted) } 49 | 50 | it 'should parse branch_name dev_hook_test' do 51 | branchname = subject.branch_name 52 | expect(branchname).to eql ("dev_hook_test") 53 | end 54 | 55 | it 'created should be false' do 56 | created = subject.created 57 | expect(created).to eql (false) 58 | end 59 | 60 | it 'deleted should be true' do 61 | deleted = subject.deleted 62 | expect(deleted).to eql (true) 63 | end 64 | 65 | it 'repo name should be webhook_test_two' do 66 | repo_name = subject.repo_name 67 | expect(repo_name).to eql ('webhook_test_two') 68 | end 69 | end 70 | 71 | describe Reaktor::Utils::GitHubPayload do 72 | let(:json_deleted_str) { read_fixture("deleted_path_as_part_of_branch_name.json") } 73 | let(:payload_deleted) { JSON.load(json_deleted_str) } 74 | 75 | subject { described_class.new(payload_deleted) } 76 | 77 | it 'should parse branch_name myfeature/dev_hook_test' do 78 | branchname = subject.branch_name 79 | expect(branchname).to eql ("myfeature/dev_hook_test") 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/reaktor/git/workdir.rb: -------------------------------------------------------------------------------- 1 | require 'git' 2 | 3 | class Reaktor::Git::WorkDir < Reaktor::Git::Repo 4 | 5 | #include Reaktor::Logging 6 | 7 | # @param path [String] 8 | # @param url [String] 9 | def initialize(path, url) 10 | 11 | @path = path 12 | @url = url 13 | @git_dir = File.join(@path, '.git') 14 | @logger ||= Logger.new(STDOUT, Logger::INFO) 15 | 16 | end 17 | 18 | # clone the repo represented by @url at @path 19 | def clone 20 | if not dir_exist? 21 | git ["clone", @url, @path] 22 | fetch 23 | end 24 | end 25 | 26 | # check out the given ref 27 | # 28 | # @param branchname - The git branch to check out 29 | def checkout(branchname) 30 | fetch 31 | if not branch_exist?(branchname) 32 | @logger.info("branch #{branchname} doesn't exist. Create it now...") 33 | git ["checkout", "-b", branchname], :path => @path 34 | else 35 | @logger.info("branch #{branchname} exists. Check it out now...") 36 | git ["checkout", "--force", branchname], :path => @path 37 | git ["pull", "origin", branchname], :path => @path 38 | git ["merge", "origin/production", "-X ours", "--no-edit"], :path => @path 39 | end 40 | end 41 | 42 | # delete the given branch 43 | # 44 | # @param branchname - The git branch to delete 45 | def deleteBranch(branchname) 46 | git ["push", "origin", ":#{branchname}"], :path => @path 47 | end 48 | 49 | # push changes in working dir to remote 50 | # 51 | # @param branchname - The git branch to push 52 | # @param commit_msg - The git commit message for this push 53 | def push(branchname, commit_msg) 54 | commit commit_msg 55 | pushed = git ["push", "origin", branchname], :path => @path 56 | if pushed.exitstatus == 0 57 | true 58 | else 59 | false 60 | end 61 | end 62 | # commit the current changes for this working dir 63 | def commit(commit_msg) 64 | #result = git ["commit", "-a", "-m", "\"#{commit_msg}\""], :path => @path 65 | git ["commit", "-a", "-m", "\"#{commit_msg}\""], :path => @path 66 | end 67 | 68 | # Does a branch exist on the remote? 69 | # @return [true, false] 70 | def branch_exist?(branchname) 71 | git_remote_branch_cmd = "git ls-remote --heads #{@url} | cut -d '/' -f3" 72 | result = `#{git_remote_branch_cmd}` 73 | @logger.info("git remote branch cmd result: #{result}") 74 | branches = result.split 75 | @logger.info("branches after split: #{branches}") 76 | if branches.include? branchname 77 | return true 78 | else 79 | return false 80 | end 81 | end 82 | 83 | def destroy_workdir 84 | destroy_out = `rm -rf #{@path}` 85 | @logger.info("cleaning up: #{destroy_out}") 86 | end 87 | 88 | # Does a directory exist where we expect a working dir to be? 89 | # @return [true, false] 90 | def dir_exist? 91 | File.directory? @path 92 | end 93 | 94 | end 95 | 96 | -------------------------------------------------------------------------------- /lib/reaktor/envconfig.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | require 'resque' 3 | require 'resque/tasks' 4 | 5 | module Reaktor 6 | class Envconfig 7 | # Initialize application logging, resque workers, and the resque config. 8 | # 9 | # @param rack_env [String] The rack environment, [development, test, production] 10 | def self.init_environment(rack_env) 11 | if rack_env.nil? or rack_env.empty? 12 | raise "Cannot init environment: RACK_ENV must be set" 13 | end 14 | init_logging(rack_env) 15 | init_workers(rack_env) 16 | end 17 | 18 | # Initialize log configuration and ensure that stdout and stderr are kept flushed. 19 | # 20 | # @param rack_env [String] The rack environment, [development, test, production] 21 | def self.init_logging(rack_env) 22 | STDOUT.sync = true 23 | STDERR.sync = true 24 | rack_root = ENV['RACK_ROOT'] || "/data/apps/sinatra/reaktor" 25 | reaktor_log = ENV['REAKTOR_LOG'] || "#{rack_root}/reaktor.log" 26 | 27 | logger = Logger.new("#{reaktor_log}", Logger::DEBUG) 28 | logger.debug("in envconfig") 29 | 30 | Resque.logger = logger.clone 31 | 32 | case rack_env 33 | when 'development', 'test' 34 | Resque.logger.level = Logger::DEBUG 35 | logger.info("DEV/TEST: setting logger level to DEBUG") 36 | when 'production' 37 | Resque.logger.level = Logger::INFO 38 | logger.info("PRODUCTION: setting logger level to INFO") 39 | else 40 | raise ArgumentError, "Cannot init logging: unknown RACK_ENV #{rack_env}" 41 | end 42 | end 43 | 44 | # Initialize resque worker configuration 45 | # 46 | # @param rack_env [String] The rack environment, [development, test, production] 47 | def self.init_workers(rack_env) 48 | # Better to use the resque_workers.god script to manage the workers 49 | rack_root = ENV['RACK_ROOT'] || "/data/apps/sinatra/reaktor" 50 | reaktor_log = ENV['REAKTOR_LOG'] || "#{rack_root}/reaktor.log" 51 | case rack_env 52 | 53 | when 'development', 'test', 'production' 54 | system("TERM_CHILD=1 QUEUE=resque_create rake resque:work >> #{reaktor_log} &") 55 | system("TERM_CHILD=1 QUEUE=resque_modify rake resque:work >> #{reaktor_log} &") 56 | system("TERM_CHILD=1 QUEUE=resque_delete rake resque:work >> #{reaktor_log} &") 57 | 58 | else 59 | raise ArgumentError, "Cannot init resque workers: unknown RACK_ENV #{rack_env}" 60 | end 61 | end 62 | 63 | # Render the contents of config/database.yml as a hash. 64 | # 65 | # @param rack_env [String] The rack environment, usually one of 'production', 66 | # 'test', 'development' 67 | # 68 | # @return [Hash] The database configuration in the given environment 69 | def self.dbconfig(rack_env) 70 | YAML.load_file(('config/resque.yml')) 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/puppetfile_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'r10k/puppetfile' 3 | require 'fileutils' 4 | require 'tmpdir' 5 | 6 | describe Reaktor::R10K::Puppetfile do 7 | let(:logger) { double('logger').as_null_object } 8 | let(:branch1) { ('reaktor_test1') } 9 | let(:branch2) { ('reaktor_test2') } 10 | let(:branch3) { ('reaktor_test3') } 11 | let(:mod1) { ('testmod1') } 12 | let(:mod2) { ('testmod2') } 13 | let(:mod3) { ('testmod3') } 14 | let(:repo_name1) { ('testmod1') } 15 | let(:repo_name2) { ('myproject-testmod2') } 16 | let(:repo_name3) { ('testmod3') } 17 | 18 | let(:now) { Time.now.strftime('%Y%m%d%H%M%S%L') } 19 | #let(:puppetfile_orig) { File.new(read_fixture("Puppetfile")) } 20 | let(:puppetfile_orig) { File.new("spec/unit/fixtures/Puppetfile") } 21 | let(:git_work_dir) { Dir.mktmpdir('rspec') } 22 | 23 | before(:each) do 24 | FileUtils.cp("spec/unit/fixtures/Puppetfile", "#{git_work_dir}/Puppetfile") 25 | end 26 | 27 | after(:each) do 28 | FileUtils.remove_entry_secure git_work_dir 29 | end 30 | 31 | subject { described_class.new(branch1, mod1, logger) } 32 | 33 | it 'should retrieve testmod1 as module name' do 34 | subject.git_work_dir = git_work_dir 35 | contents = subject.get_module_name(repo_name1) 36 | expect(contents).to eq(mod1) 37 | end 38 | 39 | it 'should update mod testmod1 ref to be branch name' do 40 | subject.git_work_dir = git_work_dir 41 | contents = subject.update_module_ref(mod1, branch1) 42 | expect(contents).to include(branch1) 43 | end 44 | 45 | it 'should write mod testmod1 to Puppetfile correctly' do 46 | subject.git_work_dir = git_work_dir 47 | contents = subject.update_module_ref(mod1, branch1) 48 | subject.write_new_puppetfile(contents) 49 | expect(File.open("#{subject.git_work_dir}/Puppetfile", "r").read).to include("mod 'testmod1',") 50 | end 51 | 52 | it 'should retrieve testmod2 as module name' do 53 | subject.git_work_dir = git_work_dir 54 | contents = subject.get_module_name(repo_name2) 55 | expect(contents).to eq(mod2) 56 | end 57 | 58 | it 'should update mod testmod2 ref to be branch2 name' do 59 | subject.git_work_dir = git_work_dir 60 | contents = subject.update_module_ref(mod2, branch2) 61 | expect(contents).to include(branch2) 62 | end 63 | 64 | it 'should write mod testmod2 to Puppetfile correctly' do 65 | subject.git_work_dir = git_work_dir 66 | contents = subject.update_module_ref(mod2, branch2) 67 | subject.write_new_puppetfile(contents) 68 | expect(File.open("#{subject.git_work_dir}/Puppetfile", "r").read).to include("mod 'testmod2',") 69 | end 70 | 71 | it 'should not retrieve testmod3 without `ref` as module name' do 72 | subject.git_work_dir = git_work_dir 73 | contents = subject.get_module_name(repo_name3) 74 | expect(contents).to eq(nil) 75 | end 76 | 77 | it 'should have contents equal to nil if mod testmod3 doesn\'t have `ref`' do 78 | subject.git_work_dir = git_work_dir 79 | contents = subject.update_module_ref(mod3, branch3) 80 | expect(contents).to be_nil 81 | end 82 | 83 | it 'should not write empty Puppetfile if mod testmod3 doesnt have `ref`' do 84 | subject.git_work_dir = git_work_dir 85 | contents = subject.update_module_ref(mod3, branch3) 86 | subject.write_new_puppetfile(contents) 87 | expect(File.open("#{subject.git_work_dir}/Puppetfile", "r").read).to include("mod 'testmod3',") 88 | end 89 | 90 | end 91 | -------------------------------------------------------------------------------- /lib/reaktor/commandrunner.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | require 'open3' 3 | require 'notification/notifier' 4 | 5 | module Reaktor 6 | # class CommandRunner 7 | module CommandRunner 8 | 9 | # read each line from stream and html format the newlines, then send notification 10 | def read_stream(label, stream) 11 | @stdout_msg = "" 12 | @stderr_msg = "" 13 | begin 14 | while line = stream.gets 15 | if label.eql? 'STDOUT' 16 | @stdout_msg << "#{line}
" 17 | else 18 | @stderr_msg << "#{line}
" 19 | end 20 | end 21 | rescue Exception 22 | if label.eql? 'STDOUT' 23 | @stdout_msg << "Something went wrong with command: #{$!}" 24 | else 25 | @stderr_msg << "Something went wrong with command: #{$!}" 26 | end 27 | end 28 | @logger.debug("######### STDOUT Message size = #{@stdout_msg.length}") 29 | @logger.debug("######### STDERR Message size = #{@stderr_msg.length}") 30 | unless @stdout_msg.length < 1 31 | Notification::Notifier.instance.notification = @stdout_msg 32 | end 33 | unless @stderr_msg.length <1 34 | Notification::Notifier.instance.notification = @stderr_msg 35 | end 36 | end 37 | 38 | # read each line from capistrano stream and html format the newlines, then send notification 39 | def read_cap_stream(stream, action) 40 | @msg = "" 41 | begin 42 | while line = stream.gets 43 | @logger.debug("line: #{line}") 44 | if action.eql? "update_environment" 45 | if line.include? "WARN" or line.include? "Sync" or line.include? "failed" or line.include? "finished" 46 | @msg << "#{line}
" 47 | end 48 | else #action = deploy_module 49 | if line.include? "Sync" or line.include? "failed" or line.include? "finished" 50 | @msg << "#{line}
" 51 | end 52 | end 53 | end 54 | rescue Exception 55 | @msg << "Something went wrong with cap #{action}: #{$!}" 56 | end 57 | 58 | Notification::Notifier.instance.notification = @msg 59 | end 60 | 61 | # takes an array consisting of main command and options 62 | def execute(command) 63 | @logger ||= Logger.new(STDOUT, Logger::INFO) 64 | cmd = command.join(' ') 65 | @logger.info("cmdRunner.cmd = #{cmd}") 66 | @exit_status = nil 67 | Open3.popen3(cmd) do |stdin, stdout, stderr, thr| 68 | t1 = Thread.new { read_stream('STDOUT', stdout) } 69 | t2 = Thread.new { read_stream('STDERR', stderr) } 70 | t1.join 71 | t2.join 72 | @exit_status = thr.value 73 | @logger.info("cmdRunner.exit_status = #{@exit_status}") 74 | if @exit_status.exitstatus == 0 75 | @logger.info("stdout msg = #{@stdout_msg}") 76 | else 77 | @logger.info("stderr msg = #{@stderr_msg}") 78 | end 79 | end 80 | @exit_status 81 | end 82 | 83 | # takes an array consisting of main capistrano command and options 84 | def execute_cap(command) 85 | #cap update_environment -s branchname=dev_RSN_592 86 | @logger ||= Logger.new(STDOUT, Logger::INFO) 87 | @action = command[1] # either deploy_module or update_environment 88 | cmd = command.join(' ') 89 | @logger.info("cmdRunner.cmd = #{cmd}") 90 | @logger.info("cmdRunner action = #{@action}") 91 | @exit_status = nil 92 | Open3.popen3(cmd) do |stdin, stdout, stderr, thr| 93 | t1 = Thread.new { read_cap_stream(stderr, @action) } 94 | t1.join 95 | @cap_exit = thr.value 96 | @logger.info("cmdRunner.cap_exit = #{@cap_exit}") 97 | @logger.info("cmdRunner.cap_exit_status = #{@cap_exit.exitstatus}") 98 | #@logger.info("msg = #{@msg}") 99 | end 100 | @cap_exit 101 | end 102 | end 103 | end 104 | 105 | -------------------------------------------------------------------------------- /spec/unit/fixtures/created_stash.json: -------------------------------------------------------------------------------- 1 | { 2 | "changesets": { 3 | "isLastPage": true, 4 | "limit": 100, 5 | "size": 1, 6 | "start": 0, 7 | "values": [ 8 | { 9 | "changes": { 10 | "isLastPage": true, 11 | "limit": 100, 12 | "size": 1, 13 | "start": 0, 14 | "values": [ 15 | { 16 | "contentId": "abeedbff158b84f0bbedc2d146d799b2a59e9115", 17 | "executable": false, 18 | "link": { 19 | "rel": "self", 20 | "url": "/projects/PUP/repos/hooktest/commits/30f6aabc3f5024aadce4f301287fa4f6d84f185d#README.md" 21 | }, 22 | "links": { 23 | "self": [ 24 | { 25 | "href": "https://lxstshd0001/projects/PUP/repos/hooktest/commits/30f6aabc3f5024aadce4f301287fa4f6d84f185d#README.md" 26 | } 27 | ] 28 | }, 29 | "nodeType": "FILE", 30 | "path": { 31 | "components": [ 32 | "README.md" 33 | ], 34 | "extension": "md", 35 | "name": "README.md", 36 | "parent": "", 37 | "toString": "README.md" 38 | }, 39 | "percentUnchanged": -1, 40 | "srcExecutable": false, 41 | "type": "MODIFY" 42 | } 43 | ] 44 | }, 45 | "fromCommit": { 46 | "displayId": "0a6beef", 47 | "id": "0a6beeffd94f23d8bcd7870bb9baa12c38dec73b" 48 | }, 49 | "link": { 50 | "rel": "self", 51 | "url": "/projects/PUP/repos/hooktest/commits/30f6aabc3f5024aadce4f301287fa4f6d84f185d#README.md" 52 | }, 53 | "links": { 54 | "self": [ 55 | { 56 | "href": "https://lxstshd0001/projects/PUP/repos/hooktest/commits/30f6aabc3f5024aadce4f301287fa4f6d84f185d#README.md" 57 | } 58 | ] 59 | }, 60 | "toCommit": { 61 | "author": { 62 | "emailAddress": "jeffrey.quaintance@acme.com", 63 | "name": "Jeff Quaintance" 64 | }, 65 | "authorTimestamp": 1412616483000, 66 | "displayId": "30f6aab", 67 | "id": "30f6aabc3f5024aadce4f301287fa4f6d84f185d", 68 | "message": "test commit", 69 | "parents": [ 70 | { 71 | "displayId": "0a6beef", 72 | "id": "0a6beeffd94f23d8bcd7870bb9baa12c38dec73b" 73 | } 74 | ] 75 | } 76 | } 77 | ] 78 | }, 79 | "refChanges": [ 80 | { 81 | "fromHash": "0000000000000000000000000000000000000000", 82 | "refId": "refs/heads/dev_hook_test", 83 | "toHash": "30f6aabc3f5024aadce4f301287fa4f6d84f185d", 84 | "type": "UPDATE" 85 | } 86 | ], 87 | "repository": { 88 | "forkable": true, 89 | "id": 14, 90 | "name": "hooktest", 91 | "project": { 92 | "description": "Repositories for Puppet", 93 | "id": 21, 94 | "key": "PUP", 95 | "name": "Puppet", 96 | "public": true, 97 | "type": "NORMAL" 98 | }, 99 | "public": false, 100 | "scmId": "git", 101 | "slug": "hooktest", 102 | "state": "AVAILABLE", 103 | "statusMessage": "Available" 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /god/resque_workers.god: -------------------------------------------------------------------------------- 1 | rack_root = ENV['RACK_ROOT'] || "/data/apps/sinatra/reaktor" 2 | reaktor_log = ENV['REAKTOR_LOG'] || "#{rack_root}/reaktor.log" 3 | hipchat_token = ENV['REAKTOR_HIPCHAT_TOKEN'] 4 | hipchat_room = ENV['REAKTOR_HIPCHAT_ROOM'] 5 | worker_user = ENV['RESQUE_WORKER_USER'] || "jenkins" 6 | worker_group = ENV['RESQUE_WORKER_GROUP'] || "jenkins" 7 | num_create_workers = ENV['RESQUE_CREATE_WORKERS'] || 3 8 | num_modify_workers = ENV['RESQUE_MODIFY_WORKERS'] || 2 9 | num_delete_workers = ENV['RESQUE_DELETE_WORKERS'] || 2 10 | 11 | num_create_workers.times do |num| 12 | God.watch do |w| 13 | w.name = "resque_create-#{num}" 14 | w.dir = "#{rack_root}" 15 | w.group = 'resque' 16 | w.uid = "#{worker_user}" 17 | w.gid = "#{worker_group}" 18 | w.interval = 30.seconds 19 | w.start = "rake -f #{rack_root}/Rakefile TERM_CHILD=1 QUEUE=resque_create resque:work" 20 | w.log = "#{reaktor_log}" 21 | # clean pid files before start if necessary 22 | w.behavior(:clean_pid_file) 23 | 24 | # restart if memory gets too high 25 | w.transition(:up, :restart) do |on| 26 | on.condition(:memory_usage) do |c| 27 | c.above = 350.megabytes 28 | c.times = 2 29 | c.notify = 'hip_notify' 30 | end 31 | end 32 | 33 | # determine the state on startup 34 | w.transition(:init, { true => :up, false => :start }) do |on| 35 | on.condition(:process_running) do |c| 36 | c.running = true 37 | c.notify = 'hip_notify' 38 | end 39 | end 40 | 41 | # determine when process has finished starting 42 | w.transition([:start, :restart], :up) do |on| 43 | on.condition(:process_running) do |c| 44 | c.running = true 45 | c.interval = 5.seconds 46 | c.notify = 'hip_notify' 47 | end 48 | 49 | # failsafe 50 | on.condition(:tries) do |c| 51 | c.times = 5 52 | c.transition = :start 53 | c.interval = 5.seconds 54 | end 55 | end 56 | 57 | # start if process is not running 58 | w.transition(:up, :start) do |on| 59 | on.condition(:process_exits) do |c| 60 | c.notify = 'hip_notify' 61 | end 62 | end 63 | end 64 | end 65 | 66 | num_delete_workers.times do |num| 67 | God.watch do |w| 68 | w.name = "resque_delete-#{num}" 69 | w.dir = "#{rack_root}" 70 | w.group = 'resque' 71 | w.uid = "#{worker_user}" 72 | w.gid = "#{worker_group}" 73 | w.interval = 30.seconds 74 | w.start = "rake -f #{rack_root}/Rakefile TERM_CHILD=1 QUEUE=resque_delete resque:work" 75 | w.log = "#{reaktor_log}" 76 | # clean pid files before start if necessary 77 | w.behavior(:clean_pid_file) 78 | 79 | # restart if memory gets too high 80 | w.transition(:up, :restart) do |on| 81 | on.condition(:memory_usage) do |c| 82 | c.above = 350.megabytes 83 | c.times = 2 84 | c.notify = 'hip_notify' 85 | end 86 | end 87 | 88 | # determine the state on startup 89 | w.transition(:init, { true => :up, false => :start }) do |on| 90 | on.condition(:process_running) do |c| 91 | c.running = true 92 | c.notify = 'hip_notify' 93 | end 94 | end 95 | 96 | # determine when process has finished starting 97 | w.transition([:start, :restart], :up) do |on| 98 | on.condition(:process_running) do |c| 99 | c.running = true 100 | c.interval = 5.seconds 101 | c.notify = 'hip_notify' 102 | end 103 | 104 | # failsafe 105 | on.condition(:tries) do |c| 106 | c.times = 5 107 | c.transition = :start 108 | c.interval = 5.seconds 109 | end 110 | end 111 | 112 | # start if process is not running 113 | w.transition(:up, :start) do |on| 114 | on.condition(:process_exits) do |c| 115 | c.notify = 'hip_notify' 116 | end 117 | end 118 | end 119 | end 120 | num_modify_workers.times do |num| 121 | God.watch do |w| 122 | w.name = "resque_modify-#{num}" 123 | w.dir = "#{rack_root}" 124 | w.group = 'resque' 125 | w.uid = "#{worker_user}" 126 | w.gid = "#{worker_group}" 127 | w.interval = 30.seconds 128 | w.start = "rake -f #{rack_root}/Rakefile TERM_CHILD=1 QUEUE=resque_modify resque:work" 129 | w.log = "#{reaktor_log}" 130 | # clean pid files before start if necessary 131 | w.behavior(:clean_pid_file) 132 | 133 | # restart if memory gets too high 134 | w.transition(:up, :restart) do |on| 135 | on.condition(:memory_usage) do |c| 136 | c.above = 350.megabytes 137 | c.times = 2 138 | c.notify = 'hip_notify' 139 | end 140 | end 141 | 142 | # determine the state on startup 143 | w.transition(:init, { true => :up, false => :start }) do |on| 144 | on.condition(:process_running) do |c| 145 | c.running = true 146 | c.notify = 'hip_notify' 147 | end 148 | end 149 | 150 | # determine when process has finished starting 151 | w.transition([:start, :restart], :up) do |on| 152 | on.condition(:process_running) do |c| 153 | c.running = true 154 | c.interval = 5.seconds 155 | c.notify = 'hip_notify' 156 | end 157 | 158 | # failsafe 159 | on.condition(:tries) do |c| 160 | c.times = 5 161 | c.transition = :start 162 | c.interval = 5.seconds 163 | end 164 | end 165 | 166 | # start if process is not running 167 | w.transition(:up, :start) do |on| 168 | on.condition(:process_exits) do |c| 169 | c.notify = 'hip_notify' 170 | end 171 | end 172 | end 173 | end 174 | 175 | God::Contacts::Hipchat.defaults do |d| 176 | d.token = "#{hipchat_token}" 177 | d.room = "#{hipchat_room}" 178 | d.from = 'Hubot' 179 | end 180 | 181 | God.contact(:hipchat) do |d| 182 | d.name = 'hip_notify' 183 | end 184 | 185 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reaktor 2 | 3 | [![Build Status](https://travis-ci.org/timhughes/reaktor.svg?branch=master)](https://travis-ci.org/timhughes/reaktor) 4 | [![Coverage Status](https://coveralls.io/repos/timhughes/reaktor/badge.svg?branch=master&service=github)](https://coveralls.io/github/timhughes/reaktor?branch=master) 5 | 6 | [![Code Climate](https://codeclimate.com/github/timhughes/reaktor/badges/gpa.svg)](https://codeclimate.com/github/timhughes/reaktor) 7 | [![Test Coverage](https://codeclimate.com/github/timhughes/reaktor/badges/coverage.svg)](https://codeclimate.com/github/timhughes/reaktor/coverage) 8 | [![Issue Count](https://codeclimate.com/github/timhughes/reaktor/badges/issue_count.svg)](https://codeclimate.com/github/timhughes/reaktor) 9 | 10 | 11 | ## Description 12 | 13 | Reaktor is a modular post-receive hook designed to work with [r10k](https://github.com/adrienthebo/r10k). It provides the energy to power the 10,000 killer robots in your [Puppet](http://puppetlabs.com/) infrastructure. The goal of reaktor is to automate as much as possible from the time puppet code is pushed through the point at which that code is deployed to your puppet masters and you've been notified accordingly. In most circumstances, there is no longer a need to manually edit the Puppetfile and ssh into the puppet masters to run r10k. 14 | 15 | ## Deeper Dive 16 | 17 | Reaktor uses r10k to deploy your changes to all of your puppet masters and notifies you when it's finished so you know when your environment is ready. 18 | 19 | Reaktor not only supports [puppet dynamic environments (via r10k)](http://puppetlabs.com/blog/git-workflow-and-puppet-environments), but also allows for Puppetfile dynamic branch creation. It provides notifications to [hipchat](http://hipchat.com) by default, but notifications are pluggable to work with other chat providers/notification types, e.g., [campfire](https://campfirenow.com/) and [slack](https://slack.com/). The default configuration supports [git webhook](https://developer.github.com/webhooks/) payloads from GitHub and GitHub Enterprise. In addition, reaktor supports the following git sources: 20 | - [Stash](https://www.atlassian.com/software/stash) 21 | - [Gitlab](https://about.gitlab.com/) 22 | 23 | Reaktor utilizes [resque](https://github.com/resque/resque) to provide event processing necessary for efficient puppet development workflow. Resque provides its own sinatra app to help monitor the state of events in the system. 24 | 25 | ### Requirements 26 | 27 | - Ruby (tested and verified with 1.9.3) 28 | - Git 29 | - Redis (needed for resque to work properly) 30 | 31 | ### Installation 32 | 33 | git clone git://github.com/pzim/reaktor 34 | cd reaktor 35 | bundle install 36 | 37 | ### User Requirements 38 | 39 | The user you install and run reaktor as needs to have ssh trusts set up to the puppet masters. 40 | 41 | That same user must also have git commit privileges to your Github or Github Enterprise puppet module and puppetfile repositories. 42 | 43 | ### Starting the post-receive hook 44 | 45 | **ensure redis is installed and running** 46 | cd reaktor 47 | rake start (starts the post-receive hook and the resque workers) 48 | 49 | ``` 50 | [jenkins@test-box-01 reaktor]$ rake start 51 | Starting server on test-box-01:4570 ... 52 | ``` 53 | 54 | ### Environment Variables 55 | 56 | Reaktor makes use of the following environment variables for configuration: 57 | 58 | ##### REAKTOR_PUPPET_MASTERS_FILE (required) 59 | 60 | `export REAKTOR_PUPPET_MASTERS_FILE="/path/to/masters.txt"` 61 | 62 | Location of file containing all puppet masters. Each entry on a single line: 63 | 64 | ``` 65 | puppet-master-01 66 | puppet-master-02 67 | ... 68 | ``` 69 | 70 | ##### PUPPETFILE_GIT_URL (required) 71 | 72 | `export PUPPETFILE_GIT_URL="git@github.com:_org_/puppetfile.git"` 73 | 74 | ##### REAKTOR_HIPCHAT_TOKEN (required if using hipchat) 75 | 76 | auth token to enable posting hipchat messages. this cannot be a 'notification' token, as reaktor needs to be able to get a room list. 77 | 78 | ##### REAKTOR_HIPCHAT_ROOM (required if using hipchat) 79 | 80 | name of hipchat room to send reaktor/r10k output notifications 81 | 82 | ##### REAKTOR_HIPCHAT_FROM (required if using hipchat) 83 | 84 | user to send hipchat notifications as 85 | 86 | ##### REAKTOR_HIPCHAT_URL (required if using hipchat local server) 87 | 88 | full url of server v1 api. ie: 'https://hipchat.foo.bar/v1' 89 | 90 | ##### RESQUE_WORKER_USER (defaults to 'jenkins') 91 | 92 | user used to start resque processes 93 | 94 | ##### RESQUE_WORKER_GROUP (defaults to 'jenkins') 95 | 96 | group used to start resque processes 97 | 98 | ##### RACK_ROOT (defaults to '/data/apps/sinatra/reaktor') 99 | 100 | set this to the fully qualified path where you installed reaktor (temporary until code is modified to auto-discover base dir) 101 | 102 | ## Host and Port Configuration (for thin server) 103 | 104 | Host and Port configuration is handled in the [reaktor/reaktor-cfg.yml](https://github.com/pzim/reaktor/blob/master/reaktor-cfg.yml) file: 105 | 106 | - The 'address' key is where you configure the hostname 107 | - The 'port' key is where you configure what port to listen on 108 | 109 | These are the most important bits to configure, as they help make up the url for the webhook setting in your git repo config. For example: 110 | 111 | - address: myserver-01.puppet.com 112 | - port: 4500 113 | 114 | The resultant url (assuming you are using GitHub or GitHub Enterprise): 115 | 116 | - http://myserver-01.puppet.com:4500/github_payload 117 | 118 | If you are using Atlassian Stash: 119 | 120 | - http://myserver-01.puppet.com:4500/stash_payload 121 | 122 | If you are using Gitlab: 123 | 124 | - http://myserver-01.puppet.com:4500/gitlab_payload 125 | 126 | This is the url you would configure in the GitHub ServiceHooks Webhook URL for each internal puppet module. 127 | 128 | The reaktor/reaktor-cfg.yml has additional items that you can configure, including pidfile and log. 129 | 130 | 131 | ## Pluggable Notifications 132 | The default IM tool for receiving reaktor notifications is [hipchat](http://hipchat.com). By setting the appropriate HIPCHAT-related environment variables above, you will receive hipchat notifications automatically. 133 | 134 | If you use a different IM tool, such as campfire or slack, you will need to implement the notifier accordingly. This is fairly straightforward. There are 2 directories under reaktor/lib/reaktor/notification: 135 | 136 | - active_notifiers (holds currently active notifiers) 137 | - available_notifiers (holds potential notifiers, but these aren't live) 138 | 139 | In order to implement a custom notifier do the following: 140 | 141 | - create the .rb file for your notifier and place it under the active_notifiers dir 142 | - use the hipchat.rb as a reference, replacing 'class Hipchat' with an appropriate name, such as 'class Slack' (there is a dummy slack.rb file in available_notifiers as well) 143 | - the new .rb file must implement the **_update_** method (again, use hipchat.rb as a reference) 144 | - remove hipchat.rb from the active_notifiers dir 145 | - restart the post-receive hook (rake stop; rake start) 146 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | DisabledByDefault: true 3 | 4 | #################### Lint ################################ 5 | 6 | Lint/AmbiguousOperator: 7 | Description: >- 8 | Checks for ambiguous operators in the first argument of a 9 | method invocation without parentheses. 10 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-as-args' 11 | Enabled: true 12 | 13 | Lint/AmbiguousRegexpLiteral: 14 | Description: >- 15 | Checks for ambiguous regexp literals in the first argument of 16 | a method invocation without parenthesis. 17 | Enabled: true 18 | 19 | Lint/AssignmentInCondition: 20 | Description: "Don't use assignment in conditions." 21 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition' 22 | Enabled: true 23 | 24 | Lint/BlockAlignment: 25 | Description: 'Align block ends correctly.' 26 | Enabled: true 27 | 28 | Lint/CircularArgumentReference: 29 | Description: "Don't refer to the keyword argument in the default value." 30 | Enabled: true 31 | 32 | Lint/ConditionPosition: 33 | Description: >- 34 | Checks for condition placed in a confusing position relative to 35 | the keyword. 36 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#same-line-condition' 37 | Enabled: true 38 | 39 | Lint/Debugger: 40 | Description: 'Check for debugger calls.' 41 | Enabled: true 42 | 43 | Lint/DefEndAlignment: 44 | Description: 'Align ends corresponding to defs correctly.' 45 | Enabled: true 46 | 47 | Lint/DeprecatedClassMethods: 48 | Description: 'Check for deprecated class method calls.' 49 | Enabled: true 50 | 51 | Lint/DuplicateMethods: 52 | Description: 'Check for duplicate methods calls.' 53 | Enabled: true 54 | 55 | Lint/EachWithObjectArgument: 56 | Description: 'Check for immutable argument given to each_with_object.' 57 | Enabled: true 58 | 59 | Lint/ElseLayout: 60 | Description: 'Check for odd code arrangement in an else block.' 61 | Enabled: true 62 | 63 | Lint/EmptyEnsure: 64 | Description: 'Checks for empty ensure block.' 65 | Enabled: true 66 | 67 | Lint/EmptyInterpolation: 68 | Description: 'Checks for empty string interpolation.' 69 | Enabled: true 70 | 71 | Lint/EndAlignment: 72 | Description: 'Align ends correctly.' 73 | Enabled: true 74 | 75 | Lint/EndInMethod: 76 | Description: 'END blocks should not be placed inside method definitions.' 77 | Enabled: true 78 | 79 | Lint/EnsureReturn: 80 | Description: 'Do not use return in an ensure block.' 81 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-return-ensure' 82 | Enabled: true 83 | 84 | Lint/Eval: 85 | Description: 'The use of eval represents a serious security risk.' 86 | Enabled: true 87 | 88 | Lint/FormatParameterMismatch: 89 | Description: 'The number of parameters to format/sprint must match the fields.' 90 | Enabled: true 91 | 92 | Lint/HandleExceptions: 93 | Description: "Don't suppress exception." 94 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions' 95 | Enabled: true 96 | 97 | Lint/InvalidCharacterLiteral: 98 | Description: >- 99 | Checks for invalid character literals with a non-escaped 100 | whitespace character. 101 | Enabled: true 102 | 103 | Lint/LiteralInCondition: 104 | Description: 'Checks of literals used in conditions.' 105 | Enabled: true 106 | 107 | Lint/LiteralInInterpolation: 108 | Description: 'Checks for literals used in interpolation.' 109 | Enabled: true 110 | 111 | Lint/Loop: 112 | Description: >- 113 | Use Kernel#loop with break rather than begin/end/until or 114 | begin/end/while for post-loop tests. 115 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#loop-with-break' 116 | Enabled: true 117 | 118 | Lint/NestedMethodDefinition: 119 | Description: 'Do not use nested method definitions.' 120 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-methods' 121 | Enabled: true 122 | 123 | Lint/NonLocalExitFromIterator: 124 | Description: 'Do not use return in iterator to cause non-local exit.' 125 | Enabled: true 126 | 127 | Lint/ParenthesesAsGroupedExpression: 128 | Description: >- 129 | Checks for method calls with a space before the opening 130 | parenthesis. 131 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces' 132 | Enabled: true 133 | 134 | Lint/RequireParentheses: 135 | Description: >- 136 | Use parentheses in the method call to avoid confusion 137 | about precedence. 138 | Enabled: true 139 | 140 | Lint/RescueException: 141 | Description: 'Avoid rescuing the Exception class.' 142 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-blind-rescues' 143 | Enabled: true 144 | 145 | Lint/ShadowingOuterLocalVariable: 146 | Description: >- 147 | Do not use the same name as outer local variable 148 | for block arguments or block local variables. 149 | Enabled: true 150 | 151 | Lint/SpaceBeforeFirstArg: 152 | Description: >- 153 | Put a space between a method name and the first argument 154 | in a method call without parentheses. 155 | Enabled: true 156 | 157 | Lint/StringConversionInInterpolation: 158 | Description: 'Checks for Object#to_s usage in string interpolation.' 159 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-to-s' 160 | Enabled: true 161 | 162 | Lint/UnderscorePrefixedVariableName: 163 | Description: 'Do not use prefix `_` for a variable that is used.' 164 | Enabled: true 165 | 166 | Lint/UnneededDisable: 167 | Description: >- 168 | Checks for rubocop:disable comments that can be removed. 169 | Note: this cop is not disabled when disabling all cops. 170 | It must be explicitly disabled. 171 | Enabled: true 172 | 173 | Lint/UnusedBlockArgument: 174 | Description: 'Checks for unused block arguments.' 175 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' 176 | Enabled: true 177 | 178 | Lint/UnusedMethodArgument: 179 | Description: 'Checks for unused method arguments.' 180 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' 181 | Enabled: true 182 | 183 | Lint/UnreachableCode: 184 | Description: 'Unreachable code.' 185 | Enabled: true 186 | 187 | Lint/UselessAccessModifier: 188 | Description: 'Checks for useless access modifiers.' 189 | Enabled: true 190 | 191 | Lint/UselessAssignment: 192 | Description: 'Checks for useless assignment to a local variable.' 193 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' 194 | Enabled: true 195 | 196 | Lint/UselessComparison: 197 | Description: 'Checks for comparison of something with itself.' 198 | Enabled: true 199 | 200 | Lint/UselessElseWithoutRescue: 201 | Description: 'Checks for useless `else` in `begin..end` without `rescue`.' 202 | Enabled: true 203 | 204 | Lint/UselessSetterCall: 205 | Description: 'Checks for useless setter call to a local variable.' 206 | Enabled: true 207 | 208 | Lint/Void: 209 | Description: 'Possible use of operator/literal/variable in void context.' 210 | Enabled: true 211 | 212 | ###################### Metrics #################################### 213 | 214 | Metrics/AbcSize: 215 | Description: >- 216 | A calculated magnitude based on number of assignments, 217 | branches, and conditions. 218 | Reference: 'http://c2.com/cgi/wiki?AbcMetric' 219 | Enabled: false 220 | Max: 20 221 | 222 | Metrics/BlockNesting: 223 | Description: 'Avoid excessive block nesting' 224 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count' 225 | Enabled: true 226 | Max: 4 227 | 228 | Metrics/ClassLength: 229 | Description: 'Avoid classes longer than 250 lines of code.' 230 | Enabled: true 231 | Max: 250 232 | 233 | Metrics/CyclomaticComplexity: 234 | Description: >- 235 | A complexity metric that is strongly correlated to the number 236 | of test cases needed to validate a method. 237 | Enabled: true 238 | 239 | Metrics/LineLength: 240 | Description: 'Limit lines to 80 characters.' 241 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits' 242 | Enabled: false 243 | 244 | Metrics/MethodLength: 245 | Description: 'Avoid methods longer than 30 lines of code.' 246 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods' 247 | Enabled: true 248 | Max: 30 249 | 250 | Metrics/ModuleLength: 251 | Description: 'Avoid modules longer than 250 lines of code.' 252 | Enabled: true 253 | Max: 250 254 | 255 | Metrics/ParameterLists: 256 | Description: 'Avoid parameter lists longer than three or four parameters.' 257 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params' 258 | Enabled: true 259 | 260 | Metrics/PerceivedComplexity: 261 | Description: >- 262 | A complexity metric geared towards measuring complexity for a 263 | human reader. 264 | Enabled: false 265 | 266 | ##################### Performance ############################# 267 | 268 | Performance/Count: 269 | Description: >- 270 | Use `count` instead of `select...size`, `reject...size`, 271 | `select...count`, `reject...count`, `select...length`, 272 | and `reject...length`. 273 | Enabled: true 274 | 275 | Performance/Detect: 276 | Description: >- 277 | Use `detect` instead of `select.first`, `find_all.first`, 278 | `select.last`, and `find_all.last`. 279 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerabledetect-vs-enumerableselectfirst-code' 280 | Enabled: true 281 | 282 | Performance/FlatMap: 283 | Description: >- 284 | Use `Enumerable#flat_map` 285 | instead of `Enumerable#map...Array#flatten(1)` 286 | or `Enumberable#collect..Array#flatten(1)` 287 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablemaparrayflatten-vs-enumerableflat_map-code' 288 | Enabled: true 289 | EnabledForFlattenWithoutParams: false 290 | # If enabled, this cop will warn about usages of 291 | # `flatten` being called without any parameters. 292 | # This can be dangerous since `flat_map` will only flatten 1 level, and 293 | # `flatten` without any parameters can flatten multiple levels. 294 | 295 | Performance/ReverseEach: 296 | Description: 'Use `reverse_each` instead of `reverse.each`.' 297 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablereverseeach-vs-enumerablereverse_each-code' 298 | Enabled: true 299 | 300 | Performance/Sample: 301 | Description: >- 302 | Use `sample` instead of `shuffle.first`, 303 | `shuffle.last`, and `shuffle[Fixnum]`. 304 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#arrayshufflefirst-vs-arraysample-code' 305 | Enabled: true 306 | 307 | Performance/Size: 308 | Description: >- 309 | Use `size` instead of `count` for counting 310 | the number of elements in `Array` and `Hash`. 311 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#arraycount-vs-arraysize-code' 312 | Enabled: true 313 | 314 | Performance/StringReplacement: 315 | Description: >- 316 | Use `tr` instead of `gsub` when you are replacing the same 317 | number of characters. Use `delete` instead of `gsub` when 318 | you are deleting characters. 319 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#stringgsub-vs-stringtr-code' 320 | Enabled: true 321 | 322 | ##################### Rails ################################## 323 | 324 | Rails/ActionFilter: 325 | Description: 'Enforces consistent use of action filter methods.' 326 | Enabled: false 327 | 328 | Rails/Date: 329 | Description: >- 330 | Checks the correct usage of date aware methods, 331 | such as Date.today, Date.current etc. 332 | Enabled: false 333 | 334 | Rails/DefaultScope: 335 | Description: 'Checks if the argument passed to default_scope is a block.' 336 | Enabled: false 337 | 338 | Rails/Delegate: 339 | Description: 'Prefer delegate method for delegations.' 340 | Enabled: false 341 | 342 | Rails/FindBy: 343 | Description: 'Prefer find_by over where.first.' 344 | Enabled: false 345 | 346 | Rails/FindEach: 347 | Description: 'Prefer all.find_each over all.find.' 348 | Enabled: false 349 | 350 | Rails/HasAndBelongsToMany: 351 | Description: 'Prefer has_many :through to has_and_belongs_to_many.' 352 | Enabled: false 353 | 354 | Rails/Output: 355 | Description: 'Checks for calls to puts, print, etc.' 356 | Enabled: false 357 | 358 | Rails/ReadWriteAttribute: 359 | Description: >- 360 | Checks for read_attribute(:attr) and 361 | write_attribute(:attr, val). 362 | Enabled: false 363 | 364 | Rails/ScopeArgs: 365 | Description: 'Checks the arguments of ActiveRecord scopes.' 366 | Enabled: false 367 | 368 | Rails/TimeZone: 369 | Description: 'Checks the correct usage of time zone aware methods.' 370 | StyleGuide: 'https://github.com/bbatsov/rails-style-guide#time' 371 | Reference: 'http://danilenko.org/2012/7/6/rails_timezones' 372 | Enabled: false 373 | 374 | Rails/Validation: 375 | Description: 'Use validates :attribute, hash of validations.' 376 | Enabled: false 377 | 378 | ################## Style ################################# 379 | 380 | Style/AccessModifierIndentation: 381 | Description: Check indentation of private/protected visibility modifiers. 382 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-public-private-protected' 383 | Enabled: false 384 | 385 | Style/AccessorMethodName: 386 | Description: Check the naming of accessor methods for get_/set_. 387 | Enabled: false 388 | 389 | Style/Alias: 390 | Description: 'Use alias_method instead of alias.' 391 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#alias-method' 392 | Enabled: false 393 | 394 | Style/AlignArray: 395 | Description: >- 396 | Align the elements of an array literal if they span more than 397 | one line. 398 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#align-multiline-arrays' 399 | Enabled: false 400 | 401 | Style/AlignHash: 402 | Description: >- 403 | Align the elements of a hash literal if they span more than 404 | one line. 405 | Enabled: false 406 | 407 | Style/AlignParameters: 408 | Description: >- 409 | Align the parameters of a method call if they span more 410 | than one line. 411 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent' 412 | Enabled: false 413 | 414 | Style/AndOr: 415 | Description: 'Use &&/|| instead of and/or.' 416 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-and-or-or' 417 | Enabled: false 418 | 419 | Style/ArrayJoin: 420 | Description: 'Use Array#join instead of Array#*.' 421 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#array-join' 422 | Enabled: false 423 | 424 | Style/AsciiComments: 425 | Description: 'Use only ascii symbols in comments.' 426 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-comments' 427 | Enabled: false 428 | 429 | Style/AsciiIdentifiers: 430 | Description: 'Use only ascii symbols in identifiers.' 431 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-identifiers' 432 | Enabled: false 433 | 434 | Style/Attr: 435 | Description: 'Checks for uses of Module#attr.' 436 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr' 437 | Enabled: false 438 | 439 | Style/BeginBlock: 440 | Description: 'Avoid the use of BEGIN blocks.' 441 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-BEGIN-blocks' 442 | Enabled: false 443 | 444 | Style/BarePercentLiterals: 445 | Description: 'Checks if usage of %() or %Q() matches configuration.' 446 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q-shorthand' 447 | Enabled: false 448 | 449 | Style/BlockComments: 450 | Description: 'Do not use block comments.' 451 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-block-comments' 452 | Enabled: false 453 | 454 | Style/BlockEndNewline: 455 | Description: 'Put end statement of multiline block on its own line.' 456 | Enabled: false 457 | 458 | Style/BlockDelimiters: 459 | Description: >- 460 | Avoid using {...} for multi-line blocks (multiline chaining is 461 | always ugly). 462 | Prefer {...} over do...end for single-line blocks. 463 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' 464 | Enabled: false 465 | 466 | Style/BracesAroundHashParameters: 467 | Description: 'Enforce braces style around hash parameters.' 468 | Enabled: false 469 | 470 | Style/CaseEquality: 471 | Description: 'Avoid explicit use of the case equality operator(===).' 472 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-case-equality' 473 | Enabled: false 474 | 475 | Style/CaseIndentation: 476 | Description: 'Indentation of when in a case/when/[else/]end.' 477 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-when-to-case' 478 | Enabled: false 479 | 480 | Style/CharacterLiteral: 481 | Description: 'Checks for uses of character literals.' 482 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-character-literals' 483 | Enabled: false 484 | 485 | Style/ClassAndModuleCamelCase: 486 | Description: 'Use CamelCase for classes and modules.' 487 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#camelcase-classes' 488 | Enabled: false 489 | 490 | Style/ClassAndModuleChildren: 491 | Description: 'Checks style of children classes and modules.' 492 | Enabled: false 493 | 494 | Style/ClassCheck: 495 | Description: 'Enforces consistent use of `Object#is_a?` or `Object#kind_of?`.' 496 | Enabled: false 497 | 498 | Style/ClassMethods: 499 | Description: 'Use self when defining module/class methods.' 500 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#def-self-class-methods' 501 | Enabled: false 502 | 503 | Style/ClassVars: 504 | Description: 'Avoid the use of class variables.' 505 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-class-vars' 506 | Enabled: false 507 | 508 | Style/ClosingParenthesisIndentation: 509 | Description: 'Checks the indentation of hanging closing parentheses.' 510 | Enabled: false 511 | 512 | Style/ColonMethodCall: 513 | Description: 'Do not use :: for method call.' 514 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#double-colons' 515 | Enabled: false 516 | 517 | Style/CommandLiteral: 518 | Description: 'Use `` or %x around command literals.' 519 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-x' 520 | Enabled: false 521 | 522 | Style/CommentAnnotation: 523 | Description: >- 524 | Checks formatting of special comments 525 | (TODO, FIXME, OPTIMIZE, HACK, REVIEW). 526 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#annotate-keywords' 527 | Enabled: false 528 | 529 | Style/CommentIndentation: 530 | Description: 'Indentation of comments.' 531 | Enabled: false 532 | 533 | Style/ConstantName: 534 | Description: 'Constants should use SCREAMING_SNAKE_CASE.' 535 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#screaming-snake-case' 536 | Enabled: false 537 | 538 | Style/DefWithParentheses: 539 | Description: 'Use def with parentheses when there are arguments.' 540 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens' 541 | Enabled: false 542 | 543 | Style/DeprecatedHashMethods: 544 | Description: 'Checks for use of deprecated Hash methods.' 545 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-key' 546 | Enabled: false 547 | 548 | Style/Documentation: 549 | Description: 'Document classes and non-namespace modules.' 550 | Enabled: false 551 | 552 | Style/DotPosition: 553 | Description: 'Checks the position of the dot in multi-line method calls.' 554 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains' 555 | Enabled: false 556 | 557 | Style/DoubleNegation: 558 | Description: 'Checks for uses of double negation (!!).' 559 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-bang-bang' 560 | Enabled: false 561 | 562 | Style/EachWithObject: 563 | Description: 'Prefer `each_with_object` over `inject` or `reduce`.' 564 | Enabled: false 565 | 566 | Style/ElseAlignment: 567 | Description: 'Align elses and elsifs correctly.' 568 | Enabled: false 569 | 570 | Style/EmptyElse: 571 | Description: 'Avoid empty else-clauses.' 572 | Enabled: false 573 | 574 | Style/EmptyLineBetweenDefs: 575 | Description: 'Use empty lines between defs.' 576 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#empty-lines-between-methods' 577 | Enabled: false 578 | 579 | Style/EmptyLines: 580 | Description: "Don't use several empty lines in a row." 581 | Enabled: false 582 | 583 | Style/EmptyLinesAroundAccessModifier: 584 | Description: "Keep blank lines around access modifiers." 585 | Enabled: false 586 | 587 | Style/EmptyLinesAroundBlockBody: 588 | Description: "Keeps track of empty lines around block bodies." 589 | Enabled: false 590 | 591 | Style/EmptyLinesAroundClassBody: 592 | Description: "Keeps track of empty lines around class bodies." 593 | Enabled: false 594 | 595 | Style/EmptyLinesAroundModuleBody: 596 | Description: "Keeps track of empty lines around module bodies." 597 | Enabled: false 598 | 599 | Style/EmptyLinesAroundMethodBody: 600 | Description: "Keeps track of empty lines around method bodies." 601 | Enabled: false 602 | 603 | Style/EmptyLiteral: 604 | Description: 'Prefer literals to Array.new/Hash.new/String.new.' 605 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#literal-array-hash' 606 | Enabled: false 607 | 608 | Style/EndBlock: 609 | Description: 'Avoid the use of END blocks.' 610 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-END-blocks' 611 | Enabled: false 612 | 613 | Style/EndOfLine: 614 | Description: 'Use Unix-style line endings.' 615 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#crlf' 616 | Enabled: false 617 | 618 | Style/EvenOdd: 619 | Description: 'Favor the use of Fixnum#even? && Fixnum#odd?' 620 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' 621 | Enabled: false 622 | 623 | Style/ExtraSpacing: 624 | Description: 'Do not use unnecessary spacing.' 625 | Enabled: false 626 | 627 | Style/FileName: 628 | Description: 'Use snake_case for source file names.' 629 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files' 630 | Enabled: false 631 | 632 | Style/InitialIndentation: 633 | Description: >- 634 | Checks the indentation of the first non-blank non-comment line in a file. 635 | Enabled: false 636 | 637 | Style/FirstParameterIndentation: 638 | Description: 'Checks the indentation of the first parameter in a method call.' 639 | Enabled: false 640 | 641 | Style/FlipFlop: 642 | Description: 'Checks for flip flops' 643 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops' 644 | Enabled: false 645 | 646 | Style/For: 647 | Description: 'Checks use of for or each in multiline loops.' 648 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-for-loops' 649 | Enabled: false 650 | 651 | Style/FormatString: 652 | Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.' 653 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#sprintf' 654 | Enabled: false 655 | 656 | Style/GlobalVars: 657 | Description: 'Do not introduce global variables.' 658 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#instance-vars' 659 | Reference: 'http://www.zenspider.com/Languages/Ruby/QuickRef.html' 660 | Enabled: false 661 | 662 | Style/GuardClause: 663 | Description: 'Check for conditionals that can be replaced with guard clauses' 664 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals' 665 | Enabled: false 666 | 667 | Style/HashSyntax: 668 | Description: >- 669 | Prefer Ruby 1.9 hash syntax { a: 1, b: 2 } over 1.8 syntax 670 | { :a => 1, :b => 2 }. 671 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-literals' 672 | Enabled: false 673 | 674 | Style/IfUnlessModifier: 675 | Description: >- 676 | Favor modifier if/unless usage when you have a 677 | single-line body. 678 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier' 679 | Enabled: false 680 | 681 | Style/IfWithSemicolon: 682 | Description: 'Do not use if x; .... Use the ternary operator instead.' 683 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon-ifs' 684 | Enabled: false 685 | 686 | Style/IndentationConsistency: 687 | Description: 'Keep indentation straight.' 688 | Enabled: false 689 | 690 | Style/IndentationWidth: 691 | Description: 'Use 2 spaces for indentation.' 692 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation' 693 | Enabled: false 694 | 695 | Style/IndentArray: 696 | Description: >- 697 | Checks the indentation of the first element in an array 698 | literal. 699 | Enabled: false 700 | 701 | Style/IndentHash: 702 | Description: 'Checks the indentation of the first key in a hash literal.' 703 | Enabled: false 704 | 705 | Style/InfiniteLoop: 706 | Description: 'Use Kernel#loop for infinite loops.' 707 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#infinite-loop' 708 | Enabled: false 709 | 710 | Style/Lambda: 711 | Description: 'Use the new lambda literal syntax for single-line blocks.' 712 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#lambda-multi-line' 713 | Enabled: false 714 | 715 | Style/LambdaCall: 716 | Description: 'Use lambda.call(...) instead of lambda.(...).' 717 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc-call' 718 | Enabled: false 719 | 720 | Style/LeadingCommentSpace: 721 | Description: 'Comments should start with a space.' 722 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-space' 723 | Enabled: false 724 | 725 | Style/LineEndConcatenation: 726 | Description: >- 727 | Use \ instead of + or << to concatenate two string literals at 728 | line end. 729 | Enabled: false 730 | 731 | Style/MethodCallParentheses: 732 | Description: 'Do not use parentheses for method calls with no arguments.' 733 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-args-no-parens' 734 | Enabled: false 735 | 736 | Style/MethodDefParentheses: 737 | Description: >- 738 | Checks if the method definitions have or don't have 739 | parentheses. 740 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens' 741 | Enabled: false 742 | 743 | Style/MethodName: 744 | Description: 'Use the configured style when naming methods.' 745 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars' 746 | Enabled: false 747 | 748 | Style/ModuleFunction: 749 | Description: 'Checks for usage of `extend self` in modules.' 750 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#module-function' 751 | Enabled: false 752 | 753 | Style/MultilineBlockChain: 754 | Description: 'Avoid multi-line chains of blocks.' 755 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' 756 | Enabled: false 757 | 758 | Style/MultilineBlockLayout: 759 | Description: 'Ensures newlines after multiline block do statements.' 760 | Enabled: false 761 | 762 | Style/MultilineIfThen: 763 | Description: 'Do not use then for multi-line if/unless.' 764 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-then' 765 | Enabled: false 766 | 767 | Style/MultilineOperationIndentation: 768 | Description: >- 769 | Checks indentation of binary operations that span more than 770 | one line. 771 | Enabled: false 772 | 773 | Style/MultilineTernaryOperator: 774 | Description: >- 775 | Avoid multi-line ?: (the ternary operator); 776 | use if/unless instead. 777 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-ternary' 778 | Enabled: false 779 | 780 | Style/NegatedIf: 781 | Description: >- 782 | Favor unless over if for negative conditions 783 | (or control flow or). 784 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#unless-for-negatives' 785 | Enabled: false 786 | 787 | Style/NegatedWhile: 788 | Description: 'Favor until over while for negative conditions.' 789 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#until-for-negatives' 790 | Enabled: false 791 | 792 | Style/NestedTernaryOperator: 793 | Description: 'Use one expression per branch in a ternary operator.' 794 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-ternary' 795 | Enabled: false 796 | 797 | Style/Next: 798 | Description: 'Use `next` to skip iteration instead of a condition at the end.' 799 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals' 800 | Enabled: false 801 | 802 | Style/NilComparison: 803 | Description: 'Prefer x.nil? to x == nil.' 804 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' 805 | Enabled: false 806 | 807 | Style/NonNilCheck: 808 | Description: 'Checks for redundant nil checks.' 809 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-non-nil-checks' 810 | Enabled: false 811 | 812 | Style/Not: 813 | Description: 'Use ! instead of not.' 814 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bang-not-not' 815 | Enabled: false 816 | 817 | Style/NumericLiterals: 818 | Description: >- 819 | Add underscores to large numeric literals to improve their 820 | readability. 821 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics' 822 | Enabled: false 823 | 824 | Style/OneLineConditional: 825 | Description: >- 826 | Favor the ternary operator(?:) over 827 | if/then/else/end constructs. 828 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#ternary-operator' 829 | Enabled: false 830 | 831 | Style/OpMethod: 832 | Description: 'When defining binary operators, name the argument other.' 833 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg' 834 | Enabled: false 835 | 836 | Style/OptionalArguments: 837 | Description: >- 838 | Checks for optional arguments that do not appear at the end 839 | of the argument list 840 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#optional-arguments' 841 | Enabled: false 842 | 843 | Style/ParallelAssignment: 844 | Description: >- 845 | Check for simple usages of parallel assignment. 846 | It will only warn when the number of variables 847 | matches on both sides of the assignment. 848 | This also provides performance benefits 849 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parallel-assignment' 850 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#parallel-assignment-vs-sequential-assignment-code' 851 | Enabled: false 852 | 853 | Style/ParenthesesAroundCondition: 854 | Description: >- 855 | Don't use parentheses around the condition of an 856 | if/unless/while. 857 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-parens-if' 858 | Enabled: false 859 | 860 | Style/PercentLiteralDelimiters: 861 | Description: 'Use `%`-literal delimiters consistently' 862 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-literal-braces' 863 | Enabled: false 864 | 865 | Style/PercentQLiterals: 866 | Description: 'Checks if uses of %Q/%q match the configured preference.' 867 | Enabled: false 868 | 869 | Style/PerlBackrefs: 870 | Description: 'Avoid Perl-style regex back references.' 871 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers' 872 | Enabled: false 873 | 874 | Style/PredicateName: 875 | Description: 'Check the names of predicate methods.' 876 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark' 877 | Enabled: false 878 | 879 | Style/Proc: 880 | Description: 'Use proc instead of Proc.new.' 881 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc' 882 | Enabled: false 883 | 884 | Style/RaiseArgs: 885 | Description: 'Checks the arguments passed to raise/fail.' 886 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#exception-class-messages' 887 | Enabled: false 888 | 889 | Style/RedundantBegin: 890 | Description: "Don't use begin blocks when they are not needed." 891 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#begin-implicit' 892 | Enabled: false 893 | 894 | Style/RedundantException: 895 | Description: "Checks for an obsolete RuntimeException argument in raise/fail." 896 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-runtimeerror' 897 | Enabled: false 898 | 899 | Style/RedundantReturn: 900 | Description: "Don't use return where it's not required." 901 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-return' 902 | Enabled: false 903 | 904 | Style/RedundantSelf: 905 | Description: "Don't use self where it's not needed." 906 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-self-unless-required' 907 | Enabled: false 908 | 909 | Style/RegexpLiteral: 910 | Description: 'Use / or %r around regular expressions.' 911 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-r' 912 | Enabled: false 913 | 914 | Style/RescueEnsureAlignment: 915 | Description: 'Align rescues and ensures correctly.' 916 | Enabled: false 917 | 918 | Style/RescueModifier: 919 | Description: 'Avoid using rescue in its modifier form.' 920 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-rescue-modifiers' 921 | Enabled: false 922 | 923 | Style/SelfAssignment: 924 | Description: >- 925 | Checks for places where self-assignment shorthand should have 926 | been used. 927 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#self-assignment' 928 | Enabled: false 929 | 930 | Style/Semicolon: 931 | Description: "Don't use semicolons to terminate expressions." 932 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon' 933 | Enabled: false 934 | 935 | Style/SignalException: 936 | Description: 'Checks for proper usage of fail and raise.' 937 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#fail-method' 938 | Enabled: false 939 | 940 | Style/SingleLineBlockParams: 941 | Description: 'Enforces the names of some block params.' 942 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#reduce-blocks' 943 | Enabled: false 944 | 945 | Style/SingleLineMethods: 946 | Description: 'Avoid single-line methods.' 947 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-single-line-methods' 948 | Enabled: false 949 | 950 | Style/SingleSpaceBeforeFirstArg: 951 | Description: >- 952 | Checks that exactly one space is used between a method name 953 | and the first argument for method calls without parentheses. 954 | Enabled: false 955 | 956 | Style/SpaceAfterColon: 957 | Description: 'Use spaces after colons.' 958 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 959 | Enabled: false 960 | 961 | Style/SpaceAfterComma: 962 | Description: 'Use spaces after commas.' 963 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 964 | Enabled: false 965 | 966 | Style/SpaceAfterControlKeyword: 967 | Description: 'Use spaces after if/elsif/unless/while/until/case/when.' 968 | Enabled: false 969 | 970 | Style/SpaceAfterMethodName: 971 | Description: >- 972 | Do not put a space between a method name and the opening 973 | parenthesis in a method definition. 974 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces' 975 | Enabled: false 976 | 977 | Style/SpaceAfterNot: 978 | Description: Tracks redundant space after the ! operator. 979 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-bang' 980 | Enabled: false 981 | 982 | Style/SpaceAfterSemicolon: 983 | Description: 'Use spaces after semicolons.' 984 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 985 | Enabled: false 986 | 987 | Style/SpaceBeforeBlockBraces: 988 | Description: >- 989 | Checks that the left block brace has or doesn't have space 990 | before it. 991 | Enabled: false 992 | 993 | Style/SpaceBeforeComma: 994 | Description: 'No spaces before commas.' 995 | Enabled: false 996 | 997 | Style/SpaceBeforeComment: 998 | Description: >- 999 | Checks for missing space between code and a comment on the 1000 | same line. 1001 | Enabled: false 1002 | 1003 | Style/SpaceBeforeSemicolon: 1004 | Description: 'No spaces before semicolons.' 1005 | Enabled: false 1006 | 1007 | Style/SpaceInsideBlockBraces: 1008 | Description: >- 1009 | Checks that block braces have or don't have surrounding space. 1010 | For blocks taking parameters, checks that the left brace has 1011 | or doesn't have trailing space. 1012 | Enabled: false 1013 | 1014 | Style/SpaceAroundBlockParameters: 1015 | Description: 'Checks the spacing inside and after block parameters pipes.' 1016 | Enabled: false 1017 | 1018 | Style/SpaceAroundEqualsInParameterDefault: 1019 | Description: >- 1020 | Checks that the equals signs in parameter default assignments 1021 | have or don't have surrounding space depending on 1022 | configuration. 1023 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-around-equals' 1024 | Enabled: false 1025 | 1026 | Style/SpaceAroundOperators: 1027 | Description: 'Use a single space around operators.' 1028 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 1029 | Enabled: false 1030 | 1031 | Style/SpaceBeforeModifierKeyword: 1032 | Description: 'Put a space before the modifier keyword.' 1033 | Enabled: false 1034 | 1035 | Style/SpaceInsideBrackets: 1036 | Description: 'No spaces after [ or before ].' 1037 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces' 1038 | Enabled: false 1039 | 1040 | Style/SpaceInsideHashLiteralBraces: 1041 | Description: "Use spaces inside hash literal braces - or don't." 1042 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 1043 | Enabled: false 1044 | 1045 | Style/SpaceInsideParens: 1046 | Description: 'No spaces after ( or before ).' 1047 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces' 1048 | Enabled: false 1049 | 1050 | Style/SpaceInsideRangeLiteral: 1051 | Description: 'No spaces inside range literals.' 1052 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-inside-range-literals' 1053 | Enabled: false 1054 | 1055 | Style/SpaceInsideStringInterpolation: 1056 | Description: 'Checks for padding/surrounding spaces inside string interpolation.' 1057 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#string-interpolation' 1058 | Enabled: false 1059 | 1060 | Style/SpecialGlobalVars: 1061 | Description: 'Avoid Perl-style global variables.' 1062 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms' 1063 | Enabled: false 1064 | 1065 | Style/StringLiterals: 1066 | Description: 'Checks if uses of quotes match the configured preference.' 1067 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-string-literals' 1068 | Enabled: false 1069 | 1070 | Style/StringLiteralsInInterpolation: 1071 | Description: >- 1072 | Checks if uses of quotes inside expressions in interpolated 1073 | strings match the configured preference. 1074 | Enabled: false 1075 | 1076 | Style/StructInheritance: 1077 | Description: 'Checks for inheritance from Struct.new.' 1078 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-extend-struct-new' 1079 | Enabled: false 1080 | 1081 | Style/SymbolLiteral: 1082 | Description: 'Use plain symbols instead of string symbols when possible.' 1083 | Enabled: false 1084 | 1085 | Style/SymbolProc: 1086 | Description: 'Use symbols as procs instead of blocks when possible.' 1087 | Enabled: false 1088 | 1089 | Style/Tab: 1090 | Description: 'No hard tabs.' 1091 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation' 1092 | Enabled: false 1093 | 1094 | Style/TrailingBlankLines: 1095 | Description: 'Checks trailing blank lines and final newline.' 1096 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#newline-eof' 1097 | Enabled: false 1098 | 1099 | Style/TrailingComma: 1100 | Description: 'Checks for trailing comma in parameter lists and literals.' 1101 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' 1102 | Enabled: false 1103 | 1104 | Style/TrailingWhitespace: 1105 | Description: 'Avoid trailing whitespace.' 1106 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-whitespace' 1107 | Enabled: false 1108 | 1109 | Style/TrivialAccessors: 1110 | Description: 'Prefer attr_* methods to trivial readers/writers.' 1111 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr_family' 1112 | Enabled: false 1113 | 1114 | Style/UnlessElse: 1115 | Description: >- 1116 | Do not use unless with else. Rewrite these with the positive 1117 | case first. 1118 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-else-with-unless' 1119 | Enabled: false 1120 | 1121 | Style/UnneededCapitalW: 1122 | Description: 'Checks for %W when interpolation is not needed.' 1123 | Enabled: false 1124 | 1125 | Style/UnneededPercentQ: 1126 | Description: 'Checks for %q/%Q when single quotes or double quotes would do.' 1127 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q' 1128 | Enabled: false 1129 | 1130 | Style/TrailingUnderscoreVariable: 1131 | Description: >- 1132 | Checks for the usage of unneeded trailing underscores at the 1133 | end of parallel variable assignment. 1134 | Enabled: false 1135 | 1136 | Style/VariableInterpolation: 1137 | Description: >- 1138 | Don't interpolate global, instance and class variables 1139 | directly in strings. 1140 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#curlies-interpolate' 1141 | Enabled: false 1142 | 1143 | Style/VariableName: 1144 | Description: 'Use the configured style when naming variables.' 1145 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars' 1146 | Enabled: false 1147 | 1148 | Style/WhenThen: 1149 | Description: 'Use when x then ... for one-line cases.' 1150 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#one-line-cases' 1151 | Enabled: false 1152 | 1153 | Style/WhileUntilDo: 1154 | Description: 'Checks for redundant do after while or until.' 1155 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-while-do' 1156 | Enabled: false 1157 | 1158 | Style/WhileUntilModifier: 1159 | Description: >- 1160 | Favor modifier while/until usage when you have a 1161 | single-line body. 1162 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier' 1163 | Enabled: false 1164 | 1165 | Style/WordArray: 1166 | Description: 'Use %w or %W for arrays of words.' 1167 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-w' 1168 | Enabled: false 1169 | --------------------------------------------------------------------------------